diff --git a/poetry.lock b/poetry.lock index 0a6ef0a..46a0804 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.3 and should not be changed by hand. [[package]] name = "alabaster" @@ -6,6 +6,8 @@ version = "1.0.0" description = "A light, configurable Sphinx theme" optional = false python-versions = ">=3.10" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"}, {file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"}, @@ -17,20 +19,21 @@ version = "3.3.11" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.9.0" +groups = ["docs"] +markers = "python_version == \"3.11\"" files = [ {file = "astroid-3.3.11-py3-none-any.whl", hash = "sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec"}, {file = "astroid-3.3.11.tar.gz", hash = "sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce"}, ] -[package.dependencies] -typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} - [[package]] name = "astroid" version = "4.1.1" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.10.0" +groups = ["docs"] +markers = "python_version >= \"3.12\"" files = [ {file = "astroid-4.1.1-py3-none-any.whl", hash = "sha256:6b28522096f7e7a36ffcf3be60e77de15e2411ab3a713184beac33fb8f20c0c9"}, {file = "astroid-4.1.1.tar.gz", hash = "sha256:445d831fe785df8c670bbb46b900b8424b82f85b4af187103f71a63a63ebed43"}, @@ -42,13 +45,15 @@ version = "2.18.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35"}, {file = "babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d"}, ] [package.extras] -dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] [[package]] name = "bandit" @@ -56,6 +61,7 @@ version = "1.8.6" description = "Security oriented static analyser for python code." optional = false python-versions = ">=3.9" +groups = ["lint"] files = [ {file = "bandit-1.8.6-py3-none-any.whl", hash = "sha256:3348e934d736fcdb68b6aa4030487097e23a501adf3e7827b63658df464dddd0"}, {file = "bandit-1.8.6.tar.gz", hash = "sha256:dbfe9c25fc6961c2078593de55fd19f2559f9e45b99f1272341f5b95dea4e56b"}, @@ -71,7 +77,7 @@ stevedore = ">=1.20.0" baseline = ["GitPython (>=3.1.30)"] sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] -toml = ["tomli (>=1.1.0)"] +toml = ["tomli (>=1.1.0) ; python_version < \"3.11\""] yaml = ["PyYAML"] [[package]] @@ -80,6 +86,7 @@ version = "25.11.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" +groups = ["lint"] files = [ {file = "black-25.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e"}, {file = "black-25.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0"}, @@ -131,6 +138,8 @@ version = "2026.2.25" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, @@ -142,6 +151,8 @@ version = "3.4.6" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "charset_normalizer-3.4.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95"}, {file = "charset_normalizer-3.4.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd"}, @@ -280,6 +291,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["lint"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -294,10 +306,238 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["docs", "lint", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {docs = "python_version >= \"3.11\" and sys_platform == \"win32\"", lint = "platform_system == \"Windows\"", test = "sys_platform == \"win32\""} + +[[package]] +name = "coverage" +version = "7.10.7" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +groups = ["test"] +markers = "python_version < \"3.12\"" +files = [ + {file = "coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a"}, + {file = "coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13"}, + {file = "coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b"}, + {file = "coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807"}, + {file = "coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59"}, + {file = "coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61"}, + {file = "coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14"}, + {file = "coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2"}, + {file = "coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a"}, + {file = "coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417"}, + {file = "coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1"}, + {file = "coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256"}, + {file = "coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba"}, + {file = "coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf"}, + {file = "coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d"}, + {file = "coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f"}, + {file = "coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698"}, + {file = "coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843"}, + {file = "coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546"}, + {file = "coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c"}, + {file = "coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2"}, + {file = "coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a"}, + {file = "coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb"}, + {file = "coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb"}, + {file = "coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520"}, + {file = "coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd"}, + {file = "coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2"}, + {file = "coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681"}, + {file = "coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880"}, + {file = "coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63"}, + {file = "coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399"}, + {file = "coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235"}, + {file = "coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d"}, + {file = "coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a"}, + {file = "coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3"}, + {file = "coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f"}, + {file = "coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431"}, + {file = "coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07"}, + {file = "coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260"}, + {file = "coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + +[[package]] +name = "coverage" +version = "7.15.0" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.10" +groups = ["test"] +markers = "python_version >= \"3.12\"" +files = [ + {file = "coverage-7.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50913d4bf5ddafa6ca3693da5e4dd833dd1b772e0283c99ca7f7d287db67331a"}, + {file = "coverage-7.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:359e141ccd33893ce3f1ad5525f8b96083003677c82182e5907d62d4ea5799fc"}, + {file = "coverage-7.15.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3200b6204935f928c64b2ca1f923ab8c1acb7c9de45ec61569711b34d25cccaf"}, + {file = "coverage-7.15.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:be616bf61346883b2cfdc5178669647e03531d81ab761a7e378558b7e8bcb628"}, + {file = "coverage-7.15.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc7bafc3fe1059463a8fdd97ca79972d6e2bf819d775c7d54991b5b1971201d6"}, + {file = "coverage-7.15.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b713aa7fcf325a01d4184d848acb46fd84f78fdb0978470c636b23a06a753d91"}, + {file = "coverage-7.15.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e38e6fba2d56652fdfaf0231f8f78aeb805234a912de25dc291ee5cce5b8faa4"}, + {file = "coverage-7.15.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:884499f42e382675be80770391983b90e0c0c774d87dbeeebf5f991cf6612b20"}, + {file = "coverage-7.15.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:840481b12e083dbcbafab14794a8781a958edf327c8d3d70b4eee42f9b8253aa"}, + {file = "coverage-7.15.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:276646e9481703d09f854f3b2f018f24e19fd7049ae670a92570043eb97203b1"}, + {file = "coverage-7.15.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4de4b4d3f5545aa6c60dc4efd9c63b5b5dcc3bf00fe83146b2bdfffb8f6613bd"}, + {file = "coverage-7.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5c504097b2a89b1e85bc6070d920df77daec701337e3aeef2c17775a5dd0ca90"}, + {file = "coverage-7.15.0-cp310-cp310-win32.whl", hash = "sha256:f6e80ed91f98316e86b9c137206b04b2bcfbffccbdff49bd2eb09dddb1cf14e0"}, + {file = "coverage-7.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:b3b3e22030f3f6f5e01a5ce69936552a5c0f6992b7698777377b99041961031f"}, + {file = "coverage-7.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:003fff99412ea848c0aaebcc78ed2b6ce7d8a1227ed17e68470672770b78a02a"}, + {file = "coverage-7.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5cbd804bf2784ce7b45114516050f346ecd50f960c4bb630a7ee9e1d78fa2118"}, + {file = "coverage-7.15.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8773e15c23305b58882a4611fb9b2755977eae0dc2e515366a1b6c98866cc4c2"}, + {file = "coverage-7.15.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f50e40081494c1dc4239ebb202014cbcc3306ea96fb6302a34c8cc0967fc5ae8"}, + {file = "coverage-7.15.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daf96f37f5fc3a7b6c6da862eb4aee61c426bd63da236ed4a73ef0e503b4bca5"}, + {file = "coverage-7.15.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:51aa20f6ae2788fd197747766edf4cd8234fd9423309b934257fa6b21a592723"}, + {file = "coverage-7.15.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03d1f922757662eb7af586e77834792274cff776bc7b1d1a0b66a49ea9d84735"}, + {file = "coverage-7.15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a6d6acc9a7666245e6133dd15144ca038a85a9cd5026bb06d6bbae9e77440dc9"}, + {file = "coverage-7.15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1ac2c4c27c7df851dc9a017c2d7de00b69147e84ba3d96f37a530b0b6fb51035"}, + {file = "coverage-7.15.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b761a1d504fd4bd1f20f418753964dca9f5862a511fc854dac58296b3b223671"}, + {file = "coverage-7.15.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e43b045e11c16e897895758ae90e4a90cf99e93d58549e2f90c0e2272e155695"}, + {file = "coverage-7.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:589b54513e901739f4b4582c705ce96b80c96f57641b1464607e2367a270e540"}, + {file = "coverage-7.15.0-cp311-cp311-win32.whl", hash = "sha256:106781b8482749162d0b47056937ba0933508e5d9447f65a5e7d5c422f0d6bb4"}, + {file = "coverage-7.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:821e92b3631d762a339695824cadbbc73020354eba2a23a551a99ad34938fbe6"}, + {file = "coverage-7.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:309990eb5fb8014b9f67cb211f7fd41876ec8a88a88d3ae76de0ed1d611e3640"}, + {file = "coverage-7.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b75ee5e8cb7575636ac598719b4307ac529ec8fcd79608a35c3cd4d4dada812d"}, + {file = "coverage-7.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffb31267816b93b075302248cc1737506081b4f163df4401e9df1a6424aafabe"}, + {file = "coverage-7.15.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e4d0bb73455bf97ab243a8f12c37c686ccf1c13bb614b7b85f1d062f06f42b2c"}, + {file = "coverage-7.15.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:20d9ccc4ebd0edc434d86dfd2a1dd2a8efa6b6b3073d0485a394fee86459ebb4"}, + {file = "coverage-7.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20c8a976c365c8cb12f0cbd099508772ea41fb5fa80657a8506df0e11bd278c5"}, + {file = "coverage-7.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f948fd5ba1b9cbca91f0ae08b4c1ce2b139509149a435e2585d056d57d70bf01"}, + {file = "coverage-7.15.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f58185f06edf6ad68ec9fb155d63ef650c82f3fbd7e1770e2867751fb13158f4"}, + {file = "coverage-7.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02adc79a920c73c647c5d117f55747df7f2de94571884758ce8bc58e04f0a796"}, + {file = "coverage-7.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6eb7c300fbed667fd6e3588eba71c1904cdb06110ca6fdf908c26bdd88b8e382"}, + {file = "coverage-7.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b5fb23fa2de9dce1f5c36c09066d8fcda16cd96e8e26686caa2d7cb9b567d65c"}, + {file = "coverage-7.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cec79341dbe6281484024979976d0c7f22beae08b4a254655decd25d42cbe766"}, + {file = "coverage-7.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c664c5444b1d970b1b2a450e21fb19ee5c9cfdf151ded2dda37260031cca0da"}, + {file = "coverage-7.15.0-cp312-cp312-win32.whl", hash = "sha256:5f764a3fa339bde6b3aa97657f5a6a3a9451e4a5b4ea98a2892c773a43525f77"}, + {file = "coverage-7.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:52f9a4d2c4c56c8848bc2f524916698354b0211488b38c49ad9ae54f6cafbff6"}, + {file = "coverage-7.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:31e5c3e70c85307ea35a12964e2e40f56ca2ee4b1c8c721ccf4609d17071080b"}, + {file = "coverage-7.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5be4caf3b28836f078abe700f8944dac4a65d78f16d6c600c89cb624e5535782"}, + {file = "coverage-7.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dd58ad1404704303ca8d4f4b8a1095e7cbc7040ef17a66df1e6619aa10176430"}, + {file = "coverage-7.15.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bbcbb317c2e5ded5b21104af81c29f391be2af98d065693ffbe8d23949b948e5"}, + {file = "coverage-7.15.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:27f31ecb458da3f859aab3f15ada871eb7a7768807d88df4a9f186bb17737970"}, + {file = "coverage-7.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fb759be317fdc62e0f56bffdf61cfcb45c7761ad6b71e3e583e71a67ae753c"}, + {file = "coverage-7.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5cf007add5ab4bb8fa9f4c77e3732127c9e6cad501d7db43355fbfafca0be84"}, + {file = "coverage-7.15.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc78d9843bd576fbe2118248258d485e968dc535f95ed504a7b0867ba9b51389"}, + {file = "coverage-7.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a263060f1de0b4b74b4e089c2a70b8003b3781c733329a9c8fd54995328f9950"}, + {file = "coverage-7.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c48decf16e0dfd5b049c7d5e82200c23c08126719142998d4f172444e3d0529e"}, + {file = "coverage-7.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:08fb028000ed0aaa0a4cbdfbb98be7cb42f370db973fbbb469733505ab20e13e"}, + {file = "coverage-7.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb7dc0c3b7d8a1077abea0b8546ebc5e26d6ef6ecefc2f0f5ad2b8a53bdad837"}, + {file = "coverage-7.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cb3602054ccbe9f0d8c2dc04bbeba90d5719236e2cd06e042ddd6d3fc7b6e37"}, + {file = "coverage-7.15.0-cp313-cp313-win32.whl", hash = "sha256:0bf781da64326b677be344df505171435b6f58716108606621d5d27d964fff8b"}, + {file = "coverage-7.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:2c57a275078ee3fa185f83e400f765bc764a549de66d99b47881645cbd4ea629"}, + {file = "coverage-7.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:3812c61afc6685c7999b39320779ab8f43b7a3081fdb0def39976e56fbdb9a21"}, + {file = "coverage-7.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:41cb79af843222e11da87127ad0ecbfa878abadd0f770a4a99391a27d3887324"}, + {file = "coverage-7.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7d2008989ef8fe54188d3f3bfa2e3099b025af11e90a6a1b9e7dc433d04263d8"}, + {file = "coverage-7.15.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:769e8ece11a596315ebf5aa7ec383aeeed016c091d2bf6363ffb996d41529092"}, + {file = "coverage-7.15.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:65a6b6164ee5c39e2f3803f314292d6c61a607ba7fee253d1e03c42dc3903502"}, + {file = "coverage-7.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75128817f95a5c45bb01d65fd2d8b9cb54bbe03d81608fb70e3e14b437ad56c2"}, + {file = "coverage-7.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9887bb428fe2d4cd4bee89bac1a6c9932f484afd5b36fbd4ff6ea5f825bb1f5e"}, + {file = "coverage-7.15.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0bfc0be1f702042207a93a00523b1065ee1fe951e96edf311581c0bbc2e34888"}, + {file = "coverage-7.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f64627d55def5a43282d70e08396672692f77e4da610a5bb8bb4060b432b6859"}, + {file = "coverage-7.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2c6f0fa473003905c6d5bac328ee4eba9fbea654f15bc24b8a3274b23363fa99"}, + {file = "coverage-7.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2bcf9afaf064172c6ec3c58a325a9957ad1178c05dd934e25f253321776e0676"}, + {file = "coverage-7.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:baf06bc987115d6fb938d403f7eab684a057766c490367999a2b71a6883110c6"}, + {file = "coverage-7.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f0405f2ff97b1c4c0e782cb32e02f32369bcf2e6b618b591d67e1ea754575dfe"}, + {file = "coverage-7.15.0-cp314-cp314-win32.whl", hash = "sha256:ab282853ed5fbd64bbb162f19cb8fcb7087187508a6374b4f9c34ec1577c4e8f"}, + {file = "coverage-7.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bb3040e9f4bbe26fcb0cd7cc85ac63e630d3f3a9c74f027abf4caa27e706663"}, + {file = "coverage-7.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:346771144d34f7fa84ec28386f78e0f31653f33cf35e19d253d5b35f9e8201da"}, + {file = "coverage-7.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d34a010905fb6401324ba016b5da03d574967f7b21ce48ea41e66f0f1f95f641"}, + {file = "coverage-7.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bb25d825d885ca8036795dacfc3924d33091fc76d71ebc99420c6b79e77d96fa"}, + {file = "coverage-7.15.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:94c9686bfe8a9a6810297aecbd99beaa3445f9e8dc2f80b1382cca0d86b64461"}, + {file = "coverage-7.15.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9bd671c25f9d85f09d7ec481d0e43d5139f486c06a37139847a7ce569788af72"}, + {file = "coverage-7.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:110cbdf8d2e216577312cf06ccf85539c0e5a5420ef747e4a4719b5e483c88cd"}, + {file = "coverage-7.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c5d4619214f1d9993e7b00a8600d14614b7e9d84e89507460b126aa5e6559e5"}, + {file = "coverage-7.15.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:781a704516e2d8346fbbd5be6c6f3412dd824785146528b3a01816f26c081007"}, + {file = "coverage-7.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd4a1b44bcb65ee29e947ac92bbee04956df3a6bfc6143641bb6cae7ede00fc9"}, + {file = "coverage-7.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0e4950c9d6d3e39c64c991814ff315e2d0b9cb8152363594212c9e55208c0a8f"}, + {file = "coverage-7.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:fe9c87ff42e5472d80d21704972e1f96e104a0a599d77c5e35db5a3c562e2571"}, + {file = "coverage-7.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f00d5ae1dd2fe13fb8186e3e7d37bcbd8b25c0d764ff7d1b32cef9be058510a8"}, + {file = "coverage-7.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:363ab38cc78b615f11c9cac3cf1d7eef950c18b9fdedfb9066f59461dcf84d68"}, + {file = "coverage-7.15.0-cp314-cp314t-win32.whl", hash = "sha256:54fd9c53a5fafff509195f1b6a3f9be615d8e8362a3629ff1de23d270c03c86b"}, + {file = "coverage-7.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:87b47553097ba185ed964866078e7e63adea9f5f51b5f39691c34f30afd21080"}, + {file = "coverage-7.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aeefb2dd178fe7eee79f0ad25d75855cb35ee9ed472db2c5ea06f5b4fd00cec5"}, + {file = "coverage-7.15.0-py3-none-any.whl", hash = "sha256:56da6a4cbe8f7e9e80bd072ca9cefe67d7106a440a7ec06519ec6507ac94ad19"}, + {file = "coverage-7.15.0.tar.gz", hash = "sha256:9ac3fe7a1435986463eaa8ee253ae2f2a268709ba4ae5c7dd1f52a05391ad78f"}, +] + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "docutils" @@ -305,17 +545,40 @@ version = "0.22.4" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.9" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de"}, {file = "docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968"}, ] +[[package]] +name = "exceptiongroup" +version = "1.3.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +groups = ["test"] +markers = "python_version < \"3.11\"" +files = [ + {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, + {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "flake8" version = "5.0.4" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.6.1" +groups = ["lint"] +markers = "python_version < \"3.12\"" files = [ {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, @@ -332,6 +595,8 @@ version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" +groups = ["lint"] +markers = "python_version >= \"3.12\"" files = [ {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, @@ -348,6 +613,7 @@ version = "0.4.0" description = "flake8 plugin to call black as a code style validator" optional = false python-versions = ">=3.9" +groups = ["lint"] files = [ {file = "flake8_black-0.4.0-py3-none-any.whl", hash = "sha256:288762d0c9ea065782d87eeecbcc20c69079d17fe1d0f0445f0eb0b0ffb80c39"}, {file = "flake8_black-0.4.0.tar.gz", hash = "sha256:bf226868f695dee48d55ff6d7747e900709bfd6f605b7a378c70e711e3fc26cb"}, @@ -367,6 +633,7 @@ version = "1.7.0" description = "Extension for flake8 which uses pydocstyle to check docstrings" optional = false python-versions = ">=3.7" +groups = ["lint"] files = [ {file = "flake8_docstrings-1.7.0-py2.py3-none-any.whl", hash = "sha256:51f2344026da083fc084166a9353f5082b01f72901df422f74b4d953ae88ac75"}, {file = "flake8_docstrings-1.7.0.tar.gz", hash = "sha256:4c8cc748dc16e6869728699e5d0d685da9a10b0ea718e090b1ba088e67a941af"}, @@ -382,6 +649,7 @@ version = "0.18.2" description = "Flake8 and pylama plugin that checks the ordering of import statements." optional = false python-versions = "*" +groups = ["lint"] files = [ {file = "flake8-import-order-0.18.2.tar.gz", hash = "sha256:e23941f892da3e0c09d711babbb0c73bc735242e9b216b726616758a920d900e"}, {file = "flake8_import_order-0.18.2-py2.py3-none-any.whl", hash = "sha256:82ed59f1083b629b030ee9d3928d9e06b6213eb196fe745b3a7d4af2168130df"}, @@ -397,6 +665,8 @@ version = "3.11" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, @@ -411,6 +681,8 @@ version = "1.5.0" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "imagesize-1.5.0-py2.py3-none-any.whl", hash = "sha256:32677681b3f434c2cb496f00e89c5a291247b35b1f527589909e008057da5899"}, {file = "imagesize-1.5.0.tar.gz", hash = "sha256:8bfc5363a7f2133a89f0098451e0bcb1cd71aba4dc02bbcecb39d99d40e1b94f"}, @@ -422,6 +694,8 @@ version = "8.7.1" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" +groups = ["lint"] +markers = "python_version == \"3.9\"" files = [ {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"}, {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"}, @@ -431,13 +705,39 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=3.4)"] perf = ["ipython"] test = ["flufl.flake8", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["mypy (<1.19)", "pytest-mypy (>=1.0.1)"] +type = ["mypy (<1.19) ; platform_python_implementation == \"PyPy\"", "pytest-mypy (>=1.0.1)"] + +[[package]] +name = "iniconfig" +version = "2.1.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.8" +groups = ["test"] +markers = "python_version < \"3.12\"" +files = [ + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +groups = ["test"] +markers = "python_version >= \"3.12\"" +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] [[package]] name = "isort" @@ -445,6 +745,7 @@ version = "6.1.0" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.9.0" +groups = ["lint"] files = [ {file = "isort-6.1.0-py3-none-any.whl", hash = "sha256:58d8927ecce74e5087aef019f778d4081a3b6c98f15a80ba35782ca8a2097784"}, {file = "isort-6.1.0.tar.gz", hash = "sha256:9b8f96a14cfee0677e78e941ff62f03769a06d412aabb9e2a90487b3b7e8d481"}, @@ -463,6 +764,8 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -480,6 +783,8 @@ version = "0.8.1" description = "Mypyc runtime library" optional = false python-versions = ">=3.9" +groups = ["lint"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc"}, {file = "librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7"}, @@ -579,6 +884,8 @@ version = "0.3.4" description = "Markdown and reStructuredText in a single file." optional = false python-versions = ">=3.7" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "m2r2-0.3.4-py3-none-any.whl", hash = "sha256:1a445514af8a229496bfb1380c52da8dd38313e48600359ee92b2c9d2e4df34a"}, {file = "m2r2-0.3.4.tar.gz", hash = "sha256:e278f5f337e9aa7b2080fcc3e94b051bda9615b02e36c6fb3f23ff019872f043"}, @@ -594,6 +901,7 @@ version = "1.3.12" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = false python-versions = ">=3.8" +groups = ["codegen"] files = [ {file = "mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9"}, {file = "mako-1.3.12.tar.gz", hash = "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a"}, @@ -613,6 +921,7 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["lint"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -637,6 +946,7 @@ version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["codegen", "docs"] files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -728,6 +1038,7 @@ files = [ {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, ] +markers = {docs = "python_version >= \"3.11\""} [[package]] name = "mccabe" @@ -735,6 +1046,7 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" +groups = ["lint"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -746,6 +1058,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["lint"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -757,6 +1070,8 @@ version = "0.8.4" description = "The fastest markdown parser in pure Python" optional = false python-versions = "*" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"}, {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, @@ -768,6 +1083,7 @@ version = "1.19.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" +groups = ["lint"] files = [ {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, @@ -829,6 +1145,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" +groups = ["lint"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -840,6 +1157,7 @@ version = "0.4.9" description = "NI's internal and external Python linter rules and plugins" optional = false python-versions = "<4.0,>=3.7" +groups = ["lint"] files = [ {file = "ni_python_styleguide-0.4.9-py3-none-any.whl", hash = "sha256:3a33735fbb52231bda00ec970d7c7aebe2b15aed2ccbd07da5c52b8e04730918"}, {file = "ni_python_styleguide-0.4.9.tar.gz", hash = "sha256:64a65da646fc8c2f0e68970e0a3190534add9380ee78782fa809fee5321f9e45"}, @@ -871,10 +1189,12 @@ version = "26.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["docs", "lint", "test"] files = [ {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, ] +markers = {docs = "python_version >= \"3.11\""} [[package]] name = "pathspec" @@ -882,6 +1202,7 @@ version = "1.0.4" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.9" +groups = ["lint"] files = [ {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, @@ -899,6 +1220,7 @@ version = "0.15.1" description = "Check PEP-8 naming conventions, plugin for flake8" optional = false python-versions = ">=3.9" +groups = ["lint"] files = [ {file = "pep8_naming-0.15.1-py3-none-any.whl", hash = "sha256:eb63925e7fd9e028c7f7ee7b1e413ec03d1ee5de0e627012102ee0222c273c86"}, {file = "pep8_naming-0.15.1.tar.gz", hash = "sha256:f6f4a499aba2deeda93c1f26ccc02f3da32b035c8b2db9696b730ef2c9639d29"}, @@ -913,6 +1235,7 @@ version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" +groups = ["lint"] files = [ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, @@ -923,12 +1246,30 @@ docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-a test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.14.1)"] +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + [[package]] name = "pycodestyle" version = "2.9.1" description = "Python style guide checker" optional = false python-versions = ">=3.6" +groups = ["lint"] +markers = "python_version < \"3.12\"" files = [ {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, @@ -940,6 +1281,8 @@ version = "2.11.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" +groups = ["lint"] +markers = "python_version >= \"3.12\"" files = [ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, @@ -951,6 +1294,7 @@ version = "6.3.0" description = "Python docstring style checker" optional = false python-versions = ">=3.6" +groups = ["lint"] files = [ {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, @@ -960,7 +1304,7 @@ files = [ snowballstemmer = ">=2.2.0" [package.extras] -toml = ["tomli (>=1.2.3)"] +toml = ["tomli (>=1.2.3) ; python_version < \"3.11\""] [[package]] name = "pyflakes" @@ -968,6 +1312,8 @@ version = "2.5.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.6" +groups = ["lint"] +markers = "python_version < \"3.12\"" files = [ {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, @@ -979,6 +1325,8 @@ version = "3.1.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" +groups = ["lint"] +markers = "python_version >= \"3.12\"" files = [ {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, @@ -990,20 +1338,109 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["docs", "lint", "test"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] +markers = {docs = "python_version >= \"3.11\""} [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pytest" +version = "8.4.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +groups = ["test"] +markers = "python_version < \"3.12\"" +files = [ + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest" +version = "9.1.1" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.10" +groups = ["test"] +markers = "python_version >= \"3.12\"" +files = [ + {file = "pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c"}, + {file = "pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +iniconfig = ">=1.0.1" +packaging = ">=22" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"}, + {file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"}, +] + +[package.dependencies] +coverage = {version = ">=7.10.6", extras = ["toml"]} +pluggy = ">=1.2" +pytest = ">=7" + +[package.extras] +testing = ["process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-mock" +version = "3.15.1" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d"}, + {file = "pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "pytokens" version = "0.4.1" description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." optional = false python-versions = ">=3.8" +groups = ["lint"] files = [ {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"}, {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"}, @@ -1058,6 +1495,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["docs", "lint"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -1133,6 +1571,7 @@ files = [ {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, ] +markers = {docs = "python_version >= \"3.11\""} [[package]] name = "requests" @@ -1140,6 +1579,8 @@ version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -1161,6 +1602,7 @@ version = "14.3.3" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" +groups = ["lint"] files = [ {file = "rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d"}, {file = "rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b"}, @@ -1179,6 +1621,8 @@ version = "4.1.0" description = "Manipulate well-formed Roman numerals" optional = false python-versions = ">=3.10" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7"}, {file = "roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2"}, @@ -1190,19 +1634,20 @@ version = "81.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["lint"] files = [ {file = "setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6"}, {file = "setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.13.0)"] -core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.13.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.18.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.18.*)", "pytest-mypy"] [[package]] name = "snowballstemmer" @@ -1210,10 +1655,12 @@ version = "3.0.1" description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*" +groups = ["docs", "lint"] files = [ {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, ] +markers = {docs = "python_version >= \"3.11\""} [[package]] name = "sphinx" @@ -1221,6 +1668,8 @@ version = "9.0.4" description = "Python documentation generator" optional = false python-versions = ">=3.11" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb"}, {file = "sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3"}, @@ -1251,6 +1700,8 @@ version = "3.6.1" description = "Sphinx API documentation generator" optional = false python-versions = ">=3.9" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "sphinx_autoapi-3.6.1-py3-none-any.whl", hash = "sha256:6b7af0d5650f6eac1f4b85c1eb9f9a4911160ec7138bdc4451c77a5e94d5832c"}, {file = "sphinx_autoapi-3.6.1.tar.gz", hash = "sha256:1ff2992b7d5e39ccf92413098a376e0f91e7b4ca532c4f3e71298dbc8a4a9900"}, @@ -1264,7 +1715,6 @@ astroid = [ Jinja2 = "*" PyYAML = "*" sphinx = ">=7.4.0" -stdlib_list = {version = "*", markers = "python_version < \"3.10\""} [[package]] name = "sphinx-rtd-theme" @@ -1272,6 +1722,8 @@ version = "3.1.0" description = "Read the Docs theme for Sphinx" optional = false python-versions = ">=3.8" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "sphinx_rtd_theme-3.1.0-py2.py3-none-any.whl", hash = "sha256:1785824ae8e6632060490f67cf3a72d404a85d2d9fc26bce3619944de5682b89"}, {file = "sphinx_rtd_theme-3.1.0.tar.gz", hash = "sha256:b44276f2c276e909239a4f6c955aa667aaafeb78597923b1c60babc76db78e4c"}, @@ -1291,6 +1743,8 @@ version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, @@ -1307,6 +1761,8 @@ version = "2.0.0" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, @@ -1323,6 +1779,8 @@ version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, @@ -1339,6 +1797,8 @@ version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" optional = false python-versions = ">=2.7" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, @@ -1353,6 +1813,8 @@ version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = false python-versions = ">=3.5" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, @@ -1367,6 +1829,8 @@ version = "2.0.0" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, @@ -1383,6 +1847,8 @@ version = "2.0.0" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, @@ -1393,30 +1859,13 @@ lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] -[[package]] -name = "stdlib-list" -version = "0.12.0" -description = "A list of Python Standard Libraries (2.7 through 3.14)." -optional = false -python-versions = ">=3.9" -files = [ - {file = "stdlib_list-0.12.0-py3-none-any.whl", hash = "sha256:df2d11e97f53812a1756fb5510393a11e3b389ebd9239dc831c7f349957f62f2"}, - {file = "stdlib_list-0.12.0.tar.gz", hash = "sha256:517824f27ee89e591d8ae7c1dd9ff34f672eae50ee886ea31bb8816d77535675"}, -] - -[package.extras] -dev = ["build", "stdlib-list[doc,lint,test]"] -doc = ["furo", "sphinx"] -lint = ["mypy", "ruff"] -support = ["sphobjinv"] -test = ["coverage[toml]", "pytest", "pytest-cov"] - [[package]] name = "stevedore" version = "5.5.0" description = "Manage dynamic plugins for Python applications" optional = false python-versions = ">=3.9" +groups = ["lint"] files = [ {file = "stevedore-5.5.0-py3-none-any.whl", hash = "sha256:18363d4d268181e8e8452e71a38cd77630f345b2ef6b4a8d5614dac5ee0d18cf"}, {file = "stevedore-5.5.0.tar.gz", hash = "sha256:d31496a4f4df9825e1a1e4f1f74d19abb0154aff311c3b376fcc89dae8fccd73"}, @@ -1428,10 +1877,12 @@ version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["docs", "lint"] files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +markers = {docs = "python_version >= \"3.11\""} [[package]] name = "tomli" @@ -1439,6 +1890,7 @@ version = "2.4.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["lint", "test"] files = [ {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, @@ -1488,6 +1940,7 @@ files = [ {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"}, {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"}, ] +markers = {lint = "python_version < \"3.11\"", test = "python_full_version <= \"3.11.0a6\""} [[package]] name = "typing-extensions" @@ -1495,10 +1948,12 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" +groups = ["lint", "test"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] +markers = {test = "python_version < \"3.11\""} [[package]] name = "urllib3" @@ -1506,16 +1961,18 @@ version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["docs"] +markers = "python_version >= \"3.11\"" files = [ {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, ] [package.extras] -brotli = ["brotli (>=1.2.0)", "brotlicffi (>=1.2.0.0)"] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["backports-zstd (>=1.0.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "zipp" @@ -1523,13 +1980,15 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" +groups = ["lint"] +markers = "python_version == \"3.9\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -1537,6 +1996,6 @@ test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_it type = ["pytest-mypy"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.9" -content-hash = "caae69a4daa1269c434e607f67c8af84505d819604540e3390e352c574de0059" +content-hash = "289236e62ba035f96a80bcc4646303c117b56a4716de702554e4e7c2f3eabc8d" diff --git a/pyproject.toml b/pyproject.toml index 1ee8060..ff5c419 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,10 +49,10 @@ optional = true [tool.poetry.group.docs.dependencies] # The latest Sphinx requires a recent Python version. Sphinx = { version = ">=8.2", python = "^3.11" } -sphinx-rtd-theme = ">=1.0.0" -sphinx-autoapi = ">=1.8.4" -m2r2 = ">=0.3.2" -toml = ">=0.10.2" +sphinx-rtd-theme = { version = ">=1.0.0", python = "^3.11" } +sphinx-autoapi = { version = ">=1.8.4", python = "^3.11" } +m2r2 = { version = ">=0.3.2", python = "^3.11" } +toml = { version = ">=0.10.2", python = "^3.11" } [tool.poetry.group.lint.dependencies] @@ -61,6 +61,12 @@ ni-python-styleguide = ">=0.4.6" bandit = ">=1.8.6" +[tool.poetry.group.test.dependencies] +pytest = ">=8.3.4" +pytest-mock = ">=3.14" +pytest-cov = ">=6" + + [tool.ni-python-styleguide] extend_exclude = "generated,src/codegen/metadata,src/codegen/templates,src/handwritten" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e2d5c09 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for the nislsc package.""" diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..9fbd30f --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1 @@ +"""Unit tests for the nislsc package.""" diff --git a/tests/unit/_session_utils.py b/tests/unit/_session_utils.py new file mode 100644 index 0000000..e2207ea --- /dev/null +++ b/tests/unit/_session_utils.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +from unittest.mock import Mock + + +def expect_initialize_library( + interpreter: Mock, + library_handle: int = 1, +) -> None: + """Expect a call to interpreter.initialize_library.""" + interpreter.initialize_library.return_value = library_handle + + +def expect_initialize_session_with_devices( + interpreter: Mock, + session_handle: int = 100, +) -> None: + """Expect a call to interpreter.initialize_session_with_devices.""" + interpreter.initialize_session_with_devices.return_value = session_handle + + +def expect_initialize_session_with_nvmem_areas( + interpreter: Mock, session_handle: int = 200 +) -> None: + """Expect a call to interpreter.initialize_session_with_nvmem_areas.""" + interpreter.initialize_session_with_nvmem_areas.return_value = session_handle + + +def expect_initialize_session_with_physical_channels( + interpreter: Mock, session_handle: int = 300 +) -> None: + """Expect a call to interpreter.initialize_session_with_physical_channels.""" + interpreter.initialize_session_with_physical_channels.return_value = session_handle + + +def expect_initialize_session_without_resources( + interpreter: Mock, session_handle: int = 400 +) -> None: + """Expect a call to interpreter.initialize_session_without_resources.""" + interpreter.initialize_session_without_resources.return_value = session_handle diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py new file mode 100644 index 0000000..8c27214 --- /dev/null +++ b/tests/unit/conftest.py @@ -0,0 +1,62 @@ +"""Shared pytest fixtures for unit tests.""" + +from __future__ import annotations + +from collections.abc import Generator +from unittest.mock import Mock + +import pytest +from pytest_mock import MockerFixture + +from nislsc import Library, Session +from nislsc._base_interpreter import BaseInterpreter +from nislsc.constants import Language, ReservationAccess +from tests.unit._session_utils import ( + expect_initialize_library, + expect_initialize_session_with_devices, +) + + +@pytest.fixture +def interpreter(mocker: MockerFixture) -> Mock: + """Create a mock interpreter. + + Patches _select_interpreter in all modules that call it, and patches + get_library_version so Library.__init__ does not need a real driver. + """ + mock_interpreter = mocker.create_autospec(BaseInterpreter) + mock_interpreter._library_handle = 0 + mock_interpreter._language = Language.CURRENT_THREAD_LOCALE + + for target in ( + "nislsc.library._select_interpreter", + "nislsc.utils._select_interpreter", + ): + stub = mocker.patch(target, autospec=True) + stub.return_value = mock_interpreter + + mocker.patch("nislsc.library.get_library_version", return_value=1) + + return mock_interpreter + + +@pytest.fixture +def library(interpreter: Mock) -> Generator[Library, None, None]: + """Create a Library instance backed by the mock interpreter.""" + expect_initialize_library(interpreter) + with Library() as lib: + yield lib + + +@pytest.fixture +def session(library: Library, interpreter: Mock) -> Generator[Session, None, None]: + """Create a Session with devices, backed by the mock interpreter. + + This fixture owns the session. Do not use it in tests that destroy the + session, or you may get double-close warnings. + """ + expect_initialize_session_with_devices(interpreter) + with Session.initialize_session_with_devices( + library, "Dev1", -1.0, ReservationAccess.NONE, "", -1.0 + ) as sess: + yield sess diff --git a/tests/unit/test_command.py b/tests/unit/test_command.py new file mode 100644 index 0000000..257a547 --- /dev/null +++ b/tests/unit/test_command.py @@ -0,0 +1,110 @@ +from __future__ import annotations + +from unittest.mock import Mock + +from nislsc import Command, Session + + +def test___session_with_device___open_device_command___command_handle_set( + interpreter: Mock, session: Session +) -> None: + interpreter.open_device_command.return_value = 500 + + with Command.open_device_command(session, "Dev1", "Reset") as cmd: + assert cmd._command_handle == 500 + interpreter.open_device_command.assert_called_once_with( + session._session_handle, "Dev1", "Reset" + ) + + +def test___session_with_device___open_device_command___session_reference_stored( + interpreter: Mock, session: Session +) -> None: + interpreter.open_device_command.return_value = 500 + + with Command.open_device_command(session, "Dev1", "Reset") as cmd: + assert cmd._session is session + assert cmd._interpreter is session._interpreter + + +def test___session_with_physical_channel___open_physical_channel_command___command_handle_set( + interpreter: Mock, session: Session +) -> None: + interpreter.open_physical_channel_command.return_value = 501 + + with Command.open_physical_channel_command(session, "Dev1/phys0", "SomeCmd") as cmd: + assert cmd._command_handle == 501 + interpreter.open_physical_channel_command.assert_called_once_with( + session._session_handle, "Dev1/phys0", "SomeCmd" + ) + + +def test___session_with_generic_command___open_generic_command___command_handle_set( + interpreter: Mock, session: Session +) -> None: + interpreter.open_generic_command.return_value = 502 + + with Command.open_generic_command(session, "$DefaultDevices", "SomeCmd") as cmd: + assert cmd._command_handle == 502 + interpreter.open_generic_command.assert_called_once_with( + session._session_handle, "$DefaultDevices", "SomeCmd" + ) + + +def test___open_device_command_open___close___close_command_called_with_handle( + interpreter: Mock, session: Session +) -> None: + interpreter.open_device_command.return_value = 500 + cmd = Command.open_device_command(session, "Dev1", "Reset") + + cmd.close() + + interpreter.close_command.assert_called_once_with(500) + assert cmd._command_handle == 0 + + +def test___open_device_command_open___close_twice___close_command_called_once( + interpreter: Mock, session: Session +) -> None: + interpreter.open_device_command.return_value = 500 + cmd = Command.open_device_command(session, "Dev1", "Reset") + + cmd.close() + cmd.close() + + interpreter.close_command.assert_called_once_with(500) + + +def test___open_device_command_open___close_command___interpreter_close_command_called( + interpreter: Mock, session: Session +) -> None: + interpreter.open_device_command.return_value = 500 + cmd = Command.open_device_command(session, "Dev1", "Reset") + + cmd.close_command() + + interpreter.close_command.assert_called_once_with(500) + + +def test___open_device_command_open___context_manager___close_command_called_on_exit( + interpreter: Mock, session: Session +) -> None: + interpreter.open_device_command.return_value = 500 + + with Command.open_device_command(session, "Dev1", "Reset"): + interpreter.close_command.assert_not_called() + + interpreter.close_command.assert_called_once_with(500) + + +def test___open_device_command_open___get_command_property_string___returns_value( + interpreter: Mock, session: Session +) -> None: + interpreter.open_device_command.return_value = 500 + interpreter.get_command_property_string.return_value = "Reset the device." + + with Command.open_device_command(session, "Dev1", "Reset") as cmd: + result = cmd.get_command_property_string("Cmd.Descr") + + interpreter.get_command_property_string.assert_called_once_with(500, "Cmd.Descr") + assert result == "Reset the device." diff --git a/tests/unit/test_error.py b/tests/unit/test_error.py new file mode 100644 index 0000000..d8649e0 --- /dev/null +++ b/tests/unit/test_error.py @@ -0,0 +1,153 @@ +from __future__ import annotations + +import warnings + +import pytest + +from nislsc.error import SLSCError, SLSCResourceWarning, SLSCWarning +from nislsc.error_codes import SLSCErrors, SLSCWarnings + + +def test___slscerror___known_error_code___error_code_stored() -> None: + err = SLSCError("Device not found.", -250806) + + assert err.error_code == -250806 + + +def test___slscerror___error_code_as_string___coerced_to_int() -> None: + err = SLSCError("Some error.", "-250806") # type: ignore[arg-type] + + assert err.error_code == -250806 + assert isinstance(err.error_code, int) + + +def test___slscerror___known_error_code___error_type_matches_enum() -> None: + err = SLSCError("Device not found.", -250806) + + assert err.error_type == SLSCErrors.DEVICE_NOT_FOUND + + +def test___slscerror___unknown_error_code___error_type_is_unknown() -> None: + err = SLSCError("Unexpected error.", -999999) + + assert err.error_type == SLSCErrors.UNKNOWN + + +def test___slscerror___internal_error_code___error_type_matches_enum() -> None: + err = SLSCError("Internal error.", -250800) + + assert err.error_type == SLSCErrors.INTERNAL + + +def test___slscerror___message_with_status_code___message_not_duplicated() -> None: + err = SLSCError("Device not found.\n\nStatus Code: -250806", -250806) + + assert str(err).count("-250806") == 1 + + +def test___slscerror___message_without_status_code___status_code_appended() -> None: + err = SLSCError("Device not found.", -250806) + + assert "-250806" in str(err) + + +def test___slscerror___empty_message___default_message_with_status_code() -> None: + err = SLSCError("", -250806) + + assert "Description could not be found" in str(err) + assert "-250806" in str(err) + + +def test___slscerror___whitespace_only_message___default_message_with_status_code() -> None: + err = SLSCError(" ", -250806) + + assert "Description could not be found" in str(err) + assert "-250806" in str(err) + + +def test___slscerror___none_message___default_message_with_status_code() -> None: + err = SLSCError(None, -250806) # type: ignore[arg-type] + + assert "Description could not be found" in str(err) + assert "-250806" in str(err) + + +def test___slscerror___raised_and_caught_as_slscerror___succeeds() -> None: + with pytest.raises(SLSCError): + raise SLSCError("Device not found.", -250806) + + +def test___slscerror___raised_and_caught_as_exception___succeeds() -> None: + with pytest.raises(Exception): + raise SLSCError("Device not found.", -250806) + + +def test___slscwarning___known_warning_code___error_code_stored() -> None: + w = SLSCWarning("Scaling timestamp outdated.", 250800) + + assert w.error_code == 250800 + + +def test___slscwarning___error_code_as_string___coerced_to_int() -> None: + w = SLSCWarning("Scaling timestamp outdated.", "250800") + + assert w.error_code == 250800 + assert isinstance(w.error_code, int) + + +def test___slscwarning___known_warning_code___error_type_matches_enum() -> None: + w = SLSCWarning("Scaling timestamp outdated.", 250800) + + assert w.error_type == SLSCWarnings.SCALING_TIMESTAMP_OUTDATED + + +def test___slscwarning___unknown_warning_code___error_type_is_unknown() -> None: + w = SLSCWarning("Some unknown warning.", 999999) + + assert w.error_type == SLSCWarnings.UNKNOWN + + +def test___slscwarning___message___includes_warning_code() -> None: + w = SLSCWarning("Scaling timestamp outdated.", 250800) + + assert "250800" in str(w) + + +def test___slscwarning___message___includes_original_message() -> None: + w = SLSCWarning("Scaling timestamp outdated.", 250800) + + assert "Scaling timestamp outdated." in str(w) + + +def test___slscwarning___message___formatted_with_warning_prefix() -> None: + w = SLSCWarning("Scaling timestamp outdated.", 250800) + + assert str(w).startswith("\nWarning 250800 occurred.") + + +def test___slscwarning___emitted_via_warnings_warn___caught_by_pytest_warns() -> None: + with pytest.warns(SLSCWarning): + warnings.warn(SLSCWarning("Scaling timestamp outdated.", 250800)) + + +def test___slscwarning___emitted___error_code_accessible_on_caught_warning() -> None: + with pytest.warns(SLSCWarning) as warning_info: + warnings.warn(SLSCWarning("Scaling timestamp outdated.", 250800)) + + assert warning_info[0].message.error_code == 250800 + + +def test___slscwarning___emitted___error_type_accessible_on_caught_warning() -> None: + with pytest.warns(SLSCWarning) as warning_info: + warnings.warn(SLSCWarning("Scaling timestamp outdated.", 250800)) + + assert warning_info[0].message.error_type == SLSCWarnings.SCALING_TIMESTAMP_OUTDATED + + +def test___slscresourcewarning___is_subclass_of_resource_warning() -> None: + assert issubclass(SLSCResourceWarning, ResourceWarning) + + +def test___slscresourcewarning___emitted___caught_by_pytest_warns() -> None: + with pytest.warns(SLSCResourceWarning): + warnings.warn("Resource was not released.", SLSCResourceWarning) diff --git a/tests/unit/test_library.py b/tests/unit/test_library.py new file mode 100644 index 0000000..74d9d56 --- /dev/null +++ b/tests/unit/test_library.py @@ -0,0 +1,110 @@ +from __future__ import annotations + +from unittest.mock import Mock + +from nislsc import Library +from nislsc.constants import Language +from tests.unit._session_utils import expect_initialize_library + + +def test___init___initialize_library_called_with_version( + interpreter: Mock, +) -> None: + expect_initialize_library(interpreter, 42) + + with Library() as lib: + interpreter.initialize_library.assert_called_once_with(1) + assert lib._interpreter._library_handle == 42 + + +def test___init___language_is_current_thread_locale( + interpreter: Mock, +) -> None: + expect_initialize_library(interpreter) + + with Library() as lib: + assert lib.language == Language.CURRENT_THREAD_LOCALE + + +def test___language_provided___init___language_set_on_interpreter( + interpreter: Mock, +) -> None: + expect_initialize_library(interpreter) + + with Library(Language.ENGLISH) as lib: + assert lib.language == Language.ENGLISH + assert lib._interpreter._language == Language.ENGLISH + + +def test___library_opened___close___finalize_library_called_with_handle( + interpreter: Mock, +) -> None: + expect_initialize_library(interpreter, 42) + lib = Library() + + lib.close() + + interpreter.finalize_library.assert_called_once_with(42) + assert lib._interpreter._library_handle == 0 + + +def test___library_opened___close_twice___close___finalize_library_called_once( + interpreter: Mock, +) -> None: + expect_initialize_library(interpreter, 42) + lib = Library() + + lib.close() + lib.close() + + interpreter.finalize_library.assert_called_once_with(42) + + +def test___context_manager___close___finalize_library_called_on_exit( + interpreter: Mock, +) -> None: + expect_initialize_library(interpreter, 42) + + with Library(): + interpreter.finalize_library.assert_not_called() + + interpreter.finalize_library.assert_called_once_with(42) + + +def test___library_opened___set_language___updates_interpreter_language( + interpreter: Mock, +) -> None: + expect_initialize_library(interpreter) + + with Library() as lib: + lib.language = Language.JAPANESE + + assert lib.language == Language.JAPANESE + assert lib._interpreter._language == Language.JAPANESE + + +def test___library_opened___get_error_description___delegates_to_interpreter( + interpreter: Mock, +) -> None: + expect_initialize_library(interpreter, 1) + interpreter.get_error_description.return_value = "Device not found." + + with Library() as lib: + result = lib.get_error_description(-250806) + + interpreter.get_error_description.assert_called_once() + assert result == "Device not found." + + +def test___language_provided___get_error_description___passes_language_to_interpreter( + interpreter: Mock, +) -> None: + expect_initialize_library(interpreter, 1) + interpreter.get_error_description.return_value = "Erreur." + + with Library() as lib: + result = lib.get_error_description(-250806, Language.FRENCH) + + call_args = interpreter.get_error_description.call_args + assert call_args.args[2] == Language.FRENCH + assert result == "Erreur." diff --git a/tests/unit/test_property.py b/tests/unit/test_property.py new file mode 100644 index 0000000..50eb9a5 --- /dev/null +++ b/tests/unit/test_property.py @@ -0,0 +1,125 @@ +from __future__ import annotations + +from unittest.mock import Mock + +import pytest + +from nislsc import Property, Session + + +def test___session_opened___open_device_property___property_handle_set( + interpreter: Mock, session: Session +) -> None: + interpreter.open_device_property.return_value = 600 + + with Property.open_device_property(session, "Dev1", "Dev.SerialNum") as prop: + assert prop._property_handle == 600 + interpreter.open_device_property.assert_called_once_with( + session._session_handle, "Dev1", "Dev.SerialNum" + ) + + +def test___session_opened___open_device_property___session_reference_stored( + interpreter: Mock, session: Session +) -> None: + interpreter.open_device_property.return_value = 600 + + with Property.open_device_property(session, "Dev1", "Dev.SerialNum") as prop: + assert prop._session is session + assert prop._interpreter is session._interpreter + + +def test___session_opened___open_physical_channel_property___property_handle_set( + interpreter: Mock, session: Session +) -> None: + interpreter.open_physical_channel_property.return_value = 601 + + with Property.open_physical_channel_property( + session, "Dev1/phys0", "PhysChan.SomeProp" + ) as prop: + assert prop._property_handle == 601 + interpreter.open_physical_channel_property.assert_called_once_with( + session._session_handle, "Dev1/phys0", "PhysChan.SomeProp" + ) + + +def test___session_opened___open_driver_defined_property___property_handle_set( + interpreter: Mock, session: Session +) -> None: + interpreter.open_driver_defined_property.return_value = 602 + + with Property.open_driver_defined_property(session, "Drv.SomeProp") as prop: + assert prop._property_handle == 602 + interpreter.open_driver_defined_property.assert_called_once_with( + session._session_handle, "Drv.SomeProp" + ) + + +def test___session_opened___open_generic_property___property_handle_set( + interpreter: Mock, session: Session +) -> None: + interpreter.open_generic_property.return_value = 603 + + with Property.open_generic_property(session, "$DefaultDevices", "Dev.SomeProp") as prop: + assert prop._property_handle == 603 + interpreter.open_generic_property.assert_called_once_with( + session._session_handle, "$DefaultDevices", "Dev.SomeProp" + ) + + +def test___session_opened___close___close_property_called_with_handle( + interpreter: Mock, session: Session +) -> None: + interpreter.open_device_property.return_value = 600 + prop = Property.open_device_property(session, "Dev1", "Dev.SerialNum") + + prop.close() + + interpreter.close_property.assert_called_once_with(600) + assert prop._property_handle == 0 + + +def test___session_opened___close_twice___close___close_property_called_once( + interpreter: Mock, session: Session +) -> None: + interpreter.open_device_property.return_value = 600 + prop = Property.open_device_property(session, "Dev1", "Dev.SerialNum") + + prop.close() + prop.close() + + interpreter.close_property.assert_called_once_with(600) + + +def test___context_manager___close___close_property_called_on_exit( + interpreter: Mock, session: Session +) -> None: + interpreter.open_device_property.return_value = 600 + + with Property.open_device_property(session, "Dev1", "Dev.SerialNum"): + interpreter.close_property.assert_not_called() + + interpreter.close_property.assert_called_once_with(600) + + +@pytest.mark.parametrize( + "method, prop_key, value", + [ + ("get_property_property_bool", "Prop.IsReadable", True), + ("get_property_property_int32", "Prop.DataType", 3), + ("get_property_property_int32_array", "Prop.EnumValues", [1, 2, 3]), + ("get_property_property_string", "Prop.Descr", "Serial number of the device."), + ("get_property_property_string_array", "Prop.EnumStrings", ["ReadOnly", "ReadWrite"]), + ], +) +def test___property_opened___get_property_property___returns_value( + interpreter: Mock, session: Session, method: str, prop_key: str, value: object +) -> None: + interpreter.open_device_property.return_value = 600 + getattr(interpreter, method).return_value = value + + with Property.open_device_property(session, "Dev1", "Dev.SomeProp") as prop: + result = getattr(prop, method)(prop_key) + + getattr(interpreter, method).assert_called_once_with(600, prop_key) + assert result == value diff --git a/tests/unit/test_session.py b/tests/unit/test_session.py new file mode 100644 index 0000000..59bff71 --- /dev/null +++ b/tests/unit/test_session.py @@ -0,0 +1,1164 @@ +from __future__ import annotations + +import warnings +from unittest.mock import Mock + +import pytest + +from nislsc import Library, Session +from nislsc.constants import ReservationAccess, TableScaleCoercion +from nislsc.error import SLSCError, SLSCWarning +from nislsc.error_codes import SLSCErrors, SLSCWarnings +from tests.unit._session_utils import ( + expect_initialize_library, + expect_initialize_session_with_devices, + expect_initialize_session_with_nvmem_areas, + expect_initialize_session_with_physical_channels, + expect_initialize_session_without_resources, +) + + +def test___library_provided___initialize_session_with_devices___session_handle_is_set( + interpreter: Mock, library: Library +) -> None: + expect_initialize_session_with_devices(interpreter, 100) + + with Session.initialize_session_with_devices( + library, "Dev1", -1.0, ReservationAccess.NONE, "", -1.0 + ) as session: + assert session._session_handle == 100 + interpreter.initialize_session_with_devices.assert_called_once() + + +def test___library_provided___initialize_session_with_devices___owns_library_is_false( + interpreter: Mock, library: Library +) -> None: + expect_initialize_session_with_devices(interpreter) + + with Session.initialize_session_with_devices( + library, "Dev1", -1.0, ReservationAccess.NONE, "", -1.0 + ) as session: + assert not session._owns_library + + +def test___no_library___initialize_session_with_devices___library_created_and_owned( + interpreter: Mock, +) -> None: + expect_initialize_library(interpreter) + expect_initialize_session_with_devices(interpreter) + + with Session.initialize_session_with_devices( + None, "Dev1", -1.0, ReservationAccess.NONE, "", -1.0 + ) as session: + assert session._owns_library + assert session._library is not None + + +def test___library_provided___initialize_session_with_devices___interpreter_called_with_args( + interpreter: Mock, library: Library +) -> None: + expect_initialize_session_with_devices(interpreter) + library_handle = library._interpreter._library_handle + + with Session.initialize_session_with_devices( + library, "Dev1,Dev2", 5.0, ReservationAccess.READ_WRITE, "MyGroup", 10.0 + ): + interpreter.initialize_session_with_devices.assert_called_once_with( + library_handle, + "Dev1,Dev2", + 5.0, + ReservationAccess.READ_WRITE, + "MyGroup", + 10.0, + ) + + +def test___interpreter_raises_error___initialize_session_with_devices___slsc_error_propagated( + interpreter: Mock, library: Library +) -> None: + interpreter.initialize_session_with_devices.side_effect = SLSCError( + "Device not found.", -250806 + ) + + with pytest.raises(SLSCError) as exc_info: + Session.initialize_session_with_devices( + library, "Dev1", -1.0, ReservationAccess.NONE, "", -1.0 + ) + + assert exc_info.value.error_code == -250806 + assert exc_info.value.error_type == SLSCErrors.DEVICE_NOT_FOUND + assert "Device not found." in str(exc_info.value) + + +def test___session_opened___interpreter_raises_error___abort_session___slsc_error_propagated( + interpreter: Mock, session: Session +) -> None: + interpreter.abort_session.side_effect = SLSCError("Internal error.", -250800) + + with pytest.raises(SLSCError) as exc_info: + session.abort_session() + + assert exc_info.value.error_code == -250800 + assert exc_info.value.error_type == SLSCErrors.INTERNAL + + +def test___session_opened___interpreter_emits_warning___connect_to_devices___slsc_warning_propagated( + interpreter: Mock, session: Session +) -> None: + def emit_warning(*args: object, **kwargs: object) -> None: + warnings.warn(SLSCWarning("Scaling timestamp outdated.", 250800)) + + interpreter.connect_to_devices.side_effect = emit_warning + + with pytest.warns(SLSCWarning) as warning_info: + session.connect_to_devices("Dev1", 10.0) + + warning = warning_info[0].message + assert isinstance(warning, SLSCWarning) + assert warning.error_code == 250800 + assert warning.error_type == SLSCWarnings.SCALING_TIMESTAMP_OUTDATED + + +@pytest.mark.parametrize("status_code", [-250806, 250800, 0]) +def test___session_opened___interpreter_status_code___connect_to_devices___error_or_warning_dispatched( + interpreter: Mock, session: Session, status_code: int +) -> None: + if status_code < 0: + interpreter.connect_to_devices.side_effect = SLSCError("Device not found.", status_code) + + with pytest.raises(SLSCError) as exc_info: + session.connect_to_devices("Dev1", 10.0) + + assert exc_info.value.error_code == status_code + elif status_code > 0: + + def emit_warning(*args: object, **kwargs: object) -> None: + warnings.warn(SLSCWarning("Scaling timestamp outdated.", status_code)) + + interpreter.connect_to_devices.side_effect = emit_warning + + with pytest.warns(SLSCWarning) as warning_info: + session.connect_to_devices("Dev1", 10.0) + + assert warning_info[0].message.error_code == status_code + else: + with warnings.catch_warnings(): + warnings.simplefilter("error") + assert session.connect_to_devices("Dev1", 10.0) is None + + +def test___library_provided___initialize_session_with_nvmem_areas___session_handle_is_set( + interpreter: Mock, library: Library +) -> None: + expect_initialize_session_with_nvmem_areas(interpreter, 200) + + with Session.initialize_session_with_nvmem_areas( + library, "Area1", -1.0, ReservationAccess.NONE, "", -1.0 + ) as session: + assert session._session_handle == 200 + interpreter.initialize_session_with_nvmem_areas.assert_called_once() + + +def test___no_library___initialize_session_with_nvmem_areas___library_created_and_owned( + interpreter: Mock, +) -> None: + expect_initialize_library(interpreter) + expect_initialize_session_with_nvmem_areas(interpreter) + + with Session.initialize_session_with_nvmem_areas( + None, "Area1", -1.0, ReservationAccess.NONE, "", -1.0 + ) as session: + assert session._owns_library + + +def test___library_provided___initialize_session_with_physical_channels___session_handle_is_set( + interpreter: Mock, library: Library +) -> None: + expect_initialize_session_with_physical_channels(interpreter, 300) + + with Session.initialize_session_with_physical_channels( + library, "Dev1/phys0", -1.0, ReservationAccess.NONE, "", -1.0 + ) as session: + assert session._session_handle == 300 + interpreter.initialize_session_with_physical_channels.assert_called_once() + + +def test___no_library___initialize_session_with_physical_channels___library_created_and_owned( + interpreter: Mock, +) -> None: + expect_initialize_library(interpreter) + expect_initialize_session_with_physical_channels(interpreter) + + with Session.initialize_session_with_physical_channels( + None, "Dev1/phys0", -1.0, ReservationAccess.NONE, "", -1.0 + ) as session: + assert session._owns_library + + +def test___library_provided___initialize_session_without_resources___session_handle_is_set( + interpreter: Mock, library: Library +) -> None: + expect_initialize_session_without_resources(interpreter, 400) + + with Session.initialize_session_without_resources(library) as session: + assert session._session_handle == 400 + interpreter.initialize_session_without_resources.assert_called_once() + + +def test___no_library___initialize_session_without_resources___library_created_and_owned( + interpreter: Mock, +) -> None: + expect_initialize_library(interpreter) + expect_initialize_session_without_resources(interpreter) + + with Session.initialize_session_without_resources(None) as session: + assert session._owns_library + + +def test___session_opened___close___close_session_called_with_handle( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.close() + + interpreter.close_session.assert_called_once_with(session_handle) + assert session._session_handle == 0 + + +def test___session_opened___close_twice___close___close_session_called_once( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.close() + session.close() + + interpreter.close_session.assert_called_once_with(session_handle) + + +def test___context_manager___close___close_session_called_on_exit( + interpreter: Mock, library: Library +) -> None: + expect_initialize_session_with_devices(interpreter, 100) + + with Session.initialize_session_with_devices( + library, "Dev1", -1.0, ReservationAccess.NONE, "", -1.0 + ): + interpreter.close_session.assert_not_called() + + interpreter.close_session.assert_called_once_with(100) + + +def test___close_owns_library___close___finalize_library_also_called( + interpreter: Mock, +) -> None: + expect_initialize_library(interpreter, 1) + expect_initialize_session_with_devices(interpreter, 100) + + session = Session.initialize_session_with_devices( + None, "Dev1", -1.0, ReservationAccess.NONE, "", -1.0 + ) + session.close() + + interpreter.close_session.assert_called_once_with(100) + interpreter.finalize_library.assert_called_once() + + +def test___close_does_not_own_library___close___finalize_library_not_called( + interpreter: Mock, library: Library +) -> None: + expect_initialize_session_with_devices(interpreter, 100) + session = Session.initialize_session_with_devices( + library, "Dev1", -1.0, ReservationAccess.NONE, "", -1.0 + ) + + session.close() + + interpreter.finalize_library.assert_not_called() + + +def test___session_opened___abort_session___interpreter_called( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.abort_session() + + interpreter.abort_session.assert_called_once_with(session_handle) + + +def test___session_opened___log_in___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.log_in("Chassis1", "admin", "secret", 10.0, False) + + interpreter.log_in.assert_called_once_with( + session_handle, "Chassis1", "admin", "secret", 10.0, False + ) + + +def test___session_opened___log_out___interpreter_called_with_chassis_name( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.log_out("Chassis1") + + interpreter.log_out.assert_called_once_with(session_handle, "Chassis1") + + +def test___session_opened___connect_to_devices___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.connect_to_devices("Dev1", 10.0) + + interpreter.connect_to_devices.assert_called_once_with(session_handle, "Dev1", 10.0) + + +def test___session_opened___disconnect_from_devices___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.disconnect_from_devices("Dev1") + + interpreter.disconnect_from_devices.assert_called_once_with(session_handle, "Dev1") + + +def test___session_opened___connect_to_chassis_by_address___returns_chassis_name( + interpreter: Mock, session: Session +) -> None: + interpreter.connect_to_chassis_by_address.return_value = "Chassis1" + session_handle = session._session_handle + + result = session.connect_to_chassis_by_address("192.168.1.1", "admin", "pass", 10.0) + + interpreter.connect_to_chassis_by_address.assert_called_once_with( + session_handle, "192.168.1.1", "admin", "pass", 10.0 + ) + assert result == "Chassis1" + + +def test___session_opened___reserve_devices___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.reserve_devices("Dev1", ReservationAccess.READ_WRITE, "MyGroup", 30.0) + + interpreter.reserve_devices.assert_called_once_with( + session_handle, "Dev1", ReservationAccess.READ_WRITE, "MyGroup", 30.0 + ) + + +def test___session_opened___unreserve_devices___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.unreserve_devices("Dev1") + + interpreter.unreserve_devices.assert_called_once_with(session_handle, "Dev1") + + +def test___session_opened___reset_devices___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.reset_devices("Dev1") + + interpreter.reset_devices.assert_called_once_with(session_handle, "Dev1") + + +def test___session_opened___rename_device___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.rename_device("Dev1", "Dev2") + + interpreter.rename_device.assert_called_once_with(session_handle, "Dev1", "Dev2") + + +def test___session_opened___add_network_chassis___returns_chassis_name( + interpreter: Mock, session: Session +) -> None: + interpreter.add_network_chassis.return_value = "Chassis1" + session_handle = session._session_handle + + result = session.add_network_chassis("192.168.1.1", "admin", "pass", 10.0) + + interpreter.add_network_chassis.assert_called_once_with( + session_handle, "192.168.1.1", "admin", "pass", 10.0 + ) + assert result == "Chassis1" + + +def test___session_opened___remove_chassis___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.remove_chassis("Chassis1") + + interpreter.remove_chassis.assert_called_once_with(session_handle, "Chassis1") + + +def test___session_opened___update_system_configuration_file___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.update_system_configuration_file("Chassis1", 5.0) + + interpreter.update_system_configuration_file.assert_called_once_with( + session_handle, "Chassis1", 5.0 + ) + + +@pytest.mark.parametrize( + "method, prop, value", + [ + ("get_device_property_bool", "Dev.IsPresent", True), + ("get_device_property_bool_array", "Dev.Flags", [True, False, True]), + ("get_device_property_double", "Dev.Temperature", 3.14), + ("get_device_property_double_array", "Dev.Values", [1.0, 2.0, 3.0]), + ("get_device_property_int32", "Dev.NumSlots", 7), + ("get_device_property_int32_array", "Dev.Indices", [1, 2, 3]), + ("get_device_property_int64", "Dev.Timestamp", 9999999999), + ("get_device_property_int64_array", "Dev.Timestamps", [100, 200, 300]), + ("get_device_property_string", "Dev.ProductName", "SLSC-99999"), + ("get_device_property_string_array", "Dev.Modules", ["Mod1", "Mod2"]), + ("get_device_property_uint32", "Dev.Count", 255), + ("get_device_property_uint32_array", "Dev.Counts", [10, 20, 30]), + ("get_device_property_uint64", "Dev.LargeCount", 18446744073709551615), + ("get_device_property_uint64_array", "Dev.LargeCounts", [111, 222, 333]), + ], +) +def test___session_opened___get_device_property___returns_value( + interpreter: Mock, session: Session, method: str, prop: str, value: object +) -> None: + getattr(interpreter, method).return_value = value + session_handle = session._session_handle + + result = getattr(session, method)("Dev1", prop) + + getattr(interpreter, method).assert_called_once_with(session_handle, "Dev1", prop) + assert result == value + + +@pytest.mark.parametrize( + "method, prop, value", + [ + ("set_device_property_bool", "Dev.Enabled", True), + ("set_device_property_bool_array", "Dev.Flags", [True, False]), + ("set_device_property_double", "Dev.Timeout", 5.0), + ("set_device_property_double_array", "Dev.Values", [1.0, 2.0]), + ("set_device_property_int32", "Dev.Index", 42), + ("set_device_property_int32_array", "Dev.Indices", [1, 2, 3]), + ("set_device_property_int64", "Dev.Timestamp", 9999999999), + ("set_device_property_int64_array", "Dev.Timestamps", [100, 200]), + ("set_device_property_string", "Dev.Name", "NewName"), + ("set_device_property_string_array", "Dev.Labels", ["a", "b"]), + ("set_device_property_uint32", "Dev.Count", 255), + ("set_device_property_uint32_array", "Dev.Counts", [10, 20]), + ("set_device_property_uint64", "Dev.LargeCount", 18446744073709551615), + ("set_device_property_uint64_array", "Dev.LargeCounts", [111, 222]), + ], +) +def test___session_opened___set_device_property___interpreter_called_with_args( + interpreter: Mock, session: Session, method: str, prop: str, value: object +) -> None: + session_handle = session._session_handle + + getattr(session, method)("Dev1", prop, value) + + getattr(interpreter, method).assert_called_once_with(session_handle, "Dev1", prop, value) + + +@pytest.mark.parametrize( + "method, resource, prop, value", + [ + ( + "get_physical_channel_property_bool", + "Dev1/phys0", + "PhysChan.IsActive", + False, + ), + ( + "get_physical_channel_property_bool_array", + "Dev1/phys0", + "PhysChan.Flags", + [True, False], + ), + ( + "get_physical_channel_property_double", + "Dev1/phys0", + "PhysChan.Gain", + 3.14, + ), + ( + "get_physical_channel_property_double_array", + "Dev1/phys0", + "PhysChan.Gains", + [1.0, 2.0], + ), + ( + "get_physical_channel_property_int32", + "Dev1/phys0", + "PhysChan.Index", + 4, + ), + ( + "get_physical_channel_property_int32_array", + "Dev1/phys0", + "PhysChan.Indices", + [1, 2, 3], + ), + ( + "get_physical_channel_property_int64", + "Dev1/phys0", + "PhysChan.Timestamp", + 9999999999, + ), + ( + "get_physical_channel_property_int64_array", + "Dev1/phys0", + "PhysChan.Timestamps", + [100, 200], + ), + ( + "get_physical_channel_property_string", + "Dev1/phys0", + "PhysChan.Name", + "load0", + ), + ( + "get_physical_channel_property_string_array", + "Dev1", + "PhysChan.PhysicalChannels", + ["load0", "load1"], + ), + ( + "get_physical_channel_property_uint32", + "Dev1/phys0", + "PhysChan.Count", + 255, + ), + ( + "get_physical_channel_property_uint32_array", + "Dev1/phys0", + "PhysChan.Counts", + [10, 20], + ), + ( + "get_physical_channel_property_uint64", + "Dev1/phys0", + "PhysChan.LargeCount", + 18446744073709551615, + ), + ( + "get_physical_channel_property_uint64_array", + "Dev1/phys0", + "PhysChan.LargeCounts", + [111, 222], + ), + ], +) +def test___session_opened___get_physical_channel_property___returns_value( + interpreter: Mock, + session: Session, + method: str, + resource: str, + prop: str, + value: object, +) -> None: + getattr(interpreter, method).return_value = value + session_handle = session._session_handle + + result = getattr(session, method)(resource, prop) + + getattr(interpreter, method).assert_called_once_with(session_handle, resource, prop) + assert result == value + + +@pytest.mark.parametrize( + "method, prop, value", + [ + ( + "set_physical_channel_property_bool", + "PhysChan.Enabled", + True, + ), + ( + "set_physical_channel_property_bool_array", + "PhysChan.Flags", + [True, False], + ), + ( + "set_physical_channel_property_double", + "PhysChan.Gain", + 3.14, + ), + ( + "set_physical_channel_property_double_array", + "PhysChan.Gains", + [1.0, 2.0], + ), + ( + "set_physical_channel_property_int32", + "PhysChan.Index", + 7, + ), + ( + "set_physical_channel_property_int32_array", + "PhysChan.Indices", + [1, 2], + ), + ( + "set_physical_channel_property_int64", + "PhysChan.Timestamp", + 9999999999, + ), + ( + "set_physical_channel_property_int64_array", + "PhysChan.Timestamps", + [100, 200], + ), + ( + "set_physical_channel_property_string", + "PhysChan.Label", + "MyLabel", + ), + ( + "set_physical_channel_property_string_array", + "PhysChan.Labels", + ["x", "y"], + ), + ( + "set_physical_channel_property_uint32", + "PhysChan.Count", + 255, + ), + ( + "set_physical_channel_property_uint32_array", + "PhysChan.Counts", + [10, 20], + ), + ( + "set_physical_channel_property_uint64", + "PhysChan.LargeCount", + 18446744073709551615, + ), + ( + "set_physical_channel_property_uint64_array", + "PhysChan.LargeCounts", + [111, 222], + ), + ], +) +def test___session_opened___set_physical_channel_property___interpreter_called_with_args( + interpreter: Mock, session: Session, method: str, prop: str, value: object +) -> None: + session_handle = session._session_handle + + getattr(session, method)("Dev1/phys0", prop, value) + + getattr(interpreter, method).assert_called_once_with(session_handle, "Dev1/phys0", prop, value) + + +def test___session_opened___commit_properties_for_devices___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.commit_properties_for_devices("Dev1") + + interpreter.commit_properties_for_devices.assert_called_once_with(session_handle, "Dev1") + + +def test___session_opened___commit_properties_for_physical_channels___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.commit_properties_for_physical_channels("Dev1/phys0") + + interpreter.commit_properties_for_physical_channels.assert_called_once_with( + session_handle, "Dev1/phys0" + ) + + +def test___session_opened___commit_properties_for_session___interpreter_called( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.commit_properties_for_session() + + interpreter.commit_properties_for_session.assert_called_once_with(session_handle) + + +def test___session_opened___commit_properties_generic___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.commit_properties_generic("$DefaultDevices") + + interpreter.commit_properties_generic.assert_called_once_with(session_handle, "$DefaultDevices") + + +@pytest.mark.parametrize( + "method, prop, value", + [ + ("get_nvmem_area_property_bool", "NVMem.IsWritable", True), + ("get_nvmem_area_property_bool_array", "NVMem.Flags", [True, False]), + ("get_nvmem_area_property_string", "NVMem.Name", "Area1"), + ("get_nvmem_area_property_string_array", "NVMem.Areas", ["Area1", "Area2"]), + ("get_nvmem_area_property_uint32", "NVMem.Size", 512), + ("get_nvmem_area_property_uint32_array", "NVMem.Counts", [10, 20, 30]), + ], +) +def test___session_opened___get_nvmem_area_property___returns_value( + interpreter: Mock, session: Session, method: str, prop: str, value: object +) -> None: + getattr(interpreter, method).return_value = value + session_handle = session._session_handle + + result = getattr(session, method)("Area1", prop) + + getattr(interpreter, method).assert_called_once_with(session_handle, "Area1", prop) + assert result == value + + +@pytest.mark.parametrize( + "method, prop, value", + [ + ("get_session_property_double", "Session.Timeout", 10.0), + ("get_session_property_string", "Session.DefaultDevices", "Dev1"), + ("get_session_property_string_array", "Session.Devices", ["Dev1", "Dev2"]), + ], +) +def test___session_opened___get_session_property___returns_value( + interpreter: Mock, session: Session, method: str, prop: str, value: object +) -> None: + getattr(interpreter, method).return_value = value + session_handle = session._session_handle + + result = getattr(session, method)(prop) + + getattr(interpreter, method).assert_called_once_with(session_handle, prop) + assert result == value + + +@pytest.mark.parametrize( + "method, prop, value", + [ + ("set_session_property_double", "Session.Timeout", 30.0), + ("set_session_property_string", "Session.DefaultDevices", "Dev1"), + ("set_session_property_string_array", "Session.Devices", ["Dev1", "Dev2"]), + ], +) +def test___session_opened___set_session_property___interpreter_called_with_args( + interpreter: Mock, session: Session, method: str, prop: str, value: object +) -> None: + session_handle = session._session_handle + + getattr(session, method)(prop, value) + + getattr(interpreter, method).assert_called_once_with(session_handle, prop, value) + + +@pytest.mark.parametrize( + "method, prop, value", + [ + ("get_system_property_double", "System.Version", 1.5), + ("get_system_property_string_array", "System.Devices", ["Dev1", "Chassis1"]), + ("get_system_property_uint64", "System.Timestamp", 12345678), + ], +) +def test___session_opened___get_system_property___returns_value( + interpreter: Mock, session: Session, method: str, prop: str, value: object +) -> None: + getattr(interpreter, method).return_value = value + session_handle = session._session_handle + + result = getattr(session, method)(prop) + + getattr(interpreter, method).assert_called_once_with(session_handle, prop) + assert result == value + + +def test___session_opened___set_system_property_double___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.set_system_property_double("System.Timeout", 5.0) + + interpreter.set_system_property_double.assert_called_once_with( + session_handle, "System.Timeout", 5.0 + ) + + +@pytest.mark.parametrize( + "method, prop, value", + [ + ("get_generic_property_bool", "Dev.IsPresent", False), + ("get_generic_property_bool_array", "Dev.Flags", [True, False]), + ("get_generic_property_double", "Dev.Temperature", 3.14), + ("get_generic_property_double_array", "Dev.Temperatures", [1.0, 2.0]), + ("get_generic_property_int32", "Dev.NumSlots", 3), + ("get_generic_property_int32_array", "Dev.Indices", [1, 2, 3]), + ("get_generic_property_int64", "Dev.Timestamp", 9999999999), + ("get_generic_property_int64_array", "Dev.Timestamps", [100, 200]), + ("get_generic_property_string", "Dev.ProductName", "SLSC-99999"), + ("get_generic_property_string_array", "Dev.Modules", ["Mod1", "Mod2"]), + ("get_generic_property_uint32", "Dev.Count", 255), + ("get_generic_property_uint32_array", "Dev.Counts", [10, 20]), + ("get_generic_property_uint64", "Dev.LargeCount", 18446744073709551615), + ("get_generic_property_uint64_array", "Dev.LargeCounts", [111, 222]), + ], +) +def test___session_opened___get_generic_property___returns_value( + interpreter: Mock, session: Session, method: str, prop: str, value: object +) -> None: + getattr(interpreter, method).return_value = value + session_handle = session._session_handle + + result = getattr(session, method)("Dev1", prop) + + getattr(interpreter, method).assert_called_once_with(session_handle, "Dev1", prop) + assert result == value + + +@pytest.mark.parametrize( + "method, prop, value", + [ + ("set_generic_property_bool", "Dev.Enabled", True), + ("set_generic_property_bool_array", "Dev.Flags", [True, False]), + ("set_generic_property_double", "Dev.Temperature", 3.14), + ("set_generic_property_double_array", "Dev.Temperatures", [1.0, 2.0]), + ("set_generic_property_int32", "Dev.Index", 42), + ("set_generic_property_int32_array", "Dev.Indices", [1, 2, 3]), + ("set_generic_property_int64", "Dev.Timestamp", 9999999999), + ("set_generic_property_int64_array", "Dev.Timestamps", [100, 200]), + ("set_generic_property_string", "Dev.Name", "NewName"), + ("set_generic_property_string_array", "Dev.Labels", ["a", "b"]), + ("set_generic_property_uint32", "Dev.Count", 255), + ("set_generic_property_uint32_array", "Dev.Counts", [10, 20]), + ("set_generic_property_uint64", "Dev.LargeCount", 18446744073709551615), + ("set_generic_property_uint64_array", "Dev.LargeCounts", [111, 222]), + ], +) +def test___session_opened___set_generic_property___interpreter_called_with_args( + interpreter: Mock, session: Session, method: str, prop: str, value: object +) -> None: + session_handle = session._session_handle + + getattr(session, method)("Dev1", prop, value) + + getattr(interpreter, method).assert_called_once_with(session_handle, "Dev1", prop, value) + + +def test___session_opened___execute_device_command___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.execute_device_command("Dev1", "Reset", 10.0) + + interpreter.execute_device_command.assert_called_once_with( + session_handle, "Dev1", "Reset", 10.0 + ) + + +def test___session_opened___execute_physical_channel_command___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.execute_physical_channel_command("Dev1/phys0", "Calibrate", 5.0) + + interpreter.execute_physical_channel_command.assert_called_once_with( + session_handle, "Dev1/phys0", "Calibrate", 5.0 + ) + + +def test___session_opened___execute_generic_command___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.execute_generic_command("$DefaultDevices", "Reset", 10.0) + + interpreter.execute_generic_command.assert_called_once_with( + session_handle, "$DefaultDevices", "Reset", 10.0 + ) + + +@pytest.mark.parametrize( + "method, address, value", + [ + ("read_register_uint8", 0x00, 0xFF), + ("read_register_uint16", 0x02, 0xFFFF), + ("read_register_uint32", 0x04, 0xDEADBEEF), + ("read_register_uint64", 0x08, 0xDEADBEEFCAFEBABE), + ], +) +def test___session_opened___read_register___returns_value( + interpreter: Mock, session: Session, method: str, address: int, value: int +) -> None: + getattr(interpreter, method).return_value = value + session_handle = session._session_handle + + result = getattr(session, method)("Dev1", address) + + getattr(interpreter, method).assert_called_once_with(session_handle, "Dev1", address) + assert result == value + + +@pytest.mark.parametrize( + "method, address, value", + [ + ("write_register_uint8", 0x00, 0xAB), + ("write_register_uint16", 0x02, 0xABCD), + ("write_register_uint32", 0x04, 0xDEADBEEF), + ("write_register_uint64", 0x08, 0xDEADBEEFCAFEBABE), + ], +) +def test___session_opened___write_register___interpreter_called_with_args( + interpreter: Mock, session: Session, method: str, address: int, value: int +) -> None: + session_handle = session._session_handle + + getattr(session, method)("Dev1", address, value) + + getattr(interpreter, method).assert_called_once_with(session_handle, "Dev1", address, value) + + +def test___session_opened___get_nvmem_bytes___returns_value( + interpreter: Mock, session: Session +) -> None: + interpreter.get_nvmem_bytes.return_value = b"\x01\x02\x03" + session_handle = session._session_handle + + result = session.get_nvmem_bytes("Area1", 0, 3) + + interpreter.get_nvmem_bytes.assert_called_once_with(session_handle, "Area1", 0, 3) + assert result == b"\x01\x02\x03" + + +def test___session_opened___set_nvmem_bytes___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.set_nvmem_bytes("Area1", 0, b"\x01\x02\x03", "SN001", "pass") + + interpreter.set_nvmem_bytes.assert_called_once_with( + session_handle, "Area1", 0, b"\x01\x02\x03", "SN001", "pass" + ) + + +def test___session_opened___commit_nvmem_areas___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.commit_nvmem_areas("Area1") + + interpreter.commit_nvmem_areas.assert_called_once_with(session_handle, "Area1") + + +def test___session_opened___commit_nvmem_for_devices___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.commit_nvmem_for_devices("Dev1") + + interpreter.commit_nvmem_for_devices.assert_called_once_with(session_handle, "Dev1") + + +def test___session_opened___commit_nvmem_for_session___interpreter_called( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.commit_nvmem_for_session() + + interpreter.commit_nvmem_for_session.assert_called_once_with(session_handle) + + +def test___session_opened___commit_nvmem_generic___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.commit_nvmem_generic("$DefaultDevices") + + interpreter.commit_nvmem_generic.assert_called_once_with(session_handle, "$DefaultDevices") + + +def test___session_opened___get_linear_scaling_parameters___returns_value( + interpreter: Mock, session: Session +) -> None: + interpreter.get_linear_scaling_parameters.return_value = (2.0, 0.5) + session_handle = session._session_handle + + result = session.get_linear_scaling_parameters("Dev1/phys0") + + interpreter.get_linear_scaling_parameters.assert_called_once_with(session_handle, "Dev1/phys0") + assert result == (2.0, 0.5) + + +def test___session_opened___get_polynomial_scaling_parameters___returns_value( + interpreter: Mock, session: Session +) -> None: + interpreter.get_polynomial_scaling_parameters.return_value = ([1.0, 2.0], [0.5, 0.25]) + session_handle = session._session_handle + + result = session.get_polynomial_scaling_parameters("Dev1/phys0") + + interpreter.get_polynomial_scaling_parameters.assert_called_once_with( + session_handle, "Dev1/phys0" + ) + assert result == ([1.0, 2.0], [0.5, 0.25]) + + +def test___session_opened___get_table_scaling_parameters___returns_value( + interpreter: Mock, session: Session +) -> None: + interpreter.get_table_scaling_parameters.return_value = ([0.0, 1.0], [0.0, 10.0], 0) + session_handle = session._session_handle + + result = session.get_table_scaling_parameters("Dev1/phys0") + + interpreter.get_table_scaling_parameters.assert_called_once_with(session_handle, "Dev1/phys0") + assert result == ([0.0, 1.0], [0.0, 10.0], 0) + + +def test___session_opened___get_user_defined_scaling_parameters___returns_value( + interpreter: Mock, session: Session +) -> None: + interpreter.get_user_defined_scaling_parameters.return_value = ( + ["gain", "offset"], + [2.0, 0.5], + ) + session_handle = session._session_handle + + result = session.get_user_defined_scaling_parameters("Dev1/phys0") + + interpreter.get_user_defined_scaling_parameters.assert_called_once_with( + session_handle, "Dev1/phys0" + ) + assert result == (["gain", "offset"], [2.0, 0.5]) + + +def test___session_opened___get_user_defined_scaling_equation___returns_value( + interpreter: Mock, session: Session +) -> None: + interpreter.get_user_defined_scaling_equation.return_value = "gain * x + offset" + session_handle = session._session_handle + + result = session.get_user_defined_scaling_equation("Dev1/phys0") + + interpreter.get_user_defined_scaling_equation.assert_called_once_with( + session_handle, "Dev1/phys0" + ) + assert result == "gain * x + offset" + + +def test___session_opened___set_linear_scaling_parameters___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.set_linear_scaling_parameters("Dev1/phys0", 2.0, 0.5, "SN001", "pass") + + interpreter.set_linear_scaling_parameters.assert_called_once_with( + session_handle, "Dev1/phys0", 2.0, 0.5, "SN001", "pass" + ) + + +def test___session_opened___set_polynomial_scaling_parameters___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.set_polynomial_scaling_parameters( + "Dev1/phys0", [1.0, 2.0], [0.5, 0.25], "SN001", "pass" + ) + + interpreter.set_polynomial_scaling_parameters.assert_called_once_with( + session_handle, "Dev1/phys0", [1.0, 2.0], [0.5, 0.25], "SN001", "pass" + ) + + +def test___session_opened___set_table_scaling_parameters___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.set_table_scaling_parameters( + "Dev1/phys0", + [0.0, 1.0], + [0.0, 10.0], + TableScaleCoercion.INTERPOLATE, + "SN001", + "pass", + ) + + interpreter.set_table_scaling_parameters.assert_called_once_with( + session_handle, + "Dev1/phys0", + [0.0, 1.0], + [0.0, 10.0], + TableScaleCoercion.INTERPOLATE, + "SN001", + "pass", + ) + + +def test___session_opened___set_user_defined_scaling_parameters___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.set_user_defined_scaling_parameters( + "Dev1/phys0", ["gain", "offset"], [2.0, 0.5], "SN001", "pass" + ) + + interpreter.set_user_defined_scaling_parameters.assert_called_once_with( + session_handle, "Dev1/phys0", ["gain", "offset"], [2.0, 0.5], "SN001", "pass" + ) + + +def test___session_opened___set_user_defined_scaling_equation___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.set_user_defined_scaling_equation("Dev1/phys0", "gain * x + offset", "SN001", "pass") + + interpreter.set_user_defined_scaling_equation.assert_called_once_with( + session_handle, "Dev1/phys0", "gain * x + offset", "SN001", "pass" + ) + + +def test___session_opened___commit_scaling_for_devices___interpreter_called_with_args( + interpreter: Mock, session: Session +) -> None: + session_handle = session._session_handle + + session.commit_scaling_for_devices("Dev1") + + interpreter.commit_scaling_for_devices.assert_called_once_with(session_handle, "Dev1") diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py new file mode 100644 index 0000000..6f597eb --- /dev/null +++ b/tests/unit/test_utils.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +from unittest.mock import Mock + +from nislsc.utils import flatten_names, get_library_version, unflatten_names + + +def test___interpreter_available___get_library_version___delegates_to_interpreter( + interpreter: Mock, +) -> None: + interpreter.get_library_version.return_value = 1 + + result = get_library_version() + + interpreter.get_library_version.assert_called_once_with() + assert result == 1 + + +def test___flatten_names___delegates_to_interpreter( + interpreter: Mock, +) -> None: + interpreter.flatten_names.return_value = "Mod1,Mod2,Chassis" + + result = flatten_names(["Mod1", "Mod2", "Chassis"]) + + interpreter.flatten_names.assert_called_once_with(["Mod1", "Mod2", "Chassis"]) + assert result == "Mod1,Mod2,Chassis" + + +def test___unflatten_names___delegates_to_interpreter( + interpreter: Mock, +) -> None: + interpreter.unflatten_names.return_value = ["Mod1", "Mod2", "Chassis"] + + result = unflatten_names("Mod1,Mod2,Chassis") + + interpreter.unflatten_names.assert_called_once_with("Mod1,Mod2,Chassis") + assert result == ["Mod1", "Mod2", "Chassis"]