Skip to content

Commit 44b3483

Browse files
authored
Merge pull request #3188 from zoldalma999/docs-to-stubs
Generate reST/ref docs from python or stub files, move time and cursors docs
2 parents 46a25b7 + d9c1c0e commit 44b3483

File tree

14 files changed

+512
-496
lines changed

14 files changed

+512
-496
lines changed

.github/workflows/build-debian-multiarch.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ env:
4444
apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev -y
4545
apt-get install libfreetype6-dev libportmidi-dev fontconfig -y
4646
apt-get install python3-dev python3-pip python3-wheel python3-sphinx -y
47-
pip3 install meson-python --break-system-packages
47+
pip3 install meson-python "sphinx-autoapi<=3.3.2" --break-system-packages
4848
4949
jobs:
5050
build-multiarch:

buildconfig/stubs/gen_stubs.py

+2
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ def get_all(mod: Any):
131131
f.write(misc_stubs)
132132

133133
for mod, items in pygame_all_imports.items():
134+
if mod == "pygame":
135+
mod = "."
134136
if len(items) <= 4:
135137
# try to write imports in a single line if it can fit the line limit
136138
import_items = (f"{string} as {string}" for string in items)

buildconfig/stubs/pygame/__init__.pyi

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# A script to auto-generate locals.pyi, constants.pyi and __init__.pyi typestubs
33
# IMPORTANT NOTE: Do not edit this file by hand!
44

