diff --git a/.coveragerc b/.coveragerc index d5f1204..58f1e4a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,44 +3,28 @@ branch = True data_file = coverage.db include = src/* +omit = + *__init__.py + tests/* [paths] source = - src/ + */src/* [report] -# Regexes for lines to exclude from consideration -exclude_lines = - # Have to re-enable the standard pragma - pragma: nocover - pragma: nobranch - pragma: no cover - pragma: no branch - - # Don't complain about missing debug-only code: +sort = Cover +exclude_also = def __repr__ - if self\.debug - \.\.\. - - # Don't complain if tests don't hit defensive assertion code: + def __str__ + if self.debug: + if settings.DEBUG raise AssertionError raise NotImplementedError - - # Don't complain if non-runnable code isn't run: if 0: if __name__ == .__main__.: if TYPE_CHECKING: - class.*\(Protocol.*\): - \@abc\.abstractmethod - -omit = - setup.py - .*env* - *lib/python* - dist* - tests* - benchmark* - docs* - mypy.py + class\s\w+\((typing\.)?Protocol(\[.*\])?\): + @(abc\.)?abstractmethod + @(typing\.)?overload skip_empty = True diff --git a/.gitignore b/.gitignore index 6a4f84d..e5fcfcb 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,4 @@ venv.bak/ /pip-wheel-metadata/ /benchmark/.cases.json *.DS_Store +coverage.db diff --git a/poetry.lock b/poetry.lock index 8cc496e..54c434a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -362,62 +362,63 @@ files = [ [[package]] name = "coverage" -version = "6.5.0" +version = "7.2.1" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, - {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, - {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, - {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, - {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, - {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, - {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, - {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, - {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, - {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, - {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, - {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, - {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, - {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, - {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, - {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, + {file = "coverage-7.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49567ec91fc5e0b15356da07a2feabb421d62f52a9fff4b1ec40e9e19772f5f8"}, + {file = "coverage-7.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d2ef6cae70168815ed91388948b5f4fcc69681480a0061114db737f957719f03"}, + {file = "coverage-7.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3004765bca3acd9e015794e5c2f0c9a05587f5e698127ff95e9cfba0d3f29339"}, + {file = "coverage-7.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cca7c0b7f5881dfe0291ef09ba7bb1582cb92ab0aeffd8afb00c700bf692415a"}, + {file = "coverage-7.2.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2167d116309f564af56f9aa5e75ef710ef871c5f9b313a83050035097b56820"}, + {file = "coverage-7.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cb5f152fb14857cbe7f3e8c9a5d98979c4c66319a33cad6e617f0067c9accdc4"}, + {file = "coverage-7.2.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:87dc37f16fb5e3a28429e094145bf7c1753e32bb50f662722e378c5851f7fdc6"}, + {file = "coverage-7.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e191a63a05851f8bce77bc875e75457f9b01d42843f8bd7feed2fc26bbe60833"}, + {file = "coverage-7.2.1-cp310-cp310-win32.whl", hash = "sha256:e3ea04b23b114572b98a88c85379e9e9ae031272ba1fb9b532aa934c621626d4"}, + {file = "coverage-7.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:0cf557827be7eca1c38a2480484d706693e7bb1929e129785fe59ec155a59de6"}, + {file = "coverage-7.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:570c21a29493b350f591a4b04c158ce1601e8d18bdcd21db136fbb135d75efa6"}, + {file = "coverage-7.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e872b082b32065ac2834149dc0adc2a2e6d8203080501e1e3c3c77851b466f9"}, + {file = "coverage-7.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fac6343bae03b176e9b58104a9810df3cdccd5cfed19f99adfa807ffbf43cf9b"}, + {file = "coverage-7.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abacd0a738e71b20e224861bc87e819ef46fedba2fb01bc1af83dfd122e9c319"}, + {file = "coverage-7.2.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9256d4c60c4bbfec92721b51579c50f9e5062c21c12bec56b55292464873508"}, + {file = "coverage-7.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:80559eaf6c15ce3da10edb7977a1548b393db36cbc6cf417633eca05d84dd1ed"}, + {file = "coverage-7.2.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0bd7e628f6c3ec4e7d2d24ec0e50aae4e5ae95ea644e849d92ae4805650b4c4e"}, + {file = "coverage-7.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09643fb0df8e29f7417adc3f40aaf379d071ee8f0350ab290517c7004f05360b"}, + {file = "coverage-7.2.1-cp311-cp311-win32.whl", hash = "sha256:1b7fb13850ecb29b62a447ac3516c777b0e7a09ecb0f4bb6718a8654c87dfc80"}, + {file = "coverage-7.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:617a94ada56bbfe547aa8d1b1a2b8299e2ec1ba14aac1d4b26a9f7d6158e1273"}, + {file = "coverage-7.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8649371570551d2fd7dee22cfbf0b61f1747cdfb2b7587bb551e4beaaa44cb97"}, + {file = "coverage-7.2.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d2b9b5e70a21474c105a133ba227c61bc95f2ac3b66861143ce39a5ea4b3f84"}, + {file = "coverage-7.2.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae82c988954722fa07ec5045c57b6d55bc1a0890defb57cf4a712ced65b26ddd"}, + {file = "coverage-7.2.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:861cc85dfbf55a7a768443d90a07e0ac5207704a9f97a8eb753292a7fcbdfcfc"}, + {file = "coverage-7.2.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0339dc3237c0d31c3b574f19c57985fcbe494280153bbcad33f2cdf469f4ac3e"}, + {file = "coverage-7.2.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5928b85416a388dd557ddc006425b0c37e8468bd1c3dc118c1a3de42f59e2a54"}, + {file = "coverage-7.2.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8d3843ca645f62c426c3d272902b9de90558e9886f15ddf5efe757b12dd376f5"}, + {file = "coverage-7.2.1-cp37-cp37m-win32.whl", hash = "sha256:6a034480e9ebd4e83d1aa0453fd78986414b5d237aea89a8fdc35d330aa13bae"}, + {file = "coverage-7.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6fce673f79a0e017a4dc35e18dc7bb90bf6d307c67a11ad5e61ca8d42b87cbff"}, + {file = "coverage-7.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f099da6958ddfa2ed84bddea7515cb248583292e16bb9231d151cd528eab657"}, + {file = "coverage-7.2.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:97a3189e019d27e914ecf5c5247ea9f13261d22c3bb0cfcfd2a9b179bb36f8b1"}, + {file = "coverage-7.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a81dbcf6c6c877986083d00b834ac1e84b375220207a059ad45d12f6e518a4e3"}, + {file = "coverage-7.2.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2c3dde4c0b9be4b02067185136b7ee4681978228ad5ec1278fa74f5ca3e99"}, + {file = "coverage-7.2.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a209d512d157379cc9ab697cbdbb4cfd18daa3e7eebaa84c3d20b6af0037384"}, + {file = "coverage-7.2.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f3d07edb912a978915576a776756069dede66d012baa503022d3a0adba1b6afa"}, + {file = "coverage-7.2.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8dca3c1706670297851bca1acff9618455122246bdae623be31eca744ade05ec"}, + {file = "coverage-7.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b1991a6d64231a3e5bbe3099fb0dd7c9aeaa4275ad0e0aeff4cb9ef885c62ba2"}, + {file = "coverage-7.2.1-cp38-cp38-win32.whl", hash = "sha256:22c308bc508372576ffa3d2dbc4824bb70d28eeb4fcd79d4d1aed663a06630d0"}, + {file = "coverage-7.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:b0c0d46de5dd97f6c2d1b560bf0fcf0215658097b604f1840365296302a9d1fb"}, + {file = "coverage-7.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4dd34a935de268a133e4741827ae951283a28c0125ddcdbcbba41c4b98f2dfef"}, + {file = "coverage-7.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0f8318ed0f3c376cfad8d3520f496946977abde080439d6689d7799791457454"}, + {file = "coverage-7.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:834c2172edff5a08d78e2f53cf5e7164aacabeb66b369f76e7bb367ca4e2d993"}, + {file = "coverage-7.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4d70c853f0546855f027890b77854508bdb4d6a81242a9d804482e667fff6e6"}, + {file = "coverage-7.2.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a6450da4c7afc4534305b2b7d8650131e130610cea448ff240b6ab73d7eab63"}, + {file = "coverage-7.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:99f4dd81b2bb8fc67c3da68b1f5ee1650aca06faa585cbc6818dbf67893c6d58"}, + {file = "coverage-7.2.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bdd3f2f285ddcf2e75174248b2406189261a79e7fedee2ceeadc76219b6faa0e"}, + {file = "coverage-7.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f29351393eb05e6326f044a7b45ed8e38cb4dcc38570d12791f271399dc41431"}, + {file = "coverage-7.2.1-cp39-cp39-win32.whl", hash = "sha256:e2b50ebc2b6121edf352336d503357321b9d8738bb7a72d06fc56153fd3f4cd8"}, + {file = "coverage-7.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:bd5a12239c0006252244f94863f1c518ac256160cd316ea5c47fb1a11b25889a"}, + {file = "coverage-7.2.1-pp37.pp38.pp39-none-any.whl", hash = "sha256:436313d129db7cf5b4ac355dd2bd3f7c7e5294af077b090b85de75f8458b8616"}, + {file = "coverage-7.2.1.tar.gz", hash = "sha256:c77f2a9093ccf329dd523a9b2b3c854c20d2a3d968b6def3b820272ca6732242"}, ] [package.dependencies] diff --git a/src/typical/classes.py b/src/typical/classes.py index dfa04bf..3a8d5d9 100644 --- a/src/typical/classes.py +++ b/src/typical/classes.py @@ -53,7 +53,7 @@ def wrap(cls): _stack.add(key) - if sys.version_info >= (3, 10) and "typical" not in cls.__qualname__: + if sys.version_info >= (3, 10) and "typical" not in cls.__module__: warnings.warn( f"You are using Python {sys.version}. " "Python 3.10 introduced native support for slotted dataclasses. " diff --git a/src/typical/core/constants.py b/src/typical/core/constants.py index d539a0b..2af8464 100644 --- a/src/typical/core/constants.py +++ b/src/typical/core/constants.py @@ -2,8 +2,7 @@ class empty: - def __bool__(self): - return False + """A singleton for signalling no input.""" DEFAULT_ENCODING = "utf-8" diff --git a/src/typical/core/constraints/core/types.py b/src/typical/core/constraints/core/types.py index 03c9dbe..9fa8a38 100644 --- a/src/typical/core/constraints/core/types.py +++ b/src/typical/core/constraints/core/types.py @@ -7,7 +7,6 @@ import numbers import re import reprlib -import sys import warnings from typing import ( Any, @@ -344,7 +343,7 @@ def error( class DelayedConstraintValidator(AbstractConstraintValidator[_VT]): __slots__ = ( "ref", - "module", + "globalns", "localns", "nullable", "readonly", @@ -357,7 +356,7 @@ class DelayedConstraintValidator(AbstractConstraintValidator[_VT]): def __init__( self, ref: ForwardRef | type, - module: str, + globalns: Mapping, localns: Mapping, nullable: bool, readonly: bool, @@ -367,7 +366,7 @@ def __init__( **config, ): self.ref = ref - self.module = module + self.globalns = globalns self.localns = localns self.nullable = nullable self.readonly = readonly @@ -381,9 +380,8 @@ def __init__( def _evaluate_reference(self) -> AbstractConstraintValidator[_VT]: type = self.ref if isinstance(self.ref, ForwardRef): - globalns = sys.modules[self.module].__dict__.copy() try: - type = evaluate_forwardref(self.ref, globalns or {}, self.localns or {}) + type = evaluate_forwardref(self.ref, self.globalns, self.localns) except NameError as e: # pragma: nocover warnings.warn( f"Counldn't resolve forward reference: {e}. " diff --git a/src/typical/core/constraints/factory.py b/src/typical/core/constraints/factory.py index 1b31403..ea7bec7 100644 --- a/src/typical/core/constraints/factory.py +++ b/src/typical/core/constraints/factory.py @@ -3,6 +3,7 @@ import decimal as stdlib_decimal import functools import inspect +import typing from typing import Any, Callable, Collection, Hashable, TypeVar, Union, cast from typical import checks, inspection @@ -56,6 +57,16 @@ def build( default: Hashable | Callable[[], VT] | constants.empty = constants.empty, **config, ) -> types.AbstractConstraintValidator: + if t in self.NOOP: + return self._from_undeclared_type( + t=t, + nullable=nullable, + readonly=readonly, + writeonly=writeonly, + cls=cls, + default=default, + ) + if hasattr(t, "__constraints__"): return t.__constraints__ # type: ignore[attr-defined] @@ -73,27 +84,6 @@ def build( t = args[0] if len(args) == 1 else Union[args] - if t in (Any, ..., type(...)): - return engine.ConstraintValidator( - constraints=types.TypeConstraints( - type=t, - nullable=nullable, - readonly=readonly, - writeonly=writeonly, - default=default, - ), - validator=validators.NoOpInstanceValidator( - type=t, - precheck=validators.NoOpPrecheck( - type=t, - nullable=nullable, - readonly=readonly, - writeonly=writeonly, - name=name, - **config, - ), - ), - ) if t is cls or t in self.__visited: module = getattr(t, "__module__", None) if cls and cls is not ...: @@ -116,14 +106,23 @@ def build( t = ForwardRef(str(t)) # type: ignore[assignment] if checks.isforwardref(t): + # If we don't have an enclosing scope, search for one. if not cls or cls is ...: - raise TypeError( - f"Cannot build constraints for {t} without an enclosing class." - ) + caller = inspection.getcaller() + globalns, localns = caller.f_globals, caller.f_locals + # Otherwise, use the context from the enclosing scope. + else: + globalns = {} + module = inspect.getmodule(cls) + if module: + globalns = vars(module) + localns = dict(inspect.getmembers(cls)) + + globalns.update(typing=typing) return types.DelayedConstraintValidator( ref=t, - module=cls.__module__, - localns=(getattr(cls, "__dict__", None) or {}).copy(), + globalns=globalns, + localns=localns, nullable=nullable, readonly=readonly, writeonly=writeonly, diff --git a/src/typical/inspection.py b/src/typical/inspection.py index 731fbfc..fd1da34 100644 --- a/src/typical/inspection.py +++ b/src/typical/inspection.py @@ -575,6 +575,30 @@ def extract(name: str, *, frame: types.FrameType = None) -> Optional[Any]: return None +def getcaller(frame: types.FrameType = None) -> types.FrameType: + """Get the caller of the current scope, excluding this library. + + If `frame` is not provided, this function will use the current frame. + """ + if frame is None: + frame = inspect.currentframe() + + while frame.f_back: + frame = frame.f_back + module = inspect.getmodule(frame) + if module and module.__name__.startswith("typical"): + continue + + code = frame.f_code + if getattr(code, "co_qualname", "").startswith("typical"): + continue + if "typical" in code.co_filename: + continue + return frame + + return frame + + @lru_cache(maxsize=None) def get_type_graph(t: Type) -> TypeGraph: """Get a directed graph of the type(s) this annotation represents.""" diff --git a/tests/unit/core/constraints/test_factory.py b/tests/unit/core/constraints/test_factory.py new file mode 100644 index 0000000..3fe4f27 --- /dev/null +++ b/tests/unit/core/constraints/test_factory.py @@ -0,0 +1,147 @@ +from __future__ import annotations + +import decimal +import enum +import inspect +import typing + +import pytest + +from typical.core import constants +from typical.core.constraints import factory +from typical.core.constraints.core import types, validators + + +class MyEnum(enum.Enum): + ... + + +class MyClass: + ... + + +@pytest.mark.suite( + anytype=dict( + given_type=typing.Any, + given_context=dict(), + expected_constraints_cls=types.UndeclaredTypeConstraints, + expected_validator_cls=validators.NoOpInstanceValidator, + ), + constants_empty=dict( + given_type=constants.empty, + given_context=dict(), + expected_constraints_cls=types.UndeclaredTypeConstraints, + expected_validator_cls=validators.NoOpInstanceValidator, + ), + param_empty=dict( + given_type=inspect.Parameter.empty, + given_context=dict(), + expected_constraints_cls=types.UndeclaredTypeConstraints, + expected_validator_cls=validators.NoOpInstanceValidator, + ), + ellipsis=dict( + given_type=Ellipsis, + given_context=dict(), + expected_constraints_cls=types.UndeclaredTypeConstraints, + expected_validator_cls=validators.NoOpInstanceValidator, + ), + enum=dict( + given_type=MyEnum, + given_context=dict(), + expected_constraints_cls=types.EnumerationConstraints, + expected_validator_cls=validators.OneOfValidator, + ), + literal=dict( + given_type=typing.Literal[1, 2], + given_context=dict(), + expected_constraints_cls=types.EnumerationConstraints, + expected_validator_cls=validators.OneOfValidator, + ), + string=dict( + given_type=str, + given_context=dict(), + expected_constraints_cls=types.TextConstraints, + expected_validator_cls=validators.IsInstanceValidator, + ), + bytestring=dict( + given_type=bytes, + given_context=dict(), + expected_constraints_cls=types.TextConstraints, + expected_validator_cls=validators.IsInstanceValidator, + ), + boolean=dict( + given_type=bool, + given_context=dict(), + expected_constraints_cls=types.TypeConstraints, + expected_validator_cls=validators.IsInstanceValidator, + ), + integer=dict( + given_type=int, + given_context=dict(), + expected_constraints_cls=types.NumberConstraints, + expected_validator_cls=validators.IsInstanceValidator, + ), + float=dict( + given_type=float, + given_context=dict(), + expected_constraints_cls=types.NumberConstraints, + expected_validator_cls=validators.IsInstanceValidator, + ), + decimal=dict( + given_type=decimal.Decimal, + given_context=dict(), + expected_constraints_cls=types.DecimalConstraints, + expected_validator_cls=validators.IsInstanceValidator, + ), + structured=dict( + given_type=MyClass, + given_context=dict(), + expected_constraints_cls=types.StructuredObjectConstraints, + expected_validator_cls=validators.IsInstanceValidator, + ), + dict=dict( + given_type=dict, + given_context=dict(), + expected_constraints_cls=types.MappingConstraints, + expected_validator_cls=validators.IsInstanceValidator, + ), + mapping=dict( + given_type=typing.Mapping, + given_context=dict(), + expected_constraints_cls=types.MappingConstraints, + expected_validator_cls=validators.IsInstanceValidator, + ), + collection=dict( + given_type=typing.Collection, + given_context=dict(), + expected_constraints_cls=types.ArrayConstraints, + expected_validator_cls=validators.IsInstanceValidator, + ), + optional=dict( + given_type=typing.Optional[str], + given_context=dict(), + expected_constraints_cls=types.TextConstraints, + expected_validator_cls=validators.NullableIsInstanceValidator, + ), +) +def test_build( + given_type, given_context, expected_constraints_cls, expected_validator_cls +): + # When + built_cv = factory.build(t=given_type, **given_context) + # Then + assert isinstance(built_cv.constraints, expected_constraints_cls) + assert isinstance(built_cv.validator, expected_validator_cls) + + +def test_build_forwardref(): + # Given + given_type = "dict | None" + expected_constraints_cls = types.MappingConstraints + expected_validator_cls = validators.NullableIsInstanceValidator + # When + built_dcv = factory.build(t=given_type) + built_cv = built_dcv.cv + # Then + assert isinstance(built_cv.constraints, expected_constraints_cls) + assert isinstance(built_cv.validator, expected_validator_cls) diff --git a/tests/unit/core/constraints/text/test_assertions.py b/tests/unit/core/constraints/text/test_assertions.py index 0a7f0cf..f3b9f7f 100644 --- a/tests/unit/core/constraints/text/test_assertions.py +++ b/tests/unit/core/constraints/text/test_assertions.py @@ -127,6 +127,20 @@ given_value="f00", expected_is_valid=False, ), + pattern_valid=dict( + given_min_length=None, + given_max_length=None, + given_regex=re.compile(r"^\d+$"), + given_value="2", + expected_is_valid=True, + ), + pattern_invalid=dict( + given_min_length=None, + given_max_length=None, + given_regex=re.compile(r"^\d+$"), + given_value="2i", + expected_is_valid=False, + ), ) def test_assertions( given_min_length,