From cc7b9ca299f9549a06a581aebccf3bf0154b5471 Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Sat, 8 Mar 2025 17:50:18 +0100 Subject: [PATCH 01/13] Replicate old Travis CI build in GitHub actions --- .github/workflows/ci.yml | 49 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..763d727 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,49 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + test: + strategy: + matrix: + os: [macos-13, ubuntu-18.04] + python-version: ['2.7', '3.4', '3.5', '3.6', 'pypy'] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools + pip install .[xmpp] + pip install coveralls yapf twine + shell: bash + + - name: Run tests + run: | + set -e + coverage run --source=ntfy setup.py test + if ! python --version 2>&1; then + yapf -r ntfy -d + [ "$(yapf -r ntfy -d | wc -l)" -eq 0 ] + fi + shell: bash + + - name: Upload coverage to Coveralls + run: coveralls + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} \ No newline at end of file From 5e9e06ba1b042c366c06acb8e9a17ee70450dd6c Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Sat, 8 Mar 2025 18:08:23 +0100 Subject: [PATCH 02/13] Run CI on current OS and Python versions --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 763d727..0e82474 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,8 +12,8 @@ jobs: test: strategy: matrix: - os: [macos-13, ubuntu-18.04] - python-version: ['2.7', '3.4', '3.5', '3.6', 'pypy'] + os: [macos-latest, ubuntu-latest] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy-3.10"] runs-on: ${{ matrix.os }} From 43e704b68db4b1dcca1251edc984c31efa3b9af1 Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Sun, 9 Mar 2025 11:40:57 +0100 Subject: [PATCH 03/13] Use pytest instead of deprecated setuptools mechanism for test suite execution [`setup.py test` has been deprecated for a while now](https://setuptools.pypa.io/en/latest/deprecated/commands.html#test-build-package-and-run-a-unittest-suite) and `test_dependencies` actually seems to fail on modern Python and setuptools versions. Thus: - Install additional test dependencies from test-requirements.txt - Switch from setup.py test to pytest for running tests - Update README with new testing commands --- .github/workflows/ci.yml | 6 ++++-- README.md | 10 +++++++++- setup.py | 4 ---- test-requirements.txt | 5 +++++ 4 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 test-requirements.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e82474..0a8233e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,15 +28,17 @@ jobs: - name: Install dependencies run: | + set -e python -m pip install --upgrade pip setuptools pip install .[xmpp] - pip install coveralls yapf twine + pip install -r test-requirements.txt + pip install coveralls yapf twine pytest-cov shell: bash - name: Run tests run: | set -e - coverage run --source=ntfy setup.py test + pytest --cov=ntfy tests if ! python --version 2>&1; then yapf -r ntfy -d [ "$(yapf -r ntfy -d | wc -l)" -eq 0 ] diff --git a/README.md b/README.md index 709d8b5..05ce1e2 100644 --- a/README.md +++ b/README.md @@ -490,8 +490,16 @@ title: Customized Title ## Testing +Additional requirements required for the test suite are defined in `test-requirements.txt`. + +```shell +pip install -r test-requirements.txt +``` + +To run the test suite, run the following command: + ``` shell -python setup.py test +pytest test ``` ## Contributors diff --git a/setup.py b/setup.py index eac4538..b801e41 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,6 @@ 'rocketchat':['rocketchat-API'], 'matrix':['matrix_client'], } -test_deps = ['mock', 'sleekxmpp', 'emoji', 'psutil'] long_description = "See the repo readme for mor information" @@ -58,9 +57,6 @@ extras_require=extra_deps, - tests_require=test_deps, - test_suite='tests', - entry_points={ 'console_scripts': [ 'ntfy = ntfy.cli:main', diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..f06338c --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,5 @@ +mock +sleekxmpp +emoji +psutil +pytest \ No newline at end of file From 249040bd44a747cee3a3323ee813425268d78b40 Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Sun, 9 Mar 2025 11:52:23 +0100 Subject: [PATCH 04/13] Stop testing xmpp support for now The used sleekxmpp library is no longer maintained and does not work on any non-EOL version of Python. We might want to switch to using https://codeberg.org/poezio/slixmpp in the future. --- .github/workflows/ci.yml | 4 ++-- test-requirements.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a8233e..3becfe4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: run: | set -e python -m pip install --upgrade pip setuptools - pip install .[xmpp] + pip install . pip install -r test-requirements.txt pip install coveralls yapf twine pytest-cov shell: bash @@ -38,7 +38,7 @@ jobs: - name: Run tests run: | set -e - pytest --cov=ntfy tests + pytest --cov=ntfy --ignore 'tests/test_xmpp.py' -k 'not test_xmpp' tests if ! python --version 2>&1; then yapf -r ntfy -d [ "$(yapf -r ntfy -d | wc -l)" -eq 0 ] diff --git a/test-requirements.txt b/test-requirements.txt index f06338c..d7c2c48 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,5 +1,5 @@ mock -sleekxmpp +#sleekxmpp Not supported on any non-EOL Python version emoji psutil pytest \ No newline at end of file From 005b7f967a03cc7bd454fa2108cd5f77e2190108 Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Sun, 9 Mar 2025 11:53:00 +0100 Subject: [PATCH 05/13] Avoid default configuration being tainted --- ntfy/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ntfy/config.py b/ntfy/config.py index 1d2fe39..1923749 100644 --- a/ntfy/config.py +++ b/ntfy/config.py @@ -34,7 +34,7 @@ def load_config(config_path=DEFAULT_CONFIG): except IOError as e: if e.errno == errno.ENOENT and config_path == DEFAULT_CONFIG: logger.info('{} not found'.format(config_path)) - config = default_configuration + config = default_configuration.copy() else: logger.error( 'Failed to open {}'.format(config_path), exc_info=True) From 84a1a66bf8841b81b26e1362c76010a303967d56 Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Sun, 9 Mar 2025 12:01:49 +0100 Subject: [PATCH 06/13] Fix Coveralls We need to account for parallel execution of jobs. --- .github/workflows/ci.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3becfe4..c16a1d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: python -m pip install --upgrade pip setuptools pip install . pip install -r test-requirements.txt - pip install coveralls yapf twine pytest-cov + pip install yapf twine pytest-cov shell: bash - name: Run tests @@ -46,6 +46,17 @@ jobs: shell: bash - name: Upload coverage to Coveralls - run: coveralls - env: - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} \ No newline at end of file + uses: coverallsapp/github-action@v2 + with: + flag-name: run-${{ join(matrix.*, '-') }} + parallel: true + + finish: + needs: test + if: ${{ always() }} + runs-on: ubuntu-latest + steps: + - name: Signal test completion to coverage + uses: coverallsapp/github-action@v2 + with: + parallel-finished: true \ No newline at end of file From 79600d93e6ea9d4602f69dd03e5a2c988617ff79 Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Sun, 9 Mar 2025 12:11:11 +0100 Subject: [PATCH 07/13] Move formatting check into a separate CI step It does not make sense to check formatting separately for each Platform --- .github/workflows/ci.yml | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c16a1d5..72144b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,30 @@ on: - master jobs: + format: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.x + + - name: Install dependencies + run: | + pip install yapf + shell: bash + + - name: Format code with yapf + run: | + set -e + if ! python --version 2>&1; then + yapf -r ntfy -d + [ "$(yapf -r ntfy -d | wc -l)" -eq 0 ] + fi + shell: bash test: strategy: matrix: @@ -32,17 +56,13 @@ jobs: python -m pip install --upgrade pip setuptools pip install . pip install -r test-requirements.txt - pip install yapf twine pytest-cov + pip install twine pytest-cov shell: bash - name: Run tests run: | set -e pytest --cov=ntfy --ignore 'tests/test_xmpp.py' -k 'not test_xmpp' tests - if ! python --version 2>&1; then - yapf -r ntfy -d - [ "$(yapf -r ntfy -d | wc -l)" -eq 0 ] - fi shell: bash - name: Upload coverage to Coveralls From ea7a85c5351dbf2f6157c6a69e440a18426b222d Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Sun, 9 Mar 2025 13:05:33 +0100 Subject: [PATCH 08/13] Skip the PyPy test on macOS The dependencies don't install properly in this case --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72144b2..7f974e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,9 @@ jobs: matrix: os: [macos-latest, ubuntu-latest] python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy-3.10"] - + exclude: + - os: macos-latest + python-version: pypy-3.10 runs-on: ${{ matrix.os }} steps: From c06f53c8d619510219aa42e17487e4beea9cd28a Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Sun, 9 Mar 2025 13:13:05 +0100 Subject: [PATCH 09/13] Enable caching of dependencies for CI --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f974e8..7b75f3e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,10 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + cache: 'pip' + cache-dependency-path: | + setup.py + test-requirements.txt - name: Install dependencies run: | From e9a77e6978722875091d0da2737c91cc15ec1622 Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Sun, 9 Mar 2025 13:30:30 +0100 Subject: [PATCH 10/13] Bring back Windows CI --- .github/workflows/ci.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b75f3e..debdd94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: test: strategy: matrix: - os: [macos-latest, ubuntu-latest] + os: [macos-latest, ubuntu-latest, windows-latest] python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy-3.10"] exclude: - os: macos-latest @@ -58,18 +58,14 @@ jobs: - name: Install dependencies run: | - set -e python -m pip install --upgrade pip setuptools pip install . pip install -r test-requirements.txt pip install twine pytest-cov - shell: bash - name: Run tests run: | - set -e pytest --cov=ntfy --ignore 'tests/test_xmpp.py' -k 'not test_xmpp' tests - shell: bash - name: Upload coverage to Coveralls uses: coverallsapp/github-action@v2 From af10b41f0ed87b8a7aabfee5d0ab98e6689e0f4b Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Sun, 9 Mar 2025 13:41:48 +0100 Subject: [PATCH 11/13] Skip the PyPy test on Windows --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index debdd94..909b221 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,8 @@ jobs: exclude: - os: macos-latest python-version: pypy-3.10 + - os: windows-latest + python-version: pypy-3.10 runs-on: ${{ matrix.os }} steps: From 09ded7649c6cb067dd93aed53303dddc4cb1d2f6 Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Sun, 9 Mar 2025 14:41:43 +0100 Subject: [PATCH 12/13] Remove unnessecary Python availability check in formatting check. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 909b221..85bb06d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,10 +28,8 @@ jobs: - name: Format code with yapf run: | set -e - if ! python --version 2>&1; then - yapf -r ntfy -d - [ "$(yapf -r ntfy -d | wc -l)" -eq 0 ] - fi + yapf -r ntfy -d + [ "$(yapf -r ntfy -d | wc -l)" -eq 0 ] shell: bash test: strategy: From 3ee341533186ce7b9df3fed2436470e87d76f7dd Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Sun, 9 Mar 2025 14:55:45 +0100 Subject: [PATCH 13/13] Fix formatting It seems the YAPF formatting check was broken for a while and non-conformant formattings snuck in. This reformats the codebase with YAPF. --- ntfy/__init__.py | 12 +-- ntfy/backends/default.py | 1 + ntfy/backends/linux.py | 8 +- ntfy/backends/matrix.py | 10 ++- ntfy/backends/notifico.py | 12 +-- ntfy/backends/ntfy_sh.py | 8 +- ntfy/backends/prowl.py | 14 ++-- ntfy/backends/pushbullet.py | 5 +- ntfy/backends/pushover.py | 11 ++- ntfy/backends/slack_webhook.py | 28 +++---- ntfy/backends/telegram.py | 3 +- ntfy/backends/win32.py | 13 ++-- ntfy/cli.py | 134 ++++++++++++++++----------------- ntfy/config.py | 4 +- ntfy/terminal.py | 7 +- 15 files changed, 143 insertions(+), 127 deletions(-) diff --git a/ntfy/__init__.py b/ntfy/__init__.py index a1043eb..b1e113a 100644 --- a/ntfy/__init__.py +++ b/ntfy/__init__.py @@ -50,11 +50,10 @@ def notify(message, title, config=None, **kwargs): continue try: - notify_ret = notifier.notify( - message=message, - title=title, - retcode=retcode, - **backend_config) + notify_ret = notifier.notify(message=message, + title=title, + retcode=retcode, + **backend_config) if notify_ret: ret = notify_ret except (SystemExit, KeyboardInterrupt): @@ -67,7 +66,8 @@ def notify(message, title, config=None, **kwargs): args, _, _, defaults, *_ = getfullargspec(notifier.notify) possible_args = set(args) - required_args = set(args) if defaults is None else set(args[:-len(defaults)]) + required_args = set(args) if defaults is None else set( + args[:-len(defaults)]) required_args -= set(['title', 'message', 'retcode']) unknown_args = set(backend_config) - possible_args missing_args = required_args - set(backend_config) diff --git a/ntfy/backends/default.py b/ntfy/backends/default.py index f908240..1e2f361 100644 --- a/ntfy/backends/default.py +++ b/ntfy/backends/default.py @@ -3,6 +3,7 @@ class DefaultNotifierError(Exception): + def __init__(self, exception, module): self.exception = exception self.module = module diff --git a/ntfy/backends/linux.py b/ntfy/backends/linux.py index 7c1ff62..0f7e677 100644 --- a/ntfy/backends/linux.py +++ b/ntfy/backends/linux.py @@ -32,8 +32,8 @@ def notify(title, bus = dbus.SessionBus() dbus_obj = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications') - dbus_iface = dbus.Interface( - dbus_obj, dbus_interface='org.freedesktop.Notifications') + dbus_iface = dbus.Interface(dbus_obj, + dbus_interface='org.freedesktop.Notifications') hints = {} @@ -61,5 +61,5 @@ def notify(title, hints.update({'sound-file': soundfile}) message = message.replace('&', '&') - dbus_iface.Notify('ntfy', 0, "" or icon, title, - message, [], hints, int(timeout)) + dbus_iface.Notify('ntfy', 0, "" or icon, title, message, [], hints, + int(timeout)) diff --git a/ntfy/backends/matrix.py b/ntfy/backends/matrix.py index b17a047..c71d0cc 100644 --- a/ntfy/backends/matrix.py +++ b/ntfy/backends/matrix.py @@ -1,7 +1,15 @@ from __future__ import unicode_literals from matrix_client.client import MatrixClient -def notify(title, message, url, roomId, userId=None, token=None, password=None, retcode=None): + +def notify(title, + message, + url, + roomId, + userId=None, + token=None, + password=None, + retcode=None): client = MatrixClient(url) if password is not None: diff --git a/ntfy/backends/notifico.py b/ntfy/backends/notifico.py index 73cc6f9..599e375 100644 --- a/ntfy/backends/notifico.py +++ b/ntfy/backends/notifico.py @@ -15,10 +15,10 @@ def notify(title, message, retcode=None, webhook=None): logger.error('please set webhook variable under ' 'notifico backend of the config file') return - response = requests.get( - webhook, - params={ - 'payload': '{title}\n{message}'.format( - title=title, message=message) - }) + response = requests.get(webhook, + params={ + 'payload': + '{title}\n{message}'.format(title=title, + message=message) + }) response.raise_for_status() diff --git a/ntfy/backends/ntfy_sh.py b/ntfy/backends/ntfy_sh.py index 83bbd6a..2472cfa 100644 --- a/ntfy/backends/ntfy_sh.py +++ b/ntfy/backends/ntfy_sh.py @@ -1,7 +1,13 @@ import requests -def notify(title, message, topic, host='https://ntfy.sh', user=None, password=None, **kwargs): +def notify(title, + message, + topic, + host='https://ntfy.sh', + user=None, + password=None, + **kwargs): auth_kwarg = {'auth': (user, password)} if user and password else {} requests.post( diff --git a/ntfy/backends/prowl.py b/ntfy/backends/prowl.py index 5fcffc6..aa6ba94 100644 --- a/ntfy/backends/prowl.py +++ b/ntfy/backends/prowl.py @@ -33,8 +33,9 @@ def notify(title, if MIN_PRIORITY <= priority <= MAX_PRIORITY: data['priority'] = priority else: - raise ValueError('priority must be an integer from {:d} to {:d}' - .format(MIN_PRIORITY, MAX_PRIORITY)) + raise ValueError( + 'priority must be an integer from {:d} to {:d}'.format( + MIN_PRIORITY, MAX_PRIORITY)) if url is not None: data['url'] = url @@ -42,9 +43,10 @@ def notify(title, if provider_key is not None: data['providerkey'] = provider_key - resp = requests.post( - API_URL, data=data, headers={ - 'User-Agent': USER_AGENT, - }) + resp = requests.post(API_URL, + data=data, + headers={ + 'User-Agent': USER_AGENT, + }) resp.raise_for_status() diff --git a/ntfy/backends/pushbullet.py b/ntfy/backends/pushbullet.py index 4fbe4d7..9576637 100644 --- a/ntfy/backends/pushbullet.py +++ b/ntfy/backends/pushbullet.py @@ -33,7 +33,8 @@ def notify(title, headers = {'Access-Token': access_token, 'User-Agent': USER_AGENT} - resp = requests.post( - 'https://api.pushbullet.com/v2/pushes', data=data, headers=headers) + resp = requests.post('https://api.pushbullet.com/v2/pushes', + data=data, + headers=headers) resp.raise_for_status() diff --git a/ntfy/backends/pushover.py b/ntfy/backends/pushover.py index 6acdf99..7e283c0 100644 --- a/ntfy/backends/pushover.py +++ b/ntfy/backends/pushover.py @@ -106,12 +106,11 @@ def notify(title, else: raise ValueError('priority must be an integer from -2 to 2') - resp = requests.post( - 'https://api.pushover.net/1/messages.json', - data=data, - headers={ - 'User-Agent': USER_AGENT, - }) + resp = requests.post('https://api.pushover.net/1/messages.json', + data=data, + headers={ + 'User-Agent': USER_AGENT, + }) if resp.status_code == 429: print("ntfy's default api_token has reached pushover's rate limit") diff --git a/ntfy/backends/slack_webhook.py b/ntfy/backends/slack_webhook.py index d3bc17e..6f3112d 100644 --- a/ntfy/backends/slack_webhook.py +++ b/ntfy/backends/slack_webhook.py @@ -6,18 +6,20 @@ def notify(title, message, url, user, **kwargs): requests.post( url, json={ - "username": "ntfy", - "icon_url": "https://ntfy.readthedocs.io/en/latest/_static/logo.png", - "text": "{0}\n{1}".format(title, message), - "channel": user, - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*{0}* {1}".format(title, message), - }, - } - ], + "username": + "ntfy", + "icon_url": + "https://ntfy.readthedocs.io/en/latest/_static/logo.png", + "text": + "{0}\n{1}".format(title, message), + "channel": + user, + "blocks": [{ + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*{0}* {1}".format(title, message), + }, + }], }, ) diff --git a/ntfy/backends/telegram.py b/ntfy/backends/telegram.py index 2532679..dde2c16 100644 --- a/ntfy/backends/telegram.py +++ b/ntfy/backends/telegram.py @@ -6,6 +6,7 @@ config_dir = user_config_dir('ntfy', 'dschep') config_file = path.join(config_dir, 'telegram.ini') + def notify(title, message, retcode=None): """Sends message over Telegram using telegram-send, title is ignored.""" if not path.exists(config_file): @@ -13,4 +14,4 @@ def notify(title, message, retcode=None): makedirs(config_dir) print("Follow the instructions to configure the Telegram backend.\n") asyncio.run(configure(config_file)) - asyncio.run(send(messages=[message], conf=config_file)) \ No newline at end of file + asyncio.run(send(messages=[message], conf=config_file)) diff --git a/ntfy/backends/win32.py b/ntfy/backends/win32.py index 645560e..a36e4a6 100644 --- a/ntfy/backends/win32.py +++ b/ntfy/backends/win32.py @@ -16,6 +16,7 @@ def notify(title, message, icon=icon.ico, retcode=None): import win32gui class WindowsBalloonTip: + def __init__(self, title, msg): message_map = { win32con.WM_DESTROY: self.OnDestroy, @@ -28,15 +29,17 @@ def __init__(self, title, msg): classAtom = win32gui.RegisterClass(wc) # Create the Window. style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU - self.hwnd = win32gui.CreateWindow( - classAtom, "Taskbar", style, 0, 0, win32con.CW_USEDEFAULT, - win32con.CW_USEDEFAULT, 0, 0, hinst, None) + self.hwnd = win32gui.CreateWindow(classAtom, "Taskbar", style, 0, + 0, win32con.CW_USEDEFAULT, + win32con.CW_USEDEFAULT, 0, 0, + hinst, None) win32gui.UpdateWindow(self.hwnd) iconPathName = os.path.abspath(icon) icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE try: - hicon = win32gui.LoadImage( - hinst, iconPathName, win32con.IMAGE_ICON, 0, 0, icon_flags) + hicon = win32gui.LoadImage(hinst, iconPathName, + win32con.IMAGE_ICON, 0, 0, + icon_flags) except: hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION) flags = win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP diff --git a/ntfy/cli.py b/ntfy/cli.py index 41145fd..24e9349 100644 --- a/ntfy/cli.py +++ b/ntfy/cli.py @@ -86,18 +86,16 @@ def _result_message(command, return_code, stdout, stderr, duration, emoji): else: command = '"{command}"'.format(command=' '.join(command)) if stdout is not None or stderr is not None: - all_output = ':\n{}{}'.format(stdout - if stdout is not None else '', stderr - if stderr is not None else '') + all_output = ':\n{}{}'.format(stdout if stdout is not None else '', + stderr if stderr is not None else '') else: all_output = '' template = '{prefix}{command} {result} in {:d}:{:02d} minutes{output}' - return template.format( - prefix=prefix, - command=command, - result=result, - output=all_output, - *map(int, divmod(duration, 60))) + return template.format(prefix=prefix, + command=command, + result=result, + output=all_output, + *map(int, divmod(duration, 60))) def watch_pid(args): @@ -163,45 +161,42 @@ def __call__(self, parser, args, values, option_string=None): '-c', '--config', help='config file to use (default: {})'.format(DEFAULT_CONFIG)) -parser.add_argument( - '-b', - '--backend', - action=BackendOptionAction, - help='override backend specified in config') -parser.add_argument( - '-o', - '--option', - nargs=2, - default=None, - action=BackendOptionAction, - metavar=('key', 'value'), - help='backend specific options') -parser.add_argument( - '-l', - '--log-level', - action='store', - default='WARNING', - choices=['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'], - help=('Specify the how verbose CLI output is ' - '(default: WARNING)')) -parser.add_argument( - '-v', - '--verbose', - dest='log_level', - action='store_const', - const='DEBUG', - help='a shortcut for --log-level=DEBUG') -parser.add_argument( - '-q', - '--quiet', - dest='log_level', - action='store_const', - const='CRITICAL', - help='a shortcut for --log-level=CRITICAL') +parser.add_argument('-b', + '--backend', + action=BackendOptionAction, + help='override backend specified in config') +parser.add_argument('-o', + '--option', + nargs=2, + default=None, + action=BackendOptionAction, + metavar=('key', 'value'), + help='backend specific options') +parser.add_argument('-l', + '--log-level', + action='store', + default='WARNING', + choices=['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'], + help=('Specify the how verbose CLI output is ' + '(default: WARNING)')) +parser.add_argument('-v', + '--verbose', + dest='log_level', + action='store_const', + const='DEBUG', + help='a shortcut for --log-level=DEBUG') +parser.add_argument('-q', + '--quiet', + dest='log_level', + action='store_const', + const='CRITICAL', + help='a shortcut for --log-level=CRITICAL') parser.add_argument('--version', action='version', version=__version__) if emojize is not None: - parser.add_argument( - '-E', '--no-emoji', action='store_true', help='Disable emoji support') + parser.add_argument('-E', + '--no-emoji', + action='store_true', + help='Disable emoji support') parser.add_argument( '-t', @@ -222,21 +217,21 @@ def default_sender(args): done_parser = subparsers.add_parser( 'done', help='run a command and send a notification when done') -done_parser.add_argument( - 'command', nargs=argparse.REMAINDER, help='command to run') +done_parser.add_argument('command', + nargs=argparse.REMAINDER, + help='command to run') done_parser.add_argument( '-L', '--longer-than', type=int, metavar='N', help="Only notify if the command runs longer than N seconds") -done_parser.add_argument( - '-b', - '--background-only', - action='store_true', - default=False, - dest='unfocused_only', - help="Only notify if shell isn't in the foreground") +done_parser.add_argument('-b', + '--background-only', + action='store_true', + default=False, + dest='unfocused_only', + help="Only notify if shell isn't in the foreground") done_parser.add_argument( '--formatter', metavar=('command', 'retcode', 'duration'), @@ -249,16 +244,14 @@ def default_sender(args): '--pid', type=int, help="Watch a PID instead of running a new command") -done_parser.add_argument( - '-o', - '--stdout', - action='store_true', - help="Capture and send standard output") -done_parser.add_argument( - '-e', - '--stderr', - action='store_true', - help="Capture and send standard error") +done_parser.add_argument('-o', + '--stdout', + action='store_true', + help="Capture and send standard output") +done_parser.add_argument('-e', + '--stderr', + action='store_true', + help="Capture and send standard error") done_parser.add_argument( '-H', '--hide-command', @@ -359,12 +352,11 @@ def main(cli_args=None): return 0 if emojize is not None and not args.no_emoji: message = emojize(message, language='alias') - return notify( - message, - args.title, - config, - retcode=retcode, - **dict(args.option.get(None, []))) + return notify(message, + args.title, + config, + retcode=retcode, + **dict(args.option.get(None, []))) else: parser.print_help() diff --git a/ntfy/config.py b/ntfy/config.py index 1923749..fc759fb 100644 --- a/ntfy/config.py +++ b/ntfy/config.py @@ -36,8 +36,8 @@ def load_config(config_path=DEFAULT_CONFIG): logger.info('{} not found'.format(config_path)) config = default_configuration.copy() else: - logger.error( - 'Failed to open {}'.format(config_path), exc_info=True) + logger.error('Failed to open {}'.format(config_path), + exc_info=True) exit(1) except ValueError as e: logger.error('Failed to load {}'.format(config_path), exc_info=True) diff --git a/ntfy/terminal.py b/ntfy/terminal.py index 17e323c..24e7b49 100644 --- a/ntfy/terminal.py +++ b/ntfy/terminal.py @@ -7,7 +7,8 @@ def linux_window_is_focused(): xprop_cmd = shlex.split('xprop -root _NET_ACTIVE_WINDOW') try: - xprop_window_id = int(check_output(xprop_cmd, stdout=PIPE, stderr=PIPE).split()[-1], 16) + xprop_window_id = int( + check_output(xprop_cmd, stdout=PIPE, stderr=PIPE).split()[-1], 16) except CalledProcessError: return False except ValueError: @@ -24,8 +25,8 @@ def linux_window_is_focused(): def osascript_tell(app, script): p = Popen(['osascript'], stdin=PIPE, stdout=PIPE) stdout, stderr = p.communicate( - ('tell application "{}"\n{}\nend tell'.format(app, script) - .encode('utf-8'))) + ('tell application "{}"\n{}\nend tell'.format(app, + script).encode('utf-8'))) return stdout.decode('utf-8').rstrip('\n')