Skip to content
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

Notifications: fixes, cleanup, and tests #131

Merged
merged 4 commits into from May 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.rst
Expand Up @@ -4,6 +4,7 @@ Changes
0.3.0.dev
---------

- Notifications: fixes, cleanup, and tests #131
- Fix slider for some older macOS versions (10.11 and before?)
- Keyboard interrupts now stop a running application

Expand Down
8 changes: 6 additions & 2 deletions rumps/__init__.py
Expand Up @@ -22,5 +22,9 @@
__license__ = 'Modified BSD'
__copyright__ = 'Copyright 2017 Jared Suttles'

from .rumps import (separator, debug_mode, alert, notification, application_support, timers, quit_application, timer,
clicked, notifications, MenuItem, SliderMenuItem, Timer, Window, App, slider)
from . import notifications as _notifications
from .rumps import (separator, debug_mode, alert, application_support, timers, quit_application, timer,
clicked, MenuItem, SliderMenuItem, Timer, Window, App, slider)

notifications = _notifications.on_notification
notification = _notifications.notify
92 changes: 92 additions & 0 deletions rumps/_internal.py
@@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-

from __future__ import print_function

import inspect
import traceback

import Foundation

from . import compat
from . import exceptions


def require_string(*objs):
for obj in objs:
if not isinstance(obj, compat.string_types):
raise TypeError(
'a string is required but given {0}, a {1}'.format(obj, type(obj).__name__)
)


def require_string_or_none(*objs):
for obj in objs:
if not(obj is None or isinstance(obj, compat.string_types)):
raise TypeError(
'a string or None is required but given {0}, a {1}'.format(obj, type(obj).__name__)
)


def call_as_function_or_method(func, event):
# The idea here is that when using decorators in a class, the functions passed are not bound so we have to
# determine later if the functions we have (those saved as callbacks) for particular events need to be passed
# 'self'.
#
# This works for an App subclass method or a standalone decorated function. Will attempt to find function as
# a bound method of the App instance. If it is found, use it, otherwise simply call function.
from . import rumps
try:
app = getattr(rumps.App, '*app_instance')
except AttributeError:
pass
else:
for name, method in inspect.getmembers(app, predicate=inspect.ismethod):
if method.__func__ is func:
return method(event)
return func(event)


def guard_unexpected_errors(func):
"""Decorator to be used in PyObjC callbacks where an error bubbling up
would cause a crash. Instead of crashing, print the error to stderr and
prevent passing to PyObjC layer.

For Python 3, print the exception using chaining. Accomplished by setting
the cause of :exc:`rumps.exceptions.InternalRumpsError` to the exception.

For Python 2, emulate exception chaining by printing the original exception
followed by :exc:`rumps.exceptions.InternalRumpsError`.
"""
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)

except Exception as e:
internal_error = exceptions.InternalRumpsError(
'an unexpected error occurred within an internal callback'
)
if compat.PY2:
import sys
traceback.print_exc()
print('\nThe above exception was the direct cause of the following exception:\n', file=sys.stderr)
traceback.print_exception(exceptions.InternalRumpsError, internal_error, None)
else:
internal_error.__cause__ = e
traceback.print_exception(exceptions.InternalRumpsError, internal_error, None)

return wrapper


def string_to_objc(x):
if isinstance(x, compat.binary_type):
return Foundation.NSData.alloc().initWithData_(x)
elif isinstance(x, compat.string_types):
return Foundation.NSString.alloc().initWithString_(x)
else:
raise TypeError(
"expected a string or a bytes-like object but provided %s, "
"having type '%s'" % (
x,
type(x).__name__
)
)
6 changes: 6 additions & 0 deletions rumps/compat.py
Expand Up @@ -5,13 +5,19 @@
PY2 = sys.version_info[0] == 2

if not PY2:
binary_type = bytes
text_type = str
string_types = (str,)

iteritems = lambda d: iter(d.items())

import collections.abc as collections_abc

else:
binary_type = ()
text_type = unicode
string_types = (str, unicode)

iteritems = lambda d: d.iteritems()

import collections as collections_abc
9 changes: 9 additions & 0 deletions rumps/exceptions.py
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-


class RumpsError(Exception):
"""A generic rumps error occurred."""


class InternalRumpsError(RumpsError):
"""Internal mechanism powering functionality of rumps failed."""