5-
from pygame import (
5+
from . import (
66
display as display,
77
draw as draw,
88
event as event,

buildconfig/stubs/pygame/time.pyi

+137-9
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,143 @@
1+
"""Pygame module for monitoring time.
2+
3+
Times in pygame are represented in milliseconds (1/1000 seconds). Most
4+
platforms have a limited time resolution of around 10 milliseconds. This
5+
resolution, in milliseconds, is given in the ``TIMER_RESOLUTION`` constant.
6+
"""
7+
18
from typing import Union, final
29

310
from pygame.event import Event
411

5-
def get_ticks() -> int: ...
6-
def wait(milliseconds: int, /) -> int: ...
7-
def delay(milliseconds: int, /) -> int: ...
8-
def set_timer(event: Union[int, Event], millis: int, loops: int = 0) -> None: ...
12+
def get_ticks() -> int:
13+
"""Get the time in milliseconds.
14+
15+
Return the number of milliseconds since ``pygame.init()`` was called. Before
16+
pygame is initialized this will always be 0.
17+
"""
18+
19+
def wait(milliseconds: int, /) -> int:
20+
"""Pause the program for an amount of time.
21+
22+
Will pause for a given number of milliseconds. This function sleeps the
23+
process to share the processor with other programs. A program that waits for
24+
even a few milliseconds will consume very little processor time. It is
25+
slightly less accurate than the ``pygame.time.delay()`` function.
26+
27+
This returns the actual number of milliseconds used.
28+
"""
29+
30+
def delay(milliseconds: int, /) -> int:
31+
"""Pause the program for an amount of time.
32+
33+
Will pause for a given number of milliseconds. This function will use the
34+
processor (rather than sleeping) in order to make the delay more accurate
35+
than ``pygame.time.wait()``.
36+
37+
This returns the actual number of milliseconds used.
38+
"""
39+
40+
def set_timer(event: Union[int, Event], millis: int, loops: int = 0) -> None:
41+
"""Repeatedly create an event on the event queue.
42+
43+
Set an event to appear on the event queue every given number of milliseconds.
44+
The first event will not appear until the amount of time has passed.
45+
46+
The ``event`` attribute can be a ``pygame.event.Event`` object or an integer
47+
type that denotes an event.
48+
49+
``loops`` is an integer that denotes the number of events posted. If 0 (default)
50+
then the events will keep getting posted, unless explicitly stopped.
51+
52+
To disable the timer for such an event, call the function again with the same
53+
event argument with ``millis`` argument set to 0.
54+
55+
It is also worth mentioning that a particular event type can only be put on a
56+
timer once. In other words, there cannot be two timers for the same event type.
57+
Setting an event timer for a particular event discards the old one for that
58+
event type.
59+
60+
When this function is called with an ``Event`` object, the event(s) received
61+
on the event queue will be a shallow copy; the dict attribute of the event
62+
object passed as an argument and the dict attributes of the event objects
63+
received on timer will be references to the same dict object in memory.
64+
Modifications on one dict can affect another, use deepcopy operations on the
65+
dict object if you don't want this behaviour.
66+
However, calling this function with an integer event type would place event objects
67+
on the queue that don't have a common dict reference.
68+
69+
``loops`` replaces the ``once`` argument, and this does not break backward
70+
compatibility.
71+
72+
.. versionaddedold:: 2.0.0.dev3 once argument added.
73+
.. versionchangedold:: 2.0.1 event argument supports ``pygame.event.Event`` object
74+
.. versionaddedold:: 2.0.1 added loops argument to replace once argument
75+
"""
76+
977
@final
1078
class Clock:
11-
def tick(self, framerate: float = 0, /) -> int: ...
12-
def tick_busy_loop(self, framerate: float = 0, /) -> int: ...
13-
def get_time(self) -> int: ...
14-
def get_rawtime(self) -> int: ...
15-
def get_fps(self) -> float: ...
79+
"""Create an object to help track time.
80+
81+
Creates a new Clock object that can be used to track an amount of time. The
82+
clock also provides several functions to help control a game's framerate.
83+
84+
.. versionchanged:: 2.1.4 This class is also available through the ``pygame.Clock``
85+
alias.
86+
"""
87+
88+
def __new__(cls) -> Clock: ...
89+
def tick(self, framerate: float = 0, /) -> int:
90+
"""Update the clock.
91+
92+
This method should be called once per frame. It will compute how many
93+
milliseconds have passed since the previous call.
94+
95+
If you pass the optional framerate argument the function will delay to
96+
keep the game running slower than the given ticks per second. This can be
97+
used to help limit the runtime speed of a game. By calling
98+
``Clock.tick(40)`` once per frame, the program will never run at more
99+
than 40 frames per second.
100+
101+
Note that this function uses SDL_Delay function which is not accurate on
102+
every platform, but does not use much CPU. Use tick_busy_loop if you want
103+
an accurate timer, and don't mind chewing CPU.
104+
"""
105+
106+
def tick_busy_loop(self, framerate: float = 0, /) -> int:
107+
"""Update the clock.
108+
109+
This method should be called once per frame. It will compute how many
110+
milliseconds have passed since the previous call.
111+
112+
If you pass the optional framerate argument the function will delay to
113+
keep the game running slower than the given ticks per second. This can be
114+
used to help limit the runtime speed of a game. By calling
115+
``Clock.tick_busy_loop(40)`` once per frame, the program will never run at
116+
more than 40 frames per second.
117+
118+
Note that this function uses :func:`pygame.time.delay`, which uses lots
119+
of CPU in a busy loop to make sure that timing is more accurate.
120+
121+
.. versionaddedold:: 1.8
122+
"""
123+
124+
def get_time(self) -> int:
125+
"""Time used in the previous tick.
126+
127+
The number of milliseconds that passed between the previous two calls to
128+
``Clock.tick()``.
129+
"""
130+
131+
def get_rawtime(self) -> int:
132+
"""Actual time used in the previous tick.
133+
134+
Similar to ``Clock.get_time()``, but does not include any time used
135+
while ``Clock.tick()`` was delaying to limit the framerate.
136+
"""
137+
138+
def get_fps(self) -> float:
139+
"""Compute the clock framerate.
140+
141+
Compute your game's framerate (in frames per second). It is computed by
142+
averaging the last ten calls to ``Clock.tick()``.
143+
"""

docs/reST/conf.py

+15-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# All configuration values have a default; values that are commented out
1111
# serve to show the default.
1212

13-
import sys, os
13+
import sys, os, pathlib
1414

1515
# If extensions (or modules to document with autodoc) are in another directory,
1616
# add these directories to sys.path here. If the directory is relative to the
@@ -23,11 +23,22 @@
2323

2424
# Add any Sphinx extension module names here, as strings. They can be extensions
2525
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
26-
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest',
27-
'sphinx.ext.coverage', 'ext.headers', 'ext.boilerplate',
28-
'ext.customversion', 'ext.edit_on_github']
26+
extensions = ['autoapi.extension', 'ext.headers', 'ext.boilerplate',
27+
'ext.customversion', 'ext.edit_on_github', 'ext.documenters']
2928

3029

30+
autoapi_dirs = [
31+
pathlib.Path('.').resolve().parent.parent / 'buildconfig' / 'stubs' / 'pygame',
32+
pathlib.Path('.').resolve().parent.parent / 'src_py',
33+
]
34+
35+
autoapi_options = ['members', 'undoc-members']
36+
autoapi_ignore = ["*controller.py", "*_sdl2/window.py", "*freetype.py", "*ftfont.py", "*__pyinstaller*", "*__init__.py", "*__briefcase*"]
37+
autoapi_generate_api_docs = False
38+
autodoc_typehints = 'none'
39+
suppress_warnings = ['autoapi.python_import_resolution']
40+
autodoc_member_order = 'bysource'
41+
3142
# Add any paths that contain templates here, relative to this directory.
3243
templates_path = ['_templates']
3344

docs/reST/ext/documenters.py

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import autoapi
2+
import autoapi.documenters
3+
from autoapi._objects import PythonClass
4+
5+
6+
def build_signatures(object):
7+
name = object.short_name
8+
9+
if isinstance(object, PythonClass):
10+
if object.constructor is not None:
11+
object = object.constructor
12+
object.obj["return_annotation"] = name
13+
object.obj["overloads"] = [
14+
(arg, name) for arg, _ in object.obj["overloads"]
15+
]
16+
17+
else:
18+
for child in object.children:
19+
if child.short_name == "__new__":
20+
object = child
21+
break
22+
23+
if object is None or object.obj.get("args", None) is None:
24+
return
25+
26+
sigs = [(object.obj["args"], object.obj["return_annotation"])]
27+
sigs.extend(object.obj["overloads"])
28+
29+
for args, ret in sigs:
30+
arg_string = ""
31+
for modifier, arg_name, _, default in args:
32+
modifier = modifier or ""
33+
arg_name = arg_name or ""
34+
default = default or ""
35+
36+
if default:
37+
default = "=" + default
38+
arg_string += f", {modifier}{arg_name}{default}"
39+
40+
if arg_string:
41+
arg_string = arg_string[2:]
42+
43+
if ret.count("[") > 2 or ret.count(",") > 3:
44+
ret = ret.split("[")[0]
45+
if ret in ("Optional", "Union"):
46+
ret = "..."
47+
48+
yield f"| :sg:`{name}({arg_string}) -> {ret}`"
49+
50+
51+
def get_doc(env, obj):
52+
if obj.docstring:
53+
return obj.docstring
54+
55+
# If we don't already have docs, check if a python implementation exists of this
56+
# module and return its docstring if it does
57+
python_object = env.autoapi_all_objects.get(
58+
obj.id.replace("pygame", "src_py"), None
59+
)
60+
if python_object is not None:
61+
return python_object.docstring
62+
63+
return ""
64+
65+
66+
class AutopgDocumenter(autoapi.documenters.AutoapiDocumenter):
67+
def format_signature(self, **kwargs):
68+
return ""
69+
70+
def get_doc(self, encoding=None, ignore=1):
71+
return [get_doc(self.env, self.object).splitlines()]
72+
73+
def add_directive_header(self, sig: str) -> None:
74+
super().add_directive_header(sig)
75+
if "module" in self.objtype:
76+
sourcename = self.get_sourcename()
77+
self.add_line(f" :synopsis: {self.get_doc()[0][0]}", sourcename)
78+
79+
def get_object_members(self, want_all):
80+
members_check_module, members = super().get_object_members(want_all)
81+
members = (
82+
member
83+
for member in members
84+
if not member.object.obj.get("hide", False)
85+
and not member.object.imported
86+
and get_doc(self.env, member.object) != ""
87+
)
88+
89+
return members_check_module, members
90+
91+
def process_doc(self, docstrings):
92+
for docstring in docstrings:
93+
if not docstring:
94+
continue
95+
96+
yield f"| :sl:`{docstring[0]}`"
97+
98+
if "args" in self.object.obj or hasattr(self.object, "constructor"):
99+
yield from build_signatures(self.object)
100+
else:
101+
annotation = self.object.obj.get("annotation", None)
102+
if annotation is not None:
103+
if annotation.count("[") > 2 or annotation.count(",") > 3:
104+
annotation = "..."
105+
yield f"| :sg:`{self.object.short_name} -> {annotation}`"
106+
107+
yield from docstring[1:]
108+
109+
yield ""
110+
111+
112+
def setup(app):
113+
names = [
114+
"function",
115+
"property",
116+
"decorator",
117+
"class",
118+
"method",
119+
"data",
120+
"attribute",
121+
"module",
122+
"exception",
123+
]
124+
125+
for name in names:
126+
capitalized = name.capitalize()
127+
app.add_autodocumenter(
128+
type(
129+
f"Autopg{capitalized}Documenter",
130+
(
131+
AutopgDocumenter,
132+
getattr(autoapi.documenters, f"Autoapi{capitalized}Documenter"),
133+
),
134+
{"objtype": f"pg{name}"},
135+
)
136+
)

docs/reST/ext/indexer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ def collect_document_info(app, doctree):
6565

6666

6767
class CollectInfo(Visitor):
68-
6968
"""Records the information for a document"""
7069

7170
desctypes = {
@@ -74,6 +73,7 @@ class CollectInfo(Visitor):
7473
"exception",
7574
"class",
7675
"attribute",
76+
"property",
7777
"method",
7878
"staticmethod",
7979
"classmethod",

0 commit comments

Comments
 (0)