Repository navigation
Expand file tree
/
Copy pathpyproject.toml
More file actions
341 lines (313 loc) · 15 KB
/
Copy pathpyproject.toml
File metadata and controls
341 lines (313 loc) · 15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
[project]
name="PyOpenGL"
dynamic=["version"]
description = "Standard OpenGL bindings for Python"
authors = [
{name="Mike C. Fletcher",email="mcfletch@vrplumber.com"},
]
readme = {file="readme.rst", content-type = "text/x-rst"}
license = "BSD-3-Clause"
license-files = ["license.txt"]
requires-python = ">=3.9"
keywords = [
"Graphics",
"3D",
"OpenGL",
"GLU",
"GLUT",
"GLE",
"GLX",
"EXT",
"ARB",
"Mesa",
"ctypes",
]
classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Topic :: Multimedia :: Graphics :: 3D Rendering",
"Topic :: Software Development :: Libraries :: Python Modules",
"Intended Audience :: Developers",
]
[project.optional-dependencies]
# The prebuilt Windows freeglut and GLE builds, which `OpenGL.GLUT` and
# `OpenGL.GLE` need on a machine that has no GLUT of its own. They shipped
# inside this wheel until 4.0, which meant every Linux and macOS install
# downloaded twelve Windows libraries it could never load, and a scanner
# objecting to a decade-old vendored binary quarantined PyOpenGL itself for
# users who never wanted GLUT (#164).
#
# Windows only, because that is the only platform the builds are for -- a
# `pip install PyOpenGL[glut]` elsewhere is satisfied by asking for nothing,
# rather than failing, so the same command works in a cross-platform install
# script. Where the system has its own freeglut it still wins; see
# `OpenGL.platform.ctypesloader`.
#
# https://github.com/mcfletch/pyopengl/issues/164
glut = ["PyOpenGL-glut-binaries >= 1.0.0 ; sys_platform == 'win32'"]
test = [
"tox",
"tox-uv >= 1",
"pip >= 23 ; python_version >= '3.12'",
"pytest",
# A hang inside a test is otherwise bounded only by the CI job limit, which
# reports a cancelled job rather than the name of what it was waiting for.
"pytest-timeout",
# Parallel runs, which CI uses and a developer can ask for. See "Running
# in parallel" in tests/README.md for the distribution mode it needs and
# the driver it is not stable on.
"pytest-xdist",
"setuptools >= 77",
# pygame-ce is the maintained fork and the only one with wheels for the
# interpreters the matrix runs; below 3.11 the suite uses glfw or the
# headless backends, and the pygame windowing backend is simply absent.
"pygame-ce >= 2.1.3; python_version >= '3.11'",
"psutil",
# python-xlib, not xlib: both install the same `Xlib` package, and
# xlib's last release was 0.21 in 2018 against python-xlib's 0.33.
# The name matters beyond currency -- conda-forge packages
# python-xlib and not xlib, so a conda environment could not
# satisfy this at all.
"python-xlib >= 0.33",
"glfw",
# tests/cdispatch reads the Khronos registry XML through src/xmlreg.py.
"lxml",
# tests/directdocs parses what a docstring becomes, and holds it to
# reStructuredText that docutils reads without a warning.
"docutils",
# `tomllib` is the standard library's TOML reader from 3.11; `tomli` is
# the same API under its original name, and is what the two interpreters
# below that floor have. The suite reads accelerate's pyproject.toml to
# check what it declares as a build requirement.
"tomli ; python_version < '3.11'",
]
# What `build-docs.py` needs. The reference pages are generated from the
# Khronos DocBook sources, which is what lxml is for; furo is the theme.
docs = [
"sphinx >= 7",
"furo",
"lxml",
]
dev = [
"cython >= 0.28",
"PyOpenGL[test]",
"PyOpenGL[docs]",
]
# PyInstaller reads this to find the hooks in OpenGL/__pyinstaller, which tell it
# about the plug-in registries and the Windows DLLs it cannot see by following
# import statements. Nothing is imported from that package at run time.
[project.entry-points.pyinstaller40]
hook-dirs = "OpenGL.__pyinstaller:get_hook_dirs"
[project.urls]
Homepage = "https://mcfletch.github.io/pyopengl/"
Download = "https://pypi.org/project/PyOpenGL/"
Source = "https://github.com/mcfletch/pyopengl"
Documentation = "https://mcfletch.github.io/pyopengl/documentation/index.html"
[build-system]
requires = [ "setuptools >= 77.0" ]
build-backend = "setuptools.build_meta"
[tool.setuptools.dynamic]
version = {attr = "OpenGL.version.__version__"}
[tool.setuptools.package-data]
# py.typed tells a type checker the package ships type information; without it
# the .pyi stubs beside the modules are ignored.
# The declarations the pure-Python path reads where no extension was built.
# The C extension holds the same facts; these are what an installation without
# a compiler has instead, so they are not optional.
# A stub per generated module as well as per API package: a friendly module
# fills its namespace from the tables at import, so the stub beside it is
# what an editor and a type checker have to read.
OpenGL = ["py.typed", "_typing.pyi", "**/*.pyi", "raw/_declarations/*.dat"]
[tool.setuptools.packages.find]
namespaces=false
include=["OpenGL*"]
exclude=["tests","accelerate","directdocs","documentation"]
[tool.pytest.ini_options]
# Where the suites are, so a bare `pytest` runs what CI runs.
testpaths = ["tests", "accelerate/tests"]
# The suites import their shared helpers by bare name -- `from arraycompat
# import np` from anywhere, `from gltestcase import GLTestCase` from within a
# suite -- and tests/cdispatch reads the build-time generator out of src/.
# Naming them here is what puts them on the path; these directories used to be
# reached by three conftest.py files editing sys.path and by pytest's prepend
# import mode adding each test file's own directory, which importlib mode
# (below) does not do.
#
# The checkout itself is named too, for the packages at its root that are not
# part of the distribution -- `directdocs` -- which `python -m pytest` reaches
# through the current directory and a bare `pytest` does not. Without it,
# `import directdocs` under a bare `pytest` finds only tests/directdocs, the
# directory of that package's tests, and there is nothing in it to import.
pythonpath = [".", "tests", "tests/gl", "tests/gles", "tests/glu", "tests/gates", "src"]
# importlib import mode gives each test module a name derived from its path, so
# two suites may hold the same basename: tests/gl and tests/gles cover the same
# extensions under two APIs and want to say so with the same file name. Under
# the default prepend mode a duplicate basename is a collection error, which is
# why the ES copies carried a `test_es_` prefix. There is no ini key for it,
# only the command-line option, so it goes in addopts.
addopts = "--import-mode=importlib"
# A test that hangs -- a driver waiting on a compositor that will not answer, a
# window that never maps -- otherwise runs until the CI job's own limit, which
# cancels the job and names nothing. Generous, because the point is to bound a
# hang rather than to time anything: the slowest case here builds a frozen
# application and takes about eight seconds.
timeout = 300
# An xfail that starts passing is a result, not a quiet success: say so, so the
# marker comes off in the same change that fixed it.
xfail_strict = true
filterwarnings = [
"error",
# setuptools reads the accelerate extension's `compiler_directives` keyword,
# which is Cython's rather than distutils'. Raised while a test imports
# accelerate/setup.py; the keyword is honoured by the Cython build.
# `(?s)` because the message opens with a newline, and `.` does not match
# one -- without it the pattern silently matches nothing and the warning is
# still an error.
"ignore:(?s).*Please remove unknown .Extension. options.*:UserWarning",
# The same warning as the line above, in the wording setuptools used
# before it was reworded. Both are here because the matrix spans the
# setuptools each interpreter's environment resolves.
"ignore:Unknown Extension options:UserWarning",
]
markers = [
"performance: asserts how fast something draws, so it needs a GPU to draw it. Deselected with '-m \"not performance\"' where the renderer rasterises on the CPU -- llvmpipe answers a question about speed wrongly by three orders of magnitude.",
"resources: asserts a budget in memory or import time rather than a result, so it measures the machine as much as the library. Deselected with '-m \"not resources\"' on a machine doing something else at the same time.",
"slow: takes seconds rather than milliseconds, because it builds something -- a frozen application, an archive. Deselected with '-m \"not slow\"' while iterating.",
]
[tool.coverage.run]
# What the suite actually executes, which is a different question from what it
# names. A case can mention an entry point in a comment, a skip reason or an
# assertion it never reaches, so counting names overstates coverage -- and the
# under-tested paths are where the defects are.
branch = true
source = ["OpenGL"]
# Several suites answer their question in a fresh interpreter, because it is
# settled once per process: which dispatch implementation is installed, what an
# import costs, which platform the entry points were built for. Those children
# are most of the coverage of the platform plugins, so measuring only the
# parent reports the OSMesa and EGL platforms as nearly untouched.
parallel = true
patch = ["subprocess"]
[tool.coverage.report]
exclude_also = [
# A generated `def name(...): pass` is a declaration, not a body: the
# decorator replaces it with a ctypes function, so the line never runs
# however often the entry point is called.
"^def .*: pass$",
"if TYPE_CHECKING:",
]
[tool.mypy]
# The gate is the shipped stub surface: the 1,307 `.pyi` files and the
# `py.typed` beside them are what a user's own type checker reads, and a defect
# there is one every user of the entry point meets. That is not hypothetical --
# a `glDeleteTextures` stub that described the C entry point rather than the
# wrapper `OpenGL.GL` exports made the documented call an error, and cost a
# release.
#
# The package's own source is deliberately *not* in it. Most of it is generated
# modules whose namespaces arrive from the declaration tables when `_define()`
# runs, which a checker cannot read at all; what is left reports around 240
# findings, overwhelmingly mypy failing to model the dynamic dispatch layer
# rather than defects. Bringing that to green would be annotation work with
# very little correctness in it, and a gate nobody can pass is not a gate.
#
# What guards the source instead is
# `tests/bindings/generated/test_every_generated_module_imports.py`: it imports
# every shipped module, which no marker can silence and which is what found the
# fourteen that did not import at all.
# Not the `requires-python` floor: numpy's own stubs use a `type` statement,
# which mypy refuses to parse below 3.12, so a lower setting stops the run
# before it has checked anything of ours. That the package installs and imports
# on its floor is checked where a user meets it, by installing it there.
python_version = "3.12"
files = ["OpenGL/**/*.pyi"]
# Only the stubs. A `.pyi` beside a module shadows it, so this checks the
# declared surface without following into the implementation.
follow_imports = "skip"
ignore_missing_imports = true
warn_unused_ignores = true
[tool.black]
skip-string-normalization=true
[tool.openglcontext-checks]
# The OGC rules (openglcontext-checks) this project is clean of, held by
# the workspace preflight. The others join as the project reaches zero of
# each; the counts are in openglcontext/plans/DEFECT-PREVENTION.md.
select = ["OGC121", "OGC131", "OGC141"]
# Upstream checkouts the code generators and the documentation build fetch:
# not this project's code, and not in version control here.
exclude = ["src/khronosapi", "src/eglapi", "directdocs/OpenGL-Refpages"]
[tool.ruff.lint]
# Named explicitly. With `select` unset ruff applies its own default, and that
# default has widened between releases -- the same checkout went from a few
# hundred findings to five thousand without a line changing. A gate whose
# meaning moves when a tool is upgraded is not a gate, so this says what the
# project holds itself to.
select = ["E", "W", "F", "B", "PGH003", "PGH004", "PLE0604", "PLE0605"]
# `_`, `err`, and the conventional `_name`: an underscore prefix is how Python
# says a name is bound but deliberately unused, and a loop target that must be
# unpacked to reach the one beside it reads better as `_feet` than as `_`.
dummy-variable-rgx = "^(_[a-zA-Z0-9_]*|err)$"
ignore = [
'D200', # Allow multiline docstring even when it could fit on one line
'C408', # Allow instantiating dictionary as dict(key=value)
'E501', # Line too long
'ANN101', # Don't require self to be annotated.
'PTH', # allow os.path
'PLR0913', # allow too many arguments
# 'F841', # we often have `err` as unused argument in this codebase
'E401', # I prefer multiple imports/line generally
'E402', # Common pattern with autogenerated boilerplate in the wrappers
'F401', # Unused imports from autogeneration are everywhere
'F403', # Standard usage is to use star imports
'F405', # Standard usage is to use star imports
# 'F821', # Undefined variables due to start imports, again
'E741', # Variables from the underlying api are often e.g. `l`
# Names arrive from the declaration tables when `_define()` runs, so a
# checker reading a generated module sees uses of things it cannot find.
# The same reason F403 and F405 are above; what actually holds those
# modules to importing is the suite, which imports every one of them.
'F821',
# Whitespace, in a codebase that has been accumulating it since 1997.
# Nine thousand of these, every one mechanical: putting them right is a
# deliberate pass of its own, on its own commit, not something a release
# gate should force into an unrelated change. Reflowing them here would
# rewrite `git blame` for most of the package to say this.
'W291', # trailing whitespace
'W293', # whitespace on a blank line
'W292', # no newline at end of file
'E101', # indentation mixes spaces and tabs
]
[tool.ruff.lint.per-file-ignores]
# The friendly modules rebind what the generated declarations defined --
# `def glFoo` over the imported glFoo, `glFoo = wrapper.wrapper(glFoo)` -- which
# is F811's whole subject. Everywhere else a second definition hides the first.
"OpenGL/**/*.py" = [
"F811",
]
"OpenGL/_bytes.py" = [
"F821",
]
"OpenGL/GL/shaders.py" = [
"F821",
]
"OpenGL/wrapper.py" = [
'F841', # we often have `err` as unused argument in this codebase
]
"OpenGL/GL/images.py" = [
"F822",
]
"OpenGL/GL/pointers.py"= [
"F822",
]
"OpenGL/Tk/__init__.py"=[
"E701",
]
[tool.ruff.format]
quote-style = "preserve"
# uv decides whether its cached metadata for a source tree is still current
# from pyproject.toml, setup.py and setup.cfg alone. This project's version
# lives in OpenGL/version.py, so name that file too: without it a version bump
# leaves uv answering with the previous number, and a dependent requiring the
# new one resolves against PyPI rather than this checkout.
[tool.uv]
cache-keys = [{ file = "pyproject.toml" }, { file = "OpenGL/version.py" }]