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