Skip to content

Commit ca9aa90

Browse files
committed
ENH: Merge current versions from SciPy and NumPy
I think I got the most current versions of each
1 parent b190cb3 commit ca9aa90

File tree

8 files changed

+377
-150
lines changed

8 files changed

+377
-150
lines changed

AUTHORS.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@ Contributors
33
============
44

55
* DWesl <22566757+DWesl@users.noreply.github.com>
6+
* rgommers
7+
* nbelakovski
8+
* lucascolley
9+
* izaid
10+
* steppi
11+
* dschmitz

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ build-backend = "setuptools.build_meta"
77
# For smarter version schemes and other configuration options,
88
# check out https://github.com/pypa/setuptools_scm
99
version_scheme = "no-guess-dev"
10+
11+
[project.scripts]
12+
check_python_h_first = "check_python_h_first.__main__:main"

setup.cfg

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ formats = bdist_wheel
106106
[flake8]
107107
# Some sane defaults for the code style checker flake8
108108
max_line_length = 88
109-
extend_ignore = E203, W503
109+
extend_ignore = E203, W503, Q000
110110
# ^ Black-compatible
111111
# E203 and W503 have edge cases handled by black
112112
exclude =
@@ -115,6 +115,19 @@ exclude =
115115
dist
116116
.eggs
117117
docs/conf.py
118+
# flake8-rst-docstrings
119+
rst-roles =
120+
class,
121+
func,
122+
ref,
123+
rst-directives =
124+
envvar,
125+
exception,
126+
rst-substitutions =
127+
version
128+
129+
[pydocstyle]
130+
convention = numpy
118131

