diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 8347d048a..000000000 --- a/.flake8 +++ /dev/null @@ -1,34 +0,0 @@ -# Configuration file for flake 8. This is used by "make lint" and by the -# GIT commit hook script. - -[flake8] -ignore = - # line break after binary operator - W504, - - # --- flake8-bugbear plugin - # Loop control variable 'keyword' not used within the loop body. If this is intended, start the name with an underscore. - B007, - # Redundant exception types in `except (IOError, OSError) as err:`. Write `except OSError as err:`, which catches exactly the same exceptions. - B014, - # Do not perform function calls in argument defaults. - B008, - - # --- flake8-blind-except plugin - # blind except Exception: statement - B902, - - # --- flake8-quotes plugin - # Double quotes found but single quotes preferred - Q000, - - # --- flake8-quotes naming; disable all except N804 and N805 - N801, N802, N803, N806, N807, N811, N812, N813, N814, N815, N816, N817, N818 - -per-file-ignores = - # T001, T201 = print() statement (flake8-print plugin) - setup.py:T001,T201 - scripts/*:T001,T201 - psutil/tests/runner.py:T001,T201 - psutil/tests/test_memleaks.py:T001,T201 - .github/workflows/*:T001,T201 diff --git a/.github/workflows/bsd.yml b/.github/workflows/bsd.yml index 9c811d183..efae0fc9b 100644 --- a/.github/workflows/bsd.yml +++ b/.github/workflows/bsd.yml @@ -13,7 +13,7 @@ jobs: freebsd: runs-on: macos-12 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run tests uses: vmactions/freebsd-vm@v0 with: @@ -30,7 +30,7 @@ jobs: openbsd: runs-on: macos-12 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run tests uses: vmactions/openbsd-vm@v0 with: @@ -48,7 +48,7 @@ jobs: netbsd: runs-on: macos-12 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run tests uses: vmactions/netbsd-vm@v0 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f03b1982..e95cee695 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,6 @@ # * https://github.com/actions/checkout # * https://github.com/actions/setup-python # * https://github.com/actions/upload-artifact -# * https://github.com/marketplace/actions/cancel-workflow-action on: pull_request: @@ -21,6 +20,9 @@ on: permissions: contents: write name: build +concurrency: + group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && github.sha || '' }} + cancel-in-progress: true jobs: tag: # additionally create a release for every commit to master @@ -47,12 +49,7 @@ jobs: - {os: windows-2019, archs: "AMD64 x86"} steps: - - name: Cancel previous runs - uses: styfle/cancel-workflow-action@0.9.1 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: 3.11 @@ -62,7 +59,7 @@ jobs: if: matrix.archs == 'aarch64' - name: Create wheels + run tests - uses: pypa/cibuildwheel@v2.11.2 + uses: pypa/cibuildwheel@v2.16.2 with: config-file: "./cibuildwheel.toml" env: @@ -107,12 +104,7 @@ jobs: CIBW_BUILD: 'cp27-*' steps: - - name: Cancel previous runs - uses: styfle/cancel-workflow-action@0.9.1 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: 3.9 @@ -163,25 +155,21 @@ jobs: linters: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: 3.x - name: 'Run linters' run: | - # py3 - python3 -m pip install flake8 isort - python3 -m flake8 . - python3 -m isort . - # clinter - find . -type f \( -iname "*.c" -o -iname "*.h" \) | xargs python3 scripts/internal/clinter.py + python3 -m pip install ruff rstcheck toml-sort sphinx + make lint-all # Check sanity of .tar.gz + wheel files check-dist: needs: [py2, py3] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: 3.x diff --git a/.github/workflows/issues.py b/.github/workflows/issues.py old mode 100644 new mode 100755 index 77a9bc96b..b89def6ff --- a/.github/workflows/issues.py +++ b/.github/workflows/issues.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Bot triggered by Github Actions every time a new issue, PR or comment +"""Bot triggered by Github Actions every time a new issue, PR or comment is created. Assign labels, provide replies, closes issues, etc. depending on the situation. """ @@ -144,10 +143,7 @@ def has_label(issue, label): def has_os_label(issue): labels = set([x.name for x in issue.labels]) - for label in OS_LABELS: - if label in labels: - return True - return False + return any(x in labels for x in OS_LABELS) def get_repo(): diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index 245d00cb4..3d3adbf83 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: # install python - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Python uses: actions/setup-python@v4 with: diff --git a/HISTORY.rst b/HISTORY.rst index f3bcba9d0..b9891788f 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,9 +1,9 @@ *Bug tracker at https://github.com/giampaolo/psutil/issues* -5.9.6 (IN DEVELOPMENT) -====================== +5.9.6 +===== -XXXX-XX-XX +2023-10-15 **Enhancements** @@ -15,24 +15,38 @@ XXXX-XX-XX - 2266_: if `Process`_ class is passed a very high PID, raise `NoSuchProcess`_ instead of OverflowError. (patch by Xuehai Pan) - 2246_: drop python 3.4 & 3.5 support. (patch by Matthieu Darbois) +- 2290_: PID reuse is now pre-emptively checked for `Process.ppid()`_ and + `Process.parents()`_. +- 2312_: use ``ruff`` Python linter instead of ``flake8 + isort``. It's an + order of magnitude faster + it adds a ton of new code quality checks. **Bug fixes** +- 2195_, [Linux]: no longer print exception at import time in case /proc/stat + can't be read due to permission error. Redirect it to ``PSUTIL_DEBUG`` + instead. - 2241_, [NetBSD]: can't compile On NetBSD 10.99.3/amd64. (patch by Thomas Klausner) - 2245_, [Windows]: fix var unbound error on possibly in `swap_memory()`_ (patch by student_2333) - 2268_: ``bytes2human()`` utility function was unable to properly represent negative values. -- 2252_, [Windows]: `psutil.disk_usage`_ fails on Python 3.12+. (patch by +- 2252_, [Windows]: `disk_usage()`_ fails on Python 3.12+. (patch by Matthieu Darbois) -- 2284_, [Linux]: `memory_full_info`_ may incorrectly raise `ZombieProcess`_ - if it's determined via ``/proc/pid/smaps_rollup``. Instead we now fallback on - reading ``/proc/pid/smaps``. +- 2284_, [Linux]: `Process.memory_full_info()`_ may incorrectly raise + `ZombieProcess`_ if it's determined via ``/proc/pid/smaps_rollup``. Instead + we now fallback on reading ``/proc/pid/smaps``. - 2287_, [OpenBSD], [NetBSD]: `Process.is_running()`_ erroneously return ``False`` for zombie processes, because creation time cannot be determined. -- 2288_, [Linux]: correctly raise `ZombieProcess`_ on `exe`_, `cmdline`_ and - `memory_maps`_ instead of returning a "null" value. +- 2288_, [Linux]: correctly raise `ZombieProcess`_ on `Process.exe()`_, + `Process.cmdline()`_ and `Process.memory_maps()`_ instead of returning a + "null" value. +- 2290_: differently from what stated in the doc, PID reuse is not + pre-emptively checked for `Process.nice()`_ (set), `Process.ionice()`_, + (set), `Process.cpu_affinity()`_ (set), `Process.rlimit()`_ + (set), `Process.parent()`_. +- 2308_, [OpenBSD]: `Process.threads()`_ always fail with AccessDenied (also as + root). 5.9.5 ===== @@ -45,10 +59,10 @@ XXXX-XX-XX `KeyError` bit deriving from a missed cache hit. - 2217_: print the full traceback when a `DeprecationWarning` or `UserWarning` is raised. -- 2230_, [OpenBSD]: `psutil.net_connections`_ implementation was rewritten from - scratch: +- 2230_, [OpenBSD]: `net_connections()`_ implementation was rewritten + from scratch: - We're now able to retrieve the path of AF_UNIX sockets (before it was an - empty string) + empty string) - The function is faster since it no longer iterates over all processes. - No longer produces duplicate connection entries. - 2238_: there are cases where `Process.cwd()`_ cannot be determined @@ -64,7 +78,7 @@ XXXX-XX-XX **Bug fixes** -- 1043_, [OpenBSD] `psutil.net_connections`_ returns duplicate entries. +- 1043_, [OpenBSD] `net_connections()`_ returns duplicate entries. - 1915_, [Linux]: on certain kernels, ``"MemAvailable"`` field from ``/proc/meminfo`` returns ``0`` (possibly a kernel bug), in which case we calculate an approximation for ``available`` memory which matches "free" @@ -122,7 +136,7 @@ XXXX-XX-XX **Bug fixes** -- 2116_, [macOS], [critical]: `psutil.net_connections`_ fails with RuntimeError. +- 2116_, [macOS], [critical]: `net_connections()`_ fails with RuntimeError. - 2135_, [macOS]: `Process.environ()`_ may contain garbage data. Fix out-of-bounds read around ``sysctl_procargs``. (patch by Bernhard Urban-Forster) - 2138_, [Linux], **[critical]**: can't compile psutil on Android due to @@ -1569,7 +1583,7 @@ XXXX-XX-XX - 564_: C extension version mismatch in case the user messed up with psutil installation or with sys.path is now detected at import time. - 568_: new `pidof.py`_ script. -- 569_, [FreeBSD]: add support for `Process.cpu_affinity`_ on FreeBSD. +- 569_, [FreeBSD]: add support for `Process.cpu_affinity()`_ on FreeBSD. **Bug fixes** @@ -1581,7 +1595,7 @@ XXXX-XX-XX (patch by spacewander) - 565_, [Windows]: use proper encoding for `Process.username()`_ and `users()`_. (patch by Sylvain Mouquet) -- 567_, [Linux]: in the alternative implementation of `Process.cpu_affinity`_ +- 567_, [Linux]: in the alternative implementation of `Process.cpu_affinity()`_ ``PyList_Append`` and ``Py_BuildValue`` return values are not checked. - 569_, [FreeBSD]: fix memory leak in `cpu_count()`_ with ``logical=False``. - 571_, [Linux]: `Process.open_files()`_ might swallow `AccessDenied`_ @@ -2163,7 +2177,8 @@ In most cases accessing the old names will work but it will cause a representation. - 283_: speedup `Process.is_running()`_ by caching its return value in case the process is terminated. -- 284_, [POSIX]: per-process number of opened file descriptors (`Process.num_fds`_). +- 284_, [POSIX]: per-process number of opened file descriptors + (`Process.num_fds()`_). - 287_: `process_iter()`_ now caches `Process`_ instances between calls. - 290_: `Process.nice()`_ property is deprecated in favor of new ``get_nice()`` and ``set_nice()`` methods. @@ -2521,8 +2536,6 @@ In most cases accessing the old names will work but it will cause a .. _`Process`: https://psutil.readthedocs.io/en/latest/#psutil.Process .. _`psutil.Popen`: https://psutil.readthedocs.io/en/latest/#psutil.Popen -.. _`psutil.Process`: https://psutil.readthedocs.io/en/latest/#psutil.Process - .. _`AccessDenied`: https://psutil.readthedocs.io/en/latest/#psutil.AccessDenied .. _`NoSuchProcess`: https://psutil.readthedocs.io/en/latest/#psutil.NoSuchProcess @@ -2580,7 +2593,6 @@ In most cases accessing the old names will work but it will cause a .. _`cpu_distribution.py`: https://github.com/giampaolo/psutil/blob/master/scripts/cpu_distribution.py .. _`disk_usage.py`: https://github.com/giampaolo/psutil/blob/master/scripts/disk_usage.py .. _`free.py`: https://github.com/giampaolo/psutil/blob/master/scripts/free.py -.. _`ifconfig.py`: https://github.com/giampaolo/psutil/blob/master/scripts/ifconfig.py .. _`iotop.py`: https://github.com/giampaolo/psutil/blob/master/scripts/iotop.py .. _`meminfo.py`: https://github.com/giampaolo/psutil/blob/master/scripts/meminfo.py .. _`netstat.py`: https://github.com/giampaolo/psutil/blob/master/scripts/netstat.py diff --git a/MANIFEST.in b/MANIFEST.in index 8defe7177..fb25643af 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ -include .flake8 include .gitignore include CONTRIBUTING.md include CREDITS @@ -8,6 +7,7 @@ include LICENSE include MANIFEST.in include Makefile include README.rst +include docs/.readthedocs.yaml include docs/DEVGUIDE.rst include docs/DEVNOTES include docs/Makefile @@ -19,6 +19,7 @@ include docs/_static/sidebar.js include docs/conf.py include docs/index.rst include docs/make.bat +include docs/requirements.txt include make.bat include psutil/__init__.py include psutil/_common.py diff --git a/Makefile b/Makefile index c6b002a60..862c8b6c4 100644 --- a/Makefile +++ b/Makefile @@ -9,26 +9,18 @@ TSCRIPT = psutil/tests/runner.py # Internal. PY3_DEPS = \ - autoflake \ - autopep8 \ check-manifest \ concurrencytest \ coverage \ - flake8 \ - flake8-blind-except \ - flake8-bugbear \ - flake8-debugger \ - flake8-print \ - flake8-quotes \ - isort \ - pep8-naming \ pylint \ pyperf \ pypinfo \ requests \ + rstcheck \ setuptools \ sphinx_rtd_theme \ teyit \ + toml-sort \ twine \ virtualenv \ wheel @@ -80,6 +72,7 @@ clean: ## Remove all build files. .coverage \ .failed-tests.txt \ .pytest_cache \ + .ruff_cache/ \ build/ \ dist/ \ docs/_build/ \ @@ -198,41 +191,44 @@ test-coverage: ## Run test coverage. # Linters # =================================================================== -flake8: ## Run flake8 linter. - @git ls-files '*.py' | xargs $(PYTHON) -m flake8 --config=.flake8 --jobs=${NUM_WORKERS} +ruff: ## Run ruff linter. + @git ls-files '*.py' | xargs $(PYTHON) -m ruff check --config=pyproject.toml --no-cache -isort: ## Run isort linter. - @git ls-files '*.py' | xargs $(PYTHON) -m isort --check-only --jobs=${NUM_WORKERS} - -pylint: ## Python pylint (not mandatory, just run it from time to time) +_pylint: ## Python pylint (not mandatory, just run it from time to time) @git ls-files '*.py' | xargs $(PYTHON) -m pylint --rcfile=pyproject.toml --jobs=${NUM_WORKERS} -c-linter: ## Run C linter. +lint-c: ## Run C linter. @git ls-files '*.c' '*.h' | xargs $(PYTHON) scripts/internal/clinter.py +lint-rst: ## Run C linter. + @git ls-files '*.rst' | xargs rstcheck --config=pyproject.toml + +lint-toml: ## Linter for pyproject.toml + @git ls-files '*.toml' | xargs toml-sort --check + lint-all: ## Run all linters - ${MAKE} flake8 - ${MAKE} isort - ${MAKE} c-linter + ${MAKE} ruff + ${MAKE} lint-c + ${MAKE} lint-rst + ${MAKE} lint-toml # =================================================================== # Fixers # =================================================================== -fix-flake8: ## Run autopep8, fix some Python flake8 / pep8 issues. - @git ls-files '*.py' | xargs $(PYTHON) -m autopep8 --in-place --jobs=${NUM_WORKERS} --global-config=.flake8 - @git ls-files '*.py' | xargs $(PYTHON) -m autoflake --in-place --jobs=${NUM_WORKERS} --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys - -fix-imports: ## Fix imports with isort. - @git ls-files '*.py' | xargs $(PYTHON) -m isort --jobs=${NUM_WORKERS} +fix-ruff: + @git ls-files '*.py' | xargs $(PYTHON) -m ruff --config=pyproject.toml --no-cache --fix fix-unittests: ## Fix unittest idioms. @git ls-files '*test_*.py' | xargs $(PYTHON) -m teyit --show-stats +fix-toml: ## Fix pyproject.toml + @git ls-files '*.toml' | xargs toml-sort + fix-all: ## Run all code fixers. - ${MAKE} fix-flake8 - ${MAKE} fix-imports + ${MAKE} fix-ruff ${MAKE} fix-unittests + ${MAKE} fix-toml # =================================================================== # GIT diff --git a/README.rst b/README.rst index ce78d8433..05378dd11 100644 --- a/README.rst +++ b/README.rst @@ -169,6 +169,8 @@ Supporters + + add your avatar @@ -333,6 +335,8 @@ Process management >>> p = psutil.Process(7055) >>> p psutil.Process(pid=7055, name='python3', status='running', started='09:04:44') + >>> p.pid + 7055 >>> p.name() 'python3' >>> p.exe() @@ -340,32 +344,29 @@ Process management >>> p.cwd() '/home/giampaolo' >>> p.cmdline() - ['/usr/bin/python', 'main.py'] + ['/usr/bin/python3', 'main.py'] >>> - >>> p.pid - 7055 >>> p.ppid() 7054 - >>> p.children(recursive=True) - [psutil.Process(pid=29835, name='python3', status='sleeping', started='11:45:38'), - psutil.Process(pid=29836, name='python3', status='waking', started='11:43:39')] - >>> >>> p.parent() psutil.Process(pid=4699, name='bash', status='sleeping', started='09:06:44') >>> p.parents() [psutil.Process(pid=4699, name='bash', started='09:06:44'), psutil.Process(pid=4689, name='gnome-terminal-server', status='sleeping', started='0:06:44'), psutil.Process(pid=1, name='systemd', status='sleeping', started='05:56:55')] + >>> p.children(recursive=True) + [psutil.Process(pid=29835, name='python3', status='sleeping', started='11:45:38'), + psutil.Process(pid=29836, name='python3', status='waking', started='11:43:39')] >>> >>> p.status() 'running' - >>> p.username() - 'giampaolo' >>> p.create_time() 1267551141.5019531 >>> p.terminal() '/dev/pts/0' >>> + >>> p.username() + 'giampaolo' >>> p.uids() puids(real=1000, effective=1000, saved=1000) >>> p.gids() @@ -405,14 +406,14 @@ Process management [pconn(fd=115, family=, type=, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED'), pconn(fd=117, family=, type=, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING')] >>> - >>> p.num_threads() - 4 - >>> p.num_fds() - 8 >>> p.threads() [pthread(id=5234, user_time=22.5, system_time=9.2891), pthread(id=5237, user_time=0.0707, system_time=1.1)] >>> + >>> p.num_threads() + 4 + >>> p.num_fds() + 8 >>> p.num_ctx_switches() pctxsw(voluntary=78, involuntary=19) >>> diff --git a/docs/.readthedocs.yaml b/docs/.readthedocs.yaml new file mode 100644 index 000000000..44ffd9687 --- /dev/null +++ b/docs/.readthedocs.yaml @@ -0,0 +1,20 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# RTD recommends specifying your dependencies to enable reproducible builds: +# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt diff --git a/docs/DEVGUIDE.rst b/docs/DEVGUIDE.rst index 384f8b25e..a53235dab 100644 --- a/docs/DEVGUIDE.rst +++ b/docs/DEVGUIDE.rst @@ -7,13 +7,12 @@ Build, setup and running tests psutil makes extensive use of C extension modules, meaning a C compiler is required, see `install instructions `__. - -Once you have a compiler installed: +Once you have a compiler installed run: .. code-block:: bash git clone git@github.com:giampaolo/psutil.git - make setup-dev-env # install useful dev libs (flake8, coverage, ...) + make setup-dev-env # install useful dev libs (ruff, coverage, ...) make build make install make test @@ -28,11 +27,19 @@ Once you have a compiler installed: make test-parallel # faster make test-memleaks make test-coverage - make lint # Python (PEP8) and C linters + make lint-all # Run Python and C linter + make fix-all # Fix linting erors make uninstall make help -- if you're working on a new feature and you wish to compile & test it "on the + +- To run a specific unit test: + +.. code-block:: bash + + make test ARGS=psutil.tests.test_system.TestDiskAPIs + +- If you're working on a new feature and you wish to compile & test it "on the fly" from a test script, this is a quick & dirty way to do it: .. code-block:: bash @@ -40,10 +47,10 @@ Once you have a compiler installed: make test TSCRIPT=test_script.py # on UNIX make test test_script.py # on Windows -- do not use ``sudo``. ``make install`` installs psutil as a limited user in +- Do not use ``sudo``. ``make install`` installs psutil as a limited user in "edit" mode, meaning you can edit psutil code on the fly while you develop. -- if you want to target a specific Python version: +- If you want to target a specific Python version: .. code-block:: bash @@ -53,7 +60,7 @@ Once you have a compiler installed: Coding style ------------ -- python code strictly follows `PEP-8`_ styling guides and this is enforced by +- Oython code strictly follows `PEP-8`_ styling guides and this is enforced by a commit GIT hook installed via ``make install-git-hooks`` which will reject commits if code is not PEP-8 complieant. - C code should follow `PEP-7`_ styling guides. @@ -74,63 +81,48 @@ Adding a new API Typically, this is what you do: -- define the new API in `psutil/__init__.py`_. -- write the platform specific implementation in ``psutil/_ps{platform}.py`` +- Define the new API in `psutil/__init__.py`_. +- Write the platform specific implementation in ``psutil/_ps{platform}.py`` (e.g. `psutil/_pslinux.py`_). -- if the change requires C code, write the C implementation in +- If the change requires C code, write the C implementation in ``psutil/_psutil_{platform}.c`` (e.g. `psutil/_psutil_linux.c`_). -- write a generic test in `psutil/tests/test_system.py`_ or +- Write a generic test in `psutil/tests/test_system.py`_ or `psutil/tests/test_process.py`_. -- if possible, write a platform-specific test in +- If possible, write a platform-specific test in ``psutil/tests/test_{platform}.py`` (e.g. `psutil/tests/test_linux.py`_). This usually means testing the return value of the new API against a system CLI tool. -- update the doc in ``doc/index.py``. -- update `HISTORY.rst`_ and `CREDITS`_ files. -- make a pull request. +- Update the doc in ``docs/index.py``. +- Update `HISTORY.rst`_ and `CREDITS`_ files. +- Make a pull request. Make a pull request ------------------- -- fork psutil (go to https://github.com/giampaolo/psutil and click on "fork") -- git clone the fork locally: ``git clone git@github.com:YOUR-USERNAME/psutil.git`` -- create a branch:``git checkout -b new-feature`` -- commit your changes: ``git commit -am 'add some feature'`` -- push the branch: ``git push origin new-feature`` -- create a new PR via the GitHub web interface and sign-off your work (see +- Fork psutil (go to https://github.com/giampaolo/psutil and click on "fork") +- Git clone the fork locally: ``git clone git@github.com:YOUR-USERNAME/psutil.git`` +- Create a branch:``git checkout -b new-feature`` +- Commit your changes: ``git commit -am 'add some feature'`` +- Push the branch: ``git push origin new-feature`` +- Create a new PR via the GitHub web interface and sign-off your work (see `CONTRIBUTING.md`_ guidelines) Continuous integration ---------------------- Unit tests are automatically run on every ``git push`` on **Linux**, **macOS**, -**Windows** and **FreeBSD** by using: - -- `Github Actions`_ (Linux, macOS, Windows) -- `Appveyor`_ (Windows) - -.. image:: https://img.shields.io/github/workflow/status/giampaolo/psutil/CI?label=Linux%2C%20macOS%2C%20FreeBSD - :target: https://github.com/giampaolo/psutil/actions?query=workflow%3ACI - -.. image:: https://img.shields.io/appveyor/ci/giampaolo/psutil/master.svg?maxAge=3600&label=Windows - :target: https://ci.appveyor.com/project/giampaolo/psutil - -OpenBSD, NetBSD, AIX and Solaris does not have continuous test integration. +**Windows**, **FreeBSD**, **NetBSD**, **OpenBSD**. +AIX and Solaris does not have continuous test integration. Documentation ------------- -- doc source code is written in a single file: `/docs/index.rst`_. +- doc source code is written in a single file: ``docs/index.rst``. - doc can be built with ``make setup-dev-env; cd docs; make html``. -- public doc is hosted at https://psutil.readthedocs.io +- public doc is hosted at https://psutil.readthedocs.io. -.. _`appveyor.yml`: https://github.com/giampaolo/psutil/blob/master/appveyor.yml -.. _`Appveyor`: https://ci.appveyor.com/project/giampaolo/psuti -.. _`coveralls.io`: https://coveralls.io/github/giampaolo/psuti .. _`CREDITS`: https://github.com/giampaolo/psutil/blob/master/CREDITS .. _`CONTRIBUTING.md`: https://github.com/giampaolo/psutil/blob/master/CONTRIBUTING.md -.. _`doc/index.rst`: https://github.com/giampaolo/psutil/blob/master/doc/index.rst -.. _`Github Actions`: https://github.com/giampaolo/psutil/actions .. _`HISTORY.rst`: https://github.com/giampaolo/psutil/blob/master/HISTORY.rst .. _`make.bat`: https://github.com/giampaolo/psutil/blob/master/make.bat .. _`Makefile`: https://github.com/giampaolo/psutil/blob/master/Makefile @@ -142,5 +134,3 @@ Documentation .. _`psutil/tests/test_linux.py`: https://github.com/giampaolo/psutil/blob/master/psutil/tests/test_linux.py .. _`psutil/tests/test_process.py`: https://github.com/giampaolo/psutil/blob/master/psutil/tests/test_process.py .. _`psutil/tests/test_system.py`: https://github.com/giampaolo/psutil/blob/master/psutil/tests/test_system.py -.. _`RsT syntax`: http://docutils.sourceforge.net/docs/user/rst/quickref.htm -.. _`sphinx`: http://sphinx-doc.org diff --git a/docs/conf.py b/docs/conf.py index f0de77723..9e0434706 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,22 +22,23 @@ # -- General configuration ------------------------------------------------ +import ast import datetime import os PROJECT_NAME = "psutil" -AUTHOR = u"Giampaolo Rodola" +AUTHOR = "Giampaolo Rodola" THIS_YEAR = str(datetime.datetime.now().year) HERE = os.path.abspath(os.path.dirname(__file__)) def get_version(): INIT = os.path.abspath(os.path.join(HERE, '../psutil/__init__.py')) - with open(INIT, 'r') as f: + with open(INIT) as f: for line in f: if line.startswith('__version__'): - ret = eval(line.strip().split(' = ')[1]) + ret = ast.literal_eval(line.strip().split(' = ')[1]) assert ret.count('.') == 2, ret for num in ret.split('.'): assert num.isdigit(), ret @@ -288,7 +289,7 @@ def get_version(): # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'psutil.tex', u'psutil Documentation', + (master_doc, 'psutil.tex', 'psutil Documentation', AUTHOR, 'manual'), ] @@ -314,7 +315,7 @@ def get_version(): # # latex_appendices = [] -# It false, will not define \strong, \code, itleref, \crossref ... but only +# It false, will not define \strong, \code, itleref, \crossref ... but only # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added # packages. # @@ -330,7 +331,7 @@ def get_version(): # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'psutil', u'psutil Documentation', + (master_doc, 'psutil', 'psutil Documentation', [author], 1) ] @@ -345,7 +346,7 @@ def get_version(): # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'psutil', u'psutil Documentation', + (master_doc, 'psutil', 'psutil Documentation', author, 'psutil', 'One line description of project.', 'Miscellaneous'), ] diff --git a/docs/index.rst b/docs/index.rst index dbd9ab7bb..0e782ebc7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -90,6 +90,8 @@ Supporters + +
add your avatar @@ -1079,6 +1081,7 @@ Process class :meth:`cpu_affinity` (set), :meth:`rlimit` (set), :meth:`children`, + :meth:`ppid`, :meth:`parent`, :meth:`parents`, :meth:`suspend` @@ -2580,7 +2583,7 @@ FAQs the Python script as a Windows service (ProcessHacker does this). * Q: is MinGW supported on Windows? -* A: no, you should Visual Studio (see `development guide`_). +* A: no, you should Visual Studio (see `development guide `_). Running tests ============= @@ -2622,7 +2625,7 @@ contact`_. Tidelift will coordinate the fix and disclosure. Development guide ================= -If you want to develop psutil take a look at the `development guide`_. +If you want to develop psutil take a look at the `DEVGUIDE.rst`_. Platforms support history ========================= @@ -2646,6 +2649,10 @@ Supported Python versions are 2.7, 3.6+ and PyPy3. Timeline ======== +- 2023-10-15: + `5.9.6 `__ - + `what's new `__ - + `diff `__ - 2023-04-17: `5.9.5 `__ - `what's new `__ - @@ -3009,7 +3016,7 @@ Timeline .. _`BPO-6973`: https://bugs.python.org/issue6973 .. _`CPU affinity`: https://www.linuxjournal.com/article/6799?page=0,0 .. _`cpu_distribution.py`: https://github.com/giampaolo/psutil/blob/master/scripts/cpu_distribution.py -.. _`development guide`: https://github.com/giampaolo/psutil/blob/master/docs/DEVGUIDE.rst +.. _`DEVGUIDE.rst`: https://github.com/giampaolo/psutil/blob/master/docs/DEVGUIDE.rst .. _`disk_usage.py`: https://github.com/giampaolo/psutil/blob/master/scripts/disk_usage.py .. _`enum`: https://docs.python.org/3/library/enum.html#module-enum .. _`fans.py`: https://github.com/giampaolo/psutil/blob/master/scripts/fans.py @@ -3032,7 +3039,6 @@ Timeline .. _`nettop.py`: https://github.com/giampaolo/psutil/blob/master/scripts/nettop.py .. _`open`: https://docs.python.org/3/library/functions.html#open .. _`os.cpu_count`: https://docs.python.org/3/library/os.html#os.cpu_count -.. _`os.getloadavg`: https://docs.python.org/3/library/os.html#os.getloadavg .. _`os.getpid`: https://docs.python.org/3/library/os.html#os.getpid .. _`os.getpriority`: https://docs.python.org/3/library/os.html#os.getpriority .. _`os.getresgid`: https://docs.python.org//library/os.html#os.getresgid @@ -3063,5 +3069,4 @@ Timeline .. _`TerminateProcess`: https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-terminateprocess .. _`threading.get_ident`: https://docs.python.org/3/library/threading.html#threading.get_ident .. _`threading.Thread`: https://docs.python.org/3/library/threading.html#threading.Thread -.. _Tidelift security contact: https://tidelift.com/security -.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-psutil?utm_source=pypi-psutil&utm_medium=referral&utm_campaign=readme +.. _`Tidelift security contact`: https://tidelift.com/security diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..82133027c --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,2 @@ +sphinx +sphinx_rtd_theme diff --git a/make.bat b/make.bat index 2c79d4291..2ac53d9ec 100644 --- a/make.bat +++ b/make.bat @@ -31,5 +31,6 @@ if "%TSCRIPT%" == "" ( rem Needed to locate the .pypirc file and upload exes on PyPI. set HOME=%USERPROFILE% +set PSUTIL_DEBUG=1 %PYTHON% scripts\internal\winmake.py %1 %2 %3 %4 %5 %6 diff --git a/psutil/__init__.py b/psutil/__init__.py index 49ab94d50..5bf6501ab 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -227,8 +227,8 @@ # See: https://github.com/giampaolo/psutil/issues/564 if (int(__version__.replace('.', '')) != getattr(_psplatform.cext, 'version', None)): - msg = "version conflict: %r C extension module was built for another " \ - "version of psutil" % _psplatform.cext.__file__ + msg = "version conflict: %r C extension " % _psplatform.cext.__file__ + msg += "module was built for another version of psutil" if hasattr(_psplatform.cext, 'version'): msg += " (%s instead of %s)" % ( '.'.join([x for x in str(_psplatform.cext.version)]), __version__) @@ -263,30 +263,11 @@ def _ppid_map(): return ret -def _assert_pid_not_reused(fun): - """Decorator which raises NoSuchProcess in case a process is no - longer running or its PID has been reused. - """ - @functools.wraps(fun) - def wrapper(self, *args, **kwargs): - if not self.is_running(): - if self._pid_reused: - msg = "process no longer exists and its PID has been reused" - else: - msg = None - raise NoSuchProcess(self.pid, self._name, msg=msg) - return fun(self, *args, **kwargs) - return wrapper - - def _pprint_secs(secs): """Format seconds in a human readable form.""" now = time.time() secs_ago = int(now - secs) - if secs_ago < 60 * 60 * 24: - fmt = "%H:%M:%S" - else: - fmt = "%Y-%m-%d %H:%M:%S" + fmt = "%H:%M:%S" if secs_ago < 60 * 60 * 24 else "%Y-%m-%d %H:%M:%S" return datetime.datetime.fromtimestamp(secs).strftime(fmt) @@ -295,7 +276,7 @@ def _pprint_secs(secs): # ===================================================================== -class Process(object): +class Process(object): # noqa: UP004 """Represents an OS process with the given PID. If PID is omitted current process PID (os.getpid()) is used. Raise NoSuchProcess if PID does not exist. @@ -442,6 +423,18 @@ def __hash__(self): self._hash = hash(self._ident) return self._hash + def _raise_if_pid_reused(self): + """Raises NoSuchProcess in case process PID has been reused.""" + if not self.is_running() and self._pid_reused: + # We may directly raise NSP in here already if PID is just + # not running, but I prefer NSP to be raised naturally by + # the actual Process API call. This way unit tests will tell + # us if the API is broken (aka don't raise NSP when it + # should). We also remain consistent with all other "get" + # APIs which don't use _raise_if_pid_reused(). + msg = "process no longer exists and its PID has been reused" + raise NoSuchProcess(self.pid, self._name, msg=msg) + @property def pid(self): """The process PID.""" @@ -627,6 +620,7 @@ def ppid(self): # XXX should we check creation time here rather than in # Process.parent()? + self._raise_if_pid_reused() if POSIX: return self._proc.ppid() else: # pragma: no cover @@ -750,8 +744,7 @@ def nice(self, value=None): if value is None: return self._proc.nice_get() else: - if not self.is_running(): - raise NoSuchProcess(self.pid, self._name) + self._raise_if_pid_reused() self._proc.nice_set(value) if POSIX: @@ -813,6 +806,7 @@ def ionice(self, ioclass=None, value=None): raise ValueError("'ioclass' argument must be specified") return self._proc.ionice_get() else: + self._raise_if_pid_reused() return self._proc.ionice_set(ioclass, value) # Linux / FreeBSD only @@ -828,6 +822,8 @@ def rlimit(self, resource, limits=None): See "man prlimit" for further info. Available on Linux and FreeBSD only. """ + if limits is not None: + self._raise_if_pid_reused() return self._proc.rlimit(resource, limits) # Windows, Linux and FreeBSD only @@ -844,6 +840,7 @@ def cpu_affinity(self, cpus=None): if cpus is None: return sorted(set(self._proc.cpu_affinity_get())) else: + self._raise_if_pid_reused() if not cpus: if hasattr(self._proc, "_get_eligible_cpus"): cpus = self._proc._get_eligible_cpus() @@ -869,7 +866,8 @@ def cpu_num(self): def environ(self): """The environment variables of the process as a dict. Note: this - might not reflect changes made after the process started. """ + might not reflect changes made after the process started. + """ return self._proc.environ() if WINDOWS: @@ -900,7 +898,6 @@ def threads(self): """ return self._proc.threads() - @_assert_pid_not_reused def children(self, recursive=False): """Return the children of this process as a list of Process instances, pre-emptively checking whether PID has been reused. @@ -927,6 +924,7 @@ def children(self, recursive=False): process Y won't be listed as the reference to process A is lost. """ + self._raise_if_pid_reused() ppid_map = _ppid_map() ret = [] if not recursive: @@ -1197,6 +1195,7 @@ def connections(self, kind='inet'): if POSIX: def _send_signal(self, sig): assert not self.pid < 0, self.pid + self._raise_if_pid_reused() if self.pid == 0: # see "man 2 kill" raise ValueError( @@ -1216,7 +1215,6 @@ def _send_signal(self, sig): except PermissionError: raise AccessDenied(self.pid, self._name) - @_assert_pid_not_reused def send_signal(self, sig): """Send a signal *sig* to process pre-emptively checking whether PID has been reused (see signal module constants) . @@ -1226,9 +1224,12 @@ def send_signal(self, sig): if POSIX: self._send_signal(sig) else: # pragma: no cover + self._raise_if_pid_reused() + if sig != signal.SIGTERM and not self.is_running(): + msg = "process no longer exists" + raise NoSuchProcess(self.pid, self._name, msg=msg) self._proc.send_signal(sig) - @_assert_pid_not_reused def suspend(self): """Suspend process execution with SIGSTOP pre-emptively checking whether PID has been reused. @@ -1237,9 +1238,9 @@ def suspend(self): if POSIX: self._send_signal(signal.SIGSTOP) else: # pragma: no cover + self._raise_if_pid_reused() self._proc.suspend() - @_assert_pid_not_reused def resume(self): """Resume process execution with SIGCONT pre-emptively checking whether PID has been reused. @@ -1248,9 +1249,9 @@ def resume(self): if POSIX: self._send_signal(signal.SIGCONT) else: # pragma: no cover + self._raise_if_pid_reused() self._proc.resume() - @_assert_pid_not_reused def terminate(self): """Terminate the process with SIGTERM pre-emptively checking whether PID has been reused. @@ -1259,9 +1260,9 @@ def terminate(self): if POSIX: self._send_signal(signal.SIGTERM) else: # pragma: no cover + self._raise_if_pid_reused() self._proc.kill() - @_assert_pid_not_reused def kill(self): """Kill the current process with SIGKILL pre-emptively checking whether PID has been reused. @@ -1269,6 +1270,7 @@ def kill(self): if POSIX: self._send_signal(signal.SIGKILL) else: # pragma: no cover + self._raise_if_pid_reused() self._proc.kill() def wait(self, timeout=None): @@ -1329,7 +1331,7 @@ class Popen(Process): >>> p.username() 'giampaolo' >>> p.communicate() - ('hi\n', None) + ('hi', None) >>> p.terminate() >>> p.wait(timeout=2) 0 @@ -1380,7 +1382,7 @@ def __getattribute__(self, name): def wait(self, timeout=None): if self.__subproc.returncode is not None: return self.__subproc.returncode - ret = super(Popen, self).wait(timeout) + ret = super(Popen, self).wait(timeout) # noqa self.__subproc.returncode = ret return ret @@ -2196,7 +2198,7 @@ def net_if_addrs(): if WINDOWS and fam == -1: fam = _psplatform.AF_LINK elif (hasattr(_psplatform, "AF_LINK") and - _psplatform.AF_LINK == fam): + fam == _psplatform.AF_LINK): # Linux defines AF_LINK as an alias for AF_PACKET. # We re-set the family here so that repr(family) # will show AF_LINK rather than AF_PACKET @@ -2421,7 +2423,7 @@ def test(): # pragma: no cover del memoize_when_activated, division if sys.version_info[0] < 3: - del num, x + del num, x # noqa if __name__ == "__main__": test() diff --git a/psutil/_common.py b/psutil/_common.py index a0d49d638..4057f541b 100644 --- a/psutil/_common.py +++ b/psutil/_common.py @@ -37,7 +37,7 @@ # can't take it from _common.py as this script is imported by setup.py -PY3 = sys.version_info[0] == 3 +PY3 = sys.version_info[0] >= 3 if PY3: import enum else: @@ -280,13 +280,14 @@ class Error(Exception): """Base exception class. All other psutil exceptions inherit from this one. """ + __module__ = 'psutil' def _infodict(self, attrs): info = collections.OrderedDict() for name in attrs: value = getattr(self, name, None) - if value: + if value: # noqa info[name] = value elif name == "pid" and value == 0: info[name] = value @@ -313,6 +314,7 @@ class NoSuchProcess(Error): """Exception raised when a process with a certain PID doesn't or no longer exists. """ + __module__ = 'psutil' def __init__(self, pid, name=None, msg=None): @@ -329,6 +331,7 @@ class ZombieProcess(NoSuchProcess): On Linux all zombie processes are querable (hence this is never raised). Windows doesn't have zombie processes. """ + __module__ = 'psutil' def __init__(self, pid, name=None, ppid=None, msg=None): @@ -339,6 +342,7 @@ def __init__(self, pid, name=None, ppid=None, msg=None): class AccessDenied(Error): """Exception raised when permission to perform an action is denied.""" + __module__ = 'psutil' def __init__(self, pid=None, name=None, msg=None): @@ -352,6 +356,7 @@ class TimeoutExpired(Error): """Raised on Process.wait(timeout) if timeout expires and process is still alive. """ + __module__ = 'psutil' def __init__(self, seconds, pid=None, name=None): @@ -496,7 +501,8 @@ def wrapper(self): def cache_activate(proc): """Activate cache. Expects a Process instance. Cache will be - stored as a "_cache" instance attribute.""" + stored as a "_cache" instance attribute. + """ proc._cache = {} def cache_deactivate(proc): @@ -514,7 +520,7 @@ def cache_deactivate(proc): def isfile_strict(path): """Same as os.path.isfile() but does not swallow EACCES / EPERM exceptions, see: - http://mail.python.org/pipermail/python-dev/2012-June/120787.html + http://mail.python.org/pipermail/python-dev/2012-June/120787.html. """ try: st = os.stat(path) @@ -528,8 +534,8 @@ def isfile_strict(path): def path_exists_strict(path): """Same as os.path.exists() but does not swallow EACCES / EPERM - exceptions, see: - http://mail.python.org/pipermail/python-dev/2012-June/120787.html + exceptions. See: + http://mail.python.org/pipermail/python-dev/2012-June/120787.html. """ try: os.stat(path) @@ -678,7 +684,7 @@ def _remove_dead_reminders(self, input_dict, name): def run(self, input_dict, name): """Cache dict and sum numbers which overflow and wrap. - Return an updated copy of `input_dict` + Return an updated copy of `input_dict`. """ if name not in self.cache: # This was the first call. @@ -689,7 +695,7 @@ def run(self, input_dict, name): old_dict = self.cache[name] new_dict = {} - for key in input_dict.keys(): + for key in input_dict: input_tuple = input_dict[key] try: old_tuple = old_dict[key] @@ -772,12 +778,12 @@ def open_text(fname): On Python 2 this is just an alias for open(name, 'rt'). """ if not PY3: - return open(fname, "rt", buffering=FILE_READ_BUFFER_SIZE) + return open(fname, buffering=FILE_READ_BUFFER_SIZE) # See: # https://github.com/giampaolo/psutil/issues/675 # https://github.com/giampaolo/psutil/pull/733 - fobj = open(fname, "rt", buffering=FILE_READ_BUFFER_SIZE, + fobj = open(fname, buffering=FILE_READ_BUFFER_SIZE, encoding=ENCODING, errors=ENCODING_ERRS) try: # Dictates per-line read(2) buffer size. Defaults is 8k. See: @@ -815,8 +821,7 @@ def bcat(fname, fallback=_DEFAULT): def bytes2human(n, format="%(value).1f%(symbol)s"): - """Used by various scripts. See: - http://goo.gl/zeJZl + """Used by various scripts. See: http://goo.gl/zeJZl. >>> bytes2human(10000) '9.8K' diff --git a/psutil/_compat.py b/psutil/_compat.py index 2531cf4b6..95754113d 100644 --- a/psutil/_compat.py +++ b/psutil/_compat.py @@ -34,7 +34,7 @@ "InterruptedError", "ChildProcessError", "FileExistsError"] -PY3 = sys.version_info[0] == 3 +PY3 = sys.version_info[0] >= 3 _SENTINEL = object() if PY3: @@ -152,7 +152,7 @@ def __init__(self, *args, **kwargs): if not attr.startswith('__'): setattr(self, attr, getattr(unwrap_me, attr)) else: - super(TemporaryClass, self).__init__(*args, **kwargs) + super(TemporaryClass, self).__init__(*args, **kwargs) # noqa class __metaclass__(type): def __instancecheck__(cls, inst): @@ -222,7 +222,7 @@ def FileExistsError(inst): "CacheInfo", ["hits", "misses", "maxsize", "currsize"]) class _HashedSeq(list): - __slots__ = 'hashvalue' + __slots__ = ('hashvalue', ) def __init__(self, tup, hash=hash): self[:] = tup @@ -251,7 +251,7 @@ def _make_key(args, kwds, typed, def lru_cache(maxsize=100, typed=False): """Least-recently-used cache decorator, see: - http://docs.python.org/3/library/functools.html#functools.lru_cache + http://docs.python.org/3/library/functools.html#functools.lru_cache. """ def decorating_function(user_function): cache = {} @@ -328,7 +328,7 @@ def wrapper(*args, **kwds): return result def cache_info(): - """Report cache statistics""" + """Report cache statistics.""" lock.acquire() try: return _CacheInfo(stats[HITS], stats[MISSES], maxsize, @@ -337,7 +337,7 @@ def cache_info(): lock.release() def cache_clear(): - """Clear the cache and cache statistics""" + """Clear the cache and cache statistics.""" lock.acquire() try: cache.clear() diff --git a/psutil/_psaix.py b/psutil/_psaix.py index 67f0314f7..6c2962c5e 100644 --- a/psutil/_psaix.py +++ b/psutil/_psaix.py @@ -123,13 +123,13 @@ def swap_memory(): def cpu_times(): - """Return system-wide CPU times as a named tuple""" + """Return system-wide CPU times as a named tuple.""" ret = cext.per_cpu_times() return scputimes(*[sum(x) for x in zip(*ret)]) def per_cpu_times(): - """Return system per-CPU times as a list of named tuples""" + """Return system per-CPU times as a list of named tuples.""" ret = cext.per_cpu_times() return [scputimes(*x) for x in ret] @@ -144,13 +144,12 @@ def cpu_count_logical(): def cpu_count_cores(): - cmd = "lsdev -Cc processor" - p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + cmd = ["lsdev", "-Cc", "processor"] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() if PY3: - stdout, stderr = [x.decode(sys.stdout.encoding) - for x in (stdout, stderr)] + stdout, stderr = (x.decode(sys.stdout.encoding) + for x in (stdout, stderr)) if p.returncode != 0: raise RuntimeError("%r command error\n%s" % (cmd, stderr)) processors = stdout.strip().splitlines() @@ -249,8 +248,8 @@ def net_if_stats(): stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() if PY3: - stdout, stderr = [x.decode(sys.stdout.encoding) - for x in (stdout, stderr)] + stdout, stderr = (x.decode(sys.stdout.encoding) + for x in (stdout, stderr)) if p.returncode == 0: re_result = re.search( r"Running: (\d+) Mbps.*?(\w+) Duplex", stdout) @@ -330,7 +329,7 @@ def wrapper(self, *args, **kwargs): return wrapper -class Process(object): +class Process: """Wrapper class around underlying C implementation.""" __slots__ = ["pid", "_name", "_ppid", "_procfs_path", "_cache"] @@ -511,8 +510,8 @@ def open_files(self): stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() if PY3: - stdout, stderr = [x.decode(sys.stdout.encoding) - for x in (stdout, stderr)] + stdout, stderr = (x.decode(sys.stdout.encoding) + for x in (stdout, stderr)) if "no such process" in stderr.lower(): raise NoSuchProcess(self.pid, self._name) procfiles = re.findall(r"(\d+): S_IFREG.*\s*.*name:(.*)\n", stdout) diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py index fb4217efe..eeab18a9a 100644 --- a/psutil/_psbsd.py +++ b/psutil/_psbsd.py @@ -8,9 +8,9 @@ import errno import functools import os -import xml.etree.ElementTree as ET from collections import defaultdict from collections import namedtuple +from xml.etree import ElementTree from . import _common from . import _psposix @@ -226,14 +226,14 @@ def swap_memory(): def cpu_times(): - """Return system per-CPU times as a namedtuple""" + """Return system per-CPU times as a namedtuple.""" user, nice, system, idle, irq = cext.cpu_times() return scputimes(user, nice, system, idle, irq) if HAS_PER_CPU_TIMES: def per_cpu_times(): - """Return system CPU times as a namedtuple""" + """Return system CPU times as a namedtuple.""" ret = [] for cpu_t in cext.per_cpu_times(): user, nice, system, idle, irq = cpu_t @@ -249,7 +249,7 @@ def per_cpu_times(): # crash at psutil import time. # Next calls will fail with NotImplementedError def per_cpu_times(): - """Return system CPU times as a namedtuple""" + """Return system CPU times as a namedtuple.""" if cpu_count_logical() == 1: return [cpu_times()] if per_cpu_times.__called__: @@ -284,7 +284,7 @@ def cpu_count_cores(): index = s.rfind("") if index != -1: s = s[:index + 9] - root = ET.fromstring(s) + root = ElementTree.fromstring(s) try: ret = len(root.findall('group/children/group/cpu')) or None finally: @@ -365,7 +365,7 @@ def cpu_freq(): def disk_partitions(all=False): """Return mounted disk partitions as a list of namedtuples. 'all' argument is ignored, see: - https://github.com/giampaolo/psutil/issues/906 + https://github.com/giampaolo/psutil/issues/906. """ retlist = [] partitions = cext.disk_partitions() @@ -599,7 +599,7 @@ def wrap_exceptions_procfs(inst): raise AccessDenied(inst.pid, inst._name) -class Process(object): +class Process: """Wrapper class around underlying C implementation.""" __slots__ = ["pid", "_name", "_ppid", "_cache"] diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index 0f102cbfa..628cd4b35 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -16,7 +16,6 @@ import socket import struct import sys -import traceback import warnings from collections import defaultdict from collections import namedtuple @@ -68,7 +67,8 @@ # connection status constants "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1", "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", - "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", ] + "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING" +] # ===================================================================== @@ -283,9 +283,9 @@ def set_scputimes_ntuple(procfs_path): try: set_scputimes_ntuple("/proc") -except Exception: # pragma: no cover +except Exception as err: # pragma: no cover # Don't want to crash at import time. - traceback.print_exc() + debug("ignoring exception on import: %r" % err) scputimes = namedtuple('scputimes', 'user system idle')(0.0, 0.0, 0.0) @@ -345,7 +345,7 @@ class StructRlimit(ctypes.Structure): def calculate_avail_vmem(mems): """Fallback for kernels < 3.14 where /proc/meminfo does not provide "MemAvailable", see: - https://blog.famzah.net/2014/09/24/ + https://blog.famzah.net/2014/09/24/. This code reimplements the algorithm outlined here: https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ @@ -549,8 +549,8 @@ def swap_memory(): f = open_binary("%s/vmstat" % get_procfs_path()) except IOError as err: # see https://github.com/giampaolo/psutil/issues/722 - msg = "'sin' and 'sout' swap memory stats couldn't " \ - "be determined and were set to 0 (%s)" % str(err) + msg = "'sin' and 'sout' swap memory stats couldn't " + \ + "be determined and were set to 0 (%s)" % str(err) warnings.warn(msg, RuntimeWarning, stacklevel=2) sin = sout = 0 else: @@ -569,8 +569,8 @@ def swap_memory(): # we might get here when dealing with exotic Linux # flavors, see: # https://github.com/giampaolo/psutil/issues/313 - msg = "'sin' and 'sout' swap memory stats couldn't " \ - "be determined and were set to 0" + msg = "'sin' and 'sout' swap memory stats couldn't " + msg += "be determined and were set to 0" warnings.warn(msg, RuntimeWarning, stacklevel=2) sin = sout = 0 return _common.sswap(total, used, free, percent, sin, sout) @@ -710,8 +710,7 @@ def cpu_stats(): def _cpu_get_cpuinfo_freq(): - """Return current CPU frequency from cpuinfo if available. - """ + """Return current CPU frequency from cpuinfo if available.""" ret = [] with open_binary('%s/cpuinfo' % get_procfs_path()) as f: for line in f: @@ -958,7 +957,7 @@ def process_unix(file, family, inodes, filter_pid=None): raise RuntimeError( "error while parsing %s; malformed line %r" % ( file, line)) - if inode in inodes: + if inode in inodes: # noqa # With UNIX sockets we can have a single inode # referencing many file descriptors. pairs = inodes[inode] @@ -968,10 +967,7 @@ def process_unix(file, family, inodes, filter_pid=None): if filter_pid is not None and filter_pid != pid: continue else: - if len(tokens) == 8: - path = tokens[-1] - else: - path = "" + path = tokens[-1] if len(tokens) == 8 else '' type_ = _common.socktype_to_enum(int(type_)) # XXX: determining the remote endpoint of a # UNIX socket on Linux is not possible, see: @@ -1191,8 +1187,9 @@ class RootFsDeviceFinder: or "rootfs". This container class uses different strategies to try to obtain the real device path. Resources: https://bootlin.com/blog/find-root-device/ - https://www.systutorials.com/how-to-find-the-disk-where-root-is-on-in-bash-on-linux/ + https://www.systutorials.com/how-to-find-the-disk-where-root-is-on-in-bash-on-linux/. """ + __slots__ = ['major', 'minor'] def __init__(self): @@ -1455,7 +1452,7 @@ def sensors_battery(): Implementation note: it appears /sys/class/power_supply/BAT0/ directory structure may vary and provide files with the same meaning but under different names, see: - https://github.com/giampaolo/psutil/issues/966 + https://github.com/giampaolo/psutil/issues/966. """ null = object() @@ -1664,7 +1661,7 @@ def wrapper(self, *args, **kwargs): return wrapper -class Process(object): +class Process: """Linux process implementation.""" __slots__ = ["pid", "_name", "_ppid", "_procfs_path", "_cache"] @@ -1901,7 +1898,7 @@ def memory_info(self): # ============================================================ with open_binary("%s/%s/statm" % (self._procfs_path, self.pid)) as f: vms, rss, shared, text, lib, data, dirty = \ - [int(x) * PAGESIZE for x in f.readline().split()[:7]] + (int(x) * PAGESIZE for x in f.readline().split()[:7]) return pmem(rss, vms, shared, text, lib, data, dirty) if HAS_PROC_SMAPS_ROLLUP or HAS_PROC_SMAPS: @@ -1981,7 +1978,7 @@ def memory_full_info(self): def memory_maps(self): """Return process's mapped memory regions as a list of named tuples. Fields are explained in 'man proc'; here is an updated - (Apr 2012) version: http://goo.gl/fmebo + (Apr 2012) version: http://goo.gl/fmebo. /proc/{PID}/smaps does not exist on kernels < 2.6.14 or if CONFIG_MMU kernel configuration option is not enabled. diff --git a/psutil/_psosx.py b/psutil/_psosx.py index 8da2d9a32..482a9d430 100644 --- a/psutil/_psosx.py +++ b/psutil/_psosx.py @@ -144,7 +144,7 @@ def cpu_times(): def per_cpu_times(): - """Return system CPU times as a named tuple""" + """Return system CPU times as a named tuple.""" ret = [] for cpu_t in cext.per_cpu_times(): user, nice, system, idle = cpu_t @@ -174,7 +174,7 @@ def cpu_freq(): """Return CPU frequency. On macOS per-cpu frequency is not supported. Also, the returned frequency never changes, see: - https://arstechnica.com/civis/viewtopic.php?f=19&t=465002 + https://arstechnica.com/civis/viewtopic.php?f=19&t=465002. """ curr, min_, max_ = cext.cpu_freq() return [_common.scpufreq(curr, min_, max_)] @@ -354,7 +354,7 @@ def wrapper(self, *args, **kwargs): return wrapper -class Process(object): +class Process: """Wrapper class around underlying C implementation.""" __slots__ = ["pid", "_name", "_ppid", "_cache"] diff --git a/psutil/_psposix.py b/psutil/_psposix.py index 0039daf44..1b26589db 100644 --- a/psutil/_psposix.py +++ b/psutil/_psposix.py @@ -78,7 +78,7 @@ def negsig_to_enum(num): def wait_pid(pid, timeout=None, proc_name=None, _waitpid=os.waitpid, - _timer=getattr(time, 'monotonic', time.time), + _timer=getattr(time, 'monotonic', time.time), # noqa: B008 _min=min, _sleep=time.sleep, _pid_exists=pid_exists): @@ -219,7 +219,7 @@ def disk_usage(path): @memoize def get_terminal_map(): """Get a map of device-id -> path as a dict. - Used by Process.terminal() + Used by Process.terminal(). """ ret = {} ls = glob.glob('/dev/tty*') + glob.glob('/dev/pts/*') diff --git a/psutil/_pssunos.py b/psutil/_pssunos.py index d44bf2d78..291dc5a00 100644 --- a/psutil/_pssunos.py +++ b/psutil/_pssunos.py @@ -170,13 +170,13 @@ def swap_memory(): def cpu_times(): - """Return system-wide CPU times as a named tuple""" + """Return system-wide CPU times as a named tuple.""" ret = cext.per_cpu_times() return scputimes(*[sum(x) for x in zip(*ret)]) def per_cpu_times(): - """Return system per-CPU times as a list of named tuples""" + """Return system per-CPU times as a list of named tuples.""" ret = cext.per_cpu_times() return [scputimes(*x) for x in ret] @@ -369,7 +369,7 @@ def wrapper(self, *args, **kwargs): return wrapper -class Process(object): +class Process: """Wrapper class around underlying C implementation.""" __slots__ = ["pid", "_name", "_ppid", "_procfs_path", "_cache"] @@ -617,13 +617,13 @@ def _get_unix_sockets(self, pid): """Get UNIX sockets used by process by parsing 'pfiles' output.""" # TODO: rewrite this in C (...but the damn netstat source code # does not include this part! Argh!!) - cmd = "pfiles %s" % pid - p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + cmd = ["pfiles", str(pid)] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() if PY3: - stdout, stderr = [x.decode(sys.stdout.encoding) - for x in (stdout, stderr)] + stdout, stderr = (x.decode(sys.stdout.encoding) + for x in (stdout, stderr)) if p.returncode != 0: if 'permission denied' in stderr.lower(): raise AccessDenied(self.pid, self._name) diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py index eec2db84d..6fd2b54bb 100644 --- a/psutil/_pswindows.py +++ b/psutil/_pswindows.py @@ -195,7 +195,7 @@ def convert_dos_path(s): r"""Convert paths using native DOS format like: "\Device\HarddiskVolume1\Windows\systemew\file.txt" into: - "C:\Windows\systemew\file.txt" + "C:\Windows\systemew\file.txt". """ rawdrive = '\\'.join(s.split('\\')[:3]) driveletter = cext.QueryDosDevice(rawdrive) @@ -348,7 +348,8 @@ def cpu_freq(): def getloadavg(): """Return the number of processes in the system run queue averaged - over the last 1, 5, and 15 minutes respectively as a tuple""" + over the last 1, 5, and 15 minutes respectively as a tuple. + """ global _loadavg_inititialized if not _loadavg_inititialized: @@ -493,7 +494,7 @@ def win_service_get(name): return service -class WindowsService(object): +class WindowsService: """Represents an installed Windows service.""" def __init__(self, name, display_name): @@ -701,7 +702,7 @@ def wrapper(self, *args, **kwargs): def retry_error_partial_copy(fun): """Workaround for https://github.com/giampaolo/psutil/issues/875. - See: https://stackoverflow.com/questions/4457745#4457745 + See: https://stackoverflow.com/questions/4457745#4457745. """ @functools.wraps(fun) def wrapper(self, *args, **kwargs): @@ -719,13 +720,15 @@ def wrapper(self, *args, **kwargs): else: raise else: - msg = "%s retried %s times, converted to AccessDenied as it's " \ - "still returning %r" % (fun, times, err) + msg = ( + "{} retried {} times, converted to AccessDenied as it's " + "still returning {}".format(fun, times, err) + ) raise AccessDenied(pid=self.pid, name=self._name, msg=msg) return wrapper -class Process(object): +class Process: """Wrapper class around underlying C implementation.""" __slots__ = ["pid", "_name", "_ppid", "_cache"] @@ -851,7 +854,7 @@ def memory_info(self): t = self._get_raw_meminfo() rss = t[2] # wset vms = t[7] # pagefile - return pmem(*(rss, vms, ) + t) + return pmem(*(rss, vms) + t) @wrap_exceptions def memory_full_info(self): diff --git a/psutil/arch/openbsd/proc.c b/psutil/arch/openbsd/proc.c index 5c984fc53..96b85bc50 100644 --- a/psutil/arch/openbsd/proc.c +++ b/psutil/arch/openbsd/proc.c @@ -219,8 +219,15 @@ psutil_proc_threads(PyObject *self, PyObject *args) { kd = kvm_openfiles(0, 0, 0, O_RDONLY, errbuf); if (! kd) { - convert_kvm_err("kvm_openfiles()", errbuf); - goto error; + // Usually fails due to EPERM against /dev/mem. We retry with + // KVM_NO_FILES which apparently has the same effect. + // https://stackoverflow.com/questions/22369736/ + psutil_debug("kvm_openfiles(O_RDONLY) failed"); + kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); + if (! kd) { + convert_kvm_err("kvm_openfiles()", errbuf); + goto error; + } } kp = kvm_getprocs( diff --git a/psutil/arch/windows/proc.c b/psutil/arch/windows/proc.c index d9b69744f..af3df267a 100644 --- a/psutil/arch/windows/proc.c +++ b/psutil/arch/windows/proc.c @@ -99,14 +99,14 @@ PyObject * psutil_proc_kill(PyObject *self, PyObject *args) { HANDLE hProcess; DWORD pid; + DWORD access = PROCESS_TERMINATE | PROCESS_QUERY_LIMITED_INFORMATION; if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) return NULL; if (pid == 0) return AccessDenied("automatically set for PID 0"); - hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); - hProcess = psutil_check_phandle(hProcess, pid, 0); + hProcess = psutil_handle_from_pid(pid, access); if (hProcess == NULL) { return NULL; } @@ -272,6 +272,11 @@ psutil_proc_exe(PyObject *self, PyObject *args) { if (pid == 0) return AccessDenied("automatically set for PID 0"); + // ...because NtQuerySystemInformation can succeed for terminated + // processes. + if (psutil_pid_is_running(pid) == 0) + return NoSuchProcess("psutil_pid_is_running -> 0"); + buffer = MALLOC_ZERO(bufferSize); if (! buffer) { PyErr_NoMemory(); @@ -535,12 +540,13 @@ psutil_proc_suspend_or_resume(PyObject *self, PyObject *args) { DWORD pid; NTSTATUS status; HANDLE hProcess; + DWORD access = PROCESS_SUSPEND_RESUME | PROCESS_QUERY_LIMITED_INFORMATION; PyObject* suspend; - if (! PyArg_ParseTuple(args, _Py_PARSE_PID "O", &pid, &suspend)) - return NULL; + if (! PyArg_ParseTuple(args, _Py_PARSE_PID "O", &pid, &suspend)) + return NULL; - hProcess = psutil_handle_from_pid(pid, PROCESS_SUSPEND_RESUME); + hProcess = psutil_handle_from_pid(pid, access); if (hProcess == NULL) return NULL; diff --git a/psutil/tests/__init__.py b/psutil/tests/__init__.py index 34746de42..93948eeee 100644 --- a/psutil/tests/__init__.py +++ b/psutil/tests/__init__.py @@ -4,9 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Test utilities. -""" +"""Test utilities.""" from __future__ import print_function @@ -374,9 +372,11 @@ def spawn_testproc(cmd=None, **kwds): testfn = get_testfn() try: safe_rmpath(testfn) - pyline = "from time import sleep;" \ - "open(r'%s', 'w').close();" \ - "sleep(60);" % testfn + pyline = ( + "from time import sleep;" + + "open(r'%s', 'w').close();" % testfn + + "sleep(60);" + ) cmd = [PYTHON_EXE, "-c", pyline] sproc = subprocess.Popen(cmd, **kwds) _subprocesses_started.add(sproc) @@ -484,7 +484,7 @@ def pyrun(src, **kwds): kwds.setdefault("stderr", None) srcfile = get_testfn() try: - with open(srcfile, 'wt') as f: + with open(srcfile, "w") as f: f.write(src) subp = spawn_testproc([PYTHON_EXE, f.name], **kwds) wait_for_pid(subp.pid) @@ -496,7 +496,7 @@ def pyrun(src, **kwds): @_reap_children_on_err def sh(cmd, **kwds): - """run cmd in a subprocess and return its output. + """Run cmd in a subprocess and return its output. raises RuntimeError on error. """ # Prevents subprocess to open error dialogs in case of error. @@ -676,10 +676,7 @@ def get_winver(): sp = wv.service_pack_major or 0 else: r = re.search(r"\s\d$", wv[4]) - if r: - sp = int(r.group(0)) - else: - sp = 0 + sp = int(r.group(0)) if r else 0 return (wv[0], wv[1], sp) @@ -688,7 +685,7 @@ def get_winver(): # =================================================================== -class retry(object): +class retry: """A retry decorator.""" def __init__(self, @@ -778,7 +775,7 @@ def call_until(fun, expr): expression is True. """ ret = fun() - assert eval(expr) + assert eval(expr) # noqa return ret @@ -855,7 +852,7 @@ def create_exe(outpath, c_code=None): } """) assert isinstance(c_code, str), c_code - with open(get_testfn(suffix='.c'), 'wt') as f: + with open(get_testfn(suffix='.c'), "w") as f: f.write(c_code) try: subprocess.check_call(["gcc", f.name, "-o", outpath]) @@ -900,7 +897,7 @@ def __str__(self): # assertRaisesRegexp renamed to assertRaisesRegex in 3.3; # add support for the new name. if not hasattr(unittest.TestCase, 'assertRaisesRegex'): - assertRaisesRegex = unittest.TestCase.assertRaisesRegexp + assertRaisesRegex = unittest.TestCase.assertRaisesRegexp # noqa # ...otherwise multiprocessing.Pool complains if not PY3: @@ -950,20 +947,48 @@ def pyrun(self, *args, **kwds): self.addCleanup(terminate, sproc) # executed first return sproc - def assertProcessGone(self, proc): - self.assertRaises(psutil.NoSuchProcess, psutil.Process, proc.pid) - if isinstance(proc, (psutil.Process, psutil.Popen)): - assert not proc.is_running() + def _check_proc_exc(self, proc, exc): + self.assertIsInstance(exc, psutil.Error) + self.assertEqual(exc.pid, proc.pid) + self.assertEqual(exc.name, proc._name) + if exc.name: + self.assertNotEqual(exc.name, "") + if isinstance(exc, psutil.ZombieProcess): + self.assertEqual(exc.ppid, proc._ppid) + if exc.ppid is not None: + self.assertGreaterEqual(exc.ppid, 0) + str(exc) + repr(exc) + + def assertPidGone(self, pid): + with self.assertRaises(psutil.NoSuchProcess) as cm: try: - status = proc.status() - except psutil.NoSuchProcess: - pass - else: - raise AssertionError("Process.status() didn't raise exception " - "(status=%s)" % status) - proc.wait(timeout=0) # assert not raise TimeoutExpired - assert not psutil.pid_exists(proc.pid), proc.pid - self.assertNotIn(proc.pid, psutil.pids()) + psutil.Process(pid) + except psutil.ZombieProcess: + raise AssertionError( + "wasn't supposed to raise ZombieProcess") + self.assertEqual(cm.exception.pid, pid) + self.assertEqual(cm.exception.name, None) + assert not psutil.pid_exists(pid), pid + self.assertNotIn(pid, psutil.pids()) + self.assertNotIn(pid, [x.pid for x in psutil.process_iter()]) + + def assertProcessGone(self, proc): + self.assertPidGone(proc.pid) + ns = process_namespace(proc) + for fun, name in ns.iter(ns.all, clear_cache=True): + with self.subTest(proc=proc, name=name): + try: + ret = fun() + except psutil.ZombieProcess: + raise + except psutil.NoSuchProcess as exc: + self._check_proc_exc(proc, exc) + else: + msg = "Process.%s() didn't raise NSP and returned %r" % ( + name, ret) + raise AssertionError(msg) + proc.wait(timeout=0) # assert not raise TimeoutExpired def assertProcessZombie(self, proc): # A zombie process should always be instantiable. @@ -987,17 +1012,23 @@ def assertProcessZombie(self, proc): self.assertIn(proc.pid, [x.pid for x in psutil.process_iter()]) # Call all methods. ns = process_namespace(proc) - for fun, name in ns.iter(ns.all): - with self.subTest(name): + for fun, name in ns.iter(ns.all, clear_cache=True): + with self.subTest(proc=proc, name=name): try: fun() - except (psutil.ZombieProcess, psutil.AccessDenied): - pass + except (psutil.ZombieProcess, psutil.AccessDenied) as exc: + self._check_proc_exc(proc, exc) if LINUX: # https://github.com/giampaolo/psutil/pull/2288 - self.assertRaises(psutil.ZombieProcess, proc.cmdline) - self.assertRaises(psutil.ZombieProcess, proc.exe) - self.assertRaises(psutil.ZombieProcess, proc.memory_maps) + with self.assertRaises(psutil.ZombieProcess) as cm: + proc.cmdline() + self._check_proc_exc(proc, cm.exception) + with self.assertRaises(psutil.ZombieProcess) as cm: + proc.exe() + self._check_proc_exc(proc, cm.exception) + with self.assertRaises(psutil.ZombieProcess) as cm: + proc.memory_maps() + self._check_proc_exc(proc, cm.exception) # Zombie cannot be signaled or terminated. proc.suspend() proc.resume() @@ -1056,6 +1087,7 @@ class TestLeaks(psutil.tests.TestMemoryLeak): def test_fun(self): self.execute(some_function) """ + # Configurable class attrs. times = 200 warmup_times = 10 @@ -1288,6 +1320,7 @@ class process_namespace: >>> for fun, name in ns.iter(ns.getters): ... fun() """ + utils = [ ('cpu_percent', (), {}), ('memory_percent', (), {}), @@ -1424,6 +1457,7 @@ class system_namespace: >>> for fun, name in ns.iter(ns.getters): ... fun() """ + getters = [ ('boot_time', (), {}), ('cpu_count', (), {'logical': False}), @@ -1887,4 +1921,4 @@ def cleanup_test_procs(): # module. With this it will. See: # https://gmpy.dev/blog/2016/how-to-always-execute-exit-functions-in-python if POSIX: - signal.signal(signal.SIGTERM, lambda sig, frame: sys.exit(sig)) + signal.signal(signal.SIGTERM, lambda sig, _: sys.exit(sig)) diff --git a/psutil/tests/__main__.py b/psutil/tests/__main__.py index e67735275..434515d21 100755 --- a/psutil/tests/__main__.py +++ b/psutil/tests/__main__.py @@ -4,9 +4,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Run unit tests. This is invoked by: -$ python -m psutil.tests +"""Run unit tests. This is invoked by: +$ python -m psutil.tests. """ from .runner import main diff --git a/psutil/tests/runner.py b/psutil/tests/runner.py index 2e6f83e26..d3938b05a 100755 --- a/psutil/tests/runner.py +++ b/psutil/tests/runner.py @@ -4,12 +4,11 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Unit test runner, providing new features on top of unittest module: +"""Unit test runner, providing new features on top of unittest module: - colourized output - parallel run (UNIX only) - print failures/tracebacks on CTRL+C -- re-run failed tests only (make test-failed) +- re-run failed tests only (make test-failed). Invocation examples: - make test @@ -59,7 +58,7 @@ USE_COLORS = not CI_TESTING and term_supports_colors() HERE = os.path.abspath(os.path.dirname(__file__)) -loadTestsFromTestCase = unittest.defaultTestLoader.loadTestsFromTestCase +loadTestsFromTestCase = unittest.defaultTestLoader.loadTestsFromTestCase # noqa def cprint(msg, color, bold=False, file=None): @@ -108,7 +107,7 @@ def last_failed(self): suite = unittest.TestSuite() if not os.path.isfile(FAILED_TESTS_FNAME): return suite - with open(FAILED_TESTS_FNAME, 'rt') as f: + with open(FAILED_TESTS_FNAME) as f: names = f.read().split() for n in names: test = unittest.defaultTestLoader.loadTestsFromName(n) @@ -145,10 +144,11 @@ def printErrorList(self, flavour, errors): class ColouredTextRunner(unittest.TextTestRunner): + """A coloured text runner which also prints failed tests on + KeyboardInterrupt and save failed tests in a file so that they can + be re-run. """ - A coloured text runner which also prints failed tests on KeyboardInterrupt - and save failed tests in a file so that they can be re-run. - """ + resultclass = ColouredResult if USE_COLORS else unittest.TextTestResult def __init__(self, *args, **kwargs): @@ -163,7 +163,7 @@ def _makeResult(self): def _write_last_failed(self): if self.failed_tnames: - with open(FAILED_TESTS_FNAME, 'wt') as f: + with open(FAILED_TESTS_FNAME, "w") as f: for tname in self.failed_tnames: f.write(tname + '\n') diff --git a/psutil/tests/test_bsd.py b/psutil/tests/test_bsd.py index 69f50732b..c5a5e7abc 100755 --- a/psutil/tests/test_bsd.py +++ b/psutil/tests/test_bsd.py @@ -503,7 +503,7 @@ class NetBSDTestCase(PsutilTestCase): @staticmethod def parse_meminfo(look_for): - with open('/proc/meminfo', 'rt') as f: + with open('/proc/meminfo') as f: for line in f: if line.startswith(look_for): return int(line.split()[1]) * 1024 diff --git a/psutil/tests/test_connections.py b/psutil/tests/test_connections.py index aec164e85..13d1aebe9 100755 --- a/psutil/tests/test_connections.py +++ b/psutil/tests/test_connections.py @@ -357,14 +357,14 @@ def check_conn(proc, conn, family, type, laddr, raddr, status, kinds): # launch various subprocess instantiating a socket of various # families and types to enrich psutil results tcp4_proc = self.pyrun(tcp4_template) - tcp4_addr = eval(wait_for_file(testfile, delete=True)) + tcp4_addr = eval(wait_for_file(testfile, delete=True)) # noqa udp4_proc = self.pyrun(udp4_template) - udp4_addr = eval(wait_for_file(testfile, delete=True)) + udp4_addr = eval(wait_for_file(testfile, delete=True)) # noqa if supports_ipv6(): tcp6_proc = self.pyrun(tcp6_template) - tcp6_addr = eval(wait_for_file(testfile, delete=True)) + tcp6_addr = eval(wait_for_file(testfile, delete=True)) # noqa udp6_proc = self.pyrun(udp6_template) - udp6_addr = eval(wait_for_file(testfile, delete=True)) + udp6_addr = eval(wait_for_file(testfile, delete=True)) # noqa else: tcp6_proc = None udp6_proc = None @@ -533,10 +533,10 @@ def test_connection_constants(self): ints.append(num) strs.append(str_) if SUNOS: - psutil.CONN_IDLE - psutil.CONN_BOUND + psutil.CONN_IDLE # noqa + psutil.CONN_BOUND # noqa if WINDOWS: - psutil.CONN_DELETE_TCB + psutil.CONN_DELETE_TCB # noqa if __name__ == '__main__': diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py index f44911af3..f8d771694 100755 --- a/psutil/tests/test_contracts.py +++ b/psutil/tests/test_contracts.py @@ -6,7 +6,7 @@ """Contracts tests. These tests mainly check API sanity in terms of returned types and APIs availability. -Some of these are duplicates of tests test_system.py and test_process.py +Some of these are duplicates of tests test_system.py and test_process.py. """ import errno @@ -196,7 +196,7 @@ def test_cpu_num(self): def test_memory_maps(self): hasit = hasattr(psutil.Process, "memory_maps") self.assertEqual( - hasit, False if OPENBSD or NETBSD or AIX or MACOS else True) + hasit, not (OPENBSD or NETBSD or AIX or MACOS)) # =================================================================== @@ -207,7 +207,7 @@ def test_memory_maps(self): class TestSystemAPITypes(PsutilTestCase): """Check the return types of system related APIs. Mainly we want to test we never return unicode on Python 2, see: - https://github.com/giampaolo/psutil/issues/1039 + https://github.com/giampaolo/psutil/issues/1039. """ @classmethod @@ -299,7 +299,7 @@ def test_net_if_stats(self): @unittest.skipIf(not HAS_NET_IO_COUNTERS, 'not supported') def test_net_io_counters(self): # Duplicate of test_system.py. Keep it anyway. - for ifname, _ in psutil.net_io_counters(pernic=True).items(): + for ifname in psutil.net_io_counters(pernic=True): self.assertIsInstance(ifname, str) @unittest.skipIf(not HAS_SENSORS_FANS, "not supported") @@ -368,6 +368,7 @@ def check_exception(exc, proc, name, ppid): elif isinstance(exc, psutil.NoSuchProcess): tcase.assertProcessGone(proc) str(exc) + repr(exc) def do_wait(): if pid != 0: @@ -378,23 +379,27 @@ def do_wait(): try: proc = psutil.Process(pid) - d = proc.as_dict(['ppid', 'name']) except psutil.NoSuchProcess: + tcase.assertPidGone(pid) return {} - - name, ppid = d['name'], d['ppid'] - info = {'pid': proc.pid} - ns = process_namespace(proc) - # We don't use oneshot() because in order not to fool - # check_exception() in case of NSP. - for fun, fun_name in ns.iter(ns.getters, clear_cache=False): - try: - info[fun_name] = fun() - except psutil.Error as exc: - check_exception(exc, proc, name, ppid) - continue - do_wait() - return info + try: + d = proc.as_dict(['ppid', 'name']) + except psutil.NoSuchProcess: + tcase.assertProcessGone(proc) + else: + name, ppid = d['name'], d['ppid'] + info = {'pid': proc.pid} + ns = process_namespace(proc) + # We don't use oneshot() because in order not to fool + # check_exception() in case of NSP. + for fun, fun_name in ns.iter(ns.getters, clear_cache=False): + try: + info[fun_name] = fun() + except psutil.Error as exc: + check_exception(exc, proc, name, ppid) + continue + do_wait() + return info @serialrun @@ -404,7 +409,7 @@ class TestFetchAllProcesses(PsutilTestCase): Uses a process pool to get info about all processes. """ - use_proc_pool = not CI_TESTING + use_proc_pool = 0 def setUp(self): # Using a pool in a CI env may result in deadlock, see: @@ -440,9 +445,9 @@ def test_all(self): meth = getattr(self, name) try: meth(value, info) - except AssertionError: + except Exception: s = '\n' + '=' * 70 + '\n' - s += "FAIL: test_%s pid=%s, ret=%s\n" % ( + s += "FAIL: name=test_%s, pid=%s, ret=%s\n" % ( name, info['pid'], repr(value)) s += '-' * 70 s += "\n%s" % traceback.format_exc() @@ -485,6 +490,7 @@ def pid(self, ret, info): def ppid(self, ret, info): self.assertIsInstance(ret, (int, long)) self.assertGreaterEqual(ret, 0) + proc_info(ret) def name(self, ret, info): self.assertIsInstance(ret, (str, unicode)) @@ -667,8 +673,7 @@ def cwd(self, ret, info): try: st = os.stat(ret) except OSError as err: - if WINDOWS and err.errno in \ - psutil._psplatform.ACCESS_DENIED_SET: + if WINDOWS and psutil._psplatform.is_permission_err(err): pass # directory has been removed in mean time elif err.errno != errno.ENOENT: diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py index ed13c73c5..1995375d6 100755 --- a/psutil/tests/test_linux.py +++ b/psutil/tests/test_linux.py @@ -114,7 +114,7 @@ def get_ipv4_broadcast(ifname): def get_ipv6_addresses(ifname): - with open("/proc/net/if_inet6", 'rt') as f: + with open("/proc/net/if_inet6") as f: all_fields = [] for line in f.readlines(): fields = line.split() @@ -124,7 +124,7 @@ def get_ipv6_addresses(ifname): if len(all_fields) == 0: raise ValueError("could not find interface %r" % ifname) - for i in range(0, len(all_fields)): + for i in range(len(all_fields)): unformatted = all_fields[i][0] groups = [] for j in range(0, len(unformatted), 4): @@ -181,7 +181,7 @@ def free_physmem(): for line in lines: if line.startswith('Mem'): total, used, free, shared = \ - [int(x) for x in line.split()[1:5]] + (int(x) for x in line.split()[1:5]) nt = collections.namedtuple( 'free', 'total used free shared output') return nt(total, used, free, shared, out) @@ -944,7 +944,7 @@ class TestLoadAvg(PsutilTestCase): @unittest.skipIf(not HAS_GETLOADAVG, "not supported") def test_getloadavg(self): psutil_value = psutil.getloadavg() - with open("/proc/loadavg", "r") as f: + with open("/proc/loadavg") as f: proc_value = f.read().split() self.assertAlmostEqual(float(proc_value[0]), psutil_value[0], delta=1) @@ -1017,7 +1017,7 @@ def test_against_ifconfig(self): def test_mtu(self): for name, stats in psutil.net_if_stats().items(): - with open("/sys/class/net/%s/mtu" % name, "rt") as f: + with open("/sys/class/net/%s/mtu" % name) as f: self.assertEqual(stats.mtu, int(f.read().strip())) @unittest.skipIf(not which("ifconfig"), "ifconfig utility not available") @@ -1161,7 +1161,7 @@ def df(path): def test_zfs_fs(self): # Test that ZFS partitions are returned. - with open("/proc/filesystems", "r") as f: + with open("/proc/filesystems") as f: data = f.read() if 'zfs' in data: for part in psutil.disk_partitions(): @@ -1599,7 +1599,7 @@ def test_percent(self): def test_emulate_power_plugged(self): # Pretend the AC power cable is connected. def open_mock(name, *args, **kwargs): - if name.endswith("AC0/online") or name.endswith("AC/online"): + if name.endswith(('AC0/online', 'AC/online')): return io.BytesIO(b"1") else: return orig_open(name, *args, **kwargs) @@ -1616,7 +1616,7 @@ def test_emulate_power_plugged_2(self): # Same as above but pretend /AC0/online does not exist in which # case code relies on /status file. def open_mock(name, *args, **kwargs): - if name.endswith("AC0/online") or name.endswith("AC/online"): + if name.endswith(('AC0/online', 'AC/online')): raise IOError(errno.ENOENT, "") elif name.endswith("/status"): return io.StringIO(u("charging")) @@ -1632,7 +1632,7 @@ def open_mock(name, *args, **kwargs): def test_emulate_power_not_plugged(self): # Pretend the AC power cable is not connected. def open_mock(name, *args, **kwargs): - if name.endswith("AC0/online") or name.endswith("AC/online"): + if name.endswith(('AC0/online', 'AC/online')): return io.BytesIO(b"0") else: return orig_open(name, *args, **kwargs) @@ -1647,7 +1647,7 @@ def test_emulate_power_not_plugged_2(self): # Same as above but pretend /AC0/online does not exist in which # case code relies on /status file. def open_mock(name, *args, **kwargs): - if name.endswith("AC0/online") or name.endswith("AC/online"): + if name.endswith(('AC0/online', 'AC/online')): raise IOError(errno.ENOENT, "") elif name.endswith("/status"): return io.StringIO(u("discharging")) @@ -1664,8 +1664,10 @@ def test_emulate_power_undetermined(self): # Pretend we can't know whether the AC power cable not # connected (assert fallback to False). def open_mock(name, *args, **kwargs): - if name.startswith("/sys/class/power_supply/AC0/online") or \ - name.startswith("/sys/class/power_supply/AC/online"): + if name.startswith( + ('/sys/class/power_supply/AC0/online', + '/sys/class/power_supply/AC/online') + ): raise IOError(errno.ENOENT, "") elif name.startswith("/sys/class/power_supply/BAT0/status"): return io.BytesIO(b"???") @@ -1779,7 +1781,7 @@ def open_mock(name, *args, **kwargs): return orig_open(name, *args, **kwargs) def glob_mock(path): - if path == '/sys/class/hwmon/hwmon*/temp*_*': + if path == '/sys/class/hwmon/hwmon*/temp*_*': # noqa return [] elif path == '/sys/class/hwmon/hwmon*/device/temp*_*': return [] @@ -1898,7 +1900,7 @@ def get_test_file(fname): testfn = self.get_testfn() with open(testfn, "w"): self.assertEqual(get_test_file(testfn).mode, "w") - with open(testfn, "r"): + with open(testfn): self.assertEqual(get_test_file(testfn).mode, "r") with open(testfn, "a"): self.assertEqual(get_test_file(testfn).mode, "a") @@ -2180,7 +2182,7 @@ def test_status_file_parsing(self): self.assertEqual(gids.real, 1004) self.assertEqual(gids.effective, 1005) self.assertEqual(gids.saved, 1006) - self.assertEqual(p._proc._get_eligible_cpus(), list(range(0, 8))) + self.assertEqual(p._proc._get_eligible_cpus(), list(range(8))) def test_connections_enametoolong(self): # Simulate a case where /proc/{pid}/fd/{fd} symlink points to diff --git a/psutil/tests/test_memleaks.py b/psutil/tests/test_memleaks.py old mode 100644 new mode 100755 index d7cff786c..56b219d6f --- a/psutil/tests/test_memleaks.py +++ b/psutil/tests/test_memleaks.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Tests for detecting function memory leaks (typically the ones +"""Tests for detecting function memory leaks (typically the ones implemented in C). It does so by calling a function many times and checking whether process memory usage keeps increasing between calls or over time. diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py index 53c640121..b3515de31 100755 --- a/psutil/tests/test_misc.py +++ b/psutil/tests/test_misc.py @@ -5,9 +5,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Miscellaneous tests. -""" +"""Miscellaneous tests.""" import ast import collections @@ -331,12 +329,12 @@ def run_against(self, obj, expected_retval=None): self.assertEqual(ret, expected_retval) self.assertEqual(len(self.calls), 4) # docstring - self.assertEqual(obj.__doc__, "my docstring") + self.assertEqual(obj.__doc__, "My docstring.") def test_function(self): @memoize def foo(*args, **kwargs): - """my docstring""" + """My docstring.""" baseclass.calls.append((args, kwargs)) return 22 @@ -346,7 +344,7 @@ def foo(*args, **kwargs): def test_class(self): @memoize class Foo: - """my docstring""" + """My docstring.""" def __init__(self, *args, **kwargs): baseclass.calls.append((args, kwargs)) @@ -376,7 +374,7 @@ class Foo: @staticmethod @memoize def bar(*args, **kwargs): - """my docstring""" + """My docstring.""" baseclass.calls.append((args, kwargs)) return 22 @@ -388,7 +386,7 @@ class Foo: @classmethod @memoize def bar(cls, *args, **kwargs): - """my docstring""" + """My docstring.""" baseclass.calls.append((args, kwargs)) return 22 @@ -400,7 +398,7 @@ def test_original(self): # against different types. Keeping it anyway. @memoize def foo(*args, **kwargs): - """foo docstring""" + """Foo docstring.""" calls.append(None) return (args, kwargs) @@ -430,7 +428,7 @@ def foo(*args, **kwargs): self.assertEqual(ret, expected) self.assertEqual(len(calls), 4) # docstring - self.assertEqual(foo.__doc__, "foo docstring") + self.assertEqual(foo.__doc__, "Foo docstring.") class TestCommonModule(PsutilTestCase): @@ -563,7 +561,7 @@ def test_debug(self): def test_cat_bcat(self): testfn = self.get_testfn() - with open(testfn, "wt") as f: + with open(testfn, "w") as f: f.write("foo") self.assertEqual(cat(testfn), "foo") self.assertEqual(bcat(testfn), b"foo") @@ -845,11 +843,7 @@ def assert_stdout(exe, *args, **kwargs): @staticmethod def assert_syntax(exe): exe = os.path.join(SCRIPTS_DIR, exe) - if PY3: - f = open(exe, 'rt', encoding='utf8') - else: - f = open(exe, 'rt') - with f: + with open(exe, encoding="utf8") if PY3 else open(exe) as f: src = f.read() ast.parse(src) diff --git a/psutil/tests/test_posix.py b/psutil/tests/test_posix.py index f5b19c717..39ab6bf91 100755 --- a/psutil/tests/test_posix.py +++ b/psutil/tests/test_posix.py @@ -44,8 +44,7 @@ def ps(fmt, pid=None): - """ - Wrapper for calling the ps command with a little bit of cross-platform + """Wrapper for calling the ps command with a little bit of cross-platform support for a narrow range of features. """ @@ -69,10 +68,7 @@ def ps(fmt, pid=None): output = sh(cmd) - if LINUX: - output = output.splitlines() - else: - output = output.splitlines()[1:] + output = output.splitlines() if LINUX else output.splitlines()[1:] all_output = [] for line in output: @@ -329,7 +325,7 @@ def test_pids(self): @unittest.skipIf(not HAS_NET_IO_COUNTERS, "not supported") def test_nic_names(self): output = sh("ifconfig -a") - for nic in psutil.net_io_counters(pernic=True).keys(): + for nic in psutil.net_io_counters(pernic=True): for line in output.split(): if line.startswith(nic): break diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index 0f5e5ee6b..6f804d260 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -978,7 +978,7 @@ def test_cpu_affinity_all_combinations(self): if len(initial) > 12: initial = initial[:12] # ...otherwise it will take forever combos = [] - for i in range(0, len(initial) + 1): + for i in range(len(initial) + 1): for subset in itertools.combinations(initial, i): if subset: combos.append(list(subset)) @@ -1086,11 +1086,6 @@ def test_ppid(self): self.assertEqual(p.ppid(), os.getppid()) p = self.spawn_psproc() self.assertEqual(p.ppid(), os.getpid()) - if APPVEYOR: - # Occasional failures, see: - # https://ci.appveyor.com/project/giampaolo/psutil/build/ - # job/0hs623nenj7w4m33 - return def test_parent(self): p = self.spawn_psproc() @@ -1105,13 +1100,6 @@ def test_parent_multi(self): self.assertEqual(grandchild.parent(), child) self.assertEqual(child.parent(), parent) - def test_parent_disappeared(self): - # Emulate a case where the parent process disappeared. - p = self.spawn_psproc() - with mock.patch("psutil.Process", - side_effect=psutil.NoSuchProcess(0, 'foo')): - self.assertIsNone(p.parent()) - @unittest.skipIf(QEMU_USER, "QEMU user not supported") @retry_on_failure() def test_parents(self): @@ -1328,11 +1316,6 @@ def assert_raises_nsp(fun, fun_name): for fun, name in ns.iter(ns.all): assert_raises_nsp(fun, name) - # NtQuerySystemInformation succeeds even if process is gone. - if WINDOWS and not GITHUB_ACTIONS: - normcase = os.path.normcase - self.assertEqual(normcase(p.exe()), normcase(PYTHON_EXE)) - @unittest.skipIf(not POSIX, 'POSIX only') def test_zombie_process(self): parent, zombie = self.spawn_zombie() @@ -1366,10 +1349,13 @@ def test_reused_pid(self): assert not p.is_running() assert p != psutil.Process(subp.pid) msg = "process no longer exists and its PID has been reused" - self.assertRaisesRegex(psutil.NoSuchProcess, msg, p.suspend) - self.assertRaisesRegex(psutil.NoSuchProcess, msg, p.resume) - self.assertRaisesRegex(psutil.NoSuchProcess, msg, p.terminate) - self.assertRaisesRegex(psutil.NoSuchProcess, msg, p.kill) + ns = process_namespace(p) + for fun, name in ns.iter(ns.setters + ns.killers, clear_cache=False): + with self.subTest(name=name): + self.assertRaisesRegex(psutil.NoSuchProcess, msg, fun) + self.assertRaisesRegex(psutil.NoSuchProcess, msg, p.ppid) + self.assertRaisesRegex(psutil.NoSuchProcess, msg, p.parent) + self.assertRaisesRegex(psutil.NoSuchProcess, msg, p.parents) self.assertRaisesRegex(psutil.NoSuchProcess, msg, p.children) def test_pid_0(self): @@ -1491,6 +1477,7 @@ class LimitedUserTestCase(TestProcess): Executed only on UNIX and only if the user who run the test script is root. """ + # the uid/gid the test suite runs under if hasattr(os, 'getuid'): PROCESS_UID = os.getuid() @@ -1554,7 +1541,7 @@ def test_misc(self): stderr=subprocess.PIPE, env=PYTHON_EXE_ENV) as proc: proc.name() proc.cpu_times() - proc.stdin + proc.stdin # noqa self.assertTrue(dir(proc)) self.assertRaises(AttributeError, getattr, proc, 'foo') proc.terminate() diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py index 0995b6970..ee52ecbf7 100755 --- a/psutil/tests/test_system.py +++ b/psutil/tests/test_system.py @@ -217,8 +217,8 @@ def test_users(self): self.assertIsInstance(user.terminal, (str, type(None))) if user.host is not None: self.assertIsInstance(user.host, (str, type(None))) - user.terminal - user.host + user.terminal # noqa + user.host # noqa assert user.started > 0.0, user datetime.datetime.fromtimestamp(user.started) if WINDOWS or OPENBSD: @@ -618,7 +618,7 @@ def check_ntuple(nt): else: # we cannot make any assumption about this, see: # http://goo.gl/p9c43 - disk.device + disk.device # noqa # on modern systems mount points can also be files assert os.path.exists(disk.mountpoint), disk assert disk.fstype, disk diff --git a/psutil/tests/test_testutils.py b/psutil/tests/test_testutils.py index 77e52b696..bff43b1c9 100755 --- a/psutil/tests/test_testutils.py +++ b/psutil/tests/test_testutils.py @@ -5,9 +5,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Tests for testing utils (psutil.tests namespace). -""" +"""Tests for testing utils (psutil.tests namespace).""" import collections import contextlib @@ -71,7 +69,7 @@ def test_retry_success(self, sleep): def foo(): while queue: queue.pop() - 1 / 0 + 1 / 0 # noqa return 1 queue = list(range(3)) @@ -85,7 +83,7 @@ def test_retry_failure(self, sleep): def foo(): while queue: queue.pop() - 1 / 0 + 1 / 0 # noqa return 1 queue = list(range(6)) @@ -107,7 +105,7 @@ def test_no_interval_arg(self, sleep): @retry(retries=5, interval=None, logfun=None) def foo(): - 1 / 0 + 1 / 0 # noqa self.assertRaises(ZeroDivisionError, foo) self.assertEqual(sleep.call_count, 0) @@ -117,7 +115,7 @@ def test_retries_arg(self, sleep): @retry(retries=5, interval=1, logfun=None) def foo(): - 1 / 0 + 1 / 0 # noqa self.assertRaises(ZeroDivisionError, foo) self.assertEqual(sleep.call_count, 5) @@ -170,7 +168,7 @@ class TestFSTestUtils(PsutilTestCase): def test_open_text(self): with open_text(__file__) as f: - self.assertEqual(f.mode, 'rt') + self.assertEqual(f.mode, 'r') def test_open_binary(self): with open_binary(__file__) as f: @@ -252,32 +250,32 @@ def test_terminate(self): # by subprocess.Popen p = self.spawn_testproc() terminate(p) - self.assertProcessGone(p) + self.assertPidGone(p.pid) terminate(p) # by psutil.Process p = psutil.Process(self.spawn_testproc().pid) terminate(p) - self.assertProcessGone(p) + self.assertPidGone(p.pid) terminate(p) # by psutil.Popen cmd = [PYTHON_EXE, "-c", "import time; time.sleep(60);"] p = psutil.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=PYTHON_EXE_ENV) terminate(p) - self.assertProcessGone(p) + self.assertPidGone(p.pid) terminate(p) # by PID pid = self.spawn_testproc().pid terminate(pid) - self.assertProcessGone(p) + self.assertPidGone(p.pid) terminate(pid) # zombie if POSIX: parent, zombie = self.spawn_zombie() terminate(parent) terminate(zombie) - self.assertProcessGone(parent) - self.assertProcessGone(zombie) + self.assertPidGone(parent.pid) + self.assertPidGone(zombie.pid) class TestNetUtils(PsutilTestCase): @@ -407,7 +405,7 @@ def fun(): def test_execute_w_exc(self): def fun_1(): - 1 / 0 + 1 / 0 # noqa self.execute_w_exc(ZeroDivisionError, fun_1) with self.assertRaises(ZeroDivisionError): self.execute_w_exc(OSError, fun_1) diff --git a/psutil/tests/test_unicode.py b/psutil/tests/test_unicode.py index c7d8dfbc0..cf9500a3f 100755 --- a/psutil/tests/test_unicode.py +++ b/psutil/tests/test_unicode.py @@ -5,9 +5,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Notes about unicode handling in psutil -====================================== +"""Notes about unicode handling in psutil +======================================. Starting from version 5.3.0 psutil adds unicode support, see: https://github.com/giampaolo/psutil/issues/1040 @@ -309,6 +308,7 @@ def normpath(p): @unittest.skipIf(CI_TESTING, "unreliable on CI") class TestFSAPIsWithInvalidPath(TestFSAPIs): """Test FS APIs with a funky, invalid path name.""" + funky_suffix = INVALID_UNICODE_SUFFIX def expect_exact_path_match(self): @@ -323,6 +323,7 @@ def expect_exact_path_match(self): class TestNonFSAPIS(BaseUnicodeTest): """Unicode tests for non fs-related APIs.""" + funky_suffix = UNICODE_SUFFIX if PY3 else 'รจ' @unittest.skipIf(not HAS_ENVIRON, "not supported") diff --git a/psutil/tests/test_windows.py b/psutil/tests/test_windows.py index bb6faabe2..47d6ad3f1 100755 --- a/psutil/tests/test_windows.py +++ b/psutil/tests/test_windows.py @@ -379,7 +379,7 @@ def test_special_pid(self): rss, vms = p.memory_info()[:2] except psutil.AccessDenied: # expected on Windows Vista and Windows 7 - if not platform.uname()[1] in ('vista', 'win-7', 'win7'): + if platform.uname()[1] not in ('vista', 'win-7', 'win7'): raise else: self.assertGreater(rss, 0) @@ -625,14 +625,13 @@ def test_create_time(self): @unittest.skipIf(not WINDOWS, "WINDOWS only") class TestDualProcessImplementation(PsutilTestCase): - """ - Certain APIs on Windows have 2 internal implementations, one + """Certain APIs on Windows have 2 internal implementations, one based on documented Windows APIs, another one based NtQuerySystemInformation() which gets called as fallback in case the first fails because of limited permission error. Here we test that the two methods return the exact same value, see: - https://github.com/giampaolo/psutil/issues/304 + https://github.com/giampaolo/psutil/issues/304. """ @classmethod diff --git a/pyproject.toml b/pyproject.toml index 8d68131c9..d99de4271 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,38 +1,117 @@ -[tool.isort] -force_single_line = true # one import per line -lines_after_imports = 2 # blank spaces after import section +[tool.ruff] +# https://beta.ruff.rs/docs/settings/ +target-version = "py37" +line-length = 79 +select = [ + # To get a list of all values: `python3 -m ruff linter`. + "ALL", + "D200", # [*] One-line docstring should fit on one line + "D204", # [*] 1 blank line required after class docstring + "D209", # [*] Multi-line docstring closing quotes should be on a separate line + "D212", # [*] Multi-line docstring summary should start at the first line + "D301", # Use `r"""` if any backslashes in a docstring + "D403", # [*] First word of the first line should be capitalized + "PERF102", # [*] When using only the keys of a dict use the `keys()` method + "S113", # Probable use of requests call without timeout + "S602", # `subprocess` call with `shell=True` identified, security issue +] +ignore = [ + "A", # flake8-builtins + "ANN", # flake8-annotations + "ARG", # flake8-unused-arguments + "B007", # Loop control variable `x` not used within loop body + "B904", # Within an `except` clause, raise exceptions with `raise ... from err` (PYTHON2.7 COMPAT) + "BLE001", # Do not catch blind exception: `Exception` + "C4", # flake8-comprehensions (PYTHON2.7 COMPAT) + "C408", # Unnecessary `dict` call (rewrite as a literal) + "C90", # mccabe (function `X` is too complex) + "COM812", # Trailing comma missing + "D", # pydocstyle + "DTZ", # flake8-datetimez + "EM", # flake8-errmsg + "ERA001", # Found commented-out code + "FBT", # flake8-boolean-trap (makes zero sense) + "FIX", # Line contains TODO / XXX / ..., consider resolving the issue + "FLY", # flynt (PYTHON2.7 COMPAT) + "INP", # flake8-no-pep420 + "N801", # Class name `async_chat` should use CapWords convention (ASYNCORE COMPAT) + "N802", # Function name X should be lowercase. + "N803", # Argument name X should be lowercase. + "N806", # Variable X in function should be lowercase. + "N818", # Exception name `FooBar` should be named with an Error suffix + "PERF", # Perflint + "PGH004", # Use specific rule codes when using `noqa` + "PLR", # pylint + "PLW", # pylint + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "PYI", # flake8-pyi + "Q000", # Single quotes found but double quotes preferred + "RET", # flake8-return + "RUF", # Ruff-specific rules + "S", # flake8-bandit + "SIM102", # Use a single `if` statement instead of nested `if` statements + "SIM105", # Use `contextlib.suppress(OSError)` instead of `try`-`except`-`pass` + "SIM115", # Use context handler for opening files + "SIM117", # Use a single `with` statement with multiple contexts instead of nested `with` statements + "SLF", # flake8-self + "TD", # all TODOs, XXXs, etc. + "TRY003", # Avoid specifying long messages outside the exception class + "TRY200", # Use `raise from` to specify exception cause (PYTHON2.7 COMPAT) + "TRY300", # Consider moving this statement to an `else` block + "TRY301", # Abstract `raise` to an inner function + "UP009", # [*] UTF-8 encoding declaration is unnecessary (PYTHON2.7 COMPAT) + "UP010", # [*] Unnecessary `__future__` import `print_function` for target Python version (PYTHON2.7 COMPAT) + "UP024", # [*] Replace aliased errors with `OSError` (PYTHON2.7 COMPAT) + "UP028", # [*] Replace `yield` over `for` loop with `yield from` (PYTHON2.7 COMPAT) + "UP031", # [*] Use format specifiers instead of percent format + "UP032", # [*] Use f-string instead of `format` call (PYTHON2.7 COMPAT) +] + +[tool.ruff.per-file-ignores] +# T201 == print(), T203 == pprint() +".github/workflows/*" = ["T201", "T203"] +"psutil/tests/runner.py" = ["T201", "T203"] +"scripts/*" = ["T201", "T203"] +"scripts/internal/*" = ["T201", "T203"] +"setup.py" = ["T201", "T203"] + +[tool.ruff.isort] +# https://beta.ruff.rs/docs/settings/#isort +force-single-line = true # one import per line +lines-after-imports = 2 [tool.coverage.report] -omit = [ - "psutil/_compat.py", - "psutil/tests/*", - "setup.py", -] exclude_lines = [ "enum.IntEnum", "except ImportError:", "globals().update", - "if __name__ == .__main__.:", - "if _WINDOWS:", "if BSD", - "if enum is None:", - "if enum is not None:", "if FREEBSD", - "if has_enums:", "if LINUX", "if LITTLE_ENDIAN:", "if MACOS", "if NETBSD", "if OPENBSD", - "if ppid_map is None:", "if PY3:", "if SUNOS", - "if sys.platform.startswith", "if WINDOWS", + "if _WINDOWS:", + "if __name__ == .__main__.:", + "if enum is None:", + "if enum is not None:", + "if has_enums:", + "if ppid_map is None:", + "if sys.platform.startswith", "import enum", "pragma: no cover", "raise NotImplementedError", ] +omit = [ + "psutil/_compat.py", + "psutil/tests/*", + "setup.py", +] [tool.pylint.messages_control] # Important ones: @@ -72,17 +151,32 @@ disable = [ "wrong-import-position", ] -[build-system] -requires = ["setuptools>=43", "wheel"] -build-backend = "setuptools.build_meta" +[tool.rstcheck] +ignore_messages = [ + "Duplicate explicit target name", + "Duplicate implicit target name", + "Hyperlink target \".*?\" is not referenced", +] + +[tool.tomlsort] +in_place = true +no_sort_tables = true +sort_inline_arrays = true +spaces_before_inline_comment = 2 +spaces_indent_inline_array = 4 +trailing_comma_inline_array = true [tool.cibuildwheel] -skip = ["pp*", "*-musllinux*"] -test-extras = "test" +skip = ["*-musllinux*", "pp*"] test-command = [ "env PYTHONWARNINGS=always PYTHONUNBUFFERED=1 PSUTIL_DEBUG=1 PSUTIL_SCRIPTS_DIR={project}/scripts python {project}/psutil/tests/runner.py", - "env PYTHONWARNINGS=always PYTHONUNBUFFERED=1 PSUTIL_DEBUG=1 PSUTIL_SCRIPTS_DIR={project}/scripts python {project}/psutil/tests/test_memleaks.py" + "env PYTHONWARNINGS=always PYTHONUNBUFFERED=1 PSUTIL_DEBUG=1 PSUTIL_SCRIPTS_DIR={project}/scripts python {project}/psutil/tests/test_memleaks.py", ] +test-extras = "test" [tool.cibuildwheel.macos] -archs = ["x86_64", "arm64"] +archs = ["arm64", "x86_64"] + +[build-system] +build-backend = "setuptools.build_meta" +requires = ["setuptools>=43", "wheel"] diff --git a/scripts/battery.py b/scripts/battery.py index bf0503e0e..040f94819 100755 --- a/scripts/battery.py +++ b/scripts/battery.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Show battery information. +"""Show battery information. $ python3 scripts/battery.py charge: 74% diff --git a/scripts/cpu_distribution.py b/scripts/cpu_distribution.py index ba71ca9cc..bfbb14b6c 100755 --- a/scripts/cpu_distribution.py +++ b/scripts/cpu_distribution.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Shows CPU workload split across different CPUs. +"""Shows CPU workload split across different CPUs. $ python3 scripts/cpu_workload.py CPU 0 CPU 1 CPU 2 CPU 3 CPU 4 CPU 5 CPU 6 CPU 7 diff --git a/scripts/disk_usage.py b/scripts/disk_usage.py index 65ae31382..d801c6ddd 100755 --- a/scripts/disk_usage.py +++ b/scripts/disk_usage.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -List all mounted disk partitions a-la "df -h" command. +"""List all mounted disk partitions a-la "df -h" command. $ python3 scripts/disk_usage.py Device Total Used Free Use % Type Mount diff --git a/scripts/fans.py b/scripts/fans.py index 304277157..a9a8b8e67 100755 --- a/scripts/fans.py +++ b/scripts/fans.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Show fans information. +"""Show fans information. $ python fans.py asus diff --git a/scripts/free.py b/scripts/free.py index 8c3359d86..f72149ac3 100755 --- a/scripts/free.py +++ b/scripts/free.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -A clone of 'free' cmdline utility. +"""A clone of 'free' cmdline utility. $ python3 scripts/free.py total used free shared buffers cache diff --git a/scripts/ifconfig.py b/scripts/ifconfig.py index 23fd26b47..7fdfa1e12 100755 --- a/scripts/ifconfig.py +++ b/scripts/ifconfig.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -A clone of 'ifconfig' on UNIX. +"""A clone of 'ifconfig' on UNIX. $ python3 scripts/ifconfig.py lo: diff --git a/scripts/internal/bench_oneshot.py b/scripts/internal/bench_oneshot.py index 605958760..74f8150ae 100755 --- a/scripts/internal/bench_oneshot.py +++ b/scripts/internal/bench_oneshot.py @@ -4,10 +4,9 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -A simple micro benchmark script which prints the speedup when using +"""A simple micro benchmark script which prints the speedup when using Process.oneshot() ctx manager. -See: https://github.com/giampaolo/psutil/issues/799 +See: https://github.com/giampaolo/psutil/issues/799. """ from __future__ import division diff --git a/scripts/internal/bench_oneshot_2.py b/scripts/internal/bench_oneshot_2.py index 051d00360..2a63dca25 100755 --- a/scripts/internal/bench_oneshot_2.py +++ b/scripts/internal/bench_oneshot_2.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Same as bench_oneshot.py but uses perf module instead, which is +"""Same as bench_oneshot.py but uses perf module instead, which is supposed to be more precise. """ diff --git a/scripts/internal/check_broken_links.py b/scripts/internal/check_broken_links.py index f5375a860..315f06fa1 100755 --- a/scripts/internal/check_broken_links.py +++ b/scripts/internal/check_broken_links.py @@ -4,8 +4,7 @@ # All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. -""" -Checks for broken links in file names specified as command line +"""Checks for broken links in file names specified as command line parameters. There are a ton of a solutions available for validating URLs in string @@ -161,7 +160,7 @@ def parse_c(fname): def parse_generic(fname): - with open(fname, 'rt', errors='ignore') as f: + with open(fname, errors='ignore') as f: text = f.read() return find_urls(text) @@ -172,10 +171,10 @@ def get_urls(fname): return parse_rst(fname) elif fname.endswith('.py'): return parse_py(fname) - elif fname.endswith('.c') or fname.endswith('.h'): + elif fname.endswith(('.c', '.h')): return parse_c(fname) else: - with open(fname, 'rt', errors='ignore') as f: + with open(fname, errors='ignore') as f: if f.readline().strip().startswith('#!/usr/bin/env python3'): return parse_py(fname) return parse_generic(fname) @@ -198,8 +197,8 @@ def validate_url(url): def parallel_validator(urls): - """validates all urls in parallel - urls: tuple(filename, url) + """Validates all urls in parallel + urls: tuple(filename, url). """ fails = [] # list of tuples (filename, url) current = 0 diff --git a/scripts/internal/clinter.py b/scripts/internal/clinter.py index 384951da8..71c77e402 100755 --- a/scripts/internal/clinter.py +++ b/scripts/internal/clinter.py @@ -54,13 +54,13 @@ def check_line(path, line, idx, lines): warn(path, line, lineno, "no blank line at EOF") ss = s.strip() - if ss.startswith(("printf(", "printf (", )): + if ss.startswith(("printf(", "printf (")): if not ss.endswith(("// NOQA", "// NOQA")): warn(path, line, lineno, "printf() statement") def process(path): - with open(path, 'rt') as f: + with open(path) as f: lines = f.readlines() for idx, line in enumerate(lines): check_line(path, line, idx, lines) diff --git a/scripts/internal/convert_readme.py b/scripts/internal/convert_readme.py index d96c6c5d3..0c4fade50 100755 --- a/scripts/internal/convert_readme.py +++ b/scripts/internal/convert_readme.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Remove raw HTML from README.rst to make it compatible with PyPI on +"""Remove raw HTML from README.rst to make it compatible with PyPI on dist upload. """ diff --git a/scripts/internal/download_wheels_appveyor.py b/scripts/internal/download_wheels_appveyor.py index dcded559b..47a33d996 100755 --- a/scripts/internal/download_wheels_appveyor.py +++ b/scripts/internal/download_wheels_appveyor.py @@ -4,12 +4,11 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Script which downloads wheel files hosted on AppVeyor: +"""Script which downloads wheel files hosted on AppVeyor: https://ci.appveyor.com/project/giampaolo/psutil Re-adapted from the original recipe of Ibarra Corretge' : -http://code.saghul.net/index.php/2015/09/09/ +http://code.saghul.net/index.php/2015/09/09/. """ from __future__ import print_function @@ -20,13 +19,14 @@ import requests -from psutil import __version__ as PSUTIL_VERSION +from psutil import __version__ from psutil._common import bytes2human from psutil._common import print_color USER = "giampaolo" PROJECT = "psutil" +PROJECT_VERSION = __version__ BASE_URL = 'https://ci.appveyor.com/api' PY_VERSIONS = ['2.7'] TIMEOUT = 30 @@ -71,12 +71,12 @@ def get_file_urls(): def rename_win27_wheels(): # See: https://github.com/giampaolo/psutil/issues/810 - src = 'dist/psutil-%s-cp27-cp27m-win32.whl' % PSUTIL_VERSION - dst = 'dist/psutil-%s-cp27-none-win32.whl' % PSUTIL_VERSION + src = 'dist/psutil-%s-cp27-cp27m-win32.whl' % PROJECT_VERSION + dst = 'dist/psutil-%s-cp27-none-win32.whl' % PROJECT_VERSION print("rename: %s\n %s" % (src, dst)) os.rename(src, dst) - src = 'dist/psutil-%s-cp27-cp27m-win_amd64.whl' % PSUTIL_VERSION - dst = 'dist/psutil-%s-cp27-none-win_amd64.whl' % PSUTIL_VERSION + src = 'dist/psutil-%s-cp27-cp27m-win_amd64.whl' % PROJECT_VERSION + dst = 'dist/psutil-%s-cp27-none-win_amd64.whl' % PROJECT_VERSION print("rename: %s\n %s" % (src, dst)) os.rename(src, dst) diff --git a/scripts/internal/download_wheels_github.py b/scripts/internal/download_wheels_github.py index 00f57116f..de6c34faa 100755 --- a/scripts/internal/download_wheels_github.py +++ b/scripts/internal/download_wheels_github.py @@ -4,15 +4,14 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Script which downloads wheel files hosted on GitHub: +"""Script which downloads wheel files hosted on GitHub: https://github.com/giampaolo/psutil/actions It needs an access token string generated from personal GitHub profile: https://github.com/settings/tokens The token must be created with at least "public_repo" scope/rights. If you lose it, just generate a new token. REST API doc: -https://developer.github.com/v3/actions/artifacts/ +https://developer.github.com/v3/actions/artifacts/. """ import argparse @@ -23,21 +22,24 @@ import requests -from psutil import __version__ as PSUTIL_VERSION +from psutil import __version__ from psutil._common import bytes2human from psutil.tests import safe_rmpath USER = "giampaolo" PROJECT = "psutil" +PROJECT_VERSION = __version__ OUTFILE = "wheels-github.zip" TOKEN = "" +TIMEOUT = 30 def get_artifacts(): base_url = "https://api.github.com/repos/%s/%s" % (USER, PROJECT) url = base_url + "/actions/artifacts" - res = requests.get(url=url, headers={"Authorization": "token %s" % TOKEN}) + res = requests.get(url=url, headers={ + "Authorization": "token %s" % TOKEN}, timeout=TIMEOUT) res.raise_for_status() data = json.loads(res.content) return data @@ -45,7 +47,8 @@ def get_artifacts(): def download_zip(url): print("downloading: " + url) - res = requests.get(url=url, headers={"Authorization": "token %s" % TOKEN}) + res = requests.get(url=url, headers={ + "Authorization": "token %s" % TOKEN}, timeout=TIMEOUT) res.raise_for_status() totbytes = 0 with open(OUTFILE, 'wb') as f: @@ -57,13 +60,13 @@ def download_zip(url): def rename_win27_wheels(): # See: https://github.com/giampaolo/psutil/issues/810 - src = 'dist/psutil-%s-cp27-cp27m-win32.whl' % PSUTIL_VERSION - dst = 'dist/psutil-%s-cp27-none-win32.whl' % PSUTIL_VERSION + src = 'dist/psutil-%s-cp27-cp27m-win32.whl' % PROJECT_VERSION + dst = 'dist/psutil-%s-cp27-none-win32.whl' % PROJECT_VERSION if os.path.exists(src): print("rename: %s\n %s" % (src, dst)) os.rename(src, dst) - src = 'dist/psutil-%s-cp27-cp27m-win_amd64.whl' % PSUTIL_VERSION - dst = 'dist/psutil-%s-cp27-none-win_amd64.whl' % PSUTIL_VERSION + src = 'dist/psutil-%s-cp27-cp27m-win_amd64.whl' % PROJECT_VERSION + dst = 'dist/psutil-%s-cp27-none-win_amd64.whl' % PROJECT_VERSION if os.path.exists(src): print("rename: %s\n %s" % (src, dst)) os.rename(src, dst) diff --git a/scripts/internal/generate_manifest.py b/scripts/internal/generate_manifest.py index b7ad8c7e1..290e8b49f 100755 --- a/scripts/internal/generate_manifest.py +++ b/scripts/internal/generate_manifest.py @@ -4,11 +4,10 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Generate MANIFEST.in file. -""" +"""Generate MANIFEST.in file.""" import os +import shlex import subprocess @@ -19,7 +18,7 @@ def sh(cmd): return subprocess.check_output( - cmd, shell=True, universal_newlines=True).strip() + shlex.split(cmd), universal_newlines=True).strip() def main(): diff --git a/scripts/internal/git_pre_commit.py b/scripts/internal/git_pre_commit.py index dad0066b2..92852f836 100755 --- a/scripts/internal/git_pre_commit.py +++ b/scripts/internal/git_pre_commit.py @@ -4,20 +4,10 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -This gets executed on 'git commit' and rejects the commit in case the -submitted code does not pass validation. Validation is run only against -the files which were modified in the commit. Checks: - -- assert no space at EOLs -- assert not pdb.set_trace in code -- assert no bare except clause ("except:") in code -- assert "flake8" checks pass -- assert "isort" checks pass -- assert C linter checks pass -- abort if files were added/renamed/removed and MANIFEST.in was not updated - -Install this with "make install-git-hooks". +"""This gets executed on 'git commit' and rejects the commit in case +the submitted code does not pass validation. Validation is run only +against the files which were modified in the commit. Install this with +"make install-git-hooks". """ from __future__ import print_function @@ -29,7 +19,7 @@ PYTHON = sys.executable -PY3 = sys.version_info[0] == 3 +PY3 = sys.version_info[0] >= 3 THIS_SCRIPT = os.path.realpath(__file__) @@ -66,8 +56,12 @@ def exit(msg): def sh(cmd): - p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, universal_newlines=True) + if isinstance(cmd, str): + cmd = shlex.split(cmd) + p = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True + ) stdout, stderr = p.communicate() if p.returncode != 0: raise RuntimeError(stderr) @@ -80,69 +74,72 @@ def sh(cmd): def open_text(path): kw = {'encoding': 'utf8'} if PY3 else {} - return open(path, 'rt', **kw) + return open(path, **kw) def git_commit_files(): - out = sh("git diff --cached --name-only") + out = sh(["git", "diff", "--cached", "--name-only"]) py_files = [x for x in out.split('\n') if x.endswith('.py') and os.path.exists(x)] c_files = [x for x in out.split('\n') if x.endswith(('.c', '.h')) and os.path.exists(x)] - new_rm_mv = sh("git diff --name-only --diff-filter=ADR --cached") + rst_files = [x for x in out.split('\n') if x.endswith('.rst') and + os.path.exists(x)] + toml_files = [ + x for x in out.split("\n") if x.endswith(".toml") and os.path.exists(x) + ] + new_rm_mv = sh( + ["git", "diff", "--name-only", "--diff-filter=ADR", "--cached"] + ) # XXX: we should escape spaces and possibly other amenities here new_rm_mv = new_rm_mv.split() - return (py_files, c_files, new_rm_mv) + return (py_files, c_files, rst_files, toml_files, new_rm_mv) + + +def ruff(files): + print("running ruff (%s)" % len(files)) + cmd = [PYTHON, "-m", "ruff", "check", "--no-cache"] + files + if subprocess.call(cmd) != 0: + return exit( + "Python code didn't pass 'ruff' style check." + "Try running 'make fix-ruff'." + ) + + +def c_linter(files): + print("running clinter (%s)" % len(files)) + # XXX: we should escape spaces and possibly other amenities here + cmd = [PYTHON, "scripts/internal/clinter.py"] + files + if subprocess.call(cmd) != 0: + return sys.exit("C code didn't pass style check") + + +def toml_sort(files): + print("running toml linter (%s)" % len(files)) + cmd = ["toml-sort", "--check"] + files + if subprocess.call(cmd) != 0: + return sys.exit("%s didn't pass style check" % ' '.join(files)) + + +def rstcheck(files): + print("running rst linter (%s)" % len(files)) + cmd = ["rstcheck", "--config=pyproject.toml"] + files + if subprocess.call(cmd) != 0: + return sys.exit("RST code didn't pass style check") def main(): - py_files, c_files, new_rm_mv = git_commit_files() - # Check file content. - for path in py_files: - if os.path.realpath(path) == THIS_SCRIPT: - continue - with open_text(path) as f: - lines = f.readlines() - for lineno, line in enumerate(lines, 1): - # space at end of line - if line.endswith(' '): - print("%s:%s %r" % (path, lineno, line)) - return sys.exit("space at end of line") - line = line.rstrip() - # # pdb (now provided by flake8-debugger plugin) - # if "pdb.set_trace" in line: - # print("%s:%s %s" % (path, lineno, line)) - # return sys.exit("you forgot a pdb in your python code") - # # bare except clause (now provided by flake8-blind-except plugin) - # if "except:" in line and not line.endswith("# NOQA"): - # print("%s:%s %s" % (path, lineno, line)) - # return sys.exit("bare except clause") - - # Python linters + py_files, c_files, rst_files, toml_files, new_rm_mv = git_commit_files() if py_files: - # flake8 - assert os.path.exists('.flake8') - cmd = "%s -m flake8 --config=.flake8 %s" % (PYTHON, " ".join(py_files)) - ret = subprocess.call(shlex.split(cmd)) - if ret != 0: - return sys.exit("python code didn't pass 'flake8' style check; " - "try running 'make fix-flake8'") - # isort - cmd = "%s -m isort --check-only %s" % ( - PYTHON, " ".join(py_files)) - ret = subprocess.call(shlex.split(cmd)) - if ret != 0: - return sys.exit("python code didn't pass 'isort' style check; " - "try running 'make fix-imports'") - # C linter + ruff(py_files) if c_files: - # XXX: we should escape spaces and possibly other amenities here - cmd = "%s scripts/internal/clinter.py %s" % (PYTHON, " ".join(c_files)) - ret = subprocess.call(cmd, shell=True) - if ret != 0: - return sys.exit("C code didn't pass style check") + c_linter(c_files) + if rst_files: + rstcheck(rst_files) + if toml_files: + toml_sort(toml_files) if new_rm_mv: - out = sh("%s scripts/internal/generate_manifest.py" % PYTHON) + out = sh([PYTHON, "scripts/internal/generate_manifest.py"]) with open_text('MANIFEST.in') as f: if out.strip() != f.read().strip(): sys.exit("some files were added, deleted or renamed; " diff --git a/scripts/internal/print_access_denied.py b/scripts/internal/print_access_denied.py index f3d0166e5..7759ca7b2 100755 --- a/scripts/internal/print_access_denied.py +++ b/scripts/internal/print_access_denied.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Helper script iterates over all processes and . +"""Helper script iterates over all processes and . It prints how many AccessDenied exceptions are raised in total and for what Process method. diff --git a/scripts/internal/print_announce.py b/scripts/internal/print_announce.py index 33fca4220..2297c0950 100755 --- a/scripts/internal/print_announce.py +++ b/scripts/internal/print_announce.py @@ -4,9 +4,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Prints release announce based on HISTORY.rst file content. -See: https://pip.pypa.io/en/stable/reference/pip_install/#hash-checking-mode +"""Prints release announce based on HISTORY.rst file content. +See: https://pip.pypa.io/en/stable/reference/pip_install/#hash-checking-mode. """ import os @@ -14,7 +13,7 @@ import subprocess import sys -from psutil import __version__ as PRJ_VERSION +from psutil import __version__ HERE = os.path.abspath(os.path.dirname(__file__)) @@ -24,6 +23,7 @@ ROOT, 'scripts', 'internal', 'print_hashes.py') PRJ_NAME = 'psutil' +PRJ_VERSION = __version__ PRJ_URL_HOME = 'https://github.com/giampaolo/psutil' PRJ_URL_DOC = 'http://psutil.readthedocs.io' PRJ_URL_DOWNLOAD = 'https://pypi.org/project/psutil/#files' diff --git a/scripts/internal/print_api_speed.py b/scripts/internal/print_api_speed.py index e85b70382..8abaed0c4 100755 --- a/scripts/internal/print_api_speed.py +++ b/scripts/internal/print_api_speed.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Benchmark all API calls and print them from fastest to slowest. +"""Benchmark all API calls and print them from fastest to slowest. $ make print_api_speed SYSTEM APIS NUM CALLS SECONDS @@ -192,8 +191,9 @@ def main(): print_timings() if not prio_set: - print_color("\nWARN: couldn't set highest process priority " + - "(requires root)", "red") + msg = "\nWARN: couldn't set highest process priority " + msg += "(requires root)" + print_color(msg, "red") if __name__ == '__main__': diff --git a/scripts/internal/print_downloads.py b/scripts/internal/print_downloads.py index b6df3b38c..1ee37e534 100755 --- a/scripts/internal/print_downloads.py +++ b/scripts/internal/print_downloads.py @@ -4,18 +4,18 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Print PYPI statistics in MarkDown format. +"""Print PYPI statistics in MarkDown format. Useful sites: * https://pepy.tech/project/psutil * https://pypistats.org/packages/psutil -* https://hugovk.github.io/top-pypi-packages/ +* https://hugovk.github.io/top-pypi-packages/. """ from __future__ import print_function import json import os +import shlex import subprocess import sys @@ -28,8 +28,10 @@ PKGNAME = 'psutil' DAYS = 30 LIMIT = 100 -GITHUB_SCRIPT_URL = "https://github.com/giampaolo/psutil/blob/master/" \ - "scripts/internal/pypistats.py" +GITHUB_SCRIPT_URL = ( + "https://github.com/giampaolo/psutil/blob/master/" + "scripts/internal/pypistats.py" +) LAST_UPDATE = None bytes_billed = 0 @@ -41,7 +43,7 @@ def sh(cmd): assert os.path.exists(AUTH_FILE) env = os.environ.copy() env['GOOGLE_APPLICATION_CREDENTIALS'] = AUTH_FILE - p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) stdout, stderr = p.communicate() if p.returncode != 0: @@ -108,7 +110,7 @@ def downloads_by_distro(): def print_row(left, right): if isinstance(right, int): - right = '{0:,}'.format(right) + right = '{:,}'.format(right) print(templ % (left, right)) diff --git a/scripts/internal/print_hashes.py b/scripts/internal/print_hashes.py index 69bc9edbe..68e06201c 100755 --- a/scripts/internal/print_hashes.py +++ b/scripts/internal/print_hashes.py @@ -4,9 +4,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Prints files hashes, see: -https://pip.pypa.io/en/stable/reference/pip_install/#hash-checking-mode +"""Prints files hashes, see: +https://pip.pypa.io/en/stable/reference/pip_install/#hash-checking-mode. """ import argparse diff --git a/scripts/internal/print_timeline.py b/scripts/internal/print_timeline.py index 0ea7355eb..a046e670a 100755 --- a/scripts/internal/print_timeline.py +++ b/scripts/internal/print_timeline.py @@ -4,10 +4,9 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Prints releases' timeline in RST format. -""" +"""Prints releases' timeline in RST format.""" +import shlex import subprocess @@ -20,7 +19,7 @@ def sh(cmd): return subprocess.check_output( - cmd, shell=True, universal_newlines=True).strip() + shlex.split(cmd), universal_newlines=True).strip() def get_tag_date(tag): diff --git a/scripts/internal/purge_installation.py b/scripts/internal/purge_installation.py index 8a9597f0b..55b2f5c50 100755 --- a/scripts/internal/purge_installation.py +++ b/scripts/internal/purge_installation.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Purge psutil installation by removing psutil-related files and +"""Purge psutil installation by removing psutil-related files and directories found in site-packages directories. This is needed mainly because sometimes "import psutil" imports a leftover installation from site-packages directory instead of the main working directory. diff --git a/scripts/internal/winmake.py b/scripts/internal/winmake.py index 5ec2ecbfd..6978a7e61 100755 --- a/scripts/internal/winmake.py +++ b/scripts/internal/winmake.py @@ -28,23 +28,15 @@ APPVEYOR = bool(os.environ.get('APPVEYOR')) -if APPVEYOR: - PYTHON = sys.executable -else: - PYTHON = os.getenv('PYTHON', sys.executable) +PYTHON = sys.executable if APPVEYOR else os.getenv('PYTHON', sys.executable) RUNNER_PY = 'psutil\\tests\\runner.py' GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" -PY3 = sys.version_info[0] == 3 +PY3 = sys.version_info[0] >= 3 HERE = os.path.abspath(os.path.dirname(__file__)) ROOT_DIR = os.path.realpath(os.path.join(HERE, "..", "..")) PYPY = '__pypy__' in sys.builtin_module_names DEPS = [ "coverage", - "flake8", - "flake8-blind-except", - "flake8-debugger", - "flake8-print", - "nose", "pdbpp", "pip", "pyperf", @@ -58,8 +50,6 @@ DEPS.append('mock') DEPS.append('ipaddress') DEPS.append('enum34') -else: - DEPS.append('flake8-bugbear') if not PYPY: DEPS.append("pywin32") @@ -124,7 +114,7 @@ def win_colorprint(s, color=LIGHTBLUE): def sh(cmd, nolog=False): if not nolog: safe_print("cmd: " + cmd) - p = subprocess.Popen(cmd, shell=True, env=os.environ, cwd=os.getcwd()) + p = subprocess.Popen(cmd, shell=True, env=os.environ, cwd=os.getcwd()) # noqa p.communicate() if p.returncode != 0: sys.exit(p.returncode) @@ -198,7 +188,7 @@ def onerror(fun, path, excinfo): def recursive_rm(*patterns): """Recursively remove a file or matching a list of patterns.""" - for root, dirs, files in os.walk(u'.'): + for root, dirs, files in os.walk('.'): root = os.path.normpath(root) if root.startswith('.git/'): continue @@ -218,7 +208,7 @@ def recursive_rm(*patterns): def build(): - """Build / compile""" + """Build / compile.""" # Make sure setuptools is installed (needed for 'develop' / # edit mode). sh('%s -c "import setuptools"' % PYTHON) @@ -269,7 +259,7 @@ def upload_wheels(): def install_pip(): - """Install pip""" + """Install pip.""" try: sh('%s -c "import pip"' % PYTHON) except SystemExit: @@ -298,13 +288,13 @@ def install_pip(): def install(): - """Install in develop / edit mode""" + """Install in develop / edit mode.""" build() sh("%s setup.py develop" % PYTHON) def uninstall(): - """Uninstall psutil""" + """Uninstall psutil.""" # Uninstalling psutil on Windows seems to be tricky. # On "import psutil" tests may import a psutil version living in # C:\PythonXY\Lib\site-packages which is not what we want, so @@ -333,7 +323,7 @@ def uninstall(): # easy_install can add a line (installation path) into # easy-install.pth; that line alters sys.path. path = os.path.join(dir, name) - with open(path, 'rt') as f: + with open(path) as f: lines = f.readlines() hasit = False for line in lines: @@ -341,7 +331,7 @@ def uninstall(): hasit = True break if hasit: - with open(path, 'wt') as f: + with open(path, "w") as f: for line in lines: if 'psutil' not in line: f.write(line) @@ -350,7 +340,7 @@ def uninstall(): def clean(): - """Deletes dev files""" + """Deletes dev files.""" recursive_rm( "$testfn*", "*.bak", @@ -376,24 +366,14 @@ def clean(): def setup_dev_env(): - """Install useful deps""" + """Install useful deps.""" install_pip() install_git_hooks() sh("%s -m pip install -U %s" % (PYTHON, " ".join(DEPS))) -def flake8(): - """Run flake8 against all py files""" - py_files = subprocess.check_output("git ls-files") - if PY3: - py_files = py_files.decode() - py_files = [x for x in py_files.split() if x.endswith('.py')] - py_files = ' '.join(py_files) - sh("%s -m flake8 %s" % (PYTHON, py_files), nolog=True) - - def test(name=RUNNER_PY): - """Run tests""" + """Run tests.""" build() sh("%s %s" % (PYTHON, name)) @@ -409,55 +389,55 @@ def coverage(): def test_process(): - """Run process tests""" + """Run process tests.""" build() sh("%s psutil\\tests\\test_process.py" % PYTHON) def test_system(): - """Run system tests""" + """Run system tests.""" build() sh("%s psutil\\tests\\test_system.py" % PYTHON) def test_platform(): - """Run windows only tests""" + """Run windows only tests.""" build() sh("%s psutil\\tests\\test_windows.py" % PYTHON) def test_misc(): - """Run misc tests""" + """Run misc tests.""" build() sh("%s psutil\\tests\\test_misc.py" % PYTHON) def test_unicode(): - """Run unicode tests""" + """Run unicode tests.""" build() sh("%s psutil\\tests\\test_unicode.py" % PYTHON) def test_connections(): - """Run connections tests""" + """Run connections tests.""" build() sh("%s psutil\\tests\\test_connections.py" % PYTHON) def test_contracts(): - """Run contracts tests""" + """Run contracts tests.""" build() sh("%s psutil\\tests\\test_contracts.py" % PYTHON) def test_testutils(): - """Run test utilities tests""" + """Run test utilities tests.""" build() sh("%s psutil\\tests\\test_testutils.py" % PYTHON) def test_by_name(name): - """Run test by name""" + """Run test by name.""" build() sh("%s -m unittest -v %s" % (PYTHON, name)) @@ -469,7 +449,7 @@ def test_failed(): def test_memleaks(): - """Run memory leaks tests""" + """Run memory leaks tests.""" build() sh("%s psutil\\tests\\test_memleaks.py" % PYTHON) @@ -481,8 +461,8 @@ def install_git_hooks(): ROOT_DIR, "scripts", "internal", "git_pre_commit.py") dst = os.path.realpath( os.path.join(ROOT_DIR, ".git", "hooks", "pre-commit")) - with open(src, "rt") as s: - with open(dst, "wt") as d: + with open(src) as s: + with open(dst, "w") as d: d.write(s.read()) @@ -572,7 +552,6 @@ def parse_args(): sp.add_parser('install', help="build + install in develop/edit mode") sp.add_parser('install-git-hooks', help="install GIT pre-commit hook") sp.add_parser('install-pip', help="install pip") - sp.add_parser('flake8', help="run flake8 against all py files") sp.add_parser('print-access-denied', help="print AD exceptions") sp.add_parser('print-api-speed', help="benchmark all API calls") sp.add_parser('setup-dev-env', help="install deps") diff --git a/scripts/iotop.py b/scripts/iotop.py index 07ae2fa3d..a8f04c870 100755 --- a/scripts/iotop.py +++ b/scripts/iotop.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -A clone of iotop (http://guichaz.free.fr/iotop/) showing real time +"""A clone of iotop (http://guichaz.free.fr/iotop/) showing real time disk I/O statistics. It works on Linux only (FreeBSD and macOS are missing support for IO @@ -143,7 +142,7 @@ def refresh_window(procs, disks_read, disks_write): def setup(): curses.start_color() curses.use_default_colors() - for i in range(0, curses.COLORS): + for i in range(curses.COLORS): curses.init_pair(i + 1, i, -1) curses.endwin() win.nodelay(1) diff --git a/scripts/killall.py b/scripts/killall.py index d985185f8..0308370de 100755 --- a/scripts/killall.py +++ b/scripts/killall.py @@ -4,9 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Kill a process by name. -""" +"""Kill a process by name.""" import os import sys diff --git a/scripts/meminfo.py b/scripts/meminfo.py index b98aa60be..a13b7e00b 100755 --- a/scripts/meminfo.py +++ b/scripts/meminfo.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Print system memory information. +"""Print system memory information. $ python3 scripts/meminfo.py MEMORY diff --git a/scripts/netstat.py b/scripts/netstat.py index 476b082e5..7a9b2908c 100755 --- a/scripts/netstat.py +++ b/scripts/netstat.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -A clone of 'netstat -antp' on Linux. +"""A clone of 'netstat -antp' on Linux. $ python3 scripts/netstat.py Proto Local address Remote address Status PID Program name diff --git a/scripts/nettop.py b/scripts/nettop.py index 9e1abe764..fedc644d0 100755 --- a/scripts/nettop.py +++ b/scripts/nettop.py @@ -6,8 +6,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Shows real-time network statistics. +"""Shows real-time network statistics. Author: Giampaolo Rodola' @@ -128,7 +127,7 @@ def refresh_window(tot_before, tot_after, pnic_before, pnic_after): def setup(): curses.start_color() curses.use_default_colors() - for i in range(0, curses.COLORS): + for i in range(curses.COLORS): curses.init_pair(i + 1, i, -1) curses.endwin() win.nodelay(1) diff --git a/scripts/pidof.py b/scripts/pidof.py index da9371072..b809fafbe 100755 --- a/scripts/pidof.py +++ b/scripts/pidof.py @@ -5,8 +5,8 @@ # found in the LICENSE file. -""" -A clone of 'pidof' cmdline utility. +"""A clone of 'pidof' cmdline utility. + $ pidof python 1140 1138 1136 1134 1133 1129 1127 1125 1121 1120 1119 """ diff --git a/scripts/pmap.py b/scripts/pmap.py index 459927bfd..56c1b4882 100755 --- a/scripts/pmap.py +++ b/scripts/pmap.py @@ -4,9 +4,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -A clone of 'pmap' utility on Linux, 'vmmap' on macOS and 'procstat -v' on BSD. -Report memory map of a process. +"""A clone of 'pmap' utility on Linux, 'vmmap' on macOS and 'procstat +-v' on BSD. Report memory map of a process. $ python3 scripts/pmap.py 32402 Address RSS Mode Mapping diff --git a/scripts/procinfo.py b/scripts/procinfo.py index fd44c83e7..562a61a46 100755 --- a/scripts/procinfo.py +++ b/scripts/procinfo.py @@ -4,8 +4,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Print detailed information about a process. +"""Print detailed information about a process. + Author: Giampaolo Rodola' $ python3 scripts/procinfo.py @@ -147,10 +147,7 @@ def run(pid, verbose=False): with proc.oneshot(): try: parent = proc.parent() - if parent: - parent = '(%s)' % parent.name() - else: - parent = '' + parent = '(%s)' % parent.name() if parent else '' except psutil.Error: parent = '' try: @@ -175,7 +172,7 @@ def run(pid, verbose=False): cpu_tot_time = datetime.timedelta(seconds=sum(pinfo['cpu_times'])) cpu_tot_time = "%s:%s.%s" % ( cpu_tot_time.seconds // 60 % 60, - str((cpu_tot_time.seconds % 60)).zfill(2), + str(cpu_tot_time.seconds % 60).zfill(2), str(cpu_tot_time.microseconds)[:2]) print_('cpu-tspent', cpu_tot_time) print_('cpu-times', str_ntuple(pinfo['cpu_times'])) diff --git a/scripts/procsmem.py b/scripts/procsmem.py index ca03729ec..574e37041 100755 --- a/scripts/procsmem.py +++ b/scripts/procsmem.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Show detailed memory usage about all (querable) processes. +"""Show detailed memory usage about all (querable) processes. Processes are sorted by their "USS" (Unique Set Size) memory, which is probably the most representative metric for determining how much memory diff --git a/scripts/ps.py b/scripts/ps.py index a234209fb..58a1d8c29 100755 --- a/scripts/ps.py +++ b/scripts/ps.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -A clone of 'ps aux'. +"""A clone of 'ps aux'. $ python3 scripts/ps.py USER PID %MEM VSZ RSS NICE STATUS START TIME CMDLINE diff --git a/scripts/pstree.py b/scripts/pstree.py index 18732b8cb..e873e467d 100755 --- a/scripts/pstree.py +++ b/scripts/pstree.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -Similar to 'ps aux --forest' on Linux, prints the process list +"""Similar to 'ps aux --forest' on Linux, prints the process list as a tree structure. $ python3 scripts/pstree.py diff --git a/scripts/sensors.py b/scripts/sensors.py index 3dc823803..a5f9729b4 100755 --- a/scripts/sensors.py +++ b/scripts/sensors.py @@ -5,8 +5,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -A clone of 'sensors' utility on Linux printing hardware temperatures, +"""A clone of 'sensors' utility on Linux printing hardware temperatures, fans speed and battery info. $ python3 scripts/sensors.py @@ -45,10 +44,7 @@ def main(): temps = psutil.sensors_temperatures() else: temps = {} - if hasattr(psutil, "sensors_fans"): - fans = psutil.sensors_fans() - else: - fans = {} + fans = psutil.sensors_fans() if hasattr(psutil, "sensors_fans") else {} if hasattr(psutil, "sensors_battery"): battery = psutil.sensors_battery() else: diff --git a/scripts/temperatures.py b/scripts/temperatures.py index 90097e514..a211b8873 100755 --- a/scripts/temperatures.py +++ b/scripts/temperatures.py @@ -5,8 +5,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -A clone of 'sensors' utility on Linux printing hardware temperatures. +"""A clone of 'sensors' utility on Linux printing hardware temperatures. $ python3 scripts/sensors.py asus diff --git a/scripts/top.py b/scripts/top.py index e07a58f1a..675f541ef 100755 --- a/scripts/top.py +++ b/scripts/top.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -A clone of top / htop. +"""A clone of top / htop. Author: Giampaolo Rodola' @@ -117,7 +116,7 @@ def print_header(procs_status, num_procs): """Print system-related info, above the process list.""" def get_dashes(perc): - dashes = "|" * int((float(perc) / 10 * 4)) + dashes = "|" * int(float(perc) / 10 * 4) empty_dashes = " " * (40 - len(dashes)) return dashes, empty_dashes @@ -182,7 +181,7 @@ def refresh_window(procs, procs_status): if p.dict['cpu_times'] is not None: ctime = datetime.timedelta(seconds=sum(p.dict['cpu_times'])) ctime = "%s:%s.%s" % (ctime.seconds // 60 % 60, - str((ctime.seconds % 60)).zfill(2), + str(ctime.seconds % 60).zfill(2), str(ctime.microseconds)[:2]) else: ctime = '' @@ -192,10 +191,7 @@ def refresh_window(procs, procs_status): p.dict['memory_percent'] = '' if p.dict['cpu_percent'] is None: p.dict['cpu_percent'] = '' - if p.dict['username']: - username = p.dict['username'][:8] - else: - username = "" + username = p.dict['username'][:8] if p.dict['username'] else '' line = templ % (p.pid, username, p.dict['nice'], @@ -216,7 +212,7 @@ def refresh_window(procs, procs_status): def setup(): curses.start_color() curses.use_default_colors() - for i in range(0, curses.COLORS): + for i in range(curses.COLORS): curses.init_pair(i + 1, i, -1) curses.endwin() win.nodelay(1) diff --git a/scripts/who.py b/scripts/who.py index c1e407299..18db1b17a 100755 --- a/scripts/who.py +++ b/scripts/who.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -""" -A clone of 'who' command; print information about users who are +"""A clone of 'who' command; print information about users who are currently logged in. $ python3 scripts/who.py diff --git a/scripts/winservices.py b/scripts/winservices.py index 5c710159b..d9c6a14a9 100755 --- a/scripts/winservices.py +++ b/scripts/winservices.py @@ -4,8 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -r""" -List all Windows services installed. +r"""List all Windows services installed. $ python3 scripts/winservices.py AeLookupSvc (Application Experience) diff --git a/setup.py b/setup.py index 35467e131..eef7bf455 100755 --- a/setup.py +++ b/setup.py @@ -8,6 +8,7 @@ from __future__ import print_function +import ast import contextlib import glob import io @@ -98,10 +99,10 @@ def get_version(): INIT = os.path.join(HERE, 'psutil/__init__.py') - with open(INIT, 'r') as f: + with open(INIT) as f: for line in f: if line.startswith('__version__'): - ret = eval(line.strip().split(' = ')[1]) + ret = ast.literal_eval(line.strip().split(' = ')[1]) assert ret.count('.') == 2, ret for num in ret.split('.'): assert num.isdigit(), ret