Skip to content

Commit 1432b19

Browse files
jholveckBoboTiG
andauthored
feat: use Sphinx autodoc for documentation (#443)
* Migrate to Sphinx autodoc. Existing API docs were moved into docstrings, and Sphinx's autodoc extension is used to generate the API reference automatically. * Move close implementations to _close_impl. Without this, autodoc was adding the close method to all the subclasses of MSSBase that implemented it. It didn't add things like grab and such, since the subclasses didn't implement it. While I'm at it, also make the close method safe to call multiple times. * Fix bugs in previous autodoc commits. First, we previously patched ctypes.WINFUNCTYPE in any time mss.windows was imported, so that autodoc could import mss.windows. Instead, move that to just when we're running Sphinx. Second, when I added _close_impl, I accidentally deleted the super() call in xshmgetimage. This mean that it never closes connections, and exhausts the server's client limit. Finally, in a previous commit, I tested changing mss.lock from a Lock to an RLock while I was dealing with the fact that MSSBase.close holds the lock, and the xlib implementation grabs the global lock. That was not a great solution, so I backed that out. Instead, the xlib implementation just trusts that it's run under the lock. However, a better implementation might be to create a per-object lock in MSSBase to protect _closing, instead of using the course-grained global lock. * Typo fix Co-authored-by: Mickaël Schoentgen <contact@tiger-222.fr> * Additional doc clarifications and formatting improvements This adds extra detail to some docstrings, and brings formatting in line with Sphinx conventions. * Clean up the way constructor parameters are handled. Most constructor parameters weren't being documented by autodoc, since __init__ wasn't documented. If we added the __init__ docstrings to the class documentation (autoclass_source="both"), then that puts an awkward "Initialize a ScreenShot object" or similar sentence in the class constructor documentation in the rendered output. Instead, we put the __init__ parameter docs in the class docstring, and removed __init__ docstrings. Added "See also" sections to make looking up constructor parameters across __init__(**kwargs) chains a little more obvious. Added other requested changes: * Added mss.darwin.IMAGE_OPTIONS to the docs * Added docstring for mss.darwin.MSS.max_displays instance variable * Added docstrings to NamedTuple classes in mss.models * Added formatting for code samples in ScreenShot.bgra and rgb * Typo fix --------- Co-authored-by: Mickaël Schoentgen <contact@tiger-222.fr>
1 parent 669a978 commit 1432b19

File tree

17 files changed

+386
-577
lines changed

17 files changed

+386
-577
lines changed

docs/source/api.rst

Lines changed: 40 additions & 434 deletions
Large diffs are not rendered by default.

docs/source/conf.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66

77
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
88

9+
import ctypes
10+
911
import mss
1012

1113
# -- General configuration ------------------------------------------------
1214

1315
extensions = [
16+
"sphinx.ext.autodoc",
1417
"sphinx_copybutton",
1518
"sphinx.ext.intersphinx",
1619
"sphinx_new_tab_link",
@@ -29,6 +32,16 @@
2932
release = "latest"
3033
language = "en"
3134
todo_include_todos = True
35+
autodoc_member_order = "bysource"
36+
autodoc_default_options = {
37+
"members": True,
38+
"undoc-members": True,
39+
"show-inheritance": True,
40+
}
41+
42+
# Monkey-patch WINFUNCTYPE into ctypes, so that we can import
43+
# mss.windows while building the documentation.
44+
ctypes.WINFUNCTYPE = ctypes.CFUNCTYPE # type:ignore[attr-defined]
3245

3346

3447
# -- Options for HTML output ----------------------------------------------

docs/source/developers.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ You will need `pytest <https://pypi.org/project/pytest/>`_::
2929
How to Test?
3030
------------
3131

32-
Launch the test suit::
32+
Launch the test suite::
3333

3434
$ python -m pytest
3535

src/mss/__init__.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1+
# This module is maintained by Mickaël Schoentgen <contact@tiger-222.fr>.
2+
#
3+
# You can always get the latest version of this module at:
4+
# https://github.com/BoboTiG/python-mss
5+
# If that URL should fail, try contacting the author.
16
"""An ultra fast cross-platform multiple screenshots module in pure python
27
using ctypes.
3-
4-
This module is maintained by Mickaël Schoentgen <contact@tiger-222.fr>.
5-
6-
You can always get the latest version of this module at:
7-
https://github.com/BoboTiG/python-mss
8-
If that URL should fail, try contacting the author.
98
"""
109

1110
from mss.exception import ScreenShotError

src/mss/base.py

Lines changed: 70 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
"""This is part of the MSS Python's module.
2-
Source: https://github.com/BoboTiG/python-mss.
3-
"""
1+
# This is part of the MSS Python's module.
2+
# Source: https://github.com/BoboTiG/python-mss.
43

54
from __future__ import annotations
65

@@ -35,15 +34,28 @@
3534

3635
UTC = timezone.utc
3736

37+
#: Global lock protecting access to platform screenshot calls.
38+
#:
39+
#: .. versionadded:: 6.0.0
3840
lock = Lock()
3941

4042
OPAQUE = 255
4143

4244

4345
class MSSBase(metaclass=ABCMeta):
44-
"""This class will be overloaded by a system specific one."""
46+
"""Base class for all Multiple ScreenShots implementations.
4547
46-
__slots__ = {"_monitors", "cls_image", "compression_level", "with_cursor"}
48+
:param backend: Backend selector, for platforms with multiple backends.
49+
:param compression_level: PNG compression level.
50+
:param with_cursor: Include the mouse cursor in screenshots.
51+
:param display: X11 display name (GNU/Linux only).
52+
:param max_displays: Maximum number of displays to enumerate (macOS only).
53+
54+
.. versionadded:: 8.0.0
55+
``compression_level``, ``display``, ``max_displays``, and ``with_cursor`` keyword arguments.
56+
"""
57+
58+
__slots__ = {"_closed", "_monitors", "cls_image", "compression_level", "with_cursor"}
4759

4860
def __init__(
4961
self,
@@ -58,9 +70,17 @@ def __init__(
5870
max_displays: int = 32, # noqa: ARG002
5971
) -> None:
6072
self.cls_image: type[ScreenShot] = ScreenShot
73+
#: PNG compression level used when saving the screenshot data into a file
74+
#: (see :py:func:`zlib.compress()` for details).
75+
#:
76+
#: .. versionadded:: 3.2.0
6177
self.compression_level = compression_level
78+
#: Include the mouse cursor in screenshots.
79+
#:
80+
#: .. versionadded:: 8.0.0
6281
self.with_cursor = with_cursor
6382
self._monitors: Monitors = []
83+
self._closed = False
6484
# If there isn't a factory that removed the "backend" argument, make sure that it was set to "default".
6585
# Factories that do backend-specific dispatch should remove that argument.
6686
if backend != "default":
@@ -91,17 +111,43 @@ def _monitors_impl(self) -> None:
91111
It must populate self._monitors.
92112
"""
93113

94-
def close(self) -> None: # noqa:B027
95-
"""Clean-up."""
114+
def _close_impl(self) -> None: # noqa:B027
115+
"""Clean up.
116+
117+
This will be called at most once.
118+
"""
119+
# It's not necessary for subclasses to implement this if they have nothing to clean up.
120+
121+
def close(self) -> None:
122+
"""Clean up.
123+
124+
This releases resources that MSS may be using. Once the MSS
125+
object is closed, it may not be used again.
126+
127+
It is safe to call this multiple times; multiple calls have no
128+
effect.
129+
130+
Rather than use :py:meth:`close` explicitly, we recommend you
131+
use the MSS object as a context manager::
132+
133+
with mss.mss() as sct:
134+
...
135+
"""
136+
with lock:
137+
if self._closed:
138+
return
139+
self._close_impl()
140+
self._closed = True
96141

97142
def grab(self, monitor: Monitor | tuple[int, int, int, int], /) -> ScreenShot:
98143
"""Retrieve screen pixels for a given monitor.
99144
100-
Note: *monitor* can be a tuple like the one PIL.Image.grab() accepts.
145+
Note: *monitor* can be a tuple like the one
146+
py:meth:`PIL.ImageGrab.grab` accepts: `(left, top, right, bottom)`
101147
102148
:param monitor: The coordinates and size of the box to capture.
103149
See :meth:`monitors <monitors>` for object details.
104-
:return :class:`ScreenShot <ScreenShot>`.
150+
:returns: Screenshot of the requested region.
105151
"""
106152
# Convert PIL bbox style
107153
if isinstance(monitor, tuple):
@@ -133,16 +179,16 @@ def monitors(self) -> Monitors:
133179
134180
This method has to fill self._monitors with all information
135181
and use it as a cache:
136-
self._monitors[0] is a dict of all monitors together
137-
self._monitors[N] is a dict of the monitor N (with N > 0)
182+
183+
- self._monitors[0] is a dict of all monitors together
184+
- self._monitors[N] is a dict of the monitor N (with N > 0)
138185
139186
Each monitor is a dict with:
140-
{
141-
'left': the x-coordinate of the upper-left corner,
142-
'top': the y-coordinate of the upper-left corner,
143-
'width': the width,
144-
'height': the height
145-
}
187+
188+
- ``left``: the x-coordinate of the upper-left corner
189+
- ``top``: the y-coordinate of the upper-left corner
190+
- ``width``: the width
191+
- ``height``: the height
146192
"""
147193
if not self._monitors:
148194
with lock:
@@ -160,28 +206,13 @@ def save(
160206
) -> Iterator[str]:
161207
"""Grab a screenshot and save it to a file.
162208
163-
:param int mon: The monitor to screenshot (default=0).
164-
-1: grab one screenshot of all monitors
165-
0: grab one screenshot by monitor
166-
N: grab the screenshot of the monitor N
167-
168-
:param str output: The output filename.
169-
170-
It can take several keywords to customize the filename:
171-
- `{mon}`: the monitor number
172-
- `{top}`: the screenshot y-coordinate of the upper-left corner
173-
- `{left}`: the screenshot x-coordinate of the upper-left corner
174-
- `{width}`: the screenshot's width
175-
- `{height}`: the screenshot's height
176-
- `{date}`: the current date using the default formatter
177-
178-
As it is using the `format()` function, you can specify
179-
formatting options like `{date:%Y-%m-%s}`.
180-
181-
:param callable callback: Callback called before saving the
182-
screenshot to a file. Take the `output` argument as parameter.
183-
184-
:return generator: Created file(s).
209+
:param int mon: The monitor to screenshot (default=0). ``-1`` grabs all
210+
monitors, ``0`` grabs each monitor, and ``N`` grabs monitor ``N``.
211+
:param str output: The output filename. Keywords: ``{mon}``, ``{top}``,
212+
``{left}``, ``{width}``, ``{height}``, ``{date}``.
213+
:param callable callback: Called before saving the screenshot; receives
214+
the ``output`` argument.
215+
:return: Created file(s).
185216
"""
186217
monitors = self.monitors
187218
if not monitors:

src/mss/darwin.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
"""This is part of the MSS Python's module.
2-
Source: https://github.com/BoboTiG/python-mss.
1+
"""macOS CoreGraphics backend for MSS.
2+
3+
Uses the CoreGraphics APIs to capture windows and enumerates up to
4+
``max_displays`` active displays.
35
"""
46

57
from __future__ import annotations
@@ -18,7 +20,7 @@
1820
if TYPE_CHECKING: # pragma: nocover
1921
from mss.models import CFunctions, Monitor
2022

21-
__all__ = ("MSS",)
23+
__all__ = ("IMAGE_OPTIONS", "MSS")
2224

2325
BACKENDS = ["default"]
2426

@@ -27,8 +29,9 @@
2729
kCGWindowImageBoundsIgnoreFraming = 1 << 0 # noqa: N816
2830
kCGWindowImageNominalResolution = 1 << 4 # noqa: N816
2931
kCGWindowImageShouldBeOpaque = 1 << 1 # noqa: N816
30-
# Note: set `IMAGE_OPTIONS = 0` to turn on scaling (see issue #257 for more information)
31-
IMAGE_OPTIONS = kCGWindowImageBoundsIgnoreFraming | kCGWindowImageShouldBeOpaque | kCGWindowImageNominalResolution
32+
#: For advanced users: as a note, you can set ``IMAGE_OPTIONS = 0`` to turn on scaling; see issue #257 for more
33+
#: information.
34+
IMAGE_OPTIONS: int = kCGWindowImageBoundsIgnoreFraming | kCGWindowImageShouldBeOpaque | kCGWindowImageNominalResolution
3235

3336

3437
def cgfloat() -> type[c_double | c_float]:
@@ -92,14 +95,22 @@ def __repr__(self) -> str:
9295
class MSS(MSSBase):
9396
"""Multiple ScreenShots implementation for macOS.
9497
It uses intensively the CoreGraphics library.
98+
99+
:param max_displays: maximum number of displays to handle (default: 32).
100+
:type max_displays: int
101+
102+
.. seealso::
103+
104+
:py:class:`mss.base.MSSBase`
105+
Lists other parameters.
95106
"""
96107

97108
__slots__ = {"core", "max_displays"}
98109

99110
def __init__(self, /, **kwargs: Any) -> None:
100-
"""MacOS initialisations."""
101111
super().__init__(**kwargs)
102112

113+
#: Maximum number of displays to handle.
103114
self.max_displays = kwargs.get("max_displays", 32)
104115

105116
self._init_library()
@@ -117,6 +128,7 @@ def _init_library(self) -> None:
117128
if not coregraphics:
118129
msg = "No CoreGraphics library found."
119130
raise ScreenShotError(msg)
131+
# :meta:private:
120132
self.core = ctypes.cdll.LoadLibrary(coregraphics)
121133

122134
def _set_cfunctions(self) -> None:

src/mss/exception.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
"""This is part of the MSS Python's module.
2-
Source: https://github.com/BoboTiG/python-mss.
3-
"""
1+
# This is part of the MSS Python's module.
2+
# Source: https://github.com/BoboTiG/python-mss.
43

54
from __future__ import annotations
65

@@ -12,4 +11,11 @@ class ScreenShotError(Exception):
1211

1312
def __init__(self, message: str, /, *, details: dict[str, Any] | None = None) -> None:
1413
super().__init__(message)
14+
#: On GNU/Linux, and if the error comes from the XServer, it contains XError details.
15+
#: This is an empty dict by default.
16+
#:
17+
#: For XErrors, you can find information on
18+
#: `Using the Default Error Handlers <https://tronche.com/gui/x/xlib/event-handling/protocol-errors/default-handlers.html>`_.
19+
#:
20+
#: .. versionadded:: 3.3.0
1521
self.details = details or {}

src/mss/factory.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
"""This is part of the MSS Python's module.
2-
Source: https://github.com/BoboTiG/python-mss.
3-
"""
1+
# This is part of the MSS Python's module.
2+
# Source: https://github.com/BoboTiG/python-mss.
43

54
import platform
65
from typing import Any
@@ -18,6 +17,15 @@ def mss(**kwargs: Any) -> MSSBase:
1817
1918
It then proxies its arguments to the class for
2019
instantiation.
20+
21+
.. seealso::
22+
- :class:`mss.darwin.MSS`
23+
- :class:`mss.linux.MSS`
24+
- :class:`mss.windows.MSS`
25+
- :func:`mss.linux.mss`
26+
- :class:`mss.linux.xshmgetimage.MSS`
27+
- :class:`mss.linux.xgetimage.MSS`
28+
- :class:`mss.linux.xlib.MSS`
2129
"""
2230
os_ = platform.system().lower()
2331

src/mss/linux/__init__.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""GNU/Linux backend dispatcher for X11 screenshot implementations."""
2+
13
from typing import Any
24

35
from mss.base import MSSBase
@@ -7,14 +9,35 @@
79

810

911
def mss(backend: str = "default", **kwargs: Any) -> MSSBase:
10-
"""Factory returning a proper MSS class instance.
12+
"""Return a backend-specific MSS implementation for GNU/Linux.
13+
14+
Selects and instantiates the appropriate X11 backend based on the
15+
``backend`` parameter.
16+
17+
:param backend: Backend selector. Valid values:
18+
19+
- ``"default"`` or ``"xshmgetimage"`` (default): XCB-based backend
20+
using XShmGetImage with automatic fallback to XGetImage when MIT-SHM
21+
is unavailable; see :py:class:`mss.linux.xshmgetimage.MSS`.
22+
- ``"xgetimage"``: XCB-based backend using XGetImage;
23+
see :py:class:`mss.linux.xgetimage.MSS`.
24+
- ``"xlib"``: Legacy Xlib-based backend retained for environments
25+
without working XCB libraries; see :py:class:`mss.linux.xlib.MSS`.
1126
12-
It examines the options provided, and chooses the most adapted MSS
13-
class to take screenshots. It then proxies its arguments to the
14-
class for instantiation.
27+
.. versionadded:: 10.2.0 Prior to this version, the
28+
:class:`mss.linux.xlib.MSS` implementation was the only available
29+
backend.
1530
16-
Currently, the only option used is the "backend" flag. Future
17-
versions will look at other options as well.
31+
:param display: Optional keyword argument. Specifies an X11 display
32+
string to connect to. The default is taken from the environment
33+
variable :envvar:`DISPLAY`.
34+
:type display: str | bytes | None
35+
:param kwargs: Additional keyword arguments passed to the backend class.
36+
:returns: An MSS backend implementation.
37+
38+
.. versionadded:: 10.2.0 Prior to this version, this didn't exist:
39+
the :func:`mss.linux.MSS` was a class equivalent to the current
40+
:class:`mss.linux.xlib.MSS` implementation.
1841
"""
1942
backend = backend.lower()
2043
if backend == "xlib":
@@ -39,4 +62,9 @@ class for instantiation.
3962

4063
# Alias in upper-case for backward compatibility. This is a supported name in the docs.
4164
def MSS(*args, **kwargs) -> MSSBase: # type: ignore[no-untyped-def] # noqa: N802, ANN002, ANN003
65+
"""Alias for :func:`mss.linux.mss.mss` for backward compatibility.
66+
67+
.. versionchanged:: 10.2.0 Prior to this version, this was a class.
68+
.. deprecated:: 10.2.0 Use :func:`mss.linux.mss` instead.
69+
"""
4270
return mss(*args, **kwargs)

0 commit comments

Comments
 (0)