diff --git a/poetry.lock b/poetry.lock index 602bc61..b00f9c3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "alabaster" @@ -436,6 +436,107 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "librt" +version = "0.8.1" +description = "Mypyc runtime library" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +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"}, + {file = "librt-0.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d56bc4011975f7460bea7b33e1ff425d2f1adf419935ff6707273c77f8a4ada6"}, + {file = "librt-0.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdc0f588ff4b663ea96c26d2a230c525c6fc62b28314edaaaca8ed5af931ad0"}, + {file = "librt-0.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c2b54ff6717a7a563b72627990bec60d8029df17df423f0ed37d56a17a176b"}, + {file = "librt-0.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f1125e6bbf2f1657d9a2f3ccc4a2c9b0c8b176965bb565dd4d86be67eddb4b6"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f4bb453f408137d7581be309b2fbc6868a80e7ef60c88e689078ee3a296ae71"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c336d61d2fe74a3195edc1646d53ff1cddd3a9600b09fa6ab75e5514ba4862a7"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eb5656019db7c4deacf0c1a55a898c5bb8f989be904597fcb5232a2f4828fa05"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c25d9e338d5bed46c1632f851babf3d13c78f49a225462017cf5e11e845c5891"}, + {file = "librt-0.8.1-cp310-cp310-win32.whl", hash = "sha256:aaab0e307e344cb28d800957ef3ec16605146ef0e59e059a60a176d19543d1b7"}, + {file = "librt-0.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:56e04c14b696300d47b3bc5f1d10a00e86ae978886d0cee14e5714fafb5df5d2"}, + {file = "librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd"}, + {file = "librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965"}, + {file = "librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da"}, + {file = "librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0"}, + {file = "librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e"}, + {file = "librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe"}, + {file = "librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb"}, + {file = "librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b"}, + {file = "librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9"}, + {file = "librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a"}, + {file = "librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9"}, + {file = "librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb"}, + {file = "librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d"}, + {file = "librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7"}, + {file = "librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0"}, + {file = "librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a"}, + {file = "librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444"}, + {file = "librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d"}, + {file = "librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35"}, + {file = "librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583"}, + {file = "librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c"}, + {file = "librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04"}, + {file = "librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363"}, + {file = "librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d"}, + {file = "librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a"}, + {file = "librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79"}, + {file = "librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0"}, + {file = "librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f"}, + {file = "librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c"}, + {file = "librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc"}, + {file = "librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c"}, + {file = "librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3"}, + {file = "librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78"}, + {file = "librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023"}, + {file = "librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730"}, + {file = "librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3"}, + {file = "librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1"}, + {file = "librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994"}, + {file = "librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a"}, + {file = "librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4"}, + {file = "librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61"}, + {file = "librt-0.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3dff3d3ca8db20e783b1bc7de49c0a2ab0b8387f31236d6a026597d07fcd68ac"}, + {file = "librt-0.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08eec3a1fc435f0d09c87b6bf1ec798986a3544f446b864e4099633a56fcd9ed"}, + {file = "librt-0.8.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e3f0a41487fd5fad7e760b9e8a90e251e27c2816fbc2cff36a22a0e6bcbbd9dd"}, + {file = "librt-0.8.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bacdb58d9939d95cc557b4dbaa86527c9db2ac1ed76a18bc8d26f6dc8647d851"}, + {file = "librt-0.8.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d7ab1f01aa753188605b09a51faa44a3327400b00b8cce424c71910fc0a128"}, + {file = "librt-0.8.1-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4998009e7cb9e896569f4be7004f09d0ed70d386fa99d42b6d363f6d200501ac"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2cc68eeeef5e906839c7bb0815748b5b0a974ec27125beefc0f942715785b551"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0bf69d79a23f4f40b8673a947a234baeeb133b5078b483b7297c5916539cf5d5"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:22b46eabd76c1986ee7d231b0765ad387d7673bbd996aa0d0d054b38ac65d8f6"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:237796479f4d0637d6b9cbcb926ff424a97735e68ade6facf402df4ec93375ed"}, + {file = "librt-0.8.1-cp39-cp39-win32.whl", hash = "sha256:4beb04b8c66c6ae62f8c1e0b2f097c1ebad9295c929a8d5286c05eae7c2fc7dc"}, + {file = "librt-0.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:64548cde61b692dc0dc379f4b5f59a2f582c2ebe7890d09c1ae3b9e66fa015b7"}, + {file = "librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73"}, +] + [[package]] name = "markupsafe" version = "2.1.5" @@ -508,53 +609,64 @@ files = [ [[package]] name = "mypy" -version = "0.990" +version = "1.19.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mypy-0.990-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aaf1be63e0207d7d17be942dcf9a6b641745581fe6c64df9a38deb562a7dbafa"}, - {file = "mypy-0.990-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d555aa7f44cecb7ea3c0ac69d58b1a5afb92caa017285a8e9c4efbf0518b61b4"}, - {file = "mypy-0.990-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f694d6d09a460b117dccb6857dda269188e3437c880d7b60fa0014fa872d1e9"}, - {file = "mypy-0.990-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:269f0dfb6463b8780333310ff4b5134425157ef0d2b1d614015adaf6d6a7eabd"}, - {file = "mypy-0.990-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8798c8ed83aa809f053abff08664bdca056038f5a02af3660de00b7290b64c47"}, - {file = "mypy-0.990-cp310-cp310-win_amd64.whl", hash = "sha256:47a9955214615108c3480a500cfda8513a0b1cd3c09a1ed42764ca0dd7b931dd"}, - {file = "mypy-0.990-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4a8a6c10f4c63fbf6ad6c03eba22c9331b3946a4cec97f008e9ffb4d3b31e8e2"}, - {file = "mypy-0.990-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd2dd3730ba894ec2a2082cc703fbf3e95a08479f7be84912e3131fc68809d46"}, - {file = "mypy-0.990-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7da0005e47975287a92b43276e460ac1831af3d23032c34e67d003388a0ce8d0"}, - {file = "mypy-0.990-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262c543ef24deb10470a3c1c254bb986714e2b6b1a67d66daf836a548a9f316c"}, - {file = "mypy-0.990-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3ff201a0c6d3ea029d73b1648943387d75aa052491365b101f6edd5570d018ea"}, - {file = "mypy-0.990-cp311-cp311-win_amd64.whl", hash = "sha256:1767830da2d1afa4e62b684647af0ff79b401f004d7fa08bc5b0ce2d45bcd5ec"}, - {file = "mypy-0.990-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6826d9c4d85bbf6d68cb279b561de6a4d8d778ca8e9ab2d00ee768ab501a9852"}, - {file = "mypy-0.990-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46897755f944176fbc504178422a5a2875bbf3f7436727374724842c0987b5af"}, - {file = "mypy-0.990-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0680389c34284287fe00e82fc8bccdea9aff318f7e7d55b90d967a13a9606013"}, - {file = "mypy-0.990-cp37-cp37m-win_amd64.whl", hash = "sha256:b08541a06eed35b543ae1a6b301590eb61826a1eb099417676ddc5a42aa151c5"}, - {file = "mypy-0.990-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:be88d665e76b452c26fb2bdc3d54555c01226fba062b004ede780b190a50f9db"}, - {file = "mypy-0.990-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b8f4a8213b1fd4b751e26b59ae0e0c12896568d7e805861035c7a15ed6dc9eb"}, - {file = "mypy-0.990-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2b6f85c2ad378e3224e017904a051b26660087b3b76490d533b7344f1546d3ff"}, - {file = "mypy-0.990-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee5f99817ee70254e7eb5cf97c1b11dda29c6893d846c8b07bce449184e9466"}, - {file = "mypy-0.990-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49082382f571c3186ce9ea0bd627cb1345d4da8d44a8377870f4442401f0a706"}, - {file = "mypy-0.990-cp38-cp38-win_amd64.whl", hash = "sha256:aba38e3dd66bdbafbbfe9c6e79637841928ea4c79b32e334099463c17b0d90ef"}, - {file = "mypy-0.990-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9d851c09b981a65d9d283a8ccb5b1d0b698e580493416a10942ef1a04b19fd37"}, - {file = "mypy-0.990-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d847dd23540e2912d9667602271e5ebf25e5788e7da46da5ffd98e7872616e8e"}, - {file = "mypy-0.990-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc6019808580565040cd2a561b593d7c3c646badd7e580e07d875eb1bf35c695"}, - {file = "mypy-0.990-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3150d409609a775c8cb65dbe305c4edd7fe576c22ea79d77d1454acd9aeda8"}, - {file = "mypy-0.990-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3227f14fe943524f5794679156488f18bf8d34bfecd4623cf76bc55958d229c5"}, - {file = "mypy-0.990-cp39-cp39-win_amd64.whl", hash = "sha256:c76c769c46a1e6062a84837badcb2a7b0cdb153d68601a61f60739c37d41cc74"}, - {file = "mypy-0.990-py3-none-any.whl", hash = "sha256:8f1940325a8ed460ba03d19ab83742260fa9534804c317224e5d4e5aa588e2d6"}, - {file = "mypy-0.990.tar.gz", hash = "sha256:72382cb609142dba3f04140d016c94b4092bc7b4d98ca718740dc989e5271b8d"}, + {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"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"}, + {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"}, + {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"}, + {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"}, + {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"}, + {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"}, + {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"}, + {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"}, + {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"}, + {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"}, + {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"}, + {file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"}, + {file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"}, + {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"}, + {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"}, ] [package.dependencies] -mypy-extensions = ">=0.4.3" +librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""} +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=3.10" +typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] +mypyc = ["setuptools (>=50)"] reports = ["lxml"] [[package]] @@ -596,6 +708,24 @@ files = [ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] +[[package]] +name = "pathspec" +version = "1.0.4" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, + {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, +] + +[package.extras] +hyperscan = ["hyperscan (>=0.7)"] +optional = ["typing-extensions (>=4)"] +re2 = ["google-re2 (>=1.1)"] +tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] + [[package]] name = "platformdirs" version = "4.0.0" @@ -1070,4 +1200,4 @@ test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-it [metadata] lock-version = "2.1" python-versions = ">=3.9" -content-hash = "2528f55c9a960b658ee758585ba3b1c76546a8bf1c53fe61bc724e6eb045ade6" +content-hash = "0eec9d00c51b390c077534841b531d85c22a7aa138e622b20525166f6a628967" diff --git a/pyproject.toml b/pyproject.toml index c30819e..b424f57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ pytest = "^7.2.0" pytest-cov = "^4.0.0" PyYAML = "^6.0" pre-commit = "^2.20.0" -mypy = "^0.990" +mypy = "1.19.1" Sphinx = "^4.3.2" furo = "^2022.9.29" @@ -54,6 +54,15 @@ force-single-line = true lines-after-imports = 2 lines-between-types = 1 +[tool.mypy] +files = "tomlkit, tests" +strict = true +enable_error_code = [ + "ignore-without-code", + "redundant-expr", + "truthy-bool", +] + [build-system] requires = ["poetry-core>=1.0.0a9"] build-backend = "poetry.core.masonry.api" diff --git a/tests/conftest.py b/tests/conftest.py index ec7956c..8a7d301 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,13 @@ import os +from collections.abc import Callable + import pytest @pytest.fixture -def example(): - def _example(name): +def example() -> Callable[[str], str]: + def _example(name: str) -> str: with open( os.path.join(os.path.dirname(__file__), "examples", name + ".toml"), encoding="utf-8", @@ -16,8 +18,8 @@ def _example(name): @pytest.fixture -def json_example(): - def _example(name): +def json_example() -> Callable[[str], str]: + def _example(name: str) -> str: with open( os.path.join(os.path.dirname(__file__), "examples", "json", name + ".json"), encoding="utf-8", @@ -28,8 +30,8 @@ def _example(name): @pytest.fixture -def invalid_example(): - def _example(name): +def invalid_example() -> Callable[[str], str]: + def _example(name: str) -> str: with open( os.path.join( os.path.dirname(__file__), "examples", "invalid", name + ".toml" diff --git a/tests/test_api.py b/tests/test_api.py index 3d6d8ae..eb27229 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -6,6 +6,8 @@ from datetime import datetime from datetime import time from types import MappingProxyType +from typing import Any +from typing import Callable import pytest @@ -38,7 +40,7 @@ from tomlkit.toml_document import TOMLDocument -def json_serial(obj): +def json_serial(obj: Any) -> str: """JSON serializer for objects not serializable by default json code""" if isinstance(obj, (datetime, date, time)): return obj.isoformat() @@ -62,7 +64,9 @@ def json_serial(obj): "table_names", ], ) -def test_parse_can_parse_valid_toml_files(example, example_name): +def test_parse_can_parse_valid_toml_files( + example: Callable[[str], str], example_name: str +) -> None: assert isinstance(parse(example(example_name)), TOMLDocument) assert isinstance(loads(example(example_name)), TOMLDocument) @@ -83,7 +87,7 @@ def test_parse_can_parse_valid_toml_files(example, example_name): "table_names", ], ) -def test_load_from_file_object(example_name): +def test_load_from_file_object(example_name: str) -> None: with open( os.path.join(os.path.dirname(__file__), "examples", example_name + ".toml"), encoding="utf-8", @@ -93,8 +97,8 @@ def test_load_from_file_object(example_name): @pytest.mark.parametrize("example_name", ["0.5.0", "pyproject", "table_names"]) def test_parsed_document_are_properly_json_representable( - example, json_example, example_name -): + example: Callable[[str], str], json_example: Callable[[str], str], example_name: str +) -> None: doc = json.loads(json.dumps(parse(example(example_name)), default=json_serial)) json_doc = json.loads(json_example(example_name)) @@ -123,8 +127,8 @@ def test_parsed_document_are_properly_json_representable( ], ) def test_parse_raises_errors_for_invalid_toml_files( - invalid_example, error, example_name -): + invalid_example: Callable[[str], str], error: type[Exception], example_name: str +) -> None: with pytest.raises(error): parse(invalid_example(example_name)) @@ -142,69 +146,71 @@ def test_parse_raises_errors_for_invalid_toml_files( "table_names", ], ) -def test_original_string_and_dumped_string_are_equal(example, example_name): +def test_original_string_and_dumped_string_are_equal( + example: Callable[[str], str], example_name: str +) -> None: content = example(example_name) parsed = parse(content) assert content == dumps(parsed) -def test_a_raw_dict_can_be_dumped(): +def test_a_raw_dict_can_be_dumped() -> None: s = dumps({"foo": "bar"}) assert s == 'foo = "bar"\n' -def test_mapping_types_can_be_dumped(): +def test_mapping_types_can_be_dumped() -> None: x = MappingProxyType({"foo": "bar"}) assert dumps(x) == 'foo = "bar"\n' -def test_dumps_weird_object(): +def test_dumps_weird_object() -> None: with pytest.raises(TypeError): - dumps(object()) + dumps(object()) # type: ignore[arg-type] -def test_dump_tuple_value_as_array(): - x = {"foo": (1, 2)} +def test_dump_tuple_value_as_array() -> None: + x: dict[str, Any] = {"foo": (1, 2)} assert dumps(x) == "foo = [1, 2]\n" x = {"foo": ({"a": 1}, {"a": 2})} assert dumps(x) == "[[foo]]\na = 1\n\n[[foo]]\na = 2\n" -def test_dump_to_file_object(): +def test_dump_to_file_object() -> None: doc = {"foo": "bar"} fp = io.StringIO() dump(doc, fp) assert fp.getvalue() == 'foo = "bar"\n' -def test_dump_nested_dotted_table(): - a = tomlkit.parse("a.b.c.d='e'")["a"] +def test_dump_nested_dotted_table() -> None: + a: Any = tomlkit.parse("a.b.c.d='e'")["a"] assert a == {"b": {"c": {"d": "e"}}} assert dumps(a) == "b.c.d='e'" -def test_integer(): +def test_integer() -> None: i = tomlkit.integer("34") assert isinstance(i, Integer) -def test_float(): +def test_float() -> None: i = tomlkit.float_("34.56") assert isinstance(i, Float) -def test_boolean(): +def test_boolean() -> None: i = tomlkit.boolean("true") assert isinstance(i, Bool) -def test_date(): +def test_date() -> None: dt = tomlkit.date("1979-05-13") assert isinstance(dt, Date) @@ -213,7 +219,7 @@ def test_date(): tomlkit.date("12:34:56") -def test_time(): +def test_time() -> None: dt = tomlkit.time("12:34:56") assert isinstance(dt, Time) @@ -222,7 +228,7 @@ def test_time(): tomlkit.time("1979-05-13") -def test_datetime(): +def test_datetime() -> None: dt = tomlkit.datetime("1979-05-13T12:34:56") assert isinstance(dt, DateTime) @@ -231,7 +237,7 @@ def test_datetime(): tomlkit.time("1979-05-13") -def test_array(): +def test_array() -> None: a = tomlkit.array() assert isinstance(a, Array) @@ -241,45 +247,45 @@ def test_array(): assert isinstance(a, Array) -def test_table(): +def test_table() -> None: t = tomlkit.table() assert isinstance(t, Table) -def test_inline_table(): +def test_inline_table() -> None: t = tomlkit.inline_table() assert isinstance(t, InlineTable) -def test_aot(): +def test_aot() -> None: t = tomlkit.aot() assert isinstance(t, AoT) -def test_key(): +def test_key() -> None: k = tomlkit.key("foo") assert isinstance(k, Key) -def test_key_value(): +def test_key_value() -> None: k, i = tomlkit.key_value("foo = 12") assert isinstance(k, Key) assert isinstance(i, Integer) -def test_string(): +def test_string() -> None: s = tomlkit.string('foo "') assert s.value == 'foo "' assert s.as_string() == '"foo \\""' -def test_item_dict_to_table(): +def test_item_dict_to_table() -> None: t = tomlkit.item({"foo": {"bar": "baz"}}) assert t.value == {"foo": {"bar": "baz"}} @@ -291,7 +297,7 @@ def test_item_dict_to_table(): ) -def test_item_mixed_aray(): +def test_item_mixed_aray() -> None: example = [{"a": 3}, "b", 42] expected = '[{a = 3}, "b", 42]' t = tomlkit.item(example) @@ -299,7 +305,7 @@ def test_item_mixed_aray(): assert dumps({"x": {"y": example}}).strip() == "[x]\ny = " + expected -def test_build_super_table(): +def test_build_super_table() -> None: doc = tomlkit.document() table = tomlkit.table(True) table.add("bar", {"x": 1}) @@ -307,7 +313,7 @@ def test_build_super_table(): assert doc.as_string() == "[foo.bar]\nx = 1\n" -def test_add_dotted_key(): +def test_add_dotted_key() -> None: doc = tomlkit.document() doc.add(tomlkit.key(["foo", "bar"]), 1) assert doc.as_string() == "foo.bar = 1\n" @@ -324,15 +330,15 @@ def test_add_dotted_key(): ("false", False), ], ) -def test_value_parses_boolean(raw, expected): +def test_value_parses_boolean(raw: str, expected: bool) -> None: parsed = tomlkit.value(raw) - assert parsed == expected + assert parsed == expected # type: ignore[comparison-overlap] @pytest.mark.parametrize( "raw", ["t", "f", "tru", "fals", "test", "friend", "truthy", "falsify"] ) -def test_value_rejects_values_looking_like_bool_at_start(raw): +def test_value_rejects_values_looking_like_bool_at_start(raw: str) -> None: """Reproduces https://github.com/sdispater/tomlkit/issues/165""" with pytest.raises(tomlkit.exceptions.ParseError): tomlkit.value(raw) @@ -347,7 +353,7 @@ def test_value_rejects_values_looking_like_bool_at_start(raw): "true_hip_hop", ], ) -def test_value_rejects_values_having_true_prefix(raw): +def test_value_rejects_values_having_true_prefix(raw: str) -> None: """Values that have ``true`` or ``false`` as prefix but then have additional chars are rejected.""" with pytest.raises(tomlkit.exceptions.ParseError): tomlkit.value(raw) @@ -362,7 +368,7 @@ def test_value_rejects_values_having_true_prefix(raw): "false_prophet", ], ) -def test_value_rejects_values_having_false_prefix(raw): +def test_value_rejects_values_having_false_prefix(raw: str) -> None: """Values that have ``true`` or ``false`` as prefix but then have additional chars are rejected.""" with pytest.raises(tomlkit.exceptions.ParseError): tomlkit.value(raw) @@ -384,18 +390,18 @@ def test_value_rejects_values_having_false_prefix(raw): "false{a=1}", ], ) -def test_value_rejects_values_with_appendage(raw): +def test_value_rejects_values_with_appendage(raw: str) -> None: """Values that appear valid at the beginning but leave chars unparsed are rejected.""" with pytest.raises(tomlkit.exceptions.ParseError): tomlkit.value(raw) -def test_create_super_table_with_table(): +def test_create_super_table_with_table() -> None: data = {"foo": {"bar": {"a": 1}}} assert dumps(data) == "[foo.bar]\na = 1\n" -def test_create_super_table_with_aot(): +def test_create_super_table_with_aot() -> None: data = {"foo": {"bar": [{"a": 1}]}} assert dumps(data) == "[[foo.bar]]\na = 1\n" @@ -442,7 +448,7 @@ def test_create_super_table_with_aot(): ), ], ) -def test_create_string(kwargs, example, expected): +def test_create_string(kwargs: dict[str, Any], example: str, expected: str) -> None: value = tomlkit.string(example, **kwargs) assert value.as_string() == expected @@ -460,12 +466,14 @@ def test_create_string(kwargs, example, expected): ({"multiline": True, "literal": True}, "My'''String"), ], ) -def test_create_string_with_invalid_characters(kwargs, example): +def test_create_string_with_invalid_characters( + kwargs: dict[str, Any], example: str +) -> None: with pytest.raises(InvalidStringError): tomlkit.string(example, **kwargs) -def test_parse_empty_quoted_table_name(): +def test_parse_empty_quoted_table_name() -> None: content = "['']\nx = 1\n" parsed = loads(content) assert parsed == {"": {"x": 1}} diff --git a/tests/test_build.py b/tests/test_build.py index 136abb8..66fa224 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -1,5 +1,7 @@ import datetime +from collections.abc import Callable + from tomlkit import aot from tomlkit import array from tomlkit import comment @@ -11,7 +13,7 @@ from tomlkit._utils import _utc -def test_build_example(example): +def test_build_example(example: Callable[[str], str]) -> None: content = example("example") doc = document() @@ -38,9 +40,8 @@ def test_build_example(example): servers = table() servers.add(nl()) - c = comment( - "You can indent as you please. Tabs or spaces. TOML don't care." - ).indent(2) + c = comment("You can indent as you please. Tabs or spaces. TOML don't care.") + c.indent(2) c.trivia.trail = "" servers.add(c) alpha = table() @@ -95,7 +96,7 @@ def test_build_example(example): assert content == doc.as_string() -def test_add_remove(): +def test_add_remove() -> None: content = "" doc = parse(content) @@ -112,7 +113,7 @@ def test_add_remove(): assert doc.as_string() == "" -def test_append_table_after_multiple_indices(): +def test_append_table_after_multiple_indices() -> None: content = """ [packages] foo = "*" @@ -127,7 +128,7 @@ def test_append_table_after_multiple_indices(): doc.append("foobar", {"name": "John"}) -def test_top_level_keys_are_put_at_the_root_of_the_document(): +def test_top_level_keys_are_put_at_the_root_of_the_document() -> None: doc = document() doc.add(comment("Comment")) doc["foo"] = {"name": "test"} diff --git a/tests/test_items.py b/tests/test_items.py index c465898..f5cbbde 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -1,12 +1,17 @@ +from __future__ import annotations + import copy import math import pickle +from collections.abc import Callable from datetime import date from datetime import datetime from datetime import time from datetime import timedelta from datetime import timezone +from datetime import tzinfo +from typing import Any import pytest @@ -33,50 +38,50 @@ @pytest.fixture() -def tz_pst(): +def tz_pst() -> tzinfo: try: from datetime import timezone return timezone(timedelta(hours=-8), "PST") except ImportError: - from datetime import tzinfo + from datetime import tzinfo as _tzinfo - class PST(tzinfo): - def utcoffset(self, dt): + class PST(_tzinfo): + def utcoffset(self, dt: datetime | None) -> timedelta: return timedelta(hours=-8) - def tzname(self, dt): + def tzname(self, dt: datetime | None) -> str: return "PST" - def dst(self, dt): + def dst(self, dt: datetime | None) -> timedelta: return timedelta(0) return PST() @pytest.fixture() -def tz_utc(): +def tz_utc() -> tzinfo: try: from datetime import timezone return timezone.utc except ImportError: - from datetime import tzinfo + from datetime import tzinfo as _tzinfo - class UTC(tzinfo): - def utcoffset(self, dt): + class UTC(_tzinfo): + def utcoffset(self, dt: datetime | None) -> timedelta: return timedelta(hours=0) - def tzname(self, dt): + def tzname(self, dt: datetime | None) -> str: return "UTC" - def dst(self, dt): + def dst(self, dt: datetime | None) -> timedelta: return timedelta(0) return UTC() -def test_item_base_has_no_unwrap(): +def test_item_base_has_no_unwrap() -> None: trivia = Trivia(indent="\t", comment_ws=" ", comment="For unit test") item = Item(trivia) try: @@ -87,37 +92,37 @@ def test_item_base_has_no_unwrap(): raise AssertionError("`items.Item` should not implement `unwrap`") -def test_integer_unwrap(): +def test_integer_unwrap() -> None: elementary_test(item(666), int) -def test_float_unwrap(): +def test_float_unwrap() -> None: elementary_test(item(2.78), float) -def test_false_unwrap(): +def test_false_unwrap() -> None: elementary_test(item(False), bool) -def test_true_unwrap(): +def test_true_unwrap() -> None: elementary_test(item(True), bool) -def test_datetime_unwrap(): +def test_datetime_unwrap() -> None: dt = datetime.now(tz=timezone.utc) elementary_test(item(dt), datetime) -def test_string_unwrap(): +def test_string_unwrap() -> None: elementary_test(item("hello"), str) -def test_null_unwrap(): +def test_null_unwrap() -> None: n = Null() elementary_test(n, type(None)) -def test_aot_unwrap(): +def test_aot_unwrap() -> None: d = item([{"a": "A"}, {"b": "B"}]) unwrapped = d.unwrap() assert_is_ppo(unwrapped, list) @@ -129,7 +134,7 @@ def test_aot_unwrap(): assert_is_ppo(vu, str) -def test_aot_set_item(): +def test_aot_set_item() -> None: d = item(["A", {"b": "B"}, ["c", "D"]]) d[0] = "C" assert isinstance(d[0], String) @@ -142,17 +147,17 @@ def test_aot_set_item(): assert d[0][1] == "C" -def test_time_unwrap(): +def test_time_unwrap() -> None: t = time(3, 8, 14) elementary_test(item(t), time) -def test_date_unwrap(): +def test_date_unwrap() -> None: d = date.today() elementary_test(item(d), date) -def test_array_unwrap(): +def test_array_unwrap() -> None: trivia = Trivia(indent="\t", comment_ws=" ", comment="For unit test") i = item(666) f = item(2.78) @@ -165,7 +170,7 @@ def test_array_unwrap(): assert_is_ppo(a_unwrapped[2], bool) -def test_abstract_table_unwrap(): +def test_abstract_table_unwrap() -> None: table = item({"foo": "bar"}) super_table = item({"table": table, "baz": "borg"}) @@ -179,7 +184,7 @@ def test_abstract_table_unwrap(): assert_is_ppo(vu, str) -def test_key_comparison(): +def test_key_comparison() -> None: k = Key("foo") assert k == Key("foo") @@ -188,7 +193,7 @@ def test_key_comparison(): assert k != 5 -def test_items_can_be_appended_to_and_removed_from_a_table(): +def test_items_can_be_appended_to_and_removed_from_a_table() -> None: string = """[table] """ @@ -221,12 +226,14 @@ def test_items_can_be_appended_to_and_removed_from_a_table(): table.remove(Key("foo")) -def test_items_can_be_appended_to_and_removed_from_an_inline_table(): +def test_items_can_be_appended_to_and_removed_from_an_inline_table() -> None: string = """table = {} """ parser = Parser(string) - _, table = parser._parse_item() + result = parser._parse_item() + assert result is not None + _, table = result assert isinstance(table, InlineTable) assert table.as_string() == "{}" @@ -251,7 +258,7 @@ def test_items_can_be_appended_to_and_removed_from_an_inline_table(): table.remove(Key("foo")) -def test_inf_and_nan_are_supported(example): +def test_inf_and_nan_are_supported(example: Callable[[str], str]) -> None: content = example("0.5.0") doc = parse(content) @@ -264,7 +271,9 @@ def test_inf_and_nan_are_supported(example): assert math.isnan(doc["sf6"]) -def test_hex_octal_and_bin_integers_are_supported(example): +def test_hex_octal_and_bin_integers_are_supported( + example: Callable[[str], str], +) -> None: content = example("0.5.0") doc = parse(content) @@ -278,7 +287,7 @@ def test_hex_octal_and_bin_integers_are_supported(example): assert doc["bin1"] == 214 -def test_key_automatically_sets_proper_string_type_if_not_bare(): +def test_key_automatically_sets_proper_string_type_if_not_bare() -> None: key = Key("foo.bar") assert key.t == KeyType.Basic @@ -287,7 +296,7 @@ def test_key_automatically_sets_proper_string_type_if_not_bare(): assert key.t == KeyType.Basic -def test_array_behaves_like_a_list(): +def test_array_behaves_like_a_list() -> None: a = item([1, 2]) assert a == [1, 2] @@ -333,7 +342,7 @@ def test_array_behaves_like_a_list(): ) -def test_array_multiline(): +def test_array_multiline() -> None: t = item([1, 2, 3, 4, 5, 6, 7, 8]) t.multiline(True) @@ -351,13 +360,13 @@ def test_array_multiline(): assert expected == t.as_string() - t = item([]) - t.multiline(True) + t2: Any = item([]) + t2.multiline(True) - assert t.as_string() == "[]" + assert t2.as_string() == "[]" -def test_array_multiline_modify(): +def test_array_multiline_modify() -> None: doc = parse( """\ a = [ @@ -381,7 +390,7 @@ def test_array_multiline_modify(): assert expected == doc.as_string() -def test_append_to_empty_array(): +def test_append_to_empty_array() -> None: doc = parse("x = [ ]") doc["x"].append("a") assert doc.as_string() == 'x = ["a" ]' @@ -390,7 +399,7 @@ def test_append_to_empty_array(): assert doc.as_string() == 'x = [\n "a"\n]' -def test_modify_array_with_comment(): +def test_modify_array_with_comment() -> None: doc = parse("x = [ # comment\n]") doc["x"].append("a") assert doc.as_string() == 'x = [ # comment\n "a"\n]' @@ -430,7 +439,7 @@ def test_modify_array_with_comment(): assert doc.as_string() == "x = [\n 2\n]" -def test_append_to_multiline_array_with_comment(): +def test_append_to_multiline_array_with_comment() -> None: doc = parse( """\ x = [ @@ -465,7 +474,7 @@ def test_append_to_multiline_array_with_comment(): ) -def test_append_dict_to_array(): +def test_append_dict_to_array() -> None: doc = parse("x = []") doc["x"].append({"name": "John Doe", "email": "john@doe.com"}) expected = 'x = [{name = "John Doe",email = "john@doe.com"}]' @@ -474,7 +483,7 @@ def test_append_dict_to_array(): assert parse(doc.as_string()) == doc -def test_dicts_are_converted_to_tables(): +def test_dicts_are_converted_to_tables() -> None: t = item({"foo": {"bar": "baz"}}) assert ( @@ -485,7 +494,7 @@ def test_dicts_are_converted_to_tables(): ) -def test_array_add_line(): +def test_array_add_line() -> None: t = api.array() t.add_line(1, 2, 3, comment="Line 1") t.add_line(4, 5, 6, comment="Line 2") @@ -505,7 +514,7 @@ def test_array_add_line(): ) -def test_array_add_line_invalid_value(): +def test_array_add_line_invalid_value() -> None: t = api.array() with pytest.raises(ValueError, match="is not allowed"): t.add_line(1, api.ws(" ")) @@ -514,7 +523,7 @@ def test_array_add_line_invalid_value(): assert len(t) == 0 -def test_dicts_are_converted_to_tables_and_keep_order(): +def test_dicts_are_converted_to_tables_and_keep_order() -> None: t = item( { "foo": { @@ -539,7 +548,7 @@ def test_dicts_are_converted_to_tables_and_keep_order(): ) -def test_dicts_are_converted_to_tables_and_are_sorted_if_requested(): +def test_dicts_are_converted_to_tables_and_are_sorted_if_requested() -> None: t = item( { "foo": { @@ -565,7 +574,7 @@ def test_dicts_are_converted_to_tables_and_are_sorted_if_requested(): ) -def test_dicts_with_sub_dicts_are_properly_converted(): +def test_dicts_with_sub_dicts_are_properly_converted() -> None: t = item( {"foo": {"bar": {"string": "baz"}, "int": 34, "float": 3.14}}, _sort_keys=True ) @@ -582,7 +591,7 @@ def test_dicts_with_sub_dicts_are_properly_converted(): ) -def test_item_array_of_dicts_converted_to_aot(): +def test_item_array_of_dicts_converted_to_aot() -> None: a = item({"foo": [{"bar": "baz"}]}) assert ( @@ -593,7 +602,7 @@ def test_item_array_of_dicts_converted_to_aot(): ) -def test_add_float_to_int(): +def test_add_float_to_int() -> None: content = "[table]\nmy_int = 2043" doc = parse(content) doc["table"]["my_int"] += 5.0 @@ -601,7 +610,7 @@ def test_add_float_to_int(): assert isinstance(doc["table"]["my_int"], float) -def test_sub_float_from_int(): +def test_sub_float_from_int() -> None: content = "[table]\nmy_int = 2048" doc = parse(content) doc["table"]["my_int"] -= 5.0 @@ -609,21 +618,21 @@ def test_sub_float_from_int(): assert isinstance(doc["table"]["my_int"], float) -def test_sub_int_from_float(): +def test_sub_int_from_float() -> None: content = "[table]\nmy_int = 2048.0" doc = parse(content) doc["table"]["my_int"] -= 5 assert doc["table"]["my_int"] == 2043.0 -def test_add_sum_int_with_float(): +def test_add_sum_int_with_float() -> None: content = "[table]\nmy_int = 2048.3" doc = parse(content) doc["table"]["my_int"] += 5 assert doc["table"]["my_int"] == 2053.3 -def test_integers_behave_like_ints(): +def test_integers_behave_like_ints() -> None: i = item(34) assert i == 34 @@ -637,9 +646,9 @@ def test_integers_behave_like_ints(): assert i == 33 assert i.as_string() == "33" - i /= 2 - assert i == 16.5 - assert i.as_string() == "16.5" + f = i / 2 + assert f == 16.5 + assert f.as_string() == "16.5" doc = parse("int = +34") doc["int"] += 1 @@ -647,7 +656,7 @@ def test_integers_behave_like_ints(): assert doc.as_string() == "int = +35" -def test_floats_behave_like_floats(): +def test_floats_behave_like_floats() -> None: i = item(34.12) assert i == 34.12 @@ -667,7 +676,7 @@ def test_floats_behave_like_floats(): assert doc.as_string() == "float = +35.12" -def test_datetimes_behave_like_datetimes(tz_utc, tz_pst): +def test_datetimes_behave_like_datetimes(tz_utc: tzinfo, tz_pst: tzinfo) -> None: i = item(datetime(2018, 7, 22, 12, 34, 56)) assert i == datetime(2018, 7, 22, 12, 34, 56) @@ -695,7 +704,7 @@ def test_datetimes_behave_like_datetimes(tz_utc, tz_pst): assert doc.as_string() == "dt = 2018-07-23T12:34:56-05:00" -def test_dates_behave_like_dates(): +def test_dates_behave_like_dates() -> None: i = item(date(2018, 7, 22)) assert i == date(2018, 7, 22) @@ -719,7 +728,7 @@ def test_dates_behave_like_dates(): assert doc.as_string() == "dt = 2018-07-23 # Comment" -def test_parse_datetime_followed_by_space(): +def test_parse_datetime_followed_by_space() -> None: # issue #260 doc = parse("dt = 2018-07-22 ") assert doc["dt"] == date(2018, 7, 22) @@ -730,7 +739,7 @@ def test_parse_datetime_followed_by_space(): assert doc.as_string() == "dt = 2013-01-24 13:48:01.123456 " -def test_times_behave_like_times(): +def test_times_behave_like_times() -> None: i = item(time(12, 34, 56)) assert i == time(12, 34, 56) @@ -741,7 +750,7 @@ def test_times_behave_like_times(): assert i.as_string() == "13:34:56" -def test_strings_behave_like_strs(): +def test_strings_behave_like_strs() -> None: i = item("foo") assert i == "foo" @@ -761,15 +770,15 @@ def test_strings_behave_like_strs(): assert doc.as_string() == 'str = "foo bar" # Comment' -def test_string_add_preserve_escapes(): - i = api.value('"foo\\"bar"') +def test_string_add_preserve_escapes() -> None: + i: Any = api.value('"foo\\"bar"') i += " baz" assert i == 'foo"bar baz' assert i.as_string() == '"foo\\"bar baz"' -def test_tables_behave_like_dicts(): +def test_tables_behave_like_dicts() -> None: t = item({"foo": "bar"}) assert ( @@ -807,8 +816,8 @@ def test_tables_behave_like_dicts(): ) -def test_items_are_pickable(): - n = item(12) +def test_items_are_pickable() -> None: + n: Item = item(12) s = pickle.dumps(n) assert pickle.loads(s).as_string() == "12" @@ -865,7 +874,7 @@ def test_items_are_pickable(): assert pickle.loads(s).as_string() == 'foo = "bar"\n' -def test_trim_comments_when_building_inline_table(): +def test_trim_comments_when_building_inline_table() -> None: table = api.inline_table() row = parse('foo = "bar" # Comment') table.update(row) @@ -877,7 +886,7 @@ def test_trim_comments_when_building_inline_table(): assert table.as_string() == '{foo = "bar", baz = "foobaz"}' -def test_deleting_inline_table_element_does_not_leave_trailing_separator(): +def test_deleting_inline_table_element_does_not_leave_trailing_separator() -> None: table = api.inline_table() table["foo"] = "bar" table["baz"] = "boom" @@ -898,7 +907,7 @@ def test_deleting_inline_table_element_does_not_leave_trailing_separator(): assert table.as_string() == '{baz = "boom"}' -def test_deleting_inline_table_element_does_not_leave_trailing_separator2(): +def test_deleting_inline_table_element_does_not_leave_trailing_separator2() -> None: doc = parse('a = {foo = "bar", baz = "boom"}') table = doc["a"] assert table.as_string() == '{foo = "bar", baz = "boom"}' @@ -914,7 +923,7 @@ def test_deleting_inline_table_element_does_not_leave_trailing_separator2(): assert table.as_string() == '{ baz = "boom"}' -def test_booleans_comparison(): +def test_booleans_comparison() -> None: boolean = Bool(True, Trivia()) assert boolean @@ -935,7 +944,7 @@ def test_booleans_comparison(): assert {"value": False} == content["foo"] -def test_table_copy(): +def test_table_copy() -> None: table = item({"foo": "bar"}) table_copy = table.copy() assert isinstance(table_copy, Table) @@ -944,7 +953,7 @@ def test_table_copy(): assert table_copy.as_string() == 'foo = "bar"\n' -def test_copy_copy(): +def test_copy_copy() -> None: result = parse( """ [tool.poetry] @@ -964,15 +973,15 @@ def test_copy_copy(): "key_str,escaped", [("\\", '"\\\\"'), ('"', '"\\""'), ("\t", '"\\t"'), ("\x10", '"\\u0010"')], ) -def test_escape_key(key_str, escaped): +def test_escape_key(key_str: str, escaped: str) -> None: assert api.key(key_str).as_string() == escaped -def test_custom_encoders(): +def test_custom_encoders() -> None: import decimal @api.register_encoder - def encode_decimal(obj): + def encode_decimal(obj: Any) -> Item: if isinstance(obj, decimal.Decimal): return api.float_(str(obj)) raise TypeError @@ -986,7 +995,7 @@ def encode_decimal(obj): api.unregister_encoder(encode_decimal) -def test_custom_encoders_with_parent_and_sort_keys(): +def test_custom_encoders_with_parent_and_sort_keys() -> None: """Test that custom encoders can receive _parent and _sort_keys parameters.""" import decimal @@ -994,7 +1003,9 @@ def test_custom_encoders_with_parent_and_sort_keys(): sort_keys_captured = None @api.register_encoder - def encode_decimal_with_context(obj, _parent=None, _sort_keys=False): + def encode_decimal_with_context( + obj: Any, _parent: Item | None = None, _sort_keys: bool = False + ) -> Item: nonlocal parent_captured, sort_keys_captured if isinstance(obj, decimal.Decimal): parent_captured = _parent @@ -1020,12 +1031,12 @@ def encode_decimal_with_context(obj, _parent=None, _sort_keys=False): api.unregister_encoder(encode_decimal_with_context) -def test_custom_encoders_backward_compatibility(): +def test_custom_encoders_backward_compatibility() -> None: """Test that old-style custom encoders still work without modification.""" import decimal @api.register_encoder - def encode_decimal_old_style(obj): + def encode_decimal_old_style(obj: Any) -> Item: # Old style encoder - only accepts obj parameter if isinstance(obj, decimal.Decimal): return api.float_(str(obj)) @@ -1043,14 +1054,14 @@ def encode_decimal_old_style(obj): api.unregister_encoder(encode_decimal_old_style) -def test_custom_encoders_with_kwargs(): +def test_custom_encoders_with_kwargs() -> None: """Test that custom encoders can use **kwargs to accept additional parameters.""" import decimal kwargs_captured = None @api.register_encoder - def encode_decimal_with_kwargs(obj, **kwargs): + def encode_decimal_with_kwargs(obj: Any, **kwargs: Any) -> Item: nonlocal kwargs_captured if isinstance(obj, decimal.Decimal): kwargs_captured = kwargs @@ -1066,15 +1077,17 @@ def encode_decimal_with_kwargs(obj, **kwargs): api.unregister_encoder(encode_decimal_with_kwargs) -def test_custom_encoders_for_complex_objects(): +def test_custom_encoders_for_complex_objects() -> None: """Test custom encoders that need to encode nested structures.""" class CustomDict: - def __init__(self, data): + def __init__(self, data: dict[str, Any]) -> None: self.data = data @api.register_encoder - def encode_custom_dict(obj, _parent=None, _sort_keys=False): + def encode_custom_dict( + obj: Any, _parent: Item | None = None, _sort_keys: bool = False + ) -> Item: if isinstance(obj, CustomDict): # Create a table and use item() to convert nested values table = api.table() @@ -1100,7 +1113,7 @@ def encode_custom_dict(obj, _parent=None, _sort_keys=False): api.unregister_encoder(encode_custom_dict) -def test_no_extra_minus_sign(): +def test_no_extra_minus_sign() -> None: doc = parse("a = -1") assert doc.as_string() == "a = -1" doc["a"] *= -1 @@ -1116,7 +1129,7 @@ def test_no_extra_minus_sign(): assert doc.as_string() == "a = -1.5" -def test_serialize_table_with_dotted_key(): +def test_serialize_table_with_dotted_key() -> None: child = api.table() child.add(api.key(("b", "c")), 1) parent = api.table() @@ -1124,10 +1137,10 @@ def test_serialize_table_with_dotted_key(): assert parent.as_string() == "[a]\nb.c = 1\n" -def test_not_showing_parent_header_for_super_table(): +def test_not_showing_parent_header_for_super_table() -> None: doc = api.document() - def add_table(parent, name): + def add_table(parent: Any, name: str) -> Any: parent.add(name, api.table()) return parent[name] @@ -1137,7 +1150,7 @@ def add_table(parent, name): assert doc.as_string() == "[root.first]\n\n[root.second]\n" -def test_removal_of_arrayitem_with_extra_whitespace(): +def test_removal_of_arrayitem_with_extra_whitespace() -> None: expected = 'x = [\n "bar",\n]' doc = parse('x = [\n "foo" ,#spam\n "bar",\n]') x = doc["x"] @@ -1148,7 +1161,7 @@ def test_removal_of_arrayitem_with_extra_whitespace(): assert docstr == expected -def test_badly_formatted_array_and_item_removal(): +def test_badly_formatted_array_and_item_removal() -> None: expected = """ x = [ '0'#a @@ -1185,7 +1198,7 @@ def test_badly_formatted_array_and_item_removal(): parse(doc.as_string()) -def test_array_item_removal_newline_restore_next(): +def test_array_item_removal_newline_restore_next() -> None: expected = "x = [\n '0',\n '2'\n]" docstr = "x = [\n '0',\n '1','2'\n]" diff --git a/tests/test_parser.py b/tests/test_parser.py index 9ec5aa7..eeb376e 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -7,7 +7,9 @@ from tomlkit.parser import Parser -def test_parser_should_raise_an_internal_error_if_parsing_wrong_type_of_string(): +def test_parser_should_raise_an_internal_error_if_parsing_wrong_type_of_string() -> ( + None +): parser = Parser('"foo"') with pytest.raises(InternalParserError) as e: @@ -17,7 +19,7 @@ def test_parser_should_raise_an_internal_error_if_parsing_wrong_type_of_string() assert e.value.col == 0 -def test_parser_should_raise_an_error_for_empty_tables(): +def test_parser_should_raise_an_error_for_empty_tables() -> None: content = """ [one] [] @@ -32,7 +34,7 @@ def test_parser_should_raise_an_error_for_empty_tables(): assert e.value.col == 1 -def test_parser_should_raise_an_error_if_equal_not_found(): +def test_parser_should_raise_an_error_if_equal_not_found() -> None: content = """[foo] a {c = 1, d = 2} """ @@ -41,7 +43,7 @@ def test_parser_should_raise_an_error_if_equal_not_found(): parser.parse() -def test_parse_multiline_string_ignore_the_first_newline(): +def test_parse_multiline_string_ignore_the_first_newline() -> None: content = 'a = """\nfoo\n"""' parser = Parser(content) assert parser.parse() == {"a": "foo\n"} diff --git a/tests/test_toml_document.py b/tests/test_toml_document.py index 4dea552..80c806c 100644 --- a/tests/test_toml_document.py +++ b/tests/test_toml_document.py @@ -2,8 +2,10 @@ import json import pickle +from collections.abc import Callable from datetime import datetime from textwrap import dedent +from typing import Any import pytest @@ -18,7 +20,7 @@ from tomlkit.toml_document import TOMLDocument -def test_document_is_a_dict(example): +def test_document_is_a_dict(example: Callable[[str], str]) -> None: content = example("example") doc = parse(content) @@ -130,7 +132,7 @@ def test_document_is_a_dict(example): ) -def test_toml_document_without_super_tables(): +def test_toml_document_without_super_tables() -> None: content = """[tool.poetry] name = "foo" """ @@ -150,13 +152,13 @@ def test_toml_document_without_super_tables(): """ ) - d = {} + d: dict[str, Any] = {} d.update(doc) assert "tool" in d -def test_toml_document_unwrap(): +def test_toml_document_unwrap() -> None: content = """[tool.poetry] name = "foo" """ @@ -170,7 +172,7 @@ def test_toml_document_unwrap(): assert_is_ppo(unwrapped["tool"]["poetry"]["name"], str) -def test_toml_document_with_dotted_keys(example): +def test_toml_document_with_dotted_keys(example: Callable[[str], str]) -> None: content = example("0.5.0") doc = parse(content) @@ -189,7 +191,9 @@ def test_toml_document_with_dotted_keys(example): assert doc["a"]["b"]["d"] == 2 -def test_toml_document_super_table_with_different_sub_sections(example): +def test_toml_document_super_table_with_different_sub_sections( + example: Callable[[str], str], +) -> None: content = example("pyproject") doc = parse(content) @@ -199,7 +203,7 @@ def test_toml_document_super_table_with_different_sub_sections(example): assert "black" in tool -def test_adding_an_element_to_existing_table_with_ws_remove_ws(): +def test_adding_an_element_to_existing_table_with_ws_remove_ws() -> None: content = """[foo] [foo.bar] @@ -219,7 +223,7 @@ def test_adding_an_element_to_existing_table_with_ws_remove_ws(): assert expected == doc.as_string() -def test_document_with_aot_after_sub_tables(): +def test_document_with_aot_after_sub_tables() -> None: content = """[foo.bar] name = "Bar" @@ -234,7 +238,7 @@ def test_document_with_aot_after_sub_tables(): assert doc["foo"]["bar"]["tests"][0]["name"] == "Test 1" -def test_document_with_new_sub_table_after_other_table(): +def test_document_with_new_sub_table_after_other_table() -> None: content = """[foo] name = "Bar" @@ -253,7 +257,7 @@ def test_document_with_new_sub_table_after_other_table(): assert doc.as_string() == content -def test_document_with_new_sub_table_after_other_table_delete(): +def test_document_with_new_sub_table_after_other_table_delete() -> None: content = """[foo] name = "Bar" @@ -277,7 +281,7 @@ def test_document_with_new_sub_table_after_other_table_delete(): ) -def test_document_with_new_sub_table_after_other_table_replace(): +def test_document_with_new_sub_table_after_other_table_replace() -> None: content = """[foo] name = "Bar" @@ -304,7 +308,7 @@ def test_document_with_new_sub_table_after_other_table_replace(): ) -def test_inserting_after_element_with_no_new_line_adds_a_new_line(): +def test_inserting_after_element_with_no_new_line_adds_a_new_line() -> None: doc = parse("foo = 10") doc["bar"] = 11 @@ -324,7 +328,7 @@ def test_inserting_after_element_with_no_new_line_adds_a_new_line(): assert expected == doc.as_string() -def test_inserting_after_deletion(): +def test_inserting_after_deletion() -> None: doc = parse("foo = 10\n") del doc["foo"] @@ -336,7 +340,9 @@ def test_inserting_after_deletion(): assert expected == doc.as_string() -def test_toml_document_with_dotted_keys_inside_table(example): +def test_toml_document_with_dotted_keys_inside_table( + example: Callable[[str], str], +) -> None: content = example("0.5.0") doc = parse(content) @@ -349,7 +355,9 @@ def test_toml_document_with_dotted_keys_inside_table(example): assert t["a"]["c"] == 3 -def test_toml_document_with_super_aot_after_super_table(example): +def test_toml_document_with_super_aot_after_super_table( + example: Callable[[str], str], +) -> None: content = example("pyproject") doc = parse(content) @@ -364,7 +372,7 @@ def test_toml_document_with_super_aot_after_super_table(example): assert second["name"] == "second" -def test_toml_document_has_always_a_new_line_after_table_header(): +def test_toml_document_has_always_a_new_line_after_table_header() -> None: content = """[section.sub]""" doc = parse(content) @@ -383,14 +391,14 @@ def test_toml_document_has_always_a_new_line_after_table_header(): assert doc.as_string() == """[section.sub]""" -def test_toml_document_is_pickable(example): +def test_toml_document_is_pickable(example: Callable[[str], str]) -> None: content = example("example") doc = parse(content) assert pickle.loads(pickle.dumps(doc)).as_string() == content -def test_toml_document_set_super_table_element(): +def test_toml_document_set_super_table_element() -> None: content = """[site.user] name = "John" """ @@ -406,7 +414,7 @@ def test_toml_document_set_super_table_element(): ) -def test_toml_document_can_be_copied(): +def test_toml_document_can_be_copied() -> None: content = "[foo]\nbar=1" doc = parse(content) @@ -436,7 +444,7 @@ def test_toml_document_can_be_copied(): assert json.loads(json.dumps(doc)) == {"foo": {"bar": 1}} -def test_getting_inline_table_is_still_an_inline_table(): +def test_getting_inline_table_is_still_an_inline_table() -> None: content = """\ [tool.poetry] name = "foo" @@ -476,7 +484,7 @@ def test_getting_inline_table_is_still_an_inline_table(): ) -def test_declare_sub_table_with_intermediate_table(): +def test_declare_sub_table_with_intermediate_table() -> None: content = """ [students] tommy = 87 @@ -495,7 +503,7 @@ def test_declare_sub_table_with_intermediate_table(): assert {"tommy": 87, "mary": 66, "bob": {"score": 91}} == doc.get("students") -def test_values_can_still_be_set_for_out_of_order_tables(): +def test_values_can_still_be_set_for_out_of_order_tables() -> None: content = """ [a.a] key = "value" @@ -555,7 +563,7 @@ def test_values_can_still_be_set_for_out_of_order_tables(): del doc["a"]["a"]["key"] -def test_out_of_order_table_can_add_multiple_tables(): +def test_out_of_order_table_can_add_multiple_tables() -> None: content = """\ [a.a.b] x = 1 @@ -571,7 +579,7 @@ def test_out_of_order_table_can_add_multiple_tables(): assert doc["a"]["a"] == {"b": {"x": 1}, "c": {"y": 1}, "d": {"z": 1}} -def test_out_of_order_tables_are_still_dicts(): +def test_out_of_order_tables_are_still_dicts() -> None: content = """ [a.a] key = "value" @@ -599,13 +607,13 @@ def test_out_of_order_tables_are_still_dicts(): assert table.pop("key") == "value" assert "key" not in table - assert table.pop("missing", default="baz") == "baz" + assert table.pop("missing", "baz") == "baz" with pytest.raises(KeyError): table.pop("missing") -def test_string_output_order_is_preserved_for_out_of_order_tables(): +def test_string_output_order_is_preserved_for_out_of_order_tables() -> None: content = """ [tool.poetry] name = "foo" @@ -652,7 +660,7 @@ def test_string_output_order_is_preserved_for_out_of_order_tables(): assert expected == doc.as_string() -def test_remove_from_out_of_order_table(): +def test_remove_from_out_of_order_table() -> None: content = """[a] x = 1 @@ -677,7 +685,7 @@ def test_remove_from_out_of_order_table(): assert json.dumps(document) == '{"a": {"x": 1}, "c": {"z": 3}}' -def test_update_nested_out_of_order_table(): +def test_update_nested_out_of_order_table() -> None: doc = parse("""\ [root1.root2.a.b.c] value = 2 @@ -703,7 +711,7 @@ def test_update_nested_out_of_order_table(): ) -def test_updating_nested_value_keeps_correct_indent(): +def test_updating_nested_value_keeps_correct_indent() -> None: content = """ [Key1] [key1.Key2] @@ -724,7 +732,7 @@ def test_updating_nested_value_keeps_correct_indent(): assert doc.as_string() == expected -def test_repr(): +def test_repr() -> None: content = """ namespace.key1 = "value1" namespace.key2 = "value2" @@ -750,7 +758,7 @@ def test_repr(): assert repr(doc["namespace"]) == "{'key1': 'value1', 'key2': 'value2'}" -def test_deepcopy(): +def test_deepcopy() -> None: content = """ [tool] name = "foo" @@ -763,7 +771,7 @@ def test_deepcopy(): assert copied.as_string() == content -def test_move_table(): +def test_move_table() -> None: content = """a = 1 [x] a = 1 @@ -785,7 +793,7 @@ def test_move_table(): ) -def test_replace_with_table(): +def test_replace_with_table() -> None: content = """a = 1 b = 2 c = 3 @@ -803,7 +811,7 @@ def test_replace_with_table(): ) -def test_replace_table_with_value(): +def test_replace_table_with_value() -> None: content = """[foo] a = 1 @@ -823,7 +831,7 @@ def test_replace_table_with_value(): ) -def test_replace_preserve_sep(): +def test_replace_preserve_sep() -> None: content = """a = 1 [foo] @@ -842,7 +850,7 @@ def test_replace_preserve_sep(): ) -def test_replace_with_table_of_nested(): +def test_replace_with_table_of_nested() -> None: example = """\ [a] x = 1 @@ -862,7 +870,7 @@ def test_replace_with_table_of_nested(): assert doc.as_string().strip() == dedent(expected).strip() -def test_replace_with_aot_of_nested(): +def test_replace_with_aot_of_nested() -> None: example = """\ [a] x = 1 @@ -898,10 +906,10 @@ def test_replace_with_aot_of_nested(): assert doc.as_string().strip() == dedent(expected).strip() -def test_replace_with_comment(): +def test_replace_with_comment() -> None: content = 'a = "1"' doc = parse(content) - a = tomlkit.item(int(doc["a"])) + a: Any = tomlkit.item(int(doc["a"])) a.comment("`a` should be an int") doc["a"] = a expected = "a = 1 # `a` should be an int" @@ -928,7 +936,7 @@ def test_replace_with_comment(): assert doc.as_string() == expected -def test_no_spurious_whitespaces(): +def test_no_spurious_whitespaces() -> None: content = """\ [x] a = 1 @@ -972,7 +980,7 @@ def test_no_spurious_whitespaces(): assert doc.as_string() == dedent(expected) -def test_pop_add_whitespace_and_insert_table_work_togheter(): +def test_pop_add_whitespace_and_insert_table_work_togheter() -> None: content = """\ a = 1 b = 2 @@ -998,7 +1006,7 @@ def test_pop_add_whitespace_and_insert_table_work_togheter(): assert text == dedent(expected) -def test_add_newline_before_super_table(): +def test_add_newline_before_super_table() -> None: doc = document() doc["a"] = 1 doc["b"] = {"c": {}} @@ -1013,7 +1021,7 @@ def test_add_newline_before_super_table(): assert doc.as_string() == dedent(expected) -def test_remove_item_from_super_table(): +def test_remove_item_from_super_table() -> None: content = """\ [hello.one] a = 1 @@ -1031,7 +1039,7 @@ def test_remove_item_from_super_table(): assert doc.as_string() == dedent(expected) -def test_nested_table_update_display_name(): +def test_nested_table_update_display_name() -> None: content = """\ [parent] @@ -1060,7 +1068,7 @@ def test_nested_table_update_display_name(): assert doc.as_string() == dedent(expected) -def test_build_table_with_dotted_key(): +def test_build_table_with_dotted_key() -> None: doc = tomlkit.document() data = { "a.b.c": 1, @@ -1089,7 +1097,7 @@ def test_build_table_with_dotted_key(): } -def test_parse_subtables_no_extra_indent(): +def test_parse_subtables_no_extra_indent() -> None: expected = """\ [a] [a.b.c] @@ -1102,7 +1110,7 @@ def test_parse_subtables_no_extra_indent(): assert doc.as_string() == expected -def test_item_preserves_the_order(): +def test_item_preserves_the_order() -> None: t = tomlkit.inline_table() t.update({"a": 1, "b": 2}) doc = {"name": "foo", "table": t, "age": 42} @@ -1114,7 +1122,7 @@ def test_item_preserves_the_order(): assert tomlkit.dumps(doc) == expected -def test_delete_out_of_order_table_key(): +def test_delete_out_of_order_table_key() -> None: content = """\ [foo] name = "foo" @@ -1152,7 +1160,7 @@ def test_delete_out_of_order_table_key(): ) -def test_overwrite_out_of_order_table_key(): +def test_overwrite_out_of_order_table_key() -> None: content = """\ [foo] name = "foo" @@ -1192,12 +1200,12 @@ def test_overwrite_out_of_order_table_key(): ) -def test_set_default_int(): +def test_set_default_int() -> None: with pytest.raises(TypeError): - TOMLDocument().setdefault(4, 5) + TOMLDocument().setdefault(4, 5) # type: ignore[arg-type] -def test_overwriting_out_of_order_table(): +def test_overwriting_out_of_order_table() -> None: content = """\ [foo.bar] open = false @@ -1235,7 +1243,7 @@ def test_overwriting_out_of_order_table(): ) -def test_delete_key_from_out_of_order_table(): +def test_delete_key_from_out_of_order_table() -> None: content = """\ [foo.bar.baz] a = 1 @@ -1259,7 +1267,7 @@ def test_delete_key_from_out_of_order_table(): ) -def test_parse_aot_without_ending_newline(): +def test_parse_aot_without_ending_newline() -> None: content = '''\ [[products]] name = "Hammer" @@ -1295,7 +1303,7 @@ def test_parse_aot_without_ending_newline(): } -def test_appending_to_super_table(): +def test_appending_to_super_table() -> None: content = """\ [a.b] value = 5 diff --git a/tests/test_toml_file.py b/tests/test_toml_file.py index c1cef3a..586cf8a 100644 --- a/tests/test_toml_file.py +++ b/tests/test_toml_file.py @@ -1,10 +1,13 @@ import os +from collections.abc import Callable +from pathlib import Path + from tomlkit.toml_document import TOMLDocument from tomlkit.toml_file import TOMLFile -def test_toml_file(example): +def test_toml_file(example: Callable[[str], str]) -> None: original_content = example("example") toml_file = os.path.join(os.path.dirname(__file__), "examples", "example.toml") @@ -24,94 +27,94 @@ def test_toml_file(example): assert f.write(original_content) -def test_keep_old_eol(tmp_path): +def test_keep_old_eol(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" - with open(toml_path, "wb+") as f: - f.write(b"a = 1\r\nb = 2\r\n") + with open(toml_path, "wb+") as fh: + fh.write(b"a = 1\r\nb = 2\r\n") - f = TOMLFile(toml_path) - content = f.read() + toml_f = TOMLFile(toml_path) + content = toml_f.read() content["b"] = 3 - f.write(content) + toml_f.write(content) - with open(toml_path, "rb") as f: - assert f.read() == b"a = 1\r\nb = 3\r\n" + with open(toml_path, "rb") as fh: + assert fh.read() == b"a = 1\r\nb = 3\r\n" -def test_keep_old_eol_2(tmp_path): +def test_keep_old_eol_2(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" - with open(toml_path, "wb+") as f: - f.write(b"a = 1\nb = 2\n") + with open(toml_path, "wb+") as fh: + fh.write(b"a = 1\nb = 2\n") - f = TOMLFile(toml_path) - content = f.read() + toml_f = TOMLFile(toml_path) + content = toml_f.read() content["b"] = 3 - f.write(content) + toml_f.write(content) - with open(toml_path, "rb") as f: - assert f.read() == b"a = 1\nb = 3\n" + with open(toml_path, "rb") as fh: + assert fh.read() == b"a = 1\nb = 3\n" -def test_mixed_eol(tmp_path): +def test_mixed_eol(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" - with open(toml_path, "wb+") as f: - f.write(b"a = 1\r\nrb = 2\n") + with open(toml_path, "wb+") as fh: + fh.write(b"a = 1\r\nrb = 2\n") - f = TOMLFile(toml_path) - f.write(f.read()) + toml_f = TOMLFile(toml_path) + toml_f.write(toml_f.read()) - with open(toml_path, "rb") as f: - assert f.read() == b"a = 1\r\nrb = 2\n" + with open(toml_path, "rb") as fh: + assert fh.read() == b"a = 1\r\nrb = 2\n" -def test_consistent_eol(tmp_path): +def test_consistent_eol(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" - with open(toml_path, "wb+") as f: - f.write(b"a = 1\r\nb = 2\r\n") + with open(toml_path, "wb+") as fh: + fh.write(b"a = 1\r\nb = 2\r\n") - f = TOMLFile(toml_path) - content = f.read() + toml_f = TOMLFile(toml_path) + content = toml_f.read() content["c"] = 3 - f.write(content) + toml_f.write(content) - with open(toml_path, "rb") as f: - assert f.read() == b"a = 1\r\nb = 2\r\nc = 3\r\n" + with open(toml_path, "rb") as fh: + assert fh.read() == b"a = 1\r\nb = 2\r\nc = 3\r\n" -def test_consistent_eol_2(tmp_path): +def test_consistent_eol_2(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" - with open(toml_path, "wb+") as f: - f.write(b"a = 1\nb = 2\n") + with open(toml_path, "wb+") as fh: + fh.write(b"a = 1\nb = 2\n") - f = TOMLFile(toml_path) - content = f.read() + toml_f = TOMLFile(toml_path) + content = toml_f.read() content["c"] = 3 content["c"].trivia.trail = "\r\n" - f.write(content) + toml_f.write(content) - with open(toml_path, "rb") as f: - assert f.read() == b"a = 1\nb = 2\nc = 3\n" + with open(toml_path, "rb") as fh: + assert fh.read() == b"a = 1\nb = 2\nc = 3\n" -def test_default_eol_is_os_linesep(tmp_path): +def test_default_eol_is_os_linesep(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" - f = TOMLFile(toml_path) + toml_f = TOMLFile(toml_path) content = TOMLDocument() content.append("a", 1) content["a"].trivia.trail = "\n" content.append("b", 2) content["b"].trivia.trail = "\r\n" - f.write(content) + toml_f.write(content) linesep = os.linesep.encode() - with open(toml_path, "rb") as f: - assert f.read() == b"a = 1" + linesep + b"b = 2" + linesep + with open(toml_path, "rb") as fh: + assert fh.read() == b"a = 1" + linesep + b"b = 2" + linesep -def test_readwrite_eol_windows(tmp_path): +def test_readwrite_eol_windows(tmp_path: Path) -> None: toml_path = tmp_path / "pyproject.toml" doc = TOMLDocument() doc.add("a", 1) - f = TOMLFile(toml_path) - f.write(doc) - readback = f.read() + toml_f = TOMLFile(toml_path) + toml_f.write(doc) + readback = toml_f.read() assert doc.as_string() == readback.as_string() diff --git a/tests/test_toml_tests.py b/tests/test_toml_tests.py index 957f7eb..667353e 100644 --- a/tests/test_toml_tests.py +++ b/tests/test_toml_tests.py @@ -1,6 +1,9 @@ import json import os +from typing import Any +from typing import Callable + import pytest from tomlkit import load @@ -14,13 +17,13 @@ FILES_LIST = os.path.join(TESTS_ROOT, "files-toml-1.1.0") -def to_bool(s): +def to_bool(s: str) -> bool: assert s in ["true", "false"] return s == "true" -stypes = { +stypes: dict[str, Callable[[str], Any]] = { "string": str, "bool": to_bool, "integer": int, @@ -32,7 +35,7 @@ def to_bool(s): } -def untag(value): +def untag(value: Any) -> Any: if isinstance(value, list): return [untag(i) for i in value] elif "type" in value and "value" in value and len(value) == 2: @@ -48,12 +51,19 @@ def untag(value): return {k: untag(v) for k, v in value.items()} -def _load_case_list(): +def _load_case_list() -> list[str]: with open(FILES_LIST, encoding="utf-8") as f: return [line.strip() for line in f if line.strip()] -def _build_cases(): +def _build_cases() -> tuple[ + list[dict[str, str]], + list[str], + list[dict[str, str]], + list[str], + list[str], + list[str], +]: valid_cases = [] valid_ids = [] invalid_decode_cases = [] @@ -109,7 +119,7 @@ def _build_cases(): @pytest.mark.parametrize("toml11_valid_case", VALID_CASES, ids=VALID_IDS) -def test_valid_decode(toml11_valid_case): +def test_valid_decode(toml11_valid_case: dict[str, str]) -> None: json_val = untag(json.loads(toml11_valid_case["json"])) toml_val = parse(toml11_valid_case["toml"]) @@ -120,7 +130,7 @@ def test_valid_decode(toml11_valid_case): @pytest.mark.parametrize( "toml11_invalid_decode_case", INVALID_DECODE_CASES, ids=INVALID_DECODE_IDS ) -def test_invalid_decode(toml11_invalid_decode_case): +def test_invalid_decode(toml11_invalid_decode_case: dict[str, str]) -> None: with pytest.raises(TOMLKitError): parse(toml11_invalid_decode_case["toml"]) @@ -128,7 +138,7 @@ def test_invalid_decode(toml11_invalid_decode_case): @pytest.mark.parametrize( "toml11_invalid_encode_case", INVALID_ENCODE_CASES, ids=INVALID_ENCODE_IDS ) -def test_invalid_encode(toml11_invalid_encode_case): +def test_invalid_encode(toml11_invalid_encode_case: str) -> None: with open(toml11_invalid_encode_case, encoding="utf-8") as f: with pytest.raises((TOMLKitError, UnicodeDecodeError)): load(f) diff --git a/tests/test_utils.py b/tests/test_utils.py index 7dde452..c4f96cf 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,6 +3,7 @@ from datetime import time from datetime import timedelta as td from datetime import timezone as tz +from typing import Union import pytest @@ -34,12 +35,12 @@ ), ], ) -def test_parse_rfc3339_datetime(string, expected): +def test_parse_rfc3339_datetime(string: str, expected: Union[dt, date, time]) -> None: assert parse_rfc3339(string) == expected @pytest.mark.parametrize("string, expected", [("1979-05-27", date(1979, 5, 27))]) -def test_parse_rfc3339_date(string, expected): +def test_parse_rfc3339_date(string: str, expected: Union[dt, date, time]) -> None: assert parse_rfc3339(string) == expected @@ -47,5 +48,5 @@ def test_parse_rfc3339_date(string, expected): "string, expected", [("12:34:56", time(12, 34, 56)), ("12:34:56.123456", time(12, 34, 56, 123456))], ) -def test_parse_rfc3339_time(string, expected): +def test_parse_rfc3339_time(string: str, expected: Union[dt, date, time]) -> None: assert parse_rfc3339(string) == expected diff --git a/tests/test_write.py b/tests/test_write.py index 19e68a3..c5b923a 100644 --- a/tests/test_write.py +++ b/tests/test_write.py @@ -1,32 +1,37 @@ +from typing import Any + from tomlkit import dumps from tomlkit import loads -def test_write_backslash(): +def test_write_backslash() -> None: d = {"foo": "\\e\u25e6\r"} expected = """foo = "\\\\e\u25e6\\r" """ assert expected == dumps(d) - assert loads(dumps(d))["foo"] == "\\e\u25e6\r" + result: Any = loads(dumps(d))["foo"] + assert result == "\\e\u25e6\r" -def test_escape_special_characters_in_key(): +def test_escape_special_characters_in_key() -> None: d = {"foo\nbar": "baz"} expected = '"foo\\nbar" = "baz"\n' assert expected == dumps(d) - assert loads(dumps(d))["foo\nbar"] == "baz" + result: Any = loads(dumps(d))["foo\nbar"] + assert result == "baz" -def test_write_inline_table_in_nested_arrays(): +def test_write_inline_table_in_nested_arrays() -> None: d = {"foo": [[{"a": 1}]]} expected = "foo = [[{a = 1}]]\n" assert expected == dumps(d) - assert loads(dumps(d))["foo"] == [[{"a": 1}]] + result: Any = loads(dumps(d))["foo"] + assert result == [[{"a": 1}]] -def test_serialize_aot_with_nested_tables(): +def test_serialize_aot_with_nested_tables() -> None: doc = {"a": [{"b": {"c": 1}}]} expected = """\ [[a]] diff --git a/tests/util.py b/tests/util.py index fbe6df2..83cf5d9 100644 --- a/tests/util.py +++ b/tests/util.py @@ -42,16 +42,16 @@ ] -def assert_not_tomlkit_type(v): +def assert_not_tomlkit_type(v: object) -> None: for _, tomlkit_type in enumerate(TOMLKIT_TYPES): assert not isinstance(v, tomlkit_type) -def assert_is_ppo(v_unwrapped, unwrapped_type): +def assert_is_ppo(v_unwrapped: object, unwrapped_type: type) -> None: assert_not_tomlkit_type(v_unwrapped) assert isinstance(v_unwrapped, unwrapped_type) -def elementary_test(v, unwrapped_type): +def elementary_test(v: Item, unwrapped_type: type) -> None: v_unwrapped = v.unwrap() assert_is_ppo(v_unwrapped, unwrapped_type) diff --git a/tomlkit/_compat.py b/tomlkit/_compat.py index 8e76b7f..2da7099 100644 --- a/tomlkit/_compat.py +++ b/tomlkit/_compat.py @@ -3,13 +3,11 @@ import contextlib import sys -from typing import Any - PY38 = sys.version_info >= (3, 8) -def decode(string: Any, encodings: list[str] | None = None): +def decode(string: str | bytes, encodings: list[str] | None = None) -> str: if not isinstance(string, bytes): return string diff --git a/tomlkit/_types.py b/tomlkit/_types.py index 501bf4d..3585d56 100644 --- a/tomlkit/_types.py +++ b/tomlkit/_types.py @@ -7,6 +7,14 @@ WT = TypeVar("WT", bound="WrapperType") +__all__ = [ + "_CustomDict", + "_CustomFloat", + "_CustomInt", + "_CustomList", + "wrap_method", +] + if TYPE_CHECKING: # pragma: no cover # Define _CustomList and _CustomDict as a workaround for: # https://github.com/python/mypy/issues/11427 @@ -73,7 +81,7 @@ class _CustomFloat(Real, float): def wrap_method( original_method: Callable[Concatenate[WT, P], Any], ) -> Callable[Concatenate[WT, P], Any]: - def wrapper(self: WT, *args: P.args, **kwargs: P.kwargs) -> Any: + def wrapper(self: WT, /, *args: P.args, **kwargs: P.kwargs) -> Any: result = original_method(self, *args, **kwargs) if result is NotImplemented: return result diff --git a/tomlkit/_utils.py b/tomlkit/_utils.py index 99887b6..3c20574 100644 --- a/tomlkit/_utils.py +++ b/tomlkit/_utils.py @@ -9,6 +9,7 @@ from datetime import time from datetime import timedelta from datetime import timezone +from typing import Any from tomlkit._compat import decode @@ -130,7 +131,7 @@ def escape_string(s: str, escape_sequences: Collection[str] = _basic_escapes) -> res = [] start = 0 - def flush(inc=1): + def flush(inc: int = 1) -> int: if start != i: res.append(s[start:i]) @@ -153,9 +154,9 @@ def flush(inc=1): return "".join(res) -def merge_dicts(d1: dict, d2: dict) -> dict: +def merge_dicts(d1: dict[str, Any], d2: dict[str, Any]) -> None: for k, v in d2.items(): if k in d1 and isinstance(d1[k], dict) and isinstance(v, Mapping): - merge_dicts(d1[k], v) + merge_dicts(d1[k], dict(v)) else: d1[k] = d2[k] diff --git a/tomlkit/api.py b/tomlkit/api.py index 3294a7a..7455a1c 100644 --- a/tomlkit/api.py +++ b/tomlkit/api.py @@ -7,6 +7,7 @@ from collections.abc import Mapping from typing import IO from typing import TYPE_CHECKING +from typing import Any from typing import TypeVar from tomlkit._utils import parse_rfc3339 @@ -32,9 +33,9 @@ from tomlkit.items import Time from tomlkit.items import Trivia from tomlkit.items import Whitespace -from tomlkit.items import item +from tomlkit.items import item as item from tomlkit.parser import Parser -from tomlkit.toml_document import TOMLDocument +from tomlkit.toml_document import TOMLDocument as TOMLDocument if TYPE_CHECKING: @@ -52,22 +53,15 @@ def loads(string: str | bytes) -> TOMLDocument: return parse(string) -def dumps(data: Mapping, sort_keys: bool = False) -> str: +def dumps(data: Mapping[str, Any], sort_keys: bool = False) -> str: """ Dumps a TOMLDocument into a string. """ - if not isinstance(data, (Table, InlineTable, Container)) and isinstance( - data, Mapping - ): - data = item(dict(data), _sort_keys=sort_keys) + if isinstance(data, (Table, InlineTable, Container)): + return data.as_string() - try: - # data should be a `Container` (and therefore implement `as_string`) - # for all type safe invocations of this function - return data.as_string() # type: ignore[attr-defined] - except AttributeError as ex: - msg = f"Expecting Mapping or TOML Table or Container, {type(data)} given" - raise TypeError(msg) from ex + table: Table = item(dict(data), _sort_keys=sort_keys) + return table.as_string() def load(fp: IO[str] | IO[bytes]) -> TOMLDocument: @@ -77,7 +71,7 @@ def load(fp: IO[str] | IO[bytes]) -> TOMLDocument: return parse(fp.read()) -def dump(data: Mapping, fp: IO[str], *, sort_keys: bool = False) -> None: +def dump(data: Mapping[str, Any], fp: IO[str], *, sort_keys: bool = False) -> None: """ Dump a TOMLDocument into a writable file stream. @@ -185,7 +179,10 @@ def array(raw: str = "[]") -> Array: >>> a [1, 2, 3] """ - return value(raw) + v = value(raw) + if not isinstance(v, Array): + raise ValueError(f"Expected an array, got {type(v)}") + return v def table(is_super_table: bool | None = None) -> Table: @@ -252,7 +249,7 @@ def key(k: str | Iterable[str]) -> Key: """ if isinstance(k, str): return SingleKey(k) - return DottedKey([key(_k) for _k in k]) + return DottedKey([SingleKey(_k) for _k in k]) def value(raw: str) -> _Item: diff --git a/tomlkit/container.py b/tomlkit/container.py index 38e3b34..4387471 100644 --- a/tomlkit/container.py +++ b/tomlkit/container.py @@ -4,8 +4,13 @@ import math from collections.abc import Iterator +from typing import TYPE_CHECKING from typing import Any + +if TYPE_CHECKING: + from typing import Self + from tomlkit._compat import decode from tomlkit._types import _CustomDict from tomlkit._utils import merge_dicts @@ -27,7 +32,7 @@ _NOT_SET = object() -class Container(_CustomDict): +class Container(_CustomDict): # type: ignore[type-arg] """ A container for items within a TOMLDocument. @@ -35,10 +40,10 @@ class Container(_CustomDict): """ def __init__(self, parsed: bool = False) -> None: - self._map: dict[SingleKey, int | tuple[int, ...]] = {} + self._map: dict[Key, int | tuple[int, ...]] = {} self._body: list[tuple[Key | None, Item]] = [] self._parsed = parsed - self._table_keys = [] + self._table_keys: list[Key] = [] @property def body(self) -> list[tuple[Key | None, Item]]: @@ -46,42 +51,39 @@ def body(self) -> list[tuple[Key | None, Item]]: def unwrap(self) -> dict[str, Any]: """Returns as pure python object (ppo)""" - unwrapped = {} + unwrapped: dict[str, Any] = {} for k, v in self.items(): if k is None: continue - if isinstance(k, Key): - k = k.key - - if hasattr(v, "unwrap"): - v = v.unwrap() + key_str: str = k.key if isinstance(k, Key) else k + val: Any = v.unwrap() if hasattr(v, "unwrap") else v - if k in unwrapped: - merge_dicts(unwrapped[k], v) + if key_str in unwrapped: + merge_dicts(unwrapped[key_str], val) else: - unwrapped[k] = v + unwrapped[key_str] = val return unwrapped @property def value(self) -> dict[str, Any]: """The wrapped dict value""" - d = {} + d: dict[str, Any] = {} for k, v in self._body: if k is None: continue - k = k.key - v = v.value + key_str = k.key + val: Any = v.value - if isinstance(v, Container): - v = v.value + if isinstance(val, Container): + val = val.value - if k in d: - merge_dicts(d[k], v) + if key_str in d: + merge_dicts(d[key_str], val) else: - d[k] = v + d[key_str] = val return d @@ -95,7 +97,7 @@ def parsing(self, parsing: bool) -> None: for t in v.body: t.value.parsing(parsing) - def add(self, key: Key | Item | str, item: Item | None = None) -> Container: + def add(self, key: Key | Item | str, item: Any = None) -> Container: """ Adds an item to the current Container. @@ -112,8 +114,9 @@ def add(self, key: Key | Item | str, item: Item | None = None) -> Container: "Non comment/whitespace items must have an associated key" ) - key, item = None, key + return self.append(None, key) + assert not isinstance(key, Item) return self.append(key, item) def _handle_dotted_key(self, key: Key, value: Item) -> None: @@ -143,23 +146,26 @@ def _get_last_index_before_table(self) -> int: if isinstance(v, Whitespace) and not v.is_fixed(): continue - if isinstance(v, (Table, AoT)) and not k.is_dotted(): + if isinstance(v, (Table, AoT)) and k is not None and not k.is_dotted(): break last_index = i return last_index + 1 - def _validate_out_of_order_table(self, key: SingleKey | None = None) -> None: + def _validate_out_of_order_table(self, key: Key | None = None) -> None: if key is None: for k in self._map: assert k is not None self._validate_out_of_order_table(k) return - if key not in self._map or not isinstance(self._map[key], tuple): + if key not in self._map: return - OutOfOrderTableProxy.validate(self, self._map[key]) + current_idx = self._map[key] + if not isinstance(current_idx, tuple): + return + OutOfOrderTableProxy.validate(self, current_idx) def append( - self, key: Key | str | None, item: Item, validate: bool = True + self, key: Key | str | None, item: Any, validate: bool = True ) -> Container: """Similar to :meth:`add` but both key and value must be given.""" if not isinstance(key, Key) and key is not None: @@ -173,6 +179,7 @@ def append( return self if isinstance(item, (AoT, Table)) and item.name is None: + assert isinstance(key, Key) item.name = key.key prev = self._previous_item() @@ -183,6 +190,7 @@ def append( if ( self._body and not (self._parsed or item.trivia.indent or prev_ws) + and key is not None and not key.is_dotted() ): item.trivia.indent = "\n" @@ -216,7 +224,7 @@ def append( current.append(item) return self - elif current.is_aot(): + elif isinstance(current, AoT): if not item.is_aot_element(): # Tried to define a table after an AoT with the same name. raise KeyAlreadyPresent(key) @@ -229,7 +237,10 @@ def append( # We need to merge both super tables if ( key.is_dotted() - or current_body_element[0].is_dotted() + or ( + current_body_element[0] is not None + and current_body_element[0].is_dotted() + ) or self._table_keys[-1] != current_body_element[0] ): if key.is_dotted() and not self._parsed: @@ -260,12 +271,16 @@ def append( ] = (current_body_element[0], current) return self - elif current_body_element[0].is_dotted(): + elif ( + current_body_element[0] is not None + and current_body_element[0].is_dotted() + ): raise TOMLKitError("Redefinition of an existing table") else: # Merging a concrete table into an existing implicit/super # table is only valid if it does not redefine existing # subtrees via dotted keys and does not change prior types. + assert isinstance(current, Table) self._validate_table_candidate(current, item) elif not item.is_super_table(): raise KeyAlreadyPresent(key) @@ -335,13 +350,13 @@ def _validate_table_candidate(self, current: Table, candidate: Table) -> None: raise TOMLKitError("Redefinition of an existing table") def _raw_append(self, key: Key | None, item: Item) -> None: - if key in self._map: + if key is not None and key in self._map: current_idx = self._map[key] if not isinstance(current_idx, tuple): current_idx = (current_idx,) current = self._body[current_idx[-1]][1] - if key is not None and not isinstance(current, Table): + if not isinstance(current, Table): raise KeyAlreadyPresent(key) self._map[key] = (*current_idx, len(self._body)) @@ -349,7 +364,7 @@ def _raw_append(self, key: Key | None, item: Item) -> None: self._map[key] = len(self._body) self._body.append((key, item)) - if item.is_table(): + if item.is_table() and key is not None: self._table_keys.append(key) if key is not None: @@ -357,19 +372,19 @@ def _raw_append(self, key: Key | None, item: Item) -> None: def _remove_at(self, idx: int) -> None: key = self._body[idx][0] + assert key is not None index = self._map.get(key) if index is None: raise NonExistentKey(key) self._body[idx] = (None, Null()) if isinstance(index, tuple): - index = list(index) - index.remove(idx) - if len(index) == 1: - index = index.pop() + index_list = list(index) + index_list.remove(idx) + if len(index_list) == 1: + self._map[key] = index_list.pop() else: - index = tuple(index) - self._map[key] = index + self._map[key] = tuple(index_list) else: dict.__delitem__(self, key.key) self._map.pop(key) @@ -486,7 +501,7 @@ def _insert_at(self, idx: int, key: Key | str, item: Any) -> Container: return self - def item(self, key: Key | str) -> Item: + def item(self, key: Key | str) -> Item | OutOfOrderTableProxy: """Get an item for the given key.""" if not isinstance(key, Key): key = SingleKey(key) @@ -507,6 +522,7 @@ def last_item(self) -> Item | None: """Get the last item.""" if self._body: return self._body[-1][1] + return None def as_string(self) -> str: """Render as TOML string.""" @@ -557,7 +573,11 @@ def _render_table(self, key: Key, table: Table, prefix: str | None = None) -> st and not key.is_dotted() ) or ( - any(k.is_dotted() for k, v in table.value.body if isinstance(v, Table)) + any( + k is not None and k.is_dotted() + for k, v in table.value.body + if isinstance(v, Table) + ) and not key.is_dotted() ) ): @@ -589,6 +609,7 @@ def _render_table(self, key: Key, table: Table, prefix: str | None = None) -> st and "\n" not in v.trivia.indent ): cur += "\n" + assert k is not None if v.is_super_table(): if k.is_dotted() and not key.is_dotted(): # Dotted key inside table @@ -604,6 +625,7 @@ def _render_table(self, key: Key, table: Table, prefix: str | None = None) -> st and "\n" not in v.trivia.indent ): cur += "\n" + assert k is not None cur += self._render_aot(k, v, prefix=_key) else: cur += self._render_simple_item( @@ -612,7 +634,7 @@ def _render_table(self, key: Key, table: Table, prefix: str | None = None) -> st return cur - def _render_aot(self, key, aot, prefix=None): + def _render_aot(self, key: Key, aot: AoT, prefix: str | None = None) -> str: _key = key.as_string() if prefix is not None: _key = prefix + "." + _key @@ -641,6 +663,7 @@ def _render_aot_table(self, table: Table, prefix: str | None = None) -> str: for k, v in table.value.body: if isinstance(v, Table): + assert k is not None if v.is_super_table(): if k.is_dotted(): # Dotted key inside table @@ -650,13 +673,16 @@ def _render_aot_table(self, table: Table, prefix: str | None = None) -> str: else: cur += self._render_table(k, v, prefix=_key) elif isinstance(v, AoT): + assert k is not None cur += self._render_aot(k, v, prefix=_key) else: cur += self._render_simple_item(k, v) return cur - def _render_simple_item(self, key, item, prefix=None): + def _render_simple_item( + self, key: Key | None, item: Item, prefix: str | None = None + ) -> str: if key is None: return item.as_string() @@ -681,7 +707,7 @@ def __iter__(self) -> Iterator[str]: return iter(dict.keys(self)) # Dictionary methods - def __getitem__(self, key: Key | str) -> Item | Container: + def __getitem__(self, key: Key | str) -> Any: item = self.item(key) if isinstance(item, Item) and item.is_boolean(): return item.value @@ -689,7 +715,7 @@ def __getitem__(self, key: Key | str) -> Item | Container: return item def __setitem__(self, key: Key | str, value: Any) -> None: - if key is not None and key in self: + if key in self: old_key = next(filter(lambda k: k == key, self._map)) self._replace(old_key, key, value) else: @@ -698,8 +724,9 @@ def __setitem__(self, key: Key | str, value: Any) -> None: def __delitem__(self, key: Key | str) -> None: self.remove(key) - def setdefault(self, key: Key | str, default: Any) -> Any: - super().setdefault(key, default=default) + def setdefault(self, key: Key | str, default: Any = None) -> Any: + if key not in self: + self[key] = default return self[key] def _replace(self, key: Key | str, new_key: Key | str, value: Item) -> None: @@ -713,7 +740,7 @@ def _replace(self, key: Key | str, new_key: Key | str, value: Item) -> None: self._replace_at(idx, new_key, value) def _replace_at( - self, idx: int | tuple[int], new_key: Key | str, value: Item + self, idx: int | tuple[int, ...], new_key: Key | str, value: Item ) -> None: value = _item(value) @@ -724,6 +751,7 @@ def _replace_at( idx = idx[0] k, v = self._body[idx] + assert k is not None if not isinstance(new_key, Key): if ( isinstance(value, (AoT, Table)) != isinstance(v, (AoT, Table)) @@ -736,7 +764,7 @@ def _replace_at( del self._map[k] self._map[new_key] = idx if new_key != k: - dict.__delitem__(self, k) + dict.__delitem__(self, k.key) if isinstance(value, (AoT, Table)) != isinstance(v, (AoT, Table)): # new tables should appear after all non-table values @@ -759,14 +787,16 @@ def _replace_at( self._body[idx] = (new_key, value) if hasattr(value, "invalidate_display_name"): - value.invalidate_display_name() # type: ignore[attr-defined] + value.invalidate_display_name() if isinstance(value, Table): # Insert a cosmetic new line for tables if: # - it does not have it yet OR is not followed by one # - it is not the last item, or # - The table being replaced has a newline - last, _ = self._previous_item_with_index() + result = self._previous_item_with_index() + assert result is not None + last, _ = result idx = last if idx < 0 else idx has_ws = ends_with_whitespace(value) replace_has_ws = ( @@ -778,6 +808,7 @@ def _replace_at( if (idx < last or replace_has_ws) and not (next_ws or has_ws): value.append(None, Whitespace("\n")) + assert isinstance(new_key, Key) dict.__setitem__(self, new_key.key, value.value) def __str__(self) -> str: @@ -786,26 +817,26 @@ def __str__(self) -> str: def __repr__(self) -> str: return repr(self.value) - def __eq__(self, other: dict) -> bool: + def __eq__(self, other: object) -> bool: if not isinstance(other, dict): return NotImplemented - return _equal_with_nan(self.value, other) + return bool(_equal_with_nan(self.value, other)) - def _getstate(self, protocol): + def _getstate(self, protocol: int) -> tuple[bool]: return (self._parsed,) - def __reduce__(self): + def __reduce__(self) -> tuple[type, tuple[bool], tuple[Any, ...]]: return self.__reduce_ex__(2) - def __reduce_ex__(self, protocol): + def __reduce_ex__(self, protocol: int) -> tuple[type, tuple[bool], tuple[Any, ...]]: # type: ignore[override] return ( self.__class__, self._getstate(protocol), (self._map, self._body, self._parsed, self._table_keys), ) - def __setstate__(self, state): + def __setstate__(self, state: tuple[Any, ...]) -> None: self._map = state[0] self._body = state[1] self._parsed = state[2] @@ -815,10 +846,10 @@ def __setstate__(self, state): if key is not None: dict.__setitem__(self, key.key, item.value) - def copy(self) -> Container: + def copy(self) -> Self: return copy.copy(self) - def __copy__(self) -> Container: + def __copy__(self) -> Self: c = self.__class__(self._parsed) for k, v in dict.items(self): dict.__setitem__(c, k, v) @@ -829,7 +860,7 @@ def __copy__(self) -> Container: return c def _previous_item_with_index( - self, idx: int | None = None, ignore=(Null,) + self, idx: int | None = None, ignore: tuple[type, ...] = (Null,) ) -> tuple[int, Item] | None: """Find the immediate previous item before index ``idx``""" if idx is None or idx > len(self._body): @@ -840,7 +871,9 @@ def _previous_item_with_index( return i, v return None - def _previous_item(self, idx: int | None = None, ignore=(Null,)) -> Item | None: + def _previous_item( + self, idx: int | None = None, ignore: tuple[type, ...] = (Null,) + ) -> Item | None: """Find the immediate previous item before index ``idx``. If ``idx`` is not given, the last item is returned. """ @@ -848,7 +881,7 @@ def _previous_item(self, idx: int | None = None, ignore=(Null,)) -> Item | None: return prev[-1] if prev else None -class OutOfOrderTableProxy(_CustomDict): +class OutOfOrderTableProxy(_CustomDict): # type: ignore[type-arg] @staticmethod def validate(container: Container, indices: tuple[int, ...]) -> None: """Validate out of order tables in the given container""" @@ -870,26 +903,26 @@ def __init__(self, container: Container, indices: tuple[int, ...]) -> None: self._tables_map: dict[Key, list[int]] = {} for i in indices: - _, item = self._container._body[i] + _, _item = self._container._body[i] - if isinstance(item, Table): - self._tables.append(item) + if isinstance(_item, Table): + self._tables.append(_item) table_idx = len(self._tables) - 1 - for k, v in item.value.body: + for k, v in _item.value.body: self._internal_container._raw_append(k, v) - indices = self._tables_map.setdefault(k, []) - if table_idx not in indices: - indices.append(table_idx) + key_indices = self._tables_map.setdefault(k, []) # type: ignore[arg-type] + if table_idx not in key_indices: + key_indices.append(table_idx) if k is not None: dict.__setitem__(self, k.key, v) self._internal_container._validate_out_of_order_table() - def unwrap(self) -> str: + def unwrap(self) -> dict[str, Any]: return self._internal_container.unwrap() @property - def value(self): + def value(self) -> dict[str, Any]: return self._internal_container.value def __getitem__(self, key: Key | str) -> Any: @@ -899,25 +932,27 @@ def __getitem__(self, key: Key | str) -> Any: return self._internal_container[key] def __setitem__(self, key: Key | str, value: Any) -> None: - from .items import item + from .items import item as _item_fn def _is_table_or_aot(it: Any) -> bool: - return isinstance(item(it), (Table, AoT)) + return isinstance(_item_fn(it), (Table, AoT)) - if key in self._tables_map: + _key: Key = key if isinstance(key, Key) else SingleKey(key) + + if _key in self._tables_map: # Overwrite the first table and remove others - indices = self._tables_map[key] - while len(indices) > 1: - table = self._tables[indices.pop()] + map_indices = self._tables_map[_key] + while len(map_indices) > 1: + table = self._tables[map_indices.pop()] self._remove_table(table) - old_value = self._tables[indices[0]][key] + old_value = self._tables[map_indices[0]][key] if _is_table_or_aot(old_value) and not _is_table_or_aot(value): # Remove the entry from the map and set value again. - del self._tables[indices[0]][key] - del self._tables_map[key] + del self._tables[map_indices[0]][key] + del self._tables_map[_key] self[key] = value return - self._tables[indices[0]][key] = value + self._tables[map_indices[0]][key] = value elif self._tables: if not _is_table_or_aot(value): # if the value is a plain value for table in self._tables: @@ -939,22 +974,23 @@ def _is_table_or_aot(it: Any) -> bool: def _remove_table(self, table: Table) -> None: """Remove table from the parent container""" self._tables.remove(table) - for idx, item in enumerate(self._container._body): - if item[1] is table: + for idx, body_item in enumerate(self._container._body): + if body_item[1] is table: self._container._remove_at(idx) break def __delitem__(self, key: Key | str) -> None: - if key not in self._tables_map: + _key: Key = key if isinstance(key, Key) else SingleKey(key) + if _key not in self._tables_map: raise NonExistentKey(key) - for i in reversed(self._tables_map[key]): + for i in reversed(self._tables_map[_key]): table = self._tables[i] del table[key] if not table and len(self._tables) > 1: self._remove_table(table) - del self._tables_map[key] + del self._tables_map[_key] del self._internal_container[key] if key is not None: dict.__delitem__(self, key) @@ -965,8 +1001,9 @@ def __iter__(self) -> Iterator[str]: def __len__(self) -> int: return dict.__len__(self) - def setdefault(self, key: Key | str, default: Any) -> Any: - super().setdefault(key, default=default) + def setdefault(self, key: Key | str, default: Any = None) -> Any: + if key not in self: + self[key] = default return self[key] @@ -994,4 +1031,4 @@ def _equal_with_nan(left: Any, right: Any) -> bool: if math.isnan(left) and math.isnan(right): return True - return left == right + return bool(left == right) diff --git a/tomlkit/exceptions.py b/tomlkit/exceptions.py index e4e85a2..473357e 100644 --- a/tomlkit/exceptions.py +++ b/tomlkit/exceptions.py @@ -24,11 +24,11 @@ def __init__(self, line: int, col: int, message: str | None = None) -> None: super().__init__(f"{message} at line {self._line} col {self._col}") @property - def line(self): + def line(self) -> int: return self._line @property - def col(self): + def col(self) -> int: return self._col @@ -182,7 +182,7 @@ class NonExistentKey(KeyError, TOMLKitError): A non-existent key was used. """ - def __init__(self, key): + def __init__(self, key: object) -> None: message = f'Key "{key}" does not exist.' super().__init__(message) @@ -193,7 +193,7 @@ class KeyAlreadyPresent(TOMLKitError): An already present key was used. """ - def __init__(self, key): + def __init__(self, key: object) -> None: key = getattr(key, "key", key) message = f'Key "{key}" already exists.' diff --git a/tomlkit/items.py b/tomlkit/items.py index 7b352e6..8670f03 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -14,12 +14,12 @@ from datetime import date from datetime import datetime from datetime import time +from datetime import timedelta from datetime import tzinfo from enum import Enum from typing import TYPE_CHECKING from typing import Any from typing import TypeVar -from typing import cast from typing import overload from tomlkit._compat import PY38 @@ -28,7 +28,6 @@ from tomlkit._types import _CustomFloat from tomlkit._types import _CustomInt from tomlkit._types import _CustomList -from tomlkit._types import wrap_method from tomlkit._utils import CONTROL_CHARS from tomlkit._utils import escape_string from tomlkit.exceptions import ConvertError @@ -39,11 +38,10 @@ from typing import Protocol from tomlkit import container + from tomlkit.container import OutOfOrderTableProxy class Encoder(Protocol): - def __call__( - self, __value: Any, _parent: Item | None = None, _sort_keys: bool = False - ) -> Item: ... + def __call__(self, __value: Any, /) -> Item: ... ItemT = TypeVar("ItemT", bound="Item") @@ -52,7 +50,7 @@ def __call__( @overload -def item(value: bool, _parent: Item | None = ..., _sort_keys: bool = ...) -> Bool: ... +def item(value: bool, _parent: Item | None = ..., _sort_keys: bool = ...) -> Bool: ... # type: ignore[overload-overlap] @overload @@ -68,7 +66,7 @@ def item(value: str, _parent: Item | None = ..., _sort_keys: bool = ...) -> Stri @overload -def item( +def item( # type: ignore[overload-overlap] value: datetime, _parent: Item | None = ..., _sort_keys: bool = ... ) -> DateTime: ... @@ -83,28 +81,36 @@ def item(value: time, _parent: Item | None = ..., _sort_keys: bool = ...) -> Tim @overload def item( - value: Sequence[dict], _parent: Item | None = ..., _sort_keys: bool = ... + value: Sequence[dict[str, Any]], _parent: Item | None = ..., _sort_keys: bool = ... ) -> AoT: ... @overload def item( - value: Sequence, _parent: Item | None = ..., _sort_keys: bool = ... + value: Sequence[Any], _parent: Item | None = ..., _sort_keys: bool = ... ) -> Array: ... @overload -def item(value: dict, _parent: Array = ..., _sort_keys: bool = ...) -> InlineTable: ... +def item( + value: dict[str, Any], _parent: Array = ..., _sort_keys: bool = ... +) -> InlineTable: ... @overload -def item(value: dict, _parent: Item | None = ..., _sort_keys: bool = ...) -> Table: ... +def item( + value: dict[str, Any], _parent: Item | None = ..., _sort_keys: bool = ... +) -> Table: ... @overload def item(value: ItemT, _parent: Item | None = ..., _sort_keys: bool = ...) -> ItemT: ... +@overload +def item(value: object, _parent: Item | None = ..., _sort_keys: bool = ...) -> Item: ... + + def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> Item: """Create a TOML item from a Python object. @@ -143,6 +149,7 @@ def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> I return val elif isinstance(value, (list, tuple)): + a: AoT | Array if ( value and all(isinstance(v, dict) for v in value) @@ -209,7 +216,7 @@ def item(value: Any, _parent: Item | None = None, _sort_keys: bool = False) -> I p.kind == p.VAR_KEYWORD for p in sig.parameters.values() ): # New style encoder that can accept additional parameters - rv = encoder(value, _parent=_parent, _sort_keys=_sort_keys) + rv = encoder(value, _parent=_parent, _sort_keys=_sort_keys) # type: ignore[call-arg] else: # Old style encoder that only accepts value rv = encoder(value) @@ -236,7 +243,7 @@ class StringType(Enum): MLL = "'''" @classmethod - def select(cls, literal=False, multiline=False) -> StringType: + def select(cls, literal: bool = False, multiline: bool = False) -> StringType: return { (False, False): cls.SLB, (False, True): cls.MLB, @@ -297,13 +304,13 @@ class BoolType(Enum): TRUE = "true" FALSE = "false" - def __bool__(self): + def __bool__(self) -> bool: return {BoolType.TRUE: True, BoolType.FALSE: False}[self] - def __iter__(self): + def __iter__(self) -> Iterator[str]: return iter(self.value) - def __len__(self): + def __len__(self) -> int: return len(self.value) @@ -434,7 +441,7 @@ def __eq__(self, other: Any) -> bool: if isinstance(other, Key): return isinstance(other, SingleKey) and self.key == other.key - return self.key == other + return bool(self.key == other) class DottedKey(Key): @@ -522,15 +529,24 @@ def is_inline_table(self) -> bool: def is_aot(self) -> bool: return isinstance(self, AoT) - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[object, ...]: return (self._trivia,) - def __reduce__(self): + def __reduce__(self) -> tuple[type, tuple[object, ...]]: return self.__reduce_ex__(2) - def __reduce_ex__(self, protocol): + def __reduce_ex__(self, protocol: int) -> tuple[type, tuple[object, ...]]: # type: ignore[override] return self.__class__, self._getstate(protocol) + def __getitem__(self, key: Key | str | int) -> Any: + raise TypeError(f"{type(self).__name__} does not support item access") + + def __setitem__(self, key: Key | str | int, value: Any) -> None: + raise TypeError(f"{type(self).__name__} does not support item assignment") + + def __delitem__(self, key: Key | str | int) -> None: + raise TypeError(f"{type(self).__name__} does not support item deletion") + class Whitespace(Item): """ @@ -568,7 +584,7 @@ def as_string(self) -> str: def __repr__(self) -> str: return f"<{self.__class__.__name__} {self._s!r}>" - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[str, bool]: return self._s, self._fixed @@ -627,60 +643,190 @@ def value(self) -> int: def as_string(self) -> str: return self._raw - def _new(self, result): + def _new(self, result: int) -> Integer: raw = str(result) if self._sign and result >= 0: raw = f"+{raw}" return Integer(result, self._trivia, raw) - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[int, Trivia, str]: return int(self), self._trivia, self._raw - # int methods - __abs__ = wrap_method(int.__abs__) - __add__ = wrap_method(int.__add__) - __and__ = wrap_method(int.__and__) - __ceil__ = wrap_method(int.__ceil__) + # int methods — explicit typed wrappers + def __abs__(self) -> Integer: + return self._new(int.__abs__(self)) + + def __add__(self, other: object) -> Integer: + result = int.__add__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __and__(self, other: object) -> Integer: + result = int.__and__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __ceil__(self) -> Integer: + return self._new(int.__ceil__(self)) + __eq__ = int.__eq__ - __floor__ = wrap_method(int.__floor__) - __floordiv__ = wrap_method(int.__floordiv__) - __invert__ = wrap_method(int.__invert__) + + def __floor__(self) -> Integer: + return self._new(int.__floor__(self)) + + def __floordiv__(self, other: object) -> Integer: + result = int.__floordiv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __invert__(self) -> Integer: + return self._new(int.__invert__(self)) + __le__ = int.__le__ - __lshift__ = wrap_method(int.__lshift__) + + def __lshift__(self, other: object) -> Integer: + result = int.__lshift__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + __lt__ = int.__lt__ - __mod__ = wrap_method(int.__mod__) - __mul__ = wrap_method(int.__mul__) - __neg__ = wrap_method(int.__neg__) - __or__ = wrap_method(int.__or__) - __pos__ = wrap_method(int.__pos__) - __pow__ = wrap_method(int.__pow__) - __radd__ = wrap_method(int.__radd__) - __rand__ = wrap_method(int.__rand__) - __rfloordiv__ = wrap_method(int.__rfloordiv__) - __rlshift__ = wrap_method(int.__rlshift__) - __rmod__ = wrap_method(int.__rmod__) - __rmul__ = wrap_method(int.__rmul__) - __ror__ = wrap_method(int.__ror__) - __round__ = wrap_method(int.__round__) - __rpow__ = wrap_method(int.__rpow__) - __rrshift__ = wrap_method(int.__rrshift__) - __rshift__ = wrap_method(int.__rshift__) - __rxor__ = wrap_method(int.__rxor__) - __trunc__ = wrap_method(int.__trunc__) - __xor__ = wrap_method(int.__xor__) - - def __rtruediv__(self, other): - result = int.__rtruediv__(self, other) + + def __mod__(self, other: object) -> Integer: + result = int.__mod__(self, other) # type: ignore[operator] if result is NotImplemented: - return result - return Float._new(self, result) + return result # type: ignore[return-value] + return self._new(result) - def __truediv__(self, other): - result = int.__truediv__(self, other) + def __mul__(self, other: object) -> Integer: + result = int.__mul__(self, other) # type: ignore[operator] if result is NotImplemented: - return result - return Float._new(self, result) + return result # type: ignore[return-value] + return self._new(result) + + def __neg__(self) -> Integer: + return self._new(int.__neg__(self)) + + def __or__(self, other: object) -> Integer: + result = int.__or__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __pos__(self) -> Integer: + return self._new(int.__pos__(self)) + + def __pow__(self, other: int, mod: int | None = None) -> Integer: # type: ignore[override] + result = ( + int.__pow__(self, other) if mod is None else int.__pow__(self, other, mod) + ) + return self._new(result) + + def __radd__(self, other: object) -> Integer: + result = int.__radd__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rand__(self, other: object) -> Integer: + result = int.__rand__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rfloordiv__(self, other: object) -> Integer: + result = int.__rfloordiv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rlshift__(self, other: object) -> Integer: + result = int.__rlshift__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rmod__(self, other: object) -> Integer: + result = int.__rmod__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rmul__(self, other: object) -> Integer: + result = int.__rmul__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __ror__(self, other: object) -> Integer: + result = int.__ror__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __round__(self, ndigits: int = 0) -> Integer: # type: ignore[override] + return self._new(int.__round__(self, ndigits)) + + def __rpow__(self, other: int, mod: int | None = None) -> Integer: # type: ignore[misc] + result = ( + int.__rpow__(self, other) if mod is None else int.__rpow__(self, other, mod) + ) + return self._new(result) + + def __rrshift__(self, other: object) -> Integer: + result = int.__rrshift__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rshift__(self, other: object) -> Integer: + result = int.__rshift__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rxor__(self, other: object) -> Integer: + result = int.__rxor__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __sub__(self, other: object) -> Integer: + result = int.__sub__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rsub__(self, other: object) -> Integer: + result = int.__rsub__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __trunc__(self) -> Integer: + return self._new(int.__trunc__(self)) + + def __xor__(self, other: object) -> Integer: + result = int.__xor__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rtruediv__(self, other: object) -> Float: + result = int.__rtruediv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return Float._new(self, result) # type: ignore[arg-type] + + def __truediv__(self, other: object) -> Float: + result = int.__truediv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return Float._new(self, result) # type: ignore[arg-type] class Float(Item, _CustomFloat): @@ -720,7 +866,7 @@ def value(self) -> float: def as_string(self) -> str: return self._raw - def _new(self, result): + def _new(self, result: float) -> Float: raw = str(result) if self._sign and result >= 0: @@ -728,31 +874,112 @@ def _new(self, result): return Float(result, self._trivia, raw) - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[float, Trivia, str]: return float(self), self._trivia, self._raw - # float methods - __abs__ = wrap_method(float.__abs__) - __add__ = wrap_method(float.__add__) + # float methods — explicit typed wrappers + def __abs__(self) -> Float: + return self._new(float.__abs__(self)) + + def __add__(self, other: object) -> Float: + result = float.__add__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + __eq__ = float.__eq__ - __floordiv__ = wrap_method(float.__floordiv__) + + def __floordiv__(self, other: object) -> Float: + result = float.__floordiv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + __le__ = float.__le__ __lt__ = float.__lt__ - __mod__ = wrap_method(float.__mod__) - __mul__ = wrap_method(float.__mul__) - __neg__ = wrap_method(float.__neg__) - __pos__ = wrap_method(float.__pos__) - __pow__ = wrap_method(float.__pow__) - __radd__ = wrap_method(float.__radd__) - __rfloordiv__ = wrap_method(float.__rfloordiv__) - __rmod__ = wrap_method(float.__rmod__) - __rmul__ = wrap_method(float.__rmul__) - __round__ = wrap_method(float.__round__) - __rpow__ = wrap_method(float.__rpow__) - __rtruediv__ = wrap_method(float.__rtruediv__) - __truediv__ = wrap_method(float.__truediv__) - __trunc__ = float.__trunc__ + def __mod__(self, other: object) -> Float: + result = float.__mod__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __mul__(self, other: object) -> Float: + result = float.__mul__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __neg__(self) -> Float: + return self._new(float.__neg__(self)) + + def __pos__(self) -> Float: + return self._new(float.__pos__(self)) + + def __pow__(self, other: object, mod: None = None) -> Float: + result = float.__pow__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[no-any-return] + return self._new(result) + + def __radd__(self, other: object) -> Float: + result = float.__radd__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rfloordiv__(self, other: object) -> Float: + result = float.__rfloordiv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rmod__(self, other: object) -> Float: + result = float.__rmod__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rmul__(self, other: object) -> Float: + result = float.__rmul__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __round__(self, ndigits: int = 0) -> Float: # type: ignore[override] + return self._new(float.__round__(self, ndigits)) + + def __rpow__(self, other: object, mod: None = None) -> Float: + result = float.__rpow__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[no-any-return] + return self._new(result) + + def __rtruediv__(self, other: object) -> Float: + result = float.__rtruediv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __truediv__(self, other: object) -> Float: + result = float.__truediv__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __sub__(self, other: object) -> Float: + result = float.__sub__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + def __rsub__(self, other: object) -> Float: + result = float.__rsub__(self, other) # type: ignore[operator] + if result is NotImplemented: + return result # type: ignore[return-value] + return self._new(result) + + __trunc__ = float.__trunc__ __ceil__ = float.__ceil__ __floor__ = float.__floor__ @@ -762,7 +989,7 @@ class Bool(Item): A boolean literal. """ - def __init__(self, t: int, trivia: Trivia) -> None: + def __init__(self, t: int | BoolType, trivia: Trivia) -> None: super().__init__(trivia) self._value = bool(t) @@ -782,24 +1009,24 @@ def value(self) -> bool: def as_string(self) -> str: return str(self._value).lower() - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[bool, Trivia]: return self._value, self._trivia - def __bool__(self): + def __bool__(self) -> bool: return self._value __nonzero__ = __bool__ - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, bool): return NotImplemented return other == self._value - def __hash__(self): + def __hash__(self) -> int: return hash(self._value) - def __repr__(self): + def __repr__(self) -> str: return repr(self._value) @@ -818,9 +1045,10 @@ def __new__( second: int, microsecond: int, tzinfo: tzinfo | None, - *_: Any, - **kwargs: Any, - ) -> datetime: + trivia: Trivia | None = None, + raw: str | None = None, + **kwargs: object, + ) -> DateTime: return datetime.__new__( cls, year, @@ -831,7 +1059,6 @@ def __new__( second, microsecond, tzinfo=tzinfo, - **kwargs, ) def __init__( @@ -846,7 +1073,7 @@ def __init__( tzinfo: tzinfo | None, trivia: Trivia | None = None, raw: str | None = None, - **kwargs: Any, + **kwargs: object, ) -> None: super().__init__(trivia or Trivia()) @@ -878,7 +1105,7 @@ def value(self) -> datetime: def as_string(self) -> str: return self._raw - def __add__(self, other): + def __add__(self, other: timedelta) -> DateTime: if PY38: result = datetime( self.year, @@ -895,7 +1122,13 @@ def __add__(self, other): return self._new(result) - def __sub__(self, other): + @overload # type: ignore[override] + def __sub__(self, other: timedelta) -> DateTime: ... + + @overload + def __sub__(self, other: datetime) -> timedelta: ... + + def __sub__(self, other: timedelta | datetime) -> DateTime | timedelta: if PY38: result = datetime( self.year, @@ -908,23 +1141,23 @@ def __sub__(self, other): self.tzinfo, ).__sub__(other) else: - result = super().__sub__(other) + result = super().__sub__(other) # type: ignore[operator] if isinstance(result, datetime): result = self._new(result) return result - def replace(self, *args: Any, **kwargs: Any) -> datetime: - return self._new(super().replace(*args, **kwargs)) + def replace(self, *args: object, **kwargs: object) -> DateTime: + return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type] - def astimezone(self, tz: tzinfo) -> datetime: + def astimezone(self, tz: tzinfo) -> DateTime: # type: ignore[override] result = super().astimezone(tz) if PY38: return result return self._new(result) - def _new(self, result) -> DateTime: + def _new(self, result: datetime) -> DateTime: raw = result.isoformat() return DateTime( @@ -940,7 +1173,9 @@ def _new(self, result) -> DateTime: raw, ) - def _getstate(self, protocol=3): + def _getstate( + self, protocol: int = 3 + ) -> tuple[int, int, int, int, int, int, int, tzinfo | None, Trivia, str]: return ( self.year, self.month, @@ -960,7 +1195,14 @@ class Date(Item, date): A date literal. """ - def __new__(cls, year: int, month: int, day: int, *_: Any) -> date: + def __new__( + cls, + year: int, + month: int, + day: int, + trivia: Trivia | None = None, + raw: str = "", + ) -> Date: return date.__new__(cls, year, month, day) def __init__( @@ -990,7 +1232,7 @@ def value(self) -> date: def as_string(self) -> str: return self._raw - def __add__(self, other): + def __add__(self, other: timedelta) -> Date: if PY38: result = date(self.year, self.month, self.day).__add__(other) else: @@ -998,26 +1240,32 @@ def __add__(self, other): return self._new(result) - def __sub__(self, other): + @overload # type: ignore[override] + def __sub__(self, other: timedelta) -> Date: ... + + @overload + def __sub__(self, other: date) -> timedelta: ... + + def __sub__(self, other: timedelta | date) -> Date | timedelta: if PY38: result = date(self.year, self.month, self.day).__sub__(other) else: - result = super().__sub__(other) + result = super().__sub__(other) # type: ignore[operator] if isinstance(result, date): result = self._new(result) return result - def replace(self, *args: Any, **kwargs: Any) -> date: - return self._new(super().replace(*args, **kwargs)) + def replace(self, *args: object, **kwargs: object) -> Date: + return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type] - def _new(self, result): + def _new(self, result: date) -> Date: raw = result.isoformat() return Date(result.year, result.month, result.day, self._trivia, raw) - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[int, int, int, Trivia, str]: return (self.year, self.month, self.day, self._trivia, self._raw) @@ -1033,8 +1281,9 @@ def __new__( second: int, microsecond: int, tzinfo: tzinfo | None, - *_: Any, - ) -> time: + trivia: Trivia | None = None, + raw: str = "", + ) -> Time: return time.__new__(cls, hour, minute, second, microsecond, tzinfo) def __init__( @@ -1066,10 +1315,10 @@ def value(self) -> time: def as_string(self) -> str: return self._raw - def replace(self, *args: Any, **kwargs: Any) -> time: - return self._new(super().replace(*args, **kwargs)) + def replace(self, *args: object, **kwargs: object) -> Time: + return self._new(super().replace(*args, **kwargs)) # type: ignore[arg-type] - def _new(self, result): + def _new(self, result: time) -> Time: raw = result.isoformat() return Time( @@ -1082,7 +1331,9 @@ def _new(self, result): raw, ) - def _getstate(self, protocol: int = 3) -> tuple: + def _getstate( + self, protocol: int = 3 + ) -> tuple[int, int, int, int, tzinfo | None, Trivia, str]: return ( self.hour, self.minute, @@ -1110,8 +1361,10 @@ def __init__( self.comment = comment def __iter__(self) -> Iterator[Item]: - return filter( - lambda x: x is not None, (self.indent, self.value, self.comma, self.comment) + return ( + x + for x in (self.indent, self.value, self.comma, self.comment) + if x is not None ) def __repr__(self) -> str: @@ -1128,7 +1381,7 @@ def __bool__(self) -> bool: return True -class Array(Item, _CustomList): +class Array(Item, _CustomList): # type: ignore[type-arg] """ An array literal """ @@ -1190,7 +1443,7 @@ def discriminant(self) -> int: return 8 @property - def value(self) -> list: + def value(self) -> list[Item]: return self def _iter_items(self) -> Iterator[Item]: @@ -1330,16 +1583,12 @@ def __len__(self) -> int: return list.__len__(self) def item(self, index: int) -> Item: - rv = list.__getitem__(self, index) - return cast(Item, rv) + return list.__getitem__(self, index) # type: ignore[no-any-return] - def __getitem__(self, key: int | slice) -> Any: - rv = list.__getitem__(self, key) - if isinstance(rv, Bool): - return rv.value - return rv + def __getitem__(self, key: int | slice) -> Any: # type: ignore[override] + return list.__getitem__(self, key) - def __setitem__(self, key: int | slice, value: Any) -> Any: + def __setitem__(self, key: int | slice, value: Any) -> None: # type: ignore[override] it = item(value, _parent=self) list.__setitem__(self, key, it) if isinstance(key, slice): @@ -1348,7 +1597,7 @@ def __setitem__(self, key: int | slice, value: Any) -> Any: key += len(self) self._value[self._index_map[key]].value = it - def insert(self, pos: int, value: Any) -> None: + def insert(self, pos: int, value: Any) -> None: # type: ignore[override] it = item(value, _parent=self) length = len(self) if not isinstance(it, (Comment, Whitespace)): @@ -1370,13 +1619,11 @@ def insert(self, pos: int, value: Any) -> None: if idx >= 1 and self._value[idx - 1].is_whitespace(): # The last item is a pure whitespace(\n ), insert before it idx -= 1 - if ( - self._value[idx].indent is not None - and "\n" in self._value[idx].indent.s - ): + _indent = self._value[idx].indent + if _indent is not None and "\n" in _indent.s: default_indent = "\n " - indent: Item | None = None - comma: Item | None = Whitespace(",") if pos < length else None + indent: Whitespace | None = None + comma: Whitespace | None = Whitespace(",") if pos < length else None if idx < len(self._value) and not self._value[idx].is_whitespace(): # Prefer to copy the indentation from the item after indent = self._value[idx].indent @@ -1398,7 +1645,7 @@ def insert(self, pos: int, value: Any) -> None: self._value.insert(idx, new_item) self._reindex() - def __delitem__(self, key: int | slice): + def __delitem__(self, key: int | slice) -> None: # type: ignore[override] length = len(self) list.__delitem__(self, key) @@ -1420,8 +1667,8 @@ def __delitem__(self, key: int | slice): if ( idx == 0 and len(self._value) > 0 - and self._value[idx].indent - and "\n" not in self._value[idx].indent.s + and (ind := self._value[idx].indent) + and "\n" not in ind.s ): # Remove the indentation of the first item if not newline self._value[idx].indent = None @@ -1471,11 +1718,11 @@ def __delitem__(self, key: int | slice): self._reindex() - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[list[Item], Trivia, bool]: return list(self._iter_items()), self._trivia, self._multiline -class AbstractTable(Item, _CustomDict): +class AbstractTable(Item, _CustomDict): # type: ignore[type-arg] """Common behaviour of both :class:`Table` and :class:`InlineTable`""" def __init__(self, value: container.Container, trivia: Trivia): @@ -1508,7 +1755,7 @@ def append(self: AT, key: None, value: Comment | Whitespace) -> AT: ... @overload def append(self: AT, key: Key | str, value: Any) -> AT: ... - def append(self, key, value): + def append(self: AT, key: Key | str | None, value: Any) -> AT: raise NotImplementedError @overload @@ -1517,13 +1764,18 @@ def add(self: AT, key: Comment | Whitespace) -> AT: ... @overload def add(self: AT, key: Key | str, value: Any = ...) -> AT: ... - def add(self, key, value=None): + def add( + self: AT, key: Key | str | Comment | Whitespace, value: Any | None = None + ) -> AT: if value is None: if not isinstance(key, (Comment, Whitespace)): msg = "Non comment/whitespace items must have an associated key" raise ValueError(msg) - key, value = None, key + return self.append(None, key) + + if isinstance(key, (Comment, Whitespace)): + raise ValueError("Comment/Whitespace keys must not have a value") return self.append(key, value) @@ -1538,14 +1790,14 @@ def remove(self: AT, key: Key | str) -> AT: return self - def item(self, key: Key | str) -> Item: + def item(self, key: Key | str) -> Item | OutOfOrderTableProxy: return self._value.item(key) - def setdefault(self, key: Key | str, default: Any) -> Any: + def setdefault(self, key: Key | str, default: Any) -> Any: # type: ignore[override] super().setdefault(key, default) return self[key] - def __str__(self): + def __str__(self) -> str: return str(self.value) def copy(self: AT) -> AT: @@ -1560,13 +1812,13 @@ def __iter__(self) -> Iterator[str]: def __len__(self) -> int: return len(self._value) - def __delitem__(self, key: Key | str) -> None: + def __delitem__(self, key: Key | str) -> None: # type: ignore[override] self.remove(key) - def __getitem__(self, key: Key | str) -> Item: - return cast(Item, self._value[key]) + def __getitem__(self, key: Key | str) -> Any: # type: ignore[override] + return self._value[key] - def __setitem__(self, key: Key | str, value: Any) -> None: + def __setitem__(self, key: Key | str, value: Any) -> None: # type: ignore[override] if not isinstance(value, Item): value = item(value, _parent=self) @@ -1722,7 +1974,7 @@ def indent(self, indent: int) -> Table: return self - def invalidate_display_name(self): + def invalidate_display_name(self) -> None: """Call ``invalidate_display_name`` on the contained tables""" self.display_name = None @@ -1730,7 +1982,9 @@ def invalidate_display_name(self): if hasattr(child, "invalidate_display_name"): child.invalidate_display_name() - def _getstate(self, protocol: int = 3) -> tuple: + def _getstate( + self, protocol: int = 3 + ) -> tuple[container.Container, Trivia, bool, bool | None, str | None, str | None]: return ( self._value, self._trivia, @@ -1847,7 +2101,7 @@ def as_string(self) -> str: return buf - def __setitem__(self, key: Key | str, value: Any) -> None: + def __setitem__(self, key: Key | str, value: Any) -> None: # type: ignore[override] if hasattr(value, "trivia") and value.trivia.comment: value.trivia.comment = "" super().__setitem__(key, value) @@ -1855,16 +2109,18 @@ def __setitem__(self, key: Key | str, value: Any) -> None: def __copy__(self) -> InlineTable: return type(self)(self._value.copy(), self._trivia.copy(), self._new) - def _getstate(self, protocol: int = 3) -> tuple: + def _getstate(self, protocol: int = 3) -> tuple[container.Container, Trivia]: return (self._value, self._trivia) -class String(str, Item): +class String(str, Item): # type: ignore[misc] """ A string literal. """ - def __new__(cls, t, value, original, trivia): + def __new__( + cls, t: StringType, value: str, original: str, trivia: Trivia + ) -> String: return super().__new__(cls, value) def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None: @@ -1891,9 +2147,7 @@ def as_string(self) -> str: def type(self) -> StringType: return self._t - def __add__(self: ItemT, other: str) -> ItemT: - if not isinstance(other, str): - return NotImplemented + def __add__(self, other: str) -> String: result = super().__add__(other) original = self._original + getattr(other, "_original", other) @@ -1902,11 +2156,13 @@ def __add__(self: ItemT, other: str) -> ItemT: def _new(self, result: str, original: str) -> String: return String(self._t, result, original, self._trivia) - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[StringType, str, str, Trivia]: return self._t, str(self), self._original, self._trivia @classmethod - def from_raw(cls, value: str, type_=StringType.SLB, escape=True) -> String: + def from_raw( + cls, value: str, type_: StringType = StringType.SLB, escape: bool = True + ) -> String: value = decode(value) invalid = type_.invalid_sequences @@ -1919,7 +2175,7 @@ def from_raw(cls, value: str, type_=StringType.SLB, escape=True) -> String: return cls(type_, decode(value), string_value, Trivia()) -class AoT(Item, _CustomList): +class AoT(Item, _CustomList): # type: ignore[type-arg] """ An array of table literal """ @@ -1954,29 +2210,29 @@ def discriminant(self) -> int: return 12 @property - def value(self) -> list[dict[Any, Any]]: + def value(self) -> list[dict[str, Any]]: return [v.value for v in self._body] def __len__(self) -> int: return len(self._body) - @overload + @overload # type: ignore[override] def __getitem__(self, key: slice) -> list[Table]: ... @overload def __getitem__(self, key: int) -> Table: ... - def __getitem__(self, key): + def __getitem__(self, key: int | slice) -> Table | list[Table]: return self._body[key] - def __setitem__(self, key: slice | int, value: Any) -> None: + def __setitem__(self, key: slice | int, value: Any) -> None: # type: ignore[override] self._body[key] = item(value, _parent=self) - def __delitem__(self, key: slice | int) -> None: + def __delitem__(self, key: slice | int) -> None: # type: ignore[override] del self._body[key] list.__delitem__(self, key) - def insert(self, index: int, value: dict) -> None: + def insert(self, index: int, value: dict[str, Any]) -> None: # type: ignore[override] value = item(value, _parent=self) if not isinstance(value, Table): raise ValueError(f"Unsupported insert value type: {type(value)}") @@ -2006,7 +2262,7 @@ def insert(self, index: int, value: dict) -> None: self._body.insert(index, value) list.insert(self, index, value) - def invalidate_display_name(self): + def invalidate_display_name(self) -> None: """Call ``invalidate_display_name`` on the contained tables""" for child in self: if hasattr(child, "invalidate_display_name"): @@ -2022,7 +2278,7 @@ def as_string(self) -> str: def __repr__(self) -> str: return f"" - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple[list[Table], str | None, bool]: return self._body, self.name, self._parsed @@ -2048,5 +2304,5 @@ def value(self) -> None: def as_string(self) -> str: return "" - def _getstate(self, protocol=3) -> tuple: + def _getstate(self, protocol: int = 3) -> tuple[()]: return () diff --git a/tomlkit/parser.py b/tomlkit/parser.py index 7321e0a..538ed03 100644 --- a/tomlkit/parser.py +++ b/tomlkit/parser.py @@ -4,6 +4,8 @@ import re import string +from typing import Any + from tomlkit._compat import decode from tomlkit._utils import RFC_3339_LOOSE from tomlkit._utils import _escaped @@ -44,6 +46,7 @@ from tomlkit.items import Trivia from tomlkit.items import Whitespace from tomlkit.source import Source +from tomlkit.source import _StateHandler from tomlkit.toml_char import TOMLChar from tomlkit.toml_document import TOMLDocument @@ -67,19 +70,19 @@ def __init__(self, string: str | bytes) -> None: self._aot_stack: list[Key] = [] @property - def _state(self): + def _state(self) -> _StateHandler: return self._src.state @property - def _idx(self): + def _idx(self) -> int: return self._src.idx @property - def _current(self): + def _current(self) -> TOMLChar: return self._src.current @property - def _marker(self): + def _marker(self) -> int: return self._src.marker def extract(self) -> str: @@ -102,7 +105,7 @@ def inc_n(self, n: int, exception: type[ParseError] | None = None) -> bool: """ return self._src.inc_n(n=n, exception=exception) - def consume(self, chars, min=0, max=-1): + def consume(self, chars: str, min: int = 0, max: int = -1) -> None: """ Consume chars until min/max is satisfied is valid. """ @@ -120,7 +123,12 @@ def mark(self) -> None: """ self._src.mark() - def parse_error(self, exception=ParseError, *args, **kwargs): + def parse_error( + self, + exception: type[ParseError] = ParseError, + *args: Any, + **kwargs: Any, + ) -> ParseError: """ Creates a generic "parse error" at the current position. """ @@ -233,7 +241,7 @@ def _parse_item(self) -> tuple[Key | None, Item] | None: return None, Comment(Trivia(indent, cws, comment, trail)) elif c == "[": # Found a table, delegate to the calling function. - return + return None else: # Beginning of a KV pair. # Return to beginning of whitespace so it gets included @@ -396,12 +404,12 @@ def _parse_quoted_key(self) -> Key: while self._current.is_spaces() and self.inc(): pass original += self.extract() - key = SingleKey(str(key_str), t=key_type, sep="", original=original) + result: Key = SingleKey(str(key_str), t=key_type, sep="", original=original) if self._current == ".": self.inc() - key = key.concat(self._parse_key()) + result = result.concat(self._parse_key()) - return key + return result def _parse_bare_key(self) -> Key: """ @@ -413,22 +421,22 @@ def _parse_bare_key(self) -> Key: pass original = self.extract() - key = original.strip() - if not key: + key_s = original.strip() + if not key_s: # Empty key raise self.parse_error(EmptyKeyError) - if " " in key: + if " " in key_s: # Bare key with spaces in it - raise self.parse_error(ParseError, f'Invalid key "{key}"') + raise self.parse_error(ParseError, f'Invalid key "{key_s}"') - key = SingleKey(key, KeyType.Bare, "", original) + result: Key = SingleKey(key_s, KeyType.Bare, "", original) if self._current == ".": self.inc() - key = key.concat(self._parse_key()) + result = result.concat(self._parse_key()) - return key + return result def _parse_value(self) -> Item: """ @@ -554,10 +562,10 @@ def _parse_value(self) -> Item: else: raise self.parse_error(UnexpectedCharError, c) - def _parse_true(self): + def _parse_true(self) -> Bool: return self._parse_bool(BoolType.TRUE) - def _parse_false(self): + def _parse_false(self) -> Bool: return self._parse_bool(BoolType.FALSE) def _parse_bool(self, style: BoolType) -> Bool: @@ -633,6 +641,8 @@ def _parse_array(self) -> Array: else: return res + raise self.parse_error(ParseError, "Failed to parse array") + def _parse_inline_table(self) -> InlineTable: # consume opening bracket, EOF here is an issue (middle of array) self.inc(exception=UnexpectedEofError) @@ -732,7 +742,7 @@ def _parse_basic_string(self) -> String: with self._state: return self._parse_string(StringType.SLB) - def _parse_escaped_char(self, multiline): + def _parse_escaped_char(self, multiline: bool) -> str: if multiline and self._current.is_ws(): # When the last non-whitespace character on a line is # a \, it will be trimmed along with all whitespace @@ -768,6 +778,7 @@ def _parse_escaped_char(self, multiline): # this needs to be a unicode u, ue = self._peek_unicode(self._current == "U") if u is not None: + assert ue is not None # consume the U char and the unicode value self.inc_n(len(ue) + 1) @@ -778,6 +789,7 @@ def _parse_escaped_char(self, multiline): if self._current == "x": h, he = self._peek_hex() if h is not None: + assert he is not None # consume the x char and the hex value self.inc_n(len(he) + 1) return h @@ -819,7 +831,7 @@ def _parse_string(self, delim: StringType) -> String: # consume the newline, EOF here is an issue (middle of string) self.inc(exception=UnexpectedEofError) else: - cur = self._current + cur: str = self._current with self._state(restore=True): if self.inc(): cur += self._current @@ -964,7 +976,7 @@ def _parse_table( cws, comment, trail = self._parse_comment_trail() - result = Null() + result: Table | AoT = Null() # type: ignore[assignment] table = Table( values, Trivia(indent, cws, comment, trail), @@ -1019,11 +1031,11 @@ def _parse_table( key = name_parts[0] while not self.end(): - item = self._parse_item() - if item: - _key, item = item - if not self._merge_ws(item, values): - table.raw_append(_key, item) + parsed = self._parse_item() + if parsed: + _key, _val = parsed + if not self._merge_ws(_val, values): + table.raw_append(_key, _val) else: if self._current == "[": _, key_next = self._peek_table() @@ -1091,12 +1103,13 @@ def _parse_aot(self, first: Table, name_first: Key) -> AoT: Parses all siblings of the provided table first and bundles them into an AoT. """ - payload = [first] + payload: list[Table] = [first] self._aot_stack.append(name_first) while not self.end(): is_aot_next, name_next = self._peek_table() if is_aot_next and name_next == name_first: _, table = self._parse_table(name_first) + assert isinstance(table, Table) payload.append(table) else: break diff --git a/tomlkit/source.py b/tomlkit/source.py index 8a8b2c3..327c627 100644 --- a/tomlkit/source.py +++ b/tomlkit/source.py @@ -28,7 +28,12 @@ def __enter__(self) -> _State: return self - def __exit__(self, exception_type, exception_val, trace): + def __exit__( + self, + exception_type: type[BaseException] | None, + exception_val: BaseException | None, + trace: Any, + ) -> None: # Exiting this context manager - restore the prior state if self.restore or exception_type: self._source._chars = self._chars @@ -45,19 +50,28 @@ class _StateHandler: def __init__(self, source: Source) -> None: self._source = source - self._states = [] + self._states: list[_State] = [] - def __call__(self, *args, **kwargs): - return _State(self._source, *args, **kwargs) + def __call__( + self, + save_marker: bool | None = False, + restore: bool | None = False, + ) -> _State: + return _State(self._source, save_marker, restore) def __enter__(self) -> _State: state = self() self._states.append(state) return state.__enter__() - def __exit__(self, exception_type, exception_val, trace): + def __exit__( + self, + exception_type: type[BaseException] | None, + exception_val: BaseException | None, + trace: Any, + ) -> None: state = self._states.pop() - return state.__exit__(exception_type, exception_val, trace) + state.__exit__(exception_type, exception_val, trace) class Source(str): @@ -77,7 +91,7 @@ def __init__(self, _: str) -> None: self.inc() - def reset(self): + def reset(self) -> None: # initialize both idx and current self.inc() @@ -130,7 +144,7 @@ def inc_n(self, n: int, exception: type[ParseError] | None = None) -> bool: """ return all(self.inc(exception=exception) for _ in range(n)) - def consume(self, chars, min=0, max=-1): + def consume(self, chars: str, min: int = 0, max: int = -1) -> None: """ Consume chars until min/max is satisfied is valid. """ diff --git a/tomlkit/toml_char.py b/tomlkit/toml_char.py index b4bb411..970cbd4 100644 --- a/tomlkit/toml_char.py +++ b/tomlkit/toml_char.py @@ -2,7 +2,7 @@ class TOMLChar(str): - def __init__(self, c): + def __init__(self, c: str) -> None: super().__init__() if len(self) > 1: diff --git a/tomlkit/toml_file.py b/tomlkit/toml_file.py index b8ffcbd..324576b 100644 --- a/tomlkit/toml_file.py +++ b/tomlkit/toml_file.py @@ -24,7 +24,7 @@ class TOMLFile: def __init__(self, path: _StrPath) -> None: self._path = path - self._linesep = os.linesep + self._linesep: str = os.linesep def read(self) -> TOMLDocument: """Read the file content as a :class:`tomlkit.toml_document.TOMLDocument`."""