deps(python): drop Python 3.7 support (#150)

This commit is contained in:
Xuehai Pan 2025-01-31 16:33:18 +08:00 committed by GitHub
parent 8d897bbb78
commit cda0149e45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 55 additions and 37 deletions

View file

@ -14,7 +14,7 @@
- Operating system and version: [e.g. Ubuntu 20.04 LTS / Windows 10 Build 19043.1110]
- Terminal emulator and version: [e.g. GNOME Terminal 3.36.2 / Windows Terminal 1.8.1521.0]
- Python version: [e.g. `3.7.2` / `3.9.6`]
- Python version: [e.g. `3.8.2` / `3.9.6`]
- NVML version (driver version): [e.g. `460.84`]
- `nvitop` version or commit: [e.g. `0.10.0` / `0.10.1.dev7+ga083321` / `main@75ae3c`]
- `python-ml-py` version: [e.g. `11.450.51`]

View file

@ -50,7 +50,7 @@ jobs:
id: py
uses: actions/setup-python@v5
with:
python-version: "3.7 - 3.13"
python-version: "3.8 - 3.13"
update-environment: true
- name: Upgrade build dependencies
@ -129,7 +129,7 @@ jobs:
uses: actions/setup-python@v5
if: startsWith(github.ref, 'refs/tags/')
with:
python-version: "3.7 - 3.13"
python-version: "3.8 - 3.13"
update-environment: true
- name: Upgrade pip

View file

@ -27,7 +27,7 @@ jobs:
id: py
uses: actions/setup-python@v5
with:
python-version: "3.7 - 3.13"
python-version: "3.8 - 3.13"
update-environment: true
- name: Upgrade pip

View file

@ -25,7 +25,7 @@ repos:
- id: debug-statements
- id: double-quote-string-fixer
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.3
rev: v0.9.4
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
@ -34,7 +34,7 @@ repos:
rev: v3.19.1
hooks:
- id: pyupgrade
args: [--py37-plus] # sync with requires-python
args: [--py38-plus] # sync with requires-python
- repo: https://github.com/pycqa/flake8
rev: 7.1.1
hooks:

View file

@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed
-
- Drop Python 3.7 support by [@XuehaiPan](https://github.com/XuehaiPan) in [#150](https://github.com/XuehaiPan/nvitop/pull/150).
------

View file

@ -2,7 +2,7 @@
<!-- markdownlint-disable html -->
![Python 3.7+](https://img.shields.io/badge/Python-3.7%2B-brightgreen)
![Python 3.8+](https://img.shields.io/badge/Python-3.8%2B-brightgreen)
[![PyPI](https://img.shields.io/pypi/v/nvitop?label=pypi&logo=pypi)](https://pypi.org/project/nvitop)
[![conda-forge](https://img.shields.io/conda/vn/conda-forge/nvitop?label=conda&logo=condaforge)](https://anaconda.org/conda-forge/nvitop)
[![Documentation Status](https://img.shields.io/readthedocs/nvitop?label=docs&logo=readthedocs)](https://nvitop.readthedocs.io)
@ -111,7 +111,7 @@ An interactive NVIDIA-GPU process viewer and beyond, the one-stop solution for G
## Requirements
- Python 3.7+
- Python 3.8+
- NVIDIA Management Library (NVML)
- nvidia-ml-py
- psutil

View file

@ -13,7 +13,7 @@ An interactive NVIDIA-GPU process viewer and beyond, the one-stop solution for G
.. |GitHub| image:: https://img.shields.io/badge/GitHub-Homepage-blue?logo=github
.. _GitHub: https://github.com/XuehaiPan/nvitop
.. |Python Version| image:: https://img.shields.io/badge/Python-3.7%2B-brightgreen
.. |Python Version| image:: https://img.shields.io/badge/Python-3.8%2B-brightgreen
.. _Python Version: https://pypi.org/project/nvitop
.. |PyPI Package| image:: https://img.shields.io/pypi/v/nvitop?label=pypi&logo=pypi
@ -56,7 +56,7 @@ Install from PyPI (|PyPI Package|_):
.. note::
Python 3.7+ is required, and Python versions lower than 3.7 is not supported.
Python 3.8+ is required, and Python versions lower than 3.8 is not supported.
Install from conda-forge (|Conda-forge Package|_):

View file

@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
name = "nvitop-exporter"
description = "Prometheus exporter built on top of `nvitop`."
readme = "README.md"
requires-python = ">= 3.7"
requires-python = ">= 3.8"
authors = [{ name = "Xuehai Pan", email = "XuehaiPan@pku.edu.cn" }]
license = { text = "Apache License, Version 2.0 (Apache-2.0)" }
keywords = [
@ -27,7 +27,6 @@ classifiers = [
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",

View file

@ -123,6 +123,7 @@ class _TTLCacheLink: # pylint: disable=too-few-public-methods
@overload
def ttl_cache(
maxsize: int | None = 128,
*,
ttl: float = 600.0,
timer: Callable[[], float] = time.monotonic,
typed: bool = False,
@ -132,6 +133,7 @@ def ttl_cache(
@overload
def ttl_cache(
maxsize: Callable[_P, _T],
*,
ttl: float = 600.0,
timer: Callable[[], float] = time.monotonic,
typed: bool = False,
@ -141,6 +143,7 @@ def ttl_cache(
# pylint: disable-next=too-many-statements
def ttl_cache(
maxsize: int | Callable[_P, _T] | None = 128,
*,
ttl: float = 600.0,
timer: Callable[[], float] = time.monotonic,
typed: bool = False,
@ -165,7 +168,7 @@ def ttl_cache(
return functools.lru_cache(maxsize=maxsize, typed=typed) # type: ignore[return-value]
# pylint: disable-next=too-many-statements,too-many-locals
def wrapper(func: Callable[_P, _T]) -> Callable[_P, _T]:
def wrapper(func: Callable[_P, _T], /) -> Callable[_P, _T]:
cache: dict[Any, _TTLCacheLink] = {}
cache_get = cache.get # bound method to lookup a key or return None
cache_len = cache.__len__ # get cache size without calling len()

View file

@ -52,7 +52,7 @@ timer = time.monotonic
_T = TypeVar('_T')
def _unique(iterable: Iterable[_T]) -> list[_T]:
def _unique(iterable: Iterable[_T], /) -> list[_T]:
return list(OrderedDict.fromkeys(iterable).keys())
@ -399,6 +399,7 @@ class ResourceMetricCollector: # pylint: disable=too-many-instance-attributes
def __init__(
self,
devices: Iterable[Device] | None = None,
*,
root_pids: Iterable[int] | None = None,
interval: float = 1.0,
) -> None:
@ -778,6 +779,7 @@ class _MetricBuffer: # pylint: disable=missing-class-docstring,missing-function
self,
tag: str,
collector: ResourceMetricCollector,
*,
prev: _MetricBuffer | None = None,
) -> None:
self.collector: ResourceMetricCollector = collector

View file

@ -1589,6 +1589,7 @@ class Device: # pylint: disable=too-many-instance-attributes,too-many-public-me
return []
def query_nvlink_throughput_counters() -> tuple[tuple[int | NaType, int]]:
assert self._handle is not None
return tuple( # type: ignore[return-value]
libnvml.nvmlQueryFieldValues(
self._handle,
@ -3149,7 +3150,7 @@ def _parse_cuda_visible_devices(
) -> list[str]: ...
@functools.lru_cache()
@functools.lru_cache
def _parse_cuda_visible_devices( # pylint: disable=too-many-branches,too-many-statements
cuda_visible_devices: str | None = None,
format: Literal['index', 'uuid'] = 'index', # pylint: disable=redefined-builtin

View file

@ -277,7 +277,7 @@ class CUDAError(Exception):
return CUDAError, (self.value,) # pylint: disable=no-member
def cudaExceptionClass(cudaErrorCode: int) -> type[CUDAError]:
def cudaExceptionClass(cudaErrorCode: int, /) -> type[CUDAError]:
"""Map value to a proper subclass of :class:`CUDAError`.
Raises:
@ -348,7 +348,7 @@ _extract_cuda_errors_as_classes()
del _extract_cuda_errors_as_classes
def _cudaCheckReturn(ret: _Any) -> _Any:
def _cudaCheckReturn(ret: _Any, /) -> _Any:
if ret != CUDA_SUCCESS:
raise CUDAError(ret)
return ret

View file

@ -325,7 +325,7 @@ class cudaError(Exception):
return cudaError, (self.value,) # pylint: disable=no-member
def cudaExceptionClass(cudaErrorCode: int) -> type[cudaError]:
def cudaExceptionClass(cudaErrorCode: int, /) -> type[cudaError]:
"""Map value to a proper subclass of :class:`cudaError`.
Raises:
@ -399,7 +399,7 @@ _extract_cuda_errors_as_classes()
del _extract_cuda_errors_as_classes
def _cudaCheckReturn(ret: _Any) -> _Any:
def _cudaCheckReturn(ret: _Any, /) -> _Any:
if ret != cudaSuccess:
raise cudaError(ret)
return ret
@ -412,7 +412,7 @@ __libLoadLock: _threading.Lock = _threading.Lock()
__cudaGetFunctionPointer_cache: dict[str, _ctypes._CFuncPtr] = {} # type: ignore[name-defined]
def __cudaGetFunctionPointer(name: str) -> _ctypes._CFuncPtr: # type: ignore[name-defined]
def __cudaGetFunctionPointer(name: str, /) -> _ctypes._CFuncPtr: # type: ignore[name-defined]
"""Get the function pointer from the CUDA Runtime library.
Raises:

View file

@ -174,7 +174,14 @@ del (
# 5. Add explicit references to appease linters
# pylint: disable=no-member
c_nvmlDevice_t: _TypeAlias = _pynvml.c_nvmlDevice_t # noqa: PYI042
if _TYPE_CHECKING:
# pylint: disable-next=missing-class-docstring,too-few-public-methods,function-redefined
class c_nvmlDevice_t(_ctypes.c_void_p):
pass
else:
c_nvmlDevice_t: _TypeAlias = _pynvml.c_nvmlDevice_t # type: ignore[no-redef] # noqa: PYI042
c_nvmlFieldValue_t: _TypeAlias = _pynvml.c_nvmlFieldValue_t # noqa: PYI042
NVML_SUCCESS: int = _pynvml.NVML_SUCCESS
NVML_ERROR_INSUFFICIENT_SIZE: int = _pynvml.NVML_ERROR_INSUFFICIENT_SIZE
@ -377,6 +384,7 @@ def nvmlShutdown() -> None: # pylint: disable=function-redefined
def nvmlQuery(
func: _Callable[..., _Any] | str,
/,
*args: _Any,
default: _Any = NA,
ignore_errors: bool = True,
@ -513,7 +521,7 @@ def nvmlQueryFieldValues(
return values_with_timestamps
def nvmlCheckReturn(retval: _Any, types: type | tuple[type, ...] | None = None) -> bool:
def nvmlCheckReturn(retval: _Any, types: type | tuple[type, ...] | None = None, /) -> bool:
"""Check whether the return value is not :const:`nvitop.NA` and is one of the given types."""
if types is None:
return retval != NA
@ -643,6 +651,7 @@ if not _pynvml_installation_corrupted:
def __nvml_device_get_running_processes(
func: str,
/,
handle: c_nvmlDevice_t,
) -> list[c_nvmlProcessInfo_t]:
"""Helper function for :func:`nvmlDeviceGet{Compute,Graphics,MPSCompute}RunningProcesses`.

View file

@ -114,9 +114,9 @@ def auto_garbage_clean(
raises an exception when falls.
"""
def wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
def wrapper(func: Callable[..., Any], /) -> Callable[..., Any]:
@functools.wraps(func)
def wrapped(self: GpuProcess, *args: Any, **kwargs: Any) -> Any:
def wrapped(self: GpuProcess, /, *args: Any, **kwargs: Any) -> Any:
try:
return func(self, *args, **kwargs)
except host.PsutilError as ex:

View file

@ -209,6 +209,7 @@ def _can_do_color(
# pylint: disable-next=too-many-arguments
def colored(
text: Any,
/,
color: Color | None = None,
on_color: Highlight | None = None,
attrs: Iterable[Attribute] | None = None,
@ -258,6 +259,7 @@ def colored(
# pylint: disable-next=too-many-arguments
def cprint(
text: object,
/,
color: Color | None = None,
on_color: Highlight | None = None,
attrs: Iterable[Attribute] | None = None,

View file

@ -29,7 +29,7 @@ import re
import sys
import time
from collections.abc import KeysView
from typing import TYPE_CHECKING, Any, Callable, TypeVar
from typing import TYPE_CHECKING, Any, Callable, TypeVar, final
from nvitop.api import termcolor
@ -79,6 +79,7 @@ def set_color(value: bool) -> None:
def colored(
text: Any,
/,
color: termcolor.Color | None = None,
on_color: termcolor.Highlight | None = None,
attrs: Iterable[termcolor.Attribute] | None = None,
@ -103,6 +104,7 @@ def colored(
return str(text)
@final
class NaType(str):
"""A singleton (:const:`str: 'N/A'`) class represents a not applicable value.
@ -133,8 +135,7 @@ class NaType(str):
nan
"""
# NOTE: Decorate this class with `@final` and remove `noqa` when we drop Python 3.7 support.
def __new__(cls) -> NaType: # noqa: PYI034
def __new__(cls) -> NaType:
"""Get the singleton instance (:const:`nvitop.NA`)."""
if not hasattr(cls, '_instance'):
cls._instance = super().__new__(cls, 'N/A')
@ -510,6 +511,7 @@ SIZE_PATTERN: re.Pattern = re.compile(
# pylint: disable-next=too-many-return-statements,too-many-branches
def bytes2human(
b: int | float | NaType, # noqa: PYI041
/,
*,
min_unit: int = 1,
) -> str:
@ -546,7 +548,7 @@ def bytes2human(
return f'{round(b / PiB, 1):.1f}PiB'
def human2bytes(s: int | str) -> int:
def human2bytes(s: int | str, /) -> int:
"""Convert a human readable size string (*case insensitive*) to bytes.
Raises:
@ -582,6 +584,7 @@ def human2bytes(s: int | str) -> int:
def timedelta2human(
dt: int | float | datetime.timedelta | NaType, # noqa: PYI041
/,
*,
round: bool = False, # pylint: disable=redefined-builtin
) -> str:
@ -601,7 +604,7 @@ def timedelta2human(
return '{:d}:{:02d}'.format(*divmod(seconds, 60))
def utilization2string(utilization: int | float | NaType) -> str: # noqa: PYI041
def utilization2string(utilization: int | float | NaType, /) -> str: # noqa: PYI041
"""Convert a utilization rate to string."""
if utilization != NA:
if isinstance(utilization, int):
@ -611,7 +614,7 @@ def utilization2string(utilization: int | float | NaType) -> str: # noqa: PYI04
return NA
def boolify(string: str, default: Any = None) -> bool:
def boolify(string: str, /, default: Any = None) -> bool:
"""Convert the given value, usually a string, to boolean."""
if string.lower() in {'true', 'yes', 'on', 'enabled', '1'}:
return True
@ -703,7 +706,7 @@ Method = TypeVar('Method', bound=Callable[..., Any])
# Modified from psutil (https://github.com/giampaolo/psutil)
def memoize_when_activated(method: Method) -> Method:
def memoize_when_activated(method: Method, /) -> Method:
"""A memoize decorator which is disabled by default.
It can be activated and deactivated on request. For efficiency reasons it can be used only
@ -711,7 +714,7 @@ def memoize_when_activated(method: Method) -> Method:
"""
@functools.wraps(method)
def wrapped(self: object, *args: Any, **kwargs: Any) -> Any:
def wrapped(self: object, /, *args: Any, **kwargs: Any) -> Any:
try:
# case 1: we previously entered oneshot() ctx
# pylint: disable-next=protected-access

View file

@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
name = "nvitop"
description = "An interactive NVIDIA-GPU process viewer and beyond, the one-stop solution for GPU process management."
readme = "README.md"
requires-python = ">= 3.7"
requires-python = ">= 3.8"
authors = [{ name = "Xuehai Pan", email = "XuehaiPan@pku.edu.cn" }]
license = { text = "Apache License, Version 2.0 (Apache-2.0) & GNU General Public License, Version 3 (GPL-3.0)" }
keywords = [
@ -24,7 +24,6 @@ classifiers = [
"License :: OSI Approved :: Apache Software License",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
@ -96,7 +95,7 @@ module = ['nvitop.callbacks.*', 'nvitop.tui.*']
ignore_errors = true
[tool.pylint]
main.py-version = "3.7"
main.py-version = "3.8"
basic.good-names = ["x", "y", "dx", "dy", "p", "s", "fg", "bg", "n", "ui", "tx", "rx"]
format.max-line-length = 120
"messages control".disable = ["consider-using-f-string", "duplicate-code", "wrong-import-order"]
@ -108,7 +107,7 @@ ignore-words = "docs/source/spelling_wordlist.txt"
[tool.ruff]
# Sync with requires-python
target-version = "py37"
target-version = "py38"
line-length = 100
output-format = "full"
src = ["nvitop", "nvitop-exporter/nvitop_exporter"]