Skip to content

ENH: Move PyperclipException and PyperclipWindowsException to error/_… #47491

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 25, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/source/reference/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Exceptions and warnings
errors.ParserError
errors.ParserWarning
errors.PerformanceWarning
errors.PyperclipException
errors.PyperclipWindowsException
errors.SettingWithCopyError
errors.SettingWithCopyWarning
errors.SpecificationError
Expand Down
17 changes: 17 additions & 0 deletions pandas/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Data sets/files
- Time zones
- Dtypes
- Ctypes
- Misc
"""

Expand Down Expand Up @@ -1692,6 +1693,22 @@ def any_skipna_inferred_dtype(request):
return inferred_dtype, values


# ----------------------------------------------------------------
# Ctypes
# ----------------------------------------------------------------
@pytest.fixture
def mock_ctypes(monkeypatch):
"""
Mocks WinError to help with testing the clipboard.
"""

def _mock_win_error():
return "Window Error"

# Set raising to False because WinError won't exist on non-windows platforms
monkeypatch.setattr("ctypes.WinError", _mock_win_error, raising=False)


# ----------------------------------------------------------------
# Misc
# ----------------------------------------------------------------
Expand Down
21 changes: 21 additions & 0 deletions pandas/errors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"""
from __future__ import annotations

import ctypes

from pandas._config.config import OptionError # noqa:F401

from pandas._libs.tslibs import ( # noqa:F401
Expand Down Expand Up @@ -374,3 +376,22 @@ class IndexingError(Exception):
>>> s.loc["a", "c", "d"] # doctest: +SKIP
... # IndexingError: Too many indexers
"""


class PyperclipException(RuntimeError):
"""
Exception is raised when trying to use methods like to_clipboard() and
read_clipboard() on an unsupported OS/platform.
"""


class PyperclipWindowsException(PyperclipException):
"""
Exception is raised when pandas is unable to get access to the clipboard handle
due to some other window process is accessing it.
"""

def __init__(self, message: str) -> None:
# attr only exists on Windows, so typing fails on other platforms
message += f" ({ctypes.WinError()})" # type: ignore[attr-defined]
super().__init__(message)
17 changes: 5 additions & 12 deletions pandas/io/clipboard/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
import time
import warnings

from pandas.errors import (
PyperclipException,
PyperclipWindowsException,
)

# `import PyQt4` sys.exit()s if DISPLAY is not in the environment.
# Thus, we need to detect the presence of $DISPLAY manually
# and not load PyQt4 if it is absent.
Expand Down Expand Up @@ -87,18 +92,6 @@ def _executable_exists(name):
)


# Exceptions
class PyperclipException(RuntimeError):
pass


class PyperclipWindowsException(PyperclipException):
def __init__(self, message) -> None:
# attr only exists on Windows, so typing fails on other platforms
message += f" ({ctypes.WinError()})" # type: ignore[attr-defined]
super().__init__(message)


def _stringifyText(text) -> str:
acceptedTypes = (str, int, float, bool)
if not isinstance(text, acceptedTypes):
Expand Down
67 changes: 67 additions & 0 deletions pandas/tests/io/test_clipboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
import numpy as np
import pytest

from pandas.errors import (
PyperclipException,
PyperclipWindowsException,
)

from pandas import (
DataFrame,
get_option,
Expand All @@ -11,6 +16,8 @@
import pandas._testing as tm

from pandas.io.clipboard import (
CheckedCall,
_stringifyText,
clipboard_get,
clipboard_set,
)
Expand Down Expand Up @@ -110,6 +117,66 @@ def df(request):
raise ValueError


@pytest.mark.usefixtures("mock_ctypes")
def test_checked_call_with_bad_call(monkeypatch):
"""
Give CheckCall a function that returns a falsey value and
mock get_errno so it returns false so an exception is raised.
"""

def _return_false():
return False

monkeypatch.setattr("pandas.io.clipboard.get_errno", lambda: True)
msg = f"Error calling {_return_false.__name__} \\(Window Error\\)"

with pytest.raises(PyperclipWindowsException, match=msg):
CheckedCall(_return_false)()


@pytest.mark.usefixtures("mock_ctypes")
def test_checked_call_with_valid_call(monkeypatch):
"""
Give CheckCall a function that returns a truthy value and
mock get_errno so it returns true so an exception is not raised.
The function should return the results from _return_true.
"""

def _return_true():
return True

monkeypatch.setattr("pandas.io.clipboard.get_errno", lambda: False)

# Give CheckedCall a callable that returns a truthy value s
checked_call = CheckedCall(_return_true)
assert checked_call() is True


@pytest.mark.parametrize(
"text",
[
"String_test",
True,
1,
1.0,
1j,
],
)
def test_stringify_text(text):
valid_types = (str, int, float, bool)

if type(text) in valid_types:
result = _stringifyText(text)
assert result == str(text)
else:
msg = (
"only str, int, float, and bool values "
f"can be copied to the clipboard, not {type(text).__name__}"
)
with pytest.raises(PyperclipException, match=msg):
_stringifyText(text)


@pytest.fixture
def mock_clipboard(monkeypatch, request):
"""Fixture mocking clipboard IO.
Expand Down
9 changes: 9 additions & 0 deletions pandas/tests/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from pandas.errors import (
AbstractMethodError,
PyperclipWindowsException,
UndefinedVariableError,
)

Expand All @@ -28,6 +29,7 @@
"SettingWithCopyWarning",
"NumExprClobberingError",
"IndexingError",
"PyperclipException",
],
)
def test_exception_importable(exc):
Expand Down Expand Up @@ -70,6 +72,13 @@ def test_catch_undefined_variable_error(is_local):
raise UndefinedVariableError(variable_name, is_local)


@pytest.mark.usefixtures("mock_ctypes")
def test_PyperclipWindowsException():
msg = "test \\(Window Error\\)"
with pytest.raises(PyperclipWindowsException, match=msg):
raise PyperclipWindowsException("test")


class Foo:
@classmethod
def classmethod(cls):
Expand Down