Skip to content

Move to a strategy implementation design.#494

Draft
jholveck wants to merge 4 commits intoBoboTiG:mainfrom
jholveck:strategy
Draft

Move to a strategy implementation design.#494
jholveck wants to merge 4 commits intoBoboTiG:mainfrom
jholveck:strategy

Conversation

@jholveck
Copy link
Copy Markdown
Contributor

@jholveck jholveck commented Mar 31, 2026

See also BoboTiG/python-mss issue #486.

The user will always work with a single class: mss.MSS. Differences in implementation, such as platform or capture strategy, are hidden in an internal implementation object held by the mss.MSS object.

This allows us to change the implementation, with arbitrary class hierarchies, without worrying about preserving compatibility with internal class names.

This deprecates the existing mss factory function, although it can easily be kept for as long as needed to give users time to adapt.

It also deprecates the existing mss.{platform}.MSS types. These are exposed to the user, so somebody calling mss.{platform}.MSS() in 10.x can still reasonably expect to get a mss.{platform}.MSS object back. However, in 11.0, we can remove the type entirely, and either remove those symbols, or make them deprecated aliases for mss.MSS.

Where possible, deprecated functionality emits a DeprecationWarning. However, note that these are ignored by default, unless triggered by code in __main__.

Many of the API docs are removed, since this change removes much of the API surface. However, they are still in available for backwards-compatibility.

This change adds tests for everything that was in the 10.1 docs, examples, etc, at least at a basic level: for instance, it tests that mss.linux.MSS still works as both a constructor and a type (for isinstance), and that mss.linux.ZPIXMAP still exists (it was listed in the 10.1 docs).

The existing code, tests, and docs are changed to use mss.MSS.

Changes proposed in this PR

Fixes #486

  • Tests added/updated
  • Documentation updated
  • Changelog entry added
  • ./check.sh passed

See also BoboTiG/python-mss issue BoboTiG#486.

The user will always work with a single class: mss.MSS.  Differences
in implementation, such as platform or capture strategy, are hidden in
an internal implementation object held by the mss.MSS object.

This allows us to change the implementation, with arbitrary class
hierarchies, without worrying about preserving compatibility with
internal class names.

This deprecates the existing `mss` factory function, although it can
easily be kept for as long as needed to give users time to adapt.

It also deprecates the existing `mss.{platform}.MSS` types.  These are
exposed to the user, so somebody calling `mss.{platform}.MSS()` in
10.x can still reasonably expect to get a `mss.{platform}.MSS` object
back.  However, in 11.0, we can remove the type entirely, and either
remove those symbols, or make them deprecated aliases for `mss.MSS`.

Where possible, deprecated functionality emits a `DeprecationWarning`.
However, note that these are ignored by default, unless triggered by
code in `__main__`.

Many of the API docs are removed, since this change removes much of
the API surface.  However, they are still in available for
backwards-compatibility.

This change adds tests for everything that was in the 10.1 docs,
examples, etc, at least at a basic level: for instance, it tests that
`mss.linux.MSS` still works as both a constructor and a type (for
`isinstance`), and that `mss.linux.ZPIXMAP` still exists (it was
listed in the 10.1 docs).

The existing code, tests, and docs are changed to use `mss.MSS`.
The mss_impl fixture would add an implicit display= argument,
regardless of platform.  The code at that time would ignore it, but we
should be (and in the previous commit, were) more strict.  Change
mss_impl to only use display= if appropriate, so we can be more strict
in the future.

In 10.1, these were allowed at all times, and ignored if the platform
didn't use them.  Emulate this behavior in mss.MSS (and mss.mss), with
DeprecationWarnings, and test.
I'm pretty sure it's unnecessary there.  Not sure why it was being done.
@BoboTiG
Copy link
Copy Markdown
Owner

BoboTiG commented Mar 31, 2026

Thank you @jholveck, that's 👍 on my side!

@jholveck
Copy link
Copy Markdown
Contributor Author

Terrific; glad to hear it!

I'm going to leave it as draft for a day or two to make sure nothing else occurs to me, while giving you and @halldorfannar a chance to review.

This refactor was the big thing that I had before wanting to release 10.2.

But I do want to add the ScreenShot alias we discussed (simple), and maybe a docs section about the version compatibility policy. I'll do those in separate PRs, though.

What's on your plate before a 10.2 release?

@jholveck
Copy link
Copy Markdown
Contributor Author

By the way, here's a summary of the API surface from the 10.1 docs. I used this as a guide when looking at what to make sure was preserved and tested.

python-mss 10.1 Public API Inventory

