Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start checking using Flake8-PYI #4380

Merged
merged 3 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions ruff.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
exclude = [
"**/_vendor",
"setuptools/_distutils",
"setuptools/config/_validate_pyproject",
]

[lint]
extend-select = [
"C901",
Expand All @@ -6,6 +12,7 @@ extend-select = [
# local
"FA", # flake8-future-annotations
"F404", # late-future-import
"PYI", # flake8-pyi
"UP", # pyupgrade
"YTT", # flake8-2020
]
Expand All @@ -32,23 +39,13 @@ ignore = [
"ISC001",
"ISC002",
]
exclude = [
"**/_vendor",
"setuptools/_distutils",
"setuptools/config/_validate_pyproject",
]

[lint.per-file-ignores]
# Auto-generated code
"setuptools/config/_validate_pyproject/*" = ["FA100"]

[format]
exclude = [
"**/_vendor",
"setuptools/_distutils",
"setuptools/config/_validate_pyproject",
]
# Enable preview, required for quote-style = "preserve"
# Enable preview to get hugged parenthesis unwrapping
preview = true
# https://docs.astral.sh/ruff/settings/#format-quote-style
quote-style = "preserve"
141 changes: 64 additions & 77 deletions setuptools/depends.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .extern.packaging.version import Version


__all__ = ['Require', 'find_module', 'get_module_constant', 'extract_constant']
__all__ = ['Require', 'find_module']


class Require:
Expand Down Expand Up @@ -95,86 +95,73 @@ def empty():
return contextlib.closing(f)


def get_module_constant(module, symbol, default=-1, paths=None):
"""Find 'module' by searching 'paths', and extract 'symbol'

Return 'None' if 'module' does not exist on 'paths', or it does not define
'symbol'. If the module defines 'symbol' as a constant, return the
constant. Otherwise, return 'default'."""

try:
f, path, (suffix, mode, kind) = info = find_module(module, paths)
except ImportError:
# Module doesn't exist
return None

with maybe_close(f):
if kind == PY_COMPILED:
f.read(8) # skip magic & date
code = marshal.load(f)
elif kind == PY_FROZEN:
code = _imp.get_frozen_object(module, paths)
elif kind == PY_SOURCE:
code = compile(f.read(), path, 'exec')
else:
# Not something we can parse; we'll have to import it. :(
imported = _imp.get_module(module, paths, info)
return getattr(imported, symbol, None)

return extract_constant(code, symbol, default)


def extract_constant(code, symbol, default=-1):
"""Extract the constant value of 'symbol' from 'code'

If the name 'symbol' is bound to a constant value by the Python code
object 'code', return that value. If 'symbol' is bound to an expression,
return 'default'. Otherwise, return 'None'.

Return value is based on the first assignment to 'symbol'. 'symbol' must
be a global, or at least a non-"fast" local in the code block. That is,
only 'STORE_NAME' and 'STORE_GLOBAL' opcodes are checked, and 'symbol'
must be present in 'code.co_names'.
"""
if symbol not in code.co_names:
# name's not there, can't possibly be an assignment
return None

name_idx = list(code.co_names).index(symbol)

STORE_NAME = dis.opmap['STORE_NAME']
STORE_GLOBAL = dis.opmap['STORE_GLOBAL']
LOAD_CONST = dis.opmap['LOAD_CONST']

const = default

for byte_code in dis.Bytecode(code):
op = byte_code.opcode
arg = byte_code.arg

if op == LOAD_CONST:
const = code.co_consts[arg]
elif arg == name_idx and (op == STORE_NAME or op == STORE_GLOBAL):
return const
else:
const = default
# Some objects are not available on some platforms.
# XXX it'd be better to test assertions about bytecode instead.
if not sys.platform.startswith('java') and sys.platform != 'cli':

def get_module_constant(module, symbol, default=-1, paths=None):
"""Find 'module' by searching 'paths', and extract 'symbol'

Return 'None' if 'module' does not exist on 'paths', or it does not define
'symbol'. If the module defines 'symbol' as a constant, return the
constant. Otherwise, return 'default'."""

try:
f, path, (suffix, mode, kind) = info = find_module(module, paths)
except ImportError:
# Module doesn't exist
return None

with maybe_close(f):
if kind == PY_COMPILED:
f.read(8) # skip magic & date
code = marshal.load(f)
elif kind == PY_FROZEN:
code = _imp.get_frozen_object(module, paths)
elif kind == PY_SOURCE:
code = compile(f.read(), path, 'exec')
else:
# Not something we can parse; we'll have to import it. :(
imported = _imp.get_module(module, paths, info)
return getattr(imported, symbol, None)

return extract_constant(code, symbol, default)

def extract_constant(code, symbol, default=-1):
"""Extract the constant value of 'symbol' from 'code'

If the name 'symbol' is bound to a constant value by the Python code
object 'code', return that value. If 'symbol' is bound to an expression,
return 'default'. Otherwise, return 'None'.

Return value is based on the first assignment to 'symbol'. 'symbol' must
be a global, or at least a non-"fast" local in the code block. That is,
only 'STORE_NAME' and 'STORE_GLOBAL' opcodes are checked, and 'symbol'
must be present in 'code.co_names'.
"""
if symbol not in code.co_names:
# name's not there, can't possibly be an assignment
return None

return None
name_idx = list(code.co_names).index(symbol)

STORE_NAME = dis.opmap['STORE_NAME']
STORE_GLOBAL = dis.opmap['STORE_GLOBAL']
LOAD_CONST = dis.opmap['LOAD_CONST']

def _update_globals():
"""
Patch the globals to remove the objects not available on some platforms.
const = default

XXX it'd be better to test assertions about bytecode instead.
"""
for byte_code in dis.Bytecode(code):
op = byte_code.opcode
arg = byte_code.arg

if not sys.platform.startswith('java') and sys.platform != 'cli':
return
incompatible = 'extract_constant', 'get_module_constant'
for name in incompatible:
del globals()[name]
__all__.remove(name)
if op == LOAD_CONST:
const = code.co_consts[arg]
elif arg == name_idx and (op == STORE_NAME or op == STORE_GLOBAL):
return const
else:
const = default

return None

_update_globals()
__all__ += ['get_module_constant', 'extract_constant']
9 changes: 7 additions & 2 deletions setuptools/tests/test_easy_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
import itertools
import distutils.errors
import io
from typing import NamedTuple
import zipfile
import time
import re
import subprocess
import pathlib
import warnings
from collections import namedtuple
from pathlib import Path
from unittest import mock

Expand Down Expand Up @@ -1344,7 +1344,12 @@ def test_header(self):
assert not hdr.startswith('\\"')


VersionStub = namedtuple("VersionStub", "major, minor, micro, releaselevel, serial")
class VersionStub(NamedTuple):
major: int
minor: int
micro: int
releaselevel: str
serial: int


def test_use_correct_python_version_string(tmpdir, tmpdir_cwd, monkeypatch):
Expand Down
Loading