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/build.yml b/.github/workflows/build.yml index 3f3ad76d9..4bb8d108b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -115,7 +115,7 @@ jobs: python-version: 3.x - name: 'Run linters' run: | - python3 -m pip install isort rstcheck toml-sort flake8 flake8-blind-except flake8-bugbear flake8-debugger flake8-print flake8-quotes sphinx + python3 -m pip install ruff rstcheck toml-sort sphinx make lint-all # Check sanity of .tar.gz + wheel files 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/HISTORY.rst b/HISTORY.rst index 3b00b3b9d..78e533234 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -17,6 +17,8 @@ XXXX-XX-XX - 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** diff --git a/MANIFEST.in b/MANIFEST.in index 78cb836a6..fb25643af 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ -include .flake8 include .gitignore include CONTRIBUTING.md include CREDITS diff --git a/Makefile b/Makefile index 95276f664..862c8b6c4 100644 --- a/Makefile +++ b/Makefile @@ -9,19 +9,9 @@ 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 \ @@ -82,6 +72,7 @@ clean: ## Remove all build files. .coverage \ .failed-tests.txt \ .pytest_cache \ + .ruff_cache/ \ build/ \ dist/ \ docs/_build/ \ @@ -200,11 +191,8 @@ test-coverage: ## Run test coverage. # Linters # =================================================================== -flake8: ## Run flake8 linter. - @git ls-files '*.py' | xargs $(PYTHON) -m flake8 --config=.flake8 --jobs=${NUM_WORKERS} - -isort: ## Run isort linter. - @git ls-files '*.py' | xargs $(PYTHON) -m isort --check-only --jobs=${NUM_WORKERS} +ruff: ## Run ruff linter. + @git ls-files '*.py' | xargs $(PYTHON) -m ruff check --config=pyproject.toml --no-cache _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} @@ -219,8 +207,7 @@ lint-toml: ## Linter for pyproject.toml @git ls-files '*.toml' | xargs toml-sort --check lint-all: ## Run all linters - ${MAKE} flake8 - ${MAKE} isort + ${MAKE} ruff ${MAKE} lint-c ${MAKE} lint-rst ${MAKE} lint-toml @@ -229,12 +216,8 @@ lint-all: ## Run all linters # 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 @@ -243,8 +226,7 @@ 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 diff --git a/docs/DEVGUIDE.rst b/docs/DEVGUIDE.rst index c59502dfd..a53235dab 100644 --- a/docs/DEVGUIDE.rst +++ b/docs/DEVGUIDE.rst @@ -12,7 +12,7 @@ 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 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/psutil/__init__.py b/psutil/__init__.py index 5a5a20606..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__) @@ -267,10 +267,7 @@ 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) @@ -279,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. @@ -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: @@ -1333,7 +1331,7 @@ class Popen(Process): >>> p.username() 'giampaolo' >>> p.communicate() - ('hi\n', None) + ('hi', None) >>> p.terminate() >>> p.wait(timeout=2) 0 @@ -1384,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 @@ -2200,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 @@ -2425,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..cc37c0615 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -68,7 +68,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" +] # ===================================================================== @@ -345,7 +346,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 +550,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 +570,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 +711,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 +958,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 +968,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 +1188,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 +1453,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 +1662,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 +1899,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 +1979,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/tests/__init__.py b/psutil/tests/__init__.py index 1aba0418f..d4d8faf6d 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 @@ -368,9 +366,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) @@ -478,7 +478,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) @@ -490,7 +490,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. @@ -670,10 +670,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) @@ -682,7 +679,7 @@ def get_winver(): # =================================================================== -class retry(object): +class retry: """A retry decorator.""" def __init__(self, @@ -772,7 +769,7 @@ def call_until(fun, expr): expression is True. """ ret = fun() - assert eval(expr) + assert eval(expr) # noqa return ret @@ -849,7 +846,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]) @@ -894,7 +891,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: @@ -1084,6 +1081,7 @@ class TestLeaks(psutil.tests.TestMemoryLeak): def test_fun(self): self.execute(some_function) """ + # Configurable class attrs. times = 200 warmup_times = 10 @@ -1316,6 +1314,7 @@ class process_namespace: >>> for fun, name in ns.iter(ns.getters): ... fun() """ + utils = [ ('cpu_percent', (), {}), ('memory_percent', (), {}), @@ -1452,6 +1451,7 @@ class system_namespace: >>> for fun, name in ns.iter(ns.getters): ... fun() """ + getters = [ ('boot_time', (), {}), ('cpu_count', (), {'logical': False}), @@ -1915,4 +1915,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 040807821..152a5b739 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 @@ -195,7 +195,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)) # =================================================================== @@ -206,7 +206,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 @@ -297,7 +297,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") diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py index adb6ff606..55839a1ba 100755 --- a/psutil/tests/test_linux.py +++ b/psutil/tests/test_linux.py @@ -113,7 +113,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() @@ -123,7 +123,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): @@ -180,7 +180,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) @@ -943,7 +943,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) @@ -1015,7 +1015,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") @@ -1159,7 +1159,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(): @@ -1597,7 +1597,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) @@ -1614,7 +1614,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")) @@ -1630,7 +1630,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) @@ -1645,7 +1645,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")) @@ -1662,8 +1662,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"???") @@ -1777,7 +1779,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 [] @@ -1896,7 +1898,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") @@ -2178,7 +2180,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 dbd1588df..cd1b3f290 --- 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 fe6936de5..eaaf0d1c2 100755 --- a/psutil/tests/test_posix.py +++ b/psutil/tests/test_posix.py @@ -43,8 +43,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. """ @@ -68,10 +67,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: @@ -324,7 +320,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 ae0192708..6a90c5b93 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -964,7 +964,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)) @@ -1462,6 +1462,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() @@ -1525,7 +1526,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 80e4988d5..68bfc24c9 100755 --- a/psutil/tests/test_system.py +++ b/psutil/tests/test_system.py @@ -216,8 +216,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: @@ -617,7 +617,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 65ca0c4d4..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: @@ -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 bf9435958..d99de4271 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,85 @@ -[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] exclude_lines = [ 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 40eaee980..92852f836 100755 --- a/scripts/internal/git_pre_commit.py +++ b/scripts/internal/git_pre_commit.py @@ -4,22 +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 -- assert RsT checks pass -- assert TOML 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 @@ -31,7 +19,7 @@ PYTHON = sys.executable -PY3 = sys.version_info[0] == 3 +PY3 = sys.version_info[0] >= 3 THIS_SCRIPT = os.path.realpath(__file__) @@ -86,7 +74,7 @@ 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(): @@ -108,26 +96,16 @@ def git_commit_files(): return (py_files, c_files, rst_files, toml_files, new_rm_mv) -def flake8(files): - assert os.path.exists('.flake8') - print("running flake8 (%s files)" % len(files)) - cmd = [PYTHON, "-m", "flake8", "--config=.flake8"] + files +def ruff(files): + print("running ruff (%s)" % len(files)) + cmd = [PYTHON, "-m", "ruff", "check", "--no-cache"] + files if subprocess.call(cmd) != 0: - return sys.exit( - "python code didn't pass 'flake8' style check; " + - "try running 'make fix-flake8'" + return exit( + "Python code didn't pass 'ruff' style check." + "Try running 'make fix-ruff'." ) -def isort(files): - print("running isort (%s)" % len(files)) - cmd = [PYTHON, "-m", "isort", "--check-only"] + files - if subprocess.call(cmd) != 0: - return sys.exit( - "python code didn't pass 'isort' style check; " + - "try running 'make fix-imports'") - - def c_linter(files): print("running clinter (%s)" % len(files)) # XXX: we should escape spaces and possibly other amenities here @@ -152,30 +130,8 @@ def rstcheck(files): def main(): py_files, c_files, rst_files, toml_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") - if py_files: - flake8(py_files) - isort(py_files) + ruff(py_files) if c_files: c_linter(c_files) if rst_files: 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