logistro-2.0.1/0000755000175000017500000000000015151314515016715 5ustar debian_developerdebian_developerlogistro-2.0.1/uv.lock0000644000175000017500000011567015101271261020226 0ustar debian_developerdebian_developerversion = 1 revision = 3 requires-python = ">=3.8" resolution-markers = [ "python_full_version >= '3.9'", "python_full_version < '3.9'", ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "exceptiongroup" version = "1.2.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, ] [[package]] name = "execnet" version = "2.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524, upload-time = "2024-04-08T09:04:19.245Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612, upload-time = "2024-04-08T09:04:17.414Z" }, ] [[package]] name = "iniconfig" version = "2.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, ] [[package]] name = "logistro" source = { editable = "." } [package.dev-dependencies] dev = [ { name = "poethepoet", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "poethepoet", version = "0.32.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "pyright" }, { name = "pytest" }, { name = "pytest-xdist" }, ] [package.metadata] [package.metadata.requires-dev] dev = [ { name = "poethepoet", specifier = ">=0.30.0" }, { name = "pyright", specifier = ">=1.1.406" }, { name = "pytest", specifier = ">=8.3.4" }, { name = "pytest-xdist" }, ] [[package]] name = "nodeenv" version = "1.9.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] [[package]] name = "packaging" version = "24.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, ] [[package]] name = "pastel" version = "0.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/76/f1/4594f5e0fcddb6953e5b8fe00da8c317b8b41b547e2b3ae2da7512943c62/pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d", size = 7555, upload-time = "2020-09-16T19:21:12.43Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/aa/18/a8444036c6dd65ba3624c63b734d3ba95ba63ace513078e1580590075d21/pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364", size = 5955, upload-time = "2020-09-16T19:21:11.409Z" }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] [[package]] name = "poethepoet" version = "0.30.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] dependencies = [ { name = "pastel", marker = "python_full_version < '3.9'" }, { name = "pyyaml", marker = "python_full_version < '3.9'" }, { name = "tomli", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/07/dfaed168414cf1e10f5c90860cdc29ffd871df80be81f3d7abd0451a4508/poethepoet-0.30.0.tar.gz", hash = "sha256:9f7ccda2d6525616ce989ca8ef973739fd668f50bef0b9d3631421d504d9ae4a", size = 60139, upload-time = "2024-11-09T22:16:58.189Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/25/98/12bff83ac39ba78ba4736c2f217bab294187de5d71ffbfeb3e126c230704/poethepoet-0.30.0-py3-none-any.whl", hash = "sha256:bf875741407a98da9e96f2f2d0b2c4c34f56d89939a7f53a4b6b3a64b546ec4e", size = 78036, upload-time = "2024-11-09T22:16:56.712Z" }, ] [[package]] name = "poethepoet" version = "0.32.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.9'", ] dependencies = [ { name = "pastel", marker = "python_full_version >= '3.9'" }, { name = "pyyaml", marker = "python_full_version >= '3.9'" }, { name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ee/c6/4bc7e21166726fc96f82f58b31fd032fdf8864d3aa17e2622578cb96c24d/poethepoet-0.32.2.tar.gz", hash = "sha256:1d68871dac1b191e27bd68fea57d0e01e9afbba3fcd01dbe6f6bc3fcb071fe4c", size = 61381, upload-time = "2025-01-26T19:53:37.638Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b1/1f/4e7a9b6b33a085172a826d1f9d0a19a2e77982298acea13d40442f14ef28/poethepoet-0.32.2-py3-none-any.whl", hash = "sha256:97e165de8e00b07d33fd8d72896fad8b20ccafcd327b1118bb6a3da26af38d33", size = 81726, upload-time = "2025-01-26T19:53:35.45Z" }, ] [[package]] name = "pyright" version = "1.1.406" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f7/16/6b4fbdd1fef59a0292cbb99f790b44983e390321eccbc5921b4d161da5d1/pyright-1.1.406.tar.gz", hash = "sha256:c4872bc58c9643dac09e8a2e74d472c62036910b3bd37a32813989ef7576ea2c", size = 4113151, upload-time = "2025-10-02T01:04:45.488Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f6/a2/e309afbb459f50507103793aaef85ca4348b66814c86bc73908bdeb66d12/pyright-1.1.406-py3-none-any.whl", hash = "sha256:1d81fb43c2407bf566e97e57abb01c811973fdb21b2df8df59f870f688bdca71", size = 5980982, upload-time = "2025-10-02T01:04:43.137Z" }, ] [[package]] name = "pytest" version = "8.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919, upload-time = "2024-12-01T12:54:25.98Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083, upload-time = "2024-12-01T12:54:19.735Z" }, ] [[package]] name = "pytest-xdist" version = "3.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "execnet" }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/41/c4/3c310a19bc1f1e9ef50075582652673ef2bfc8cd62afef9585683821902f/pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d", size = 84060, upload-time = "2024-04-28T19:29:54.414Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108, upload-time = "2024-04-28T19:29:52.813Z" }, ] [[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, upload-time = "2024-08-06T20:33:50.674Z" } 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, upload-time = "2024-08-06T20:31:40.178Z" }, { 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, upload-time = "2024-08-06T20:31:42.173Z" }, { 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, upload-time = "2024-08-06T20:31:44.263Z" }, { 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, upload-time = "2024-08-06T20:31:50.199Z" }, { 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, upload-time = "2024-08-06T20:31:52.292Z" }, { 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, upload-time = "2024-08-06T20:31:53.836Z" }, { 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, upload-time = "2024-08-06T20:31:55.565Z" }, { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, { 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, upload-time = "2024-08-06T20:32:03.408Z" }, { 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, upload-time = "2024-08-06T20:32:04.926Z" }, { 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, upload-time = "2024-08-06T20:32:06.459Z" }, { 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, upload-time = "2024-08-06T20:32:08.338Z" }, { 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, upload-time = "2024-08-06T20:32:14.124Z" }, { 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, upload-time = "2024-08-06T20:32:16.17Z" }, { 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, upload-time = "2024-08-06T20:32:18.555Z" }, { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, { 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, upload-time = "2024-08-06T20:32:25.131Z" }, { 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, upload-time = "2024-08-06T20:32:26.511Z" }, { 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, upload-time = "2024-08-06T20:32:28.363Z" }, { 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, upload-time = "2024-08-06T20:32:30.058Z" }, { 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, upload-time = "2024-08-06T20:32:31.881Z" }, { 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, upload-time = "2024-08-06T20:32:37.083Z" }, { 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, upload-time = "2024-08-06T20:32:38.898Z" }, { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, { 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, upload-time = "2024-08-06T20:32:43.4Z" }, { 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, upload-time = "2024-08-06T20:32:44.801Z" }, { 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, upload-time = "2024-08-06T20:32:46.432Z" }, { 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, upload-time = "2024-08-06T20:32:51.188Z" }, { 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, upload-time = "2024-08-06T20:32:53.019Z" }, { 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, upload-time = "2024-08-06T20:32:54.708Z" }, { 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, upload-time = "2024-08-06T20:32:56.985Z" }, { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, { url = "https://files.pythonhosted.org/packages/74/d9/323a59d506f12f498c2097488d80d16f4cf965cee1791eab58b56b19f47a/PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", size = 183218, upload-time = "2024-08-06T20:33:06.411Z" }, { url = "https://files.pythonhosted.org/packages/74/cc/20c34d00f04d785f2028737e2e2a8254e1425102e730fee1d6396f832577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", size = 728067, upload-time = "2024-08-06T20:33:07.879Z" }, { url = "https://files.pythonhosted.org/packages/20/52/551c69ca1501d21c0de51ddafa8c23a0191ef296ff098e98358f69080577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", size = 757812, upload-time = "2024-08-06T20:33:12.542Z" }, { url = "https://files.pythonhosted.org/packages/fd/7f/2c3697bba5d4aa5cc2afe81826d73dfae5f049458e44732c7a0938baa673/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", size = 746531, upload-time = "2024-08-06T20:33:14.391Z" }, { url = "https://files.pythonhosted.org/packages/8c/ab/6226d3df99900e580091bb44258fde77a8433511a86883bd4681ea19a858/PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", size = 800820, upload-time = "2024-08-06T20:33:16.586Z" }, { url = "https://files.pythonhosted.org/packages/a0/99/a9eb0f3e710c06c5d922026f6736e920d431812ace24aae38228d0d64b04/PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", size = 145514, upload-time = "2024-08-06T20:33:22.414Z" }, { url = "https://files.pythonhosted.org/packages/75/8a/ee831ad5fafa4431099aa4e078d4c8efd43cd5e48fbc774641d233b683a9/PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", size = 162702, upload-time = "2024-08-06T20:33:23.813Z" }, { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload-time = "2024-08-06T20:33:25.896Z" }, { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload-time = "2024-08-06T20:33:27.212Z" }, { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload-time = "2024-08-06T20:33:28.974Z" }, { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload-time = "2024-08-06T20:33:34.157Z" }, { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload-time = "2024-08-06T20:33:35.84Z" }, { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload-time = "2024-08-06T20:33:37.501Z" }, { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload-time = "2024-08-06T20:33:39.389Z" }, { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload-time = "2024-08-06T20:33:46.63Z" }, { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" }, ] [[package]] name = "tomli" version = "2.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] [[package]] name = "typing-extensions" version = "4.13.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, ] [[package]] name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] logistro-2.0.1/LICENSE0000644000175000017500000000205015101271261017712 0ustar debian_developerdebian_developerMIT License Copyright (c) 2025 GeoPozo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. logistro-2.0.1/README.md0000644000175000017500000000420115101271261020164 0ustar debian_developerdebian_developer# **logistro (lo-hฤซ-stro)** `logistro` is an extremely light addition to `logging`, providing sensible defaults. It also includes `getPipeLogger()` which can be passed to `Popen()` so that its `stderr` is piped to the already thread-safe `logging` library. ## Which logger level should I use? `debug2` I use if I'm in a situation where it's okay to dump large amounts of information. I'm planning on scrolling. `info` I will maybe use at the beginning and end of functions called. `debug` Might print stuff from inside functions or it might print shortened versioning of `debug2`. Warning, Error, Critical, Exception, these are all more obvious. ## Quickstart ```python import logistro logger = logistro.getLogger(__name__) logger.debug2(...) # new! logger.debug(...) # or debug1() logger.info(...) logger.warning(...) logger.error(...) logger.critical(...) logger.exception(...) # always inside except: # For subprocesses: pipe, logger = logistro.getPipeLogger(__name__+"-subprocess") subprocess.Popen(cli_command, stderr=pipe) os.close(pipe) # eventually ``` ## CLI Flags * `--logistro-level DEBUG|DEBUG2|INFO|WARNING|ERROR|CRITICAL` * `--logistro-human` (default) * `--logistro-structured` which outputs JSON The help for CLI commands can be included in your program: ``` parser = argparse.ArgumentParser(parents=[logistro.parser]) ``` ### Functions * `logistro.set_structured()` * `logistro.set_human()` *Generally, they must be called before any other logging call (See note below).* ## Additionally `logistro.betterConfig(...)` applies our formats and levels. It accepts the same arguments as `logging.basicConfig(...)` except `format=`, which it ignores. **It is better to call this early in a multithread program.** `logistro.getLogger(...)` will ensure `betterConfig()`. You can use our two formatters manually instead: * `human_formatter` * `structured_formatter` ## Changing Logger Formatter Mid-Execution With a typical setup, calling `set_structured()` or `set_human()` and then `logistro.coerce_logger(logistro.getLogger())` will change the format. See [the tech note](TECH_NOTE.md) for an intro into the complexities of `logging`. logistro-2.0.1/tests/0000755000175000017500000000000015101271261020052 5ustar debian_developerdebian_developerlogistro-2.0.1/tests/test_logistro.py0000644000175000017500000000334715101271261023334 0ustar debian_developerdebian_developerimport json import logging import os import time import logistro def write_to_pipe(caplog, pipe, message, human): termed_message = message + "\n" os.write(pipe, termed_message.encode("utf-8")) time.sleep(0.5) if human: assert message in caplog.text else: obj = json.loads(caplog.text) assert obj["message"] == message caplog.clear() def write_to_logger(caplog, logger, level, message, human): logger.log(level, message) level_name = logging.getLevelName(level) if human: assert level_name in caplog.text assert message in caplog.text else: obj = json.loads(caplog.text) assert obj["level"] == level_name assert obj["message"] == message caplog.clear() def test_human_logs(caplog): human = logistro.getLogger("human") for handler in logistro.getLogger().handlers: handler.setFormatter(logistro.human_formatter) human.setLevel(logistro.DEBUG2) write_to_logger(caplog, human, logistro.DEBUG2, "d2-message", human=True) w, pipelogger = logistro.getPipeLogger("pipe1") pipelogger.setLevel(logistro.DEBUG2) try: write_to_pipe(caplog, w, "d2-message", human=True) finally: os.close(w) def test_structured_logs(caplog): structured = logistro.getLogger("structured") for handler in logistro.getLogger().handlers: handler.setFormatter(logistro.structured_formatter) structured.setLevel(logistro.DEBUG2) write_to_logger(caplog, structured, logistro.DEBUG2, "d2-message", human=False) w, pipelogger = logistro.getPipeLogger("pipe2") pipelogger.setLevel(logistro.DEBUG2) try: write_to_pipe(caplog, w, "d2-message", human=False) finally: os.close(w) logistro-2.0.1/tests/conftest.py0000644000175000017500000000000015101271261022237 0ustar debian_developerdebian_developerlogistro-2.0.1/CHANGELOG.txt0000644000175000017500000000102515101271261020736 0ustar debian_developerdebian_developerv2.0.1 - Close read pipe when pipeLogger is closed v2.0.0 - Remove all import-time side effects: Users must now call `logistro.betterConfig()` manually. - Improve processing of exc_info. v1.1.0 - Allow users to set their own stacklevel v1.0.12 - Properly decode argument to number or string - Make default level from CLI = None v1.0.11 - Set minimum requirement to python 3.8 v1.0.10 - Improve formatting v1.0.9 - Fix exception() calls to show stacktrace - Turn off help in parser and expose so importing packages can include it logistro-2.0.1/.gitignore0000644000175000017500000000012515101271261020676 0ustar debian_developerdebian_developer.venv # ignore build artifacts __pycache__ *.egg-info/ # ipynb .ipynb_checkpoints/ logistro-2.0.1/.github/0000755000175000017500000000000015101271261020250 5ustar debian_developerdebian_developerlogistro-2.0.1/.github/workflows/0000755000175000017500000000000015101271261022305 5ustar debian_developerdebian_developerlogistro-2.0.1/.github/workflows/publish_all.yml0000644000175000017500000001067515101271261025337 0ustar debian_developerdebian_developer# .github/workflows/publish_testpypi.yml --- name: test-n-build on: push: tags: - v* jobs: super-test: strategy: max-parallel: 2 matrix: python_v: ['3.8', '3.9', '3.10', ''] name: Build and Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v4 # must actually checkout for version determination - run: git checkout ${{ github.ref_name }} - run: uv python install ${{ matrix.python_v }} - run: uv python pin ${{ matrix.python_v }} if: ${{ matrix.python_v != '' }} # don't modify sync file! messes up version! - run: uv sync --all-extras --dev --frozen # ah order here matters? - run: uv build - name: Reinstall from wheel run: > uv pip install dist/logistro-$(uv run --no-sync --with setuptools-git-versioning setuptools-git-versioning)-py3-none-any.whl - name: Test run: uv run --no-sync poe test timeout-minutes: 8 - name: Test (Debug) if: runner.debug run: uv run --no-sync poe debug-test store-build: name: build release needs: super-test if: always() && !cancelled() && !failure() runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v4 - uses: actions/setup-python@v5 with: python-version-file: "pyproject.toml" - run: git checkout ${{ github.ref_name }} - run: uv sync --frozen --all-extras - run: uv build - name: Store the distribution packages uses: actions/upload-artifact@v4 with: name: python-package-distributions path: dist/ publish-to-testpypi: name: >- Publish Python ๐Ÿ distribution ๐Ÿ“ฆ to testPyPI needs: - super-test - store-build if: always() && !cancelled() && !failure() runs-on: ubuntu-latest environment: name: testpypi url: https://test.pypi.org/p/logistro # Signs this workflow so pypi trusts it permissions: id-token: write steps: - name: Download the dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Push to testPyPi uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/. github-release: name: >- Sign the Python ๐Ÿ distribution ๐Ÿ“ฆ with Sigstore and upload them to GitHub Release needs: - publish-to-testpypi - super-test - store-build if: always() && !cancelled() && !failure() runs-on: ubuntu-latest permissions: contents: write # IMPORTANT: mandatory for making GitHub Releases id-token: write # IMPORTANT: mandatory for sigstore steps: - name: Download all the dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Sign the dists with Sigstore uses: sigstore/gh-action-sigstore-python@v3.0.0 with: inputs: >- ./dist/*.tar.gz ./dist/*.whl - name: Create GitHub Release env: GITHUB_TOKEN: ${{ github.token }} run: >- gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --notes "" - name: Upload artifact signatures to GitHub Release env: GITHUB_TOKEN: ${{ github.token }} # Upload to GitHub Release using the `gh` CLI. # `dist/` contains the built packages, and the # sigstore-produced signatures and certificates. run: >- gh release upload "$GITHUB_REF_NAME" dist/** --repo "$GITHUB_REPOSITORY" publish-to-pypi: name: >- Publish Python ๐Ÿ distribution ๐Ÿ“ฆ to PyPI needs: - super-test - store-build - publish-to-testpypi - github-release if: always() && !cancelled() && !failure() runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/logistro # Signs this workflow so pypi trusts it permissions: id-token: write steps: - name: Download the dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Push to PyPi uses: pypa/gh-action-pypi-publish@release/v1 logistro-2.0.1/.github/workflows/test.yml0000644000175000017500000000105615101271261024011 0ustar debian_developerdebian_developer--- name: test-wf on: pull_request: push: tags-ignore: - v* jobs: test-all: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v4 - uses: actions/setup-python@v5 with: python-version-file: "pyproject.toml" - name: Install logistro run: uv sync --dev - name: Test if: ${{ ! runner.debug }} run: uv run poe test timeout-minutes: 7 - name: Test (Debug) if: runner.debug run: uv run poe debug-test logistro-2.0.1/.github/workflows/static.yml0000644000175000017500000000234715101271261024325 0ustar debian_developerdebian_developer# Simple workflow for deploying static content to GitHub Pages name: Deploy static content to Pages on: # Runs on pushes targeting the default branch push: branches: ["main"] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: group: "pages" cancel-in-progress: false jobs: # Single deploy job since we're just deploying deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Pages uses: actions/configure-pages@v5 - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: # Upload entire repository path: './site' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 logistro-2.0.1/.github/workflows/ruff.yml0000644000175000017500000000031315101271261023767 0ustar debian_developerdebian_developer--- name: ruff-wf on: pull_request jobs: ruff: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: chartboost/ruff-action@v1 with: src: './logistro' logistro-2.0.1/.pre-commit-config.yaml0000644000175000017500000000512715101271261023176 0ustar debian_developerdebian_developer# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks %YAML 1.2 --- exclude: 'site/.*' repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files - id: check-case-conflict - id: check-merge-conflict - id: check-toml - id: debug-statements - repo: https://github.com/asottile/add-trailing-comma rev: v4.0.0 hooks: - id: add-trailing-comma - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. rev: v0.14.0 hooks: # Run the linter. - id: ruff types_or: [python, pyi] # Run the formatter. - id: ruff-format types_or: [python, pyi] # options: ignore one line things [E701] - repo: https://github.com/adrienverge/yamllint rev: v1.37.1 hooks: - id: yamllint name: yamllint description: This hook runs yamllint. entry: yamllint language: python types: [file, yaml] args: [ '-d', "{ extends: default, rules: { colons: { max-spaces-after: -1 } } }", ] - repo: https://github.com/rhysd/actionlint rev: v1.7.8 hooks: - id: actionlint name: Lint GitHub Actions workflow files description: Runs actionlint to lint GitHub Actions workflow files language: golang types: ["yaml"] files: ^\.github/workflows/ entry: actionlint - repo: https://github.com/jorisroovers/gitlint rev: v0.19.1 hooks: - id: gitlint name: gitlint description: Checks your git commit messages for style. language: python additional_dependencies: ["./gitlint-core[trusted-deps]"] entry: gitlint args: [--staged, --msg-filename] stages: [commit-msg] - repo: https://github.com/crate-ci/typos rev: v1 hooks: - id: typos - repo: https://github.com/Yelp/detect-secrets rev: v1.5.0 hooks: - id: detect-secrets name: Detect secrets language: python entry: detect-secrets-hook args: [''] - repo: https://github.com/rvben/rumdl-pre-commit rev: v0.0.155 # Use the latest release tag hooks: - id: rumdl # To only check (default): # args: [] # To automatically fix issues: # args: [--fix] - repo: https://github.com/RobertCraigie/pyright-python rev: v1.1.406 # pin a tag; latest as of 2025-10-01 hooks: - id: pyright logistro-2.0.1/mkdocs.yml0000644000175000017500000000131615101271261020714 0ustar debian_developerdebian_developer--- ### Site metadata ### site_name: logistro repo_name: github ### Build settings ### docs_dir: 'docs/' nav: - Readme: >- { "dest": "README.md", "src": "../README.md", "replace": {"src='docs/": "src='"} } - Note: >- { "dest": "TECH_NOTE.md", "src": "../TECH_NOTE.md", "replace": {"src='docs/": "src='"} } - Reference: >- { "api": "logistro" } theme: name: material markdown_extensions: - pymdownx.highlight: anchor_linenums: true line_spans: __span pygments_lang_class: true - pymdownx.inlinehilite - pymdownx.snippets - pymdownx.superfences plugins: - quimeta - quicopy - quiapi logistro-2.0.1/logistro/0000755000175000017500000000000015101271261020552 5ustar debian_developerdebian_developerlogistro-2.0.1/logistro/_api.py0000644000175000017500000002300115101271261022030 0ustar debian_developerdebian_developerfrom __future__ import annotations import json import logging import os import platform import sys import traceback from threading import Thread from typing import TYPE_CHECKING, Any, Callable, Dict, cast from logistro import _args as cli_args if TYPE_CHECKING: from collections.abc import MutableMapping ## New Constants and Globals DEBUG2 = 5 """A more verbose version of `logging.DEBUG`""" logging.addLevelName(DEBUG2, "DEBUG2") pipe_attr_blacklist = ["filename", "funcName", "threadName", "taskName"] """List of attributes to ignore in getPipeLogger()""" # Our basic formatting list _output = { "time": "%(asctime)s", "level": "%(levelname)s", "name": "%(name)s", "file": "%(filename)s", "func": "%(funcName)s", "task": "%(taskName)s", "thread": "%(threadName)s", "message": "%(message)s", } # async taskName not supported below 3.12, remove it if bool(sys.version_info[:3] < (3, 12)): del _output["task"] # A more readable, human readable string _date_string = "%a, %d-%b %H:%M:%S" class HumanFormatter(logging.Formatter): def format(self, record: logging.LogRecord) -> str: # Add level result: str = record.levelname + "\t" # Add threadname result += ( f"Thread({record.threadName})." if record.threadName != "MainThread" else "" ) # Add taskname result += f"Task({record.taskName})." if hasattr(record, "taskName") else "" # type: ignore[reportAttributeAccessError] # Add module/package + filename module = record.name or "" filename = record.filename or "" result += f"{module}.{filename}" # Add function name result += f".{record.funcName}" if record.funcName else "" # Add message message = str(record.msg) % record.args if record.args else str(record.msg) result += f"- {message}" if exc := record.exc_info: if exc is True: etuple = sys.exc_info() elif isinstance(exc, BaseException): # don't need to do this for format_exception after 3.10 etuple = (type(exc), exc, exc.__traceback__) elif isinstance(exc, tuple): etuple = exc else: raise ValueError( f"exc_info passed value of type {type(record.exc_info)}", ) result += f"\n {' '.join(traceback.format_exception(*etuple))}" return result human_formatter: HumanFormatter = HumanFormatter() structured_formatter: logging.Formatter = logging.Formatter(json.dumps(_output)) """A `logging.Formatter()` to print output as JSON for machine consumption.""" # https://github.com/python/mypy/wiki/Unsupported-Python-Features class _LogistroLogger(logging.getLoggerClass()): # type: ignore[misc] def debug1(self, msg: str, *args: Any, **kwargs: Any) -> None: stacklevel = kwargs.pop("stacklevel", 0) + 2 super().log(logging.DEBUG, msg, *args, stacklevel=stacklevel, **kwargs) def debug2(self, msg: str, *args: Any, **kwargs: Any) -> None: stacklevel = kwargs.pop("stacklevel", 0) + 2 super().log(DEBUG2, msg, *args, stacklevel=stacklevel, **kwargs) logging.setLoggerClass(_LogistroLogger) def set_human() -> None: """Set `--logistro-human` (default).""" cli_args.parsed.human = True def set_structured() -> None: """Set `--logistro-structured`.""" cli_args.parsed.human = False def betterConfig(**kwargs: Any) -> None: # noqa: N802 camel-case like logging """ Call `logging.basicConfig()` with our defaults. It will overwrite any `format` or `datefmt` arguments passed. It is only ever run once. """ if "level" not in kwargs and cli_args.parsed.log: if cli_args.parsed.log.isnumeric(): kwargs["level"] = int(cli_args.parsed.log) else: kwargs["level"] = cli_args.parsed.log.upper() if "datefmt" not in kwargs: kwargs["datefmt"] = _date_string logging.basicConfig(**kwargs) if "format" not in kwargs: logging.getLogger().handlers[0].setFormatter( human_formatter if cli_args.parsed.human else structured_formatter, ) betterConfig.__code__ = (lambda **_kwargs: None).__code__ # function won't run after this def getLogger(name: str | None = None) -> _LogistroLogger: # noqa: N802 camel-case like logging """Call `logging.getLogger()` but check `betterConfig()` first.""" return cast("_LogistroLogger", logging.getLogger(name)) _LoggerFilter = Callable[[logging.LogRecord, Dict[str, Any]], bool] class _PipeLoggerFilter: _parser: _LoggerFilter | None def __init__( self, parser: _LoggerFilter | None, ) -> None: self._parser = parser def filter(self, record: logging.LogRecord) -> bool: old_info = {} for attr in pipe_attr_blacklist: if hasattr(record, attr): old_info[attr] = getattr(record, attr) setattr(record, attr, "") if self._parser: return self._parser(record, old_info) return True FD = int def getPipeLogger( # noqa: N802 camel-case like logging name: str, parser: _LoggerFilter | None = None, default_level: int = logging.DEBUG, ifs: str | bytes = "\n", ) -> tuple[FD, _LogistroLogger]: r""" Is a special `getLogger()` which returns a pipe and logger. It spins a separate thread to feed the pipe into the logger. Common usage would be to pass the pipe to `Popen(stderr=...)`. You should close the pipe `os.close(pipe)` in case the other process freezes. Args: name: the name of the logger (รก la `logging.getLogger(name)`) parser: a function whose first parameter is a `LogRecord` to modify it. If it doesn't return `True`, the whole record is ignored. The second parameter is a dictionary of already filtered info. See keys `pipe_attr_blacklist` in `logistro._api`. default_level: the default level for the logger. ifs: The character used as delimiter for pipe reads, defaults:"\n". Returns: A tuple: (pipe, logger). The pipe can be closed immediately after passing it to the writing process. """ ifs = ifs.encode("utf-8") if isinstance(ifs, str) else ifs logger = getLogger(name) logger.addFilter(_PipeLoggerFilter(parser)) r, w = os.pipe() # needs r, logger, and default_level def read_pipe( r: int, logger: logging.Logger, default_level: int, ) -> None: if bool(sys.version_info[:3] >= (3, 12) or platform.system() != "Windows"): os.set_blocking(r, True) raw_buffer = b"" try: while True: try: last_size = len(raw_buffer) raw_buffer += os.read( r, 1000, ) if last_size == len(raw_buffer): # Just move us to exception mode raise Exception("") # noqa: TRY002,TRY301 # Catch any exception, its all weird OS stuff, the game is up except Exception: # noqa: BLE001 while raw_buffer: line, _, raw_buffer = raw_buffer.partition(ifs) if line: logger.log(default_level, line.decode()) return while raw_buffer: line, m, raw_buffer = raw_buffer.partition(ifs) if not m: raw_buffer = line break logger.log(default_level, line.decode()) finally: os.close(r) pipe_reader = Thread( target=read_pipe, name=name + "Thread", args=(r, logger, default_level), ) pipe_reader.start() return w, logger class LoggingNode: def __init__( self, name: str, *, logger: logging.Logger | None = None, parent: LoggingNode | None = None, ) -> None: self.logger = logger self._name = name self.children: MutableMapping[str, LoggingNode] = {} self.parent = parent def add_child(self, name: str, logger: logging.Logger | None = None) -> LoggingNode: if logger: if name in self.children: self.children[name].logger = logger else: self.children[name] = LoggingNode(name, logger=logger, parent=self) else: self.children[name] = LoggingNode(name, parent=self) return self.children[name] def print(self, indent: int = 0) -> None: label = f"{self._name}- {self.logger}" if self.logger and hasattr(self.logger, "handlers"): label += f"- {self.logger.handlers}" print(("-" * indent) + label) # noqa: T201 for _, child in sorted(self.children.items()): child.print(indent + 1) def __str__(self) -> str: return self._name def describe_logging() -> None: """Print out information about the current logging configuration.""" logger = getLogger() root = LoggingNode("root", logger=logger, parent=None) for name, sublogger in logger.manager.loggerDict.items(): if not isinstance(sublogger, logging.Logger): continue parent = root nodes = name.split(".") for node in nodes[:-1]: parent = parent.add_child(node) parent.add_child(nodes[-1], sublogger) root.print() logistro-2.0.1/logistro/py.typed0000644000175000017500000000000015101271261022237 0ustar debian_developerdebian_developerlogistro-2.0.1/logistro/_args.py0000644000175000017500000000172315101271261022222 0ustar debian_developerdebian_developerimport argparse import sys # Verify arg sanity if "--logistro-human" in sys.argv and "--logistro-structured" in sys.argv: raise ValueError( "Choose either '--logistro-human' or '--logistro-structured'.", ) parser: argparse.ArgumentParser = argparse.ArgumentParser(add_help=False) """ The argsparse parser is exported if you'd like to include it as a parent in your own `argparse.ArgumentParser` and thereby getting better help messages. """ parser.add_argument( "--logistro-human", action="store_true", dest="human", default=True, help="Format the logs for humans", ) parser.add_argument( "--logistro-structured", action="store_false", dest="human", help="Format the logs as JSON", ) parser.add_argument( "--logistro-level", default=None, type=str, dest="log", help="Set the logging level (no default, fallback to system default)", ) # Get the Format parsed, remaining_args = parser.parse_known_args() logistro-2.0.1/logistro/__init__.py0000644000175000017500000000246315101271261022670 0ustar debian_developerdebian_developer""" Logistro wraps `logging` for added defaults and subprocess logging. Typical usage: ```python import logistro logger = logistro.getLogger(__name__) logger.debug2("This will be printed more informatively") # Advanced pipe, logger = logistro.getPipeLogger(__name__) # Pipe all stderr to our logger subprocess.Popen(process_name, stderr=pipe) # Eventually close the pipe in case other process doesn't subprocess.wait() os.close(pipe) ``` """ import logging from ._api import ( DEBUG2, betterConfig, describe_logging, getLogger, getPipeLogger, human_formatter, set_human, set_structured, structured_formatter, ) from ._args import parsed, parser, remaining_args CRITICAL = logging.CRITICAL """Equal to logging.CRITICAL level.""" DEBUG = logging.DEBUG """Equal to logging.DEBUG level.""" ERROR = logging.ERROR """Equal to logging.ERROR level.""" INFO = logging.INFO """Equal to logging.INFO level.""" WARNING = logging.WARNING """Equal to logging.WARNING level.""" __all__ = [ "CRITICAL", "DEBUG", "DEBUG2", "ERROR", "INFO", "WARNING", "betterConfig", "describe_logging", "getLogger", "getPipeLogger", "human_formatter", "parsed", "parser", "remaining_args", "set_human", "set_structured", "structured_formatter", ] logistro-2.0.1/.python-version0000644000175000017500000000000415101271261021707 0ustar debian_developerdebian_developer3.8 logistro-2.0.1/pyproject.toml0000644000175000017500000000561715101271261021635 0ustar debian_developerdebian_developer[build-system] requires = ["setuptools", "wheel", "setuptools-git-versioning"] build-backend = "setuptools.build_meta" [tool.setuptools.packages] find = {namespaces = false} [tool.setuptools-git-versioning] enabled = true [project] name = "logistro" description = "Simple wrapper over logging for a couple basic features" dynamic = ["version"] readme = "README.md" license = { "file" = "LICENSE" } requires-python = ">=3.8" authors = [ {name = "Andrew Pikul", email="ajpikul@gmail.com"}, {name = "Neyberson Atencio", email="neyberatencio@gmail.com"} ] maintainers = [ {name = "Andrew Pikul", email = "ajpikul@gmail.com"}, ] [project.urls] Homepage = "https://github.com/geopozo/logistro" Repository = "https://github.com/geopozo/logistro" [dependency-groups] dev = [ "pytest-xdist", "pytest>=8.3.4", "poethepoet>=0.30.0", "pyright>=1.1.406", ] # uv doens't allow dependency groups to have separate python requirements # it resolves everything all at once # this group we need to require higher python # and only resolve if explicitly asked for #docs = [ # "mkquixote @ git+ssh://git@github.com/geopozo/mkquixote; python_version >= '3.11'", # "mkdocs>=1.6.1", # "mkdocs-material>=9.5.49", #] [tool.ruff.lint] select = ["ALL"] ignore = [ "ANN", # no types "EM", # allow strings in raise(), despite python being ugly about it "TRY003", # allow long error messages inside raise() "D203", # No blank before class docstring (D211 = require blank line) "D212", # Commit message style docstring is D213, ignore D212 "COM812", # manual says linter rule conflicts with formatter "ISC001", # manual says litner rule conflicts with formatter "RET504", # Allow else if unnecessary because more readable "RET505", # Allow else if unnecessary because more readable "RET506", # Allow else if unnecessary because more readable "RET507", # Allow else if unnecessary because more readable "RET508", # Allow else if unnecessary because more readable "RUF012", # We don't do typing, so no typing "SIM105", # Too opionated (try-except-pass) "PT003", # scope="function" implied but I like readability "G004", # I like fstrings in my log ] [tool.ruff.lint.per-file-ignores] "tests/*" = [ "D", # ignore docstring errors "S101", # allow assert "INP001", # no need for __init__ in test directories ] [tool.pytest.ini_options] log_cli = true [tool.poe.tasks.test] cmd = "pytest -W error -n auto -v -rfE" help = "Run all tests quickly" [tool.poe.tasks.debug-test] cmd = "pytest -W error -vvvx -rA" help = "Run test by test, slowly, quitting after first error" [tool.poe.tasks.filter-test] cmd = "pytest -W error -vvvx -rA" help = "Run any/all tests one by one with basic settings: can include filename and -k filters" logistro-2.0.1/TECH_NOTE.md0000644000175000017500000000167515101271261020613 0ustar debian_developerdebian_developer# Technical Note Python's `logging` is over-engineered and not quite good enough: ## Background `Loggers` exist in a tree structure, hierarchy defined by the name. There is a root logger that you cannot change. `Loggers` have `Handlers`, `Handlers` have `Formatters`. When you call a logging function on a `Logger`, it searches up the tree for all parent handlers. In simple usage, usually only the root logger has a handler. ## Problem * Changing the format mid-program means finding all the (relevant?) handlers in the tree. `pytest` for example attaches other handlers so it can capture logging. Our `better_config()` will set our format on the root logger's handlers. * You cannot attach two formatters to a handler * Two handlers cannot access the same file Having both those be true simultaneously presents problems. For our process logger, where a lot of context is erroneous, we strip useless data from the process logger by using a filter. logistro-2.0.1/.markdown.rb0000644000175000017500000000012015101271261021126 0ustar debian_developerdebian_developerrule 'MD026', :punctuation => '.,;:!' rule 'MD013', :ignore_code_blocks => true