This is a pragmatic inventory of what users were likely relying on in 10.1.0, based on:

  • docs in docs/source/*.rst
  • shipped example code in docs/source/examples/*.py
  • obvious entry-point module docstrings/signatures in src/mss/*

This is not an exhaustive symbol dump. It is a compatibility checklist for 10.2 and 11.0 transition work.

Tag legend:

  • [api] means the item appears in docs/source/api.rst but is not reinforced elsewhere in docs/examples and is less likely to be widely relied on. Keep if practical, but generally lower preservation priority than untagged items.

1) Top-level package surface (mss)

Likely public imports from src/mss/__init__.py:

  • mss.mss (factory function)
  • mss.ScreenShotError (exception class)

Also visibly present and commonly inspected by users/tools:

  • mss.__version__

Import/call styles shown in docs/examples:

  • from mss import mss then with mss() as sct:
  • import mss then with mss.mss() as sct:
  • import mss.tools then mss.tools.to_png(...)

2) Factory and main object

Primary construction path (shown in README/docs/examples):

  • mss.mss(**kwargs) -> MSSBase (actually returns OS-specific subclass)

Documented/used kwargs:

  • compression_level: int = 6
  • with_cursor: bool = False
  • display: bytes | str | None = None (Linux-focused)
  • max_displays: int = 32 (macOS-focused)

Returned runtime class is one of:

  • mss.darwin.MSS
  • mss.linux.MSS
  • mss.windows.MSS

Compatibility note:

  • docs describe this only implicitly ("appropriate MSS class" + OS-specific import examples)
  • explicit concrete-class guarantee exists in implementation (src/mss/factory.py) rather than strongly worded prose docs
  • for 10.2/11.0 planning, preserving exact concrete return class identity can be treated as lower-priority than preserving MSSBase behavior

Direct OS-specific imports are documented in usage.rst and therefore likely public:

  • from mss.linux import MSS as mss
  • from mss.darwin import MSS as mss
  • from mss.windows import MSS as mss

3) Core screenshot session object API (MSSBase / platform MSS)

Documented module-level symbol in mss.base:

  • [api] lock (threading lock)

Public methods users are shown to call:

  • close() -> None
  • grab(monitor_or_bbox) -> ScreenShot
  • save(mon=0, output="monitor-{mon}.png", callback=None) -> Iterator[str]
  • shot(**kwargs) -> str

Public context-manager protocol users rely on:

  • __enter__() / __exit__() via with mss() as sct:

Public attributes/properties users are shown to access or set:

  • monitors (property): list[dict[str, int]]
  • cls_image (custom screenshot class hook)
  • compression_level (PNG compression control)
  • with_cursor

Monitor dictionary shape (implied contract throughout docs/examples):

  • keys: left, top, width, height

grab() accepted inputs (documented + examples):

  • monitor dict with the shape above
  • PIL-style bbox tuple (left, top, right, lower)

save() filename formatting contract (documented):

  • placeholders: {mon}, {top}, {left}, {width}, {height}, {date}

4) Returned image object API (mss.screenshot.ScreenShot)

grab() returns ScreenShot (or subclass if cls_image is overridden).

Public constructor/class APIs:

  • ScreenShot(data, monitor, *, size=None)
  • ScreenShot.from_size(data, width, height)

Public methods:

  • pixel(coord_x, coord_y) -> tuple[int, int, int]

Public properties/attributes (docs/examples use these directly):

  • raw (BGRA bytearray)
  • bgra (bytes)
  • rgb (bytes)
  • pixels (2D RGB tuples)
  • pos (named tuple with left, top)
  • size (named tuple with width, height)
  • left
  • top
  • width
  • height
  • __array_interface__ (NumPy interoperability)

raw usage note:

  • not code-only; it is documented in API docs (docs/source/api.rst) and used directly in shipped example code (docs/source/examples/pil_pixels.py)
  • it is also described in the ScreenShot entry-point class comments/docstrings (src/mss/screenshot.py)

High-confidence example usage includes:

  • np.array(sct.grab(...))
  • sct_img.rgb, sct_img.bgra, sct_img.raw, sct_img.size
  • sct_img.width, sct_img.height, sct_img.pixel(x, y)

5) Helper function API

Public helper used in docs/examples:

  • mss.tools.to_png(data, size, *, level=6, output=None) -> bytes | None

Behavior contract:

  • if output is None, returns PNG bytes
  • otherwise writes a PNG file and returns None

6) Exception API

Public exception:

  • mss.exception.ScreenShotError

Public attribute documented for user handling:

  • .details: dict[str, Any] (notably for X11 error details on Linux)

7) CLI surface (python -m mss / mss)

Documented in usage.rst and implemented in src/mss/__main__.py.

Likely compatibility-sensitive options:

  • -c, --coordinates
  • -l, --level
  • -m, --monitor
  • -o, --output
  • --with-cursor
  • -q, --quiet
  • -v, --version

Documented coordinate format contract:

  • top, left, width, height

8) Documented platform low-level symbols (niche but publicly documented)

Because these appear in docs/source/api.rst, they were at least documented as public, even if most users do not rely on them.

mss.darwin documented symbols:

  • [api] CFUNCTIONS
  • [api] cgfloat
  • [api] CGPoint, CGSize, CGRect
  • [api] class MSS attributes: core, max_displays

mss.linux documented symbols:

  • [api] CFUNCTIONS, PLAINMASK, ZPIXMAP
  • [api] Display, XErrorEvent, XFixesCursorImage, XImage, XRRCrtcInfo, XRRModeInfo, XRRScreenResources, XWindowAttributes
  • [api] class MSS method: close()

mss.windows documented symbols:

  • [api] CAPTUREBLT, CFUNCTIONS, DIB_RGB_COLORS, SRCCOPY, MONITORNUMPROC
  • [api] BITMAPINFOHEADER, BITMAPINFO
  • [api] class MSS attributes: gdi32, user32

9) Additional likely-public typing/data-model symbols

Examples/docs expose or encourage importing these:

  • mss.models.Monitor
  • mss.models.Monitors
  • mss.models.Pixel
  • mss.models.Pixels
  • mss.models.Pos
  • mss.models.Size

Note: these are type aliases / named tuples. They are lower risk to keep but still worth preserving if refactoring modules.

10) Documented vs runtime mismatches (worth preserving intentionally)

There are stale-doc mismatches in 10.1.0 that users may still have copied from docs.

save() defaults:

  • api.rst documents save([mon=1], [output='mon-{mon}.png'], ...)
  • runtime in src/mss/base.py is mon=0, output='monitor-{mon}.png'

shot() behavior:

  • runtime default is effectively monitor 1 (shot() sets mon=1 if absent)
  • examples/docs consistently present shot() as "monitor 1" behavior

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

API design: handling multiple backends while keeping a stable MSS interface

2 participants