119132
[pyscaffold]
120133
# PyScaffold's parameters when the project was created.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Run the checker on the given files."""
2+
3+
import argparse
4+
import os.path
5+
import sys
6+
7+
from . import __version__
8+
from .wrapper import find_c_cpp_files, process_files
9+
10+
PARSER = argparse.ArgumentParser(description=__doc__)
11+
PARSER.add_argument(
12+
"files",
13+
nargs="+",
14+
help="Lint these files or this directory; use **/*.c to lint all files\n"
15+
"Expects relative paths",
16+
)
17+
PARSER.add_argument(
18+
"--version", action="version", version="check_python_h_first {__version__}"
19+
)
20+
21+
22+
def main():
23+
"""Run the checker on the files passed on the command line."""
24+
args = PARSER.parse_args()
25+
26+
files = args.files
27+
if len(files) == 1 and os.path.isdir(files[0]):
28+
files = find_c_cpp_files(files[0])
29+
30+
# See which of the headers include Python.h and add them to the list
31+
n_out_of_order = process_files(files)
32+
sys.exit(n_out_of_order)
33+
34+
35+
if __name__ == "__main__":
36+
main()
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Find files in submodules.
2+
3+
Those should be fixed in upstream project repos, not here.
4+
"""
5+
6+
import glob
7+
import os.path
8+
9+
10+
def get_submodule_paths():
11+
"""Get submodule roots.
12+
13+
Get paths to submodules so that we can exclude them from things like
14+
check_test_name.py, check_unicode.py, etc.
15+
"""
16+
root_directory = os.path.dirname(os.path.dirname(__file__))
17+
gitmodule_file = os.path.join(root_directory, ".gitmodules")
18+
with open(gitmodule_file) as gitmodules:
19+
data = gitmodules.read().split("\n")
20+
submodule_paths = [
21+
datum.split(" = ")[1] for datum in data if datum.startswith("\tpath = ")
22+
]
23+
submodule_paths = [
24+
os.path.join(root_directory, path) for path in submodule_paths
25+
]
26+
# vendored with a script rather than via gitmodules
27+
with open(os.path.join(root_directory, ".gitattributes"), "r") as attr_file:
28+
for line in attr_file:
29+
if "vendored" in line:
30+
pattern = line.split(" ", 1)[0]
31+
submodule_paths.extend(glob.glob(pattern))
32+
33+
return submodule_paths
34+
35+
36+
if __name__ == "__main__":
37+
print("\n".join(get_submodule_paths()))
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""Check that Python.h is included before any stdlib headers.
2+
3+
May be a bit overzealous, but it should get the job done.
4+
"""
5+
6+
import os.path
7+
import re
8+
import sys
9+
10+
HEADER_PATTERN = re.compile(
11+
r'^\s*#\s*include\s*[<"]((?:\w+/)*\w+(?:\.h[hp+]{0,2})?)[>"]\s*$'
12+
)
13+
14+
PYTHON_INCLUDING_HEADERS = [
15+
"Python.h",
16+
# This isn't all of Python.h, but it is the visibility macros
17+
"pyconfig.h",
18+
# NumPy
19+
"numpy/npy_common.h",
20+
"numpy/npy_math.h",
21+
"numpy/arrayobject.h",
22+
"numpy/ndarrayobject.h",
23+
"numpy/ndarraytypes.h",
24+
"numpy/random/distributions.h",
25+
# Pybind
26+
"pybind11/pybind11.h",
27+
# Boost::Python
28+
"boost/python.hpp",
29+
# Pythran
30+
"pythonic/core.hpp",
31+
# xsf::numpy
32+
"xsf/numpy.h",
33+
]
34+
LEAF_HEADERS = [
35+
"numpy/numpyconfig.h",
36+
"numpy/npy_os.h",
37+
"numpy/npy_cpu.h",
38+
"numpy/utils.h",
39+
]
40+
41+
42+
def check_python_h_included_first(name_to_check: str) -> int:
43+
"""Check that the passed file includes Python.h first if it does at all.
44+
45+
Perhaps overzealous, but that should work around concerns with
46+
recursion.
47+
48+
Parameters
49+
----------
50+
name_to_check : str
51+
The name of the file to check.
52+
53+
Returns
54+
-------
55+
int
56+
The number of headers before Python.h
57+
"""
58+
included_python = False
59+
included_non_python_header = []
60+
warned_python_construct = False
61+
basename_to_check = os.path.basename(name_to_check)
62+
in_comment = False
63+
includes_headers = False
64+
with open(name_to_check) as in_file:
65+
for i, line in enumerate(in_file, 1):
66+
# Very basic comment parsing
67+
# Assumes /*...*/ comments are on their own lines
68+
if "/*" in line:
69+
if "*/" not in line:
70+
in_comment = True
71+
# else-branch could use regex to remove comment and continue
72+
continue
73+
if in_comment:
74+
if "*/" in line:
75+
in_comment = False
76+
continue
77+
line = line.split("//", 1)[0].strip()
78+
# Now that there's no comments, look for headers
79+
match = HEADER_PATTERN.match(line)
80+
if match:
81+
includes_headers = True
82+
this_header = match.group(1)
83+
if this_header in PYTHON_INCLUDING_HEADERS:
84+
if included_non_python_header and not included_python:
85+
# Headers before python-including header
86+
print(
87+
f"Header before Python.h in file {name_to_check:s}\n"
88+
f"Python.h on line {i:d}, other header(s) on line(s)"
89+
f" {included_non_python_header}",
90+
file=sys.stderr,
91+
)
92+
# else: # no headers before python-including header
93+
included_python = True
94+
PYTHON_INCLUDING_HEADERS.append(basename_to_check)
95+
if os.path.dirname(name_to_check).endswith("include/numpy"):
96+
PYTHON_INCLUDING_HEADERS.append(f"numpy/{basename_to_check:s}")
97+
# We just found out where Python.h comes in this file
98+
break
99+
elif this_header in LEAF_HEADERS:
100+
# This header is just defines, so it won't include
101+
# the system headers that cause problems
102+
continue
103+
elif not included_python and (
104+
"numpy/" in this_header
105+
and this_header not in LEAF_HEADERS
106+
or "python" in this_header.lower()
107+
or "pybind" in this_header
108+
):
109+
print(
110+
f"Python.h not included before python-including header "
111+
f"in file {name_to_check:s}\n"
112+
f"{this_header:s} on line {i:d}",
113+
file=sys.stderr,
114+
)
115+
included_python = True
116+
PYTHON_INCLUDING_HEADERS.append(basename_to_check)
117+
elif not included_python and this_header not in LEAF_HEADERS:
118+
included_non_python_header.append(i)
119+
elif (
120+
not included_python
121+
and not warned_python_construct
122+
and ".h" not in basename_to_check
123+
) and ("py::" in line or "PYBIND11_" in line):
124+
print(
125+
"Python-including header not used before python constructs "
126+
f"in file {name_to_check:s}\nConstruct on line {i:d}",
127+
file=sys.stderr,
128+
)
129+
warned_python_construct = True
130+
if not includes_headers:
131+
LEAF_HEADERS.append(basename_to_check)
132+
return included_python and len(included_non_python_header)

0 commit comments

Comments
 (0)