From f9939c61142def2e4fec17ce1e62c81e4ef83180 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Mon, 10 Mar 2025 15:27:20 +0100 Subject: [PATCH 01/27] [#2]: initialize python project; done with: $ (cd code/python && \ uv init --bare \ --name keyring-insecure-backend \ --package \ --python 3.10) --- code/python/pyproject.toml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 code/python/pyproject.toml diff --git a/code/python/pyproject.toml b/code/python/pyproject.toml new file mode 100644 index 0000000..eaa389e --- /dev/null +++ b/code/python/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "keyring-insecure-backend" +version = "0.1.0" +authors = [ + { name = "Stefan Schablowski", email = "stefan.schablowski@desmodyne.com" } +] +requires-python = ">=3.10" +dependencies = [] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" From df1a25791eb8cd1f74bfc485b8c8e21cbcb48b03 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Mon, 10 Mar 2025 15:53:22 +0100 Subject: [PATCH 02/27] [#2]: align python project conf with corporate convention, extend: + add file header, order sections alphabetically, replace " by ', add classifiers, description, license; reset version to 0.0.0 + NOTE: attempting to create a venv at this stage fails with $ (cd code/python && uv sync) ... (python exception leaked by uv omitted) ... ValueError: Unable to determine which files to ship inside the wheel using the following heuristics: https://hatch.pypa.io/latest/plugins/ ... ... builder/wheel/#default-file-selection The most likely cause of this is that there is no directory that matches the name of your project (keyring_insecure_backend). At least one file selection option must be defined in the `tool.hatch.build.targets.wheel` table, see: https://hatch.pypa.io/latest/config/build/ As an example, if you intend to ship a directory named `foo` that resides within a `src` directory located at the root of your project, you can define the following: [tool.hatch.build.targets.wheel] packages = ["src/foo"] hint: This usually indicates a problem with the package or the build environment. --- code/python/pyproject.toml | 40 +++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/code/python/pyproject.toml b/code/python/pyproject.toml index eaa389e..aafcd5a 100644 --- a/code/python/pyproject.toml +++ b/code/python/pyproject.toml @@ -1,12 +1,38 @@ +# pyproject.toml +# +# python project configuration +# +# author : stefan schablowski +# contact : stefan.schablowski@desmodyne.com +# created : 2025-03-10 + + +[build-system] +requires = ['hatchling'] +build-backend = 'hatchling.build' + + [project] -name = "keyring-insecure-backend" -version = "0.1.0" authors = [ - { name = "Stefan Schablowski", email = "stefan.schablowski@desmodyne.com" } + { name = 'Stefan Schablowski', email = 'stefan.schablowski@desmodyne.com' } ] -requires-python = ">=3.10" + +classifiers = [ + 'Development Status :: 3 - Alpha', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3', + 'Topic :: Security', + 'Typing :: Typed' +] + dependencies = [] -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" +description = 'An insecure Keyring backend that stores secrets in plain text.' +license = { text = 'MIT' } +name = 'keyring-insecure-backend' +requires-python = '>=3.10' +version = '0.0.0' From 67473263ea8227db59de9af4c4d5c9d5895f2a40 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Mon, 10 Mar 2025 15:56:41 +0100 Subject: [PATCH 03/27] [#2]: get `uv sync` to work; add uv.lock; done with: $ (cd code/python && \ mkdir keyring_insecure_backend && cd $_ && touch __init__.py) --- code/python/keyring_insecure_backend/__init__.py | 0 code/python/uv.lock | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 code/python/keyring_insecure_backend/__init__.py create mode 100644 code/python/uv.lock diff --git a/code/python/keyring_insecure_backend/__init__.py b/code/python/keyring_insecure_backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/code/python/uv.lock b/code/python/uv.lock new file mode 100644 index 0000000..3095fa0 --- /dev/null +++ b/code/python/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 1 +requires-python = ">=3.10" + +[[package]] +name = "keyring-insecure-backend" +version = "0.0.0" +source = { editable = "." } From 597989ae0e02ecb3f1f5e61711ff48f6f818e105 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Mon, 10 Mar 2025 16:00:39 +0100 Subject: [PATCH 04/27] [#2]: $ (cd code/python && uv add keyring) Resolved 15 packages in 304ms Built keyring-insecure-backend @ file:/// [...] ... keyring-insecure-backend/code/python Prepared 6 packages in 570ms Uninstalled 1 package in 1ms Installed 6 packages in 4ms + jaraco-classes==3.4.0 + jaraco-context==6.0.1 + jaraco-functools==4.1.0 + keyring==25.6.0 ~ keyring-insecure-backend==0.0.0 (from file:/// [...] ... keyring-insecure-backend/code/python) + more-itertools==10.6.0 --- code/python/pyproject.toml | 4 +- code/python/uv.lock | 217 +++++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 1 deletion(-) diff --git a/code/python/pyproject.toml b/code/python/pyproject.toml index aafcd5a..136a094 100644 --- a/code/python/pyproject.toml +++ b/code/python/pyproject.toml @@ -29,7 +29,9 @@ classifiers = [ 'Typing :: Typed' ] -dependencies = [] +dependencies = [ + "keyring>=25.6.0", +] description = 'An insecure Keyring backend that stores secrets in plain text.' license = { text = 'MIT' } diff --git a/code/python/uv.lock b/code/python/uv.lock index 3095fa0..2ccd0cc 100644 --- a/code/python/uv.lock +++ b/code/python/uv.lock @@ -2,7 +2,224 @@ version = 1 revision = 1 requires-python = ">=3.10" +[[package]] +name = "backports-tarfile" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", size = 86406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, +] + +[[package]] +name = "cryptography" +version = "44.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/25/4ce80c78963834b8a9fd1cc1266be5ed8d1840785c0f2e1b73b8d128d505/cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0", size = 710807 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/ec/7ea7c1e4c8fc8329506b46c6c4a52e2f20318425d48e0fe597977c71dbce/cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1", size = 3952350 }, + { url = "https://files.pythonhosted.org/packages/27/61/72e3afdb3c5ac510330feba4fc1faa0fe62e070592d6ad00c40bb69165e5/cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb", size = 4166572 }, + { url = "https://files.pythonhosted.org/packages/26/e4/ba680f0b35ed4a07d87f9e98f3ebccb05091f3bf6b5a478b943253b3bbd5/cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843", size = 3958124 }, + { url = "https://files.pythonhosted.org/packages/9c/e8/44ae3e68c8b6d1cbc59040288056df2ad7f7f03bbcaca6b503c737ab8e73/cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5", size = 3678122 }, + { url = "https://files.pythonhosted.org/packages/27/7b/664ea5e0d1eab511a10e480baf1c5d3e681c7d91718f60e149cec09edf01/cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c", size = 4191831 }, + { url = "https://files.pythonhosted.org/packages/2a/07/79554a9c40eb11345e1861f46f845fa71c9e25bf66d132e123d9feb8e7f9/cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a", size = 3960583 }, + { url = "https://files.pythonhosted.org/packages/bb/6d/858e356a49a4f0b591bd6789d821427de18432212e137290b6d8a817e9bf/cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308", size = 4191753 }, + { url = "https://files.pythonhosted.org/packages/b2/80/62df41ba4916067fa6b125aa8c14d7e9181773f0d5d0bd4dcef580d8b7c6/cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688", size = 4079550 }, + { url = "https://files.pythonhosted.org/packages/f3/cd/2558cc08f7b1bb40683f99ff4327f8dcfc7de3affc669e9065e14824511b/cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7", size = 4298367 }, + { url = "https://files.pythonhosted.org/packages/06/88/638865be7198a84a7713950b1db7343391c6066a20e614f8fa286eb178ed/cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639", size = 3951919 }, + { url = "https://files.pythonhosted.org/packages/d7/fc/99fe639bcdf58561dfad1faa8a7369d1dc13f20acd78371bb97a01613585/cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd", size = 4167812 }, + { url = "https://files.pythonhosted.org/packages/53/7b/aafe60210ec93d5d7f552592a28192e51d3c6b6be449e7fd0a91399b5d07/cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181", size = 3958571 }, + { url = "https://files.pythonhosted.org/packages/16/32/051f7ce79ad5a6ef5e26a92b37f172ee2d6e1cce09931646eef8de1e9827/cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea", size = 3679832 }, + { url = "https://files.pythonhosted.org/packages/78/2b/999b2a1e1ba2206f2d3bca267d68f350beb2b048a41ea827e08ce7260098/cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699", size = 4193719 }, + { url = "https://files.pythonhosted.org/packages/72/97/430e56e39a1356e8e8f10f723211a0e256e11895ef1a135f30d7d40f2540/cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9", size = 3960852 }, + { url = "https://files.pythonhosted.org/packages/89/33/c1cf182c152e1d262cac56850939530c05ca6c8d149aa0dcee490b417e99/cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23", size = 4193906 }, + { url = "https://files.pythonhosted.org/packages/e1/99/87cf26d4f125380dc674233971069bc28d19b07f7755b29861570e513650/cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922", size = 4081572 }, + { url = "https://files.pythonhosted.org/packages/b3/9f/6a3e0391957cc0c5f84aef9fbdd763035f2b52e998a53f99345e3ac69312/cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4", size = 4298631 }, + { url = "https://files.pythonhosted.org/packages/2f/b4/424ea2d0fce08c24ede307cead3409ecbfc2f566725d4701b9754c0a1174/cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0529b1d5a0105dd3731fa65680b45ce49da4d8115ea76e9da77a875396727b41", size = 3892387 }, + { url = "https://files.pythonhosted.org/packages/28/20/8eaa1a4f7c68a1cb15019dbaad59c812d4df4fac6fd5f7b0b9c5177f1edd/cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7ca25849404be2f8e4b3c59483d9d3c51298a22c1c61a0e84415104dacaf5562", size = 4109922 }, + { url = "https://files.pythonhosted.org/packages/11/25/5ed9a17d532c32b3bc81cc294d21a36c772d053981c22bd678396bc4ae30/cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:268e4e9b177c76d569e8a145a6939eca9a5fec658c932348598818acf31ae9a5", size = 3895715 }, + { url = "https://files.pythonhosted.org/packages/63/31/2aac03b19c6329b62c45ba4e091f9de0b8f687e1b0cd84f101401bece343/cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:9eb9d22b0a5d8fd9925a7764a054dca914000607dff201a24c791ff5c799e1fa", size = 4109876 }, + { url = "https://files.pythonhosted.org/packages/d6/d7/f30e75a6aa7d0f65031886fa4a1485c2fbfe25a1896953920f6a9cfe2d3b/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:909c97ab43a9c0c0b0ada7a1281430e4e5ec0458e6d9244c0e821bbf152f061d", size = 3887513 }, + { url = "https://files.pythonhosted.org/packages/9c/b4/7a494ce1032323ca9db9a3661894c66e0d7142ad2079a4249303402d8c71/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96e7a5e9d6e71f9f4fca8eebfd603f8e86c5225bb18eb621b2c1e50b290a9471", size = 4107432 }, + { url = "https://files.pythonhosted.org/packages/45/f8/6b3ec0bc56123b344a8d2b3264a325646d2dcdbdd9848b5e6f3d37db90b3/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d1b3031093a366ac767b3feb8bcddb596671b3aaff82d4050f984da0c248b615", size = 3891421 }, + { url = "https://files.pythonhosted.org/packages/57/ff/f3b4b2d007c2a646b0f69440ab06224f9cf37a977a72cdb7b50632174e8a/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:04abd71114848aa25edb28e225ab5f268096f44cf0127f3d36975bdf1bdf3390", size = 4107081 }, +] + +[[package]] +name = "importlib-metadata" +version = "8.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/08/c1395a292bb23fd03bdf572a1357c5a733d3eecbab877641ceacab23db6e/importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580", size = 55767 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/9d/0fb148dc4d6fa4a7dd1d8378168d9b4cd8d4560a6fbf6f0121c5fc34eb68/importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e", size = 26971 }, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777 }, +] + +[[package]] +name = "jaraco-context" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-tarfile", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", size = 13912 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", size = 6825 }, +] + +[[package]] +name = "jaraco-functools" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/23/9894b3df5d0a6eb44611c36aec777823fc2e07740dabbd0b810e19594013/jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d", size = 19159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/4f/24b319316142c44283d7540e76c7b5a6dbd5db623abd86bb7b3491c21018/jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649", size = 10187 }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010 }, +] + +[[package]] +name = "keyring" +version = "25.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085 }, +] + [[package]] name = "keyring-insecure-backend" version = "0.0.0" source = { editable = "." } +dependencies = [ + { name = "keyring" }, +] + +[package.metadata] +requires-dist = [{ name = "keyring", specifier = ">=25.6.0" }] + +[[package]] +name = "more-itertools" +version = "10.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/3b/7fa1fe835e2e93fd6d7b52b2f95ae810cf5ba133e1845f726f5a992d62c2/more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b", size = 125009 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/62/0fe302c6d1be1c777cab0616e6302478251dfbf9055ad426f5d0def75c89/more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89", size = 63038 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 }, +] + +[[package]] +name = "secretstorage" +version = "3.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", size = 19739 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", size = 15221 }, +] + +[[package]] +name = "zipp" +version = "3.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630 }, +] From f0333688b0c8a72acc422feef26567d6a2698cd8 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Mon, 10 Mar 2025 16:29:52 +0100 Subject: [PATCH 05/27] [#2]: $ (cd code/python && uv add --group dev ruff) Resolved 16 packages in 385ms Built keyring-insecure-backend @ file:/// [...] ... keyring-insecure-backend/code/python Prepared 2 packages in 1.18s Uninstalled 1 package in 2ms Installed 2 packages in 3ms ~ keyring-insecure-backend==0.0.0 (from file:/// [...] ... keyring-insecure-backend/code/python) + ruff==0.9.10 --- code/python/pyproject.toml | 6 ++++++ code/python/uv.lock | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/code/python/pyproject.toml b/code/python/pyproject.toml index 136a094..b07ac39 100644 --- a/code/python/pyproject.toml +++ b/code/python/pyproject.toml @@ -38,3 +38,9 @@ license = { text = 'MIT' } name = 'keyring-insecure-backend' requires-python = '>=3.10' version = '0.0.0' + + +[dependency-groups] +dev = [ + "ruff>=0.9.10", +] diff --git a/code/python/uv.lock b/code/python/uv.lock index 2ccd0cc..b8dcdc5 100644 --- a/code/python/uv.lock +++ b/code/python/uv.lock @@ -172,9 +172,17 @@ dependencies = [ { name = "keyring" }, ] +[package.dev-dependencies] +dev = [ + { name = "ruff" }, +] + [package.metadata] requires-dist = [{ name = "keyring", specifier = ">=25.6.0" }] +[package.metadata.requires-dev] +dev = [{ name = "ruff", specifier = ">=0.9.10" }] + [[package]] name = "more-itertools" version = "10.6.0" @@ -202,6 +210,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 }, ] +[[package]] +name = "ruff" +version = "0.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/8e/fafaa6f15c332e73425d9c44ada85360501045d5ab0b81400076aff27cf6/ruff-0.9.10.tar.gz", hash = "sha256:9bacb735d7bada9cfb0f2c227d3658fc443d90a727b47f206fb33f52f3c0eac7", size = 3759776 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/b2/af7c2cc9e438cbc19fafeec4f20bfcd72165460fe75b2b6e9a0958c8c62b/ruff-0.9.10-py3-none-linux_armv6l.whl", hash = "sha256:eb4d25532cfd9fe461acc83498361ec2e2252795b4f40b17e80692814329e42d", size = 10049494 }, + { url = "https://files.pythonhosted.org/packages/6d/12/03f6dfa1b95ddd47e6969f0225d60d9d7437c91938a310835feb27927ca0/ruff-0.9.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:188a6638dab1aa9bb6228a7302387b2c9954e455fb25d6b4470cb0641d16759d", size = 10853584 }, + { url = "https://files.pythonhosted.org/packages/02/49/1c79e0906b6ff551fb0894168763f705bf980864739572b2815ecd3c9df0/ruff-0.9.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5284dcac6b9dbc2fcb71fdfc26a217b2ca4ede6ccd57476f52a587451ebe450d", size = 10155692 }, + { url = "https://files.pythonhosted.org/packages/5b/01/85e8082e41585e0e1ceb11e41c054e9e36fed45f4b210991052d8a75089f/ruff-0.9.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47678f39fa2a3da62724851107f438c8229a3470f533894b5568a39b40029c0c", size = 10369760 }, + { url = "https://files.pythonhosted.org/packages/a1/90/0bc60bd4e5db051f12445046d0c85cc2c617095c0904f1aa81067dc64aea/ruff-0.9.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99713a6e2766b7a17147b309e8c915b32b07a25c9efd12ada79f217c9c778b3e", size = 9912196 }, + { url = "https://files.pythonhosted.org/packages/66/ea/0b7e8c42b1ec608033c4d5a02939c82097ddcb0b3e393e4238584b7054ab/ruff-0.9.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524ee184d92f7c7304aa568e2db20f50c32d1d0caa235d8ddf10497566ea1a12", size = 11434985 }, + { url = "https://files.pythonhosted.org/packages/d5/86/3171d1eff893db4f91755175a6e1163c5887be1f1e2f4f6c0c59527c2bfd/ruff-0.9.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df92aeac30af821f9acf819fc01b4afc3dfb829d2782884f8739fb52a8119a16", size = 12155842 }, + { url = "https://files.pythonhosted.org/packages/89/9e/700ca289f172a38eb0bca752056d0a42637fa17b81649b9331786cb791d7/ruff-0.9.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de42e4edc296f520bb84954eb992a07a0ec5a02fecb834498415908469854a52", size = 11613804 }, + { url = "https://files.pythonhosted.org/packages/f2/92/648020b3b5db180f41a931a68b1c8575cca3e63cec86fd26807422a0dbad/ruff-0.9.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d257f95b65806104b6b1ffca0ea53f4ef98454036df65b1eda3693534813ecd1", size = 13823776 }, + { url = "https://files.pythonhosted.org/packages/5e/a6/cc472161cd04d30a09d5c90698696b70c169eeba2c41030344194242db45/ruff-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60dec7201c0b10d6d11be00e8f2dbb6f40ef1828ee75ed739923799513db24c", size = 11302673 }, + { url = "https://files.pythonhosted.org/packages/6c/db/d31c361c4025b1b9102b4d032c70a69adb9ee6fde093f6c3bf29f831c85c/ruff-0.9.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d838b60007da7a39c046fcdd317293d10b845001f38bcb55ba766c3875b01e43", size = 10235358 }, + { url = "https://files.pythonhosted.org/packages/d1/86/d6374e24a14d4d93ebe120f45edd82ad7dcf3ef999ffc92b197d81cdc2a5/ruff-0.9.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ccaf903108b899beb8e09a63ffae5869057ab649c1e9231c05ae354ebc62066c", size = 9886177 }, + { url = "https://files.pythonhosted.org/packages/00/62/a61691f6eaaac1e945a1f3f59f1eea9a218513139d5b6c2b8f88b43b5b8f/ruff-0.9.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f9567d135265d46e59d62dc60c0bfad10e9a6822e231f5b24032dba5a55be6b5", size = 10864747 }, + { url = "https://files.pythonhosted.org/packages/ee/94/2c7065e1d92a8a8a46d46d9c3cf07b0aa7e0a1e0153d74baa5e6620b4102/ruff-0.9.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f202f0d93738c28a89f8ed9eaba01b7be339e5d8d642c994347eaa81c6d75b8", size = 11360441 }, + { url = "https://files.pythonhosted.org/packages/a7/8f/1f545ea6f9fcd7bf4368551fb91d2064d8f0577b3079bb3f0ae5779fb773/ruff-0.9.10-py3-none-win32.whl", hash = "sha256:bfb834e87c916521ce46b1788fbb8484966e5113c02df216680102e9eb960029", size = 10247401 }, + { url = "https://files.pythonhosted.org/packages/4f/18/fb703603ab108e5c165f52f5b86ee2aa9be43bb781703ec87c66a5f5d604/ruff-0.9.10-py3-none-win_amd64.whl", hash = "sha256:f2160eeef3031bf4b17df74e307d4c5fb689a6f3a26a2de3f7ef4044e3c484f1", size = 11366360 }, + { url = "https://files.pythonhosted.org/packages/35/85/338e603dc68e7d9994d5d84f24adbf69bae760ba5efd3e20f5ff2cec18da/ruff-0.9.10-py3-none-win_arm64.whl", hash = "sha256:5fd804c0327a5e5ea26615550e706942f348b197d5475ff34c19733aee4b2e69", size = 10436892 }, +] + [[package]] name = "secretstorage" version = "3.3.3" From 7384d791d75f539c6f72c22d4d17fc2ca3d8fa1b Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Tue, 11 Mar 2025 14:30:25 +0100 Subject: [PATCH 06/27] [#2]: implement the very first version of the backend; add py.typed --- .../InsecureKeyringBackend.py | 49 +++++++++++++++++++ code/python/py.typed | 0 2 files changed, 49 insertions(+) create mode 100644 code/python/keyring_insecure_backend/InsecureKeyringBackend.py create mode 100644 code/python/py.typed diff --git a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py new file mode 100644 index 0000000..05d03f3 --- /dev/null +++ b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py @@ -0,0 +1,49 @@ +# keyring.py +# +# Insecure Keyring Backend implementation. +# +# author : stefan schablowski +# contact : stefan.schablowski@desmodyne.com +# created : 2025-03-11 + + +# Keyring > Write your own keyring backend: +# https://github.com/jaraco/keyring ... +# ... ?tab=readme-ov-file#write-your-own-keyring-backend + + +# https://pypi.org/project/keyring +from keyring.backend import KeyringBackend + + +class InsecureKeyringBackend(KeyringBackend): + """ + An insecure Keyring backend that stores secrets in plain text. + """ + + # TODO: doc + priority = 30 + + + def delete_password(self, service: str, username: str) -> None: + """ + TODO: doc + """ + + raise NotImplementedError + + + def get_password(self, service: str, username: str) -> str: + """ + TODO: doc + """ + + return 'some password' + + + def set_password(self, service: str, username: str, password: str) -> None: + """ + TODO: doc + """ + + raise NotImplementedError diff --git a/code/python/py.typed b/code/python/py.typed new file mode 100644 index 0000000..e69de29 From fd144d6127ddcd8414e179125403cc586bd02592 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Tue, 11 Mar 2025 14:32:32 +0100 Subject: [PATCH 07/27] [#2]: make keyring load backend: conf project entry points; doc issues --- code/python/pyproject.toml | 54 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/code/python/pyproject.toml b/code/python/pyproject.toml index b07ac39..aa3538a 100644 --- a/code/python/pyproject.toml +++ b/code/python/pyproject.toml @@ -44,3 +44,57 @@ version = '0.0.0' dev = [ "ruff>=0.9.10", ] + + +# NOTE: this is required to make Keyring load this backend as plugin: +# uv > Plugin entry points: +# https://docs.astral.sh/uv/concepts/projects/config/#plugin-entry-points +# + project.entry-points is the name of the pyproject.toml conf table: +# https://packaging.python.org/en/latest/guides/ ... +# ... creating-and-discovering-plugins/#using-package-metadata +# + keyring.backends is the name of the plugin group set by Keyring: +# https://github.com/jaraco/keyring/blob/main/keyring/backend.py#L241 +# + InsecureKeyringBackend is the backend plugin name, used internally only +# + keyring_insecure_backend.InsecureKeyringBackend is the path to +# the module that implements the backend plugin, relative to this file: +# keyring_insecure_backend/InsecureKeyringBackend.py +# in python import notation, i.e. '/' replaced by '.' and '.py' omitted +# + InsecureKeyringBackend is the name of the backend plugin python class +# TODO: align 'keyring_insecure_backend.InsecureKeyringBackend' with conv: +# $ keyring --list-backends +# keyring.backends.chainer.ChainerBackend (priority: 10) +# keyring_insecure_backend.InsecureKeyringBackend.InsecureKeyringBackend (priority: 30) +# keyring.backends.macOS.Keyring (priority: 5) +# keyrings.onepassword.keyring.OnePasswordKeyring (priority: 20) +# keyring.backends.fail.Keyring (priority: 0) +# - but then, there doesn't really seem to be any conv used in practice; +# from Keyring > Write your own keyring backend at +# https://github.com/jaraco/keyring ... +# ... ?tab=readme-ov-file#write-your-own-keyring-backend: +# > Those interested in creating new backends are encouraged to create new, +# > third-party packages in the keyrings namespace, in a manner modeled by +# > the keyrings.alt package. See the setup.cfg file in that project for +# > hints on how to create the requisite entry points. +# documentation is outdated, there is no setup.cfg file at +# https://github.com/jaraco/keyrings.alt +# the correct file +# https://github.com/jaraco/keyrings.alt/blob/main/pyproject.toml#L81 +# has there entry points (reproduction aligned and sorted for clarity): +# [project.entry-points."keyring.backends"] +# file = "keyrings.alt.file" +# Gnome = "keyrings.alt.Gnome" +# Google = "keyrings.alt.Google" +# keyczar = "keyrings.alt.keyczar" +# multi = "keyrings.alt.multi" +# "Windows (alt)" = "keyrings.alt.Windows" +# the Keyring project lists these entry point for built-in backends: +# [project.entry-points."keyring.backends"] +# chainer = "keyring.backends.chainer" +# KWallet = "keyring.backends.kwallet" +# libsecret = "keyring.backends.libsecret" +# macOS = "keyring.backends.macOS" +# SecretService = "keyring.backends.SecretService" +# Windows = "keyring.backends.Windows" +# also, aligning enforces folder names onto this project +[project.entry-points.'keyring.backends'] +InsecureKeyringBackend = 'keyring_insecure_backend.InsecureKeyringBackend:InsecureKeyringBackend' From 02e8d8237c5ed49f1d114c3c6f44bc2c4bbd36c8 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Tue, 11 Mar 2025 14:42:22 +0100 Subject: [PATCH 08/27] [#2]: $ (cd code/python && uv add platformdirs pydantic PyYAML rich) --- code/python/pyproject.toml | 4 + code/python/uv.lock | 216 ++++++++++++++++++++++++++++++++++++- 2 files changed, 219 insertions(+), 1 deletion(-) diff --git a/code/python/pyproject.toml b/code/python/pyproject.toml index aa3538a..8f5f464 100644 --- a/code/python/pyproject.toml +++ b/code/python/pyproject.toml @@ -31,6 +31,10 @@ classifiers = [ dependencies = [ "keyring>=25.6.0", + "platformdirs>=4.3.6", + "pydantic>=2.10.6", + "pyyaml>=6.0.2", + "rich>=13.9.4", ] description = 'An insecure Keyring backend that stores secrets in plain text.' diff --git a/code/python/uv.lock b/code/python/uv.lock index b8dcdc5..34a2712 100644 --- a/code/python/uv.lock +++ b/code/python/uv.lock @@ -2,6 +2,15 @@ version = 1 revision = 1 requires-python = ">=3.10" +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + [[package]] name = "backports-tarfile" version = "1.2.0" @@ -170,6 +179,10 @@ version = "0.0.0" source = { editable = "." } dependencies = [ { name = "keyring" }, + { name = "platformdirs" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "rich" }, ] [package.dev-dependencies] @@ -178,11 +191,38 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "keyring", specifier = ">=25.6.0" }] +requires-dist = [ + { name = "keyring", specifier = ">=25.6.0" }, + { name = "platformdirs", specifier = ">=4.3.6" }, + { name = "pydantic", specifier = ">=2.10.6" }, + { name = "pyyaml", specifier = ">=6.0.2" }, + { name = "rich", specifier = ">=13.9.4" }, +] [package.metadata.requires-dev] dev = [{ name = "ruff", specifier = ">=0.9.10" }] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + [[package]] name = "more-itertools" version = "10.6.0" @@ -192,6 +232,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/62/0fe302c6d1be1c777cab0616e6302478251dfbf9055ad426f5d0def75c89/more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89", size = 63038 }, ] +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + [[package]] name = "pycparser" version = "2.22" @@ -201,6 +250,104 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, ] +[[package]] +name = "pydantic" +version = "2.10.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, +] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938 }, + { url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684 }, + { url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169 }, + { url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227 }, + { url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695 }, + { url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662 }, + { url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370 }, + { url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813 }, + { url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287 }, + { url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414 }, + { url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301 }, + { url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685 }, + { url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876 }, + { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, + { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, + { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, + { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, + { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, + { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, + { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, + { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, + { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, + { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, + { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, + { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, + { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, + { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, + { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, + { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, + { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, + { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, + { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, + { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, + { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, + { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, + { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, + { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, + { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, + { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, + { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, + { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, + { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, + { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, + { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, + { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, + { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, + { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, + { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, + { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, + { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, + { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, + { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, + { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, + { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, + { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, + { url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159 }, + { url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331 }, + { url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467 }, + { url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797 }, + { url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839 }, + { url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861 }, + { url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582 }, + { url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985 }, + { url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + [[package]] name = "pywin32-ctypes" version = "0.2.3" @@ -210,6 +357,64 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 }, ] +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "rich" +version = "13.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, +] + [[package]] name = "ruff" version = "0.9.10" @@ -248,6 +453,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", size = 15221 }, ] +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + [[package]] name = "zipp" version = "3.21.0" From ddd60fe9113d7b266f05754acb6734f29770c717 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Tue, 11 Mar 2025 19:05:30 +0100 Subject: [PATCH 09/27] [#2]: add conf file model; process conf, set up logging; add conf file --- .../keyring_insecure_backend/ConfFileModel.py | 30 ++++ .../InsecureKeyringBackend.py | 160 ++++++++++++++++++ etc/keyring_insecure_backend.yaml | 23 +++ 3 files changed, 213 insertions(+) create mode 100644 code/python/keyring_insecure_backend/ConfFileModel.py create mode 100644 etc/keyring_insecure_backend.yaml diff --git a/code/python/keyring_insecure_backend/ConfFileModel.py b/code/python/keyring_insecure_backend/ConfFileModel.py new file mode 100644 index 0000000..b87b729 --- /dev/null +++ b/code/python/keyring_insecure_backend/ConfFileModel.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# encoding: utf-8 + +""" +ConfFileModel.py + +Insecure Keyring Backend configuration file data model. + +author : stefan schablowski +contact : stefan.schablowski@desmodyne.com +created : 2025-03-11 +""" + + +from pathlib import Path + +# https://pypi.org/project/pydantic +from pydantic import BaseModel, ConfigDict + + +# ----------------------------------------------------------------------------- +# public types + +class ConfFileModel(BaseModel): + """ + Pydantic model that defines configuration file attributes. + """ + + model_config = ConfigDict(extra='forbid') + path_to_log_file: Path diff --git a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py index 05d03f3..3912321 100644 --- a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py +++ b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py @@ -12,8 +12,28 @@ # ... ?tab=readme-ov-file#write-your-own-keyring-backend +# TODO: review exception handling, establish / align with conv + + +# NOTE: need to import entire package +import logging + +from logging import basicConfig, info + # https://pypi.org/project/keyring from keyring.backend import KeyringBackend +# https://pypi.org/project/platformdirs +from platformdirs import user_config_path, user_log_path +# https://pypi.org/project/rich +from rich.console import Console +from rich.logging import RichHandler +# https://pypi.org/project/PyYAML +from yaml import safe_load + +from .ConfFileModel import ConfFileModel + +from pydantic import ValidationError +from yaml import YAMLError class InsecureKeyringBackend(KeyringBackend): @@ -25,6 +45,45 @@ class InsecureKeyringBackend(KeyringBackend): priority = 30 + # name of this application, used to determine local user folders: + # https://platformdirs.readthedocs.io/en/latest/api.html ... + # ... #platformdirs.api.PlatformDirsABC.appname + _application_name: str = 'InsecureKeyringBackend' + + # name of configuration file + _conf_file_name: str = 'keyring_insecure_backend.yaml' + + + def __init__(self) -> None: + """ + Initialize class instance. + + Args: + None. + + Returns: + None. + + Raises: + OSError, ValidationError, YAMLError. + """ + + # NOTE: keyring doesn't seem to do any proper exception handling: + # displays a message 'Error initializing plugin EntryPoint ... ', + # but then just spills any execeptions raised here with full stack + # TODO: keyring apparently calls this code twice + + super().__init__() + + try: + conf = self._proc_conf(self._application_name, self._conf_file_name) + self._set_up_logging(conf) + except (OSError, ValidationError, YAMLError): + raise + + info('InsecureKeyringBackend started ✅') + + def delete_password(self, service: str, username: str) -> None: """ TODO: doc @@ -47,3 +106,104 @@ def set_password(self, service: str, username: str, password: str) -> None: """ raise NotImplementedError + + + def _proc_conf(self, app_name: str, file_name: str) -> ConfFileModel: + """ + Load configuration file and prepare configuration for use. + + Args: + app_name: Name of this application. + file_name: Name of configuration file. + + Returns: + A ConfFileModel configuration object. + + Raises: + OSError, ValidationError, YAMLError. + """ + + path_to_conf_root = user_config_path(app_name) + path_to_log_root = user_log_path(app_name) + + path_to_conf_file = path_to_conf_root / file_name + + # NOTE: as opposed to e.g. json.loads(...), + # pyyaml does not support loading from text + # --> open file, load from file-like object + try: + with open(path_to_conf_file, 'r') as stream: + try: + conf_dict = safe_load(stream) + except YAMLError: + raise + except OSError: + raise + + try: + conf = ConfFileModel(**conf_dict) + except ValidationError: + raise + + conf.path_to_log_file = path_to_log_root / conf.path_to_log_file + + return conf + + + def _set_up_logging(self, conf: ConfFileModel) -> None: + """ + Set up logging to file configured in configuration file. + + Args: + conf: A ConfFileModel configuration object. + + Returns: + None. + + Raises: + OSError. + """ + + # rich > log to file: + # https://rich.readthedocs.io/en/stable/console.html#file-output + # + # NOTE: truncate log file during development: + # https://stackoverflow.com/a/13576390 + # $ > ~/Library/Logs/InsecureKeyringBackend/keyring_insecure_backend.log + + path_to_log_file = conf.path_to_log_file + + if not path_to_log_file.parent.exists(): + try: + path_to_log_file.parent.mkdir(parents=True) + except OSError: + raise + + # TODO: get these from conf + datefmt = '[%Y%m%d-%H%M%S]' + log_level = 'info' + + format = '$message' + style = '$' + + # NOTE: log file would usually be opened in a context, e.g. + # with open(path_to_log_file, 'a') as log_file: + # and code up to the basicConfig(...) call would indented; + # however, this fails upon the first info(...) call with + # ValueError: I/O operation on closed file. + # TODO: close file upon exit + + try: + log_file = open(path_to_log_file, 'a') + except OSError: + raise + + console = Console(file = log_file) + handlers = [RichHandler(console = console)] + # NOTE: this requires importing entire package using `import logging` + level = getattr(logging, log_level.upper()) + basicConfig(datefmt = datefmt, + format = format, + handlers = handlers, + level = level, + style = style) diff --git a/etc/keyring_insecure_backend.yaml b/etc/keyring_insecure_backend.yaml new file mode 100644 index 0000000..0865b42 --- /dev/null +++ b/etc/keyring_insecure_backend.yaml @@ -0,0 +1,23 @@ +--- +# keyring_insecure_backend.yaml +# +# Insecure Keyring Backend configuration. +# +# author : stefan schablowski +# contact : stefan.schablowski@desmodyne.com +# created : 2025-03-11 + + +# NOTE: this configuration file is expected at an OS-dependent user conf path; +# Linux / Ubuntu 24.04 LTS: +# ${HOME}/.config/InsecureKeyringBackend/keyring_insecure_backend.yaml +# macOS / Sonoma 14.7: +# ${HOME}/Library/Application Support/InsecureKeyringBackend/keyring_insecure_backend.yaml + + +# path to log file, relative to OS-dependent user log path; +# Linux / Ubuntu 24.04 LTS: +# ${HOME}/.local/state/InsecureKeyringBackend/log/ +# macOS / Sonoma 14.7: +# ${HOME}/Library/Logs/InsecureKeyringBackend/ +path_to_log_file: keyring_insecure_backend.log From 1806b19e505e5485c1470cbbfd53f2fd1d446964 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Thu, 13 Mar 2025 11:09:54 +0100 Subject: [PATCH 10/27] [#2]: add data file; add to conf file model/conf; use it; add func docs --- .../keyring_insecure_backend/ConfFileModel.py | 1 + .../InsecureKeyringBackend.py | 80 ++++++++++++++++--- data/keyring_insecure_backend.json | 4 + etc/keyring_insecure_backend.yaml | 7 ++ 4 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 data/keyring_insecure_backend.json diff --git a/code/python/keyring_insecure_backend/ConfFileModel.py b/code/python/keyring_insecure_backend/ConfFileModel.py index b87b729..ad06459 100644 --- a/code/python/keyring_insecure_backend/ConfFileModel.py +++ b/code/python/keyring_insecure_backend/ConfFileModel.py @@ -27,4 +27,5 @@ class ConfFileModel(BaseModel): """ model_config = ConfigDict(extra='forbid') + path_to_data_file: Path path_to_log_file: Path diff --git a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py index 3912321..a234993 100644 --- a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py +++ b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py @@ -18,12 +18,15 @@ # NOTE: need to import entire package import logging -from logging import basicConfig, info +from json import loads +from logging import basicConfig, info, warning # https://pypi.org/project/keyring from keyring.backend import KeyringBackend # https://pypi.org/project/platformdirs -from platformdirs import user_config_path, user_log_path +from platformdirs import (user_config_path, + user_data_path, + user_log_path) # https://pypi.org/project/rich from rich.console import Console from rich.logging import RichHandler @@ -32,6 +35,8 @@ from .ConfFileModel import ConfFileModel +from json import JSONDecodeError + from pydantic import ValidationError from yaml import YAMLError @@ -81,28 +86,83 @@ def __init__(self) -> None: except (OSError, ValidationError, YAMLError): raise - info('InsecureKeyringBackend started ✅') + path_to_data_file = conf.path_to_data_file + + message = f'load data file\n {path_to_data_file}' + try: + data_text = path_to_data_file.read_text() + except OSError: + raise + + try: + data_dict = loads(data_text) + self._logger.info(f'{message}: OK') + except (JSONDecodeError, UnicodeDecodeError): + raise + + self._data_dict = data_dict def delete_password(self, service: str, username: str) -> None: """ - TODO: doc + Delete password for and . + + Args: + service: Name of service; currently ignored. + username: Name of user; currently ignored. + + Returns: + Does not return; always raises NotImplementedError. + + Raises: + NotImplementedError. """ raise NotImplementedError - def get_password(self, service: str, username: str) -> str: + def get_password(self, service: str, username: str) -> str | None: """ - TODO: doc + Get password for and . + + Args: + service: Name of service to get password for. + username: Name of user; currently ignored. + + Returns: + The password; None if no password was found. + + Raises: + None. """ - return 'some password' + info( 'get_password:\n' + f' service: {service}\n' + f' username: {username} (ignored)') + + try: + password = self._data_dict[service] + info(f"found password '{password}' for service '{service}'") + except KeyError: + warning(f"failed to find a password for service '{service}'") + return None + + return password def set_password(self, service: str, username: str, password: str) -> None: """ - TODO: doc + Set password for and . + + Args: + service: Name of service; currently ignored. + username: Name of user; currently ignored. + + Returns: + Does not return; always raises NotImplementedError. + + Raises: + NotImplementedError. """ raise NotImplementedError @@ -124,6 +184,7 @@ def _proc_conf(self, app_name: str, file_name: str) -> ConfFileModel: """ path_to_conf_root = user_config_path(app_name) + path_to_data_root = user_data_path(app_name) path_to_log_root = user_log_path(app_name) path_to_conf_file = path_to_conf_root / file_name @@ -145,7 +206,8 @@ def _proc_conf(self, app_name: str, file_name: str) -> ConfFileModel: except ValidationError: raise - conf.path_to_log_file = path_to_log_root / conf.path_to_log_file + conf.path_to_data_file = path_to_data_root / conf.path_to_data_file + conf.path_to_log_file = path_to_log_root / conf.path_to_log_file return conf diff --git a/data/keyring_insecure_backend.json b/data/keyring_insecure_backend.json new file mode 100644 index 0000000..2b6d360 --- /dev/null +++ b/data/keyring_insecure_backend.json @@ -0,0 +1,4 @@ +{ + "https://gitlab.com/api/v4/projects/67672163/packages/pypi": "gldt-yshnTacpy6SChzkU9k7f", + "https://gitlab.com/api/v4/projects/67685669/packages/pypi": "gldt-yJs6cqHXow1f2mVpRg-9" +} diff --git a/etc/keyring_insecure_backend.yaml b/etc/keyring_insecure_backend.yaml index 0865b42..8782e0c 100644 --- a/etc/keyring_insecure_backend.yaml +++ b/etc/keyring_insecure_backend.yaml @@ -15,6 +15,13 @@ # ${HOME}/Library/Application Support/InsecureKeyringBackend/keyring_insecure_backend.yaml +# path to data file, relative to OS-dependent user data path; +# Linux / Ubuntu 24.04 LTS: +# ${HOME}/.local/share/InsecureKeyringBackend/ +# macOS / Sonoma 14.7: +# ${HOME}/Library/Application Support/InsecureKeyringBackend/ +path_to_data_file: keyring_insecure_backend.json + # path to log file, relative to OS-dependent user log path; # Linux / Ubuntu 24.04 LTS: # ${HOME}/.local/state/InsecureKeyringBackend/log/ From ac5da62bd7933dbe01151d7e1ef33d1ba13469d4 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Tue, 18 Mar 2025 09:12:59 +0100 Subject: [PATCH 11/27] [#2]: ruff doesn't show any live issues in VS Code; remove: $ (cd code/python && uv remove --group dev ruff) ... --- code/python/pyproject.toml | 4 +--- code/python/uv.lock | 32 +------------------------------- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/code/python/pyproject.toml b/code/python/pyproject.toml index 8f5f464..4cbba4e 100644 --- a/code/python/pyproject.toml +++ b/code/python/pyproject.toml @@ -45,9 +45,7 @@ version = '0.0.0' [dependency-groups] -dev = [ - "ruff>=0.9.10", -] +dev = [] # NOTE: this is required to make Keyring load this backend as plugin: diff --git a/code/python/uv.lock b/code/python/uv.lock index 34a2712..f6f2c50 100644 --- a/code/python/uv.lock +++ b/code/python/uv.lock @@ -185,11 +185,6 @@ dependencies = [ { name = "rich" }, ] -[package.dev-dependencies] -dev = [ - { name = "ruff" }, -] - [package.metadata] requires-dist = [ { name = "keyring", specifier = ">=25.6.0" }, @@ -200,7 +195,7 @@ requires-dist = [ ] [package.metadata.requires-dev] -dev = [{ name = "ruff", specifier = ">=0.9.10" }] +dev = [] [[package]] name = "markdown-it-py" @@ -415,31 +410,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, ] -[[package]] -name = "ruff" -version = "0.9.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/8e/fafaa6f15c332e73425d9c44ada85360501045d5ab0b81400076aff27cf6/ruff-0.9.10.tar.gz", hash = "sha256:9bacb735d7bada9cfb0f2c227d3658fc443d90a727b47f206fb33f52f3c0eac7", size = 3759776 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/b2/af7c2cc9e438cbc19fafeec4f20bfcd72165460fe75b2b6e9a0958c8c62b/ruff-0.9.10-py3-none-linux_armv6l.whl", hash = "sha256:eb4d25532cfd9fe461acc83498361ec2e2252795b4f40b17e80692814329e42d", size = 10049494 }, - { url = "https://files.pythonhosted.org/packages/6d/12/03f6dfa1b95ddd47e6969f0225d60d9d7437c91938a310835feb27927ca0/ruff-0.9.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:188a6638dab1aa9bb6228a7302387b2c9954e455fb25d6b4470cb0641d16759d", size = 10853584 }, - { url = "https://files.pythonhosted.org/packages/02/49/1c79e0906b6ff551fb0894168763f705bf980864739572b2815ecd3c9df0/ruff-0.9.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5284dcac6b9dbc2fcb71fdfc26a217b2ca4ede6ccd57476f52a587451ebe450d", size = 10155692 }, - { url = "https://files.pythonhosted.org/packages/5b/01/85e8082e41585e0e1ceb11e41c054e9e36fed45f4b210991052d8a75089f/ruff-0.9.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47678f39fa2a3da62724851107f438c8229a3470f533894b5568a39b40029c0c", size = 10369760 }, - { url = "https://files.pythonhosted.org/packages/a1/90/0bc60bd4e5db051f12445046d0c85cc2c617095c0904f1aa81067dc64aea/ruff-0.9.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99713a6e2766b7a17147b309e8c915b32b07a25c9efd12ada79f217c9c778b3e", size = 9912196 }, - { url = "https://files.pythonhosted.org/packages/66/ea/0b7e8c42b1ec608033c4d5a02939c82097ddcb0b3e393e4238584b7054ab/ruff-0.9.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524ee184d92f7c7304aa568e2db20f50c32d1d0caa235d8ddf10497566ea1a12", size = 11434985 }, - { url = "https://files.pythonhosted.org/packages/d5/86/3171d1eff893db4f91755175a6e1163c5887be1f1e2f4f6c0c59527c2bfd/ruff-0.9.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df92aeac30af821f9acf819fc01b4afc3dfb829d2782884f8739fb52a8119a16", size = 12155842 }, - { url = "https://files.pythonhosted.org/packages/89/9e/700ca289f172a38eb0bca752056d0a42637fa17b81649b9331786cb791d7/ruff-0.9.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de42e4edc296f520bb84954eb992a07a0ec5a02fecb834498415908469854a52", size = 11613804 }, - { url = "https://files.pythonhosted.org/packages/f2/92/648020b3b5db180f41a931a68b1c8575cca3e63cec86fd26807422a0dbad/ruff-0.9.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d257f95b65806104b6b1ffca0ea53f4ef98454036df65b1eda3693534813ecd1", size = 13823776 }, - { url = "https://files.pythonhosted.org/packages/5e/a6/cc472161cd04d30a09d5c90698696b70c169eeba2c41030344194242db45/ruff-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60dec7201c0b10d6d11be00e8f2dbb6f40ef1828ee75ed739923799513db24c", size = 11302673 }, - { url = "https://files.pythonhosted.org/packages/6c/db/d31c361c4025b1b9102b4d032c70a69adb9ee6fde093f6c3bf29f831c85c/ruff-0.9.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d838b60007da7a39c046fcdd317293d10b845001f38bcb55ba766c3875b01e43", size = 10235358 }, - { url = "https://files.pythonhosted.org/packages/d1/86/d6374e24a14d4d93ebe120f45edd82ad7dcf3ef999ffc92b197d81cdc2a5/ruff-0.9.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ccaf903108b899beb8e09a63ffae5869057ab649c1e9231c05ae354ebc62066c", size = 9886177 }, - { url = "https://files.pythonhosted.org/packages/00/62/a61691f6eaaac1e945a1f3f59f1eea9a218513139d5b6c2b8f88b43b5b8f/ruff-0.9.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f9567d135265d46e59d62dc60c0bfad10e9a6822e231f5b24032dba5a55be6b5", size = 10864747 }, - { url = "https://files.pythonhosted.org/packages/ee/94/2c7065e1d92a8a8a46d46d9c3cf07b0aa7e0a1e0153d74baa5e6620b4102/ruff-0.9.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f202f0d93738c28a89f8ed9eaba01b7be339e5d8d642c994347eaa81c6d75b8", size = 11360441 }, - { url = "https://files.pythonhosted.org/packages/a7/8f/1f545ea6f9fcd7bf4368551fb91d2064d8f0577b3079bb3f0ae5779fb773/ruff-0.9.10-py3-none-win32.whl", hash = "sha256:bfb834e87c916521ce46b1788fbb8484966e5113c02df216680102e9eb960029", size = 10247401 }, - { url = "https://files.pythonhosted.org/packages/4f/18/fb703603ab108e5c165f52f5b86ee2aa9be43bb781703ec87c66a5f5d604/ruff-0.9.10-py3-none-win_amd64.whl", hash = "sha256:f2160eeef3031bf4b17df74e307d4c5fb689a6f3a26a2de3f7ef4044e3c484f1", size = 11366360 }, - { url = "https://files.pythonhosted.org/packages/35/85/338e603dc68e7d9994d5d84f24adbf69bae760ba5efd3e20f5ff2cec18da/ruff-0.9.10-py3-none-win_arm64.whl", hash = "sha256:5fd804c0327a5e5ea26615550e706942f348b197d5475ff34c19733aee4b2e69", size = 10436892 }, -] - [[package]] name = "secretstorage" version = "3.3.3" From f1e01d4685e9527f53ae2726de5c5d2313755508 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Tue, 18 Mar 2025 09:14:12 +0100 Subject: [PATCH 12/27] [#2]: enable live issues in VS Code: add VS Code conf file --- .vscode/settings.json | 51 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0a5bd2e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,51 @@ +// settings.json +// +// Visual Studio Code workspace settings +// +// author : stefan schablowski +// contact : stefan.schablowski@desmodyne.com +// created : 2021-09-25 + + +// NOTE: file suffix says .json, but actual file format is some proprietary +// Microsoft mess: mostly a json file, but with JavaScript-style (?) comments; +// can't be read/modified or even displayed properly by regular json tools :-/ + +// NOTE: VS Code user ./. workspace settings: +// + user settings are stored in +// $HOME/Library/Application Support/Code/User/settings.json +// + workspace settings are stored in +// .../.vscode/settings.json +// + workspace settings take precedence over user settings: +// https://code.visualstudio.com/docs/getstarted/settings#_settings-precedence +// +// TODO: review VS Code settings, move from user to workspace settings +// TODO: review VS Code analysis / linting / etc. settings: +// https://code.visualstudio.com/docs/python/settings-reference + + +{ + // make auto indentation less dumb + "editor.autoIndent": "advanced", + // display a vertical line at 80 characters + "editor.rulers": [ 80 ], + + // remove trailing whitespace upon saving files + "files.trimTrailingWhitespace": true, + + // pylance > pyright settings + "python.analysis.diagnosticMode": "workspace", + "python.analysis.exclude": [ + "attic", + "code/python/.venv", + "doc/python/.venv", + "tmp" + ], + "python.analysis.typeCheckingMode": "strict", + + // NOTE: one single argument is probably not worth creating a shell frontend + "shellcheck.customArgs": [ + "--enable=all", + "-x" + ] +} From 9cf6a91c95ee3d1389bd7ba9bddcc14d10bb4926 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Tue, 18 Mar 2025 09:15:35 +0100 Subject: [PATCH 13/27] [#2]: suppress pyright issue; doc keyring issue; add doc and TODO --- .../InsecureKeyringBackend.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py index a234993..26c572b 100644 --- a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py +++ b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py @@ -46,8 +46,18 @@ class InsecureKeyringBackend(KeyringBackend): An insecure Keyring backend that stores secrets in plain text. """ - # TODO: doc - priority = 30 + # backend priority; see base class + # https://github.com/jaraco/keyring/blob/main/keyring/backend.py#L74 + # + # TODO: implementing this without typing issues + # requires importing / using custom property class: + # from keyring.compat import properties + # @properties.classproperty + # def priority(self) -> float: + # return 30 + # + # TODO: review supressed pyright issues + priority = 30 # pyright: ignore[reportAssignmentType] # name of this application, used to determine local user folders: From 3b2219be4a615b6298b755658484d3ae5e71a6c2 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Tue, 18 Mar 2025 09:25:51 +0100 Subject: [PATCH 14/27] [#2]: doc issues with rich logging ./. two backends; work around --- .../InsecureKeyringBackend.py | 117 +++++++++++++++--- 1 file changed, 99 insertions(+), 18 deletions(-) diff --git a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py index 26c572b..1e4bbb2 100644 --- a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py +++ b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py @@ -18,8 +18,7 @@ # NOTE: need to import entire package import logging -from json import loads -from logging import basicConfig, info, warning +from json import loads # https://pypi.org/project/keyring from keyring.backend import KeyringBackend @@ -146,15 +145,18 @@ def get_password(self, service: str, username: str) -> str | None: None. """ - info( 'get_password:\n' - f' service: {service}\n' - f' username: {username} (ignored)') + message = ( 'get_password:\n' + f' service: {service}\n' + f' username: {username} (ignored)') + self._logger.info(message) try: password = self._data_dict[service] - info(f"found password '{password}' for service '{service}'") + message = f"found password '{password}' for service '{service}'" + self._logger.info(message) except KeyError: - warning(f"failed to find a password for service '{service}'") + message = f"failed to find a password for service '{service}'" + self._logger.warning(message) return None return password @@ -252,12 +254,11 @@ def _set_up_logging(self, conf: ConfFileModel) -> None: raise # TODO: get these from conf + # TODO: use python logging configuration: + # https://docs.python.org/3/library/logging.config.html datefmt = '[%Y%m%d-%H%M%S]' log_level = 'info' - format = '$message' - style = '$' - # NOTE: log file would usually be opened in a context, e.g. # with open(path_to_log_file, 'a') as log_file: # and code up to the basicConfig(...) call would indented; @@ -270,12 +271,92 @@ def _set_up_logging(self, conf: ConfFileModel) -> None: except OSError: raise - console = Console(file = log_file) - handlers = [RichHandler(console = console)] + # NOTE: the usual way to set up logging using rich would be e.g. + # from logging import basicConfig + # # (more imports for rich Console, etc., see top of file) + # log_file = Path('/path/to/file.log') + # console = Console(file = log_file) + # handlers = [RichHandler(console = console)] + # basicConfig(datefmt = '[%Y%m%d-%H%M%S]', + # format = '$message', + # handlers = handlers, + # level = 'INFO', + # style = '$') + # this works only as long as there is only one single backend installed + # for keyring that uses rich; if there are two or more, logging breaks: + # if for example both backends use a log file as done above, log content + # for the first backend is sent to the log file of the second backend; + # cause: both use a logger called 'rich' by default + # fix: _add_ a logger (and a handler) with a unique name + # + # python > display existing loggers: + # https://stackoverflow.com/a/60381742 + # import logging + # loggers = [logging.getLogger()] + # loggers += [logging.getLogger(name) + # for name in logging.root.manager.loggerDict] + # from pprint import pprint + # print('loggers:') + # pprint(loggers) + # sample log output: + # loggers: + # [, + # , + # , + # , + # , + # , + # , + # ] + # + # NOTE: it seems importing anything from rich + # alone already creates a logger called 'rich': + # from rich.pretty import pretty_repr + # print(f'loggers:\n{pretty_repr(loggers)}') + # for some reason, keyring calls this code twice at initialization; + # the `import pretty_repr` seems to add the logger called rich: + # sample log output: + # loggers: + # [ + # , + # , + # , + # , + # , + # , + # , + # + # ] + # loggers: + # [ + # , + # , + # , + # , + # , + # , + # , + # , + # + # ] + # NOTE: this requires importing entire package using `import logging` - level = getattr(logging, log_level.upper()) - basicConfig(datefmt = datefmt, - format = format, - handlers = handlers, - level = level, - style = style) + level = getattr(logging, log_level.upper()) + + # TODO: really set log level in both handler and logger ? + console = Console(file = log_file) + handler = RichHandler(console = console, + level = level, + log_time_format = datefmt) + + # TODO: get this from conf + logger_name = 'keyring.backends.InsecureKeyringBackend' + logger = logging.getLogger(logger_name) + logger.setLevel(level) + + # NOTE: keyring calls this code twice; + # add a handler only if required: once + if not logger.hasHandlers(): + logger.addHandler(handler) + + self._logger = logger From a5bc49895c98470f0fabd34f3e349ce569f1d798 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Fri, 21 Mar 2025 12:04:07 +0100 Subject: [PATCH 15/27] [#2]: add a pydantic model for python logging configuration, many TODOs; use in own conf file model; add conf: works but logs to stdout, not file --- .../keyring_insecure_backend/ConfFileModel.py | 3 + .../ConfLoggingModel.py | 149 ++++++++++++++++++ .../InsecureKeyringBackend.py | 56 ++++++- etc/keyring_insecure_backend.yaml | 17 ++ 4 files changed, 218 insertions(+), 7 deletions(-) create mode 100644 code/python/keyring_insecure_backend/ConfLoggingModel.py diff --git a/code/python/keyring_insecure_backend/ConfFileModel.py b/code/python/keyring_insecure_backend/ConfFileModel.py index ad06459..7362075 100644 --- a/code/python/keyring_insecure_backend/ConfFileModel.py +++ b/code/python/keyring_insecure_backend/ConfFileModel.py @@ -17,6 +17,8 @@ # https://pypi.org/project/pydantic from pydantic import BaseModel, ConfigDict +from .ConfLoggingModel import ConfLoggingModel + # ----------------------------------------------------------------------------- # public types @@ -27,5 +29,6 @@ class ConfFileModel(BaseModel): """ model_config = ConfigDict(extra='forbid') + logging: ConfLoggingModel path_to_data_file: Path path_to_log_file: Path diff --git a/code/python/keyring_insecure_backend/ConfLoggingModel.py b/code/python/keyring_insecure_backend/ConfLoggingModel.py new file mode 100644 index 0000000..63ef730 --- /dev/null +++ b/code/python/keyring_insecure_backend/ConfLoggingModel.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# encoding: utf-8 + +""" +ConfLoggingModel.py + +Python logging configuration data model. + +author : stefan schablowski +contact : stefan.schablowski@desmodyne.com +created : 2025-03-18 +""" + + +# python > logging > configuration: +# https://docs.python.org/3/library/logging.config.html +# python > logging > configuration > data model: +# https://docs.python.org/3/library/logging.config.html#dictionary-schema-details + + +# TODO: add validators to ensure logging objects reference valid other objects +# TODO: support user-defined objects ? +# https://docs.python.org/3/library/logging.config.html#user-defined-objects + + +from typing import Any, Dict, List, Literal, Optional + +# https://pypi.org/project/pydantic +from pydantic import BaseModel, ConfigDict, Field + + +# ----------------------------------------------------------------------------- +# supporting types + +class ConfLoggingFilterModel(BaseModel): + """ + Pydantic helper model that defines logging filter conf attrbutes. + """ + + # https://docs.python.org/3/library/logging.html#logging.Filter + + model_config = ConfigDict(extra='forbid') + name: Optional[str] = None + + +class ConfLoggingFormatterModel(BaseModel): + """ + Pydantic helper model that defines logging formatter conf attrbutes. + """ + + # https://docs.python.org/3/library/logging.html#logging.Formatter + + model_config = ConfigDict(extra='forbid') + # NOTE: 'class' obviously is a python keyword; use alias to work around: + # https://stackoverflow.com/a/70584815 + # https://docs.pydantic.dev/latest/concepts/fields/#field-aliases + # TODO: restrict to dot notation / valid class names ? + class_: Optional[str] = Field(alias = 'class', default = None) + # TODO: verify this is a valid date format ? + datefmt: Optional[str] = None + defaults: Optional[Dict[str, Any]] = None + # TODO: verify this is a valid log message format ? + format: Optional[str] = None + # TODO: restrict to '%', '{' or '$' + style: Optional[str] = None + # NOTE: 'validate' is a BaseModel method; see class_ above + validate_: Optional[bool] = Field(alias = 'validate', default = None) + + +class ConfLoggingHandlerModel(BaseModel): + """ + Pydantic helper model that defines logging handler conf attrbutes. + """ + + # https://docs.python.org/3/library/logging.html#logging.Handler + # however, most useful info is in the handlers section at + # https://docs.python.org/3/library/logging.config.html ... + # ... #dictionary-schema-details + + # from above page: + # > All other keys are passed through as keyword + # > arguments to the handler’s constructor. + # --> do not forbid extra attributes as usual + model_config = ConfigDict(extra='allow') + class_: Optional[str] = Field(alias = 'class', default = None) + filters: Optional[List[str]] = None + formatter: Optional[str] = None + # TODO: restrict to python log levels (?) in upper case (?) + level: Optional[str] = None + + +class ConfLoggingLoggerModel(BaseModel): + """ + Pydantic helper model that defines logging logger conf attrbutes. + """ + + # https://docs.python.org/3/library/logging.html#logging.Logger + # however, most useful info is in the loggers section at + # https://docs.python.org/3/library/logging.config.html ... + # ... #dictionary-schema-details and at + # https://docs.python.org/3/library/logging.config.html ... + # ... #object-connections + + model_config = ConfigDict(extra='forbid') + filters: Optional[List[str]] = None + handlers: Optional[List[str]] = None + level: Optional[str] = None + propagate: Optional[bool] = None + + +# ----------------------------------------------------------------------------- +# public types + +class ConfLoggingModel(BaseModel): + """ + Pydantic model that defines python logging configuration attributes. + """ + + # TODO: python's interpretation of 'optional' seems inconsistent: + # + python logging configuration docs say attributes are optional: + # https://docs.python.org/3/library/logging.config.html ... + # ... #dictionary-schema-details > 'All other keys are optional' + # + from python typing docs at + # https://docs.python.org/3/library/typing.html#typing.Optional: + # > Optional[X] is equivalent to X | None (or Union[X, None]). + # however, using `Optional[...] = None` below as in e.g. + # formatters: Optional[Dict[str, ConfLoggingFormatterModel]] = None + # fails because python code seems to expect empty dictionaries: + # File ".../lib/python3.13/logging/config.py", line 581, in configure + # for name in formatters: + # ^^^^^^^^^^ + # TypeError: 'NoneType' object is not iterable + # workaround: use {} as default value instead of None + + model_config = ConfigDict(extra='forbid') + # NOTE: attribute order not alphabetical, but follows documentation at + # https://docs.python.org/3/library/logging.config.html ... + # ... #dictionary-schema-details + version: Literal[1] + formatters: Optional[Dict[str, ConfLoggingFormatterModel]] = {} + filters: Optional[Dict[str, ConfLoggingFilterModel]] = {} + handlers: Optional[Dict[str, ConfLoggingHandlerModel]] = {} + loggers: Optional[Dict[str, ConfLoggingLoggerModel]] = {} + # TODO: from https://docs.python.org/3/library/logging.config.html: + # > configuration will be as for any logger, + # > except that the propagate setting will not be applicable + root: Optional[ConfLoggingLoggerModel] = None + incremental: Optional[bool] = None + disable_existing_loggers: Optional[bool] = None diff --git a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py index 1e4bbb2..acc73b8 100644 --- a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py +++ b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py @@ -18,7 +18,8 @@ # NOTE: need to import entire package import logging -from json import loads +from json import loads +from logging.config import dictConfig # https://pypi.org/project/keyring from keyring.backend import KeyringBackend @@ -238,6 +239,53 @@ def _set_up_logging(self, conf: ConfFileModel) -> None: OSError. """ + # from rich.pretty import pretty_repr + # log_conf = pretty_repr(conf.logging.model_dump(by_alias = True)) + # print(f'conf.logging:\n{log_conf}') + # sample log output: + # conf.logging: + # { + # 'version': 1, + # 'formatters': {}, + # 'filters': {}, + # 'handlers': { + # 'rich': { + # 'class': 'rich.logging.RichHandler', + # 'filters': None, + # 'formatter': None, + # 'level': 'INFO', + # 'log_time_format': '[%Y%m%d-%H%M%S]' + # } + # }, + # 'loggers': { + # 'keyring.backends.InsecureKeyringBackend': { + # 'filters': None, + # 'handlers': ['rich'], + # 'level': 'INFO', + # 'propagate': None + # } + # }, + # 'root': None, + # 'incremental': None, + # 'disable_existing_loggers': None + # } + + # python > logging > Configuring Logging: + # https://docs.python.org/3/howto/logging.html#configuring-logging + # https://docs.python.org/3/library/ ... + # ... logging.config.html#logging.config.dictConfig + try: + # NOTE: pass by_alias to use e.g. class, not class_: + # https://stackoverflow.com/a/70584815 + dictConfig(conf.logging.model_dump(by_alias = True)) + except (AttributeError, ImportError, TypeError, ValueError): + raise + + logger_name = 'keyring.backends.InsecureKeyringBackend' + self._logger = logging.getLogger(logger_name) + + return + # rich > log to file: # https://rich.readthedocs.io/en/stable/console.html#file-output # @@ -253,12 +301,6 @@ def _set_up_logging(self, conf: ConfFileModel) -> None: except OSError: raise - # TODO: get these from conf - # TODO: use python logging configuration: - # https://docs.python.org/3/library/logging.config.html - datefmt = '[%Y%m%d-%H%M%S]' - log_level = 'info' - # NOTE: log file would usually be opened in a context, e.g. # with open(path_to_log_file, 'a') as log_file: # and code up to the basicConfig(...) call would indented; diff --git a/etc/keyring_insecure_backend.yaml b/etc/keyring_insecure_backend.yaml index 8782e0c..9384a04 100644 --- a/etc/keyring_insecure_backend.yaml +++ b/etc/keyring_insecure_backend.yaml @@ -15,6 +15,23 @@ # ${HOME}/Library/Application Support/InsecureKeyringBackend/keyring_insecure_backend.yaml +# logging configuration: +# https://docs.python.org/3/library/logging.config.html +logging: + version: 1 + handlers: + rich: + class: rich.logging.RichHandler + level: INFO + # rich.logging.RichHandler configuration attributes: + # https://rich.readthedocs.io/en/stable/reference/logging.html#logging + log_time_format: '[%Y%m%d-%H%M%S]' + loggers: + keyring.backends.InsecureKeyringBackend: + handlers: + - rich + level: INFO + # path to data file, relative to OS-dependent user data path; # Linux / Ubuntu 24.04 LTS: # ${HOME}/.local/share/InsecureKeyringBackend/ From 6c7391a11cdffbdbad3777ca6e3c321975d17e93 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Fri, 21 Mar 2025 12:07:40 +0100 Subject: [PATCH 16/27] [#2]: add pydantic model for rich logging conf extending logging conf --- .../keyring_insecure_backend/ConfFileModel.py | 5 +- .../ConfRichLoggingModel.py | 91 +++++++++++++++++++ 2 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 code/python/keyring_insecure_backend/ConfRichLoggingModel.py diff --git a/code/python/keyring_insecure_backend/ConfFileModel.py b/code/python/keyring_insecure_backend/ConfFileModel.py index 7362075..1240d9e 100644 --- a/code/python/keyring_insecure_backend/ConfFileModel.py +++ b/code/python/keyring_insecure_backend/ConfFileModel.py @@ -17,7 +17,7 @@ # https://pypi.org/project/pydantic from pydantic import BaseModel, ConfigDict -from .ConfLoggingModel import ConfLoggingModel +from .ConfRichLoggingModel import ConfRichLoggingModel # ----------------------------------------------------------------------------- @@ -29,6 +29,5 @@ class ConfFileModel(BaseModel): """ model_config = ConfigDict(extra='forbid') - logging: ConfLoggingModel + logging: ConfRichLoggingModel path_to_data_file: Path - path_to_log_file: Path diff --git a/code/python/keyring_insecure_backend/ConfRichLoggingModel.py b/code/python/keyring_insecure_backend/ConfRichLoggingModel.py new file mode 100644 index 0000000..f85b835 --- /dev/null +++ b/code/python/keyring_insecure_backend/ConfRichLoggingModel.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# encoding: utf-8 + +""" +ConfRichLoggingModel.py + +Rich logging configuration data model. + +author : stefan schablowski +contact : stefan.schablowski@desmodyne.com +created : 2025-03-18 +""" + + +# NOTE: this data model is based on the python logging configuration data model +# and extends it to support configuring logging to file using a rich Console; +# it follows the same concept: similar to formatters, filters, handlers and +# loggers that may reference other logging objects in the python logging conf, +# there is a dict of rich console confs and a dict of rich handler confs that +# may reference entries in said console conf dicts using a textual ID + + +from pathlib import Path +from typing import Dict, Optional + +# https://pypi.org/project/pydantic +from pydantic import BaseModel, ConfigDict + +from .ConfLoggingModel import ConfLoggingHandlerModel, ConfLoggingModel + + +# ----------------------------------------------------------------------------- +# supporting types + +class ConfRichConsoleModel(BaseModel): + """ + Pydantic helper model that defines a subset of rich console conf attrbutes. + """ + + # https://rich.readthedocs.io/en/stable/reference/console.html + + model_config = ConfigDict(extra='forbid') + # NOTE: rich docs ref'd above say `IO`, but that causes a pyright issue + # Expected type arguments for generic class "IO" + # --> use str type arg, although that assumes all log files contain strings + # NOTE: using IO[str] or TextIO as in e.g. + # file: Optional[IO[str]] = None + # fails with pydantic.errors.PydanticSchemaGenerationError: + # Unable to generate pydantic-core schema for typing.IO[str]. + # Set `arbitrary_types_allowed=True` in the model_config to + # ignore this error or implement `__get_pydantic_core_schema__` + # on your type to fully support it. + # If you got this error by calling handler() within + # `__get_pydantic_core_schema__` then you likely need to call + # `handler.generate_schema()` since we do not call + # `__get_pydantic_core_schema__` on `` otherwise + # to avoid infinite recursion. + # For further information visit + # https://errors.pydantic.dev/2.10/u/schema-for-unknown-type + # workaround: use file _path_ here, use file object at runtime + path_to_file: Optional[Path] = None + + +class ConfRichHandlerModel(ConfLoggingHandlerModel): + """ + Pydantic helper model that defines a subset of rich handler conf attrbutes. + """ + + # https://rich.readthedocs.io/en/stable/reference/logging.html + + # NOTE: need to allow extra attributes as in base class + model_config = ConfigDict(extra='allow') + console: Optional[str] = None + + +# ----------------------------------------------------------------------------- +# public types + +class ConfRichLoggingModel(ConfLoggingModel): + """ + Pydantic model that defines rich logging configuration attributes. + """ + + model_config = ConfigDict(extra='forbid') + consoles: Optional[Dict[str, ConfRichConsoleModel]] = {} + # TODO: review supressed pyright issue: + # "handlers" overrides symbol of same name in class "ConfLoggingModel" + # Variable is mutable so its type is invariant + # Override type "Dict[str, ConfRichHandlerModel] | None" is not the same + # as base type "Dict[str, ConfLoggingHandlerModel] | None" + handlers: Optional[Dict[str, ConfRichHandlerModel]] = {} # pyright: ignore[reportIncompatibleVariableOverride] From cee34a2fb4505d54c1a6d37661289d8d1a5bd15d Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Fri, 21 Mar 2025 12:09:48 +0100 Subject: [PATCH 17/27] [#2]: add conf for console and reference to it in handler conf --- etc/keyring_insecure_backend.yaml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/etc/keyring_insecure_backend.yaml b/etc/keyring_insecure_backend.yaml index 9384a04..07c8cd2 100644 --- a/etc/keyring_insecure_backend.yaml +++ b/etc/keyring_insecure_backend.yaml @@ -17,8 +17,20 @@ # logging configuration: # https://docs.python.org/3/library/logging.config.html +# NOTE: the model for this conf follows the python logging conf, +# but is extended to support configuring rich logging to file: +# .../code/python/keyring_insecure_backend/ConfRichLoggingModel.py logging: version: 1 + consoles: + rich: + # path to log file, relative to OS-dependent user log path; + # Linux / Ubuntu 24.04 LTS: + # ${HOME}/.local/state/InsecureKeyringBackend/log/ + # macOS / Sonoma 14.7: + # ${HOME}/Library/Logs/InsecureKeyringBackend/ + path_to_file: keyring_insecure_backend.log + # TODO: really set log level in both handler and logger ? handlers: rich: class: rich.logging.RichHandler @@ -26,6 +38,8 @@ logging: # rich.logging.RichHandler configuration attributes: # https://rich.readthedocs.io/en/stable/reference/logging.html#logging log_time_format: '[%Y%m%d-%H%M%S]' + # ConfRichHandlerModel configuration attributes + console: rich loggers: keyring.backends.InsecureKeyringBackend: handlers: @@ -38,10 +52,3 @@ logging: # macOS / Sonoma 14.7: # ${HOME}/Library/Application Support/InsecureKeyringBackend/ path_to_data_file: keyring_insecure_backend.json - -# path to log file, relative to OS-dependent user log path; -# Linux / Ubuntu 24.04 LTS: -# ${HOME}/.local/state/InsecureKeyringBackend/log/ -# macOS / Sonoma 14.7: -# ${HOME}/Library/Logs/InsecureKeyringBackend/ -path_to_log_file: keyring_insecure_backend.log From 294d5b5d9e828c0b79d1952505daae089e87ab52 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Fri, 21 Mar 2025 14:51:23 +0100 Subject: [PATCH 18/27] [#2]: move sample log output to match flow and simplify coming changes --- .../InsecureKeyringBackend.py | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py index acc73b8..4cd256d 100644 --- a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py +++ b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py @@ -239,36 +239,6 @@ def _set_up_logging(self, conf: ConfFileModel) -> None: OSError. """ - # from rich.pretty import pretty_repr - # log_conf = pretty_repr(conf.logging.model_dump(by_alias = True)) - # print(f'conf.logging:\n{log_conf}') - # sample log output: - # conf.logging: - # { - # 'version': 1, - # 'formatters': {}, - # 'filters': {}, - # 'handlers': { - # 'rich': { - # 'class': 'rich.logging.RichHandler', - # 'filters': None, - # 'formatter': None, - # 'level': 'INFO', - # 'log_time_format': '[%Y%m%d-%H%M%S]' - # } - # }, - # 'loggers': { - # 'keyring.backends.InsecureKeyringBackend': { - # 'filters': None, - # 'handlers': ['rich'], - # 'level': 'INFO', - # 'propagate': None - # } - # }, - # 'root': None, - # 'incremental': None, - # 'disable_existing_loggers': None - # } # python > logging > Configuring Logging: # https://docs.python.org/3/howto/logging.html#configuring-logging @@ -384,6 +354,42 @@ def _set_up_logging(self, conf: ConfFileModel) -> None: # NOTE: this requires importing entire package using `import logging` level = getattr(logging, log_level.upper()) + # from rich.pretty import pretty_repr + # log_conf = pretty_repr(conf.logging.model_dump(by_alias = True)) + # print(f'conf.logging:\n{log_conf}') + # sample log output: + # conf.logging: + # { + # 'version': 1, + # 'formatters': {}, + # 'filters': {}, + # 'handlers': { + # 'rich': { + # 'class': 'rich.logging.RichHandler', + # 'filters': None, + # 'formatter': None, + # 'level': 'INFO', + # 'console': 'rich', + # 'log_time_format': '[%Y%m%d-%H%M%S]' + # } + # }, + # 'loggers': { + # 'keyring.backends.InsecureKeyringBackend': { + # 'filters': None, + # 'handlers': ['rich'], + # 'level': 'INFO', + # 'propagate': None + # } + # }, + # 'root': None, + # 'incremental': None, + # 'disable_existing_loggers': None, + # 'consoles': { + # 'rich': { + # 'path_to_file': PosixPath('/Users/ssc/Library/Logs/InsecureKeyringBackend/keyring_insecure_backend.log') + # } + # } + # } # TODO: really set log level in both handler and logger ? console = Console(file = log_file) From 9cdf830ed8ff4b2ca8c1083b5c5ddada050ac60a Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Fri, 21 Mar 2025 14:52:41 +0100 Subject: [PATCH 19/27] [#2]: adapt to reviewed conf data model: prep log root wherever needed --- .../keyring_insecure_backend/InsecureKeyringBackend.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py index 4cd256d..9f3f158 100644 --- a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py +++ b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py @@ -220,7 +220,13 @@ def _proc_conf(self, app_name: str, file_name: str) -> ConfFileModel: raise conf.path_to_data_file = path_to_data_root / conf.path_to_data_file - conf.path_to_log_file = path_to_log_root / conf.path_to_log_file + + console_confs = conf.logging.consoles + if console_confs: + for console_conf in console_confs.values(): + if console_conf.path_to_file: + console_conf.path_to_file \ + = path_to_log_root / console_conf.path_to_file return conf From f61895e46e1d64a9749b14653e809b2a2b598546 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Fri, 21 Mar 2025 14:58:13 +0100 Subject: [PATCH 20/27] [#2]: refactor to using reviewed conf data model: process console confs, then process handler confs, finally call dictConfig(...) with the result --- .../InsecureKeyringBackend.py | 134 +++++++++++------- 1 file changed, 79 insertions(+), 55 deletions(-) diff --git a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py index 9f3f158..1f29aaf 100644 --- a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py +++ b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py @@ -20,6 +20,7 @@ from json import loads from logging.config import dictConfig +from typing import Dict # https://pypi.org/project/keyring from keyring.backend import KeyringBackend @@ -29,7 +30,6 @@ user_log_path) # https://pypi.org/project/rich from rich.console import Console -from rich.logging import RichHandler # https://pypi.org/project/PyYAML from yaml import safe_load @@ -245,50 +245,13 @@ def _set_up_logging(self, conf: ConfFileModel) -> None: OSError. """ - - # python > logging > Configuring Logging: - # https://docs.python.org/3/howto/logging.html#configuring-logging - # https://docs.python.org/3/library/ ... - # ... logging.config.html#logging.config.dictConfig - try: - # NOTE: pass by_alias to use e.g. class, not class_: - # https://stackoverflow.com/a/70584815 - dictConfig(conf.logging.model_dump(by_alias = True)) - except (AttributeError, ImportError, TypeError, ValueError): - raise - - logger_name = 'keyring.backends.InsecureKeyringBackend' - self._logger = logging.getLogger(logger_name) - - return - # rich > log to file: # https://rich.readthedocs.io/en/stable/console.html#file-output # # NOTE: truncate log file during development: # https://stackoverflow.com/a/13576390 # $ > ~/Library/Logs/InsecureKeyringBackend/keyring_insecure_backend.log - - path_to_log_file = conf.path_to_log_file - - if not path_to_log_file.parent.exists(): - try: - path_to_log_file.parent.mkdir(parents=True) - except OSError: - raise - - # NOTE: log file would usually be opened in a context, e.g. - # with open(path_to_log_file, 'a') as log_file: - # and code up to the basicConfig(...) call would indented; - # however, this fails upon the first info(...) call with - # ValueError: I/O operation on closed file. - # TODO: close file upon exit - - try: - log_file = open(path_to_log_file, 'a') - except OSError: - raise - + # # NOTE: the usual way to set up logging using rich would be e.g. # from logging import basicConfig # # (more imports for rich Console, etc., see top of file) @@ -358,8 +321,6 @@ def _set_up_logging(self, conf: ConfFileModel) -> None: # # ] - # NOTE: this requires importing entire package using `import logging` - level = getattr(logging, log_level.upper()) # from rich.pretty import pretty_repr # log_conf = pretty_repr(conf.logging.model_dump(by_alias = True)) # print(f'conf.logging:\n{log_conf}') @@ -397,20 +358,83 @@ def _set_up_logging(self, conf: ConfFileModel) -> None: # } # } - # TODO: really set log level in both handler and logger ? - console = Console(file = log_file) - handler = RichHandler(console = console, - level = level, - log_time_format = datefmt) + # for each console configured to write to log file: + # prepare file and create a Console object + console_confs = conf.logging.consoles + # dict with console IDs as keys and Console objects as values + consoles: Dict[str, Console] = {} + + if console_confs: + for console_id, console_conf in console_confs.items(): + path_to_file = console_conf.path_to_file + if path_to_file: + if not path_to_file.parent.exists(): + try: + path_to_file.parent.mkdir(parents=True) + except OSError: + raise + + # NOTE: log file would usually be opened in a context: + # with open(path_to_log_file, 'a') as log_file: + # ... + # but this fails upon the first logging call with + # ValueError: I/O operation on closed file. + # --> open file conventionally / old school + # TODO: close file upon exit + try: + log_file = open(path_to_file, 'a') + except OSError: + raise + + # TODO: pass remaing conf in Console(...) call + consoles[console_id] = Console(file = log_file) + + + # for each handler configured to use a console: + # in handler conf, replace console id by Console object + # + # NOTE: strictly speaking, this approach mixes configuration and objects + # - but it drastically simplifies configuring logging: the alternative + # would be to call dictConfig(...) first and then - afterwards - update + # handlers that log to a file with the rich Console, i.e. remove them + # from logging, create new objects, re-add those, etc. pp. + # NOTE: can not modify handler conf in place: pydantic defines console + # attribute data type as str, setting it to Console object fails with + # UserWarning: Pydantic serializer warnings: + # Expected `str` but got `Console` with value `` + # - serialized value may not be as expected + # --> turn pydantic conf data model instance into dict and modify that + # NOTE: pass by_alias to use e.g. class, not class_: + # https://stackoverflow.com/a/70584815 + logging_conf = conf.logging.model_dump(by_alias = True) + handler_confs = logging_conf['handlers'] + + if handler_confs: + for handler_id, handler_conf in handler_confs.items(): + # console_id = handler_conf.console + console_id = handler_conf['console'] + if console_id: + try: + console = consoles[console_id] + except KeyError: + message = ( 'failed to find a configuration ' + f"for a console with ID '{console_id}' " + f"used by handler with ID '{handler_id}'") + raise RuntimeError(message) + + # replace console id by console object + handler_conf['console'] = console - # TODO: get this from conf - logger_name = 'keyring.backends.InsecureKeyringBackend' - logger = logging.getLogger(logger_name) - logger.setLevel(level) - # NOTE: keyring calls this code twice; - # add a handler only if required: once - if not logger.hasHandlers(): - logger.addHandler(handler) + # python > logging > Configuring Logging: + # https://docs.python.org/3/howto/logging.html#configuring-logging + # https://docs.python.org/3/library/ ... + # ... logging.config.html#logging.config.dictConfig + try: + dictConfig(logging_conf) + except (AttributeError, ImportError, TypeError, ValueError): + raise - self._logger = logger + # TODO: how to determine which logger to use ? + logger_name = 'keyring.backends.InsecureKeyringBackend' + self._logger = logging.getLogger(logger_name) From b111e67cc95a5cbbe51df5a428e0303b256759a0 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Fri, 21 Mar 2025 18:38:54 +0100 Subject: [PATCH 21/27] [#2]: review approach to methods: do not return values, but set members; init members to None so they always exist and to list them all; move secrets setup to own method; rename _data_dict to _secrets; verify there is at least one logger conf'd, use first if more than one --- .../InsecureKeyringBackend.py | 132 +++++++++++++----- 1 file changed, 94 insertions(+), 38 deletions(-) diff --git a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py index 1f29aaf..40feb04 100644 --- a/code/python/keyring_insecure_backend/InsecureKeyringBackend.py +++ b/code/python/keyring_insecure_backend/InsecureKeyringBackend.py @@ -15,12 +15,10 @@ # TODO: review exception handling, establish / align with conv -# NOTE: need to import entire package -import logging - from json import loads +from logging import Logger, getLogger from logging.config import dictConfig -from typing import Dict +from typing import Dict, Optional # https://pypi.org/project/keyring from keyring.backend import KeyringBackend @@ -80,7 +78,11 @@ def __init__(self) -> None: None. Raises: - OSError, ValidationError, YAMLError. + JSONDecodeError, + OSError, + UnicodeDecodeError, + ValidationError, + YAMLError. """ # NOTE: keyring doesn't seem to do any proper exception handling: @@ -90,28 +92,21 @@ def __init__(self) -> None: super().__init__() - try: - conf = self._proc_conf(self._application_name, self._conf_file_name) - self._set_up_logging(conf) - except (OSError, ValidationError, YAMLError): - raise + self._conf: Optional[ConfFileModel] = None + self._logger: Optional[Logger] = None + self._secrets: Optional[Dict[str, str]] = None - path_to_data_file = conf.path_to_data_file - - message = f'load data file\n {path_to_data_file}' try: - data_text = path_to_data_file.read_text() - except OSError: - raise - - try: - data_dict = loads(data_text) - self._logger.info(f'{message}: OK') - except (JSONDecodeError, UnicodeDecodeError): + self._proc_conf(self._application_name, self._conf_file_name) + self._set_up_logging() + self._set_up_secrets() + except (JSONDecodeError, + OSError, + UnicodeDecodeError, + ValidationError, + YAMLError): raise - self._data_dict = data_dict - def delete_password(self, service: str, username: str) -> None: """ @@ -146,18 +141,25 @@ def get_password(self, service: str, username: str) -> str | None: None. """ + # fix pyright issue + if not self._secrets: + return + message = ( 'get_password:\n' f' service: {service}\n' f' username: {username} (ignored)') - self._logger.info(message) + if self._logger: + self._logger.info(message) try: - password = self._data_dict[service] + password = self._secrets[service] message = f"found password '{password}' for service '{service}'" - self._logger.info(message) + if self._logger: + self._logger.info(message) except KeyError: message = f"failed to find a password for service '{service}'" - self._logger.warning(message) + if self._logger: + self._logger.warning(message) return None return password @@ -181,7 +183,7 @@ def set_password(self, service: str, username: str, password: str) -> None: raise NotImplementedError - def _proc_conf(self, app_name: str, file_name: str) -> ConfFileModel: + def _proc_conf(self, app_name: str, file_name: str) -> None: """ Load configuration file and prepare configuration for use. @@ -190,7 +192,7 @@ def _proc_conf(self, app_name: str, file_name: str) -> ConfFileModel: file_name: Name of configuration file. Returns: - A ConfFileModel configuration object. + None. Raises: OSError, ValidationError, YAMLError. @@ -228,15 +230,15 @@ def _proc_conf(self, app_name: str, file_name: str) -> ConfFileModel: console_conf.path_to_file \ = path_to_log_root / console_conf.path_to_file - return conf + self._conf = conf - def _set_up_logging(self, conf: ConfFileModel) -> None: + def _set_up_logging(self) -> None: """ - Set up logging to file configured in configuration file. + Set up logging as configured in configuration file. Args: - conf: A ConfFileModel configuration object. + None. Returns: None. @@ -358,9 +360,15 @@ def _set_up_logging(self, conf: ConfFileModel) -> None: # } # } + # fix pyright issue + if not self._conf: + return + + # TODO: access conf only once at a single location + # for each console configured to write to log file: # prepare file and create a Console object - console_confs = conf.logging.consoles + console_confs = self._conf.logging.consoles # dict with console IDs as keys and Console objects as values consoles: Dict[str, Console] = {} @@ -406,7 +414,7 @@ def _set_up_logging(self, conf: ConfFileModel) -> None: # --> turn pydantic conf data model instance into dict and modify that # NOTE: pass by_alias to use e.g. class, not class_: # https://stackoverflow.com/a/70584815 - logging_conf = conf.logging.model_dump(by_alias = True) + logging_conf = self._conf.logging.model_dump(by_alias = True) handler_confs = logging_conf['handlers'] if handler_confs: @@ -435,6 +443,54 @@ def _set_up_logging(self, conf: ConfFileModel) -> None: except (AttributeError, ImportError, TypeError, ValueError): raise - # TODO: how to determine which logger to use ? - logger_name = 'keyring.backends.InsecureKeyringBackend' - self._logger = logging.getLogger(logger_name) + logger_confs = self._conf.logging.loggers + + if logger_confs: + if len(logger_confs) > 1: + message = ( 'WARNING: found more than one logger configurations; ' + f'using the first one, ignoring all others') + print(message) + + logger_name = list(logger_confs.keys())[0] + self._logger = getLogger(logger_name) + + else: + message = ( 'WARNING: failed to find any logger configurations; ' + f'logging disabled') + print(message) + + + def _set_up_secrets(self) -> None: + """ + Set up service : password dict. + + Args: + None. + + Returns: + None. + + Raises: + JSONDecodeError, OSError, UnicodeDecodeError. + """ + + # fix pyright issue + if not self._conf: + return + + path_to_data_file = self._conf.path_to_data_file + + message = f'load data file\n {path_to_data_file}' + try: + data_text = path_to_data_file.read_text() + except OSError: + raise + + try: + data_dict = loads(data_text) + if self._logger: + self._logger.info(f'{message}: OK') + except (JSONDecodeError, UnicodeDecodeError): + raise + + self._secrets = data_dict From 6ab2ec5f6f7d57f448d2be1ffdc37b532dd93d07 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Sat, 22 Mar 2025 08:28:09 +0100 Subject: [PATCH 22/27] [#2]: gitlab.com project deploy tokens were rotated: update local copies --- data/keyring_insecure_backend.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/keyring_insecure_backend.json b/data/keyring_insecure_backend.json index 2b6d360..acdf6db 100644 --- a/data/keyring_insecure_backend.json +++ b/data/keyring_insecure_backend.json @@ -1,4 +1,4 @@ { - "https://gitlab.com/api/v4/projects/67672163/packages/pypi": "gldt-yshnTacpy6SChzkU9k7f", - "https://gitlab.com/api/v4/projects/67685669/packages/pypi": "gldt-yJs6cqHXow1f2mVpRg-9" + "https://gitlab.com/api/v4/projects/67672163/packages/pypi": "gldt-f5cdihjCHbJwvNhcTabz", + "https://gitlab.com/api/v4/projects/67685669/packages/pypi": "gldt-pUesAqQG4Nvaw5oUyNC3" } From 1ad9466feddab549d6c0432c764d5ba7bc0e27f1 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Sun, 23 Mar 2025 07:01:46 +0100 Subject: [PATCH 23/27] [#2]: add first GitHub Action / CI/CD conf: do setup, run `uv sync` --- .github/workflows/sync.yaml | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/sync.yaml diff --git a/.github/workflows/sync.yaml b/.github/workflows/sync.yaml new file mode 100644 index 0000000..2efcb4a --- /dev/null +++ b/.github/workflows/sync.yaml @@ -0,0 +1,38 @@ +--- +# sync.yaml +# +# GitHub Action Sync Workflow CI/CD configuration. +# +# author : stefan schablowski +# contact : stefan.schablowski@desmodyne.com +# created : 2025-03-23 + + +# uv > Using uv in GitHub Actions: +# https://docs.astral.sh/uv/guides/integration/github + + +name: Sync +on: [push] + +jobs: + sync: + name: sync + runs-on: ubuntu-latest + + steps: + - name: Check out + uses: actions/checkout@v4 + + - name: Set up uv + uses: astral-sh/setup-uv@v5 + with: + version: 0.6.9 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version-file: code/python/pyproject.toml + + - name: Run `uv sync` + run: uv sync --directory code/python From e7b17d67fecdda8f599b22ad5125f58cd951a6c7 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Sun, 23 Mar 2025 07:34:10 +0100 Subject: [PATCH 24/27] [#2]: add second GitHub Action / CI/CD conf: do setup, run `uv build`; make this the lead doc: first alphabetically --> likely first looked at --- .github/workflows/build.yaml | 38 ++++++++++++++++++++++++++++++++++++ .github/workflows/sync.yaml | 3 +-- 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/build.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..faab8b7 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,38 @@ +--- +# build.yaml +# +# GitHub Action Build Workflow CI/CD configuration. +# +# author : stefan schablowski +# contact : stefan.schablowski@desmodyne.com +# created : 2025-03-23 + + +# uv > Using uv in GitHub Actions: +# https://docs.astral.sh/uv/guides/integration/github + + +name: Build +on: [push] + +jobs: + build: + name: build + runs-on: ubuntu-latest + + steps: + - name: Check out + uses: actions/checkout@v4 + + - name: Set up uv + uses: astral-sh/setup-uv@v5 + with: + version: 0.6.9 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version-file: code/python/pyproject.toml + + - name: Run `uv build` + run: uv build --directory code/python diff --git a/.github/workflows/sync.yaml b/.github/workflows/sync.yaml index 2efcb4a..866c5ff 100644 --- a/.github/workflows/sync.yaml +++ b/.github/workflows/sync.yaml @@ -8,8 +8,7 @@ # created : 2025-03-23 -# uv > Using uv in GitHub Actions: -# https://docs.astral.sh/uv/guides/integration/github +# see ./build.yaml name: Sync From 389a002dc40dace1e2bc25ac9256fe8f1ce3ae24 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Sun, 23 Mar 2025 07:49:31 +0100 Subject: [PATCH 25/27] [#2]: add third GitHub Action / CI/CD conf: do setup, run `uv publish`; fails with `error: No files found to publish` (presumably) because artifacts / package was built in build workflow, is not available here --- .github/workflows/publish.yaml | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/publish.yaml diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..a618778 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,37 @@ +--- +# publish.yaml +# +# GitHub Action Publish Workflow CI/CD configuration. +# +# author : stefan schablowski +# contact : stefan.schablowski@desmodyne.com +# created : 2025-03-23 + + +# see ./build.yaml + + +name: Publish +on: [push] + +jobs: + publish: + name: publish + runs-on: ubuntu-latest + + steps: + - name: Check out + uses: actions/checkout@v4 + + - name: Set up uv + uses: astral-sh/setup-uv@v5 + with: + version: 0.6.9 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version-file: code/python/pyproject.toml + + - name: Run `uv publish` + run: uv publish --directory code/python From 14e3649ab5c60325b5f9ee668caa556fa3e5c0ce Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Sun, 23 Mar 2025 09:15:54 +0100 Subject: [PATCH 26/27] [#2]: add link to medium.com article, update Publish GitHub Action --- .github/workflows/publish.yaml | 56 +++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index a618778..13e6e70 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -8,15 +8,25 @@ # created : 2025-03-23 -# see ./build.yaml +# Publishing a Python Package from GitHub to PyPI in 2024: +# https://medium.com/@blackary/ ... +# ... publishing-a-python-package-from-github-to-pypi-in-2024-a6fb8635d45d +# configuration in here adapted from "Step 3" name: Publish -on: [push] +on: + release: + types: [created] jobs: publish: + environment: + name: pypi + url: https://pypi.org/p/keyring-insecure-backend name: publish + permissions: + id-token: write runs-on: ubuntu-latest steps: @@ -33,5 +43,43 @@ jobs: with: python-version-file: code/python/pyproject.toml - - name: Run `uv publish` - run: uv publish --directory code/python + # NOTE: these two jobs don't agree on where the package is built: + # + `uv build` out of the box builds package in .../code/python/dist + # + `gh-action-pypi-publish` action fails with error message + # No such file or directory: '/github/workspace/dist' + # two options: + # 1. in `uv build` call, add `--out-dir`: + # https://docs.astral.sh/uv/reference/cli/#uv-build--out-dir + # 2. in `gh-action-pypi-publish` action, configure `packages-dir`: + # https://github.com/marketplace/actions/pypi-publish ... + # ... #customizing-target-package-dists-directory, e.g. + # with: + # packages-dir: code/python/dist + # --> use first option so only one job requires customizing + # TODO: don't run `uv build` here again, but set up artifact in build job + + - name: Run `uv build` + run: uv build --directory code/python --out-dir /github/workspace/dist + + # TODO: from the GitHub action log: + # Checking code/python/dist/keyring_insecure_backend-0.0.1-py3-none-any.whl: PASSED with warnings + # WARNING `long_description_content_type` missing. defaulting to `text/x-rst`. + # WARNING `long_description` missing. + # Checking code/python/dist/keyring_insecure_backend-0.0.1.tar.gz: PASSED with warnings + # WARNING `long_description_content_type` missing. defaulting to `text/x-rst`. + # WARNING `long_description` missing. + # also, from https://pypi.org/project/keyring-insecure-backend: + # The author of this package has not provided a project description + # --> `long_description` was supported in `setup.py` (or `setup.cfg`): + # https://github.com/pypi/warehouse/issues/4036 + # https://packaging.python.org/en/latest/guides/ ... + # ... making-a-pypi-friendly-readme/#including- ... + # ... your-readme-in-your-package-s-metadata + # https://stackoverflow.com/a/58062850 + # https://github.com/pypa/packaging.python.org/ ... + # ... issues/1535#issuecomment-2072711593 + # but is not supported in pyproject.toml 🤦🏻‍♂️ + # python packaging is just a sad laughing stock 😞 + # TODO: see e.g. https://pypi.org/project/uv for possible project info + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 From 548dc9fa6a37d1819b2b74708e1d5cf304dd8ce7 Mon Sep 17 00:00:00 2001 From: Stefan Schablowski Date: Sun, 23 Mar 2025 09:19:14 +0100 Subject: [PATCH 27/27] [#2]: manually increase project version number: 0.0.0 -> 0.0.1 --- code/python/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/python/pyproject.toml b/code/python/pyproject.toml index 4cbba4e..15fdaef 100644 --- a/code/python/pyproject.toml +++ b/code/python/pyproject.toml @@ -41,7 +41,7 @@ description = 'An insecure Keyring backend that stores secrets in plain t license = { text = 'MIT' } name = 'keyring-insecure-backend' requires-python = '>=3.10' -version = '0.0.0' +version = '0.0.1' [dependency-groups]