From 2cbf7d160ece33c797de30688dd42bac35961149 Mon Sep 17 00:00:00 2001 From: Zack Mayoh Date: Fri, 11 Oct 2024 18:11:50 -0600 Subject: [PATCH 1/4] Update README and Copyrights - Update references to package name in README - Update License and Copyright --- LICENSE.txt | 2 +- README.md | 11 ++++------- aiosdnotify/__init__.py | 8 ++++++++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index efab5e1..52392b6 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 Brett Bethke +Copyright 2024 Calian Ltd. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 7bda180..7836f56 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # systemd Service Notification -This is Garrett's Asyncio version of the systemd sd_notify package written by bb4242 (Brett Bethke), +This is a Calian fork of Garrett's Asyncio version (https://github.com/seatsnob/sdnotify-asyncio) +of the systemd sd_notify package written by bb4242 (Brett Bethke), available at https://github.com/bb4242/sdnotify This is a pure Python implementation of the @@ -9,11 +10,7 @@ This is a pure Python implementation of the protocol. This protocol can be used to inform `systemd` about service start-up completion, watchdog events, and other service status changes. Thus, this package can be used to write system services in Python that play nicely with -`systemd`. `sdnotify` is compatible with both Python 2 and Python 3. - -Just so we're clear, I figured out _why_ nobody made this. Sdnotify uses UDP under the hood. -There isn't an asyncio implementation of UDP. So I mostly just wound up writing shitty wrappers -for asyncio around multithreading. +`systemd`. `async-sdnotify` is compatible with Python 3. Normally the `SystemdNotifier.notify` method silently ignores exceptions (for example, if the systemd notification socket is not available) to allow applications to @@ -23,7 +20,7 @@ aid in debugging. # Installation -`pip install sdnotify-asyncio` +`pip install async-sdnotify` # Example Usage diff --git a/aiosdnotify/__init__.py b/aiosdnotify/__init__.py index 7b16f8a..07ad71b 100644 --- a/aiosdnotify/__init__.py +++ b/aiosdnotify/__init__.py @@ -1,3 +1,11 @@ +# +# Copyright 2024 Calian Ltd. All rights reserved. +# +# Original authors: +# - Copyright 2023 Seat Snob (garrett@seatsnob.com) +# - Copyright 2016 Brett Bethke (bbethke@gmail.com) +# + import socket import asyncio import os From acb032e8e34f78ddae3d1d40500cbacf351a13c2 Mon Sep 17 00:00:00 2001 From: Zack Mayoh Date: Fri, 11 Oct 2024 18:14:05 -0600 Subject: [PATCH 2/4] Add poetry project configuration Add configuration to use Poetry instead of setuptools --- poetry.lock | 184 +++++++++++++++++++++++++++++++++++++++++++++++++ poetry.toml | 2 + pyproject.toml | 36 ++++++++++ 3 files changed, 222 insertions(+) create mode 100644 poetry.lock create mode 100644 poetry.toml create mode 100644 pyproject.toml diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..c6f7b83 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,184 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.6.2" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "coverage-7.6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c9df1950fb92d49970cce38100d7e7293c84ed3606eaa16ea0b6bc27175bb667"}, + {file = "coverage-7.6.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:24500f4b0e03aab60ce575c85365beab64b44d4db837021e08339f61d1fbfe52"}, + {file = "coverage-7.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a663b180b6669c400b4630a24cc776f23a992d38ce7ae72ede2a397ce6b0f170"}, + {file = "coverage-7.6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfde025e2793a22efe8c21f807d276bd1d6a4bcc5ba6f19dbdfc4e7a12160909"}, + {file = "coverage-7.6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:087932079c065d7b8ebadd3a0160656c55954144af6439886c8bcf78bbbcde7f"}, + {file = "coverage-7.6.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9c6b0c1cafd96213a0327cf680acb39f70e452caf8e9a25aeb05316db9c07f89"}, + {file = "coverage-7.6.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6e85830eed5b5263ffa0c62428e43cb844296f3b4461f09e4bdb0d44ec190bc2"}, + {file = "coverage-7.6.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62ab4231c01e156ece1b3a187c87173f31cbeee83a5e1f6dff17f288dca93345"}, + {file = "coverage-7.6.2-cp310-cp310-win32.whl", hash = "sha256:7b80fbb0da3aebde102a37ef0138aeedff45997e22f8962e5f16ae1742852676"}, + {file = "coverage-7.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:d20c3d1f31f14d6962a4e2f549c21d31e670b90f777ef4171be540fb7fb70f02"}, + {file = "coverage-7.6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bb21bac7783c1bf6f4bbe68b1e0ff0d20e7e7732cfb7995bc8d96e23aa90fc7b"}, + {file = "coverage-7.6.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7b2e437fbd8fae5bc7716b9c7ff97aecc95f0b4d56e4ca08b3c8d8adcaadb84"}, + {file = "coverage-7.6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:536f77f2bf5797983652d1d55f1a7272a29afcc89e3ae51caa99b2db4e89d658"}, + {file = "coverage-7.6.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f361296ca7054f0936b02525646b2731b32c8074ba6defab524b79b2b7eeac72"}, + {file = "coverage-7.6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7926d8d034e06b479797c199747dd774d5e86179f2ce44294423327a88d66ca7"}, + {file = "coverage-7.6.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0bbae11c138585c89fb4e991faefb174a80112e1a7557d507aaa07675c62e66b"}, + {file = "coverage-7.6.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fcad7d5d2bbfeae1026b395036a8aa5abf67e8038ae7e6a25c7d0f88b10a8e6a"}, + {file = "coverage-7.6.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f01e53575f27097d75d42de33b1b289c74b16891ce576d767ad8c48d17aeb5e0"}, + {file = "coverage-7.6.2-cp311-cp311-win32.whl", hash = "sha256:7781f4f70c9b0b39e1b129b10c7d43a4e0c91f90c60435e6da8288efc2b73438"}, + {file = "coverage-7.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:9bcd51eeca35a80e76dc5794a9dd7cb04b97f0e8af620d54711793bfc1fbba4b"}, + {file = "coverage-7.6.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ebc94fadbd4a3f4215993326a6a00e47d79889391f5659bf310f55fe5d9f581c"}, + {file = "coverage-7.6.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9681516288e3dcf0aa7c26231178cc0be6cac9705cac06709f2353c5b406cfea"}, + {file = "coverage-7.6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d9c5d13927d77af4fbe453953810db766f75401e764727e73a6ee4f82527b3e"}, + {file = "coverage-7.6.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b92f9ca04b3e719d69b02dc4a69debb795af84cb7afd09c5eb5d54b4a1ae2191"}, + {file = "coverage-7.6.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ff2ef83d6d0b527b5c9dad73819b24a2f76fdddcfd6c4e7a4d7e73ecb0656b4"}, + {file = "coverage-7.6.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:47ccb6e99a3031ffbbd6e7cc041e70770b4fe405370c66a54dbf26a500ded80b"}, + {file = "coverage-7.6.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a867d26f06bcd047ef716175b2696b315cb7571ccb951006d61ca80bbc356e9e"}, + {file = "coverage-7.6.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cdfcf2e914e2ba653101157458afd0ad92a16731eeba9a611b5cbb3e7124e74b"}, + {file = "coverage-7.6.2-cp312-cp312-win32.whl", hash = "sha256:f9035695dadfb397bee9eeaf1dc7fbeda483bf7664a7397a629846800ce6e276"}, + {file = "coverage-7.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:5ed69befa9a9fc796fe015a7040c9398722d6b97df73a6b608e9e275fa0932b0"}, + {file = "coverage-7.6.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eea60c79d36a8f39475b1af887663bc3ae4f31289cd216f514ce18d5938df40"}, + {file = "coverage-7.6.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa68a6cdbe1bc6793a9dbfc38302c11599bbe1837392ae9b1d238b9ef3dafcf1"}, + {file = "coverage-7.6.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ec528ae69f0a139690fad6deac8a7d33629fa61ccce693fdd07ddf7e9931fba"}, + {file = "coverage-7.6.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed5ac02126f74d190fa2cc14a9eb2a5d9837d5863920fa472b02eb1595cdc925"}, + {file = "coverage-7.6.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21c0ea0d4db8a36b275cb6fb2437a3715697a4ba3cb7b918d3525cc75f726304"}, + {file = "coverage-7.6.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:35a51598f29b2a19e26d0908bd196f771a9b1c5d9a07bf20be0adf28f1ad4f77"}, + {file = "coverage-7.6.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c9192925acc33e146864b8cf037e2ed32a91fdf7644ae875f5d46cd2ef086a5f"}, + {file = "coverage-7.6.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf4eeecc9e10f5403ec06138978235af79c9a79af494eb6b1d60a50b49ed2869"}, + {file = "coverage-7.6.2-cp313-cp313-win32.whl", hash = "sha256:e4ee15b267d2dad3e8759ca441ad450c334f3733304c55210c2a44516e8d5530"}, + {file = "coverage-7.6.2-cp313-cp313-win_amd64.whl", hash = "sha256:c71965d1ced48bf97aab79fad56df82c566b4c498ffc09c2094605727c4b7e36"}, + {file = "coverage-7.6.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7571e8bbecc6ac066256f9de40365ff833553e2e0c0c004f4482facb131820ef"}, + {file = "coverage-7.6.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:078a87519057dacb5d77e333f740708ec2a8f768655f1db07f8dfd28d7a005f0"}, + {file = "coverage-7.6.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5e92e3e84a8718d2de36cd8387459cba9a4508337b8c5f450ce42b87a9e760"}, + {file = "coverage-7.6.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebabdf1c76593a09ee18c1a06cd3022919861365219ea3aca0247ededf6facd6"}, + {file = "coverage-7.6.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12179eb0575b8900912711688e45474f04ab3934aaa7b624dea7b3c511ecc90f"}, + {file = "coverage-7.6.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:39d3b964abfe1519b9d313ab28abf1d02faea26cd14b27f5283849bf59479ff5"}, + {file = "coverage-7.6.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:84c4315577f7cd511d6250ffd0f695c825efe729f4205c0340f7004eda51191f"}, + {file = "coverage-7.6.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ff797320dcbff57caa6b2301c3913784a010e13b1f6cf4ab3f563f3c5e7919db"}, + {file = "coverage-7.6.2-cp313-cp313t-win32.whl", hash = "sha256:2b636a301e53964550e2f3094484fa5a96e699db318d65398cfba438c5c92171"}, + {file = "coverage-7.6.2-cp313-cp313t-win_amd64.whl", hash = "sha256:d03a060ac1a08e10589c27d509bbdb35b65f2d7f3f8d81cf2fa199877c7bc58a"}, + {file = "coverage-7.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c37faddc8acd826cfc5e2392531aba734b229741d3daec7f4c777a8f0d4993e5"}, + {file = "coverage-7.6.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab31fdd643f162c467cfe6a86e9cb5f1965b632e5e65c072d90854ff486d02cf"}, + {file = "coverage-7.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97df87e1a20deb75ac7d920c812e9326096aa00a9a4b6d07679b4f1f14b06c90"}, + {file = "coverage-7.6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:343056c5e0737487a5291f5691f4dfeb25b3e3c8699b4d36b92bb0e586219d14"}, + {file = "coverage-7.6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4ef1c56b47b6b9024b939d503ab487231df1f722065a48f4fc61832130b90e"}, + {file = "coverage-7.6.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fca4a92c8a7a73dee6946471bce6d1443d94155694b893b79e19ca2a540d86e"}, + {file = "coverage-7.6.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69f251804e052fc46d29d0e7348cdc5fcbfc4861dc4a1ebedef7e78d241ad39e"}, + {file = "coverage-7.6.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e8ea055b3ea046c0f66217af65bc193bbbeca1c8661dc5fd42698db5795d2627"}, + {file = "coverage-7.6.2-cp39-cp39-win32.whl", hash = "sha256:6c2ba1e0c24d8fae8f2cf0aeb2fc0a2a7f69b6d20bd8d3749fd6b36ecef5edf0"}, + {file = "coverage-7.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:2186369a654a15628e9c1c9921409a6b3eda833e4b91f3ca2a7d9f77abb4987c"}, + {file = "coverage-7.6.2-pp39.pp310-none-any.whl", hash = "sha256:667952739daafe9616db19fbedbdb87917eee253ac4f31d70c7587f7ab531b4e"}, + {file = "coverage-7.6.2.tar.gz", hash = "sha256:a5f81e68aa62bc0cfca04f7b19eaa8f9c826b53fc82ab9e2121976dc74f131f3"}, +] + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "8.3.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.24.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, + {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, +] + +[package.dependencies] +pytest = ">=8.2,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "7dca67ee546c9582a336059dc8734cd09e388b02b9de26bd118ba102f70e46cd" diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 0000000..efa46ec --- /dev/null +++ b/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..35b2e78 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,36 @@ +[tool.poetry] +name = "async-sdnotify" +version = "0.1.0" +description = "A pure asynchronous Python implementation of systemd's service notification protocol (sd_notify)" +authors = [ + "Calian Ltd. ", + "Seat Snob ", + "Brett Bethke ", +] +license = "MIT" +readme = "README.md" +classifiers = [ + "Intended Audience :: Developers", + "Operating System :: POSIX :: Linux", + "Topic :: Software Development :: Libraries :: Python Modules", +] +packages = [ + {include = "aiosdnotify"}, +] + +[tool.poetry.dependencies] +python = "^3.11" + +[tool.poetry.group.dev.dependencies] +pytest = "^8.3.3" +pytest-asyncio = "^0.24.0" +pytest-cov = "^5.0.0" + +[tool.mypy] +mypy_path = [ + "$MYPY_CONFIG_FILE_DIR/aiosdnotify" +] + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" From c69109e40869573592ab54e36128bc7ecb7bcbe5 Mon Sep 17 00:00:00 2001 From: Zack Mayoh Date: Fri, 11 Oct 2024 18:16:40 -0600 Subject: [PATCH 3/4] Fix type annotations Add missing type annotations and fix mismatched type issues --- aiosdnotify/__init__.py | 45 +++++++++++++++++++++++++---------------- aiosdnotify/py.typed | 0 2 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 aiosdnotify/py.typed diff --git a/aiosdnotify/__init__.py b/aiosdnotify/__init__.py index 07ad71b..c4ff314 100644 --- a/aiosdnotify/__init__.py +++ b/aiosdnotify/__init__.py @@ -10,9 +10,11 @@ import asyncio import os import logging +from types import TracebackType +from typing import Any, Optional, Self, Type -__version__ = "1.0.0" +__version__ = "0.1.0" logger = logging.getLogger(__name__) @@ -21,7 +23,7 @@ class SystemdNotifier: """This class holds a connection to the systemd notification socket and can be used to send messages to systemd using its notify method.""" - def __init__(self, debug=False, warn_limit=3, watchdog=True): + def __init__(self, debug: bool=False, warn_limit: int=3, watchdog: bool=True) -> None: """Instantiate a new notifier object. This will initiate a connection to the systemd notification socket. @@ -44,29 +46,31 @@ def __init__(self, debug=False, warn_limit=3, watchdog=True): self._warnings = 0 self._transport: asyncio.DatagramTransport | None = None self._protocol: asyncio.DatagramProtocol | None = None - self._watchdog_task: asyncio.Task | None = None + self._watchdog_task: asyncio.Task[None] | None = None self._ready_event = asyncio.Event() - async def connect(self): + async def connect(self) -> None: try: - notify_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) addr = os.getenv('NOTIFY_SOCKET') + if addr is None: + raise EnvironmentError('NOTIFY_SOCKET not set') + notify_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) if addr[0] == '@': addr = '\0' + addr[1:] notify_socket.connect(addr) self._transport, self._protocol = await asyncio.get_running_loop().create_datagram_endpoint( asyncio.DatagramProtocol, sock=notify_socket) - except Exception as e: + except Exception: if self.debug: raise elif self.warn_limit: logger.warning('SystemdNotifier failed to connect', exc_info=True) - def disconnect(self): + def disconnect(self) -> None: if self._transport is not None and not self._transport.is_closing(): self._transport.close() - def _notify(self, state_str: str): + def _notify(self, state_str: str) -> None: """Send a notification to systemd. state is a string; see the man page of sd_notify (http://www.freedesktop.org/software/systemd/man/sd_notify.html) for a description of the allowable values. @@ -77,6 +81,8 @@ def _notify(self, state_str: str): cause this method to raise any exceptions generated to the caller, to aid in debugging.""" try: + if self._transport is None: + raise RuntimeError('SystemdNotifier is not connected') self._transport.sendto(state_str.encode()) self._warnings = 0 except Exception: @@ -86,35 +92,40 @@ def _notify(self, state_str: str): logger.warning('SystemdNotifier failed to notify', exc_info=True) self._warnings += 1 - def ready(self): + def ready(self) -> None: self._notify("READY=1") self._ready_event.set() - def status(self, status: str): + def status(self, status: str) -> None: self._notify(f"STATUS={status}") - def start_watchdog(self, interval: float | None = None): + def start_watchdog(self, interval: float | None = None) -> None: if interval is None: - interval_microseconds = float(os.getenv('WATCHDOG_USEC')) + interval_microseconds = os.getenv('WATCHDOG_USEC') if interval_microseconds is None: - raise EnvironmentError('Unable to determine watchdog interval from ENV, and none was specified') - interval = interval_microseconds / 1_000_000 / 2 # We try to notify at half the env specified interval + message = 'Unable to determine watchdog interval from ENV, and none was specified' + if self.debug: + raise EnvironmentError(message) + else: + logger.warning(message) + return + interval = float(interval_microseconds) / 1_000_000 / 2 # We try to notify at half the env specified interval self._watchdog_task = asyncio.create_task( self._watchdog(interval)) - async def _watchdog(self, interval: float): + async def _watchdog(self, interval: float) -> None: await self._ready_event.wait() while True: self._notify('WATCHDOG=1') await asyncio.sleep(interval) - async def __aenter__(self): + async def __aenter__(self) -> Self: await self.connect() if self.watchdog: self.start_watchdog() return self - async def __aexit__(self, exc_type, exc_val, exc_tb): + async def __aexit__(self, exc_type: Type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None) -> None: if ( self._watchdog_task and not self._watchdog_task.done() diff --git a/aiosdnotify/py.typed b/aiosdnotify/py.typed new file mode 100644 index 0000000..e69de29 From c3d01b72681fe1fdc5096e63ce893e08dc5c8f5f Mon Sep 17 00:00:00 2001 From: Zack Mayoh Date: Fri, 11 Oct 2024 18:17:39 -0600 Subject: [PATCH 4/4] Add unit tests for SystemdNotifier Gets SystemdNotifier to 100% test coverage. --- .gitignore | 4 +++ .vscode/settings.json | 15 +++++++++ tests/test_systemdnotifier.py | 57 +++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 tests/test_systemdnotifier.py diff --git a/.gitignore b/.gitignore index 7eae2ca..0927d44 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,7 @@ var/ *.egg-info/ .installed.cfg *.egg + +# Python +.coverage +coverage.xml diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..464cdf7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "python.analysis.autoImportCompletions": true, + "python.testing.pytestArgs": [ + "-v", + "--cov=aiosdnotify", + "--cov-report=xml", + "--cov-report=term", + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "mypy-type-checker.args": [ + "--strict" + ] +} diff --git a/tests/test_systemdnotifier.py b/tests/test_systemdnotifier.py new file mode 100644 index 0000000..98ac323 --- /dev/null +++ b/tests/test_systemdnotifier.py @@ -0,0 +1,57 @@ +import asyncio +from contextlib import nullcontext as does_not_raise +import os +import socket +from typing import Iterator +import pytest +from aiosdnotify import SystemdNotifier + + +@pytest.fixture +def watchdog_socket() -> Iterator[socket.socket]: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + sock.bind('\0' + 'watchdog') + os.environ['NOTIFY_SOCKET'] = '@watchdog' + os.environ['WATCHDOG_USEC'] = '1000000' + yield sock + sock.close() + del os.environ['NOTIFY_SOCKET'] + del os.environ['WATCHDOG_USEC'] + + +@pytest.mark.asyncio +async def test_no_exceptions_without_environment() -> None: + with does_not_raise(): + async with SystemdNotifier() as notifier: + notifier.ready() + notifier.status("Testing") + + +@pytest.mark.asyncio +async def test_connect_throws_exception_when_debug() -> None: + with pytest.raises(EnvironmentError): + await SystemdNotifier(debug=True).connect() + + +def test_status_throws_exception_when_debug() -> None: + with pytest.raises(RuntimeError): + SystemdNotifier(debug=True).status("Testing") + + +def test_start_watchdog_throws_exception_when_debug() -> None: + with pytest.raises(EnvironmentError): + SystemdNotifier(debug=True).start_watchdog() + + +@pytest.mark.asyncio +async def test_sends_notifications(watchdog_socket: socket.socket) -> None: + async with SystemdNotifier() as notifier: + notifier.ready() + ready = watchdog_socket.recv(1024) + assert ready == b"READY=1" + notifier.status("Testing") + status = watchdog_socket.recv(1024) + assert status == b"STATUS=Testing" + await asyncio.sleep(1) + data = watchdog_socket.recv(1024) + assert data == b"WATCHDOG=1"