diff --git a/Makefile b/Makefile index 62f07c6e..4c80c366 100644 --- a/Makefile +++ b/Makefile @@ -69,4 +69,16 @@ docs: docs/pytm/index.html docs/threats.md .PHONY: fmt fmt: - black $(wildcard pytm/*.py) $(wildcard tests/*.py) $(wildcard *.py) + poetry run ruff format + +.PHONY: ana +ana: + poetry run ruff analyze graph + +.PHONY: fix +fix: + poetry run ruff check . --fix + +.PHONY: check +check: + poetry run ruff check diff --git a/poetry.lock b/poetry.lock index 7c47da05..2973880b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,81 +1,5 @@ # This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. -[[package]] -name = "black" -version = "25.9.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7"}, - {file = "black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92"}, - {file = "black-25.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96b6726d690c96c60ba682955199f8c39abc1ae0c3a494a9c62c0184049a713"}, - {file = "black-25.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:d119957b37cc641596063cd7db2656c5be3752ac17877017b2ffcdb9dfc4d2b1"}, - {file = "black-25.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa"}, - {file = "black-25.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d"}, - {file = "black-25.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608"}, - {file = "black-25.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f"}, - {file = "black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0"}, - {file = "black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4"}, - {file = "black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e"}, - {file = "black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a"}, - {file = "black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175"}, - {file = "black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f"}, - {file = "black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831"}, - {file = "black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357"}, - {file = "black-25.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef69351df3c84485a8beb6f7b8f9721e2009e20ef80a8d619e2d1788b7816d47"}, - {file = "black-25.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3c1f4cd5e93842774d9ee4ef6cd8d17790e65f44f7cdbaab5f2cf8ccf22a823"}, - {file = "black-25.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:154b06d618233fe468236ba1f0e40823d4eb08b26f5e9261526fde34916b9140"}, - {file = "black-25.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e593466de7b998374ea2585a471ba90553283fb9beefcfa430d84a2651ed5933"}, - {file = "black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae"}, - {file = "black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -pytokens = ">=0.1.10" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.10)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "click" -version = "8.1.8" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, - {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -markers = "platform_system == \"Windows\"" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - [[package]] name = "importlib-metadata" version = "8.7.0" @@ -239,42 +163,6 @@ files = [ {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, ] -[[package]] -name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, -] - -[[package]] -name = "packaging" -version = "25.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - [[package]] name = "pdoc3" version = "0.11.6" @@ -291,23 +179,6 @@ files = [ mako = "*" markdown = ">=3.0" -[[package]] -name = "platformdirs" -version = "4.4.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, - {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.14.1)"] - [[package]] name = "pydal" version = "20200714.1" @@ -320,74 +191,32 @@ files = [ ] [[package]] -name = "pytokens" -version = "0.1.10" -description = "A Fast, spec compliant Python 3.12+ tokenizer that runs on older Pythons." +name = "ruff" +version = "0.14.0" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pytokens-0.1.10-py3-none-any.whl", hash = "sha256:db7b72284e480e69fb085d9f251f66b3d2df8b7166059261258ff35f50fb711b"}, - {file = "pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044"}, -] - -[package.extras] -dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] - -[[package]] -name = "tomli" -version = "2.2.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version < \"3.11\"" -files = [ - {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, - {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, - {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, - {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, - {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, - {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, - {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, - {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, - {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, - {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -description = "Backported and Experimental Type Hints for Python 3.9+" -optional = false -python-versions = ">=3.9" +python-versions = ">=3.7" groups = ["dev"] -markers = "python_version < \"3.11\"" files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, + {file = "ruff-0.14.0-py3-none-linux_armv6l.whl", hash = "sha256:58e15bffa7054299becf4bab8a1187062c6f8cafbe9f6e39e0d5aface455d6b3"}, + {file = "ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:838d1b065f4df676b7c9957992f2304e41ead7a50a568185efd404297d5701e8"}, + {file = "ruff-0.14.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:703799d059ba50f745605b04638fa7e9682cc3da084b2092feee63500ff3d9b8"}, + {file = "ruff-0.14.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ba9a8925e90f861502f7d974cc60e18ca29c72bb0ee8bfeabb6ade35a3abde7"}, + {file = "ruff-0.14.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e41f785498bd200ffc276eb9e1570c019c1d907b07cfb081092c8ad51975bbe7"}, + {file = "ruff-0.14.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30a58c087aef4584c193aebf2700f0fbcfc1e77b89c7385e3139956fa90434e2"}, + {file = "ruff-0.14.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f8d07350bc7af0a5ce8812b7d5c1a7293cf02476752f23fdfc500d24b79b783c"}, + {file = "ruff-0.14.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eec3bbbf3a7d5482b5c1f42d5fc972774d71d107d447919fca620b0be3e3b75e"}, + {file = "ruff-0.14.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16b68e183a0e28e5c176d51004aaa40559e8f90065a10a559176713fcf435206"}, + {file = "ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb732d17db2e945cfcbbc52af0143eda1da36ca8ae25083dd4f66f1542fdf82e"}, + {file = "ruff-0.14.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c958f66ab884b7873e72df38dcabee03d556a8f2ee1b8538ee1c2bbd619883dd"}, + {file = "ruff-0.14.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7eb0499a2e01f6e0c285afc5bac43ab380cbfc17cd43a2e1dd10ec97d6f2c42d"}, + {file = "ruff-0.14.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4c63b2d99fafa05efca0ab198fd48fa6030d57e4423df3f18e03aa62518c565f"}, + {file = "ruff-0.14.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:668fce701b7a222f3f5327f86909db2bbe99c30877c8001ff934c5413812ac02"}, + {file = "ruff-0.14.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a86bf575e05cb68dcb34e4c7dfe1064d44d3f0c04bbc0491949092192b515296"}, + {file = "ruff-0.14.0-py3-none-win32.whl", hash = "sha256:7450a243d7125d1c032cb4b93d9625dea46c8c42b4f06c6b709baac168e10543"}, + {file = "ruff-0.14.0-py3-none-win_amd64.whl", hash = "sha256:ea95da28cd874c4d9c922b39381cbd69cb7e7b49c21b8152b014bd4f52acddc2"}, + {file = "ruff-0.14.0-py3-none-win_arm64.whl", hash = "sha256:f42c9495f5c13ff841b1da4cb3c2a42075409592825dada7c5885c2c844ac730"}, + {file = "ruff-0.14.0.tar.gz", hash = "sha256:62ec8969b7510f77945df916de15da55311fade8d6050995ff7f680afe582c57"}, ] [[package]] @@ -414,4 +243,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<=3.11.13" -content-hash = "db76b1120c727c70e551fdf885e3eb4ee6a99b0c17e332662ca8b1c1d39aa63d" +content-hash = "00b34c6ef4bbb6631da3cae4a0e30fac48fc2fb133d33920e09f66a7851e8225" diff --git a/pyproject.toml b/pyproject.toml index 56a5d28c..673ad410 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,9 +10,58 @@ python = ">=3.9,<=3.11.13" pydal = "~20200714.1" [tool.poetry.group.dev.dependencies] -black = "^25.9.0" pdoc3 = "^0.11.6" +ruff = "^0.14.0" [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" + +[tool.ruff] +target-version = "py310" + +# https://docs.astral.sh/ruff/settings/#analyze +[tool.ruff.analyze] +detect-string-imports = false +direction = "dependencies" +exclude = [] +include-dependencies = {} +preview = false +string-imports-min-dots = 2 + +# https://docs.astral.sh/ruff/settings/#format +[tool.ruff.format] +docstring-code-format = true +docstring-code-line-length = "dynamic" +exclude = [] +indent-style = "space" +line-ending = "lf" +preview = false +quote-style = "double" +skip-magic-trailing-comma = false + +# https://docs.astral.sh/ruff/settings/#lint +[tool.ruff.lint] +allowed-confusables = [] +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +exclude = [] +explicit-preview-rules = false +extend-fixable = [] +extend-ignore = [] +extend-per-file-ignores = {} +extend-safe-fixes = [] +extend-select = [] +extend-unsafe-fixes = [] +external = [] +fixable = ["ALL"] +future-annotations = false +ignore = [] +ignore-init-module-imports = true +logger-objects = [] +per-file-ignores = {} +preview = false +select = ["E4", "E7", "E9", "F"] +task-tags = ["TODO", "FIXME", "XXX"] +typing-extensions = true +typing-modules = [] +unfixable = [] diff --git a/pytm/flows.py b/pytm/flows.py index a1e882e4..b05c0cab 100644 --- a/pytm/flows.py +++ b/pytm/flows.py @@ -3,7 +3,7 @@ def req_reply(src: Element, dest: Element, req_name: str, reply_name=None) -> (DF, DF): - ''' + """ This function creates two datflows where one dataflow is a request and the second dataflow is the corresponding reply to the newly created request. @@ -22,9 +22,9 @@ def req_reply(src: Element, dest: Element, req_name: str, reply_name=None) -> (D Returns: a tuple of two dataflows, where the first is the request and the second is the reply. - ''' + """ if not reply_name: - reply_name = f'Reply to {req_name}' + reply_name = f"Reply to {req_name}" req = DF(src, dest, req_name) reply = DF(dest, src, name=reply_name) reply.responseTo = req @@ -32,7 +32,7 @@ def req_reply(src: Element, dest: Element, req_name: str, reply_name=None) -> (D def reply(req: DF, **kwargs) -> DF: - ''' + """ This function takes a dataflow as an argument and returns a new dataflow, which is a response to the given dataflow. Args: @@ -45,12 +45,12 @@ def reply(req: DF, **kwargs) -> DF: client_reply = reply(client_query) Returns: a Dataflow which is a reply to the given datadlow req - ''' - if 'name' not in kwargs: - name = f'Reply to {req.name}' + """ + if "name" not in kwargs: + name = f"Reply to {req.name}" else: - name = kwargs['name'] - del kwargs['name'] + name = kwargs["name"] + del kwargs["name"] reply = DF(req.sink, req.source, name, **kwargs) reply.responseTo = req return req, reply diff --git a/pytm/json.py b/pytm/json.py index db0c7af7..d7133b5c 100644 --- a/pytm/json.py +++ b/pytm/json.py @@ -4,17 +4,11 @@ from .pytm import ( TM, Boundary, - Element, Dataflow, Server, - ExternalEntity, Datastore, Actor, - Process, - SetOfProcesses, Action, - Lambda, - Controls, ) diff --git a/pytm/pytm.py b/pytm/pytm.py index a7116878..c9f955bc 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -142,8 +142,9 @@ class varElement(var): def __set__(self, instance, value): if not isinstance(value, Element): raise ValueError( - "expecting an Element (or inherited) " - "value, got a {}".format(type(value)) + "expecting an Element (or inherited) value, got a {}".format( + type(value) + ) ) super().__set__(instance, value) @@ -262,9 +263,7 @@ def __str__(self): class varControls(var): def __set__(self, instance, value): if not isinstance(value, Controls): - raise ValueError( - f"expecting an Controls value, got a {type(value)}" - ) + raise ValueError(f"expecting an Controls value, got a {type(value)}") super().__set__(instance, value) @@ -283,9 +282,7 @@ def __set__(self, instance, value): class varAssumption(var): def __set__(self, instance, value): if not isinstance(value, Assumption): - raise ValueError( - f"expecting an Assumption value, got a {type(value)}" - ) + raise ValueError(f"expecting an Assumption value, got a {type(value)}") super().__set__(instance, value) @@ -560,7 +557,7 @@ def _describe_classes(classes): docs.append("required") if attr.default or isinstance(attr.default, bool): docs.append("default: {}".format(attr.default)) - lpadding = f'\n{" ":<{longest+2}}' + lpadding = f"\n{' ':<{longest + 2}}" print(f" {i:<{longest}}{lpadding.join(docs)}") print() @@ -687,7 +684,11 @@ class Finding: threat_id = varString("", required=True, doc="Threat ID") references = varString("", required=True, doc="Threat references") condition = varString("", required=True, doc="Threat condition") - assumption = varAssumption(None, required=False, doc="The assumption, that caused this finding to be excluded") + assumption = varAssumption( + None, + required=False, + doc="The assumption, that caused this finding to be excluded", + ) response = varString( "", required=False, @@ -799,7 +800,7 @@ class TM: excluded_findings = varFindings( [], doc="Threats found for elements of this model, " - "that were excluded on a per-element basis, using the Assumptions class" + "that were excluded on a per-element basis, using the Assumptions class", ) onDuplicates = varAction( Action.NO_ACTION, @@ -845,7 +846,9 @@ def _add_threats(self): raise UIError( e, f"while trying to open the the threat file ({self.threatsFile})." ) - active_threats = (threat for threat in threats_json if "DEPRECATED" not in threat) + active_threats = ( + threat for threat in threats_json if "DEPRECATED" not in threat + ) for threat in active_threats: TM._threats.append(Threat(**threat)) @@ -883,7 +886,12 @@ def resolve(self): for assumption in e.assumptions + global_assumptions: # type: Assumption if t.id in assumption.exclude: excluded_finding_count += 1 - f = Finding(e, id=str(excluded_finding_count), threat=t, assumption=assumption) + f = Finding( + e, + id=str(excluded_finding_count), + threat=t, + assumption=assumption, + ) excluded_findings.append(f) _continue = True break @@ -1223,9 +1231,10 @@ def sqlDump(self, filename): from pydal import DAL, Field except ImportError as e: raise UIError( - e, """This feature requires the pyDAL package, + e, + """This feature requires the pyDAL package, Please install the package via pip or your packagemanger of choice. - """ + """, ) @lru_cache(maxsize=None) @@ -1278,7 +1287,6 @@ def get_table(db, klass): db.close() - class Controls: """Controls implemented by/on and Element""" @@ -1383,8 +1391,12 @@ class Assumption: Assumption used by an Element. Used to exclude threats on a per-element basis. """ + name = varString("", required=True) - exclude = varStrings([], doc="A list of threat SIDs to exclude for this assumption. For example: INP01") + exclude = varStrings( + [], + doc="A list of threat SIDs to exclude for this assumption. For example: INP01", + ) description = varString("", doc="An additional description of the assumption") def __init__(self, name, **kwargs): diff --git a/pytm/report_util.py b/pytm/report_util.py index 90df7de6..2d82d775 100644 --- a/pytm/report_util.py +++ b/pytm/report_util.py @@ -1,39 +1,53 @@ - class ReportUtils: @staticmethod def getParentName(element): from pytm import Boundary - if (isinstance(element, Boundary)): + + if isinstance(element, Boundary): parent = element.inBoundary - if (parent is not None): + if parent is not None: return parent.name else: return str("") else: - return "ERROR: getParentName method is not valid for " + element.__class__.__name__ - + return ( + "ERROR: getParentName method is not valid for " + + element.__class__.__name__ + ) @staticmethod def getNamesOfParents(element): from pytm import Boundary - if (isinstance(element, Boundary)): - parents = [p.name for p in element.parents()] - return parents + + if isinstance(element, Boundary): + parents = [p.name for p in element.parents()] + return parents else: - return "ERROR: getNamesOfParents method is not valid for " + element.__class__.__name__ + return ( + "ERROR: getNamesOfParents method is not valid for " + + element.__class__.__name__ + ) @staticmethod def getFindingCount(element): from pytm import Element - if (isinstance(element, Element)): + + if isinstance(element, Element): return str(len(list(element.findings))) else: - return "ERROR: getFindingCount method is not valid for " + element.__class__.__name__ + return ( + "ERROR: getFindingCount method is not valid for " + + element.__class__.__name__ + ) @staticmethod def getElementType(element): from pytm import Element - if (isinstance(element, Element)): + + if isinstance(element, Element): return str(element.__class__.__name__) else: - return "ERROR: getElementType method is not valid for " + element.__class__.__name__ + return ( + "ERROR: getElementType method is not valid for " + + element.__class__.__name__ + ) diff --git a/pytm/template_engine.py b/pytm/template_engine.py index 9118a5d4..aa7bbae5 100644 --- a/pytm/template_engine.py +++ b/pytm/template_engine.py @@ -9,11 +9,10 @@ class SuperFormatter(string.Formatter): """World's simplest Template engine.""" def format_field(self, value, spec): - spec_parts = spec.split(":") if spec.startswith("repeat"): - # Example usage, format, count of spec_parts, exampple format - # object:repeat:template 2 {item.findings:repeat:{{item.id}}, } + # Example usage, format, count of spec_parts, exampple format + # object:repeat:template 2 {item.findings:repeat:{{item.id}}, } template = spec.partition(":")[-1] if type(value) is dict: @@ -21,9 +20,9 @@ def format_field(self, value, spec): return "".join([self.format(template, item=item) for item in value]) elif spec.startswith("call:") and hasattr(value, "__call__"): - # Example usage, format, exampple format - # methood:call {item.display_name:call:} - # methood:call:template {item.parents:call:{{item.name}}, } + # Example usage, format, exampple format + # methood:call {item.display_name:call:} + # methood:call:template {item.parents:call:{{item.name}}, } result = value() if type(result) is list: @@ -33,11 +32,11 @@ def format_field(self, value, spec): return result elif spec.startswith("call:"): - # Example usage, format, exampple format - # object:call:method_name {item:call:getFindingCount} - # object:call:method_name:template {item:call:getNamesOfParents: - # {{item}} - # } + # Example usage, format, exampple format + # object:call:method_name {item:call:getFindingCount} + # object:call:method_name:template {item:call:getNamesOfParents: + # {{item}} + # } method_name = spec_parts[1] @@ -50,26 +49,26 @@ def format_field(self, value, spec): return result - elif (spec.startswith("if") or spec.startswith("not")): - # Example usage, format, exampple format - # object.bool:if:template {item.inScope:if:Is in scope} - # object:if:template {item.findings:if:Has Findings} - # object.method:if:template {item.parents:if:Has Parents} - # - # object.bool:not:template {item.inScope:not:Is not in scope} - # object:not:template {item.findings:not:Has No Findings} - # object.method:not:template {item.parents:not:Has No Parents} - + elif spec.startswith("if") or spec.startswith("not"): + # Example usage, format, exampple format + # object.bool:if:template {item.inScope:if:Is in scope} + # object:if:template {item.findings:if:Has Findings} + # object.method:if:template {item.parents:if:Has Parents} + # + # object.bool:not:template {item.inScope:not:Is not in scope} + # object:not:template {item.findings:not:Has No Findings} + # object.method:not:template {item.parents:not:Has No Parents} + template = spec.partition(":")[-1] - if (hasattr(value, "__call__")): + if hasattr(value, "__call__"): result = value() else: result = value - if (spec.startswith("if")): - return (result and template or "") - else: - return (not result and template or "") + if spec.startswith("if"): + return result and template or "" + else: + return not result and template or "" else: return super(SuperFormatter, self).format_field(value, spec) @@ -77,7 +76,7 @@ def format_field(self, value, spec): def call_util_method(self, method_name, object): module_name = "pytm.report_util" klass_name = "ReportUtils" - module = __import__(module_name, fromlist=['ReportUtils']) + module = __import__(module_name, fromlist=["ReportUtils"]) klass = getattr(module, klass_name) method = getattr(klass, method_name) diff --git a/tests/test_private_func.py b/tests/test_private_func.py index 4ce0816e..8ada296f 100644 --- a/tests/test_private_func.py +++ b/tests/test_private_func.py @@ -86,7 +86,7 @@ def test_defaults(self): tm = TM("TM") user_data = Data("HTTP") user = Actor("User", data=user_data) - user.controls.authenticatesDestination=True + user.controls.authenticatesDestination = True json_data = Data("JSON") server = Server( @@ -99,7 +99,7 @@ def test_defaults(self): protocol="PostgreSQL", data=sql_resp, ) - db.controls.isEncrypted=False + db.controls.isEncrypted = False db.type = DatastoreType.SQL worker = Process("Task queue worker") @@ -110,7 +110,9 @@ def test_defaults(self): result_data = Data("Results") result = Dataflow(db, server, "Results", data=result_data, isResponse=True) resp_get_data = Data("HTTP Response") - resp_get = Dataflow(server, user, "HTTP Response", data=resp_get_data, isResponse=True) + resp_get = Dataflow( + server, user, "HTTP Response", data=resp_get_data, isResponse=True + ) test_assumption = Assumption("test assumption") resp_get.assumptions = [test_assumption] @@ -118,9 +120,11 @@ def test_defaults(self): req_post_data = Data("JSON") req_post = Dataflow(user, server, "HTTP POST", data=req_post_data) resp_post = Dataflow(server, user, "HTTP Response", isResponse=True) - test_assumption_exclude = Assumption("test assumption", exclude=["ABCD", "BCDE"]) + test_assumption_exclude = Assumption( + "test assumption", exclude=["ABCD", "BCDE"] + ) resp_post.assumptions = [test_assumption_exclude] - + sql_data = Data("SQL") worker_query = Dataflow(worker, db, "Query", data=sql_data) Dataflow(db, worker, "Results", isResponse=True) @@ -133,7 +137,8 @@ def test_defaults(self): self.assertEqual(req_get.dstPort, server.port) self.assertEqual(req_get.controls.isEncrypted, server.controls.isEncrypted) self.assertEqual( - req_get.controls.authenticatesDestination, user.controls.authenticatesDestination + req_get.controls.authenticatesDestination, + user.controls.authenticatesDestination, ) self.assertEqual(req_get.protocol, server.protocol) self.assertTrue(user.data.issubset(req_get.data)) @@ -142,7 +147,8 @@ def test_defaults(self): self.assertEqual(server_query.dstPort, db.port) self.assertEqual(server_query.controls.isEncrypted, db.controls.isEncrypted) self.assertEqual( - server_query.controls.authenticatesDestination, server.controls.authenticatesDestination + server_query.controls.authenticatesDestination, + server.controls.authenticatesDestination, ) self.assertEqual(server_query.protocol, db.protocol) self.assertTrue(server.data.issubset(server_query.data)) @@ -167,7 +173,8 @@ def test_defaults(self): self.assertEqual(req_post.dstPort, server.port) self.assertEqual(req_post.controls.isEncrypted, server.controls.isEncrypted) self.assertEqual( - req_post.controls.authenticatesDestination, user.controls.authenticatesDestination + req_post.controls.authenticatesDestination, + user.controls.authenticatesDestination, ) self.assertEqual(req_post.protocol, server.protocol) self.assertTrue(user.data.issubset(req_post.data)) @@ -179,7 +186,9 @@ def test_defaults(self): self.assertEqual(resp_post.protocol, server.protocol) self.assertTrue(server.data.issubset(resp_post.data)) self.assertListEqual(resp_post.assumptions, [test_assumption_exclude]) - self.assertSetEqual(resp_post.assumptions[0].exclude, test_assumption_exclude.exclude) + self.assertSetEqual( + resp_post.assumptions[0].exclude, test_assumption_exclude.exclude + ) self.assertListEqual(server.inputs, [req_get, req_post]) self.assertListEqual(server.outputs, [server_query]) @@ -268,7 +277,7 @@ def test_encode_threat_data(self): cvss="1.234", response="A test response", assumption=Assumption("Test Assumption", exclude=["INP02"]), - ) + ), ] encoded_findings = encode_threat_data(findings) @@ -279,7 +288,9 @@ def test_encode_threat_data(self): self.assertEqual(encoded_findings[0].threat_id, "INP01") self.assertEqual(encoded_findings[0].cvss, "9.876") self.assertEqual(encoded_findings[0].response, "A test response") - self.assertEqual(encoded_findings[1].description, "An escape test <script>") + self.assertEqual( + encoded_findings[1].description, "An escape test <script>" + ) self.assertEqual(encoded_findings[1].severity, "Medium") self.assertEqual(encoded_findings[1].id, "2") self.assertEqual(encoded_findings[1].threat_id, "INP02") diff --git a/tests/test_pytmfunc.py b/tests/test_pytmfunc.py index f94d042c..c2f37d8b 100644 --- a/tests/test_pytmfunc.py +++ b/tests/test_pytmfunc.py @@ -4,7 +4,6 @@ import re import unittest import tempfile -from contextlib import redirect_stdout from pytm import ( pytm, @@ -36,7 +35,8 @@ ) as threat_file: threats = {t["SID"]: Threat(**t) for t in json.load(threat_file)} -output_path=tempfile.gettempdir() +output_path = tempfile.gettempdir() + class TestTM(unittest.TestCase): def test_seq(self): @@ -659,7 +659,6 @@ def test_DS01(self): self.assertTrue(threat.apply(web)) def test_DE01(self): - with self.subTest("Default case"): user = Actor("User") web = Server("Web Server") diff --git a/tm.py b/tm.py index 9f0cb464..cd6fdc4c 100755 --- a/tm.py +++ b/tm.py @@ -134,4 +134,3 @@ if __name__ == "__main__": tm.process() -