From e1530dd9d3839d8b0a752d6337711e4242e0c89e Mon Sep 17 00:00:00 2001 From: stynoo Date: Wed, 13 Nov 2024 10:36:38 +0100 Subject: [PATCH 01/53] Use of pkg_resources is deprecated in favor of importlib.resources, importlib.metadata and their backports (importlib_resources, importlib_metadata). --- withings_sync/withings2.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/withings_sync/withings2.py b/withings_sync/withings2.py index 3c2987e..ead7d40 100644 --- a/withings_sync/withings2.py +++ b/withings_sync/withings2.py @@ -4,7 +4,7 @@ import json import os import time -import pkg_resources +import importlib_resources import requests log = logging.getLogger("withings") @@ -14,9 +14,10 @@ TOKEN_URL = "https://wbsapi.withings.net/v2/oauth2" GETMEAS_URL = "https://wbsapi.withings.net/measure?action=getmeas" + APP_CONFIG = os.environ.get( "WITHINGS_APP", - pkg_resources.resource_filename(__name__, "config/withings_app.json"), + importlib_resources.files(__name__) / "config/withings_app.json", ) USER_CONFIG = os.environ.get("WITHINGS_USER", HOME + "/.withings_user.json") From 9c5ac519d3b3e67fbeb30465ca856426224967ed Mon Sep 17 00:00:00 2001 From: stynoo Date: Wed, 13 Nov 2024 10:42:08 +0100 Subject: [PATCH 02/53] The recommended way of using Setuptools has shifted in the direction of using the Pyproject.toml in favour of setup.py and setup.cfg. --- poetry.lock | 770 +++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 29 ++ requirements.txt | 4 - setup.py | 35 --- 4 files changed, 799 insertions(+), 39 deletions(-) create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 setup.py diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..6fb4bff --- /dev/null +++ b/poetry.lock @@ -0,0 +1,770 @@ +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "astroid" +version = "3.3.5" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.9.0" +files = [ + {file = "astroid-3.3.5-py3-none-any.whl", hash = "sha256:a9d1c946ada25098d790e079ba2a1b112157278f3fb7e718ae6a9252f5835dc8"}, + {file = "astroid-3.3.5.tar.gz", hash = "sha256:5cfc40ae9f68311075d27ef68a4841bdc5cc7f6cf86671b49f00607d30188e2d"}, +] + +[[package]] +name = "black" +version = "24.10.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.9" +files = [ + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.10)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[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 = "dill" +version = "0.3.9" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"}, + {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + +[[package]] +name = "garth" +version = "0.4.46" +description = "Garmin SSO auth + Connect client" +optional = false +python-versions = ">=3.8" +files = [ + {file = "garth-0.4.46-py3-none-any.whl", hash = "sha256:c209192bac793f42764f0f06131dc503f74783d41af326f29187a1f6394dc5ff"}, + {file = "garth-0.4.46.tar.gz", hash = "sha256:5ae19e67612083285b10321b8e0e1f7c815a8f60f21e2f13be8c21a22e64d8eb"}, +] + +[package.dependencies] +pydantic = ">=1.10.12,<3.0.0" +requests = ">=2.0.0,<3.0.0" +requests-oauthlib = ">=1.3.1,<2.0.0" + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "importlib-resources" +version = "6.4.5" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, + {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +type = ["pytest-mypy"] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "lxml" +version = "5.3.0" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.6" +files = [ + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"}, + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7"}, + {file = "lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80"}, + {file = "lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be"}, + {file = "lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9"}, + {file = "lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d"}, + {file = "lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30"}, + {file = "lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b"}, + {file = "lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957"}, + {file = "lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d"}, + {file = "lxml-5.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237"}, + {file = "lxml-5.3.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577"}, + {file = "lxml-5.3.0-cp36-cp36m-win32.whl", hash = "sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70"}, + {file = "lxml-5.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c"}, + {file = "lxml-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b"}, + {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5"}, + {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11"}, + {file = "lxml-5.3.0-cp37-cp37m-win32.whl", hash = "sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84"}, + {file = "lxml-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e"}, + {file = "lxml-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920"}, + {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945"}, + {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42"}, + {file = "lxml-5.3.0-cp38-cp38-win32.whl", hash = "sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e"}, + {file = "lxml-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903"}, + {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de"}, + {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a"}, + {file = "lxml-5.3.0-cp39-cp39-win32.whl", hash = "sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff"}, + {file = "lxml-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c"}, + {file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml-html-clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=3.0.11)"] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +optional = false +python-versions = ">=3.6" +files = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] + +[[package]] +name = "pydantic" +version = "2.9.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, + {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.23.4" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.23.4" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, + {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, + {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, + {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, + {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pylint" +version = "3.3.1" +description = "python code static checker" +optional = false +python-versions = ">=3.9.0" +files = [ + {file = "pylint-3.3.1-py3-none-any.whl", hash = "sha256:2f846a466dd023513240bc140ad2dd73bfc080a5d85a710afdb728c420a5a2b9"}, + {file = "pylint-3.3.1.tar.gz", hash = "sha256:9f3dcc87b1203e612b78d91a896407787e708b3f189b5fa0b307712d49ff0c6e"}, +] + +[package.dependencies] +astroid = ">=3.3.4,<=3.4.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = {version = ">=0.3.7", markers = "python_version >= \"3.12\""} +isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-oauthlib" +version = "1.3.1" +description = "OAuthlib authentication support for Requests." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, + {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, +] + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[[package]] +name = "tomlkit" +version = "0.13.2" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.12" +content-hash = "222c59bc6d0b5b3221673e17663b51e77c2e4cbe6d48748fb671fb33d072fe7f" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4393aa1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,29 @@ +[tool.poetry] +name = "withings-sync" +version = "4.2.6-rc.2" +description = "A tool for synchronisation of Withings (ex. Nokia Health Body) to Garmin Connect and Trainer Road." +authors = ["Steffen Vogel ", + "Masayuki Hamasaki", + ] +license = "MIT" +readme = "README.md" +packages = [{include = "withings_sync"}] + +[tool.poetry.dependencies] +python = "^3.12" +garth = "^0.4.46" +requests = "^2.32.3" +lxml = "^5.3.0" +python-dotenv = "^1.0.1" +importlib-resources = "^6.4.5" + +[tool.poetry.group.dev.dependencies] +black = "^24.10.0" +pylint = "^3.3.1" + +[tool.poetry.scripts] +withings-sync = "withings_sync.sync:main" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index ceae4a5..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -lxml -requests -garth -setuptools diff --git a/setup.py b/setup.py deleted file mode 100644 index c5d8bff..0000000 --- a/setup.py +++ /dev/null @@ -1,35 +0,0 @@ -import os -from setuptools import setup - - -# Utility function to read the README file. -# Used for the long_description. It's nice, because now 1) we have a top level -# README file and 2) it's easier to type in the README file than to put a raw -# string in below ... -def read(fname): - return open(os.path.join(os.path.dirname(__file__), fname)).read() - - -setup( - name="withings-sync", - version=read(".VERSION"), - author="Masayuki Hamasaki, Steffen Vogel", - author_email="post@steffenvogel.de", - description="A tool for synchronisation of Withings (ex. Nokia Health Body) to Garmin Connect and Trainer Road.", - license="MIT", - keywords="garmin withings sync api scale smarthome", - url="https://github.com/jaroslawhartman/withings-sync", - packages=["withings_sync"], - long_description=read("README.md"), - long_description_content_type="text/markdown", - classifiers=[ - "Topic :: Utilities", - "License :: OSI Approved :: MIT License", - ], - install_requires=["lxml", "requests", "garth>=0.4.32", "python-dotenv"], - entry_points={ - "console_scripts": ["withings-sync=withings_sync.sync:main"], - }, - zip_safe=False, - include_package_data=True, -) From 0e920f4d532e4c528688e9dd0d60fcfea967ade0 Mon Sep 17 00:00:00 2001 From: stynoo Date: Wed, 13 Nov 2024 10:43:45 +0100 Subject: [PATCH 03/53] Update Dockerfile to use poetry & run as a non-priviledged user. --- Dockerfile | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index deead53..71ce5c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,26 @@ FROM python:3.12-alpine -# Install python-lxml -RUN apk add --no-cache --virtual .build-deps \ - gcc musl-dev \ - libxslt-dev libxml2-dev && \ - pip install lxml setuptools && \ - apk del .build-deps && \ - apk add --no-cache libxslt libxml2 +RUN adduser -D withings-sync -RUN mkdir -p /src -COPY . /src +USER withings-sync +WORKDIR /home/withings-sync -RUN cd /src && \ - pip install . +ENV PATH="/home/withings-sync/.poetry/bin:${PATH}" \ + PATH="/home/withings-sync/.local/bin:${PATH}" \ + PIP_ROOT_USER_ACTION=ignore \ + PIP_DISABLE_PIP_VERSION_CHECK=on \ + POETRY_HOME=/home/withings-sync/.poetry \ + POETRY_NO_INTERACTION=1 \ + POETRY_VIRTUALENVS_IN_PROJECT=1 \ + POETRY_VIRTUALENVS_CREATE=1 \ + POETRY_CACHE_DIR=/tmp/poetry_cache -ENTRYPOINT ["withings-sync"] +RUN pip install poetry + +COPY --chown=withings-sync:withings-sync pyproject.toml poetry.lock README.md .VERSION ./ +RUN poetry install --without dev --no-root && rm -rf $POETRY_CACHE_DIR + +COPY --chown=withings-sync:withings-sync withings_sync ./withings_sync/ +RUN poetry install --without dev + +ENTRYPOINT ["poetry", "run", "withings-sync"] From 63920bb70d5b22ad1985c05156d94104ad1fe8d9 Mon Sep 17 00:00:00 2001 From: stynoo Date: Wed, 13 Nov 2024 10:52:57 +0100 Subject: [PATCH 04/53] up to new major version - https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4393aa1..26e3c7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "withings-sync" -version = "4.2.6-rc.2" +version = "5.0.0a1" description = "A tool for synchronisation of Withings (ex. Nokia Health Body) to Garmin Connect and Trainer Road." authors = ["Steffen Vogel ", "Masayuki Hamasaki", From 09edea9a0fdc180cbba0ae2b41264b43b2af838f Mon Sep 17 00:00:00 2001 From: stynoo Date: Wed, 13 Nov 2024 11:23:51 +0100 Subject: [PATCH 05/53] migrate release script from setup.py/twine to pyproject.toml/poetry --- contrib/do_release.sh | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/contrib/do_release.sh b/contrib/do_release.sh index e74e31b..6e76bd4 100644 --- a/contrib/do_release.sh +++ b/contrib/do_release.sh @@ -4,8 +4,8 @@ # (username=__token__, password=pypi-***) set -e -# extract the version="x.y.z" from setup.py -VER=$(sed -n -e 's/.*version="\(.*\)".*/\1/p' < setup.py) +# extract the version="x.y.z" from pyproject.toml +VER=$(sed -n -e 's/.*version = "\(.*\)".*/\1/p' < pyproject.toml) function tag_if_not_tagged { TAG=v$1 @@ -21,11 +21,9 @@ function tag_if_not_tagged { function publish_to_pypi() { VERSION=$1 echo "creating sdist.." - python3 setup.py sdist > /dev/null - ARTIFACT="dist/withings-sync-${VERSION}.tar.gz" + poetry build # Publish to pypi.org - twine check $ARTIFACT - twine upload $ARTIFACT + poetry publish } tag_if_not_tagged $VER From 131195d21c01f5fc59b196b6b81983d096f05df9 Mon Sep 17 00:00:00 2001 From: stynoo Date: Wed, 13 Nov 2024 11:30:06 +0100 Subject: [PATCH 06/53] do_release.sh: user versions & tags from pyproject.toml --- contrib/do_release.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contrib/do_release.sh b/contrib/do_release.sh index 6e76bd4..bca460a 100644 --- a/contrib/do_release.sh +++ b/contrib/do_release.sh @@ -8,7 +8,7 @@ set -e VER=$(sed -n -e 's/.*version = "\(.*\)".*/\1/p' < pyproject.toml) function tag_if_not_tagged { - TAG=v$1 + TAG=v$VER if git rev-parse --verify --quiet "refs/tags/$TAG" >/dev/null; then echo "tag ${TAG} already exists" else @@ -19,7 +19,6 @@ function tag_if_not_tagged { } function publish_to_pypi() { - VERSION=$1 echo "creating sdist.." poetry build # Publish to pypi.org From 69cff35ded8f4993d267d746de516f38bdfd8e9a Mon Sep 17 00:00:00 2001 From: stynoo Date: Wed, 13 Nov 2024 11:36:58 +0100 Subject: [PATCH 07/53] sync.py: minimal requirement python 3.12 --- withings_sync/sync.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/withings_sync/sync.py b/withings_sync/sync.py index e874d00..bd54cde 100644 --- a/withings_sync/sync.py +++ b/withings_sync/sync.py @@ -503,8 +503,8 @@ def main(): logging.debug("withings-sync script version %s", version("withings-sync")) logging.debug("Script invoked with the following arguments: %s", ARGS) - if sys.version_info < (3, 7): - print("Sorry, requires at least Python3.7 to avoid issues with SSL.") + if sys.version_info < (3, 12): + print("Sorry, requires at least Python3.12.") sys.exit(1) sync() From c166adda22e2ecb752b0174a96e898f1c1ef9493 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 13 Nov 2024 12:04:33 +0100 Subject: [PATCH 08/53] build-publish.yml: update to use poetry --- .github/workflows/build-publish.yml | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index d1c7df8..c1cab98 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -10,31 +10,27 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - name: Checkout code 🛒 + uses: actions/checkout@v4 + - name: Update version file ⬆️ uses: brettdorrans/write-version-to-file@v1.1.0 with: filename: '.VERSION' placeholder: '${VERSION}' - - name: Set up Python 3.12 + - name: Set up Python 3.12 🐍 uses: actions/setup-python@v5 with: python-version: "3.12" - - name: Install pypa/build - run: >- - python -m - pip install - build - --user - - - name: Build a source tarball - run: >- - python -m - build - --sdist - --outdir dist/ . + - name: Install and configure Poetry 📜 + uses: snok/install-poetry@v1 + with: + virtualenvs-create: false + + - name: Build & Package project 👷 + run: poetry build - name: Publish distribution 📦 to PyPI if: startsWith(github.ref, 'refs/tags') From c351a31dd62cc4131a2b00d03b86aaa32b1a3f39 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sat, 7 Dec 2024 15:34:12 +0100 Subject: [PATCH 09/53] update version in pyproject.toml from the git tags --- .github/workflows/build-publish.yml | 6 +++--- .github/workflows/docker-image.yml | 6 +++--- README.md | 3 +-- contrib/do_release.sh | 31 ----------------------------- pyproject.toml | 2 +- 5 files changed, 8 insertions(+), 40 deletions(-) delete mode 100644 contrib/do_release.sh diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index c1cab98..e9e8e7b 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -13,11 +13,11 @@ jobs: - name: Checkout code 🛒 uses: actions/checkout@v4 - - name: Update version file ⬆️ + - name: Update version in pyproject.toml ⬆️ uses: brettdorrans/write-version-to-file@v1.1.0 with: - filename: '.VERSION' - placeholder: '${VERSION}' + filename: 'pyproject.toml' + placeholder: '1.0.0.dev1' - name: Set up Python 3.12 🐍 uses: actions/setup-python@v5 diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index dea5da5..7d3a96d 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -30,11 +30,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Update version file ⬆️ + - name: Update version in pyproject.toml ⬆️ uses: brettdorrans/write-version-to-file@v1.1.0 with: - filename: '.VERSION' - placeholder: '${VERSION}' + filename: 'pyproject.toml' + placeholder: '1.0.0.dev1' - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/README.md b/README.md index 8fa5db2..e776a40 100644 --- a/README.md +++ b/README.md @@ -252,8 +252,7 @@ This will run the job every 3 hours (at a random minute) and writing the output Release works via the GitHub [Draft a new Release](https://github.com/jaroslawhartman/withings-sync/releases/new) function. -The `version` key in `setup.py` will be bumped automatically (Version will be written to .VERSION file). -Keep in mind to update the `.VERSION` if a major release is done. +The `version` key in `pyproject.toml` will be bumped automatically (Version will be written to pyproject.toml file). ### Docker Image diff --git a/contrib/do_release.sh b/contrib/do_release.sh deleted file mode 100644 index bca460a..0000000 --- a/contrib/do_release.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh -# This script can be used to bypass github actions -# it deploys to pipy with api credentials -# (username=__token__, password=pypi-***) - -set -e -# extract the version="x.y.z" from pyproject.toml -VER=$(sed -n -e 's/.*version = "\(.*\)".*/\1/p' < pyproject.toml) - -function tag_if_not_tagged { - TAG=v$VER - if git rev-parse --verify --quiet "refs/tags/$TAG" >/dev/null; then - echo "tag ${TAG} already exists" - else - git tag $TAG - git push --tags - echo "tagged ${TAG}" - fi -} - -function publish_to_pypi() { - echo "creating sdist.." - poetry build - # Publish to pypi.org - poetry publish -} - -tag_if_not_tagged $VER -publish_to_pypi $VER - - diff --git a/pyproject.toml b/pyproject.toml index 26e3c7a..5b3cc23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "withings-sync" -version = "5.0.0a1" +version = "1.0.0.dev1" description = "A tool for synchronisation of Withings (ex. Nokia Health Body) to Garmin Connect and Trainer Road." authors = ["Steffen Vogel ", "Masayuki Hamasaki", From 9896a152a5725bc29af3a3aba5dd873338bf8cd1 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sat, 7 Dec 2024 16:09:50 +0100 Subject: [PATCH 10/53] check if we are good to run on ubuntu-24.04 (latest will upgrade soon from 22.04 to 24.04 --- .github/workflows/build-publish.yml | 6 +++--- .github/workflows/docker-image.yml | 15 ++++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index e9e8e7b..3208421 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -6,11 +6,11 @@ on: push jobs: build-n-publish: - name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI - runs-on: ubuntu-latest + name: Build and publish Python 🐍 distributions 📦 to PyPI + runs-on: ubuntu-24.04 steps: - - name: Checkout code 🛒 + - name: Checkout repository 🛒 uses: actions/checkout@v4 - name: Update version in pyproject.toml ⬆️ diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 7d3a96d..ab03aab 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -21,13 +21,14 @@ env: jobs: build-and-push-image: - runs-on: ubuntu-latest + name: Build and publish Docker image 📦 to ghcr 🗃️ + runs-on: ubuntu-24.04 permissions: contents: read packages: write steps: - - name: Checkout repository + - name: Checkout repository 🛒 uses: actions/checkout@v4 - name: Update version in pyproject.toml ⬆️ @@ -36,26 +37,26 @@ jobs: filename: 'pyproject.toml' placeholder: '1.0.0.dev1' - - name: Set up QEMU + - name: Set up QEMU 👷 uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx + - name: Set up Docker Buildx 👷 uses: docker/setup-buildx-action@v3 - - name: Log in to the Container registry + - name: Log in to the Container registry 🗃️ uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker + - name: Extract metadata (tags, labels) for Docker 🗂️ id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - name: Build and push Docker image 📦 to ghcr + - name: Build and push Docker image 📦 to ghcr 🗃️ uses: docker/build-push-action@v6 with: context: . From 43951b50b752dad001c3683cc273dadc331d73b2 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sat, 7 Dec 2024 16:18:35 +0100 Subject: [PATCH 11/53] test run on ubuntu-24.04 successfull, reverting to latest, we are good to go --- .VERSION | 1 - .github/workflows/build-publish.yml | 2 +- .github/workflows/docker-image.yml | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 .VERSION diff --git a/.VERSION b/.VERSION deleted file mode 100644 index 4640e9f..0000000 --- a/.VERSION +++ /dev/null @@ -1 +0,0 @@ -${VERSION} diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 3208421..69bcb84 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -7,7 +7,7 @@ on: push jobs: build-n-publish: name: Build and publish Python 🐍 distributions 📦 to PyPI - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - name: Checkout repository 🛒 diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index ab03aab..725870a 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -22,7 +22,7 @@ env: jobs: build-and-push-image: name: Build and publish Docker image 📦 to ghcr 🗃️ - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest permissions: contents: read packages: write From ac480e9f5a2adaedbccf2f127ea81eb6a62613cb Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sat, 7 Dec 2024 16:41:59 +0100 Subject: [PATCH 12/53] remove .VERSION from Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 71ce5c3..1266019 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ ENV PATH="/home/withings-sync/.poetry/bin:${PATH}" \ RUN pip install poetry -COPY --chown=withings-sync:withings-sync pyproject.toml poetry.lock README.md .VERSION ./ +COPY --chown=withings-sync:withings-sync pyproject.toml poetry.lock README.md ./ RUN poetry install --without dev --no-root && rm -rf $POETRY_CACHE_DIR COPY --chown=withings-sync:withings-sync withings_sync ./withings_sync/ From 192a6f75682f200d8e8d7d7e1d59bb3e23ecef28 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:33:56 +0100 Subject: [PATCH 13/53] test package building to test-pypi --- .github/workflows/build-publish.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 69bcb84..598d165 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -32,8 +32,15 @@ jobs: - name: Build & Package project 👷 run: poetry build - - name: Publish distribution 📦 to PyPI +# - name: Publish distribution 📦 to PyPI +# if: startsWith(github.ref, 'refs/tags') +# uses: pypa/gh-action-pypi-publish@release/v1 +# with: +# password: ${{ secrets.PYPI_API_TOKEN }} + + - name: Publish package to TestPyPI if: startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@release/v1 with: - password: ${{ secrets.PYPI_API_TOKEN }} + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository-url: https://test.pypi.org/legacy/ From 6d99ecb64b034ff0d2cbf7eaf0f9e7249e3b2d35 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:46:00 +0100 Subject: [PATCH 14/53] build-publish.yml: allow trusted package publishing --- .github/workflows/build-publish.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 598d165..ae7bc40 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -9,6 +9,9 @@ jobs: name: Build and publish Python 🐍 distributions 📦 to PyPI runs-on: ubuntu-latest + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + steps: - name: Checkout repository 🛒 uses: actions/checkout@v4 From 75b598a207a03dd2993d4f920cf70718b50480e9 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 11 Dec 2024 22:12:34 +0100 Subject: [PATCH 15/53] build-publish.yml: remove secrets --- .github/workflows/build-publish.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index ae7bc40..a72a1ca 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -45,5 +45,4 @@ jobs: if: startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@release/v1 with: - password: ${{ secrets.TEST_PYPI_API_TOKEN }} repository-url: https://test.pypi.org/legacy/ From 53827f818c574c4efa2e4b16a06f795c0c9034a3 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 11 Dec 2024 22:27:28 +0100 Subject: [PATCH 16/53] build-publish.yml: split build & push --- .github/workflows/build-publish.yml | 46 ++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index a72a1ca..16f0db3 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -5,13 +5,10 @@ name: Build and Publish Release via PyPi on: push jobs: - build-n-publish: - name: Build and publish Python 🐍 distributions 📦 to PyPI + build: + name: Build distribution 📦 runs-on: ubuntu-latest - permissions: - id-token: write # IMPORTANT: this permission is mandatory for trusted publishing - steps: - name: Checkout repository 🛒 uses: actions/checkout@v4 @@ -35,14 +32,47 @@ jobs: - name: Build & Package project 👷 run: poetry build +# publish-to-pypi: +# name: >- +# Publish Python 🐍 distribution 📦 to PyPI +# if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes +# needs: +# - build +# runs-on: ubuntu-latest +# environment: +# name: pypi +# url: https://pypi.org/p/withings-sync +# permissions: +# id-token: write # IMPORTANT: mandatory for trusted publishing +# steps: +# - name: Download all the dists +# uses: actions/download-artifact@v3 +# with: +# name: python-package-distributions +# path: dist/ # - name: Publish distribution 📦 to PyPI -# if: startsWith(github.ref, 'refs/tags') # uses: pypa/gh-action-pypi-publish@release/v1 # with: # password: ${{ secrets.PYPI_API_TOKEN }} - - name: Publish package to TestPyPI - if: startsWith(github.ref, 'refs/tags') + publish-to-testpypi: + name: Publish Python 🐍 distribution 📦 to TestPyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: testpypi + url: https://test.pypi.org/p/withings-sync + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Download all the dists + uses: actions/download-artifact@v3 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to TestPyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ From dd3055dcc70a8ea71e0efc788a3998334379638b Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 11 Dec 2024 22:32:01 +0100 Subject: [PATCH 17/53] build-publish.yml: Store the distribution packages --- .github/workflows/build-publish.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 16f0db3..affc383 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -8,29 +8,29 @@ jobs: build: name: Build distribution 📦 runs-on: ubuntu-latest - steps: - name: Checkout repository 🛒 uses: actions/checkout@v4 - - name: Update version in pyproject.toml ⬆️ uses: brettdorrans/write-version-to-file@v1.1.0 with: filename: 'pyproject.toml' placeholder: '1.0.0.dev1' - - name: Set up Python 3.12 🐍 uses: actions/setup-python@v5 with: python-version: "3.12" - - name: Install and configure Poetry 📜 uses: snok/install-poetry@v1 with: virtualenvs-create: false - - name: Build & Package project 👷 run: poetry build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ # publish-to-pypi: # name: >- From c8de1154c34bfe05a9c08c909123c097a47420a0 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 11 Dec 2024 22:37:38 +0100 Subject: [PATCH 18/53] build-publish.yml: match versions --- .github/workflows/build-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index affc383..456c92c 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -46,7 +46,7 @@ jobs: # id-token: write # IMPORTANT: mandatory for trusted publishing # steps: # - name: Download all the dists -# uses: actions/download-artifact@v3 +# uses: actions/download-artifact@v4 # with: # name: python-package-distributions # path: dist/ @@ -68,7 +68,7 @@ jobs: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - name: Download all the dists - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ From 49a1afec35ec21ac504820dfda7aeb756927e74b Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 11 Dec 2024 22:56:23 +0100 Subject: [PATCH 19/53] build-publish.yml: publish always to testpypi but only to pypi on release --- .github/workflows/build-publish.yml | 45 ++++++++++++++--------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 456c92c..26c8533 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -32,32 +32,31 @@ jobs: name: python-package-distributions path: dist/ -# publish-to-pypi: -# name: >- -# Publish Python 🐍 distribution 📦 to PyPI -# if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes -# needs: -# - build -# runs-on: ubuntu-latest -# environment: -# name: pypi -# url: https://pypi.org/p/withings-sync -# permissions: -# id-token: write # IMPORTANT: mandatory for trusted publishing -# steps: -# - name: Download all the dists -# uses: actions/download-artifact@v4 -# with: -# name: python-package-distributions -# path: dist/ -# - name: Publish distribution 📦 to PyPI -# uses: pypa/gh-action-pypi-publish@release/v1 -# with: -# password: ${{ secrets.PYPI_API_TOKEN }} + publish-to-pypi: + name: >- + Publish Python 🐍 distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/withings-sync + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} # LEAVING THIS HERE UNTIL TRUSTED PUBLISHING IS IN PLACE publish-to-testpypi: name: Publish Python 🐍 distribution 📦 to TestPyPI - if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes needs: - build runs-on: ubuntu-latest From ebb3a10be687efaeebdc75489feb1c187b98d63b Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 11 Dec 2024 23:03:15 +0100 Subject: [PATCH 20/53] build-publish.yml: add skip-existing to testpypi --- .github/workflows/build-publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 26c8533..932b415 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -75,3 +75,4 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ + skip-existing: true From 5a3c36a3a798537ea82789e23b12cdf210afb90b Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 11 Dec 2024 23:36:14 +0100 Subject: [PATCH 21/53] build-publish.yml: enrich github release with package --- .github/workflows/build-publish.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 932b415..6812262 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -33,8 +33,7 @@ jobs: path: dist/ publish-to-pypi: - name: >- - Publish Python 🐍 distribution 📦 to PyPI + name: Publish Python 🐍 distribution 📦 to PyPI if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes needs: - build @@ -55,6 +54,27 @@ jobs: with: password: ${{ secrets.PYPI_API_TOKEN }} # LEAVING THIS HERE UNTIL TRUSTED PUBLISHING IS IN PLACE + publish-to-github-release: + name: Upload the Python 🐍 distribution to GitHub Release + if: startsWith(github.ref, 'refs/tags/') # only upload to GitHub Release on tag pushes + needs: + - build + runs-on: ubuntu-latest + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Release + uses: fnkr/github-action-ghr@v1 + if: startsWith(github.ref, 'refs/tags/') + env: + GHR_PATH: dist/ + GHR_REPLACE: true + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish-to-testpypi: name: Publish Python 🐍 distribution 📦 to TestPyPI needs: From 30247bc423ab4d4a93738a57fac8ce51106101ac Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sat, 14 Dec 2024 13:43:25 +0100 Subject: [PATCH 22/53] sync.py: add version parameter && black code formatting --- withings_sync/sync.py | 71 +++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/withings_sync/sync.py b/withings_sync/sync.py index bd54cde..2a0b9ec 100644 --- a/withings_sync/sync.py +++ b/withings_sync/sync.py @@ -26,7 +26,7 @@ def load_variable(env_var, secrets_file): # Try to read the value from the secrets file. Silently fail if the file # cannot be read and use an empty value try: - with open(secrets_file, encoding='utf-8') as secret: + with open(secrets_file, encoding="utf-8") as secret: value = secret.read().strip("\n") except OSError: value = "" @@ -36,11 +36,14 @@ def load_variable(env_var, secrets_file): return os.getenv(env_var, value) -GARMIN_USERNAME = load_variable('GARMIN_USERNAME', "/run/secrets/garmin_username") -GARMIN_PASSWORD = load_variable('GARMIN_PASSWORD', "/run/secrets/garmin_password") -TRAINERROAD_USERNAME = load_variable('TRAINERROAD_USERNAME', "/run/secrets/trainerroad_username") -TRAINERROAD_PASSWORD = load_variable('TRAINERROAD_PASSWORD', "/run/secrets/trainerroad_password") - +GARMIN_USERNAME = load_variable("GARMIN_USERNAME", "/run/secrets/garmin_username") +GARMIN_PASSWORD = load_variable("GARMIN_PASSWORD", "/run/secrets/garmin_password") +TRAINERROAD_USERNAME = load_variable( + "TRAINERROAD_USERNAME", "/run/secrets/trainerroad_username" +) +TRAINERROAD_PASSWORD = load_variable( + "TRAINERROAD_PASSWORD", "/run/secrets/trainerroad_password" +) def get_args(): @@ -56,6 +59,9 @@ def get_args(): def date_parser(date_string): return datetime.strptime(date_string, "%Y-%m-%d") + parser.add_argument( + "--version", "-V", action="version", version=version("withings-sync") + ) parser.add_argument( "--garmin-username", "--gu", @@ -92,14 +98,16 @@ def date_parser(date_string): ) parser.add_argument( - "--fromdate", "-f", + "--fromdate", + "-f", type=date_parser, metavar="DATE", help="Date to start syncing from. Ex: 2023-12-20", ) parser.add_argument( - "--todate", "-t", + "--todate", + "-t", type=date_parser, default=date.today(), metavar="DATE", @@ -107,7 +115,8 @@ def date_parser(date_string): ) parser.add_argument( - "--to-fit", "-F", + "--to-fit", + "-F", action="store_true", help="Write output file in FIT format.", ) @@ -135,18 +144,13 @@ def date_parser(date_string): parser.add_argument( "--features", - nargs='+', + nargs="+", default=[], metavar="BLOOD_PRESSURE", - help="Enable Features like BLOOD_PRESSURE." + help="Enable Features like BLOOD_PRESSURE.", ) - parser.add_argument( - "--verbose", - "-v", - action="store_true", - help="Run verbosely." - ) + parser.add_argument("--verbose", "-v", action="store_true", help="Run verbosely.") return parser.parse_args() @@ -176,7 +180,9 @@ def generate_fitdata(syncdata): logging.debug("Generating fit data...") weight_measurements = list(filter(lambda x: (x["type"] == "weight"), syncdata)) - blood_pressure_measurements = list(filter(lambda x: (x["type"] == "blood_pressure"), syncdata)) + blood_pressure_measurements = list( + filter(lambda x: (x["type"] == "blood_pressure"), syncdata) + ) fit_weight = None fit_blood_pressure = None @@ -238,7 +244,10 @@ def generate_jsondata(syncdata): if "bmi" in record: json_data[sdt]["BMI"] = {"Value": record["bmi"], "Unit": "kg/m^2"} if "percent_hydration" in record: - json_data[sdt]["Percent_Hydration"] = {"Value": record["percent_hydration"], "Unit": "%"} + json_data[sdt]["Percent_Hydration"] = { + "Value": record["percent_hydration"], + "Unit": "%", + } logging.debug("Json data generated...") return json_data @@ -288,21 +297,24 @@ def prepare_syncdata(height, groups): "systolic_blood_pressure": group.get_systolic_blood_pressure(), "heart_pulse": group.get_heart_pulse(), "raw_data": group.get_raw_data(), - "type": "blood_pressure" + "type": "blood_pressure", } # execute the code below, if this is not a whitelisted entry like weight and blood pressure if "weight" not in group_data and not ( - "diastolic_blood_pressure" in group_data and "BLOOD_PRESSURE" in ARGS.features): + "diastolic_blood_pressure" in group_data + and "BLOOD_PRESSURE" in ARGS.features + ): collected_metrics = "weight data" if "BLOOD_PRESSURE" in ARGS.features: collected_metrics += " or blood pressure" elif "diastolic_blood_pressure" in group_data: collected_metrics += ", but blood pressure (to enable sync set --features BLOOD_PRESSURE)" - logging.info( - "%s This Withings metric contains no %s. Not syncing...", dt, collected_metrics + "%s This Withings metric contains no %s. Not syncing...", + dt, + collected_metrics, ) groupdata_log_raw_data(group_data) # for now, remove the entry as we're handling only weight and feature enabled data @@ -396,7 +408,9 @@ def write_to_file_when_needed(fit_data_weigth, fit_data_blood_pressure, json_dat if fit_data_weigth is not None: write_to_fitfile(ARGS.output + ".weight.fit", fit_data_weigth) if fit_data_blood_pressure is not None: - write_to_fitfile(ARGS.output + ".blood_pressure.fit", fit_data_blood_pressure) + write_to_fitfile( + ARGS.output + ".blood_pressure.fit", fit_data_blood_pressure + ) if ARGS.to_json: filename = ARGS.output + ".json" @@ -448,7 +462,9 @@ def sync(): # Upload to Trainer Road if ARGS.trainerroad_username and last_weight_exists: # sort and get last weight - last_weight_measurement = sorted(only_weight_entries, key=lambda x: x["date_time"])[-1] + last_weight_measurement = sorted( + only_weight_entries, key=lambda x: x["date_time"] + )[-1] last_weight = last_weight_measurement["weight"] logging.info("Trainerroad username set -- attempting to sync") logging.info(" Last weight %s", last_weight) @@ -456,7 +472,9 @@ def sync(): if sync_trainerroad(last_weight): logging.info("TrainerRoad update done!") else: - logging.info("No TrainerRoad username or a new measurement " "- skipping sync") + logging.info( + "No TrainerRoad username or a new measurement " "- skipping sync" + ) # Upload to Garmin Connect if ARGS.garmin_username and ( @@ -508,4 +526,3 @@ def main(): sys.exit(1) sync() - From 5f30683e55c3d2ca47a5546e4443e8d3873610ba Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sun, 15 Dec 2024 20:05:05 +0100 Subject: [PATCH 23/53] Dockerfile: best practices & prep for supercronic usage --- Dockerfile | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1266019..80ec168 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,26 +1,32 @@ FROM python:3.12-alpine -RUN adduser -D withings-sync +ENV PROJECT=withings-sync +ENV PACKAGE=withings_sync +ENV PROJECT_DIR=/home/$PROJECT -USER withings-sync -WORKDIR /home/withings-sync +RUN apk --no-cache add supercronic -ENV PATH="/home/withings-sync/.poetry/bin:${PATH}" \ - PATH="/home/withings-sync/.local/bin:${PATH}" \ +RUN adduser -D $PROJECT + +USER $PROJECT +WORKDIR $PROJECT_DIR + +ENV PATH="${PROJECT_DIR}/.poetry/bin:${PATH}" \ + PATH="${PROJECT_DIR}/.local/bin:${PATH}" \ PIP_ROOT_USER_ACTION=ignore \ PIP_DISABLE_PIP_VERSION_CHECK=on \ - POETRY_HOME=/home/withings-sync/.poetry \ + POETRY_HOME="${PROJECT_DIR}/.poetry" \ POETRY_NO_INTERACTION=1 \ POETRY_VIRTUALENVS_IN_PROJECT=1 \ POETRY_VIRTUALENVS_CREATE=1 \ - POETRY_CACHE_DIR=/tmp/poetry_cache + POETRY_CACHE_DIR="/tmp/poetry_cache" RUN pip install poetry -COPY --chown=withings-sync:withings-sync pyproject.toml poetry.lock README.md ./ +COPY --chown=$PROJECT:$PROJECT pyproject.toml poetry.lock README.md $PROJECT_DIR/ RUN poetry install --without dev --no-root && rm -rf $POETRY_CACHE_DIR -COPY --chown=withings-sync:withings-sync withings_sync ./withings_sync/ +COPY --chown=$PROJECT:$PROJECT $PACKAGE ./$PACKAGE/ RUN poetry install --without dev ENTRYPOINT ["poetry", "run", "withings-sync"] From 569d443a3b5afb329ad7cde66869a35962b83ee6 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sun, 15 Dec 2024 20:42:25 +0100 Subject: [PATCH 24/53] docker-image.yml: prevent unknown/unknown - OS/Arch - images pushed to ghcr --- .github/workflows/docker-image.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 725870a..c23ab6b 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -61,6 +61,7 @@ jobs: with: context: . push: true + provenance: false platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} From 2fd7c1c9f47b82dd636b4b313679cc77b60dd435 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sun, 15 Dec 2024 21:18:03 +0100 Subject: [PATCH 25/53] streamline actions / only prod push on releases --- .github/workflows/build-publish.yml | 2 +- .github/workflows/docker-image.yml | 15 ++------------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 6812262..6a011e8 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -1,6 +1,6 @@ --- -name: Build and Publish Release via PyPi +name: Build and Publish Release to PyPI & GitHub on: push diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index c23ab6b..bb9af58 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -2,18 +2,7 @@ name: Publish Docker Image -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -on: - schedule: - - cron: '21 5 */5 * *' - push: - branches: [ master ] - # Publish semver tags as releases. - tags: [ 'v*.*.*' ] +on: push env: REGISTRY: ghcr.io @@ -60,7 +49,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - push: true + push: ${{ GitHub.event_name == 'release' }} # only push on a new release provenance: false platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} From 2995e46bd9735ed65a15fd7ffb7bcb1711d38e6a Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sun, 15 Dec 2024 21:38:10 +0100 Subject: [PATCH 26/53] docker-image.yml: case --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index bb9af58..57b40bf 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -49,7 +49,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - push: ${{ GitHub.event_name == 'release' }} # only push on a new release + push: ${{ github.event_name == 'release' }} provenance: false platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} From 745ea1ebb680860f7817b018cae19b7f0321b0a8 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sun, 15 Dec 2024 21:41:41 +0100 Subject: [PATCH 27/53] docker-image.yml: streamline action filters --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 57b40bf..69e9916 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -49,7 +49,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - push: ${{ github.event_name == 'release' }} + push: ${{ startsWith(github.ref, 'refs/tags/') }} # only upload to ghcr on tag pushes provenance: false platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} From b90cdcd0a63ae8fd27fc51a1caaca2262835e08d Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sun, 15 Dec 2024 21:51:17 +0100 Subject: [PATCH 28/53] build-publish.yml: remove double condition --- .github/workflows/build-publish.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 6a011e8..ff90246 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -68,7 +68,6 @@ jobs: path: dist/ - name: Release uses: fnkr/github-action-ghr@v1 - if: startsWith(github.ref, 'refs/tags/') env: GHR_PATH: dist/ GHR_REPLACE: true From 32adfd3393dc03117d0c0122dc8b356d28950aa3 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sun, 15 Dec 2024 23:13:09 +0100 Subject: [PATCH 29/53] Update README.md --- README.md | 155 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 139 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e776a40..44ee866 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ +> [!CAUTION] +> This fork of the withings-sync project introduces breaking changes that users need to be aware of before upgrading or using it. +> These changes were made to enhance security and compatibility but may require modifications to your existing setup. +> +> - The container now runs without root privileges. +> - Dependencies, virtual envs, packaging is now done by Poetry. +> - This fork requires a recent version of Python, currently capped at >= python 3.12. +> +> Make sure to go over the updated readme and test these new changes thoroughly for your environment. +> Chances are quite high you will have to make changes to make this work again. + # withings-sync A tool for synchronisation of the Withings API to: @@ -6,42 +17,154 @@ A tool for synchronisation of the Withings API to: - Trainer Road - raw JSON output -## Installation - -```bash -$ pip install withings-sync -``` +### Table of Contents +**[1. Installation Instructions](#installation-instructions)**
+**[2. Usage Instructions](#usage-instructions)**
+**[3. Providing credentials](#troubleshooting)**
+**[Compatibility](#compatibility)**
+**[Notes and Miscellaneous](#notes-and-miscellaneous)**
+**[Building the Extension Bundles](#building-the-extension-bundles)**
+**[Next Steps, Credits, Feedback, License](#next-steps)**
+ +## 1. Installation Instructions +
+ Installation of withings-sync with pip + + ```bash + $ pip install withings-sync + ``` +
+ +
+ Installation of withings-sync with docker compose (not using cron) + + > This method follows a default approach of utilizing a single container to run one job at a time, then exiting upon completion. It relies on an external scheduler (e.g., cron on the host operating system) to manage job execution. + + ### create the following file/directory structure: + ```bash + . # STACK_PATH + ./.env # .env file containing your variables + ./docker-compose.yml # docker-compose file + ./config/ # config directory + ./config/withings-sync/ # config directory for withings-sync + ./config/withings-sync/.garmin_session/ # .garmin_session directory to store oauth tokens + ``` + + ### contents of an example .env file: + ```bash + TZ=Europe/Kyiv + STACK_PATH=/home/youruser/withings-sync + GARMIN_USERNAME="your.name@domain.ext" + GARMIN_PASSWORD="YourPasswordHere" + ``` + + ### contents of an example docker-compose.yml file: + ```yaml + services: + withings-sync: + image: ghcr.io/jaroslawhartman/withings-sync:latest + container_name: withings-sync + stdin_open: true # docker run -i + tty: true # docker run -t + environment: + - TZ=${TZ:?err} + - GARMIN_USERNAME=${GARMIN_USERNAME:?err} + - GARMIN_PASSWORD=${GARMIN_PASSWORD:?err} + volumes: + - /etc/localtime:/etc/localtime:ro + - ${STACK_PATH:?err}/config/withings-sync/.withings_user.json:/home/withings-sync/.withings_user.json + - ${STACK_PATH:?err}/config/withings-sync/.garmin_session:/home/withings-sync/.garmin_session + restart: unless-stopped + ``` +
+ +
+ Installation of withings-sync with docker compose (using supercronic) + + > This method leverages the included supercronic package for scheduling jobs directly within the container. This eliminates the need for an external scheduler, allowing the container to manage job execution independently. + + ### create the following file/directory structure: + ```bash + . # STACK_PATH + ./.env # .env file containing your variables + ./docker-compose.yml # docker-compose file + ./config/ # config directory + ./config/withings-sync/ # config directory for withings-sync + ./config/withings-sync/entrypoint.sh # entrypoint.sh file containing your + ./config/withings-sync/.garmin_session/ # .garmin_session directory to store oauth tokens + ``` + + ### contents of an example .env file: + ```bash + TZ=Europe/Kyiv + STACK_PATH=/home/youruser/withings-sync + GARMIN_USERNAME="your.name@domain.ext" + GARMIN_PASSWORD="YourPasswordHere" + ``` + + ### contents of an example entrypoint.sh file: + ```bash + #!/bin/sh + echo "$(( $RANDOM % 59 +0 )) */3 * * * poetry run withings-sync --verbose --features BLOOD_PRESSURE" > /home/withings-sync/cronjob + supercronic /home/withings-sync/cronjob + ``` + + ### contents of an example docker-compose.yml file: + ```yaml + services: + withings-sync: + image: ghcr.io/jaroslawhartman/withings-sync:latest + container_name: withings-sync + stdin_open: true # docker run -i + tty: true # docker run -t + environment: + - TZ=${TZ:?err} + - GARMIN_USERNAME=${GARMIN_USERNAME:?err} + - GARMIN_PASSWORD=${GARMIN_PASSWORD:?err} + volumes: + - /etc/localtime:/etc/localtime:ro + - ${STACK_PATH:?err}/config/withings-sync/.withings_user.json:/home/withings-sync/.withings_user.json + - ${STACK_PATH:?err}/config/withings-sync/.garmin_session:/home/withings-sync/.garmin_session + - ${STACK_PATH:?err}/config/withings-sync/entrypoint.sh:/home/withings-sync/entrypoint.sh + entrypoint: "/home/withings-sync/entrypoint.sh" + restart: unless-stopped + ``` +
-## Usage +## 2. Usage Instructions ``` -usage: withings-sync [-h] [--garmin-username GARMIN_USERNAME] [--garmin-password GARMIN_PASSWORD] [--trainerroad-username TRAINERROAD_USERNAME] [--trainerroad-password TRAINERROAD_PASSWORD] [--fromdate DATE] - [--todate DATE] [--to-fit] [--to-json] [--output BASENAME] [--no-upload] [--verbose] +usage: withings-sync [-h] [--version] [--garmin-username GARMIN_USERNAME] [--garmin-password GARMIN_PASSWORD] [--trainerroad-username TRAINERROAD_USERNAME] [--trainerroad-password TRAINERROAD_PASSWORD] [--fromdate DATE] [--todate DATE] [--to-fit] [--to-json] [--output BASENAME] [--no-upload] + [--features BLOOD_PRESSURE [BLOOD_PRESSURE ...]] [--verbose] A tool for synchronisation of Withings (ex. Nokia Health Body) to Garmin Connect and Trainer Road or to provide a json string. -optional arguments: +options: -h, --help show this help message and exit + --version, -V show program's version number and exit --garmin-username GARMIN_USERNAME, --gu GARMIN_USERNAME - username to log in to Garmin Connect. + Username to log in to Garmin Connect. --garmin-password GARMIN_PASSWORD, --gp GARMIN_PASSWORD - password to log in to Garmin Connect. + Password to log in to Garmin Connect. --trainerroad-username TRAINERROAD_USERNAME, --tu TRAINERROAD_USERNAME - username to log in to TrainerRoad. + Username to log in to TrainerRoad. --trainerroad-password TRAINERROAD_PASSWORD, --tp TRAINERROAD_PASSWORD - password to log in to TrainerRoad. + Password to log in to TrainerRoad. --fromdate DATE, -f DATE + Date to start syncing from. Ex: 2023-12-20 --todate DATE, -t DATE + Date for the last sync. Ex: 2023-12-30 --to-fit, -F Write output file in FIT format. --to-json, -J Write output file in JSON format. --output BASENAME, -o BASENAME Write downloaded measurements to file. - --features Enable Features - BLOOD_PRESSURE = sync blood pressure --no-upload Won't upload to Garmin Connect or TrainerRoad. - --verbose, -v Run verbosely + --features BLOOD_PRESSURE [BLOOD_PRESSURE ...] + Enable Features like BLOOD_PRESSURE. + --verbose, -v Run verbosely. ``` +## 3. Providing credentials ### Providing credentials via environment variables You can use the following environment variables for providing the Garmin and/or Trainerroad credentials: From 64ba9c39e11443aa79d823de41757339a501d4a7 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sun, 15 Dec 2024 23:25:39 +0100 Subject: [PATCH 30/53] README.md: updates --- README.md | 56 ++++++++++++------------------------------------------- 1 file changed, 12 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 44ee866..829604d 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,9 @@ A tool for synchronisation of the Withings API to: - raw JSON output ### Table of Contents -**[1. Installation Instructions](#installation-instructions)**
-**[2. Usage Instructions](#usage-instructions)**
-**[3. Providing credentials](#troubleshooting)**
-**[Compatibility](#compatibility)**
-**[Notes and Miscellaneous](#notes-and-miscellaneous)**
-**[Building the Extension Bundles](#building-the-extension-bundles)**
-**[Next Steps, Credits, Feedback, License](#next-steps)**
+**[1. Installation Instructions](#1.-installation-instructions)**
+**[2. Usage Instructions](#2.-usage-instructions)**
+**[3. Providing credentials](#3.-providing-credentials)**
## 1. Installation Instructions
@@ -244,7 +240,7 @@ See also: https://github.com/jaroslawhartman/withings-sync/issues/31 ### Docker ``` -$ docker pull ghcr.io/jaroslawhartman/withings-sync:master +$ docker pull ghcr.io/jaroslawhartman/withings-sync:latest ``` First start to ensure the script can start successfully: @@ -253,7 +249,7 @@ First start to ensure the script can start successfully: Obtaining Withings authorisation: ``` -$ docker run -v $HOME:/root --interactive --tty --name withings ghcr.io/jaroslawhartman/withings-sync:master --garmin-username= --garmin-password= +$ docker run -v $HOME:/root --interactive --tty --name withings ghcr.io/jaroslawhartman/withings-sync:latest --garmin-username= --garmin-password= Can't read config file config/withings_user.json User interaction needed to get Authentification Code from Withings! @@ -297,8 +293,10 @@ $ kubectl apply -f contrib/k8s-job.yaml ``` ### For advanced users - registering own Withings application +
+ If you are not sure you need this, you most likely won't. -The script has been registered as a Withings application and got assigned `Client ID` and `Consumer Secret`. If you wish to create your own application - feel free! +The script has been registered as a Withings application and got assigned `Client ID` and `Consumer Secret`. If you wish to create your own application - feel free! * First you need a Withings account. [Sign up here](https://account.withings.com/connectionuser/account_create). @@ -340,36 +338,7 @@ Example docker-compose: ``` You can then add the app-config in `withings-sync/withings_app.json` - -### Run a periodic docker-compose cronjob - -We take the official docker image and override the entrypoint to crond. - -If you have completed the initial setup (withings_user.json created and working), you can create the following config - -``` -version: "3.8" -services: - withings-sync: - container_name: withings-sync - image: ghcr.io/jaroslawhartman/withings-sync:master - volumes: - - "${VOLUME_PATH}/withings-sync:/root" - - /etc/localtime:/etc/localtime:ro - environment: - - TZ=${TIME_ZONE} - entrypoint: "/root/entrypoint.sh" -``` - -The `entrypoint.sh` will then register the cronjob. For example: - -``` -#!/bin/sh -echo "$(( $RANDOM % 59 +0 )) */3 * * * withings-sync --gu garmin-username --gp 'mypassword' -v | tee -a /root/withings-sync.log" > /etc/crontabs/root -crond -f -l 6 -L /dev/stdout -``` - -This will run the job every 3 hours (at a random minute) and writing the output to console and the `/root/withings-sync.log`. +
## Release @@ -379,15 +348,14 @@ The `version` key in `pyproject.toml` will be bumped automatically (Version will ### Docker Image -An image is created magically by GitHub Action and published +Container images are created automagically by GitHub Action and published to [ghcr](https://github.com/jaroslawhartman/withings-sync/pkgs/container/withings-sync). -### Manual release: pypi +### Pypi & GitHub Will be conducted automatically within the Github-Release cycle. -You'll find a script to create and upload a release to pypi here `contrib/do_release.sh`. -It requires [twine](https://pypi.org/project/twine/). This needs the permission on the [pypi-project](https://pypi.org/project/withings-sync/). +The python packages are added to the GitHub releases by a GitHub Action. ## References From 1052bdf16819eb05688bf6ccc816a0c6cea72024 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sun, 15 Dec 2024 23:37:56 +0100 Subject: [PATCH 31/53] README.md: more updates --- README.md | 76 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 829604d..37e0c66 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,11 @@ A tool for synchronisation of the Withings API to: - raw JSON output ### Table of Contents -**[1. Installation Instructions](#1.-installation-instructions)**
-**[2. Usage Instructions](#2.-usage-instructions)**
-**[3. Providing credentials](#3.-providing-credentials)**
+**[1. Installation Instructions](#1-installation-instructions)**
+**[2. Usage Instructions](#2-usage-instructions)**
+**[3. Providing credentials](#3-providing-credentials)**
+**[4. Obtaining Withings Authorization](#4-obtaining-withings-authorization)**
+**[5. Tips](#5-tips)**
## 1. Installation Instructions
@@ -161,7 +163,7 @@ options: ``` ## 3. Providing credentials -### Providing credentials via environment variables +### 3.1 Providing credentials via environment variables You can use the following environment variables for providing the Garmin and/or Trainerroad credentials: @@ -173,7 +175,7 @@ You can use the following environment variables for providing the Garmin and/or The CLI also uses python-dotenv to populate the variables above. Therefore setting the environment variables has the same effect as placing the variables in a `.env` file in the working directory. -### Providing credentials via secrets files +### 3.2 Providing credentials via secrets files You can also populate the following 'secrets' files to provide the Garmin and/or Trainerroad credentials: @@ -184,7 +186,7 @@ You can also populate the following 'secrets' files to provide the Garmin and/or Secrets are useful in an orchestrated container context — see the [Docker Swarm](https://docs.docker.com/engine/swarm/secrets/) or [Rancher](https://rancher.com/docs/rancher/v1.6/en/cattle/secrets/) docs for more information on how to securely inject secrets into a container. -### Order of priority for credentials +### 3.3 Order of priority for credentials In the case of credentials being available via multiple means (e.g. [environment variables](#providing-credentials-via-environment-variables) and [secrets files](#providing-credentials-via-secrets-files)), the order of resolution for determining which credentials to use is as follows, with later methods overriding credentials supplied by an earlier method: @@ -192,10 +194,12 @@ In the case of credentials being available via multiple means (e.g. [environment 2. Read environment variable(s), variables set explicitly take precedence over values from a `.env` file. 3. Use command invocation argument(s) -### Obtaining Withings Authorization Code +## 4. Obtaining Withings Authorization When running for a very first time, you need to obtain Withings authorization: +### 4.1 'normal' shell: + ```bash $ withings-sync -f 2019-01-25 -v Can't read config file config/withings_user.json @@ -213,31 +217,7 @@ You need to visit the URL listed by the script and then - copy Authentification This is one-time activity and it will not be needed to repeat. - -## Tips - -### Garmin SSO errors - -Some users reported errors raised by the Garmin SSO login: - -``` -withings_sync.garmin.APIException: SSO error 401 -``` - -or - -``` -withings_sync.garmin.APIException: SSO error 403 -``` - -These errors are raised if a user tries to login too frequently. -E.g. by running the script every 10 minutes. - -**We recommend to run the script around 8-10 times per day (every 2-3 hours).** - -See also: https://github.com/jaroslawhartman/withings-sync/issues/31 - -### Docker +### 4.2 Docker ``` $ docker pull ghcr.io/jaroslawhartman/withings-sync:latest @@ -280,6 +260,30 @@ JaHa.WAW.PL Garmin Connect User Name: JaHa.WAW.PL Fit file uploaded to Garmin Connect ``` + +## 5. Tips + +### Garmin SSO errors + +Some users reported errors raised by the Garmin SSO login: + +``` +withings_sync.garmin.APIException: SSO error 401 +``` + +or + +``` +withings_sync.garmin.APIException: SSO error 403 +``` + +These errors are raised if a user tries to login too frequently. +E.g. by running the script every 10 minutes. + +**We recommend to run the script around 8-10 times per day (every 2-3 hours).** + +See also: https://github.com/jaroslawhartman/withings-sync/issues/31 + ### Garmin auth You can configure the location of the garmin session file with the variabe `GARMIN_SESSION`. @@ -333,14 +337,14 @@ Example docker-compose: - "withings-sync:/root" - "/etc/localtime:/etc/localtime:ro" environment: - WITHINGS_APP: /root/withings_app.json + WITHINGS_APP: /home/withings-sync/withings_app.json (...) ``` You can then add the app-config in `withings-sync/withings_app.json`
-## Release +## 6. Release Release works via the GitHub [Draft a new Release](https://github.com/jaroslawhartman/withings-sync/releases/new) function. @@ -357,12 +361,12 @@ Will be conducted automatically within the Github-Release cycle. This needs the permission on the [pypi-project](https://pypi.org/project/withings-sync/). The python packages are added to the GitHub releases by a GitHub Action. -## References +## 7. References * SSO authorization derived from https://github.com/cpfair/tapiriik * TrainerRoad API from https://github.com/stuwilkins/python-trainerroad -## Credits / Authors +## 8. Credits / Authors * Based on [withings-garmin](https://github.com/ikasamah/withings-garmin) by Masayuki Hamasaki, improved to support SSO authorization in Garmin Connect 2. * Based on [withings-garmin-v2](https://github.com/jaroslawhartman/withings-garmin-v2) by Jarek Hartman, improved Python 3 compatability, code-style and setuptools packaging, Kubernetes and Docker support. From 458b786ff32fee495cf1db9936e174d052b2b8a1 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sun, 15 Dec 2024 23:44:05 +0100 Subject: [PATCH 32/53] README.md: cleanup --- README.md | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 37e0c66..778d98e 100644 --- a/README.md +++ b/README.md @@ -17,28 +17,23 @@ A tool for synchronisation of the Withings API to: - Trainer Road - raw JSON output -### Table of Contents -**[1. Installation Instructions](#1-installation-instructions)**
-**[2. Usage Instructions](#2-usage-instructions)**
-**[3. Providing credentials](#3-providing-credentials)**
-**[4. Obtaining Withings Authorization](#4-obtaining-withings-authorization)**
-**[5. Tips](#5-tips)**
- ## 1. Installation Instructions +### 1.1 Installation of withings-sync with pip
- Installation of withings-sync with pip + Expand ```bash $ pip install withings-sync ```
+### 1.2 Installation of withings-sync with docker compose (not using cron)
- Installation of withings-sync with docker compose (not using cron) + Expand > This method follows a default approach of utilizing a single container to run one job at a time, then exiting upon completion. It relies on an external scheduler (e.g., cron on the host operating system) to manage job execution. - ### create the following file/directory structure: + - create the following file/directory structure: ```bash . # STACK_PATH ./.env # .env file containing your variables @@ -48,7 +43,7 @@ A tool for synchronisation of the Withings API to: ./config/withings-sync/.garmin_session/ # .garmin_session directory to store oauth tokens ``` - ### contents of an example .env file: + - contents of an example .env file: ```bash TZ=Europe/Kyiv STACK_PATH=/home/youruser/withings-sync @@ -56,7 +51,7 @@ A tool for synchronisation of the Withings API to: GARMIN_PASSWORD="YourPasswordHere" ``` - ### contents of an example docker-compose.yml file: + - contents of an example docker-compose.yml file: ```yaml services: withings-sync: @@ -76,12 +71,13 @@ A tool for synchronisation of the Withings API to: ```
+### 1.3 Installation of withings-sync with docker compose (using supercronic)
- Installation of withings-sync with docker compose (using supercronic) + Expand > This method leverages the included supercronic package for scheduling jobs directly within the container. This eliminates the need for an external scheduler, allowing the container to manage job execution independently. - ### create the following file/directory structure: + - create the following file/directory structure: ```bash . # STACK_PATH ./.env # .env file containing your variables @@ -92,7 +88,7 @@ A tool for synchronisation of the Withings API to: ./config/withings-sync/.garmin_session/ # .garmin_session directory to store oauth tokens ``` - ### contents of an example .env file: + - contents of an example .env file: ```bash TZ=Europe/Kyiv STACK_PATH=/home/youruser/withings-sync @@ -100,14 +96,14 @@ A tool for synchronisation of the Withings API to: GARMIN_PASSWORD="YourPasswordHere" ``` - ### contents of an example entrypoint.sh file: + - contents of an example entrypoint.sh file: ```bash #!/bin/sh echo "$(( $RANDOM % 59 +0 )) */3 * * * poetry run withings-sync --verbose --features BLOOD_PRESSURE" > /home/withings-sync/cronjob supercronic /home/withings-sync/cronjob ``` - ### contents of an example docker-compose.yml file: + - contents of an example docker-compose.yml file: ```yaml services: withings-sync: From 64b3adcab5ddefa090dcbe780c49ee88a894b943 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sun, 15 Dec 2024 23:47:18 +0100 Subject: [PATCH 33/53] README.md: more cleanup --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 778d98e..821f9c7 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,10 @@ A tool for synchronisation of the Withings API to:
### 1.2 Installation of withings-sync with docker compose (not using cron) +> This method follows a default approach of utilizing a single container to run one job at a time, then exiting upon completion. It relies on an external scheduler (e.g., cron on the host operating system) to manage job execution.
Expand - > This method follows a default approach of utilizing a single container to run one job at a time, then exiting upon completion. It relies on an external scheduler (e.g., cron on the host operating system) to manage job execution. - - create the following file/directory structure: ```bash . # STACK_PATH @@ -72,11 +71,10 @@ A tool for synchronisation of the Withings API to:
### 1.3 Installation of withings-sync with docker compose (using supercronic) +> This method leverages the included supercronic package for scheduling jobs directly within the container. This eliminates the need for an external scheduler, allowing the container to manage job execution independently.
Expand - > This method leverages the included supercronic package for scheduling jobs directly within the container. This eliminates the need for an external scheduler, allowing the container to manage job execution independently. - - create the following file/directory structure: ```bash . # STACK_PATH From cb1777373a9443e04766ce0450f3547cbc3d432f Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Tue, 17 Dec 2024 19:58:31 +0100 Subject: [PATCH 34/53] README.md: expand docker-compose commands --- README.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 821f9c7..e9600e7 100644 --- a/README.md +++ b/README.md @@ -19,20 +19,41 @@ A tool for synchronisation of the Withings API to: ## 1. Installation Instructions ### 1.1 Installation of withings-sync with pip +> This method installs the package asuming you have a working python and pip installation.
Expand + 1. installing the package: ```bash $ pip install withings-sync ``` + + 2. obtaining Withings authorization: + When running for a very first time, you need to obtain Withings authorization: + ```bash + $ withings-sync -f 2019-01-25 -v + Can't read config file config/withings_user.json + User interaction needed to get Authentification Code from Withings! + + Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP! + (This is one-time activity) + + https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://wieloryb.uk.to/withings/withings.html& + + Token : + ``` + + You need to visit the URL listed by the script and then - copy Authentification Code back to the prompt. + + This is one-time activity and it will not be needed to repeat.
### 1.2 Installation of withings-sync with docker compose (not using cron) > This method follows a default approach of utilizing a single container to run one job at a time, then exiting upon completion. It relies on an external scheduler (e.g., cron on the host operating system) to manage job execution.
- Expand + Expand to show installation steps. - - create the following file/directory structure: + 1. create the following file/directory structure: ```bash . # STACK_PATH ./.env # .env file containing your variables @@ -42,7 +63,7 @@ A tool for synchronisation of the Withings API to: ./config/withings-sync/.garmin_session/ # .garmin_session directory to store oauth tokens ``` - - contents of an example .env file: + 2. contents of an example .env file: ```bash TZ=Europe/Kyiv STACK_PATH=/home/youruser/withings-sync @@ -50,7 +71,7 @@ A tool for synchronisation of the Withings API to: GARMIN_PASSWORD="YourPasswordHere" ``` - - contents of an example docker-compose.yml file: + 3. contents of an example docker-compose.yml file: ```yaml services: withings-sync: @@ -68,14 +89,53 @@ A tool for synchronisation of the Withings API to: - ${STACK_PATH:?err}/config/withings-sync/.garmin_session:/home/withings-sync/.garmin_session restart: unless-stopped ``` + + 4. obtaining Withings authorization: + ```bash + $ docker compose pull + ``` + + First start to ensure the script can start successfully: + + ```bash + $ docker run -it withings-sync + 2024-12-17 18:54:00,850 - withings - ERROR - Can't read config file /home/withings-sync/.withings_user.json + 2024-12-17 18:54:00,850 - withings - WARNING - User interaction needed to get Authentification Code from Withings! + 2024-12-17 18:54:00,850 - withings - WARNING - + 2024-12-17 18:54:00,850 - withings - WARNING - Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP! + 2024-12-17 18:54:00,851 - withings - WARNING - (This is one-time activity) + 2024-12-17 18:54:00,851 - withings - WARNING - + 2024-12-17 18:54:00,851 - withings - INFO - https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://jaroslawhartman.github.io/withings-sync/contrib/withings.html& + 2024-12-17 18:54:00,851 - withings - INFO - + Token : + Withings: Get Access Token + Withings: Refresh Access Token + Withings: Get Measurements + Measurements received + JaHa.WAW.PL + Garmin Connect User Name: JaHa.WAW.PL + Fit file uploaded to Garmin Connect + ``` + + And for subsequent runs: + + ``` + $ docker start -i withings + Withings: Refresh Access Token + Withings: Get Measurements + Measurements received + JaHa.WAW.PL + Garmin Connect User Name: JaHa.WAW.PL + Fit file uploaded to Garmin Connect + ```
### 1.3 Installation of withings-sync with docker compose (using supercronic) > This method leverages the included supercronic package for scheduling jobs directly within the container. This eliminates the need for an external scheduler, allowing the container to manage job execution independently.
- Expand + Expand to show installation steps. - - create the following file/directory structure: + 1. create the following file/directory structure: ```bash . # STACK_PATH ./.env # .env file containing your variables @@ -86,7 +146,7 @@ A tool for synchronisation of the Withings API to: ./config/withings-sync/.garmin_session/ # .garmin_session directory to store oauth tokens ``` - - contents of an example .env file: + 2. contents of an example .env file: ```bash TZ=Europe/Kyiv STACK_PATH=/home/youruser/withings-sync @@ -94,14 +154,14 @@ A tool for synchronisation of the Withings API to: GARMIN_PASSWORD="YourPasswordHere" ``` - - contents of an example entrypoint.sh file: + 3. contents of an example entrypoint.sh file: ```bash #!/bin/sh echo "$(( $RANDOM % 59 +0 )) */3 * * * poetry run withings-sync --verbose --features BLOOD_PRESSURE" > /home/withings-sync/cronjob supercronic /home/withings-sync/cronjob ``` - - contents of an example docker-compose.yml file: + 4. contents of an example docker-compose.yml file: ```yaml services: withings-sync: @@ -126,7 +186,9 @@ A tool for synchronisation of the Withings API to: ## 2. Usage Instructions ``` -usage: withings-sync [-h] [--version] [--garmin-username GARMIN_USERNAME] [--garmin-password GARMIN_PASSWORD] [--trainerroad-username TRAINERROAD_USERNAME] [--trainerroad-password TRAINERROAD_PASSWORD] [--fromdate DATE] [--todate DATE] [--to-fit] [--to-json] [--output BASENAME] [--no-upload] +usage: withings-sync [-h] [--version] [--garmin-username GARMIN_USERNAME] [--garmin-password GARMIN_PASSWORD] + [--trainerroad-username TRAINERROAD_USERNAME] [--trainerroad-password TRAINERROAD_PASSWORD] + [--fromdate DATE] [--todate DATE] [--to-fit] [--to-json] [--output BASENAME] [--no-upload] [--features BLOOD_PRESSURE [BLOOD_PRESSURE ...]] [--verbose] A tool for synchronisation of Withings (ex. Nokia Health Body) to Garmin Connect and Trainer Road or to provide a json string. From e158679b8744cb8fb76781149fe378a72361f93b Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:44:52 +0100 Subject: [PATCH 35/53] README.md: work on expanding the pip installation & running methods --- README.md | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e9600e7..70cad97 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,9 @@ A tool for synchronisation of the Withings API to: ## 1. Installation Instructions ### 1.1 Installation of withings-sync with pip -> This method installs the package asuming you have a working python and pip installation. +> This method installs the package asuming you have a working python and pip installation. It relies on an external scheduler (e.g., cron on the host operating system) to manage job execution.
- Expand + Expand to show installation steps. 1. installing the package: ```bash @@ -31,21 +31,34 @@ A tool for synchronisation of the Withings API to: 2. obtaining Withings authorization: When running for a very first time, you need to obtain Withings authorization: ```bash - $ withings-sync -f 2019-01-25 -v - Can't read config file config/withings_user.json - User interaction needed to get Authentification Code from Withings! - - Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP! - (This is one-time activity) - - https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://wieloryb.uk.to/withings/withings.html& - - Token : + $ withings-sync + 2024-12-01 01:29:02,601 - withings - ERROR - Can't read config file /home/youruser/.withings_user.json + 2024-12-01 01:29:02,602 - withings - WARNING - User interaction needed to get Authentification Code from Withings! + 2024-12-01 01:29:02,603 - withings - WARNING - + 2024-12-01 01:29:02,603 - withings - WARNING - Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP! + 2024-12-01 01:29:02,603 - withings - WARNING - (This is one-time activity) + 2024-12-01 01:29:02,604 - withings - WARNING - + 2024-12-01 01:29:02,604 - withings - INFO - https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://jaroslawhartman.github.io/withings-sync/contrib/withings.html& + 2024-12-01 01:29:02,604 - withings - INFO - + + Token : + + 2024-12-01 01:31:07,832 - withings - INFO - Get Access Token + 2024-12-01 01:31:08,313 - withings - INFO - Refresh Access Token + 2024-12-01 01:31:08,771 - root - INFO - Fetching measurements from 2024-12-18 00:00 to 2024-12-18 23:59 + 2024-12-01 01:31:09,406 - withings - INFO - Get Measurements + 2024-12-01 01:31:09,856 - root - ERROR - No measurements to upload for date or period specified ``` - You need to visit the URL listed by the script and then - copy Authentification Code back to the prompt. - This is one-time activity and it will not be needed to repeat. + This is one-time activity and it will not be needed to repeat. Subsequent runs will use the saved access tokens in ~/.withings_user.json + ```bash + $ withings-sync + 2024-12-01 01:37:41,500 - withings - INFO - Refresh Access Token + 2024-12-01 01:37:41,954 - root - INFO - Fetching measurements from 2024-12-18 00:00 to 2024-12-18 23:59 + 2024-12-01 01:37:42,563 - withings - INFO - Get Measurements + 2024-12-01 01:37:43,069 - root - ERROR - No measurements to upload for date or period specified + ```
### 1.2 Installation of withings-sync with docker compose (not using cron) @@ -66,7 +79,7 @@ A tool for synchronisation of the Withings API to: 2. contents of an example .env file: ```bash TZ=Europe/Kyiv - STACK_PATH=/home/youruser/withings-sync + STACK_PATH=/home/your_user/your_stack_name GARMIN_USERNAME="your.name@domain.ext" GARMIN_PASSWORD="YourPasswordHere" ``` @@ -119,7 +132,7 @@ A tool for synchronisation of the Withings API to: And for subsequent runs: - ``` + ```bash $ docker start -i withings Withings: Refresh Access Token Withings: Get Measurements From 028045f0ba8a20165d40d7c86ca6373b857b1500 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 18 Dec 2024 12:05:05 +0100 Subject: [PATCH 36/53] README.md: more improvements to the installation section --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 70cad97..ac0878c 100644 --- a/README.md +++ b/README.md @@ -45,17 +45,17 @@ A tool for synchronisation of the Withings API to: 2024-12-01 01:31:07,832 - withings - INFO - Get Access Token 2024-12-01 01:31:08,313 - withings - INFO - Refresh Access Token - 2024-12-01 01:31:08,771 - root - INFO - Fetching measurements from 2024-12-18 00:00 to 2024-12-18 23:59 + 2024-12-01 01:31:08,771 - root - INFO - Fetching measurements from 2024-12-01 00:00 to 2024-12-01 23:59 2024-12-01 01:31:09,406 - withings - INFO - Get Measurements 2024-12-01 01:31:09,856 - root - ERROR - No measurements to upload for date or period specified ``` You need to visit the URL listed by the script and then - copy Authentification Code back to the prompt. - This is one-time activity and it will not be needed to repeat. Subsequent runs will use the saved access tokens in ~/.withings_user.json + This is one-time activity and it will not be needed to repeat. Subsequent runs will use the saved access tokens in `~/.withings_user.json` ```bash $ withings-sync 2024-12-01 01:37:41,500 - withings - INFO - Refresh Access Token - 2024-12-01 01:37:41,954 - root - INFO - Fetching measurements from 2024-12-18 00:00 to 2024-12-18 23:59 + 2024-12-01 01:37:41,954 - root - INFO - Fetching measurements from 2024-01-18 00:00 to 2024-01-18 23:59 2024-12-01 01:37:42,563 - withings - INFO - Get Measurements 2024-12-01 01:37:43,069 - root - ERROR - No measurements to upload for date or period specified ``` @@ -76,7 +76,7 @@ A tool for synchronisation of the Withings API to: ./config/withings-sync/.garmin_session/ # .garmin_session directory to store oauth tokens ``` - 2. contents of an example .env file: + 2. contents of an example `.env` file: ```bash TZ=Europe/Kyiv STACK_PATH=/home/your_user/your_stack_name @@ -84,7 +84,7 @@ A tool for synchronisation of the Withings API to: GARMIN_PASSWORD="YourPasswordHere" ``` - 3. contents of an example docker-compose.yml file: + 3. contents of an example `docker-compose.yml` file: ```yaml services: withings-sync: @@ -159,7 +159,7 @@ A tool for synchronisation of the Withings API to: ./config/withings-sync/.garmin_session/ # .garmin_session directory to store oauth tokens ``` - 2. contents of an example .env file: + 2. contents of an example `.env` file: ```bash TZ=Europe/Kyiv STACK_PATH=/home/youruser/withings-sync @@ -167,14 +167,14 @@ A tool for synchronisation of the Withings API to: GARMIN_PASSWORD="YourPasswordHere" ``` - 3. contents of an example entrypoint.sh file: + 3. contents of an example `entrypoint.sh` file: ```bash #!/bin/sh echo "$(( $RANDOM % 59 +0 )) */3 * * * poetry run withings-sync --verbose --features BLOOD_PRESSURE" > /home/withings-sync/cronjob supercronic /home/withings-sync/cronjob ``` - 4. contents of an example docker-compose.yml file: + 4. contents of an example `docker-compose.yml` file: ```yaml services: withings-sync: From 7b13b8198f45ec7dd8a8488b455dde1cb5e96bf9 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:06:23 +0100 Subject: [PATCH 37/53] README.md: expand docker instructions --- README.md | 92 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index ac0878c..943b59a 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ A tool for synchronisation of the Withings API to: ```bash $ withings-sync 2024-12-01 01:37:41,500 - withings - INFO - Refresh Access Token - 2024-12-01 01:37:41,954 - root - INFO - Fetching measurements from 2024-01-18 00:00 to 2024-01-18 23:59 + 2024-12-01 01:37:41,954 - root - INFO - Fetching measurements from 2024-12-01 00:00 to 2024-12-01 23:59 2024-12-01 01:37:42,563 - withings - INFO - Get Measurements 2024-12-01 01:37:43,069 - root - ERROR - No measurements to upload for date or period specified ``` @@ -73,6 +73,7 @@ A tool for synchronisation of the Withings API to: ./docker-compose.yml # docker-compose file ./config/ # config directory ./config/withings-sync/ # config directory for withings-sync + ./config/withings-sync/.withings_user.json # .withings_user.json file to store access tokens ./config/withings-sync/.garmin_session/ # .garmin_session directory to store oauth tokens ``` @@ -87,59 +88,70 @@ A tool for synchronisation of the Withings API to: 3. contents of an example `docker-compose.yml` file: ```yaml services: - withings-sync: - image: ghcr.io/jaroslawhartman/withings-sync:latest - container_name: withings-sync - stdin_open: true # docker run -i - tty: true # docker run -t - environment: - - TZ=${TZ:?err} - - GARMIN_USERNAME=${GARMIN_USERNAME:?err} - - GARMIN_PASSWORD=${GARMIN_PASSWORD:?err} - volumes: - - /etc/localtime:/etc/localtime:ro - - ${STACK_PATH:?err}/config/withings-sync/.withings_user.json:/home/withings-sync/.withings_user.json - - ${STACK_PATH:?err}/config/withings-sync/.garmin_session:/home/withings-sync/.garmin_session - restart: unless-stopped + withings-sync: + image: ghcr.io/jaroslawhartman/withings-sync:latest + container_name: withings-sync + stdin_open: true # docker run -i + tty: true # docker run -t + environment: + - TZ=${TZ:?err} + - GARMIN_USERNAME=${GARMIN_USERNAME:?err} + - GARMIN_PASSWORD=${GARMIN_PASSWORD:?err} + volumes: + - /etc/localtime:/etc/localtime:ro + - ${STACK_PATH:?err}/config/withings-sync/.withings_user.json:/home/withings-sync/.withings_user.json + - ${STACK_PATH:?err}/config/withings-sync/.garmin_session:/home/withings-sync/.garmin_session + restart: unless-stopped ``` 4. obtaining Withings authorization: ```bash $ docker compose pull + [+] Pulling 13/13 + ✔ withings-sync Pulled 56.0s + ✔ cb8611c9fe51 Pull complete 4.2s + ✔ 52e189a1282f Pull complete 6.4s + ✔ 95e68cb0cebc Pull complete 19.0s + ✔ c3ba8bc06a4d Pull complete 19.3s + ✔ fc2b9c85008a Pull complete 21.6s + ✔ 0376fca350d9 Pull complete 21.7s + ✔ 4f4fb700ef54 Pull complete 21.9s + ✔ c749d618f51d Pull complete 43.0s + ✔ 86d00088bd8d Pull complete 43.2s + ✔ 98dec7b84387 Pull complete 52.8s + ✔ 8825309bd8c2 Pull complete 53.1s + ✔ 7747652082d6 Pull complete ``` First start to ensure the script can start successfully: ```bash - $ docker run -it withings-sync - 2024-12-17 18:54:00,850 - withings - ERROR - Can't read config file /home/withings-sync/.withings_user.json - 2024-12-17 18:54:00,850 - withings - WARNING - User interaction needed to get Authentification Code from Withings! - 2024-12-17 18:54:00,850 - withings - WARNING - - 2024-12-17 18:54:00,850 - withings - WARNING - Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP! - 2024-12-17 18:54:00,851 - withings - WARNING - (This is one-time activity) - 2024-12-17 18:54:00,851 - withings - WARNING - - 2024-12-17 18:54:00,851 - withings - INFO - https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://jaroslawhartman.github.io/withings-sync/contrib/withings.html& - 2024-12-17 18:54:00,851 - withings - INFO - - Token : - Withings: Get Access Token - Withings: Refresh Access Token - Withings: Get Measurements - Measurements received - JaHa.WAW.PL - Garmin Connect User Name: JaHa.WAW.PL - Fit file uploaded to Garmin Connect + $ docker compose run -it --remove-orphans --entrypoint "poetry run withings-sync" withings-sync + [+] Creating 1/0 + 2024-12-01 01:29:02,601 - withings - ERROR - Can't read config file /home/youruser/.withings_user.json + 2024-12-01 01:29:02,602 - withings - WARNING - User interaction needed to get Authentification Code from Withings! + 2024-12-01 01:29:02,603 - withings - WARNING - + 2024-12-01 01:29:02,603 - withings - WARNING - Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP! + 2024-12-01 01:29:02,603 - withings - WARNING - (This is one-time activity) + 2024-12-01 01:29:02,604 - withings - WARNING - + 2024-12-01 01:29:02,604 - withings - INFO - https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://jaroslawhartman.github.io/withings-sync/contrib/withings.html& + 2024-12-01 01:29:02,604 - withings - INFO - + + Token : + + 2024-12-01 01:31:07,832 - withings - INFO - Get Access Token + 2024-12-01 01:31:08,313 - withings - INFO - Refresh Access Token + 2024-12-01 01:31:08,771 - root - INFO - Fetching measurements from 2024-12-01 00:00 to 2024-12-01 23:59 + 2024-12-01 01:31:09,406 - withings - INFO - Get Measurements + 2024-12-01 01:31:09,856 - root - ERROR - No measurements to upload for date or period specified ``` And for subsequent runs: ```bash - $ docker start -i withings - Withings: Refresh Access Token - Withings: Get Measurements - Measurements received - JaHa.WAW.PL - Garmin Connect User Name: JaHa.WAW.PL - Fit file uploaded to Garmin Connect + $ docker compose up -d --remove-orphans + [+] Running 1/1 + ✔ Container withings-sync Started ```
@@ -191,7 +203,7 @@ A tool for synchronisation of the Withings API to: - ${STACK_PATH:?err}/config/withings-sync/.withings_user.json:/home/withings-sync/.withings_user.json - ${STACK_PATH:?err}/config/withings-sync/.garmin_session:/home/withings-sync/.garmin_session - ${STACK_PATH:?err}/config/withings-sync/entrypoint.sh:/home/withings-sync/entrypoint.sh - entrypoint: "/home/withings-sync/entrypoint.sh" + entrypoint: "sh /home/withings-sync/entrypoint.sh" restart: unless-stopped ```
From 1b48102100c71e1aaa1fab7e21b4ff700f7f2152 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:31:27 +0100 Subject: [PATCH 38/53] README.md: expand even more on docker compose install & execution methods --- README.md | 122 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 101 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 943b59a..753fd9c 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ A tool for synchronisation of the Withings API to: When running for a very first time, you need to obtain Withings authorization: ```bash $ withings-sync - 2024-12-01 01:29:02,601 - withings - ERROR - Can't read config file /home/youruser/.withings_user.json + 2024-12-01 01:29:02,601 - withings - ERROR - Can\'t read config file /home/youruser/.withings_user.json 2024-12-01 01:29:02,602 - withings - WARNING - User interaction needed to get Authentification Code from Withings! 2024-12-01 01:29:02,603 - withings - WARNING - 2024-12-01 01:29:02,603 - withings - WARNING - Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP! @@ -51,7 +51,10 @@ A tool for synchronisation of the Withings API to: ``` You need to visit the URL listed by the script and then - copy Authentification Code back to the prompt. - This is one-time activity and it will not be needed to repeat. Subsequent runs will use the saved access tokens in `~/.withings_user.json` + This is one-time activity and it will not be needed to repeat. + + 3. running the application: + Subsequent runs will use the saved access tokens in `~/.withings_user.json` ```bash $ withings-sync 2024-12-01 01:37:41,500 - withings - INFO - Refresh Access Token @@ -108,27 +111,28 @@ A tool for synchronisation of the Withings API to: ```bash $ docker compose pull [+] Pulling 13/13 - ✔ withings-sync Pulled 56.0s - ✔ cb8611c9fe51 Pull complete 4.2s - ✔ 52e189a1282f Pull complete 6.4s - ✔ 95e68cb0cebc Pull complete 19.0s - ✔ c3ba8bc06a4d Pull complete 19.3s - ✔ fc2b9c85008a Pull complete 21.6s - ✔ 0376fca350d9 Pull complete 21.7s - ✔ 4f4fb700ef54 Pull complete 21.9s - ✔ c749d618f51d Pull complete 43.0s - ✔ 86d00088bd8d Pull complete 43.2s - ✔ 98dec7b84387 Pull complete 52.8s - ✔ 8825309bd8c2 Pull complete 53.1s - ✔ 7747652082d6 Pull complete + ✔ withings-sync Pulled 56.0s + ✔ cb8611c9fe51 Pull complete 4.2s + ✔ 52e189a1282f Pull complete 6.4s + ✔ 95e68cb0cebc Pull complete 19.0s + ✔ c3ba8bc06a4d Pull complete 19.3s + ✔ fc2b9c85008a Pull complete 21.6s + ✔ 0376fca350d9 Pull complete 21.7s + ✔ 4f4fb700ef54 Pull complete 21.9s + ✔ c749d618f51d Pull complete 43.0s + ✔ 86d00088bd8d Pull complete 43.2s + ✔ 98dec7b84387 Pull complete 52.8s + ✔ 8825309bd8c2 Pull complete 53.1s + ✔ 7747652082d6 Pull complete 53.3s ``` First start to ensure the script can start successfully: ```bash $ docker compose run -it --remove-orphans --entrypoint "poetry run withings-sync" withings-sync - [+] Creating 1/0 - 2024-12-01 01:29:02,601 - withings - ERROR - Can't read config file /home/youruser/.withings_user.json + [+] Creating 1/1 + ✔ Network stack_default Created 0.5s + 2024-12-01 01:29:02,601 - withings - ERROR - Can\'t read config file /home/youruser/.withings_user.json 2024-12-01 01:29:02,602 - withings - WARNING - User interaction needed to get Authentification Code from Withings! 2024-12-01 01:29:02,603 - withings - WARNING - 2024-12-01 01:29:02,603 - withings - WARNING - Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP! @@ -145,14 +149,29 @@ A tool for synchronisation of the Withings API to: 2024-12-01 01:31:09,406 - withings - INFO - Get Measurements 2024-12-01 01:31:09,856 - root - ERROR - No measurements to upload for date or period specified ``` + You need to visit the URL listed by the script and then - copy Authentification Code back to the prompt. - And for subsequent runs: + This is one-time activity and it will not be needed to repeat. + + 5. running the container: + Subsequent runs will use the saved access tokens in `~/.withings_user.json` ```bash - $ docker compose up -d --remove-orphans - [+] Running 1/1 - ✔ Container withings-sync Started + $ docker compose run -it --remove-orphans withings-sync 0.5s + [+] Creating 1/1 + ✔ Container stack-withings-sync-run-3f24bc7ec7f9 Removed + 2024-12-01 01:37:41,500 - withings - INFO - Refresh Access Token + 2024-12-01 01:37:41,954 - root - INFO - Fetching measurements from 2024-12-01 00:00 to 2024-12-01 23:59 + 2024-12-01 01:37:42,563 - withings - INFO - Get Measurements + 2024-12-01 01:37:43,069 - root - ERROR - No measurements to upload for date or period specified + ``` + + 6. updating to a newer version: + ```bash + $ docker compose pull + $ docker compose run -it --remove-orphans withings-sync ``` +
### 1.3 Installation of withings-sync with docker compose (using supercronic) @@ -206,6 +225,67 @@ A tool for synchronisation of the Withings API to: entrypoint: "sh /home/withings-sync/entrypoint.sh" restart: unless-stopped ``` + + 5. obtaining Withings authorization: + ```bash + $ docker compose pull + [+] Pulling 13/13 + ✔ withings-sync Pulled 56.0s + ✔ cb8611c9fe51 Pull complete 4.2s + ✔ 52e189a1282f Pull complete 6.4s + ✔ 95e68cb0cebc Pull complete 19.0s + ✔ c3ba8bc06a4d Pull complete 19.3s + ✔ fc2b9c85008a Pull complete 21.6s + ✔ 0376fca350d9 Pull complete 21.7s + ✔ 4f4fb700ef54 Pull complete 21.9s + ✔ c749d618f51d Pull complete 43.0s + ✔ 86d00088bd8d Pull complete 43.2s + ✔ 98dec7b84387 Pull complete 52.8s + ✔ 8825309bd8c2 Pull complete 53.1s + ✔ 7747652082d6 Pull complete 53.3s + ``` + + First start to ensure the container can start successfully: + + ```bash + $ docker compose run -it --remove-orphans --entrypoint "poetry run withings-sync" withings-sync + [+] Creating 1/1 + ✔ Network stack_default Created 0.5s + 2024-12-01 01:29:02,601 - withings - ERROR - Can\'t read config file /home/youruser/.withings_user.json + 2024-12-01 01:29:02,602 - withings - WARNING - User interaction needed to get Authentification Code from Withings! + 2024-12-01 01:29:02,603 - withings - WARNING - + 2024-12-01 01:29:02,603 - withings - WARNING - Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP! + 2024-12-01 01:29:02,603 - withings - WARNING - (This is one-time activity) + 2024-12-01 01:29:02,604 - withings - WARNING - + 2024-12-01 01:29:02,604 - withings - INFO - https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://jaroslawhartman.github.io/withings-sync/contrib/withings.html& + 2024-12-01 01:29:02,604 - withings - INFO - + + Token : + + 2024-12-01 01:31:07,832 - withings - INFO - Get Access Token + 2024-12-01 01:31:08,313 - withings - INFO - Refresh Access Token + 2024-12-01 01:31:08,771 - root - INFO - Fetching measurements from 2024-12-01 00:00 to 2024-12-01 23:59 + 2024-12-01 01:31:09,406 - withings - INFO - Get Measurements + 2024-12-01 01:31:09,856 - root - ERROR - No measurements to upload for date or period specified + ``` + + 6. running the container: + And for subsequent runs we start docker compose and let the container run in the background. + Subsequent runs will use the saved access tokens in `~/.withings_user.json` + + ```bash + $ docker compose up -d --remove-orphans + [+] Running 1/1 + ✔ Container withings-sync Started 1.5s + ``` + + 7. updating to a newer version: + ```bash + $ docker compose pull + $ docker compose down + $ docker compose up -d --remove-orphans + $ docker image prune -f + ``` ## 2. Usage Instructions From b1cc2e3326a53ad2ca732706063567954933a019 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:33:18 +0100 Subject: [PATCH 39/53] README.md: formatting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 753fd9c..e0ecd73 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ A tool for synchronisation of the Withings API to: ✔ 86d00088bd8d Pull complete 43.2s ✔ 98dec7b84387 Pull complete 52.8s ✔ 8825309bd8c2 Pull complete 53.1s - ✔ 7747652082d6 Pull complete 53.3s + ✔ 7747652082d6 Pull complete 53.3s ``` First start to ensure the script can start successfully: From 316c9441ac43c1515309f659e25587bef21dd34b Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:41:54 +0100 Subject: [PATCH 40/53] README.md: add normal docker instructions --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e0ecd73..807f43a 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,6 @@ A tool for synchronisation of the Withings API to: $ docker compose pull $ docker compose run -it --remove-orphans withings-sync ``` - ### 1.3 Installation of withings-sync with docker compose (using supercronic) @@ -288,6 +287,54 @@ A tool for synchronisation of the Withings API to: ``` +### 1.4 Installation of withings-sync with docker (not compose) +> This method follows a default approach of utilizing a single container to run one job at a time, then exiting upon completion. It relies on an external scheduler (e.g., cron on the host operating system) to manage job execution. +
+ Expand to show installation steps. + +```bash +$ docker pull ghcr.io/jaroslawhartman/withings-sync:latest +``` + +First start to ensure the script can start successfully: + + +Obtaining Withings authorisation: + +```bash +$ docker run -v .withings_user.json:/home/withings-sync/.withings_user.json -v .garmin_session:/home/withings-sync/.garmin_session --interactive --tty --name withings-sync ghcr.io/jaroslawhartman/withings-sync:latest --garmin-username= --garmin-password= + +Can't read config file config/withings_user.json +User interaction needed to get Authentification Code from Withings! + +Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP! +(This is one-time activity) + +https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://wieloryb.uk.to/withings/withings.html& + +Token : +Withings: Get Access Token +Withings: Refresh Access Token +Withings: Get Measurements + Measurements received +JaHa.WAW.PL +Garmin Connect User Name: JaHa.WAW.PL +Fit file uploaded to Garmin Connect +``` + +And for subsequent runs: + +```bash +$ docker start -i withings-sync +Withings: Refresh Access Token +Withings: Get Measurements + Measurements received +JaHa.WAW.PL +Garmin Connect User Name: JaHa.WAW.PL +Fit file uploaded to Garmin Connect +``` +
+ ## 2. Usage Instructions ``` From bc90031dd3cc385a7c506e6b047246fc99d0e729 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 18 Dec 2024 22:25:37 +0100 Subject: [PATCH 41/53] README.md: moved Obtaining Authorization to the relevant install sections --- README.md | 78 +++++-------------------------------------------------- 1 file changed, 6 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 807f43a..9ea4927 100644 --- a/README.md +++ b/README.md @@ -402,76 +402,9 @@ In the case of credentials being available via multiple means (e.g. [environment 2. Read environment variable(s), variables set explicitly take precedence over values from a `.env` file. 3. Use command invocation argument(s) -## 4. Obtaining Withings Authorization +## 4. Tips -When running for a very first time, you need to obtain Withings authorization: - -### 4.1 'normal' shell: - -```bash -$ withings-sync -f 2019-01-25 -v -Can't read config file config/withings_user.json -User interaction needed to get Authentification Code from Withings! - -Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP! -(This is one-time activity) - -https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://wieloryb.uk.to/withings/withings.html& - -Token : -``` - -You need to visit the URL listed by the script and then - copy Authentification Code back to the prompt. - -This is one-time activity and it will not be needed to repeat. - -### 4.2 Docker - -``` -$ docker pull ghcr.io/jaroslawhartman/withings-sync:latest -``` - -First start to ensure the script can start successfully: - - -Obtaining Withings authorisation: - -``` -$ docker run -v $HOME:/root --interactive --tty --name withings ghcr.io/jaroslawhartman/withings-sync:latest --garmin-username= --garmin-password= - -Can't read config file config/withings_user.json -User interaction needed to get Authentification Code from Withings! - -Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP! -(This is one-time activity) - -https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://wieloryb.uk.to/withings/withings.html& - -Token : -Withings: Get Access Token -Withings: Refresh Access Token -Withings: Get Measurements - Measurements received -JaHa.WAW.PL -Garmin Connect User Name: JaHa.WAW.PL -Fit file uploaded to Garmin Connect -``` - -And for subsequent runs: - -``` -$ docker start -i withings -Withings: Refresh Access Token -Withings: Get Measurements - Measurements received -JaHa.WAW.PL -Garmin Connect User Name: JaHa.WAW.PL -Fit file uploaded to Garmin Connect -``` - -## 5. Tips - -### Garmin SSO errors +### 4.1 Garmin SSO errors Some users reported errors raised by the Garmin SSO login: @@ -492,11 +425,11 @@ E.g. by running the script every 10 minutes. See also: https://github.com/jaroslawhartman/withings-sync/issues/31 -### Garmin auth +### 4.2 Garmin auth You can configure the location of the garmin session file with the variabe `GARMIN_SESSION`. -### Run a periodic Kubernetes job +### 4.3 Run a periodic Kubernetes job Edit the credentials in `contrib/k8s-job.yaml` and run: @@ -504,7 +437,8 @@ Edit the credentials in `contrib/k8s-job.yaml` and run: $ kubectl apply -f contrib/k8s-job.yaml ``` -### For advanced users - registering own Withings application +## 5 For advanced users - registering own Withings application +> Instead of using the provided Withings application tokens you can register your own app with Withings and use that one instead.
If you are not sure you need this, you most likely won't. From 71bbb592bb082829ab453581a0941d435ea5020d Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Wed, 18 Dec 2024 22:57:02 +0100 Subject: [PATCH 42/53] README.md: add .withings_user.json to docker-compose section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9ea4927..793e990 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ A tool for synchronisation of the Withings API to: ./config/ # config directory ./config/withings-sync/ # config directory for withings-sync ./config/withings-sync/entrypoint.sh # entrypoint.sh file containing your + ./config/withings-sync/.withings_user.json # .withings_user.json file to store access tokens ./config/withings-sync/.garmin_session/ # .garmin_session directory to store oauth tokens ``` From b9343ec075d445436850c284b2b9c50468a1def3 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Fri, 20 Dec 2024 20:49:23 +0100 Subject: [PATCH 43/53] update to latest packages --- poetry.lock | 249 ++++++++++++++++++++++++++----------------------- pyproject.toml | 14 +-- 2 files changed, 138 insertions(+), 125 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6fb4bff..b880fe8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "astroid" -version = "3.3.5" +version = "3.3.6" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.9.0" files = [ - {file = "astroid-3.3.5-py3-none-any.whl", hash = "sha256:a9d1c946ada25098d790e079ba2a1b112157278f3fb7e718ae6a9252f5835dc8"}, - {file = "astroid-3.3.5.tar.gz", hash = "sha256:5cfc40ae9f68311075d27ef68a4841bdc5cc7f6cf86671b49f00607d30188e2d"}, + {file = "astroid-3.3.6-py3-none-any.whl", hash = "sha256:db676dc4f3ae6bfe31cda227dc60e03438378d7a896aec57422c95634e8d722f"}, + {file = "astroid-3.3.6.tar.gz", hash = "sha256:6aaea045f938c735ead292204afdb977a36e989522b7833ef6fea94de743f442"}, ] [[package]] @@ -68,13 +68,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2024.8.30" +version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, + {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, + {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, ] [[package]] @@ -233,19 +233,24 @@ profile = ["gprof2dot (>=2022.7.29)"] [[package]] name = "garth" -version = "0.4.46" +version = "0.5.2" description = "Garmin SSO auth + Connect client" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" files = [ - {file = "garth-0.4.46-py3-none-any.whl", hash = "sha256:c209192bac793f42764f0f06131dc503f74783d41af326f29187a1f6394dc5ff"}, - {file = "garth-0.4.46.tar.gz", hash = "sha256:5ae19e67612083285b10321b8e0e1f7c815a8f60f21e2f13be8c21a22e64d8eb"}, + {file = "garth-0.5.2-py3-none-any.whl", hash = "sha256:bd0297ef82afdf06e10ed0c271c9270bbf6bec832d62d108a325a8ac4723d74a"}, + {file = "garth-0.5.2.tar.gz", hash = "sha256:594acafe27989daa3ffbc8460caf063802359c6b1f40c98ffd3b21f69adc6e09"}, ] [package.dependencies] pydantic = ">=1.10.12,<3.0.0" requests = ">=2.0.0,<3.0.0" -requests-oauthlib = ">=1.3.1,<2.0.0" +requests-oauthlib = ">=1.3.1,<3.0.0" + +[package.extras] +dev = ["ipdb", "ipykernel", "ipython", "matplotlib", "pandas"] +linting = ["mypy", "ruff", "types-requests"] +testing = ["coverage", "pytest", "pytest-vcr"] [[package]] name = "idna" @@ -526,22 +531,19 @@ type = ["mypy (>=1.11.2)"] [[package]] name = "pydantic" -version = "2.9.2" +version = "2.10.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, - {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, + {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, + {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.23.4" -typing-extensions = [ - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, -] +pydantic-core = "2.27.2" +typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] @@ -549,100 +551,111 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.23.4" +version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, - {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, - {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, - {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, - {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, - {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, - {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, - {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, - {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, - {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, - {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, - {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, - {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, - {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, + {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, ] [package.dependencies] @@ -650,17 +663,17 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pylint" -version = "3.3.1" +version = "3.3.2" description = "python code static checker" optional = false python-versions = ">=3.9.0" files = [ - {file = "pylint-3.3.1-py3-none-any.whl", hash = "sha256:2f846a466dd023513240bc140ad2dd73bfc080a5d85a710afdb728c420a5a2b9"}, - {file = "pylint-3.3.1.tar.gz", hash = "sha256:9f3dcc87b1203e612b78d91a896407787e708b3f189b5fa0b307712d49ff0c6e"}, + {file = "pylint-3.3.2-py3-none-any.whl", hash = "sha256:77f068c287d49b8683cd7c6e624243c74f92890f767f106ffa1ddf3c0a54cb7a"}, + {file = "pylint-3.3.2.tar.gz", hash = "sha256:9ec054ec992cd05ad30a6df1676229739a73f8feeabf3912c995d17601052b01"}, ] [package.dependencies] -astroid = ">=3.3.4,<=3.4.0-dev0" +astroid = ">=3.3.5,<=3.4.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = {version = ">=0.3.7", markers = "python_version >= \"3.12\""} isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" @@ -709,13 +722,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-oauthlib" -version = "1.3.1" +version = "2.0.0" description = "OAuthlib authentication support for Requests." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.4" files = [ - {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, - {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, + {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, + {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, ] [package.dependencies] @@ -767,4 +780,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "222c59bc6d0b5b3221673e17663b51e77c2e4cbe6d48748fb671fb33d072fe7f" +content-hash = "dcf0a8023a0a9b98774c676a481ab2d608b13658d681954780db15160c577304" diff --git a/pyproject.toml b/pyproject.toml index 5b3cc23..b0fd31f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,15 +11,15 @@ packages = [{include = "withings_sync"}] [tool.poetry.dependencies] python = "^3.12" -garth = "^0.4.46" -requests = "^2.32.3" -lxml = "^5.3.0" -python-dotenv = "^1.0.1" -importlib-resources = "^6.4.5" +garth = ">=0.5.0" +requests = ">=2.32.3" +lxml = ">=5.3.0" +python-dotenv = ">=1.0.1" +importlib-resources = ">=6.4.5" [tool.poetry.group.dev.dependencies] -black = "^24.10.0" -pylint = "^3.3.1" +black = ">=24.10.0" +pylint = ">=3.3.1" [tool.poetry.scripts] withings-sync = "withings_sync.sync:main" From 1d174b18104329dbad95125ec8d8f7639bda32fc Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Fri, 20 Dec 2024 20:51:07 +0100 Subject: [PATCH 44/53] README.md: fix some issues in the readme that prevent proper auth --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 793e990..a9ecbbe 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,6 @@ A tool for synchronisation of the Withings API to: ./config/ # config directory ./config/withings-sync/ # config directory for withings-sync ./config/withings-sync/.withings_user.json # .withings_user.json file to store access tokens - ./config/withings-sync/.garmin_session/ # .garmin_session directory to store oauth tokens ``` 2. contents of an example `.env` file: @@ -103,7 +102,6 @@ A tool for synchronisation of the Withings API to: volumes: - /etc/localtime:/etc/localtime:ro - ${STACK_PATH:?err}/config/withings-sync/.withings_user.json:/home/withings-sync/.withings_user.json - - ${STACK_PATH:?err}/config/withings-sync/.garmin_session:/home/withings-sync/.garmin_session restart: unless-stopped ``` @@ -187,7 +185,6 @@ A tool for synchronisation of the Withings API to: ./config/withings-sync/ # config directory for withings-sync ./config/withings-sync/entrypoint.sh # entrypoint.sh file containing your ./config/withings-sync/.withings_user.json # .withings_user.json file to store access tokens - ./config/withings-sync/.garmin_session/ # .garmin_session directory to store oauth tokens ``` 2. contents of an example `.env` file: @@ -220,7 +217,6 @@ A tool for synchronisation of the Withings API to: volumes: - /etc/localtime:/etc/localtime:ro - ${STACK_PATH:?err}/config/withings-sync/.withings_user.json:/home/withings-sync/.withings_user.json - - ${STACK_PATH:?err}/config/withings-sync/.garmin_session:/home/withings-sync/.garmin_session - ${STACK_PATH:?err}/config/withings-sync/entrypoint.sh:/home/withings-sync/entrypoint.sh entrypoint: "sh /home/withings-sync/entrypoint.sh" restart: unless-stopped @@ -303,7 +299,7 @@ First start to ensure the script can start successfully: Obtaining Withings authorisation: ```bash -$ docker run -v .withings_user.json:/home/withings-sync/.withings_user.json -v .garmin_session:/home/withings-sync/.garmin_session --interactive --tty --name withings-sync ghcr.io/jaroslawhartman/withings-sync:latest --garmin-username= --garmin-password= +$ docker run -v .withings_user.json:/home/withings-sync/.withings_user.json --interactive --tty --name withings-sync ghcr.io/jaroslawhartman/withings-sync:latest --garmin-username= --garmin-password= Can't read config file config/withings_user.json User interaction needed to get Authentification Code from Withings! @@ -311,7 +307,7 @@ User interaction needed to get Authentification Code from Withings! Open the following URL in your web browser and copy back the token. You will have *30 seconds* before the token expires. HURRY UP! (This is one-time activity) -https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://wieloryb.uk.to/withings/withings.html& +https://account.withings.com/oauth2_user/authorize2?response_type=code&client_id=183e03e1f363110b3551f96765c98c10e8f1aa647a37067a1cb64bbbaf491626&state=OK&scope=user.metrics&redirect_uri=https://jaroslawhartman.github.io/withings-sync/contrib/withings.html& Token : Withings: Get Access Token From 198591cd2faaf18244ae354cdcefb3a90a8051fd Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:50:18 +0100 Subject: [PATCH 45/53] pyproject.toml: make sure we sync and fill all entries with the old setup.py --- pyproject.toml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b0fd31f..e480fe9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,12 +2,21 @@ name = "withings-sync" version = "1.0.0.dev1" description = "A tool for synchronisation of Withings (ex. Nokia Health Body) to Garmin Connect and Trainer Road." -authors = ["Steffen Vogel ", - "Masayuki Hamasaki", - ] license = "MIT" +authors = ["Masayuki Hamasaki ", + "Steffen Vogel ", + "Jarek Hartman ", + ] +maintainers = ["longstone <2110765+longstone@users.noreply.github.com>", + ] +repository = "https://github.com/jaroslawhartman/withings-sync" +homepage = "https://github.com/jaroslawhartman/withings-sync" readme = "README.md" +keywords = ["garmin", "withings", "sync", "api", "scale", "smarthome"] packages = [{include = "withings_sync"}] +classifiers = ["Topic :: Utilities", + "Topic :: Home Automation", + ] [tool.poetry.dependencies] python = "^3.12" From ac82ad3528bc6732c61abc7ac7179bd9ab56ac8f Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Fri, 20 Dec 2024 22:19:41 +0100 Subject: [PATCH 46/53] Dockerfile: optimize layers --- Dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 80ec168..56631f7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,14 @@ FROM python:3.12-alpine -ENV PROJECT=withings-sync -ENV PACKAGE=withings_sync -ENV PROJECT_DIR=/home/$PROJECT +ENV PROJECT="withings-sync" \ + PACKAGE="withings_sync" RUN apk --no-cache add supercronic RUN adduser -D $PROJECT +ENV PROJECT_DIR="/home/${PROJECT}" + USER $PROJECT WORKDIR $PROJECT_DIR From 7e4944c5b971737f2711cc9fa43e7240a953f56b Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:58:09 +0100 Subject: [PATCH 47/53] README.md: add logging to the docker compose method --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a9ecbbe..21df1aa 100644 --- a/README.md +++ b/README.md @@ -177,13 +177,15 @@ A tool for synchronisation of the Withings API to: Expand to show installation steps. 1. create the following file/directory structure: + > Make sure to create the directories (`mkdir`) & files (`touch`) upfront or docker will create them as root. ```bash . # STACK_PATH ./.env # .env file containing your variables ./docker-compose.yml # docker-compose file ./config/ # config directory ./config/withings-sync/ # config directory for withings-sync - ./config/withings-sync/entrypoint.sh # entrypoint.sh file containing your + ./config/withings-sync/entrypoint.sh # entrypoint.sh file containing your cmd & arguments + ./config/withings-sync/withings-sync.log # withings-sync.log file containing the logs ./config/withings-sync/.withings_user.json # .withings_user.json file to store access tokens ``` @@ -198,7 +200,7 @@ A tool for synchronisation of the Withings API to: 3. contents of an example `entrypoint.sh` file: ```bash #!/bin/sh - echo "$(( $RANDOM % 59 +0 )) */3 * * * poetry run withings-sync --verbose --features BLOOD_PRESSURE" > /home/withings-sync/cronjob + echo "$(( $RANDOM % 59 +0 )) */3 * * * poetry run withings-sync --features BLOOD_PRESSURE | tee -a /home/withings-sync/withings-sync.log" > /home/withings-sync/cronjob supercronic /home/withings-sync/cronjob ``` @@ -216,8 +218,9 @@ A tool for synchronisation of the Withings API to: - GARMIN_PASSWORD=${GARMIN_PASSWORD:?err} volumes: - /etc/localtime:/etc/localtime:ro - - ${STACK_PATH:?err}/config/withings-sync/.withings_user.json:/home/withings-sync/.withings_user.json - ${STACK_PATH:?err}/config/withings-sync/entrypoint.sh:/home/withings-sync/entrypoint.sh + - ${STACK_PATH:?err}/config/withings-sync/withings-sync.log:/home/withings-sync/withings-sync.log + - ${STACK_PATH:?err}/config/withings-sync/.withings_user.json:/home/withings-sync/.withings_user.json entrypoint: "sh /home/withings-sync/entrypoint.sh" restart: unless-stopped ``` From f124b7e4dd122355bb81dd4fdcdf41b4a7b11c39 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sun, 22 Dec 2024 20:03:49 +0100 Subject: [PATCH 48/53] README.md: more formatting --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 21df1aa..7e59561 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,12 @@ A tool for synchronisation of the Withings API to:
Expand to show installation steps. - 1. installing the package: + 1. installing the package: ```bash $ pip install withings-sync ``` - 2. obtaining Withings authorization: + 2. obtaining Withings authorization: When running for a very first time, you need to obtain Withings authorization: ```bash $ withings-sync @@ -53,7 +53,7 @@ A tool for synchronisation of the Withings API to: This is one-time activity and it will not be needed to repeat. - 3. running the application: + 3. running the application: Subsequent runs will use the saved access tokens in `~/.withings_user.json` ```bash $ withings-sync @@ -152,6 +152,7 @@ A tool for synchronisation of the Withings API to: This is one-time activity and it will not be needed to repeat. 5. running the container: + Subsequent runs will use the saved access tokens in `~/.withings_user.json` ```bash @@ -269,6 +270,7 @@ A tool for synchronisation of the Withings API to: ``` 6. running the container: + And for subsequent runs we start docker compose and let the container run in the background. Subsequent runs will use the saved access tokens in `~/.withings_user.json` From db3a1c220246f8c90661f71f4c4f82c4bf6f2376 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Sun, 22 Dec 2024 20:11:24 +0100 Subject: [PATCH 49/53] README.md: even more formatting --- README.md | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7e59561..a263656 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,13 @@ A tool for synchronisation of the Withings API to: Expand to show installation steps. 1. installing the package: + ```bash $ pip install withings-sync ``` 2. obtaining Withings authorization: + When running for a very first time, you need to obtain Withings authorization: ```bash $ withings-sync @@ -54,6 +56,7 @@ A tool for synchronisation of the Withings API to: This is one-time activity and it will not be needed to repeat. 3. running the application: + Subsequent runs will use the saved access tokens in `~/.withings_user.json` ```bash $ withings-sync @@ -69,7 +72,8 @@ A tool for synchronisation of the Withings API to:
Expand to show installation steps. - 1. create the following file/directory structure: + 1. create the following file/directory structure: + ```bash . # STACK_PATH ./.env # .env file containing your variables @@ -79,15 +83,17 @@ A tool for synchronisation of the Withings API to: ./config/withings-sync/.withings_user.json # .withings_user.json file to store access tokens ``` - 2. contents of an example `.env` file: + 2. contents of an example `.env` file: + ```bash TZ=Europe/Kyiv STACK_PATH=/home/your_user/your_stack_name GARMIN_USERNAME="your.name@domain.ext" GARMIN_PASSWORD="YourPasswordHere" ``` - - 3. contents of an example `docker-compose.yml` file: + + 3. contents of an example `docker-compose.yml` file: + ```yaml services: withings-sync: @@ -105,7 +111,8 @@ A tool for synchronisation of the Withings API to: restart: unless-stopped ``` - 4. obtaining Withings authorization: + 4. obtaining Withings authorization: + ```bash $ docker compose pull [+] Pulling 13/13 @@ -151,7 +158,7 @@ A tool for synchronisation of the Withings API to: This is one-time activity and it will not be needed to repeat. - 5. running the container: + 5. running the container: Subsequent runs will use the saved access tokens in `~/.withings_user.json` @@ -165,7 +172,8 @@ A tool for synchronisation of the Withings API to: 2024-12-01 01:37:43,069 - root - ERROR - No measurements to upload for date or period specified ``` - 6. updating to a newer version: + 6. updating to a newer version: + ```bash $ docker compose pull $ docker compose run -it --remove-orphans withings-sync @@ -177,7 +185,8 @@ A tool for synchronisation of the Withings API to:
Expand to show installation steps. - 1. create the following file/directory structure: + 1. create the following file/directory structure: + > Make sure to create the directories (`mkdir`) & files (`touch`) upfront or docker will create them as root. ```bash . # STACK_PATH @@ -190,7 +199,8 @@ A tool for synchronisation of the Withings API to: ./config/withings-sync/.withings_user.json # .withings_user.json file to store access tokens ``` - 2. contents of an example `.env` file: + 2. contents of an example `.env` file: + ```bash TZ=Europe/Kyiv STACK_PATH=/home/youruser/withings-sync @@ -198,14 +208,16 @@ A tool for synchronisation of the Withings API to: GARMIN_PASSWORD="YourPasswordHere" ``` - 3. contents of an example `entrypoint.sh` file: + 3. contents of an example `entrypoint.sh` file: + ```bash #!/bin/sh echo "$(( $RANDOM % 59 +0 )) */3 * * * poetry run withings-sync --features BLOOD_PRESSURE | tee -a /home/withings-sync/withings-sync.log" > /home/withings-sync/cronjob supercronic /home/withings-sync/cronjob ``` - - 4. contents of an example `docker-compose.yml` file: + + 4. contents of an example `docker-compose.yml` file: + ```yaml services: withings-sync: @@ -226,7 +238,8 @@ A tool for synchronisation of the Withings API to: restart: unless-stopped ``` - 5. obtaining Withings authorization: + 5. obtaining Withings authorization: + ```bash $ docker compose pull [+] Pulling 13/13 @@ -269,7 +282,7 @@ A tool for synchronisation of the Withings API to: 2024-12-01 01:31:09,856 - root - ERROR - No measurements to upload for date or period specified ``` - 6. running the container: + 6. running the container: And for subsequent runs we start docker compose and let the container run in the background. Subsequent runs will use the saved access tokens in `~/.withings_user.json` @@ -280,7 +293,8 @@ A tool for synchronisation of the Withings API to: ✔ Container withings-sync Started 1.5s ``` - 7. updating to a newer version: + 7. updating to a newer version: + ```bash $ docker compose pull $ docker compose down From 9ae8e6c05dcc1392950fe067660430365101510f Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Tue, 24 Dec 2024 10:46:15 +0100 Subject: [PATCH 50/53] README.md: improve container logging with supercronic --- README.md | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a263656..89f65c6 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,6 @@ A tool for synchronisation of the Withings API to: ./config/ # config directory ./config/withings-sync/ # config directory for withings-sync ./config/withings-sync/entrypoint.sh # entrypoint.sh file containing your cmd & arguments - ./config/withings-sync/withings-sync.log # withings-sync.log file containing the logs ./config/withings-sync/.withings_user.json # .withings_user.json file to store access tokens ``` @@ -212,8 +211,8 @@ A tool for synchronisation of the Withings API to: ```bash #!/bin/sh - echo "$(( $RANDOM % 59 +0 )) */3 * * * poetry run withings-sync --features BLOOD_PRESSURE | tee -a /home/withings-sync/withings-sync.log" > /home/withings-sync/cronjob - supercronic /home/withings-sync/cronjob + echo "$(( $RANDOM % 59 +0 )) */3 * * * poetry run withings-sync --features BLOOD_PRESSURE" > /home/withings-sync/cronjob + supercronic -debug -passthrough-logs /home/withings-sync/cronjob ``` 4. contents of an example `docker-compose.yml` file: @@ -232,7 +231,6 @@ A tool for synchronisation of the Withings API to: volumes: - /etc/localtime:/etc/localtime:ro - ${STACK_PATH:?err}/config/withings-sync/entrypoint.sh:/home/withings-sync/entrypoint.sh - - ${STACK_PATH:?err}/config/withings-sync/withings-sync.log:/home/withings-sync/withings-sync.log - ${STACK_PATH:?err}/config/withings-sync/.withings_user.json:/home/withings-sync/.withings_user.json entrypoint: "sh /home/withings-sync/entrypoint.sh" restart: unless-stopped @@ -293,7 +291,34 @@ A tool for synchronisation of the Withings API to: ✔ Container withings-sync Started 1.5s ``` - 7. updating to a newer version: + 7. logging: + + ```bash + $ docker compose logs withings-sync + withings-sync | WARN[2024-12-24T09:23:55+01:00] process reaping disabled, not pid 1 + withings-sync | INFO[2024-12-24T09:23:55+01:00] read crontab: /home/withings-sync/cronjob + withings-sync | DEBU[2024-12-24T09:23:55+01:00] try parse (7 fields): '53 */3 * * * poetry run' + withings-sync | DEBU[2024-12-24T09:23:55+01:00] failed to parse (7 fields): '53 */3 * * * poetry run': failed: syntax error in day-of-week field: 'poetry' + withings-sync | DEBU[2024-12-24T09:23:55+01:00] try parse (6 fields): '53 */3 * * * poetry' + withings-sync | DEBU[2024-12-24T09:23:55+01:00] failed to parse (6 fields): '53 */3 * * * poetry': failed: syntax error in year field: 'poetry' + withings-sync | DEBU[2024-12-24T09:23:55+01:00] try parse (5 fields): '53 */3 * * *' + withings-sync | DEBU[2024-12-24T09:23:55+01:00] job will run next at 2024-12-24 09:53:00 +0100 CET job.command="poetry run withings-sync --features BLOOD_PRESSURE" job.position=0 job.schedule="53 */3 * * *" + withings-sync | INFO[2024-12-24T09:53:00+01:00] starting iteration=0 job.command="poetry run withings-sync --features BLOOD_PRESSURE" job.position=0 job.schedule="53 */3 * * *" + withings-sync | 2024-12-24 09:53:29,177 - withings - INFO - Refresh Access Token + withings-sync | 2024-12-24 09:53:29,380 - root - INFO - Fetching measurements from 2024-12-22 18:52 to 2024-12-24 23:59 + withings-sync | 2024-12-24 09:53:29,662 - withings - INFO - Get Measurements + withings-sync | 2024-12-24 09:53:29,866 - root - INFO - 2024-12-24 08:08:57 This Withings metric contains no weight data or blood pressure. Not syncing... + withings-sync | 2024-12-24 09:53:29,868 - root - INFO - 2024-12-24 08:08:57 This Withings metric contains no weight data or blood pressure. Not syncing... + withings-sync | 2024-12-24 09:53:29,870 - root - INFO - 2024-12-24 08:08:57 This Withings metric contains no weight data or blood pressure. Not syncing... + withings-sync | 2024-12-24 09:53:29,878 - root - INFO - No blood pressure data to sync for FIT file + withings-sync | 2024-12-24 09:53:29,880 - root - INFO - No TrainerRoad username or a new measurement - skipping sync + withings-sync | 2024-12-24 09:53:33,665 - root - INFO - Fit file with weight information uploaded to Garmin Connect + withings-sync | 2024-12-24 09:53:33,666 - withings - INFO - Saving Last Sync + withings-sync | INFO[2024-12-24T09:53:34+01:00] job succeeded iteration=0 job.command="poetry run withings-sync --features BLOOD_PRESSURE" job.position=0 job.schedule="53 */3 * * *" + withings-sync | DEBU[2024-12-24T09:53:34+01:00] job will run next at 2024-12-24 12:53:00 +0100 CET job.command="poetry run withings-sync --features BLOOD_PRESSURE" job.position=0 job.schedule="53 */3 * * *" + ``` + + 8. updating to a newer version: ```bash $ docker compose pull From 77de05394fd64d1e8b757f0a1c7ee09010bee408 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:37:47 +0100 Subject: [PATCH 51/53] Dockerfile: implement user UID/GID logic --- Dockerfile | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 56631f7..3b459ab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,19 @@ FROM python:3.12-alpine -ENV PROJECT="withings-sync" \ - PACKAGE="withings_sync" +ARG PROJECT="withings-sync" +ARG PACKAGE="withings_sync" +ARG USER_UID=1000 +ARG USER_GID=$USER_UID RUN apk --no-cache add supercronic -RUN adduser -D $PROJECT +RUN if getent passwd ${USER_UID} >/dev/null; then \ + deluser $(getent passwd ${USER_UID} | cut -d: -f1); fi && \ + if getent group ${USER_GID} >/dev/null; then \ + delgroup $(getent group ${USER_GID} | cut -d: -f1); fi +RUN addgroup --system --gid ${USER_GID} ${PROJECT} && \ + adduser --system --disabled-password --home /home/${PROJECT} \ + --uid ${USER_UID} --ingroup ${PROJECT} ${PROJECT} ENV PROJECT_DIR="/home/${PROJECT}" From 8ddeca24ed28720e6e92f9882fe585efb6184929 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Fri, 3 Jan 2025 11:06:59 +0100 Subject: [PATCH 52/53] sync.py: add additinal info logging to avoid confusion --- withings_sync/sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/withings_sync/sync.py b/withings_sync/sync.py index 2a0b9ec..3c16fc4 100644 --- a/withings_sync/sync.py +++ b/withings_sync/sync.py @@ -329,7 +329,7 @@ def prepare_syncdata(height, groups): group_data["percent_hydration"] = round( group_data["hydration"] * 100.0 / group_data["weight"], 2 ) - + logging.info("%s This Withings metric contains valid data. Syncing...", dt) logging.debug("%s Detected data: ", dt) groupdata_log_raw_data(group_data) if "weight" in group_data: From 9d1e4951a88cb99ec236f2b3baf8e1956512d542 Mon Sep 17 00:00:00 2001 From: stynoo <17745813+stynoo@users.noreply.github.com> Date: Fri, 3 Jan 2025 11:12:57 +0100 Subject: [PATCH 53/53] sync.py: black & pylint: no logic changes --- withings_sync/sync.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/withings_sync/sync.py b/withings_sync/sync.py index 3c16fc4..18caae1 100644 --- a/withings_sync/sync.py +++ b/withings_sync/sync.py @@ -1,25 +1,25 @@ """This module syncs measurement data from Withings to Garmin a/o TrainerRoad.""" + import argparse import time import sys import os import logging import json -import dotenv - from datetime import date, datetime from importlib.metadata import version - -# Load the environment variables from a .env (dotenv) file. -# This is done prior to importing other modules such that all variables, -# also the ones accessed in those modules, can be set in the dotenv file. -dotenv.load_dotenv() +import dotenv from withings_sync.withings2 import WithingsAccount from withings_sync.garmin import GarminConnect from withings_sync.trainerroad import TrainerRoad from withings_sync.fit import FitEncoderWeight, FitEncoderBloodPressure +# Load the environment variables from a .env (dotenv) file. +# This is done prior to importing other modules such that all variables, +# also the ones accessed in those modules, can be set in the dotenv file. +dotenv.load_dotenv() + def load_variable(env_var, secrets_file): """Load a variable from an environment variable or from a secrets file""" @@ -388,11 +388,13 @@ def prepare_syncdata(height, groups): def groupdata_log_raw_data(groupdata): + """Logs raw data to debug""" for dataentry in groupdata["raw_data"]: logging.debug("%s", dataentry) def write_to_fitfile(filename, fit_data): + """Writes fit data to fit file""" logging.info("Writing fitfile to %s.", filename) try: with open(filename, "wb") as fitfile: @@ -472,9 +474,7 @@ def sync(): if sync_trainerroad(last_weight): logging.info("TrainerRoad update done!") else: - logging.info( - "No TrainerRoad username or a new measurement " "- skipping sync" - ) + logging.info("No TrainerRoad username or a new measurement - skipping sync") # Upload to Garmin Connect if ARGS.garmin_username and (