diff --git a/.github/linters/.mypy.ini b/.github/linters/.mypy.ini index 7be7ce15..f67c7a3e 100644 --- a/.github/linters/.mypy.ini +++ b/.github/linters/.mypy.ini @@ -2,3 +2,4 @@ disallow_incomplete_defs = true disallow_untyped_defs = true ignore_missing_imports = true +exclude = ["tests/data"] diff --git a/.github/linters/.ruff.toml b/.github/linters/.ruff.toml index 2e1a26ea..684caa68 100644 --- a/.github/linters/.ruff.toml +++ b/.github/linters/.ruff.toml @@ -89,6 +89,8 @@ ignore = [ "TRY003", ] +extend-exclude = ["tests/data"] + [per-file-ignores] "*test*.py" = [ # Undocumented declarations diff --git a/.gitignore b/.gitignore index 49da5c37..2f5b3ba8 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ __pycache__/ venv/ # Pytest outputs +.mypy_cache/ .pytest_cache/ htmlcov/ .coverage diff --git a/.mega-linter.yml b/.mega-linter.yml index f1e96608..7b03cfab 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -3,7 +3,7 @@ EXTENDS: https://raw.githubusercontent.com/lars-reimann/.github/main/.mega-linter.yml # Config -FILTER_REGEX_EXCLUDE: (\.github/workflows/|mkdocs.yml) +FILTER_REGEX_EXCLUDE: (\.github/workflows/|mkdocs.yml|tests/data) # Workaround to also run prettier on other supported file types. We deactivate it for Markdown compared to the extended # configuration since it breaks admonitions of Material for MkDocs. diff --git a/poetry.lock b/poetry.lock index 121abfd6..d1441b48 100644 --- a/poetry.lock +++ b/poetry.lock @@ -213,6 +213,17 @@ files = [ [package.extras] toml = ["tomli"] +[[package]] +name = "docstring-parser" +version = "0.15" +description = "Parse Python docstrings in reST, Google and Numpydoc format" +optional = false +python-versions = ">=3.6,<4.0" +files = [ + {file = "docstring_parser-0.15-py3-none-any.whl", hash = "sha256:d1679b86250d269d06a99670924d6bce45adc00b08069dae8c47d98e89b667a9"}, + {file = "docstring_parser-0.15.tar.gz", hash = "sha256:48ddc093e8b1865899956fcc03b03e66bb7240c310fac5af81814580c55bf682"}, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -311,6 +322,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -649,6 +670,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -656,8 +678,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -674,6 +703,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -681,6 +711,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -702,99 +733,99 @@ pyyaml = "*" [[package]] name = "regex" -version = "2023.8.8" +version = "2023.10.3" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "regex-2023.8.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88900f521c645f784260a8d346e12a1590f79e96403971241e64c3a265c8ecdb"}, - {file = "regex-2023.8.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3611576aff55918af2697410ff0293d6071b7e00f4b09e005d614686ac4cd57c"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8a0ccc8f2698f120e9e5742f4b38dc944c38744d4bdfc427616f3a163dd9de5"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c662a4cbdd6280ee56f841f14620787215a171c4e2d1744c9528bed8f5816c96"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf0633e4a1b667bfe0bb10b5e53fe0d5f34a6243ea2530eb342491f1adf4f739"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551ad543fa19e94943c5b2cebc54c73353ffff08228ee5f3376bd27b3d5b9800"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54de2619f5ea58474f2ac211ceea6b615af2d7e4306220d4f3fe690c91988a61"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ec4b3f0aebbbe2fc0134ee30a791af522a92ad9f164858805a77442d7d18570"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ae646c35cb9f820491760ac62c25b6d6b496757fda2d51be429e0e7b67ae0ab"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca339088839582d01654e6f83a637a4b8194d0960477b9769d2ff2cfa0fa36d2"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d9b6627408021452dcd0d2cdf8da0534e19d93d070bfa8b6b4176f99711e7f90"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:bd3366aceedf274f765a3a4bc95d6cd97b130d1dda524d8f25225d14123c01db"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7aed90a72fc3654fba9bc4b7f851571dcc368120432ad68b226bd593f3f6c0b7"}, - {file = "regex-2023.8.8-cp310-cp310-win32.whl", hash = "sha256:80b80b889cb767cc47f31d2b2f3dec2db8126fbcd0cff31b3925b4dc6609dcdb"}, - {file = "regex-2023.8.8-cp310-cp310-win_amd64.whl", hash = "sha256:b82edc98d107cbc7357da7a5a695901b47d6eb0420e587256ba3ad24b80b7d0b"}, - {file = "regex-2023.8.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e7d84d64c84ad97bf06f3c8cb5e48941f135ace28f450d86af6b6512f1c9a71"}, - {file = "regex-2023.8.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce0f9fbe7d295f9922c0424a3637b88c6c472b75eafeaff6f910494a1fa719ef"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06c57e14ac723b04458df5956cfb7e2d9caa6e9d353c0b4c7d5d54fcb1325c46"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7a9aaa5a1267125eef22cef3b63484c3241aaec6f48949b366d26c7250e0357"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b7408511fca48a82a119d78a77c2f5eb1b22fe88b0d2450ed0756d194fe7a9a"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14dc6f2d88192a67d708341f3085df6a4f5a0c7b03dec08d763ca2cd86e9f559"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c640b99213643d141550326f34f0502fedb1798adb3c9eb79650b1ecb2f177"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0085da0f6c6393428bf0d9c08d8b1874d805bb55e17cb1dfa5ddb7cfb11140bf"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:964b16dcc10c79a4a2be9f1273fcc2684a9eedb3906439720598029a797b46e6"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7ce606c14bb195b0e5108544b540e2c5faed6843367e4ab3deb5c6aa5e681208"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:40f029d73b10fac448c73d6eb33d57b34607f40116e9f6e9f0d32e9229b147d7"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3b8e6ea6be6d64104d8e9afc34c151926f8182f84e7ac290a93925c0db004bfd"}, - {file = "regex-2023.8.8-cp311-cp311-win32.whl", hash = "sha256:942f8b1f3b223638b02df7df79140646c03938d488fbfb771824f3d05fc083a8"}, - {file = "regex-2023.8.8-cp311-cp311-win_amd64.whl", hash = "sha256:51d8ea2a3a1a8fe4f67de21b8b93757005213e8ac3917567872f2865185fa7fb"}, - {file = "regex-2023.8.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e951d1a8e9963ea51efd7f150450803e3b95db5939f994ad3d5edac2b6f6e2b4"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704f63b774218207b8ccc6c47fcef5340741e5d839d11d606f70af93ee78e4d4"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22283c769a7b01c8ac355d5be0715bf6929b6267619505e289f792b01304d898"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91129ff1bb0619bc1f4ad19485718cc623a2dc433dff95baadbf89405c7f6b57"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de35342190deb7b866ad6ba5cbcccb2d22c0487ee0cbb251efef0843d705f0d4"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b993b6f524d1e274a5062488a43e3f9f8764ee9745ccd8e8193df743dbe5ee61"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3026cbcf11d79095a32d9a13bbc572a458727bd5b1ca332df4a79faecd45281c"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:293352710172239bf579c90a9864d0df57340b6fd21272345222fb6371bf82b3"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d909b5a3fff619dc7e48b6b1bedc2f30ec43033ba7af32f936c10839e81b9217"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3d370ff652323c5307d9c8e4c62efd1956fb08051b0e9210212bc51168b4ff56"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:b076da1ed19dc37788f6a934c60adf97bd02c7eea461b73730513921a85d4235"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e9941a4ada58f6218694f382e43fdd256e97615db9da135e77359da257a7168b"}, - {file = "regex-2023.8.8-cp36-cp36m-win32.whl", hash = "sha256:a8c65c17aed7e15a0c824cdc63a6b104dfc530f6fa8cb6ac51c437af52b481c7"}, - {file = "regex-2023.8.8-cp36-cp36m-win_amd64.whl", hash = "sha256:aadf28046e77a72f30dcc1ab185639e8de7f4104b8cb5c6dfa5d8ed860e57236"}, - {file = "regex-2023.8.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:423adfa872b4908843ac3e7a30f957f5d5282944b81ca0a3b8a7ccbbfaa06103"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ae594c66f4a7e1ea67232a0846649a7c94c188d6c071ac0210c3e86a5f92109"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e51c80c168074faa793685656c38eb7a06cbad7774c8cbc3ea05552d615393d8"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09b7f4c66aa9d1522b06e31a54f15581c37286237208df1345108fcf4e050c18"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e73e5243af12d9cd6a9d6a45a43570dbe2e5b1cdfc862f5ae2b031e44dd95a8"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:941460db8fe3bd613db52f05259c9336f5a47ccae7d7def44cc277184030a116"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f0ccf3e01afeb412a1a9993049cb160d0352dba635bbca7762b2dc722aa5742a"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e9216e0d2cdce7dbc9be48cb3eacb962740a09b011a116fd7af8c832ab116ca"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5cd9cd7170459b9223c5e592ac036e0704bee765706445c353d96f2890e816c8"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4873ef92e03a4309b3ccd8281454801b291b689f6ad45ef8c3658b6fa761d7ac"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:239c3c2a339d3b3ddd51c2daef10874410917cd2b998f043c13e2084cb191684"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1005c60ed7037be0d9dea1f9c53cc42f836188227366370867222bda4c3c6bd7"}, - {file = "regex-2023.8.8-cp37-cp37m-win32.whl", hash = "sha256:e6bd1e9b95bc5614a7a9c9c44fde9539cba1c823b43a9f7bc11266446dd568e3"}, - {file = "regex-2023.8.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9a96edd79661e93327cfeac4edec72a4046e14550a1d22aa0dd2e3ca52aec921"}, - {file = "regex-2023.8.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2181c20ef18747d5f4a7ea513e09ea03bdd50884a11ce46066bb90fe4213675"}, - {file = "regex-2023.8.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a2ad5add903eb7cdde2b7c64aaca405f3957ab34f16594d2b78d53b8b1a6a7d6"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9233ac249b354c54146e392e8a451e465dd2d967fc773690811d3a8c240ac601"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920974009fb37b20d32afcdf0227a2e707eb83fe418713f7a8b7de038b870d0b"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2b6c5dfe0929b6c23dde9624483380b170b6e34ed79054ad131b20203a1a63"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96979d753b1dc3b2169003e1854dc67bfc86edf93c01e84757927f810b8c3c93"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ae54a338191e1356253e7883d9d19f8679b6143703086245fb14d1f20196be9"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2162ae2eb8b079622176a81b65d486ba50b888271302190870b8cc488587d280"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c884d1a59e69e03b93cf0dfee8794c63d7de0ee8f7ffb76e5f75be8131b6400a"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf9273e96f3ee2ac89ffcb17627a78f78e7516b08f94dc435844ae72576a276e"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:83215147121e15d5f3a45d99abeed9cf1fe16869d5c233b08c56cdf75f43a504"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f7454aa427b8ab9101f3787eb178057c5250478e39b99540cfc2b889c7d0586"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0640913d2c1044d97e30d7c41728195fc37e54d190c5385eacb52115127b882"}, - {file = "regex-2023.8.8-cp38-cp38-win32.whl", hash = "sha256:0c59122ceccb905a941fb23b087b8eafc5290bf983ebcb14d2301febcbe199c7"}, - {file = "regex-2023.8.8-cp38-cp38-win_amd64.whl", hash = "sha256:c12f6f67495ea05c3d542d119d270007090bad5b843f642d418eb601ec0fa7be"}, - {file = "regex-2023.8.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82cd0a69cd28f6cc3789cc6adeb1027f79526b1ab50b1f6062bbc3a0ccb2dbc3"}, - {file = "regex-2023.8.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bb34d1605f96a245fc39790a117ac1bac8de84ab7691637b26ab2c5efb8f228c"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:987b9ac04d0b38ef4f89fbc035e84a7efad9cdd5f1e29024f9289182c8d99e09"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dd6082f4e2aec9b6a0927202c85bc1b09dcab113f97265127c1dc20e2e32495"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7eb95fe8222932c10d4436e7a6f7c99991e3fdd9f36c949eff16a69246dee2dc"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7098c524ba9f20717a56a8d551d2ed491ea89cbf37e540759ed3b776a4f8d6eb"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b694430b3f00eb02c594ff5a16db30e054c1b9589a043fe9174584c6efa8033"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2aeab3895d778155054abea5238d0eb9a72e9242bd4b43f42fd911ef9a13470"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:988631b9d78b546e284478c2ec15c8a85960e262e247b35ca5eaf7ee22f6050a"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:67ecd894e56a0c6108ec5ab1d8fa8418ec0cff45844a855966b875d1039a2e34"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:14898830f0a0eb67cae2bbbc787c1a7d6e34ecc06fbd39d3af5fe29a4468e2c9"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f2200e00b62568cfd920127782c61bc1c546062a879cdc741cfcc6976668dfcf"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9691a549c19c22d26a4f3b948071e93517bdf86e41b81d8c6ac8a964bb71e5a6"}, - {file = "regex-2023.8.8-cp39-cp39-win32.whl", hash = "sha256:6ab2ed84bf0137927846b37e882745a827458689eb969028af8032b1b3dac78e"}, - {file = "regex-2023.8.8-cp39-cp39-win_amd64.whl", hash = "sha256:5543c055d8ec7801901e1193a51570643d6a6ab8751b1f7dd9af71af467538bb"}, - {file = "regex-2023.8.8.tar.gz", hash = "sha256:fcbdc5f2b0f1cd0f6a56cdb46fe41d2cce1e644e3b68832f3eeebc5fb0f7712e"}, + {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, + {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, + {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, + {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, + {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, + {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, + {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, + {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, + {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, + {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, + {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, + {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, + {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, + {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, + {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, + {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, ] [[package]] @@ -818,6 +849,22 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "setuptools" +version = "68.2.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "six" version = "1.16.0" @@ -829,6 +876,20 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "syrupy" +version = "4.5.0" +description = "Pytest Snapshot Test Utility" +optional = false +python-versions = ">=3.8.1,<4" +files = [ + {file = "syrupy-4.5.0-py3-none-any.whl", hash = "sha256:ea6a237ef374bacebbdb4049f73bf48e3dda76eabd4621a6d104d43077529de6"}, + {file = "syrupy-4.5.0.tar.gz", hash = "sha256:6e01fccb4cd5ad37ce54e8c265cde068fa9c37b7a0946c603c328e8a38a7330d"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<8.0.0" + [[package]] name = "typing-extensions" version = "4.8.0" @@ -899,4 +960,4 @@ watchmedo = ["PyYAML (>=3.10)"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "070cd4e681e2fb69281ac77e01487966ebd34839e47ccb702ee0fb2f0545029c" +content-hash = "04ee5b19d4106a4837fa7e38c32805f92d80572bf1887a9416f9d97a7c80adcd" diff --git a/pyproject.toml b/pyproject.toml index be9c205b..f6159c94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,10 +18,13 @@ generate-safeds-stubs = "safeds_stubgen.main:main" [tool.poetry.dependencies] python = "^3.11" mypy = "^1.5.1" +docstring-parser = "^0.15" +syrupy = "^4.5.0" [tool.poetry.group.dev.dependencies] pytest = "^7.4.2" pytest-cov = "^4.0.0" +setuptools = "^68.0.0" [tool.poetry.group.docs.dependencies] mkdocs = "^1.5.3" @@ -34,3 +37,4 @@ build-backend = "poetry.core.masonry.api" [tool.black] line-length = 120 + diff --git a/src/safeds_stubgen/__init__.py b/src/safeds_stubgen/__init__.py index e69de29b..8d067eff 100644 --- a/src/safeds_stubgen/__init__.py +++ b/src/safeds_stubgen/__init__.py @@ -0,0 +1,2 @@ +"""Safe-DS stubs generator.""" +from __future__ import annotations diff --git a/src/safeds_stubgen/api_analyzer/__init__.py b/src/safeds_stubgen/api_analyzer/__init__.py new file mode 100644 index 00000000..a4babdd1 --- /dev/null +++ b/src/safeds_stubgen/api_analyzer/__init__.py @@ -0,0 +1,49 @@ +"""API-Analyzer for the Safe-DS stubs generator.""" +from __future__ import annotations + +from ._api import API, Attribute, Class, Function, Parameter, ParameterAssignment +from ._get_api import get_api +from ._mypy_helpers import get_classdef_definitions, get_funcdef_definitions, get_mypyfile_definitions +from ._package_metadata import distribution, distribution_version, package_root +from ._types import ( + AbstractType, + BoundaryType, + DictType, + EnumType, + FinalType, + ListType, + LiteralType, + NamedType, + OptionalType, + SetType, + TupleType, + UnionType, +) + +__all__ = [ + "AbstractType", + "API", + "Attribute", + "BoundaryType", + "Class", + "DictType", + "distribution", + "distribution_version", + "EnumType", + "FinalType", + "Function", + "get_api", + "get_classdef_definitions", + "get_funcdef_definitions", + "get_mypyfile_definitions", + "ListType", + "LiteralType", + "NamedType", + "OptionalType", + "package_root", + "Parameter", + "ParameterAssignment", + "SetType", + "TupleType", + "UnionType", +] diff --git a/src/safeds_stubgen/api_analyzer/_api.py b/src/safeds_stubgen/api_analyzer/_api.py new file mode 100644 index 00000000..201ac5b6 --- /dev/null +++ b/src/safeds_stubgen/api_analyzer/_api.py @@ -0,0 +1,344 @@ +from __future__ import annotations + +import json +from dataclasses import dataclass, field +from enum import Enum as PythonEnum +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from pathlib import Path + + from safeds_stubgen.docstring_parsing import ( + AttributeDocstring, + ClassDocstring, + FunctionDocstring, + ParameterDocstring, + ResultDocstring, + ) + + from ._types import AbstractType + +API_SCHEMA_VERSION = 1 + + +def ensure_file_exists(file: Path) -> None: + """ + Create a file and all parent directories if they don't exist already. + + Parameters + ---------- + file: Path + The file path. + """ + file.parent.mkdir(parents=True, exist_ok=True) + file.touch(exist_ok=True) + + +class API: + def __init__(self, distribution: str, package: str, version: str) -> None: + self.distribution: str = distribution + self.package: str = package + self.version: str = version + self.modules: dict[str, Module] = {} + self.classes: dict[str, Class] = {} + self.functions: dict[str, Function] = {} + self.results: dict[str, Result] = {} + self.enums: dict[str, Enum] = {} + self.enum_instances: dict[str, EnumInstance] = {} + self.attributes_: dict[str, Attribute] = {} + self.parameters_: dict[str, Parameter] = {} + + def add_module(self, module: Module) -> None: + self.modules[module.id] = module + + def add_class(self, class_: Class) -> None: + self.classes[class_.id] = class_ + + def add_function(self, function: Function) -> None: + self.functions[function.id] = function + + def add_enum(self, enum: Enum) -> None: + self.enums[enum.id] = enum + + def add_results(self, results: list[Result]) -> None: + for result in results: + self.results[result.id] = result + + def add_enum_instance(self, enum_instance: EnumInstance) -> None: + self.enum_instances[enum_instance.id] = enum_instance + + def add_attribute(self, attribute: Attribute) -> None: + self.attributes_[attribute.id] = attribute + + def add_parameter(self, parameter: Parameter) -> None: + self.parameters_[parameter.id] = parameter + + def to_json_file(self, path: Path) -> None: + ensure_file_exists(path) + with path.open("w", encoding="utf-8") as f: + json.dump(self.to_dict(), f, indent=2) + + def to_dict(self) -> dict[str, Any]: + return { + "schemaVersion": API_SCHEMA_VERSION, + "distribution": self.distribution, + "package": self.package, + "version": self.version, + "modules": [module.to_dict() for module in sorted(self.modules.values(), key=lambda it: it.id)], + "classes": [class_.to_dict() for class_ in sorted(self.classes.values(), key=lambda it: it.id)], + "functions": [function.to_dict() for function in sorted(self.functions.values(), key=lambda it: it.id)], + "results": [result.to_dict() for result in sorted(self.results.values(), key=lambda it: it.id)], + "enums": [enum.to_dict() for enum in sorted(self.enums.values(), key=lambda it: it.id)], + "enum_instances": [ + enum_instance.to_dict() for enum_instance in sorted(self.enum_instances.values(), key=lambda it: it.id) + ], + "attributes": [ + attribute.to_dict() for attribute in sorted(self.attributes_.values(), key=lambda it: it.id) + ], + "parameters": [ + parameter.to_dict() for parameter in sorted(self.parameters_.values(), key=lambda it: it.id) + ], + } + + +class Module: + def __init__( + self, + id_: str, + name: str, + docstring: str = "", + qualified_imports: list[QualifiedImport] | None = None, + wildcard_imports: list[WildcardImport] | None = None, + ): + self.id: str = id_ + self.name: str = name + self.docstring: str = docstring + self.qualified_imports: list[QualifiedImport] = qualified_imports or [] + self.wildcard_imports: list[WildcardImport] = wildcard_imports or [] + self.classes: list[Class] = [] + self.global_functions: list[Function] = [] + self.enums: list[Enum] = [] + + def to_dict(self) -> dict[str, Any]: + return { + "id": self.id, + "name": self.name, + "docstring": self.docstring, + "qualified_imports": [import_.to_dict() for import_ in self.qualified_imports], + "wildcard_imports": [import_.to_dict() for import_ in self.wildcard_imports], + "classes": [class_.id for class_ in self.classes], + "functions": [function.id for function in self.global_functions], + "enums": [enum.id for enum in self.enums], + } + + def add_class(self, class_: Class) -> None: + self.classes.append(class_) + + def add_function(self, function: Function) -> None: + self.global_functions.append(function) + + def add_enum(self, enum: Enum) -> None: + self.enums.append(enum) + + +@dataclass +class QualifiedImport: + qualified_name: str + alias: str | None = None + + def to_dict(self) -> dict[str, Any]: + return { + "qualified_name": self.qualified_name, + "alias": self.alias, + } + + +@dataclass +class WildcardImport: + module_name: str + + def to_dict(self) -> dict[str, Any]: + return {"module_name": self.module_name} + + +@dataclass +class Class: + id: str + name: str + superclasses: list[Class] + is_public: bool + docstring: ClassDocstring + constructor: Function | None = None + constructor_fulldocstring: str = "" + reexported_by: list[Module] = field(default_factory=list) + attributes: list[Attribute] = field(default_factory=list) + methods: list[Function] = field(default_factory=list) + classes: list[Class] = field(default_factory=list) + + def to_dict(self) -> dict[str, Any]: + return { + "id": self.id, + "name": self.name, + "docstring": self.docstring.to_dict(), + "is_public": self.is_public, + "superclasses": self.superclasses, + "constructor": self.constructor.to_dict() if self.constructor is not None else None, + "reexported_by": [module.id for module in self.reexported_by], + "attributes": [attribute.id for attribute in self.attributes], + "methods": [method.id for method in self.methods], + "classes": [class_.id for class_ in self.classes], + } + + def add_method(self, method: Function) -> None: + self.methods.append(method) + + def add_class(self, class_: Class) -> None: + self.classes.append(class_) + + def add_constructor(self, constructor: Function) -> None: + self.constructor = constructor + + def add_attribute(self, attribute: Attribute) -> None: + self.attributes.append(attribute) + + +@dataclass(frozen=True) +class Attribute: + id: str + name: str + is_public: bool + is_static: bool + type: AbstractType | None + docstring: AttributeDocstring + + def to_dict(self) -> dict[str, Any]: + return { + "id": self.id, + "name": self.name, + "docstring": self.docstring.to_dict(), + "is_public": self.is_public, + "is_static": self.is_static, + "type": self.type.to_dict() if self.type is not None else None, + } + + +@dataclass +class Function: + id: str + name: str + docstring: FunctionDocstring + is_public: bool + is_static: bool + results: list[Result] = field(default_factory=list) + reexported_by: list[Module] = field(default_factory=list) + parameters: list[Parameter] = field(default_factory=list) + + def to_dict(self) -> dict[str, Any]: + return { + "id": self.id, + "name": self.name, + "docstring": self.docstring.to_dict(), + "is_public": self.is_public, + "is_static": self.is_static, + "results": [result.id for result in self.results], + "reexported_by": [module.id for module in self.reexported_by], + "parameters": [parameter.id for parameter in self.parameters], + } + + +@dataclass(frozen=True) +class Parameter: + id: str + name: str + is_optional: bool + default_value: str | bool | int | float | None + assigned_by: ParameterAssignment + docstring: ParameterDocstring + type: AbstractType + + @property + def is_required(self) -> bool: + return self.default_value is None + + @property + def is_variadic(self) -> bool: + return self.assigned_by in ( + ParameterAssignment.POSITIONAL_VARARG, + ParameterAssignment.NAMED_VARARG, + ) + + def to_dict(self) -> dict[str, Any]: + return { + "id": self.id, + "name": self.name, + "docstring": self.docstring.to_dict(), + "is_optional": self.is_optional, + "default_value": self.default_value, + "assigned_by": self.assigned_by.name, + "type": self.type.to_dict(), + } + + +class ParameterAssignment(PythonEnum): + """ + How arguments are assigned to parameters. The parameters must appear exactly in this order in a parameter list. + + IMPLICIT parameters appear on instance methods (usually called "self") and on class methods (usually called "cls"). + POSITION_ONLY parameters precede the "/" in a parameter list. NAME_ONLY parameters follow the "*" or the + POSITIONAL_VARARGS parameter ("*args"). Between the "/" and the "*" the POSITION_OR_NAME parameters reside. Finally, + the parameter list might optionally include a NAMED_VARARG parameter ("**kwargs"). + """ + + IMPLICIT = "IMPLICIT" + POSITION_ONLY = "POSITION_ONLY" + POSITION_OR_NAME = "POSITION_OR_NAME" + POSITIONAL_VARARG = "POSITIONAL_VARARG" + NAME_ONLY = "NAME_ONLY" + NAMED_VARARG = "NAMED_VARARG" + + +@dataclass(frozen=True) +class Result: + id: str + name: str + type: AbstractType | None + docstring: ResultDocstring + + def to_dict(self) -> dict[str, Any]: + return { + "id": self.id, + "name": self.name, + "docstring": self.docstring.to_dict(), + "type": self.type.to_dict() if self.type is not None else None, + } + + +@dataclass +class Enum: + id: str + name: str + docstring: ClassDocstring + instances: list[EnumInstance] = field(default_factory=list) + + def to_dict(self) -> dict[str, Any]: + return { + "id": self.id, + "name": self.name, + "docstring": self.docstring.to_dict(), + "instances": [instance.id for instance in self.instances], + } + + def add_enum_instance(self, enum_instance: EnumInstance) -> None: + self.instances.append(enum_instance) + + +@dataclass(frozen=True) +class EnumInstance: + id: str + name: str + + def to_dict(self) -> dict[str, str]: + return { + "id": self.id, + "name": self.name, + } diff --git a/src/safeds_stubgen/api_analyzer/_ast_visitor.py b/src/safeds_stubgen/api_analyzer/_ast_visitor.py new file mode 100644 index 00000000..2c7295e5 --- /dev/null +++ b/src/safeds_stubgen/api_analyzer/_ast_visitor.py @@ -0,0 +1,634 @@ +from __future__ import annotations + +from types import NoneType +from typing import TYPE_CHECKING + +import mypy.types as mp_types +from mypy.nodes import ( + AssignmentStmt, + CallExpr, + ClassDef, + Expression, + ExpressionStmt, + FuncDef, + Import, + ImportAll, + ImportFrom, + MemberExpr, + MypyFile, + NameExpr, + StrExpr, + TupleExpr, + Var, +) + +import safeds_stubgen.api_analyzer._types as sds_types + +from ._api import ( + API, + Attribute, + Class, + Enum, + EnumInstance, + Function, + Module, + Parameter, + QualifiedImport, + Result, + WildcardImport, +) +from ._mypy_helpers import ( + get_argument_kind, + get_classdef_definitions, + get_mypyfile_definitions, + mypy_type_to_abstract_type, +) + +if TYPE_CHECKING: + from safeds_stubgen.docstring_parsing import AbstractDocstringParser + + +class MyPyAstVisitor: + def __init__(self, docstring_parser: AbstractDocstringParser, api: API) -> None: + self.docstring_parser: AbstractDocstringParser = docstring_parser + self.reexported: dict[str, list[Module]] = {} + self.api: API = api + self.__declaration_stack: list[Module | Class | Function | Enum | list[Attribute | EnumInstance]] = [] + + def enter_moduledef(self, node: MypyFile) -> None: + is_package = node.path.endswith("__init__.py") + + qualified_imports: list[QualifiedImport] = [] + wildcard_imports: list[WildcardImport] = [] + docstring = "" + + # We don't need to check functions, classes and assignments, since the ast walker will already check them + child_definitions = [ + _definition + for _definition in get_mypyfile_definitions(node) + if _definition.__class__.__name__ not in ["FuncDef", "Decorator", "ClassDef", "AssignmentStmt"] + ] + + for definition in child_definitions: + # Imports + if isinstance(definition, Import): + for import_name, import_alias in definition.ids: + qualified_imports.append( + QualifiedImport(import_name, import_alias), + ) + + elif isinstance(definition, ImportFrom): + for import_name, import_alias in definition.names: + qualified_imports.append( + QualifiedImport( + f"{definition.id}.{import_name}", + import_alias, + ), + ) + + elif isinstance(definition, ImportAll): + wildcard_imports.append( + WildcardImport(definition.id), + ) + + # Docstring + elif isinstance(definition, ExpressionStmt) and isinstance(definition.expr, StrExpr): + docstring = definition.expr.value + + # If we are checking a package node.name will be the package name, but since we get import information from + # the __init__.py file we set the name to __init__ + if is_package: + name = "__init__" + else: + name = node.name + id_ = self.__get_id(name) + + # Remember module, so we can later add classes and global functions + module = Module( + id_=id_, + name=name, + docstring=docstring, + qualified_imports=qualified_imports, + wildcard_imports=wildcard_imports, + ) + + if is_package: + self.add_reexports(module) + + self.__declaration_stack.append(module) + + def leave_moduledef(self, _: MypyFile) -> None: + module = self.__declaration_stack.pop() + if not isinstance(module, Module): # pragma: no cover + raise AssertionError("Imbalanced push/pop on stack") # noqa: TRY004 + + self.api.add_module(module) + + def enter_classdef(self, node: ClassDef) -> None: + id_ = self.__get_id(node.name) + name = node.name + + # Get docstring + docstring = self.docstring_parser.get_class_documentation(node) + + # superclasses + # Todo Aliasing: Werden noch nicht aufgelöst + superclasses = [superclass.fullname for superclass in node.base_type_exprs if hasattr(superclass, "fullname")] + + # Get reexported data + reexported_by = self.get_reexported_by(name) + + # Get constructor docstring + definitions = get_classdef_definitions(node) + constructor_fulldocstring = "" + for definition in definitions: + if isinstance(definition, FuncDef) and definition.name == "__init__": + constructor_docstring = self.docstring_parser.get_function_documentation(definition) + constructor_fulldocstring = constructor_docstring.full_docstring + + # Remember class, so we can later add methods + class_ = Class( + id=id_, + name=name, + superclasses=superclasses, + is_public=self.is_public(node.name, name), + docstring=docstring, + reexported_by=reexported_by, + constructor_fulldocstring=constructor_fulldocstring, + ) + self.__declaration_stack.append(class_) + + def leave_classdef(self, _: ClassDef) -> None: + class_ = self.__declaration_stack.pop() + if not isinstance(class_, Class): # pragma: no cover + raise AssertionError("Imbalanced push/pop on stack") # noqa: TRY004 + + if len(self.__declaration_stack) > 0: + parent = self.__declaration_stack[-1] + + if isinstance(parent, Module | Class): + self.api.add_class(class_) + parent.add_class(class_) + + def enter_funcdef(self, node: FuncDef) -> None: + name = node.name + function_id = self.__get_id(name) + + is_public = self.is_public(name, node.fullname) + is_static = node.is_static + + # Get docstring + docstring = self.docstring_parser.get_function_documentation(node) + + # Function args + arguments: list[Parameter] = [] + if getattr(node, "arguments", None) is not None: + arguments = self.parse_parameter_data(node, function_id) + + # Create results + results = self.create_result(node, function_id) + + # Get reexported data + reexported_by = self.get_reexported_by(name) + + # Create and add Function to stack + function = Function( + id=function_id, + name=name, + docstring=docstring, + is_public=is_public, + is_static=is_static, + results=results, + reexported_by=reexported_by, + parameters=arguments, + ) + self.__declaration_stack.append(function) + + def leave_funcdef(self, _: FuncDef) -> None: + function = self.__declaration_stack.pop() + if not isinstance(function, Function): # pragma: no cover + raise AssertionError("Imbalanced push/pop on stack") # noqa: TRY004 + + if len(self.__declaration_stack) > 0: + parent = self.__declaration_stack[-1] + + # Add the data of the function and its results to the API class + self.api.add_function(function) + self.api.add_results(function.results) + + for parameter in function.parameters: + self.api.add_parameter(parameter) + + # Ignore nested functions for now + if isinstance(parent, Module): + parent.add_function(function) + elif isinstance(parent, Class): + if function.name == "__init__": + parent.add_constructor(function) + else: + parent.add_method(function) + + def enter_enumdef(self, node: ClassDef) -> None: + id_ = self.__get_id(node.name) + self.__declaration_stack.append( + Enum( + id=id_, + name=node.name, + docstring=self.docstring_parser.get_class_documentation(node), + ), + ) + + def leave_enumdef(self, _: ClassDef) -> None: + enum = self.__declaration_stack.pop() + if not isinstance(enum, Enum): # pragma: no cover + raise AssertionError("Imbalanced push/pop on stack") # noqa: TRY004 + + if len(self.__declaration_stack) > 0: + parent = self.__declaration_stack[-1] + + # Ignore nested functions for now + if isinstance(parent, Module): + self.api.add_enum(enum) + parent.add_enum(enum) + + def enter_assignmentstmt(self, node: AssignmentStmt) -> None: + # Assignments are attributes or enum instances + parent = self.__declaration_stack[-1] + assignments: list[Attribute | EnumInstance] = [] + + for lvalue in node.lvalues: + if isinstance(parent, Class): + for assignment in self.parse_attributes(lvalue, node.unanalyzed_type, is_static=True): + assignments.append(assignment) + elif isinstance(parent, Function) and parent.name == "__init__": + try: + grand_parent = self.__declaration_stack[-2] + except IndexError: + # If the function has no parent (and is therefore not a class method) ignore the attributes + grand_parent = None + + if grand_parent is not None and isinstance(grand_parent, Class) and not isinstance(lvalue, NameExpr): + # Ignore non instance attributes in __init__ classes + for assignment in self.parse_attributes(lvalue, node.unanalyzed_type, is_static=False): + assignments.append(assignment) + + elif isinstance(parent, Enum): + names = [] + if hasattr(lvalue, "items"): + for item in lvalue.items: + names.append(item.name) + else: + if not hasattr(lvalue, "name"): # pragma: no cover + raise AttributeError("Expected lvalue to have attribtue 'name'.") + names.append(lvalue.name) + + for name in names: + assignments.append( + EnumInstance( + id=f"{parent.id}/{name}", + name=name, + ), + ) + + self.__declaration_stack.append(assignments) + + def leave_assignmentstmt(self, _: AssignmentStmt) -> None: + # Assignments are attributes or enum instances + assignments = self.__declaration_stack.pop() + + if not isinstance(assignments, list): # pragma: no cover + raise AssertionError("Imbalanced push/pop on stack") # noqa: TRY004 + + if len(self.__declaration_stack) > 0: + parent = self.__declaration_stack[-1] + assert isinstance(parent, Function | Class | Enum) + + for assignment in assignments: + if isinstance(assignment, Attribute): + if isinstance(parent, Function): + self.api.add_attribute(assignment) + # Add the attributes to the (grand)parent class + grandparent = self.__declaration_stack[-2] + + if not isinstance(grandparent, Class): # pragma: no cover + raise TypeError(f"Expected 'Class'. Got {grandparent.__class__}.") + + grandparent.add_attribute(assignment) + elif isinstance(parent, Class): + self.api.add_attribute(assignment) + parent.add_attribute(assignment) + + elif isinstance(assignment, EnumInstance): + if isinstance(parent, Enum): + self.api.add_enum_instance(assignment) + parent.add_enum_instance(assignment) + + else: + raise TypeError("Unexpected value type for assignments") + + # ############################## Utilities ############################## # + + # #### Result utilities + + def create_result(self, node: FuncDef, function_id: str) -> list[Result]: + # __init__ functions aren't supposed to have returns, so we can ignore them + if node.name == "__init__": + return [] + + ret_type = None + if getattr(node, "type", None): + node_type = node.type + if node_type is not None and hasattr(node_type, "ret_type"): + node_ret_type = node_type.ret_type + else: # pragma: no cover + raise AttributeError("Result has no return type information.") + if not isinstance(node_ret_type, mp_types.NoneType): + ret_type = mypy_type_to_abstract_type(node_ret_type) + + if ret_type is None: + return [] + + results = [] + + docstring = self.docstring_parser.get_result_documentation(node) + if isinstance(ret_type, sds_types.TupleType): + for i, type_ in enumerate(ret_type.types): + name = f"result_{i + 1}" + results.append( + Result( + id=f"{function_id}/{name}", + type=type_, + name=name, + docstring=docstring, + ), + ) + else: + name = "result_1" + results.append( + Result( + id=f"{function_id}/{name}", + type=ret_type, + name=name, + docstring=docstring, + ), + ) + + return results + + # #### Attribute utilities + + def parse_attributes( + self, + lvalue: Expression, + unanalyzed_type: mp_types.Type | None, + is_static: bool = True, + ) -> list[Attribute]: + assert isinstance(lvalue, NameExpr | MemberExpr | TupleExpr) + attributes: list[Attribute] = [] + + if hasattr(lvalue, "name"): + if self.check_attribute_already_defined(lvalue, lvalue.name): + return attributes + + attributes.append( + self.create_attribute(lvalue, unanalyzed_type, is_static), + ) + + elif hasattr(lvalue, "items"): + lvalues = list(lvalue.items) + for lvalue_ in lvalues: + if not hasattr(lvalue_, "name"): # pragma: no cover + raise AttributeError("Expected value to have attribute 'name'.") + + if self.check_attribute_already_defined(lvalue_, lvalue_.name): + continue + + attributes.append( + self.create_attribute(lvalue_, unanalyzed_type, is_static), + ) + + return attributes + + def check_attribute_already_defined(self, lvalue: Expression, value_name: str) -> bool: + assert isinstance(lvalue, NameExpr | MemberExpr | TupleExpr) + if hasattr(lvalue, "node"): + node = lvalue.node + else: # pragma: no cover + raise AttributeError("Expected value to have attribute 'node'.") + + # If node is None, it's possible that the attribute was already defined once + if node is None: + parent = self.__declaration_stack[-1] + if isinstance(parent, Function): + parent = self.__declaration_stack[-2] + + if not isinstance(parent, Class): # pragma: no cover + raise TypeError("Parent has the wrong class, cannot get attribute values.") + + for attribute in parent.attributes: + if value_name == attribute.name: + return True + + raise ValueError(f"The attribute {value_name} has no value.") + return False + + def create_attribute( + self, + attribute: Expression, + unanalyzed_type: mp_types.Type | None, + is_static: bool, + ) -> Attribute: + if hasattr(attribute, "name"): + name = attribute.name + else: # pragma: no cover + raise AttributeError("Expected attribute to have attribute 'name'.") + qname = getattr(attribute, "fullname", "") + + if hasattr(attribute, "node"): + if not isinstance(attribute.node, Var): # pragma: no cover + raise TypeError("node has wrong type") + + node: Var = attribute.node + else: # pragma: no cover + raise AttributeError("Expected attribute to have attribute 'node'.") + + if qname in (name, "") and node is not None: + qname = node.fullname + + # Check if there is a type hint and get its value + attribute_type = None + if isinstance(attribute, MemberExpr): + # Sometimes the is_inferred value is True even thoght has_explicit_value is False, thus the second check + if not node.is_inferred or (node.is_inferred and not node.has_explicit_value): + attribute_type = node.type + elif isinstance(attribute, NameExpr): + if not node.explicit_self_type and not node.is_inferred: + attribute_type = node.type + + # We need to get the unanalyzed_type for lists, since mypy is not able to check information regarding + # list item types + if ( + attribute_type is not None + and hasattr(attribute_type, "type") + and hasattr(attribute_type, "args") + and attribute_type.type.fullname == "builtins.list" + ): + if unanalyzed_type is not None and hasattr(unanalyzed_type, "args"): + attribute_type.args = unanalyzed_type.args + else: # pragma: no cover + raise AttributeError("Could not get argument information for attribute.") + + else: + raise TypeError("Attribute has an unexpected type.") + + type_ = None + if attribute_type is not None: + type_ = mypy_type_to_abstract_type(attribute_type) + + # Get docstring + parent = self.__declaration_stack[-1] + if isinstance(parent, Function) and parent.name == "__init__": + parent = self.__declaration_stack[-2] + assert isinstance(parent, Class) + docstring = self.docstring_parser.get_attribute_documentation(parent, name) + + # Remove __init__ for attribute ids + id_ = self.__get_id(name).replace("__init__/", "") + + return Attribute( + id=id_, + name=name, + type=type_, + is_public=self.is_public(name, qname), + is_static=is_static, + docstring=docstring, + ) + + # #### Parameter utilities + + def parse_parameter_data(self, node: FuncDef, function_id: str) -> list[Parameter]: + arguments: list[Parameter] = [] + + for argument in node.arguments: + arg_name = argument.variable.name + mypy_type = argument.variable.type + if mypy_type is None: # pragma: no cover + raise ValueError("Argument has no type.") + arg_type = mypy_type_to_abstract_type(mypy_type) + arg_kind = get_argument_kind(argument) + + default_value = None + is_optional = False + initializer = argument.initializer + if initializer is not None: + if not hasattr(initializer, "value"): + if isinstance(initializer, CallExpr): + # Special case when the default is a call expression + value = None + elif hasattr(initializer, "name") and initializer.name == "None": + value = None + else: + raise ValueError("No value found for parameter") + else: + value = initializer.value + + if type(value) in {str, bool, int, float, NoneType}: + default_value = value + is_optional = True + + parent = self.__declaration_stack[-1] + docstring = self.docstring_parser.get_parameter_documentation( + function_node=node, + parameter_name=arg_name, + parameter_assigned_by=arg_kind, + parent_class=parent if isinstance(parent, Class) else None, + ) + + arguments.append( + Parameter( + id=f"{function_id}/{arg_name}", + name=arg_name, + is_optional=is_optional, + default_value=default_value, + assigned_by=arg_kind, + docstring=docstring, + type=arg_type, + ), + ) + + return arguments + + # #### Reexport utilities + + def get_reexported_by(self, name: str) -> list[Module]: + # Get the uppermost module and the path to the current node + parents = [] + parent = None + i = 1 + while not isinstance(parent, Module): + parent = self.__declaration_stack[-i] + if isinstance(parent, list): # pragma: no cover + continue + parents.append(parent.name) + i += 1 + path = [*list(reversed(parents)), name] + + # Check if there is a reexport entry for each item in the path to the current module + reexported_by = set() + for i in range(len(path)): + reexport_name = ".".join(path[: i + 1]) + if reexport_name in self.reexported: + for mod in self.reexported[reexport_name]: + reexported_by.add(mod) + + return list(reexported_by) + + def add_reexports(self, module: Module) -> None: + for qualified_import in module.qualified_imports: + name = qualified_import.qualified_name + if name in self.reexported: + if module not in self.reexported[name]: + self.reexported[name].append(module) + else: + self.reexported[name] = [module] + + for wildcard_import in module.wildcard_imports: + name = wildcard_import.module_name + if name in self.reexported: + if module not in self.reexported[name]: + self.reexported[name].append(module) + else: + self.reexported[name] = [module] + + # #### Misc. utilities + + def is_public(self, name: str, qualified_name: str) -> bool: + if name.startswith("_") and not name.endswith("__"): + return False + + for reexported_item in self.reexported: + if reexported_item.endswith(f".{name}"): + return True + + parent = self.__declaration_stack[-1] + if isinstance(parent, Class): + # Containing class is re-exported (always false if the current API element is not a method) + if parent.reexported_by: + return True + + if name == "__init__": + return parent.is_public + + # The slicing is necessary so __init__ functions are not excluded (already handled in the first condition). + return all(not it.startswith("_") for it in qualified_name.split(".")[:-1]) + + def __get_id(self, name: str) -> str: + segments = [self.api.package] + segments += [ + it.name + for it in self.__declaration_stack + if not isinstance(it, list) # Check for the linter, on runtime can never be list type + ] + segments += [name] + + return "/".join(segments) diff --git a/src/safeds_stubgen/api_analyzer/_ast_walker.py b/src/safeds_stubgen/api_analyzer/_ast_walker.py new file mode 100644 index 00000000..6ff59713 --- /dev/null +++ b/src/safeds_stubgen/api_analyzer/_ast_walker.py @@ -0,0 +1,111 @@ +from __future__ import annotations + +from collections.abc import Callable +from typing import Any + +from mypy.nodes import AssignmentStmt, ClassDef, Decorator, FuncDef, MypyFile + +from ._mypy_helpers import get_classdef_definitions, get_funcdef_definitions, get_mypyfile_definitions + +_EnterAndLeaveFunctions = tuple[ + Callable[[MypyFile | ClassDef | FuncDef | AssignmentStmt], None] | None, + Callable[[MypyFile | ClassDef | FuncDef | AssignmentStmt], None] | None, +] + + +class ASTWalker: + """A walker visiting an abstract syntax tree in preorder. + + The following methods get called: + + * enter_ on entering a node, where class name is the class of the node in lower case. + * leave_ on leaving a node, where class name is the class of the node in lower case. + """ + + def __init__(self, handler: Any) -> None: + self._handler = handler + self._cache: dict[str, _EnterAndLeaveFunctions] = {} + + def walk(self, tree: MypyFile) -> None: + self.__walk(tree, set()) + + def __walk(self, node: MypyFile | ClassDef | Decorator | FuncDef | AssignmentStmt, visited_nodes: set) -> None: + # It's possible to get decorator data but for now we'll ignore them and just get the func + if isinstance(node, Decorator): + node = node.func + + if node in visited_nodes: + raise AssertionError("Node visited twice") + visited_nodes.add(node) + + self.__enter(node) + + definitions: list = [] + if isinstance(node, MypyFile): + definitions = get_mypyfile_definitions(node) + elif isinstance(node, ClassDef): + definitions = get_classdef_definitions(node) + elif isinstance(node, FuncDef): + definitions = get_funcdef_definitions(node) + + # Skip other types, since we either get them through the ast_visitor, some other way or + # don't need to parse them + child_nodes = [ + _def + for _def in definitions + if _def.__class__.__name__ + in { + "AssignmentStmt", + "FuncDef", + "ClassDef", + "Decorator", + } + ] + + for child_node in child_nodes: + # Ignore global variables and function attributes if the function is an __init__ + if isinstance(child_node, AssignmentStmt): + if isinstance(node, MypyFile): + continue + if isinstance(node, FuncDef) and node.name != "__init__": + continue + + self.__walk(child_node, visited_nodes) + self.__leave(node) + + def __enter(self, node: MypyFile | ClassDef | FuncDef | AssignmentStmt) -> None: + method = self.__get_callbacks(node)[0] + if method is not None: + method(node) + + def __leave(self, node: MypyFile | ClassDef | FuncDef | AssignmentStmt) -> None: + method = self.__get_callbacks(node)[1] + if method is not None: + method(node) + + def __get_callbacks(self, node: MypyFile | ClassDef | FuncDef | AssignmentStmt) -> _EnterAndLeaveFunctions: + class_ = node.__class__ + class_name = class_.__name__.lower() + + # Handle special cases + if class_name == "classdef": + if not hasattr(node, "base_type_exprs"): # pragma: no cover + raise AttributeError("Expected classdef node to have attribute 'base_type_exprs'.") + + for superclass in node.base_type_exprs: + if hasattr(superclass, "fullname") and superclass.fullname in ("enum.Enum", "enum.IntEnum"): + class_name = "enumdef" + elif class_name == "mypyfile": + class_name = "moduledef" + + # Get class methods + methods = self._cache.get(class_name, None) + if methods is None: + handler = self._handler + enter_method = getattr(handler, f"enter_{class_name}", getattr(handler, "enter_default", None)) + leave_method = getattr(handler, f"leave_{class_name}", getattr(handler, "leave_default", None)) + self._cache[class_name] = (enter_method, leave_method) + else: + enter_method, leave_method = methods + + return enter_method, leave_method diff --git a/src/safeds_stubgen/api_analyzer/_files.py b/src/safeds_stubgen/api_analyzer/_files.py new file mode 100644 index 00000000..b5bc3d9c --- /dev/null +++ b/src/safeds_stubgen/api_analyzer/_files.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +import os +from pathlib import Path + + +def list_files(root_dir: Path, extension: str = "") -> list[str]: + """ + List all files in a directory and its subdirectories. + + Parameters + ---------- + root_dir: Path + The directory containing the files. + extension: str + The extension the files should have. + + Returns + ------- + files: list[str] + A list with absolute paths to the files. + """ + result: list[str] = [] + + for root, _, files in os.walk(root_dir): + for filename in files: + if filename.endswith(extension): + result.append(str(Path(root) / filename)) + + return result diff --git a/src/safeds_stubgen/api_analyzer/_get_api.py b/src/safeds_stubgen/api_analyzer/_get_api.py new file mode 100644 index 00000000..ebb9c9a3 --- /dev/null +++ b/src/safeds_stubgen/api_analyzer/_get_api.py @@ -0,0 +1,117 @@ +from __future__ import annotations + +import logging +from pathlib import Path +from typing import TYPE_CHECKING + +import mypy.build as mypy_build +import mypy.main as mypy_main + +from safeds_stubgen.docstring_parsing import DocstringStyle, create_docstring_parser + +from ._api import API +from ._ast_visitor import MyPyAstVisitor +from ._ast_walker import ASTWalker +from ._files import list_files +from ._package_metadata import distribution, distribution_version, package_root + +if TYPE_CHECKING: + from mypy.nodes import MypyFile + + +def get_api( + package_name: str, + root: Path | None = None, + docstring_style: DocstringStyle = DocstringStyle.PLAINTEXT, + is_test_run: bool = False, +) -> API: + # Check root + if root is None: + root = package_root(package_name) + + # Get distribution data + dist = distribution(package_name) or "" + dist_version = distribution_version(dist) or "" + + # Setup api walker + api = API(dist, package_name, dist_version) + docstring_parser = create_docstring_parser(docstring_style) + callable_visitor = MyPyAstVisitor(docstring_parser, api) + walker = ASTWalker(callable_visitor) + + walkable_files = [] + package_paths = [] + for file in list_files(root, ".py"): + file_path = Path(file) + logging.info( + "Working on file {posix_path}", + extra={"posix_path": file}, + ) + + # Check if the current path is a test directory + if not is_test_run and ("test" in file_path.parts or "tests" in file_path.parts): + logging.info("Skipping test file") + continue + + # Check if the current file is an init file + if file_path.parts[-1] == "__init__.py": + # if a directory contains an __init__.py file it's a package + package_paths.append( + file_path.parent, + ) + continue + + walkable_files.append(file) + + mypy_trees = _get_mypy_ast(walkable_files, package_paths, root) + for tree in mypy_trees: + walker.walk(tree) + + return callable_visitor.api + + +def _get_mypy_ast(files: list[str], package_paths: list[Path], root: Path) -> list[MypyFile]: + if not files: + raise ValueError("No files found to analyse.") + + # Build mypy checker + mypyfiles, opt = mypy_main.process_options(files) + opt.preserve_asts = True + opt.fine_grained_incremental = True + result = mypy_build.build(mypyfiles, options=opt) + + # Check mypy data key root start + parts = root.parts + graph_keys = list(result.graph.keys()) + root_start_after = 0 + for i in range(len(parts)): + if ".".join(parts[i:]) in graph_keys: + root_start_after = i + break + + # Create the keys for getting the corresponding data + packages = [ + ".".join( + package_path.parts[root_start_after:], + ).replace(".py", "") + for package_path in package_paths + ] + + modules = [ + ".".join( + Path(file).parts[root_start_after:], + ).replace(".py", "") + for file in files + ] + + # Get the needed data from mypy. The packages need to be checked first, since we have + # to get the reexported data first + all_paths = packages + modules + + results = [] + for path_key in all_paths: + tree = result.graph[path_key].tree + if tree is not None: + results.append(tree) + + return results diff --git a/src/safeds_stubgen/api_analyzer/_mypy_helpers.py b/src/safeds_stubgen/api_analyzer/_mypy_helpers.py new file mode 100644 index 00000000..951a349b --- /dev/null +++ b/src/safeds_stubgen/api_analyzer/_mypy_helpers.py @@ -0,0 +1,110 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import mypy.types as mp_types +from mypy.nodes import ArgKind, Argument +from mypy.types import Instance + +import safeds_stubgen.api_analyzer._types as sds_types + +from ._api import ParameterAssignment + +if TYPE_CHECKING: + from mypy.nodes import ClassDef, FuncDef, MypyFile + from mypy.types import ProperType + from mypy.types import Type as MypyType + + from safeds_stubgen.api_analyzer._types import AbstractType + + +def get_classdef_definitions(node: ClassDef) -> list: + return node.defs.body + + +def get_funcdef_definitions(node: FuncDef) -> list: + return node.body.body + + +def get_mypyfile_definitions(node: MypyFile) -> list: + return node.defs + + +def mypy_type_to_abstract_type(mypy_type: Instance | ProperType | MypyType) -> AbstractType: + types = [] + + # Iterable mypy types + if isinstance(mypy_type, mp_types.TupleType): + for item in mypy_type.items: + types.append( + mypy_type_to_abstract_type(item), + ) + return sds_types.TupleType(types=types) + elif isinstance(mypy_type, mp_types.UnionType): + for item in mypy_type.items: + types.append( + mypy_type_to_abstract_type(item), + ) + return sds_types.UnionType(types=types) + + # Special Cases + elif isinstance(mypy_type, mp_types.AnyType): + return sds_types.NamedType(name="Any") + elif isinstance(mypy_type, mp_types.NoneType): + return sds_types.NamedType(name="None") + elif isinstance(mypy_type, mp_types.UnboundType): + # Todo Aliasing: Import auflösen + return sds_types.NamedType(name=mypy_type.name) + + # Builtins + elif isinstance(mypy_type, Instance): + type_name = mypy_type.type.name + if type_name in {"int", "str", "bool", "float"}: + return sds_types.NamedType(name=type_name) + elif type_name == "tuple": + return sds_types.TupleType(types=[]) + elif type_name == "list": + for arg in mypy_type.args: + types.append( + mypy_type_to_abstract_type(arg), + ) + return sds_types.ListType(types=types) + elif type_name == "set": + for arg in mypy_type.args: + types.append( + mypy_type_to_abstract_type(arg), + ) + return sds_types.SetType(types=types) + elif type_name == "dict": + key_type = mypy_type_to_abstract_type(mypy_type.args[0]) + value_types = [mypy_type_to_abstract_type(arg) for arg in mypy_type.args[1:]] + + value_type: AbstractType + if len(value_types) == 0: + value_type = sds_types.NamedType(name="Any") + elif len(value_types) == 1: + value_type = value_types[0] + else: + value_type = sds_types.UnionType(types=value_types) + + return sds_types.DictType(key_type=key_type, value_type=value_type) + else: + return sds_types.NamedType(name=type_name) + raise ValueError("Unexpected type.") + + +def get_argument_kind(arg: Argument) -> ParameterAssignment: + if arg.variable.is_self or arg.variable.is_cls: + return ParameterAssignment.IMPLICIT + elif arg.kind == ArgKind.ARG_POS and arg.pos_only: + return ParameterAssignment.POSITION_ONLY + elif arg.kind in (ArgKind.ARG_OPT, ArgKind.ARG_POS) and not arg.pos_only: + return ParameterAssignment.POSITION_OR_NAME + elif arg.kind == ArgKind.ARG_STAR: + return ParameterAssignment.POSITIONAL_VARARG + elif arg.kind in (ArgKind.ARG_NAMED, ArgKind.ARG_NAMED_OPT): + return ParameterAssignment.NAME_ONLY + elif arg.kind == ArgKind.ARG_STAR2: + return ParameterAssignment.NAMED_VARARG + else: + raise ValueError("Could not find an appropriate parameter assignment.") diff --git a/src/safeds_stubgen/api_analyzer/_package_metadata.py b/src/safeds_stubgen/api_analyzer/_package_metadata.py new file mode 100644 index 00000000..63a62d26 --- /dev/null +++ b/src/safeds_stubgen/api_analyzer/_package_metadata.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +import importlib +from importlib.metadata import packages_distributions, version +from pathlib import Path + + +def package_root(package_name: str) -> Path: + path_as_string = importlib.import_module(package_name).__file__ + if path_as_string is None: + raise AssertionError(f"Cannot find package root for '{path_as_string}'.") + return Path(path_as_string).parent + + +def distribution(package_name: str) -> str | None: + dist = packages_distributions().get(package_name) + if dist is None or len(dist) == 0: + return None + + return dist[0] + + +def distribution_version(dist: str | None) -> str | None: + if dist is None or len(dist) == 0: + return None + + return version(dist) diff --git a/src/safeds_stubgen/api_analyzer/_types.py b/src/safeds_stubgen/api_analyzer/_types.py new file mode 100644 index 00000000..f72e4890 --- /dev/null +++ b/src/safeds_stubgen/api_analyzer/_types.py @@ -0,0 +1,558 @@ +from __future__ import annotations + +import re +from abc import ABCMeta, abstractmethod +from dataclasses import dataclass, field +from typing import Any, ClassVar + + +class AbstractType(metaclass=ABCMeta): + @classmethod + def from_dict(cls, d: dict[str, Any]) -> AbstractType: + match d["kind"]: + case NamedType.__name__: + return NamedType.from_dict(d) + case EnumType.__name__: + return EnumType.from_dict(d) + case BoundaryType.__name__: + return BoundaryType.from_dict(d) + case ListType.__name__: + return ListType.from_dict(d) + case DictType.__name__: + return DictType.from_dict(d) + case SetType.__name__: + return SetType.from_dict(d) + case OptionalType.__name__: + return OptionalType.from_dict(d) + case LiteralType.__name__: + return LiteralType.from_dict(d) + case FinalType.__name__: + return FinalType.from_dict(d) + case TupleType.__name__: + return TupleType.from_dict(d) + case UnionType.__name__: + return UnionType.from_dict(d) + case _: + raise ValueError(f"Cannot parse {d['kind']} value.") + + @abstractmethod + def to_dict(self) -> dict[str, Any]: + pass + + +@dataclass(frozen=True) +class NamedType(AbstractType): + name: str + + @classmethod + def from_dict(cls, d: dict[str, Any]) -> NamedType: + return NamedType(d["name"]) + + @classmethod + def from_string(cls, string: str) -> NamedType: + return NamedType(string) + + def to_dict(self) -> dict[str, str]: + return {"kind": self.__class__.__name__, "name": self.name} + + +@dataclass(frozen=True) +class EnumType(AbstractType): + values: frozenset[str] = field(default_factory=frozenset) + full_match: str = field(default="", compare=False) + + @classmethod + def from_dict(cls, d: dict[str, Any]) -> EnumType: + return EnumType(d["values"]) + + @classmethod + def from_string(cls, string: str) -> EnumType | None: + def remove_backslash(e: str) -> str: + e = e.replace(r"\"", '"') + return e.replace(r"\'", "'") + + enum_match = re.search(r"{(.*?)}", string) + if enum_match: + quotes = "'\"" + values = set() + enum_str = enum_match.group(1) + value = "" + inside_value = False + curr_quote = None + for i, char in enumerate(enum_str): + if char in quotes and (i == 0 or (i > 0 and enum_str[i - 1] != "\\")): + if not inside_value: + inside_value = True + curr_quote = char + elif inside_value: + if curr_quote == char: + inside_value = False + curr_quote = None + values.add(remove_backslash(value)) + value = "" + else: + value += char + elif inside_value: + value += char + + return EnumType(frozenset(values), enum_match.group(0)) + + return None + + def update(self, enum: EnumType) -> EnumType: + values = set(self.values) + values.update(enum.values) + return EnumType(frozenset(values)) + + def to_dict(self) -> dict[str, Any]: + return {"kind": self.__class__.__name__, "values": set(self.values)} + + +@dataclass(frozen=True) +class BoundaryType(AbstractType): + NEGATIVE_INFINITY: ClassVar = "NegativeInfinity" + INFINITY: ClassVar = "Infinity" + + base_type: str + min: float | int | str + max: float | int | str + min_inclusive: bool + max_inclusive: bool + + full_match: str = field(default="", compare=False) + + @classmethod + def _is_inclusive(cls, bracket: str) -> bool: + if bracket in ("(", ")"): + return False + if bracket in ("[", "]"): + return True + raise ValueError(f"{bracket} is not one of []()") + + @classmethod + def from_dict(cls, d: dict[str, Any]) -> BoundaryType: + return BoundaryType( + d["base_type"], + d["min"], + d["max"], + d["min_inclusive"], + d["max_inclusive"], + ) + + @classmethod + def from_string(cls, string: str) -> BoundaryType | None: + pattern = r"""(?Pfloat|int)?[ ] # optional base type of either float or int + (in|of)[ ](the[ ])?(range|interval)[ ](of[ ])? + # 'in' or 'of', optional 'the', 'range' or 'interval', optional 'of' + `?(?P[\[(])(?P[-+]?\d+(.\d*)?|negative_infinity),[ ] # left side of the range + (?P[-+]?\d+(.\d*)?|infinity)(?P[\])])`?""" # right side of the range + match = re.search(pattern, string, re.VERBOSE) + + if match is not None: + base_type = match.group("base_type") + if base_type is None: + base_type = "float" + + min_value: str | int | float = match.group("min") + if min_value != "negative_infinity": + if base_type == "int": + min_value = int(min_value) + else: + min_value = float(min_value) + else: + min_value = BoundaryType.NEGATIVE_INFINITY + + max_value: str | int | float = match.group("max") + if max_value != "infinity": + if base_type == "int": + max_value = int(max_value) + else: + max_value = float(max_value) + else: + max_value = BoundaryType.INFINITY + + min_bracket = match.group("min_bracket") + max_bracket = match.group("max_bracket") + min_inclusive = BoundaryType._is_inclusive(min_bracket) + max_inclusive = BoundaryType._is_inclusive(max_bracket) + + return BoundaryType( + base_type=base_type, + min=min_value, + max=max_value, + min_inclusive=min_inclusive, + max_inclusive=max_inclusive, + full_match=match.group(0), + ) + + return None + + def __eq__(self, __o: object) -> bool: + if isinstance(__o, BoundaryType): + eq = ( + self.base_type == __o.base_type + and self.min == __o.min + and self.min_inclusive == __o.min_inclusive + and self.max == __o.max + ) + if eq: + if self.max == BoundaryType.INFINITY: + return True + return self.max_inclusive == __o.max_inclusive + return False + + def to_dict(self) -> dict[str, Any]: + return { + "kind": self.__class__.__name__, + "base_type": self.base_type, + "min": self.min, + "max": self.max, + "min_inclusive": self.min_inclusive, + "max_inclusive": self.max_inclusive, + } + + +@dataclass(frozen=True) +class UnionType(AbstractType): + types: list[AbstractType] + + @classmethod + def from_dict(cls, d: dict[str, Any]) -> UnionType: + types = [] + for element in d["types"]: + type_ = AbstractType.from_dict(element) + if type_ is not None: + types.append(type_) + return UnionType(types) + + def to_dict(self) -> dict[str, Any]: + type_list = [] + for t in self.types: + type_list.append(t.to_dict()) + + return {"kind": self.__class__.__name__, "types": type_list} + + def __hash__(self) -> int: + return hash(frozenset(self.types)) + + +@dataclass(frozen=True) +class ListType(AbstractType): + types: list[AbstractType] + + @classmethod + def from_dict(cls, d: dict[str, Any]) -> ListType: + types = [] + for element in d["types"]: + type_ = AbstractType.from_dict(element) + if type_ is not None: + types.append(type_) + return ListType(types) + + def to_dict(self) -> dict[str, Any]: + type_list = [t.to_dict() for t in self.types] + + return {"kind": self.__class__.__name__, "types": type_list} + + def __hash__(self) -> int: + return hash(frozenset(self.types)) + + +@dataclass(frozen=True) +class DictType(AbstractType): + key_type: AbstractType + value_type: AbstractType + + @classmethod + def from_dict(cls, d: dict[str, Any]) -> DictType: + return DictType(AbstractType.from_dict(d["key_type"]), AbstractType.from_dict(d["value_type"])) + + def to_dict(self) -> dict[str, Any]: + return { + "kind": self.__class__.__name__, + "key_type": self.key_type.to_dict(), + "value_type": self.value_type.to_dict(), + } + + def __hash__(self) -> int: + return hash(frozenset([self.key_type, self.value_type])) + + +@dataclass(frozen=True) +class SetType(AbstractType): + types: list[AbstractType] + + @classmethod + def from_dict(cls, d: dict[str, Any]) -> SetType: + types = [] + for element in d["types"]: + type_ = AbstractType.from_dict(element) + if type_ is not None: + types.append(type_) + return SetType(types) + + def to_dict(self) -> dict[str, Any]: + type_list = [t.to_dict() for t in self.types] + + return {"kind": self.__class__.__name__, "types": type_list} + + def __hash__(self) -> int: + return hash(frozenset(self.types)) + + +@dataclass(frozen=True) +class OptionalType(AbstractType): + type: AbstractType + + @classmethod + def from_dict(cls, d: dict[str, Any]) -> OptionalType: + return OptionalType(AbstractType.from_dict(d["type"])) + + def to_dict(self) -> dict[str, Any]: + return {"kind": self.__class__.__name__, "type": self.type.to_dict()} + + def __hash__(self) -> int: + return hash(frozenset([self.type])) + + +@dataclass(frozen=True) +class LiteralType(AbstractType): + literals: list[str | int | float | bool] + + @classmethod + def from_dict(cls, d: dict[str, Any]) -> LiteralType: + literals = list(d["literals"]) + return LiteralType(literals) + + def to_dict(self) -> dict[str, Any]: + return {"kind": self.__class__.__name__, "literals": self.literals} + + def __hash__(self) -> int: + return hash(frozenset(self.literals)) + + +@dataclass(frozen=True) +class FinalType(AbstractType): + type_: AbstractType + + @classmethod + def from_dict(cls, d: dict[str, Any]) -> FinalType: + return FinalType(AbstractType.from_dict(d["type"])) + + def to_dict(self) -> dict[str, Any]: + return {"kind": self.__class__.__name__, "type": self.type_.to_dict()} + + def __hash__(self) -> int: + return hash(frozenset([self.type_])) + + +@dataclass(frozen=True) +class TupleType(AbstractType): + types: list[AbstractType] + + @classmethod + def from_dict(cls, d: dict[str, Any]) -> TupleType: + types = [] + for element in d["types"]: + type_ = AbstractType.from_dict(element) + if type_ is not None: + types.append(type_) + return TupleType(types) + + def to_dict(self) -> dict[str, Any]: + type_list = [t.to_dict() for t in self.types] + + return {"kind": self.__class__.__name__, "types": type_list} + + def __hash__(self) -> int: + return hash(frozenset(self.types)) + + +# ############################## Utilities ############################## # +# def _dismantel_type_string_structure(type_structure: str) -> list: +# current_type = "" +# result = [] +# +# while True: +# i = 0 +# for i, char in enumerate(type_structure): +# if char == "[": +# try: +# brackets_content, remaining_content = _parse_type_string_bracket_content(type_structure[i + 1:]) +# except TypeParsingError as parsing_error: +# raise TypeParsingError( +# f"Missing brackets in the following string: \n{type_structure}") from parsing_error +# +# result.append(current_type + "[" + brackets_content + "]") +# type_structure = remaining_content +# current_type = "" +# break +# elif char == ",": +# if current_type: +# result.append(current_type) +# current_type = "" +# else: +# current_type += char +# +# if len(type_structure) == 0 or i + 1 == len(type_structure): +# break +# +# if current_type: +# result.append(current_type) +# +# return result +# +# +# def _parse_type_string_bracket_content(substring: str) -> tuple[str, str]: +# brackets_content = "" +# bracket_count = 0 +# for i, char in enumerate(substring): +# if char == "[": +# bracket_count += 1 +# elif char == "]" and bracket_count: +# bracket_count -= 1 +# elif char == "]" and not bracket_count: +# return brackets_content, substring[i + 1:] +# +# brackets_content += char +# raise TypeParsingError("") +# +# +# # Todo Return mypy\types -> Type class +# def create_type(type_string: str, description: str) -> AbstractType: +# if not type_string: +# return NamedType("None") +# +# type_string = type_string.replace(" ", "") +# +# # todo Replace pipes with Union +# # if "|" in type_string: +# # type_string = _replace_pipes_with_union(type_string) +# +# # Structures, which only take one type argument +# one_arg_structures = {"Final": FinalType, "Optional": OptionalType} +# for key in one_arg_structures: +# regex = r"^" + key + r"\[(.*)]$" +# match = re.match(regex, type_string) +# if match: +# content = match.group(1) +# return one_arg_structures[key](create_type(content, description)) +# +# # List-like structures, which take multiple type arguments +# mult_arg_structures = {"List": ListType, "Set": SetType, "Tuple": TupleType, "Union": UnionType} +# for key in mult_arg_structures: +# regex = r"^" + key + r"\[(.*)]$" +# match = re.match(regex, type_string) +# if match: +# content = match.group(1) +# content_elements = _dismantel_type_string_structure(content) +# return mult_arg_structures[key]([ +# create_type(element, description) +# for element in content_elements +# ]) +# +# match = re.match(r"^Literal\[(.*)]$", type_string) +# if match: +# content = match.group(1) +# contents = content.replace(" ", "").split(",") +# literals = [] +# for element in contents: +# try: +# value = ast.literal_eval(element) +# except (SyntaxError, ValueError): +# value = element[1:-1] +# literals.append(value) +# return LiteralType(literals) +# +# # Misc. special structures +# match = re.match(r"^Dict\[(.*)]$", type_string) +# if match: +# content = match.group(1) +# content_elements = _dismantel_type_string_structure(content) +# if len(content_elements) != 2: +# raise TypeParsingError(f"Could not parse Dict from the following string: \n{type_string}") +# return DictType( +# create_type(content_elements[0], description), +# create_type(content_elements[1], description), +# ) +# +# # raise TypeParsingError(f"Could not parse type for the following type string:\n{type_string}") +# type_ = _create_enum_boundry_type(type_string, description) +# if type_ is not None: +# return type_ +# return NamedType(type_string) +# +# +# # todo übernehmen in create_type -> Tests schlagen nun fehl +# def _create_enum_boundry_type(type_string: str, description: str) -> AbstractType | None: +# types: list[AbstractType] = [] +# +# # Collapse whitespaces +# type_string = re.sub(r"\s+", " ", type_string) +# +# # Get boundary from description +# boundary = BoundaryType.from_string(description) +# if boundary is not None: +# types.append(boundary) +# +# # Find all enums and remove them from doc_string +# enum_array_matches = re.findall(r"\{.*?}", type_string) +# type_string = re.sub(r"\{.*?}", " ", type_string) +# for enum in enum_array_matches: +# enum_type = EnumType.from_string(enum) +# if enum_type is not None: +# types.append(enum_type) +# +# # Remove default value from doc_string +# type_string = re.sub("default=.*", " ", type_string) +# +# # Create a list with all values and types +# # ") or (" must be replaced by a very unlikely string ("&%&") so that it is not removed when filtering out. +# # The string will be replaced by ") or (" again after filtering out. +# type_string = re.sub(r"\) or \(", "&%&", type_string) +# type_string = re.sub(r" ?, ?or ", ", ", type_string) +# type_string = re.sub(r" or ", ", ", type_string) +# type_string = re.sub("&%&", ") or (", type_string) +# +# brackets = 0 +# build_string = "" +# for c in type_string: +# if c == "(": +# brackets += 1 +# elif c == ")": +# brackets -= 1 +# +# if brackets > 0: +# build_string += c +# continue +# +# if brackets == 0 and c != ",": +# build_string += c +# elif brackets == 0 and c == ",": +# # remove leading and trailing whitespaces +# build_string = build_string.strip() +# if build_string != "": +# named = NamedType.from_string(build_string) +# types.append(named) +# build_string = "" +# +# build_string = build_string.strip() +# +# # Append the last remaining entry +# if build_string != "": +# named = NamedType.from_string(build_string) +# types.append(named) +# +# if len(types) == 1: +# return types[0] +# if len(types) == 0: +# return None +# return UnionType(types) +# +# +# class TypeParsingError(Exception): +# def __init__(self, message: str): +# self.message = message +# +# def __str__(self) -> str: +# return f"TypeParsingException: {self.message}" diff --git a/src/safeds_stubgen/api_analyzer/cli/__init__.py b/src/safeds_stubgen/api_analyzer/cli/__init__.py new file mode 100644 index 00000000..8fe0ecb3 --- /dev/null +++ b/src/safeds_stubgen/api_analyzer/cli/__init__.py @@ -0,0 +1,6 @@ +"""The command line interface for the `library-analyzer`.""" +from __future__ import annotations + +from ._cli import cli + +__all__ = ["cli"] diff --git a/src/safeds_stubgen/api_analyzer/cli/_cli.py b/src/safeds_stubgen/api_analyzer/cli/_cli.py new file mode 100644 index 00000000..2700136a --- /dev/null +++ b/src/safeds_stubgen/api_analyzer/cli/_cli.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import argparse +import logging +from pathlib import Path +from typing import TYPE_CHECKING + +from safeds_stubgen.api_analyzer import get_api + +if TYPE_CHECKING: + from safeds_stubgen.docstring_parsing import DocstringStyle + + +def cli() -> None: + args = _get_args() + if args.verbose: + logging.basicConfig(level=logging.INFO) + + _run_api_command(args.package, args.src, args.out, args.docstyle, args.testrun) + + +def _get_args() -> argparse.Namespace: + from safeds_stubgen.docstring_parsing import DocstringStyle + + parser = argparse.ArgumentParser(description="Analyze Python code.") + + parser.add_argument("-v", "--verbose", help="show info messages", action="store_true") + parser.add_argument( + "-p", + "--package", + help="The name of the package.", + type=str, + required=True, + ) + parser.add_argument( + "-s", + "--src", + help=( + "Directory containing the Python code of the package. If this is omitted, we try to locate the package " + "with the given name in the current Python interpreter." + ), + type=Path, + required=False, + default=None, + ) + parser.add_argument("-o", "--out", help="Output directory.", type=Path, required=True) + parser.add_argument( + "--docstyle", + help="The docstring style.", + type=DocstringStyle.from_string, + choices=list(DocstringStyle), + required=False, + default=DocstringStyle.PLAINTEXT.name, + ) + parser.add_argument( + "-tr", + "--testrun", + help="Set this flag if files in /test or /tests directories should be included.", + required=False, + action="store_true", + ) + + return parser.parse_args() + + +def _run_api_command( + package: str, + src_dir_path: Path, + out_dir_path: Path, + docstring_style: DocstringStyle, + is_test_run: bool, +) -> None: + """ + List the API of a package. + + Parameters + ---------- + package : str + The name of the package. + out_dir_path : Path + The path to the output directory. + docstring_style : DocstringStyle + The style of docstrings that used in the library. + is_test_run : bool + Set True if files in test directories should be parsed too. + """ + api = get_api(package, src_dir_path, docstring_style, is_test_run) + out_file_api = out_dir_path.joinpath(f"{package}__api.json") + api.to_json_file(out_file_api) diff --git a/src/safeds_stubgen/docstring_parsing/__init__.py b/src/safeds_stubgen/docstring_parsing/__init__.py new file mode 100644 index 00000000..d1099ce4 --- /dev/null +++ b/src/safeds_stubgen/docstring_parsing/__init__.py @@ -0,0 +1,27 @@ +"""Parsing docstrings into a common format.""" + +from ._abstract_docstring_parser import AbstractDocstringParser +from ._create_docstring_parser import create_docstring_parser +from ._docstring import AttributeDocstring, ClassDocstring, FunctionDocstring, ParameterDocstring, ResultDocstring +from ._docstring_style import DocstringStyle +from ._epydoc_parser import EpydocParser +from ._googledoc_parser import GoogleDocParser +from ._numpydoc_parser import NumpyDocParser +from ._plaintext_docstring_parser import PlaintextDocstringParser +from ._restdoc_parser import RestDocParser + +__all__ = [ + "AbstractDocstringParser", + "AttributeDocstring", + "ClassDocstring", + "create_docstring_parser", + "DocstringStyle", + "EpydocParser", + "FunctionDocstring", + "GoogleDocParser", + "NumpyDocParser", + "ParameterDocstring", + "PlaintextDocstringParser", + "RestDocParser", + "ResultDocstring", +] diff --git a/src/safeds_stubgen/docstring_parsing/_abstract_docstring_parser.py b/src/safeds_stubgen/docstring_parsing/_abstract_docstring_parser.py new file mode 100644 index 00000000..7f5460fb --- /dev/null +++ b/src/safeds_stubgen/docstring_parsing/_abstract_docstring_parser.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from mypy import nodes + + from safeds_stubgen.api_analyzer import Class, ParameterAssignment + + from ._docstring import ( + AttributeDocstring, + ClassDocstring, + FunctionDocstring, + ParameterDocstring, + ResultDocstring, + ) + + +class AbstractDocstringParser(ABC): + @abstractmethod + def get_class_documentation(self, class_node: nodes.ClassDef) -> ClassDocstring: + pass + + @abstractmethod + def get_function_documentation(self, function_node: nodes.FuncDef) -> FunctionDocstring: + pass + + @abstractmethod + def get_parameter_documentation( + self, + function_node: nodes.FuncDef, + parameter_name: str, + parameter_assigned_by: ParameterAssignment, + parent_class: Class | None, + ) -> ParameterDocstring: + pass + + @abstractmethod + def get_attribute_documentation( + self, + parent_class: Class, + attribute_name: str, + ) -> AttributeDocstring: + pass + + @abstractmethod + def get_result_documentation(self, function_node: nodes.FuncDef) -> ResultDocstring: + pass diff --git a/src/safeds_stubgen/docstring_parsing/_create_docstring_parser.py b/src/safeds_stubgen/docstring_parsing/_create_docstring_parser.py new file mode 100644 index 00000000..7ac188c9 --- /dev/null +++ b/src/safeds_stubgen/docstring_parsing/_create_docstring_parser.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ._docstring_style import DocstringStyle +from ._epydoc_parser import EpydocParser +from ._googledoc_parser import GoogleDocParser +from ._numpydoc_parser import NumpyDocParser +from ._plaintext_docstring_parser import PlaintextDocstringParser +from ._restdoc_parser import RestDocParser + +if TYPE_CHECKING: + from ._abstract_docstring_parser import AbstractDocstringParser + + +def create_docstring_parser(style: DocstringStyle) -> AbstractDocstringParser: + if style == DocstringStyle.EPYDOC: + return EpydocParser() + elif style == DocstringStyle.GOOGLE: + return GoogleDocParser() + elif style == DocstringStyle.NUMPYDOC: + return NumpyDocParser() + elif style == DocstringStyle.REST: + return RestDocParser() + else: + return PlaintextDocstringParser() diff --git a/src/safeds_stubgen/docstring_parsing/_docstring.py b/src/safeds_stubgen/docstring_parsing/_docstring.py new file mode 100644 index 00000000..aea61379 --- /dev/null +++ b/src/safeds_stubgen/docstring_parsing/_docstring.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +import dataclasses +from dataclasses import dataclass +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + + +@dataclass(frozen=True) +class ClassDocstring: + description: str = "" + full_docstring: str = "" + + def to_dict(self) -> dict[str, Any]: + return dataclasses.asdict(self) + + +@dataclass(frozen=True) +class FunctionDocstring: + description: str = "" + full_docstring: str = "" + + def to_dict(self) -> dict[str, Any]: + return dataclasses.asdict(self) + + +@dataclass(frozen=True) +class ParameterDocstring: + type: str = "" + default_value: str = "" + description: str = "" + + def to_dict(self) -> dict[str, Any]: + return dataclasses.asdict(self) + + +@dataclass(frozen=True) +class AttributeDocstring: + type: str = "" + default_value: str = "" + description: str = "" + + def to_dict(self) -> dict[str, Any]: + return dataclasses.asdict(self) + + +@dataclass(frozen=True) +class ResultDocstring: + type: str = "" + description: str = "" + + def to_dict(self) -> dict[str, Any]: + return dataclasses.asdict(self) diff --git a/src/safeds_stubgen/docstring_parsing/_docstring_style.py b/src/safeds_stubgen/docstring_parsing/_docstring_style.py new file mode 100644 index 00000000..e7116892 --- /dev/null +++ b/src/safeds_stubgen/docstring_parsing/_docstring_style.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from enum import Enum + + +class DocstringStyle(Enum): + # AUTO = "auto", + PLAINTEXT = "plaintext" + EPYDOC = "epydoc" + GOOGLE = "google" + NUMPYDOC = "numpydoc" + REST = "rest" + + def __str__(self) -> str: + return self.name + + @staticmethod + def from_string(key: str) -> DocstringStyle: + try: + return DocstringStyle[key.upper()] + except KeyError as err: + raise ValueError(f"Unknown docstring style: {key}") from err diff --git a/src/safeds_stubgen/docstring_parsing/_epydoc_parser.py b/src/safeds_stubgen/docstring_parsing/_epydoc_parser.py new file mode 100644 index 00000000..b111712a --- /dev/null +++ b/src/safeds_stubgen/docstring_parsing/_epydoc_parser.py @@ -0,0 +1,123 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from docstring_parser import Docstring, DocstringParam +from docstring_parser import DocstringStyle as DP_DocstringStyle +from docstring_parser import parse as parse_docstring + +from ._abstract_docstring_parser import AbstractDocstringParser +from ._docstring import ( + AttributeDocstring, + ClassDocstring, + FunctionDocstring, + ParameterDocstring, + ResultDocstring, +) +from ._helpers import get_description, get_full_docstring + +if TYPE_CHECKING: + from mypy import nodes + + from safeds_stubgen.api_analyzer import Class, ParameterAssignment + + +class EpydocParser(AbstractDocstringParser): + """Parses documentation in the Epydoc format. + + See https://epydoc.sourceforge.net/epytext.html for more information. + This class is not thread-safe. Each thread should create its own instance. + """ + + def __init__(self) -> None: + self.__cached_node: nodes.FuncDef | None = None + self.__cached_docstring: Docstring | None = None + + def get_class_documentation(self, class_node: nodes.ClassDef) -> ClassDocstring: + docstring = get_full_docstring(class_node) + docstring_obj = parse_docstring(docstring, style=DP_DocstringStyle.EPYDOC) + + return ClassDocstring( + description=get_description(docstring_obj), + full_docstring=docstring, + ) + + def get_function_documentation(self, function_node: nodes.FuncDef) -> FunctionDocstring: + docstring = get_full_docstring(function_node) + docstring_obj = self.__get_cached_epydoc_string(function_node, docstring) + + return FunctionDocstring( + description=get_description(docstring_obj), + full_docstring=docstring, + ) + + def get_parameter_documentation( + self, + function_node: nodes.FuncDef, + parameter_name: str, + parameter_assigned_by: ParameterAssignment, # noqa: ARG002 + parent_class: Class | None, + ) -> ParameterDocstring: + from safeds_stubgen.api_analyzer import Class + + # For constructors (__init__ functions) the parameters are described on the class + if function_node.name == "__init__" and isinstance(parent_class, Class): + docstring = parent_class.docstring.full_docstring + else: + docstring = get_full_docstring(function_node) + + # Find matching parameter docstrings + function_epydoc = self.__get_cached_epydoc_string(function_node, docstring) + all_parameters_epydoc: list[DocstringParam] = function_epydoc.params + matching_parameters_epydoc = [it for it in all_parameters_epydoc if it.arg_name == parameter_name] + + if len(matching_parameters_epydoc) == 0: + return ParameterDocstring() + + last_parameter_docstring_obj = matching_parameters_epydoc[-1] + return ParameterDocstring( + type=last_parameter_docstring_obj.type_name or "", + default_value=last_parameter_docstring_obj.default or "", + description=last_parameter_docstring_obj.description or "", + ) + + # Todo Epydoc: Attribute handling not yet implemented in docstring_parser library + def get_attribute_documentation( + self, + parent_class: Class, # noqa: ARG002 + attribute_name: str, # noqa: ARG002 + ) -> AttributeDocstring: + return AttributeDocstring() + + def get_result_documentation(self, function_node: nodes.FuncDef) -> ResultDocstring: + docstring = get_full_docstring(function_node) + + # Find matching parameter docstrings + function_epydoc = self.__get_cached_epydoc_string(function_node, docstring) + function_returns = function_epydoc.returns + + if function_returns is None: + return ResultDocstring() + + return ResultDocstring( + type=function_returns.type_name or "", + description=function_returns.description or "", + ) + + def __get_cached_epydoc_string(self, node: nodes.FuncDef, docstring: str) -> Docstring: + """ + Return the EpydocString for the given function node. + + It is only recomputed when the function node differs from the previous one that was passed to this function. + This avoids reparsing the docstring for the function itself and all of its parameters. + + On Lars's system this caused a significant performance improvement: Previously, 8.382s were spent inside the + function get_parameter_documentation when parsing sklearn. Afterwards, it was only 2.113s. + """ + if self.__cached_node is not node or node.name == "__init__": + self.__cached_node = node + self.__cached_docstring = parse_docstring(docstring, style=DP_DocstringStyle.EPYDOC) + + if self.__cached_docstring is None: # pragma: no cover + raise ValueError("Expected a docstring, got None instead.") + return self.__cached_docstring diff --git a/src/safeds_stubgen/docstring_parsing/_googledoc_parser.py b/src/safeds_stubgen/docstring_parsing/_googledoc_parser.py new file mode 100644 index 00000000..12c84dc7 --- /dev/null +++ b/src/safeds_stubgen/docstring_parsing/_googledoc_parser.py @@ -0,0 +1,136 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from docstring_parser import Docstring, DocstringParam +from docstring_parser import DocstringStyle as DP_DocstringStyle +from docstring_parser import parse as parse_docstring + +from ._abstract_docstring_parser import AbstractDocstringParser +from ._docstring import ( + AttributeDocstring, + ClassDocstring, + FunctionDocstring, + ParameterDocstring, + ResultDocstring, +) +from ._helpers import get_description, get_full_docstring + +if TYPE_CHECKING: + from mypy import nodes + + from safeds_stubgen.api_analyzer import Class, ParameterAssignment + + +class GoogleDocParser(AbstractDocstringParser): + """Parses documentation in the Googledoc format. + + See https://google.github.io/styleguide/pyguide.html#381-docstrings for more information. + This class is not thread-safe. Each thread should create its own instance. + """ + + def __init__(self) -> None: + self.__cached_node: nodes.FuncDef | Class | None = None + self.__cached_docstring: Docstring | None = None + + def get_class_documentation(self, class_node: nodes.ClassDef) -> ClassDocstring: + docstring = get_full_docstring(class_node) + docstring_obj = parse_docstring(docstring, style=DP_DocstringStyle.GOOGLE) + + return ClassDocstring( + description=get_description(docstring_obj), + full_docstring=docstring, + ) + + def get_function_documentation(self, function_node: nodes.FuncDef) -> FunctionDocstring: + docstring = get_full_docstring(function_node) + docstring_obj = self.__get_cached_googledoc_string(function_node, docstring) + + return FunctionDocstring( + description=get_description(docstring_obj), + full_docstring=docstring, + ) + + def get_parameter_documentation( + self, + function_node: nodes.FuncDef, + parameter_name: str, + parameter_assigned_by: ParameterAssignment, # noqa: ARG002 + parent_class: Class | None, + ) -> ParameterDocstring: + from safeds_stubgen.api_analyzer import Class + + # For constructors (__init__ functions) the parameters are described on the class + if function_node.name == "__init__" and isinstance(parent_class, Class): + docstring = parent_class.docstring.full_docstring + else: + docstring = get_full_docstring(function_node) + + # Find matching parameter docstrings + function_googledoc = self.__get_cached_googledoc_string(function_node, docstring) + all_parameters_googledoc: list[DocstringParam] = function_googledoc.params + matching_parameters_googledoc = [ + it for it in all_parameters_googledoc if it.arg_name == parameter_name and it.args[0] == "param" + ] + + if len(matching_parameters_googledoc) == 0: + return ParameterDocstring() + + last_parameter_docstring_obj = matching_parameters_googledoc[-1] + return ParameterDocstring( + type=last_parameter_docstring_obj.type_name or "", + default_value=last_parameter_docstring_obj.default or "", + description=last_parameter_docstring_obj.description or "", + ) + + def get_attribute_documentation( + self, + parent_class: Class, + attribute_name: str, + ) -> AttributeDocstring: + # Find matching attribute docstrings + function_googledoc = self.__get_cached_googledoc_string(parent_class, parent_class.docstring.full_docstring) + all_attributes_googledoc: list[DocstringParam] = function_googledoc.params + matching_attributes_googledoc = [ + it for it in all_attributes_googledoc if it.arg_name == attribute_name and it.args[0] == "attribute" + ] + + if len(matching_attributes_googledoc) == 0: + return AttributeDocstring() + + last_attribute_docstring_obj = matching_attributes_googledoc[-1] + return AttributeDocstring( + type=last_attribute_docstring_obj.type_name or "", + default_value=last_attribute_docstring_obj.default or "", + description=last_attribute_docstring_obj.description or "", + ) + + def get_result_documentation(self, function_node: nodes.FuncDef) -> ResultDocstring: + docstring = get_full_docstring(function_node) + + # Find matching parameter docstrings + function_googledoc = self.__get_cached_googledoc_string(function_node, docstring) + function_returns = function_googledoc.returns + + if function_returns is None: + return ResultDocstring() + + return ResultDocstring(type=function_returns.type_name or "", description=function_returns.description or "") + + def __get_cached_googledoc_string(self, node: nodes.FuncDef | Class, docstring: str) -> Docstring: + """ + Return the GoogleDocString for the given function node. + + It is only recomputed when the function node differs from the previous one that was passed to this function. + This avoids reparsing the docstring for the function itself and all of its parameters. + + On Lars's system this caused a significant performance improvement: Previously, 8.382s were spent inside the + function get_parameter_documentation when parsing sklearn. Afterward, it was only 2.113s. + """ + if self.__cached_node is not node or node.name == "__init__": + self.__cached_node = node + self.__cached_docstring = parse_docstring(docstring, style=DP_DocstringStyle.GOOGLE) + + if self.__cached_docstring is None: # pragma: no cover + raise ValueError("Expected a docstring, got None instead.") + return self.__cached_docstring diff --git a/src/safeds_stubgen/docstring_parsing/_helpers.py b/src/safeds_stubgen/docstring_parsing/_helpers.py new file mode 100644 index 00000000..9550c073 --- /dev/null +++ b/src/safeds_stubgen/docstring_parsing/_helpers.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +import inspect +from typing import TYPE_CHECKING + +from mypy import nodes + +if TYPE_CHECKING: + from docstring_parser import Docstring + + +def get_full_docstring(declaration: nodes.ClassDef | nodes.FuncDef) -> str: + """ + Return the full docstring of the given declaration. + + If no docstring is available, an empty string is returned. Indentation is cleaned up. + """ + from safeds_stubgen.api_analyzer import get_classdef_definitions, get_funcdef_definitions + + if isinstance(declaration, nodes.ClassDef): + definitions = get_classdef_definitions(declaration) + elif isinstance(declaration, nodes.FuncDef): + definitions = get_funcdef_definitions(declaration) + else: # pragma: no cover + raise TypeError("Declaration is of wrong type.") + + full_docstring = "" + for definition in definitions: + if isinstance(definition, nodes.ExpressionStmt) and isinstance(definition.expr, nodes.StrExpr): + full_docstring = definition.expr.value + + return inspect.cleandoc(full_docstring) + + +def get_description(docstring_obj: Docstring) -> str: + """ + Return the concatenated short and long description of the given docstring object. + + If these parts are blank, an empty string is returned. + """ + summary: str = docstring_obj.short_description or "" + extended_summary: str = docstring_obj.long_description or "" + + result = "" + result += summary.rstrip() + result += "\n\n" + result += extended_summary.rstrip() + return result.strip() diff --git a/src/safeds_stubgen/docstring_parsing/_numpydoc_parser.py b/src/safeds_stubgen/docstring_parsing/_numpydoc_parser.py new file mode 100644 index 00000000..ef4910e5 --- /dev/null +++ b/src/safeds_stubgen/docstring_parsing/_numpydoc_parser.py @@ -0,0 +1,218 @@ +from __future__ import annotations + +import re +from typing import TYPE_CHECKING + +from docstring_parser import Docstring, DocstringParam +from docstring_parser import DocstringStyle as DP_DocstringStyle +from docstring_parser import parse as parse_docstring + +from ._abstract_docstring_parser import AbstractDocstringParser +from ._docstring import ( + AttributeDocstring, + ClassDocstring, + FunctionDocstring, + ParameterDocstring, + ResultDocstring, +) +from ._helpers import get_description, get_full_docstring + +if TYPE_CHECKING: + from mypy import nodes + + from safeds_stubgen.api_analyzer import Class, ParameterAssignment + + +class NumpyDocParser(AbstractDocstringParser): + """ + Parse documentation in the NumpyDoc format. + + Notes + ----- + This class is not thread-safe. Each thread should create its own instance. + + References + ---------- + ... [1] https://numpydoc.readthedocs.io/en/latest/format.html + """ + + def __init__(self) -> None: + self.__cached_node: nodes.FuncDef | Class | None = None + self.__cached_docstring: Docstring | None = None + + def get_class_documentation(self, class_node: nodes.ClassDef) -> ClassDocstring: + docstring = get_full_docstring(class_node) + docstring_obj = parse_docstring(docstring, style=DP_DocstringStyle.NUMPYDOC) + + return ClassDocstring( + description=get_description(docstring_obj), + full_docstring=docstring, + ) + + def get_function_documentation(self, function_node: nodes.FuncDef) -> FunctionDocstring: + docstring = get_full_docstring(function_node) + docstring_obj = self.__get_cached_numpydoc_string(function_node, docstring) + + return FunctionDocstring( + description=get_description(docstring_obj), + full_docstring=docstring, + ) + + def get_parameter_documentation( + self, + function_node: nodes.FuncDef, + parameter_name: str, + parameter_assigned_by: ParameterAssignment, + parent_class: Class | None, + ) -> ParameterDocstring: + from safeds_stubgen.api_analyzer import Class + + # For constructors (__init__ functions) the parameters are described on the class + if function_node.name == "__init__" and isinstance(parent_class, Class): + docstring = parent_class.docstring.full_docstring + else: + docstring = get_full_docstring(function_node) + + # Find matching parameter docstrings + function_numpydoc = self.__get_cached_numpydoc_string(function_node, docstring) + all_parameters_numpydoc: list[DocstringParam] = function_numpydoc.params + matching_parameters_numpydoc = [ + it + for it in all_parameters_numpydoc + if it.args[0] == "param" and _is_matching_parameter_numpydoc(it, parameter_name, parameter_assigned_by) + ] + + if len(matching_parameters_numpydoc) == 0: + # If we have a constructor we have to check both, the class and then the constructor (see issue #10) + if function_node.name == "__init__": + docstring_constructor = get_full_docstring(function_node) + # Find matching parameter docstrings + function_numpydoc = parse_docstring(docstring_constructor, style=DP_DocstringStyle.NUMPYDOC) + all_parameters_numpydoc = function_numpydoc.params + + # Overwrite previous matching_parameters_numpydoc list + matching_parameters_numpydoc = [ + it + for it in all_parameters_numpydoc + if _is_matching_parameter_numpydoc(it, parameter_name, parameter_assigned_by) + ] + + if len(matching_parameters_numpydoc) == 0: + return ParameterDocstring(type="", default_value="", description="") + + last_parameter_numpydoc = matching_parameters_numpydoc[-1] + type_, default_value = _get_type_and_default_value(last_parameter_numpydoc) + return ParameterDocstring( + type=type_, + default_value=default_value, + description=last_parameter_numpydoc.description or "", + ) + + def get_attribute_documentation( + self, + parent_class: Class, + attribute_name: str, + ) -> AttributeDocstring: + # Find matching attribute docstrings + function_numpydoc = self.__get_cached_numpydoc_string(parent_class, parent_class.docstring.full_docstring) + all_attributes_numpydoc: list[DocstringParam] = function_numpydoc.params + matching_attributes_numpydoc = [ + it + for it in all_attributes_numpydoc + if it.args[0] == "attribute" and _is_matching_attribute_numpydoc(it, attribute_name) + ] + + # If the class has a constructor we have to check both the class and then the constructor (see issue #10) + if len(matching_attributes_numpydoc) == 0: + # Find matching attribute docstrings + function_numpydoc = parse_docstring( + parent_class.constructor_fulldocstring, + style=DP_DocstringStyle.NUMPYDOC, + ) + all_attributes_numpydoc = function_numpydoc.params + + # Overwrite previous matching_attributes_numpydoc list + matching_attributes_numpydoc = [ + it for it in all_attributes_numpydoc if _is_matching_attribute_numpydoc(it, attribute_name) + ] + + if len(matching_attributes_numpydoc) == 0: + return AttributeDocstring(type="", default_value="", description="") + + last_attribute_numpydoc = matching_attributes_numpydoc[-1] + type_, default_value = _get_type_and_default_value(last_attribute_numpydoc) + return AttributeDocstring( + type=type_, + default_value=default_value, + description=last_attribute_numpydoc.description or "", + ) + + def get_result_documentation(self, function_node: nodes.FuncDef) -> ResultDocstring: + docstring = get_full_docstring(function_node) + + # Find matching parameter docstrings + function_numpydoc = self.__get_cached_numpydoc_string(function_node, docstring) + function_result = function_numpydoc.returns + + if function_result is None: + return ResultDocstring() + + return ResultDocstring( + type=function_result.type_name or "", + description=function_result.description or "", + ) + + def __get_cached_numpydoc_string(self, node: nodes.FuncDef | Class, docstring: str) -> Docstring: + """ + Return the NumpyDocString for the given function node. + + It is only recomputed when the function node differs from the previous one that was passed to this function. + This avoids reparsing the docstring for the function itself and all of its parameters. + + On Lars's system this caused a significant performance improvement: Previously, 8.382s were spent inside the + function `get_parameter_documentation` when parsing sklearn. Afterwards, it was only 2.113s. + """ + if self.__cached_node is not node or node.name == "__init__": + self.__cached_node = node + self.__cached_docstring = parse_docstring(docstring, style=DP_DocstringStyle.NUMPYDOC) + + if self.__cached_docstring is None: # pragma: no cover + raise ValueError("Expected a docstring, got None instead.") + return self.__cached_docstring + + +def _is_matching_parameter_numpydoc( + parameter_docstring_obj: DocstringParam, + parameter_name: str, + parameter_assigned_by: ParameterAssignment, +) -> bool: + """Return whether the given docstring object applies to the parameter with the given name.""" + from safeds_stubgen.api_analyzer import ParameterAssignment + + if parameter_assigned_by == ParameterAssignment.POSITIONAL_VARARG: + lookup_name = f"*{parameter_name}" + elif parameter_assigned_by == ParameterAssignment.NAMED_VARARG: + lookup_name = f"**{parameter_name}" + else: + lookup_name = parameter_name + + # Numpydoc allows multiple parameters to be documented at once. See + # https://numpydoc.readthedocs.io/en/latest/format.html#parameters for more information. + return any(name.strip() == lookup_name for name in parameter_docstring_obj.arg_name.split(",")) + + +def _is_matching_attribute_numpydoc(parameter_docstring_obj: DocstringParam, parameter_name: str) -> bool: + return any(name.strip() == parameter_name for name in parameter_docstring_obj.arg_name.split(",")) + + +def _get_type_and_default_value( + parameter_docstring_obj: DocstringParam, +) -> tuple[str, str]: + """Return the type and default value for the given NumpyDoc.""" + type_name = parameter_docstring_obj.type_name or "" + parts = re.split(r",\s*optional|,\s*default\s*[:=]?", type_name) + + if len(parts) != 2: + return type_name.strip(), parameter_docstring_obj.default or "" + + return parts[0].strip(), parts[1].strip() diff --git a/src/safeds_stubgen/docstring_parsing/_plaintext_docstring_parser.py b/src/safeds_stubgen/docstring_parsing/_plaintext_docstring_parser.py new file mode 100644 index 00000000..8a2dbfb4 --- /dev/null +++ b/src/safeds_stubgen/docstring_parsing/_plaintext_docstring_parser.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ._abstract_docstring_parser import AbstractDocstringParser +from ._docstring import AttributeDocstring, ClassDocstring, FunctionDocstring, ParameterDocstring, ResultDocstring +from ._helpers import get_full_docstring + +if TYPE_CHECKING: + from mypy import nodes + + from safeds_stubgen.api_analyzer import Class, ParameterAssignment + + +class PlaintextDocstringParser(AbstractDocstringParser): + """Parses documentation in any format. Should not be used if there is another parser for the specific format.""" + + def get_class_documentation(self, class_node: nodes.ClassDef) -> ClassDocstring: + docstring = get_full_docstring(class_node) + + return ClassDocstring( + description=docstring, + full_docstring=docstring, + ) + + def get_function_documentation(self, function_node: nodes.FuncDef) -> FunctionDocstring: + docstring = get_full_docstring(function_node) + + return FunctionDocstring( + description=docstring, + full_docstring=docstring, + ) + + def get_parameter_documentation( + self, + function_node: nodes.FuncDef, # noqa: ARG002 + parameter_name: str, # noqa: ARG002 + parameter_assigned_by: ParameterAssignment, # noqa: ARG002 + parent_class: Class | None, # noqa: ARG002 + ) -> ParameterDocstring: + return ParameterDocstring() + + def get_attribute_documentation( + self, + parent_class: Class, # noqa: ARG002 + attribute_name: str, # noqa: ARG002 + ) -> AttributeDocstring: + return AttributeDocstring() + + def get_result_documentation( + self, + function_node: nodes.FuncDef, # noqa: ARG002 + ) -> ResultDocstring: + return ResultDocstring() diff --git a/src/safeds_stubgen/docstring_parsing/_restdoc_parser.py b/src/safeds_stubgen/docstring_parsing/_restdoc_parser.py new file mode 100644 index 00000000..d75f3c77 --- /dev/null +++ b/src/safeds_stubgen/docstring_parsing/_restdoc_parser.py @@ -0,0 +1,138 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from docstring_parser import Docstring, DocstringParam +from docstring_parser import DocstringStyle as DP_DocstringStyle +from docstring_parser import parse as parse_docstring + +from ._abstract_docstring_parser import AbstractDocstringParser +from ._docstring import ( + AttributeDocstring, + ClassDocstring, + FunctionDocstring, + ParameterDocstring, + ResultDocstring, +) +from ._helpers import get_description, get_full_docstring + +if TYPE_CHECKING: + from mypy import nodes + + from safeds_stubgen.api_analyzer import Class, ParameterAssignment + + +class RestDocParser(AbstractDocstringParser): + """Parses documentation in the Restdoc format. + + See https://spring.io/projects/spring-restdocs#samples for more information. + This class is not thread-safe. Each thread should create its own instance. + """ + + def __init__(self) -> None: + self.__cached_node: nodes.FuncDef | Class | None = None + self.__cached_docstring: Docstring | None = None + + def get_class_documentation(self, class_node: nodes.ClassDef) -> ClassDocstring: + docstring = get_full_docstring(class_node) + docstring_obj = parse_docstring(docstring, style=DP_DocstringStyle.REST) + + return ClassDocstring( + description=get_description(docstring_obj), + full_docstring=docstring, + ) + + def get_function_documentation(self, function_node: nodes.FuncDef) -> FunctionDocstring: + docstring = get_full_docstring(function_node) + docstring_obj = self.__get_cached_restdoc_string(function_node, docstring) + + return FunctionDocstring( + description=get_description(docstring_obj), + full_docstring=docstring, + ) + + def get_parameter_documentation( + self, + function_node: nodes.FuncDef, + parameter_name: str, + parameter_assigned_by: ParameterAssignment, # noqa: ARG002 + parent_class: Class | None, + ) -> ParameterDocstring: + from safeds_stubgen.api_analyzer import Class + + # For constructors the parameters are described in the class + if function_node.name == "__init__" and isinstance(parent_class, Class): + docstring = parent_class.docstring.full_docstring + else: + docstring = get_full_docstring(function_node) + + # Find matching parameter docstrings + function_restdoc = self.__get_cached_restdoc_string(function_node, docstring) + all_parameters_restdoc: list[DocstringParam] = function_restdoc.params + matching_parameters_restdoc = [it for it in all_parameters_restdoc if it.arg_name == parameter_name] + + if len(matching_parameters_restdoc) == 0: + return ParameterDocstring() + + last_parameter_docstring_obj = matching_parameters_restdoc[-1] + return ParameterDocstring( + type=last_parameter_docstring_obj.type_name or "", + default_value=last_parameter_docstring_obj.default or "", + description=last_parameter_docstring_obj.description or "", + ) + + def get_attribute_documentation( + self, + parent_class: Class, + attribute_name: str, + ) -> AttributeDocstring: + # ReST docstrings do not differentiate between parameter and attributes, therefore the parameter and attribute + # functions are quite similiar + + # Find matching attribute docstrings + class_restdoc = self.__get_cached_restdoc_string(parent_class, parent_class.docstring.full_docstring) + all_attributes_restdoc: list[DocstringParam] = class_restdoc.params + matching_attributes_restdoc = [it for it in all_attributes_restdoc if it.arg_name == attribute_name] + + if len(matching_attributes_restdoc) == 0: + return AttributeDocstring() + + last_attribute_docstring_obj = matching_attributes_restdoc[-1] + return AttributeDocstring( + type=last_attribute_docstring_obj.type_name or "", + default_value=last_attribute_docstring_obj.default or "", + description=last_attribute_docstring_obj.description or "", + ) + + def get_result_documentation(self, function_node: nodes.FuncDef) -> ResultDocstring: + docstring = get_full_docstring(function_node) + + # Find matching parameter docstrings + function_restdoc = self.__get_cached_restdoc_string(function_node, docstring) + function_returns = function_restdoc.returns + + if function_returns is None: + return ResultDocstring() + + return ResultDocstring( + type=function_returns.type_name or "", + description=function_returns.description or "", + ) + + def __get_cached_restdoc_string(self, node: nodes.FuncDef | Class, docstring: str) -> Docstring: + """ + Return the RestDocString for the given function node. + + It is only recomputed when the function node differs from the previous one that was passed to this function. + This avoids reparsing the docstring for the function itself and all of its parameters. + + On Lars's system this caused a significant performance improvement: Previously, 8.382s were spent inside the + function get_parameter_documentation when parsing sklearn. Afterward, it was only 2.113s. + """ + if self.__cached_node is not node or node.name == "__init__": + self.__cached_node = node + self.__cached_docstring = parse_docstring(docstring, style=DP_DocstringStyle.REST) + + if self.__cached_docstring is None: # pragma: no cover + raise ValueError("Expected a docstring, got None instead.") + return self.__cached_docstring diff --git a/src/safeds_stubgen/main.py b/src/safeds_stubgen/main.py index 2f360f40..57360631 100644 --- a/src/safeds_stubgen/main.py +++ b/src/safeds_stubgen/main.py @@ -1,2 +1,20 @@ -def main(): - raise NotImplementedError() +"""The entrypoint to the program.""" +from __future__ import annotations + +import time + +from safeds_stubgen.api_analyzer.cli import cli + + +def main() -> None: + """Launch the program.""" + start_time = time.time() + + cli() + + print("\n============================================================") # noqa: T201 + print(f"Program ran in {time.time() - start_time}s") # noqa: T201 + + +if __name__ == "__main__": + main() diff --git a/tests/data/test_docstring_parser_package/__ini__.py b/tests/data/test_docstring_parser_package/__ini__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/test_docstring_parser_package/test_epydoc.py b/tests/data/test_docstring_parser_package/test_epydoc.py new file mode 100644 index 00000000..1464c9ab --- /dev/null +++ b/tests/data/test_docstring_parser_package/test_epydoc.py @@ -0,0 +1,112 @@ +class ClassWithDocumentation: + """ + Lorem ipsum. Code:: + + pass + + Dolor sit amet. + """ + + +class ClassWithoutDocumentation: + pass + + +def function_with_documentation() -> None: + """ + Lorem ipsum. Code:: + + pass + + Dolor sit amet. + """ + + +def function_without_documentation() -> None: + pass + + +class ClassWithParameters: + """ + Lorem ipsum. + + Dolor sit amet. + + @param p: foo defaults to 1 + @type p: int + """ + + def __init__(self) -> None: + pass + + +class ClassWithAttributes: + """ + Lorem ipsum. + + Dolor sit amet. + + @ivar p: foo defaults to 1 + @type p: int + """ + + def __init__(self) -> None: + pass + + +class ClassWithAttributesNoType: + """ + Lorem ipsum. + + Dolor sit amet. + + @ivar p: foo defaults to 1 + """ + + def __init__(self) -> None: + pass + + +def function_with_parameters() -> None: + """ + Lorem ipsum. + + Dolor sit amet. + + Parameters + ---------- + @param no_type_no_default: no type and no default + @param type_no_default: type but no default + @type type_no_default: int + @param with_default: foo that defaults to 2 + @type with_default: int + """ + + +def function_with_result_value_and_type() -> None: + """ + Lorem ipsum. + + Dolor sit amet. + + @return: return value + @rtype: float + """ + + +def function_with_result_value_no_type() -> None: + """ + Lorem ipsum. + + Dolor sit amet. + + @return: return value + """ + + +def function_without_result_value() -> None: + """ + Lorem ipsum. + + Dolor sit amet. + """ diff --git a/tests/data/test_docstring_parser_package/test_full_docstring.py b/tests/data/test_docstring_parser_package/test_full_docstring.py new file mode 100644 index 00000000..1dc35230 --- /dev/null +++ b/tests/data/test_docstring_parser_package/test_full_docstring.py @@ -0,0 +1,30 @@ +class ClassWithMultiLineDocumentation: + """ + Lorem ipsum. + + Dolor sit amet. + """ + + +class ClassWithSingleLineDocumentation: + """Lorem ipsum.""" + + +class ClassWithoutDocumentation: + pass + + +def function_with_multi_line_documentation() -> None: + """ + Lorem ipsum. + + Dolor sit amet. + """ + + +def function_with_single_line_documentation() -> None: + """Lorem ipsum.""" + + +def function_without_documentation() -> None: + pass diff --git a/tests/data/test_docstring_parser_package/test_googledoc.py b/tests/data/test_docstring_parser_package/test_googledoc.py new file mode 100644 index 00000000..5c4d822c --- /dev/null +++ b/tests/data/test_docstring_parser_package/test_googledoc.py @@ -0,0 +1,104 @@ +class ClassWithDocumentation: + """ + Lorem ipsum. Code:: + + pass + + Dolor sit amet. + """ + + +class ClassWithoutDocumentation: + pass + + +def function_with_documentation() -> None: + """ + Lorem ipsum. Code:: + + pass + + Dolor sit amet. + """ + + +def function_without_documentation() -> None: + pass + + +class ClassWithParameters: + """Lorem ipsum. + + Dolor sit amet. + + Args: + p (int): foo. Defaults to 1. + """ + + def __init__(self) -> None: + pass + + +def function_with_parameters() -> None: + """Lorem ipsum. + + Dolor sit amet. + + Args: + no_type_no_default: no type and no default. + type_no_default (int): type but no default. + with_default (int): foo. Defaults to 2. + *args (int): foo: *args + **kwargs (int): foo: **kwargs + """ + + +def function_with_attributes_and_parameters() -> None: + """Lorem ipsum. + + Dolor sit amet. + + Attributes: + p (int): foo. Defaults to 2. + + Args: + q (int): foo. Defaults to 2. + + """ + + +class ClassWithAttributes: + """Lorem ipsum. + + Dolor sit amet. + + Attributes: + p (int): foo. Defaults to 1. + """ + + +def function_with_return_value_and_type() -> None: + """Lorem ipsum. + + Dolor sit amet. + + Returns: + int: this will be the return value. + """ + + +def function_with_return_value_no_type() -> None: + """Lorem ipsum. + + Dolor sit amet. + + Returns: + int + """ + + +def function_without_return_value() -> None: + """Lorem ipsum. + + Dolor sit amet. + """ diff --git a/tests/data/test_docstring_parser_package/test_numpydoc.py b/tests/data/test_docstring_parser_package/test_numpydoc.py new file mode 100644 index 00000000..9204cfa6 --- /dev/null +++ b/tests/data/test_docstring_parser_package/test_numpydoc.py @@ -0,0 +1,166 @@ +class ClassWithDocumentation: + """ + Lorem ipsum. Code:: + + pass + + Dolor sit amet. + """ + + +class ClassWithoutDocumentation: + pass + + +def function_with_documentation() -> None: + """ + Lorem ipsum. Code:: + + pass + + Dolor sit amet. + """ + + +def function_without_documentation() -> None: + pass + + +class ClassWithParameters: + """ + Lorem ipsum. + + Dolor sit amet. + + Parameters + ---------- + p : int, default=1 + foo + """ + + def __init__(self) -> None: + pass + + +def function_with_parameters() -> None: + """ + Lorem ipsum. + + Dolor sit amet. + + Parameters + ---------- + no_type_no_default + foo: no_type_no_default. Code:: + + pass + type_no_default : int + foo: type_no_default + optional_unknown_default : int, optional + foo: optional_unknown_default + with_default_syntax_1 : int, default 1 + foo: with_default_syntax_1 + with_default_syntax_2 : int, default: 2 + foo: with_default_syntax_2 + with_default_syntax_3 : int, default=3 + foo: with_default_syntax_3 + grouped_parameter_1, grouped_parameter_2 : int, default=4 + foo: grouped_parameter_1 and grouped_parameter_2 + *args : int + foo: *args + **kwargs : int + foo: **kwargs + """ + + +class ClassAndFunctionWithParameters: + """ + Parameters + ---------- + x: str + Lorem ipsum 1. + z: int, default=5 + Lorem ipsum 3. + """ + + def __init__(self, x, y, z) -> None: + """ + Parameters + ---------- + y: str + Lorem ipsum 2. + z: str + Lorem ipsum 4. + """ + + +class ClassWithParametersAndAttributes: + """ + Lorem ipsum. + + Dolor sit amet. + + Parameters + ---------- + p : int, default=1 + foo + + Attributes + ---------- + q : int, default=1 + foo + """ + + def __init__(self) -> None: + pass + + +class ClassWithAttributes: + """ + Lorem ipsum. + + Dolor sit amet. + + Attributes + ---------- + no_type_no_default + foo: no_type_no_default. Code:: + + pass + type_no_default : int + foo: type_no_default + optional_unknown_default : int, optional + foo: optional_unknown_default + with_default_syntax_1 : int, default 1 + foo: with_default_syntax_1 + with_default_syntax_2 : int, default: 2 + foo: with_default_syntax_2 + with_default_syntax_3 : int, default=3 + foo: with_default_syntax_3 + grouped_attribute_1, grouped_attribute_2 : int, default=4 + foo: grouped_attribute_1 and grouped_attribute_2 + """ + + def __init__(self) -> None: + pass + + +def function_with_result_value_and_type() -> None: + """ + Lorem ipsum. + + Dolor sit amet. + + Returns + ------- + int + this will be the return value + """ + + +def function_without_result_value() -> None: + """ + Lorem ipsum. + + Dolor sit amet. + """ diff --git a/tests/data/test_docstring_parser_package/test_plaintext.py b/tests/data/test_docstring_parser_package/test_plaintext.py new file mode 100644 index 00000000..d8387b2e --- /dev/null +++ b/tests/data/test_docstring_parser_package/test_plaintext.py @@ -0,0 +1,25 @@ +class ClassWithDocumentation: + """ + Lorem ipsum. + + Dolor sit amet. + """ + + def __init__(self, p: int) -> None: + pass + + +class ClassWithoutDocumentation: + pass + + +def function_with_documentation(p: int) -> None: + """ + Lorem ipsum. + + Dolor sit amet. + """ + + +def function_without_documentation(p: int) -> None: + pass diff --git a/tests/data/test_docstring_parser_package/test_restdoc.py b/tests/data/test_docstring_parser_package/test_restdoc.py new file mode 100644 index 00000000..c7bcc5ef --- /dev/null +++ b/tests/data/test_docstring_parser_package/test_restdoc.py @@ -0,0 +1,87 @@ +class ClassWithDocumentation: + """ + Lorem ipsum. Code:: + + pass + + Dolor sit amet. + """ + + +class ClassWithoutDocumentation: + pass + + +def function_with_documentation() -> None: + """ + Lorem ipsum. Code:: + + pass + + Dolor sit amet. + """ + + +def function_without_documentation() -> None: + pass + + +class ClassWithParameters: + """ + Lorem ipsum. + + Dolor sit amet. + + :param p: foo defaults to 1 + :type p: int + """ + + def __init__(self) -> None: + pass + + +def function_with_parameters() -> None: + """ + Lorem ipsum. + + Dolor sit amet. + + :param no_type_no_default: no type and no default + :param type_no_default: type but no default + :type type_no_default: int + :param with_default: foo that defaults to 2 + :type with_default: int + :param *args: foo: *args + :type *args: int + :param **kwargs: foo: **kwargs + :type **kwargs: int + """ + + +def function_with_return_value_and_type() -> None: + """ + Lorem ipsum. + + Dolor sit amet. + + :return: return value + :rtype: bool + """ + + +def function_with_return_value_no_type() -> None: + """ + Lorem ipsum. + + Dolor sit amet. + + :return: return value + """ + + +def function_without_return_value() -> None: + """ + Lorem ipsum. + + Dolor sit amet. + """ diff --git a/tests/data/test_package/__init__.py b/tests/data/test_package/__init__.py new file mode 100644 index 00000000..d2c2476b --- /dev/null +++ b/tests/data/test_package/__init__.py @@ -0,0 +1,17 @@ +from . import _reexport_module_1 as reex_1 +from ._reexport_module_1 import ReexportClass +from ._reexport_module_2 import reexported_function_2 +from ._reexport_module_3 import * +from ._reexport_module_4 import FourthReexportClass +from ._reexport_module_4 import _reexported_function_4 +from .test_enums import _ReexportedEmptyEnum + +__all__ = [ + "reex_1", + "ReexportClass", + "reexported_function_2", + "reexported_function_3", + "_reexported_function_4", + "FourthReexportClass", + "_ReexportedEmptyEnum", +] diff --git a/tests/data/test_package/_reexport_module_1.py b/tests/data/test_package/_reexport_module_1.py new file mode 100644 index 00000000..89fec437 --- /dev/null +++ b/tests/data/test_package/_reexport_module_1.py @@ -0,0 +1,8 @@ +class ReexportClass: + @staticmethod + def _private_class_method_of_reexported_class() -> None: + pass + + +def reexported_function() -> None: + pass diff --git a/tests/data/test_package/_reexport_module_2.py b/tests/data/test_package/_reexport_module_2.py new file mode 100644 index 00000000..fc2dadad --- /dev/null +++ b/tests/data/test_package/_reexport_module_2.py @@ -0,0 +1,6 @@ +class AnotherReexportClass: + pass + + +def reexported_function_2() -> None: + pass diff --git a/tests/data/test_package/_reexport_module_3.py b/tests/data/test_package/_reexport_module_3.py new file mode 100644 index 00000000..deb00c69 --- /dev/null +++ b/tests/data/test_package/_reexport_module_3.py @@ -0,0 +1,6 @@ +class _ThirdReexportClass: + pass + + +def reexported_function_3() -> None: + pass diff --git a/tests/data/test_package/_reexport_module_4.py b/tests/data/test_package/_reexport_module_4.py new file mode 100644 index 00000000..c7818391 --- /dev/null +++ b/tests/data/test_package/_reexport_module_4.py @@ -0,0 +1,10 @@ +class FourthReexportClass: + pass + + +def _unreexported_function() -> None: + pass + + +def _reexported_function_4() -> None: + pass diff --git a/tests/data/test_package/another_path/another_module.py b/tests/data/test_package/another_path/another_module.py new file mode 100644 index 00000000..0d53fe84 --- /dev/null +++ b/tests/data/test_package/another_path/another_module.py @@ -0,0 +1,8 @@ +"""Another Module Docstring. + +Full Docstring Description +""" + + +class AnotherClass: + pass diff --git a/tests/data/test_package/test_docstrings.py b/tests/data/test_package/test_docstrings.py new file mode 100644 index 00000000..a5313d6e --- /dev/null +++ b/tests/data/test_package/test_docstrings.py @@ -0,0 +1,142 @@ +"""Test module for docstring tests. + +A module for testing the various docstring types. +""" + + +class EpydocDocstringClass: + """ + A class with a variety of different methods for calculations. (Epydoc). + + @ivar attr_1: Attribute of the calculator. (Epydoc) + @type attr_1: str + @param param_1: Parameter of the calculator. (Epydoc) + @type param_1: str + """ + + attr_1: str + + def __init__(self, param_1: str): + pass + + def epydoc_docstring_func(self, x: int, y: int) -> bool: + """ + This function checks if the sum of x and y is less than the value 10 and returns True if it is. (Epydoc). + + @param x: First integer value for the calculation. (Epydoc) + @type x: int + @param y: Second integer value for the calculation. (Epydoc) + @type y: int + @return: Checks if the sum of x and y is greater than 10. (Epydoc) + @rtype: bool + """ + z = x + y + return z < 10 + + +class RestDocstringClass: + """ + A class with a variety of different methods for calculations. (ReST). + + :param attr_1: Attribute of the calculator. (ReST) + :type attr_1: str + :param param_1: Parameter of the calculator. (ReST) + :type param_1: str + """ + + attr_1: str + + def __init__(self, param_1: str): + pass + + def rest_docstring_func(self, x: int, y: int) -> bool: + """ + This function checks if the sum of x and y is less than the value 10 + and returns True if it is. (ReST). + + :param x: First integer value for the calculation. (ReST) + :type x: int + :param y: Second integer value for the calculation. (ReST) + :type y: int + :returns: Checks if the sum of x and y is greater than 10. (ReST) + :rtype: bool + """ + z = x + y + return z < 10 + + +class NumpyDocstringClass: + """A class that calculates stuff. (Numpy). + + A class with a variety of different methods for calculations. (Numpy) + + Attributes + ---------- + attr_1 : str + Attribute of the calculator. (Numpy) + + Parameters + ---------- + param_1 : str + Parameter of the calculator. (Numpy) + """ + + attr_1: str + + def __init__(self, param_1: str): + pass + + def numpy_docstring_func(self, x: int, y: int) -> bool: + """Checks if the sum of two variables is over the value of 10. (Numpy). + + This function checks if the sum of `x` and `y` is less than the value 10 and returns True if it is. (Numpy) + + Parameters + ---------- + x : int + First integer value for the calculation. (Numpy) + y : int + Second integer value for the calculation. (Numpy) + + Returns + ------- + bool + Checks if the sum of `x` and `y` is greater than 10. (Numpy) + """ + z = x + y + return z < 10 + + +class GoogleDocstringClass: + """A class that calculates stuff. (Google Style). + + A class with a variety of different methods for calculations. (Google Style) + + Attributes: + attr_1 (str): Attribute of the calculator. (Google Style) + + Args: + param_1 (str): Parameter of the calculator. (Google Style) + """ + + attr_1: str + + def __init__(self, param_1: str): + pass + + def google_docstring_func(self, x: int, y: int) -> bool: + """Checks if the sum of two variables is over the value of 10. (Google Style). + + This function checks if the sum of x and y is less than the value 10 + and returns True if it is. (Google Style) + + Args: + x (int): First integer value for the calculation. (Google Style) + y (int): Second integer value for the calculation. (Google Style) + + Returns: + bool: Checks if the sum of x and y is greater than 10 and returns + a boolean value. (Google Style) + """ + z = x + y + return z < 10 diff --git a/tests/data/test_package/test_enums.py b/tests/data/test_package/test_enums.py new file mode 100644 index 00000000..432c8b8d --- /dev/null +++ b/tests/data/test_package/test_enums.py @@ -0,0 +1,27 @@ +from enum import Enum, IntEnum +from enum import Enum as _Enum + +from .another_path.another_module import AnotherClass as _AcImportAlias + + +class EnumTest(Enum): + """Enum Docstring. + + Full Docstring Description + """ + + ONE = "first" + TWO = (2, 2) + THREE = 3 + FOUR = FIVE = "forth and fifth" + SIX, SEVEN = ("sixth", 7) + EIGHT, NINE = "89" + TEN = _AcImportAlias() + + +class _ReexportedEmptyEnum(_Enum): + """Nothing's here.""" + + +class AnotherTestEnum(IntEnum): + ELEVEN = 11 diff --git a/tests/data/test_package/test_module.py b/tests/data/test_package/test_module.py new file mode 100644 index 00000000..90f37be2 --- /dev/null +++ b/tests/data/test_package/test_module.py @@ -0,0 +1,128 @@ +"""Docstring of the some_class.py module.""" +# noinspection PyUnresolvedReferences +import math as mathematics +from typing import * + +# noinspection PyUnresolvedReferences +import mypy + +# noinspection PyUnresolvedReferences +from docstring_parser import * + +from .another_path.another_module import AnotherClass +from .another_path.another_module import AnotherClass as _AcImportAlias + +AcDoubleAlias = _AcImportAlias +ac_alias = AnotherClass + + +# noinspection PyUnusedLocal +def global_func(param_1: str = "first param", param_2: ac_alias | None = None) -> ac_alias: + """Docstring 1. + + Docstring 2. + """ + + +def _private_global_func() -> _AcImportAlias | AcDoubleAlias | ac_alias: + pass + + +class SomeClass(AcDoubleAlias): + """Summary of the description. + + Full description + """ + + type_hint_public: int + _type_hint_private: int + + no_type_hint_public = 1 + _no_type_hint_private = 1 + + object_attr: _AcImportAlias + object_attr_2: AcDoubleAlias + + tuple_attr_1: tuple + tuple_attr_2: tuple[str | int] + tuple_attr_3: tuple[str, int] + + list_attr_1: list + list_attr_2: list[str | _AcImportAlias] + list_attr_3: list[str, AcDoubleAlias] + list_attr_4: list[str, _AcImportAlias | int] + + dict_attr_1: dict + dict_attr_2: dict[str, int] + dict_attr_3: dict[str | int, None | _AcImportAlias] + + bool_attr: bool + none_attr: None + flaot_attr: float + int_or_bool_attr: int | bool + str_attr_with_none_value: str = None + + mulit_attr_1, _mulit_attr_2_private = (123456, "I am a String") + mulit_attr_3 = _mulit_attr_4_private = ["I am some", "kind of list"] + + # noinspection PyUnusedLocal + def __init__(self, init_param_1): + """Summary of the init description. + + Full init description. + """ + self.init_attr: bool + # noinspection PyTypeChecker + self._init_attr_private: float = "I'm a string" + no_class_attr: bool + + def _some_function(self, param_1: bool, param_2: ac_alias) -> ac_alias: + """Function Docstring. + + param_1: bool. + """ + + @staticmethod + def static_function(param_1: bool, param_2: int | bool = 123456) -> tuple[bool, int]: + """Function Docstring.""" + + def test_position(self, param1, /, param2: bool, param3=1, *, param4=AcDoubleAlias(), param5: int = 1) -> Any: + """Function Docstring.""" + + @staticmethod + def test_params(*args, **kwargs): + pass + + @staticmethod + def multiple_results(param_1: int) -> Any | tuple[int, str]: + """Function Docstring.""" + + def no_return_1(self): + pass + + def no_return_2(self) -> None: + pass + + class NestedClass(_AcImportAlias): + def nested_class_function(self, param_1: int) -> set[bool | None]: + pass + + +class _PrivateClass: + public_attr_in_private_class = 0 + + def __init__(self): + self.public_init_attr_in_private_class: int = 0 + + def public_func_in_private_class(self): + pass + + class NestedPrivateClass: + nested_class_attr = 0 + + @staticmethod + def static_nested_private_class_function(): + pass + + class NestedNestedPrivateClass: + pass diff --git a/tests/safeds_stubgen/__init__.py b/tests/safeds_stubgen/__init__.py index e69de29b..9d48db4f 100644 --- a/tests/safeds_stubgen/__init__.py +++ b/tests/safeds_stubgen/__init__.py @@ -0,0 +1 @@ +from __future__ import annotations diff --git a/tests/safeds_stubgen/__snapshots__/test_main.ambr b/tests/safeds_stubgen/__snapshots__/test_main.ambr new file mode 100644 index 00000000..66dd7350 --- /dev/null +++ b/tests/safeds_stubgen/__snapshots__/test_main.ambr @@ -0,0 +1,3026 @@ +# serializer version: 1 +# name: test_main + dict({ + 'attributes': list([ + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'attr_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'attr_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'attr_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'attr_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/_init_attr_private', + 'is_public': False, + 'is_static': False, + 'name': '_init_attr_private', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'float', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/_mulit_attr_2_private', + 'is_public': False, + 'is_static': True, + 'name': '_mulit_attr_2_private', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/_mulit_attr_4_private', + 'is_public': False, + 'is_static': True, + 'name': '_mulit_attr_4_private', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/_no_type_hint_private', + 'is_public': False, + 'is_static': True, + 'name': '_no_type_hint_private', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/_type_hint_private', + 'is_public': False, + 'is_static': True, + 'name': '_type_hint_private', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/bool_attr', + 'is_public': True, + 'is_static': True, + 'name': 'bool_attr', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/dict_attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'dict_attr_1', + 'type': dict({ + 'key_type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + 'kind': 'DictType', + 'value_type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/dict_attr_2', + 'is_public': True, + 'is_static': True, + 'name': 'dict_attr_2', + 'type': dict({ + 'key_type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + 'kind': 'DictType', + 'value_type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/dict_attr_3', + 'is_public': True, + 'is_static': True, + 'name': 'dict_attr_3', + 'type': dict({ + 'key_type': dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + ]), + }), + 'kind': 'DictType', + 'value_type': dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'None', + }), + dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + ]), + }), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/flaot_attr', + 'is_public': True, + 'is_static': True, + 'name': 'flaot_attr', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'float', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/init_attr', + 'is_public': True, + 'is_static': False, + 'name': 'init_attr', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/int_or_bool_attr', + 'is_public': True, + 'is_static': True, + 'name': 'int_or_bool_attr', + 'type': dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/list_attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'list_attr_1', + 'type': dict({ + 'kind': 'ListType', + 'types': list([ + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/list_attr_2', + 'is_public': True, + 'is_static': True, + 'name': 'list_attr_2', + 'type': dict({ + 'kind': 'ListType', + 'types': list([ + dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + dict({ + 'kind': 'NamedType', + 'name': '_AcImportAlias', + }), + ]), + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/list_attr_3', + 'is_public': True, + 'is_static': True, + 'name': 'list_attr_3', + 'type': dict({ + 'kind': 'ListType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + dict({ + 'kind': 'NamedType', + 'name': 'AcDoubleAlias', + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/list_attr_4', + 'is_public': True, + 'is_static': True, + 'name': 'list_attr_4', + 'type': dict({ + 'kind': 'ListType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': '_AcImportAlias', + }), + dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + ]), + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/mulit_attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'mulit_attr_1', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/mulit_attr_3', + 'is_public': True, + 'is_static': True, + 'name': 'mulit_attr_3', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/no_type_hint_public', + 'is_public': True, + 'is_static': True, + 'name': 'no_type_hint_public', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/none_attr', + 'is_public': True, + 'is_static': True, + 'name': 'none_attr', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'None', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/object_attr', + 'is_public': True, + 'is_static': True, + 'name': 'object_attr', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/object_attr_2', + 'is_public': True, + 'is_static': True, + 'name': 'object_attr_2', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/str_attr_with_none_value', + 'is_public': True, + 'is_static': True, + 'name': 'str_attr_with_none_value', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/tuple_attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'tuple_attr_1', + 'type': dict({ + 'kind': 'TupleType', + 'types': list([ + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/tuple_attr_2', + 'is_public': True, + 'is_static': True, + 'name': 'tuple_attr_2', + 'type': dict({ + 'kind': 'TupleType', + 'types': list([ + dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + ]), + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/tuple_attr_3', + 'is_public': True, + 'is_static': True, + 'name': 'tuple_attr_3', + 'type': dict({ + 'kind': 'TupleType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/type_hint_public', + 'is_public': True, + 'is_static': True, + 'name': 'type_hint_public', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/_PrivateClass/NestedPrivateClass/nested_class_attr', + 'is_public': False, + 'is_static': True, + 'name': 'nested_class_attr', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/_PrivateClass/public_attr_in_private_class', + 'is_public': False, + 'is_static': True, + 'name': 'public_attr_in_private_class', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/_PrivateClass/public_init_attr_in_private_class', + 'is_public': False, + 'is_static': False, + 'name': 'public_init_attr_in_private_class', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + ]), + 'classes': list([ + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_1/ReexportClass', + 'is_public': True, + 'methods': list([ + 'test_package/_reexport_module_1/ReexportClass/_private_class_method_of_reexported_class', + ]), + 'name': 'ReexportClass', + 'reexported_by': list([ + 'test_package/__init__', + ]), + 'superclasses': list([ + ]), + }), + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_2/AnotherReexportClass', + 'is_public': True, + 'methods': list([ + ]), + 'name': 'AnotherReexportClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }), + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_3/_ThirdReexportClass', + 'is_public': False, + 'methods': list([ + ]), + 'name': '_ThirdReexportClass', + 'reexported_by': list([ + 'test_package/__init__', + ]), + 'superclasses': list([ + ]), + }), + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_4/FourthReexportClass', + 'is_public': True, + 'methods': list([ + ]), + 'name': 'FourthReexportClass', + 'reexported_by': list([ + 'test_package/__init__', + ]), + 'superclasses': list([ + ]), + }), + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/another_module/AnotherClass', + 'is_public': True, + 'methods': list([ + ]), + 'name': 'AnotherClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }), + dict({ + 'attributes': list([ + 'test_package/test_docstrings/EpydocDocstringClass/attr_1', + ]), + 'classes': list([ + ]), + 'constructor': dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_docstrings/EpydocDocstringClass/__init__/self', + 'test_package/test_docstrings/EpydocDocstringClass/__init__/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + 'docstring': dict({ + 'description': ''' + A class with a variety of different methods for calculations. (Epydoc). + + @ivar attr_1: Attribute of the calculator. (Epydoc) + @type attr_1: str + @param param_1: Parameter of the calculator. (Epydoc) + @type param_1: str + ''', + 'full_docstring': ''' + A class with a variety of different methods for calculations. (Epydoc). + + @ivar attr_1: Attribute of the calculator. (Epydoc) + @type attr_1: str + @param param_1: Parameter of the calculator. (Epydoc) + @type param_1: str + ''', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass', + 'is_public': True, + 'methods': list([ + 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func', + ]), + 'name': 'EpydocDocstringClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }), + dict({ + 'attributes': list([ + 'test_package/test_docstrings/GoogleDocstringClass/attr_1', + ]), + 'classes': list([ + ]), + 'constructor': dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_docstrings/GoogleDocstringClass/__init__/self', + 'test_package/test_docstrings/GoogleDocstringClass/__init__/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + 'docstring': dict({ + 'description': ''' + A class that calculates stuff. (Google Style). + + A class with a variety of different methods for calculations. (Google Style) + + Attributes: + attr_1 (str): Attribute of the calculator. (Google Style) + + Args: + param_1 (str): Parameter of the calculator. (Google Style) + ''', + 'full_docstring': ''' + A class that calculates stuff. (Google Style). + + A class with a variety of different methods for calculations. (Google Style) + + Attributes: + attr_1 (str): Attribute of the calculator. (Google Style) + + Args: + param_1 (str): Parameter of the calculator. (Google Style) + ''', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass', + 'is_public': True, + 'methods': list([ + 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func', + ]), + 'name': 'GoogleDocstringClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }), + dict({ + 'attributes': list([ + 'test_package/test_docstrings/NumpyDocstringClass/attr_1', + ]), + 'classes': list([ + ]), + 'constructor': dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_docstrings/NumpyDocstringClass/__init__/self', + 'test_package/test_docstrings/NumpyDocstringClass/__init__/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + 'docstring': dict({ + 'description': ''' + A class that calculates stuff. (Numpy). + + A class with a variety of different methods for calculations. (Numpy) + + Attributes + ---------- + attr_1 : str + Attribute of the calculator. (Numpy) + + Parameters + ---------- + param_1 : str + Parameter of the calculator. (Numpy) + ''', + 'full_docstring': ''' + A class that calculates stuff. (Numpy). + + A class with a variety of different methods for calculations. (Numpy) + + Attributes + ---------- + attr_1 : str + Attribute of the calculator. (Numpy) + + Parameters + ---------- + param_1 : str + Parameter of the calculator. (Numpy) + ''', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass', + 'is_public': True, + 'methods': list([ + 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func', + ]), + 'name': 'NumpyDocstringClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }), + dict({ + 'attributes': list([ + 'test_package/test_docstrings/RestDocstringClass/attr_1', + ]), + 'classes': list([ + ]), + 'constructor': dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_docstrings/RestDocstringClass/__init__/self', + 'test_package/test_docstrings/RestDocstringClass/__init__/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + 'docstring': dict({ + 'description': ''' + A class with a variety of different methods for calculations. (ReST). + + :param attr_1: Attribute of the calculator. (ReST) + :type attr_1: str + :param param_1: Parameter of the calculator. (ReST) + :type param_1: str + ''', + 'full_docstring': ''' + A class with a variety of different methods for calculations. (ReST). + + :param attr_1: Attribute of the calculator. (ReST) + :type attr_1: str + :param param_1: Parameter of the calculator. (ReST) + :type param_1: str + ''', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass', + 'is_public': True, + 'methods': list([ + 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func', + ]), + 'name': 'RestDocstringClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }), + dict({ + 'attributes': list([ + 'test_package/test_module/SomeClass/type_hint_public', + 'test_package/test_module/SomeClass/_type_hint_private', + 'test_package/test_module/SomeClass/no_type_hint_public', + 'test_package/test_module/SomeClass/_no_type_hint_private', + 'test_package/test_module/SomeClass/object_attr', + 'test_package/test_module/SomeClass/object_attr_2', + 'test_package/test_module/SomeClass/tuple_attr_1', + 'test_package/test_module/SomeClass/tuple_attr_2', + 'test_package/test_module/SomeClass/tuple_attr_3', + 'test_package/test_module/SomeClass/list_attr_1', + 'test_package/test_module/SomeClass/list_attr_2', + 'test_package/test_module/SomeClass/list_attr_3', + 'test_package/test_module/SomeClass/list_attr_4', + 'test_package/test_module/SomeClass/dict_attr_1', + 'test_package/test_module/SomeClass/dict_attr_2', + 'test_package/test_module/SomeClass/dict_attr_3', + 'test_package/test_module/SomeClass/bool_attr', + 'test_package/test_module/SomeClass/none_attr', + 'test_package/test_module/SomeClass/flaot_attr', + 'test_package/test_module/SomeClass/int_or_bool_attr', + 'test_package/test_module/SomeClass/str_attr_with_none_value', + 'test_package/test_module/SomeClass/mulit_attr_1', + 'test_package/test_module/SomeClass/_mulit_attr_2_private', + 'test_package/test_module/SomeClass/mulit_attr_3', + 'test_package/test_module/SomeClass/_mulit_attr_4_private', + 'test_package/test_module/SomeClass/init_attr', + 'test_package/test_module/SomeClass/_init_attr_private', + ]), + 'classes': list([ + 'test_package/test_module/SomeClass/NestedClass', + ]), + 'constructor': dict({ + 'docstring': dict({ + 'description': ''' + Summary of the init description. + + Full init description. + ''', + 'full_docstring': ''' + Summary of the init description. + + Full init description. + ''', + }), + 'id': 'test_package/test_module/SomeClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_module/SomeClass/__init__/self', + 'test_package/test_module/SomeClass/__init__/init_param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + 'docstring': dict({ + 'description': ''' + Summary of the description. + + Full description + ''', + 'full_docstring': ''' + Summary of the description. + + Full description + ''', + }), + 'id': 'test_package/test_module/SomeClass', + 'is_public': True, + 'methods': list([ + 'test_package/test_module/SomeClass/_some_function', + 'test_package/test_module/SomeClass/static_function', + 'test_package/test_module/SomeClass/test_position', + 'test_package/test_module/SomeClass/test_params', + 'test_package/test_module/SomeClass/multiple_results', + 'test_package/test_module/SomeClass/no_return_1', + 'test_package/test_module/SomeClass/no_return_2', + ]), + 'name': 'SomeClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + 'tests.data.test_package.test_module.AcDoubleAlias', + ]), + }), + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/SomeClass/NestedClass', + 'is_public': True, + 'methods': list([ + 'test_package/test_module/SomeClass/NestedClass/nested_class_function', + ]), + 'name': 'NestedClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + 'tests.data.test_package.another_path.another_module.AnotherClass', + ]), + }), + dict({ + 'attributes': list([ + 'test_package/test_module/_PrivateClass/public_attr_in_private_class', + 'test_package/test_module/_PrivateClass/public_init_attr_in_private_class', + ]), + 'classes': list([ + 'test_package/test_module/_PrivateClass/NestedPrivateClass', + ]), + 'constructor': dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_PrivateClass/__init__', + 'is_public': False, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_module/_PrivateClass/__init__/self', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_PrivateClass', + 'is_public': False, + 'methods': list([ + 'test_package/test_module/_PrivateClass/public_func_in_private_class', + ]), + 'name': '_PrivateClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }), + dict({ + 'attributes': list([ + 'test_package/test_module/_PrivateClass/NestedPrivateClass/nested_class_attr', + ]), + 'classes': list([ + 'test_package/test_module/_PrivateClass/NestedPrivateClass/NestedNestedPrivateClass', + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_PrivateClass/NestedPrivateClass', + 'is_public': True, + 'methods': list([ + 'test_package/test_module/_PrivateClass/NestedPrivateClass/static_nested_private_class_function', + ]), + 'name': 'NestedPrivateClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }), + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_PrivateClass/NestedPrivateClass/NestedNestedPrivateClass', + 'is_public': True, + 'methods': list([ + ]), + 'name': 'NestedNestedPrivateClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }), + ]), + 'distribution': '', + 'enum_instances': list([ + dict({ + 'id': 'test_package/test_enums/AnotherTestEnum/ELEVEN', + 'name': 'ELEVEN', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/EIGHT', + 'name': 'EIGHT', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/FIVE', + 'name': 'FIVE', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/FOUR', + 'name': 'FOUR', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/NINE', + 'name': 'NINE', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/ONE', + 'name': 'ONE', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/SEVEN', + 'name': 'SEVEN', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/SIX', + 'name': 'SIX', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/TEN', + 'name': 'TEN', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/THREE', + 'name': 'THREE', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/TWO', + 'name': 'TWO', + }), + ]), + 'enums': list([ + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_enums/AnotherTestEnum', + 'instances': list([ + 'test_package/test_enums/AnotherTestEnum/ELEVEN', + ]), + 'name': 'AnotherTestEnum', + }), + dict({ + 'docstring': dict({ + 'description': ''' + Enum Docstring. + + Full Docstring Description + ''', + 'full_docstring': ''' + Enum Docstring. + + Full Docstring Description + ''', + }), + 'id': 'test_package/test_enums/EnumTest', + 'instances': list([ + 'test_package/test_enums/EnumTest/ONE', + 'test_package/test_enums/EnumTest/TWO', + 'test_package/test_enums/EnumTest/THREE', + 'test_package/test_enums/EnumTest/FOUR', + 'test_package/test_enums/EnumTest/FIVE', + 'test_package/test_enums/EnumTest/SIX', + 'test_package/test_enums/EnumTest/SEVEN', + 'test_package/test_enums/EnumTest/EIGHT', + 'test_package/test_enums/EnumTest/NINE', + 'test_package/test_enums/EnumTest/TEN', + ]), + 'name': 'EnumTest', + }), + dict({ + 'docstring': dict({ + 'description': "Nothing's here.", + 'full_docstring': "Nothing's here.", + }), + 'id': 'test_package/test_enums/_ReexportedEmptyEnum', + 'instances': list([ + ]), + 'name': '_ReexportedEmptyEnum', + }), + ]), + 'functions': list([ + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_1/ReexportClass/_private_class_method_of_reexported_class', + 'is_public': False, + 'is_static': True, + 'name': '_private_class_method_of_reexported_class', + 'parameters': list([ + ]), + 'reexported_by': list([ + 'test_package/__init__', + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_1/reexported_function', + 'is_public': False, + 'is_static': False, + 'name': 'reexported_function', + 'parameters': list([ + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_2/reexported_function_2', + 'is_public': True, + 'is_static': False, + 'name': 'reexported_function_2', + 'parameters': list([ + ]), + 'reexported_by': list([ + 'test_package/__init__', + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_3/reexported_function_3', + 'is_public': False, + 'is_static': False, + 'name': 'reexported_function_3', + 'parameters': list([ + ]), + 'reexported_by': list([ + 'test_package/__init__', + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_4/_reexported_function_4', + 'is_public': False, + 'is_static': False, + 'name': '_reexported_function_4', + 'parameters': list([ + ]), + 'reexported_by': list([ + 'test_package/__init__', + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_4/_unreexported_function', + 'is_public': False, + 'is_static': False, + 'name': '_unreexported_function', + 'parameters': list([ + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_docstrings/EpydocDocstringClass/__init__/self', + 'test_package/test_docstrings/EpydocDocstringClass/__init__/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': ''' + This function checks if the sum of x and y is less than the value 10 and returns True if it is. (Epydoc). + + @param x: First integer value for the calculation. (Epydoc) + @type x: int + @param y: Second integer value for the calculation. (Epydoc) + @type y: int + @return: Checks if the sum of x and y is greater than 10. (Epydoc) + @rtype: bool + ''', + 'full_docstring': ''' + This function checks if the sum of x and y is less than the value 10 and returns True if it is. (Epydoc). + + @param x: First integer value for the calculation. (Epydoc) + @type x: int + @param y: Second integer value for the calculation. (Epydoc) + @type y: int + @return: Checks if the sum of x and y is greater than 10. (Epydoc) + @rtype: bool + ''', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func', + 'is_public': True, + 'is_static': False, + 'name': 'epydoc_docstring_func', + 'parameters': list([ + 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/self', + 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/x', + 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/y', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/result_1', + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_docstrings/GoogleDocstringClass/__init__/self', + 'test_package/test_docstrings/GoogleDocstringClass/__init__/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': ''' + Checks if the sum of two variables is over the value of 10. (Google Style). + + This function checks if the sum of x and y is less than the value 10 + and returns True if it is. (Google Style) + + Args: + x (int): First integer value for the calculation. (Google Style) + y (int): Second integer value for the calculation. (Google Style) + + Returns: + bool: Checks if the sum of x and y is greater than 10 and returns + a boolean value. (Google Style) + ''', + 'full_docstring': ''' + Checks if the sum of two variables is over the value of 10. (Google Style). + + This function checks if the sum of x and y is less than the value 10 + and returns True if it is. (Google Style) + + Args: + x (int): First integer value for the calculation. (Google Style) + y (int): Second integer value for the calculation. (Google Style) + + Returns: + bool: Checks if the sum of x and y is greater than 10 and returns + a boolean value. (Google Style) + ''', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func', + 'is_public': True, + 'is_static': False, + 'name': 'google_docstring_func', + 'parameters': list([ + 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/self', + 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/x', + 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/y', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/result_1', + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_docstrings/NumpyDocstringClass/__init__/self', + 'test_package/test_docstrings/NumpyDocstringClass/__init__/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': ''' + Checks if the sum of two variables is over the value of 10. (Numpy). + + This function checks if the sum of `x` and `y` is less than the value 10 and returns True if it is. (Numpy) + + Parameters + ---------- + x : int + First integer value for the calculation. (Numpy) + y : int + Second integer value for the calculation. (Numpy) + + Returns + ------- + bool + Checks if the sum of `x` and `y` is greater than 10. (Numpy) + ''', + 'full_docstring': ''' + Checks if the sum of two variables is over the value of 10. (Numpy). + + This function checks if the sum of `x` and `y` is less than the value 10 and returns True if it is. (Numpy) + + Parameters + ---------- + x : int + First integer value for the calculation. (Numpy) + y : int + Second integer value for the calculation. (Numpy) + + Returns + ------- + bool + Checks if the sum of `x` and `y` is greater than 10. (Numpy) + ''', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func', + 'is_public': True, + 'is_static': False, + 'name': 'numpy_docstring_func', + 'parameters': list([ + 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/self', + 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/x', + 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/y', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/result_1', + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_docstrings/RestDocstringClass/__init__/self', + 'test_package/test_docstrings/RestDocstringClass/__init__/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': ''' + This function checks if the sum of x and y is less than the value 10 + and returns True if it is. (ReST). + + :param x: First integer value for the calculation. (ReST) + :type x: int + :param y: Second integer value for the calculation. (ReST) + :type y: int + :returns: Checks if the sum of x and y is greater than 10. (ReST) + :rtype: bool + ''', + 'full_docstring': ''' + This function checks if the sum of x and y is less than the value 10 + and returns True if it is. (ReST). + + :param x: First integer value for the calculation. (ReST) + :type x: int + :param y: Second integer value for the calculation. (ReST) + :type y: int + :returns: Checks if the sum of x and y is greater than 10. (ReST) + :rtype: bool + ''', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func', + 'is_public': True, + 'is_static': False, + 'name': 'rest_docstring_func', + 'parameters': list([ + 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/self', + 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/x', + 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/y', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/result_1', + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/SomeClass/NestedClass/nested_class_function', + 'is_public': True, + 'is_static': False, + 'name': 'nested_class_function', + 'parameters': list([ + 'test_package/test_module/SomeClass/NestedClass/nested_class_function/self', + 'test_package/test_module/SomeClass/NestedClass/nested_class_function/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_module/SomeClass/NestedClass/nested_class_function/result_1', + ]), + }), + dict({ + 'docstring': dict({ + 'description': ''' + Summary of the init description. + + Full init description. + ''', + 'full_docstring': ''' + Summary of the init description. + + Full init description. + ''', + }), + 'id': 'test_package/test_module/SomeClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_module/SomeClass/__init__/self', + 'test_package/test_module/SomeClass/__init__/init_param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': ''' + Function Docstring. + + param_1: bool. + ''', + 'full_docstring': ''' + Function Docstring. + + param_1: bool. + ''', + }), + 'id': 'test_package/test_module/SomeClass/_some_function', + 'is_public': False, + 'is_static': False, + 'name': '_some_function', + 'parameters': list([ + 'test_package/test_module/SomeClass/_some_function/self', + 'test_package/test_module/SomeClass/_some_function/param_1', + 'test_package/test_module/SomeClass/_some_function/param_2', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_module/SomeClass/_some_function/result_1', + ]), + }), + dict({ + 'docstring': dict({ + 'description': 'Function Docstring.', + 'full_docstring': 'Function Docstring.', + }), + 'id': 'test_package/test_module/SomeClass/multiple_results', + 'is_public': True, + 'is_static': True, + 'name': 'multiple_results', + 'parameters': list([ + 'test_package/test_module/SomeClass/multiple_results/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_module/SomeClass/multiple_results/result_1', + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/SomeClass/no_return_1', + 'is_public': True, + 'is_static': False, + 'name': 'no_return_1', + 'parameters': list([ + 'test_package/test_module/SomeClass/no_return_1/self', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/SomeClass/no_return_2', + 'is_public': True, + 'is_static': False, + 'name': 'no_return_2', + 'parameters': list([ + 'test_package/test_module/SomeClass/no_return_2/self', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': 'Function Docstring.', + 'full_docstring': 'Function Docstring.', + }), + 'id': 'test_package/test_module/SomeClass/static_function', + 'is_public': True, + 'is_static': True, + 'name': 'static_function', + 'parameters': list([ + 'test_package/test_module/SomeClass/static_function/param_1', + 'test_package/test_module/SomeClass/static_function/param_2', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_module/SomeClass/static_function/result_1', + 'test_package/test_module/SomeClass/static_function/result_2', + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/SomeClass/test_params', + 'is_public': True, + 'is_static': True, + 'name': 'test_params', + 'parameters': list([ + 'test_package/test_module/SomeClass/test_params/args', + 'test_package/test_module/SomeClass/test_params/kwargs', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': 'Function Docstring.', + 'full_docstring': 'Function Docstring.', + }), + 'id': 'test_package/test_module/SomeClass/test_position', + 'is_public': True, + 'is_static': False, + 'name': 'test_position', + 'parameters': list([ + 'test_package/test_module/SomeClass/test_position/self', + 'test_package/test_module/SomeClass/test_position/param1', + 'test_package/test_module/SomeClass/test_position/param2', + 'test_package/test_module/SomeClass/test_position/param3', + 'test_package/test_module/SomeClass/test_position/param4', + 'test_package/test_module/SomeClass/test_position/param5', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_module/SomeClass/test_position/result_1', + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_PrivateClass/NestedPrivateClass/static_nested_private_class_function', + 'is_public': False, + 'is_static': True, + 'name': 'static_nested_private_class_function', + 'parameters': list([ + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_PrivateClass/__init__', + 'is_public': False, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_module/_PrivateClass/__init__/self', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_PrivateClass/public_func_in_private_class', + 'is_public': False, + 'is_static': False, + 'name': 'public_func_in_private_class', + 'parameters': list([ + 'test_package/test_module/_PrivateClass/public_func_in_private_class/self', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_private_global_func', + 'is_public': False, + 'is_static': False, + 'name': '_private_global_func', + 'parameters': list([ + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_module/_private_global_func/result_1', + ]), + }), + dict({ + 'docstring': dict({ + 'description': ''' + Docstring 1. + + Docstring 2. + ''', + 'full_docstring': ''' + Docstring 1. + + Docstring 2. + ''', + }), + 'id': 'test_package/test_module/global_func', + 'is_public': True, + 'is_static': False, + 'name': 'global_func', + 'parameters': list([ + 'test_package/test_module/global_func/param_1', + 'test_package/test_module/global_func/param_2', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_module/global_func/result_1', + ]), + }), + ]), + 'modules': list([ + dict({ + 'classes': list([ + ]), + 'docstring': '', + 'enums': list([ + ]), + 'functions': list([ + ]), + 'id': 'test_package/__init__', + 'name': '__init__', + 'qualified_imports': list([ + dict({ + 'alias': 'reex_1', + 'qualified_name': '._reexport_module_1', + }), + dict({ + 'alias': None, + 'qualified_name': '_reexport_module_1.ReexportClass', + }), + dict({ + 'alias': None, + 'qualified_name': '_reexport_module_2.reexported_function_2', + }), + dict({ + 'alias': None, + 'qualified_name': '_reexport_module_4.FourthReexportClass', + }), + dict({ + 'alias': None, + 'qualified_name': '_reexport_module_4._reexported_function_4', + }), + dict({ + 'alias': None, + 'qualified_name': 'test_enums._ReexportedEmptyEnum', + }), + ]), + 'wildcard_imports': list([ + dict({ + 'module_name': '_reexport_module_3', + }), + ]), + }), + dict({ + 'classes': list([ + 'test_package/_reexport_module_1/ReexportClass', + ]), + 'docstring': '', + 'enums': list([ + ]), + 'functions': list([ + 'test_package/_reexport_module_1/reexported_function', + ]), + 'id': 'test_package/_reexport_module_1', + 'name': '_reexport_module_1', + 'qualified_imports': list([ + ]), + 'wildcard_imports': list([ + ]), + }), + dict({ + 'classes': list([ + 'test_package/_reexport_module_2/AnotherReexportClass', + ]), + 'docstring': '', + 'enums': list([ + ]), + 'functions': list([ + 'test_package/_reexport_module_2/reexported_function_2', + ]), + 'id': 'test_package/_reexport_module_2', + 'name': '_reexport_module_2', + 'qualified_imports': list([ + ]), + 'wildcard_imports': list([ + ]), + }), + dict({ + 'classes': list([ + 'test_package/_reexport_module_3/_ThirdReexportClass', + ]), + 'docstring': '', + 'enums': list([ + ]), + 'functions': list([ + 'test_package/_reexport_module_3/reexported_function_3', + ]), + 'id': 'test_package/_reexport_module_3', + 'name': '_reexport_module_3', + 'qualified_imports': list([ + ]), + 'wildcard_imports': list([ + ]), + }), + dict({ + 'classes': list([ + 'test_package/_reexport_module_4/FourthReexportClass', + ]), + 'docstring': '', + 'enums': list([ + ]), + 'functions': list([ + 'test_package/_reexport_module_4/_unreexported_function', + 'test_package/_reexport_module_4/_reexported_function_4', + ]), + 'id': 'test_package/_reexport_module_4', + 'name': '_reexport_module_4', + 'qualified_imports': list([ + ]), + 'wildcard_imports': list([ + ]), + }), + dict({ + 'classes': list([ + 'test_package/another_module/AnotherClass', + ]), + 'docstring': ''' + Another Module Docstring. + + Full Docstring Description + + ''', + 'enums': list([ + ]), + 'functions': list([ + ]), + 'id': 'test_package/another_module', + 'name': 'another_module', + 'qualified_imports': list([ + ]), + 'wildcard_imports': list([ + ]), + }), + dict({ + 'classes': list([ + 'test_package/test_docstrings/EpydocDocstringClass', + 'test_package/test_docstrings/RestDocstringClass', + 'test_package/test_docstrings/NumpyDocstringClass', + 'test_package/test_docstrings/GoogleDocstringClass', + ]), + 'docstring': ''' + Test module for docstring tests. + + A module for testing the various docstring types. + + ''', + 'enums': list([ + ]), + 'functions': list([ + ]), + 'id': 'test_package/test_docstrings', + 'name': 'test_docstrings', + 'qualified_imports': list([ + ]), + 'wildcard_imports': list([ + ]), + }), + dict({ + 'classes': list([ + ]), + 'docstring': '', + 'enums': list([ + 'test_package/test_enums/EnumTest', + 'test_package/test_enums/_ReexportedEmptyEnum', + 'test_package/test_enums/AnotherTestEnum', + ]), + 'functions': list([ + ]), + 'id': 'test_package/test_enums', + 'name': 'test_enums', + 'qualified_imports': list([ + dict({ + 'alias': None, + 'qualified_name': 'enum.Enum', + }), + dict({ + 'alias': None, + 'qualified_name': 'enum.IntEnum', + }), + dict({ + 'alias': '_Enum', + 'qualified_name': 'enum.Enum', + }), + dict({ + 'alias': '_AcImportAlias', + 'qualified_name': 'another_path.another_module.AnotherClass', + }), + ]), + 'wildcard_imports': list([ + ]), + }), + dict({ + 'classes': list([ + 'test_package/test_module/SomeClass', + 'test_package/test_module/_PrivateClass', + ]), + 'docstring': 'Docstring of the some_class.py module.', + 'enums': list([ + ]), + 'functions': list([ + 'test_package/test_module/global_func', + 'test_package/test_module/_private_global_func', + ]), + 'id': 'test_package/test_module', + 'name': 'test_module', + 'qualified_imports': list([ + dict({ + 'alias': 'mathematics', + 'qualified_name': 'math', + }), + dict({ + 'alias': None, + 'qualified_name': 'mypy', + }), + dict({ + 'alias': None, + 'qualified_name': 'another_path.another_module.AnotherClass', + }), + dict({ + 'alias': '_AcImportAlias', + 'qualified_name': 'another_path.another_module.AnotherClass', + }), + ]), + 'wildcard_imports': list([ + dict({ + 'module_name': 'typing', + }), + dict({ + 'module_name': 'docstring_parser', + }), + ]), + }), + ]), + 'package': 'test_package', + 'parameters': list([ + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/__init__/param_1', + 'is_optional': False, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/__init__/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'EpydocDocstringClass', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'EpydocDocstringClass', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/x', + 'is_optional': False, + 'name': 'x', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/y', + 'is_optional': False, + 'name': 'y', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/__init__/param_1', + 'is_optional': False, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/__init__/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'GoogleDocstringClass', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'GoogleDocstringClass', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/x', + 'is_optional': False, + 'name': 'x', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/y', + 'is_optional': False, + 'name': 'y', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/__init__/param_1', + 'is_optional': False, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/__init__/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'NumpyDocstringClass', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'NumpyDocstringClass', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/x', + 'is_optional': False, + 'name': 'x', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/y', + 'is_optional': False, + 'name': 'y', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/__init__/param_1', + 'is_optional': False, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/__init__/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'RestDocstringClass', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'RestDocstringClass', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/x', + 'is_optional': False, + 'name': 'x', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/y', + 'is_optional': False, + 'name': 'y', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/NestedClass/nested_class_function/param_1', + 'is_optional': False, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/NestedClass/nested_class_function/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'NestedClass', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/__init__/init_param_1', + 'is_optional': False, + 'name': 'init_param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/__init__/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'SomeClass', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/_some_function/param_1', + 'is_optional': False, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/_some_function/param_2', + 'is_optional': False, + 'name': 'param_2', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/_some_function/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'SomeClass', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/multiple_results/param_1', + 'is_optional': False, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/no_return_1/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'SomeClass', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/no_return_2/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'SomeClass', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/static_function/param_1', + 'is_optional': False, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': 123456, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/static_function/param_2', + 'is_optional': True, + 'name': 'param_2', + 'type': dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + ]), + }), + }), + dict({ + 'assigned_by': 'POSITIONAL_VARARG', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_params/args', + 'is_optional': False, + 'name': 'args', + 'type': dict({ + 'kind': 'TupleType', + 'types': list([ + ]), + }), + }), + dict({ + 'assigned_by': 'NAMED_VARARG', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_params/kwargs', + 'is_optional': False, + 'name': 'kwargs', + 'type': dict({ + 'key_type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + 'kind': 'DictType', + 'value_type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + }), + }), + dict({ + 'assigned_by': 'POSITION_ONLY', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_position/param1', + 'is_optional': False, + 'name': 'param1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_position/param2', + 'is_optional': False, + 'name': 'param2', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': 1, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_position/param3', + 'is_optional': True, + 'name': 'param3', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + }), + dict({ + 'assigned_by': 'NAME_ONLY', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_position/param4', + 'is_optional': True, + 'name': 'param4', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + }), + dict({ + 'assigned_by': 'NAME_ONLY', + 'default_value': 1, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_position/param5', + 'is_optional': True, + 'name': 'param5', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_position/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'SomeClass', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/_PrivateClass/__init__/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': '_PrivateClass', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/_PrivateClass/public_func_in_private_class/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': '_PrivateClass', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': 'first param', + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/global_func/param_1', + 'is_optional': True, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/global_func/param_2', + 'is_optional': True, + 'name': 'param_2', + 'type': dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + dict({ + 'kind': 'NamedType', + 'name': 'None', + }), + ]), + }), + }), + ]), + 'results': list([ + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/NestedClass/nested_class_function/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'SetType', + 'types': list([ + dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + dict({ + 'kind': 'NamedType', + 'name': 'None', + }), + ]), + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/_some_function/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/multiple_results/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + dict({ + 'kind': 'TupleType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + ]), + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/static_function/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/static_function/result_2', + 'name': 'result_2', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_position/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/_private_global_func/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/global_func/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + }), + ]), + 'schemaVersion': 1, + 'version': '', + }) +# --- diff --git a/tests/safeds_stubgen/_helpers.py b/tests/safeds_stubgen/_helpers.py new file mode 100644 index 00000000..3f0691ae --- /dev/null +++ b/tests/safeds_stubgen/_helpers.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from mypy.nodes import ClassDef, FuncDef + +if TYPE_CHECKING: + from mypy.nodes import MypyFile + + +def get_specific_mypy_node( + mypy_file: MypyFile, + node_name: str, +) -> ClassDef | FuncDef: + for definition in mypy_file.defs: + if isinstance(definition, ClassDef | FuncDef) and definition.name == node_name: + return definition + raise ValueError diff --git a/tests/safeds_stubgen/api_analyzer/__init__.py b/tests/safeds_stubgen/api_analyzer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr b/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr new file mode 100644 index 00000000..405f17ce --- /dev/null +++ b/tests/safeds_stubgen/api_analyzer/__snapshots__/test__get_api.ambr @@ -0,0 +1,2883 @@ +# serializer version: 1 +# name: test_class_attributes_GoogleDocstringClass + list([ + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': 'Attribute of the calculator. (Google Style)', + 'type': 'str', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'attr_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + ]) +# --- +# name: test_class_attributes_NestedClass + list([ + ]) +# --- +# name: test_class_attributes_NestedNestedPrivateClass + list([ + ]) +# --- +# name: test_class_attributes_NestedPrivateClass + list([ + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/_PrivateClass/NestedPrivateClass/nested_class_attr', + 'is_public': False, + 'is_static': True, + 'name': 'nested_class_attr', + 'type': None, + }), + ]) +# --- +# name: test_class_attributes_NumpyDocstringClass + list([ + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': 'Attribute of the calculator. (Numpy)', + 'type': 'str', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'attr_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + ]) +# --- +# name: test_class_attributes_RestDocstringClass + list([ + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': 'Attribute of the calculator. (ReST)', + 'type': 'str', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'attr_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + ]) +# --- +# name: test_class_attributes_SomeClass + list([ + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/_init_attr_private', + 'is_public': False, + 'is_static': False, + 'name': '_init_attr_private', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'float', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/_mulit_attr_2_private', + 'is_public': False, + 'is_static': True, + 'name': '_mulit_attr_2_private', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/_mulit_attr_4_private', + 'is_public': False, + 'is_static': True, + 'name': '_mulit_attr_4_private', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/_no_type_hint_private', + 'is_public': False, + 'is_static': True, + 'name': '_no_type_hint_private', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/_type_hint_private', + 'is_public': False, + 'is_static': True, + 'name': '_type_hint_private', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/bool_attr', + 'is_public': True, + 'is_static': True, + 'name': 'bool_attr', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/dict_attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'dict_attr_1', + 'type': dict({ + 'key_type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + 'kind': 'DictType', + 'value_type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/dict_attr_2', + 'is_public': True, + 'is_static': True, + 'name': 'dict_attr_2', + 'type': dict({ + 'key_type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + 'kind': 'DictType', + 'value_type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/dict_attr_3', + 'is_public': True, + 'is_static': True, + 'name': 'dict_attr_3', + 'type': dict({ + 'key_type': dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + ]), + }), + 'kind': 'DictType', + 'value_type': dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'None', + }), + dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + ]), + }), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/flaot_attr', + 'is_public': True, + 'is_static': True, + 'name': 'flaot_attr', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'float', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/init_attr', + 'is_public': True, + 'is_static': False, + 'name': 'init_attr', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/int_or_bool_attr', + 'is_public': True, + 'is_static': True, + 'name': 'int_or_bool_attr', + 'type': dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/list_attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'list_attr_1', + 'type': dict({ + 'kind': 'ListType', + 'types': list([ + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/list_attr_2', + 'is_public': True, + 'is_static': True, + 'name': 'list_attr_2', + 'type': dict({ + 'kind': 'ListType', + 'types': list([ + dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + dict({ + 'kind': 'NamedType', + 'name': '_AcImportAlias', + }), + ]), + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/list_attr_3', + 'is_public': True, + 'is_static': True, + 'name': 'list_attr_3', + 'type': dict({ + 'kind': 'ListType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + dict({ + 'kind': 'NamedType', + 'name': 'AcDoubleAlias', + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/list_attr_4', + 'is_public': True, + 'is_static': True, + 'name': 'list_attr_4', + 'type': dict({ + 'kind': 'ListType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': '_AcImportAlias', + }), + dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + ]), + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/mulit_attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'mulit_attr_1', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/mulit_attr_3', + 'is_public': True, + 'is_static': True, + 'name': 'mulit_attr_3', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/no_type_hint_public', + 'is_public': True, + 'is_static': True, + 'name': 'no_type_hint_public', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/none_attr', + 'is_public': True, + 'is_static': True, + 'name': 'none_attr', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'None', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/object_attr', + 'is_public': True, + 'is_static': True, + 'name': 'object_attr', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/object_attr_2', + 'is_public': True, + 'is_static': True, + 'name': 'object_attr_2', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/str_attr_with_none_value', + 'is_public': True, + 'is_static': True, + 'name': 'str_attr_with_none_value', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/tuple_attr_1', + 'is_public': True, + 'is_static': True, + 'name': 'tuple_attr_1', + 'type': dict({ + 'kind': 'TupleType', + 'types': list([ + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/tuple_attr_2', + 'is_public': True, + 'is_static': True, + 'name': 'tuple_attr_2', + 'type': dict({ + 'kind': 'TupleType', + 'types': list([ + dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + ]), + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/tuple_attr_3', + 'is_public': True, + 'is_static': True, + 'name': 'tuple_attr_3', + 'type': dict({ + 'kind': 'TupleType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + ]), + }), + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/type_hint_public', + 'is_public': True, + 'is_static': True, + 'name': 'type_hint_public', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + ]) +# --- +# name: test_class_attributes__PrivateClass + list([ + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/_PrivateClass/public_attr_in_private_class', + 'is_public': False, + 'is_static': True, + 'name': 'public_attr_in_private_class', + 'type': None, + }), + dict({ + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/_PrivateClass/public_init_attr_in_private_class', + 'is_public': False, + 'is_static': False, + 'name': 'public_init_attr_in_private_class', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + ]) +# --- +# name: test_class_methods_EpydocDocstringClass + list([ + dict({ + 'docstring': dict({ + 'description': 'This function checks if the sum of x and y is less than the value 10 and returns True if it is. (Epydoc).', + 'full_docstring': ''' + This function checks if the sum of x and y is less than the value 10 and returns True if it is. (Epydoc). + + @param x: First integer value for the calculation. (Epydoc) + @type x: int + @param y: Second integer value for the calculation. (Epydoc) + @type y: int + @return: Checks if the sum of x and y is greater than 10. (Epydoc) + @rtype: bool + ''', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func', + 'is_public': True, + 'is_static': False, + 'name': 'epydoc_docstring_func', + 'parameters': list([ + 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/self', + 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/x', + 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/y', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/result_1', + ]), + }), + ]) +# --- +# name: test_class_methods_GoogleDocstringClass + list([ + dict({ + 'docstring': dict({ + 'description': ''' + Checks if the sum of two variables is over the value of 10. (Google Style). + + This function checks if the sum of x and y is less than the value 10 + and returns True if it is. (Google Style) + ''', + 'full_docstring': ''' + Checks if the sum of two variables is over the value of 10. (Google Style). + + This function checks if the sum of x and y is less than the value 10 + and returns True if it is. (Google Style) + + Args: + x (int): First integer value for the calculation. (Google Style) + y (int): Second integer value for the calculation. (Google Style) + + Returns: + bool: Checks if the sum of x and y is greater than 10 and returns + a boolean value. (Google Style) + ''', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func', + 'is_public': True, + 'is_static': False, + 'name': 'google_docstring_func', + 'parameters': list([ + 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/self', + 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/x', + 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/y', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/result_1', + ]), + }), + ]) +# --- +# name: test_class_methods_NestedClass + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/SomeClass/NestedClass/nested_class_function', + 'is_public': True, + 'is_static': False, + 'name': 'nested_class_function', + 'parameters': list([ + 'test_package/test_module/SomeClass/NestedClass/nested_class_function/self', + 'test_package/test_module/SomeClass/NestedClass/nested_class_function/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_module/SomeClass/NestedClass/nested_class_function/result_1', + ]), + }), + ]) +# --- +# name: test_class_methods_NestedNestedPrivateClass + list([ + ]) +# --- +# name: test_class_methods_NestedPrivateClass + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_PrivateClass/NestedPrivateClass/static_nested_private_class_function', + 'is_public': False, + 'is_static': True, + 'name': 'static_nested_private_class_function', + 'parameters': list([ + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + ]) +# --- +# name: test_class_methods_NumpyDocstringClass + list([ + dict({ + 'docstring': dict({ + 'description': ''' + Checks if the sum of two variables is over the value of 10. (Numpy). + + This function checks if the sum of `x` and `y` is less than the value 10 and returns True if it is. (Numpy) + ''', + 'full_docstring': ''' + Checks if the sum of two variables is over the value of 10. (Numpy). + + This function checks if the sum of `x` and `y` is less than the value 10 and returns True if it is. (Numpy) + + Parameters + ---------- + x : int + First integer value for the calculation. (Numpy) + y : int + Second integer value for the calculation. (Numpy) + + Returns + ------- + bool + Checks if the sum of `x` and `y` is greater than 10. (Numpy) + ''', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func', + 'is_public': True, + 'is_static': False, + 'name': 'numpy_docstring_func', + 'parameters': list([ + 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/self', + 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/x', + 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/y', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/result_1', + ]), + }), + ]) +# --- +# name: test_class_methods_ReexportClass + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_1/ReexportClass/_private_class_method_of_reexported_class', + 'is_public': False, + 'is_static': True, + 'name': '_private_class_method_of_reexported_class', + 'parameters': list([ + ]), + 'reexported_by': list([ + 'test_package/__init__', + ]), + 'results': list([ + ]), + }), + ]) +# --- +# name: test_class_methods_RestDocstringClass + list([ + dict({ + 'docstring': dict({ + 'description': ''' + This function checks if the sum of x and y is less than the value 10 + + and returns True if it is. (ReST). + ''', + 'full_docstring': ''' + This function checks if the sum of x and y is less than the value 10 + and returns True if it is. (ReST). + + :param x: First integer value for the calculation. (ReST) + :type x: int + :param y: Second integer value for the calculation. (ReST) + :type y: int + :returns: Checks if the sum of x and y is greater than 10. (ReST) + :rtype: bool + ''', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func', + 'is_public': True, + 'is_static': False, + 'name': 'rest_docstring_func', + 'parameters': list([ + 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/self', + 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/x', + 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/y', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/result_1', + ]), + }), + ]) +# --- +# name: test_class_methods_SomeClass + list([ + dict({ + 'docstring': dict({ + 'description': ''' + Function Docstring. + + param_1: bool. + ''', + 'full_docstring': ''' + Function Docstring. + + param_1: bool. + ''', + }), + 'id': 'test_package/test_module/SomeClass/_some_function', + 'is_public': False, + 'is_static': False, + 'name': '_some_function', + 'parameters': list([ + 'test_package/test_module/SomeClass/_some_function/self', + 'test_package/test_module/SomeClass/_some_function/param_1', + 'test_package/test_module/SomeClass/_some_function/param_2', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_module/SomeClass/_some_function/result_1', + ]), + }), + dict({ + 'docstring': dict({ + 'description': 'Function Docstring.', + 'full_docstring': 'Function Docstring.', + }), + 'id': 'test_package/test_module/SomeClass/multiple_results', + 'is_public': True, + 'is_static': True, + 'name': 'multiple_results', + 'parameters': list([ + 'test_package/test_module/SomeClass/multiple_results/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_module/SomeClass/multiple_results/result_1', + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/SomeClass/no_return_1', + 'is_public': True, + 'is_static': False, + 'name': 'no_return_1', + 'parameters': list([ + 'test_package/test_module/SomeClass/no_return_1/self', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/SomeClass/no_return_2', + 'is_public': True, + 'is_static': False, + 'name': 'no_return_2', + 'parameters': list([ + 'test_package/test_module/SomeClass/no_return_2/self', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': 'Function Docstring.', + 'full_docstring': 'Function Docstring.', + }), + 'id': 'test_package/test_module/SomeClass/static_function', + 'is_public': True, + 'is_static': True, + 'name': 'static_function', + 'parameters': list([ + 'test_package/test_module/SomeClass/static_function/param_1', + 'test_package/test_module/SomeClass/static_function/param_2', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_module/SomeClass/static_function/result_1', + 'test_package/test_module/SomeClass/static_function/result_2', + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/SomeClass/test_params', + 'is_public': True, + 'is_static': True, + 'name': 'test_params', + 'parameters': list([ + 'test_package/test_module/SomeClass/test_params/args', + 'test_package/test_module/SomeClass/test_params/kwargs', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': 'Function Docstring.', + 'full_docstring': 'Function Docstring.', + }), + 'id': 'test_package/test_module/SomeClass/test_position', + 'is_public': True, + 'is_static': False, + 'name': 'test_position', + 'parameters': list([ + 'test_package/test_module/SomeClass/test_position/self', + 'test_package/test_module/SomeClass/test_position/param1', + 'test_package/test_module/SomeClass/test_position/param2', + 'test_package/test_module/SomeClass/test_position/param3', + 'test_package/test_module/SomeClass/test_position/param4', + 'test_package/test_module/SomeClass/test_position/param5', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_module/SomeClass/test_position/result_1', + ]), + }), + ]) +# --- +# name: test_class_methods__PrivateClass + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_PrivateClass/public_func_in_private_class', + 'is_public': False, + 'is_static': False, + 'name': 'public_func_in_private_class', + 'parameters': list([ + 'test_package/test_module/_PrivateClass/public_func_in_private_class/self', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + ]) +# --- +# name: test_classes_AnotherReexportClass + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_2/AnotherReexportClass', + 'is_public': True, + 'methods': list([ + ]), + 'name': 'AnotherReexportClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }) +# --- +# name: test_classes_EpydocDocstringClass + dict({ + 'attributes': list([ + 'test_package/test_docstrings/EpydocDocstringClass/attr_1', + ]), + 'classes': list([ + ]), + 'constructor': dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_docstrings/EpydocDocstringClass/__init__/self', + 'test_package/test_docstrings/EpydocDocstringClass/__init__/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + 'docstring': dict({ + 'description': 'A class with a variety of different methods for calculations. (Epydoc).', + 'full_docstring': ''' + A class with a variety of different methods for calculations. (Epydoc). + + @ivar attr_1: Attribute of the calculator. (Epydoc) + @type attr_1: str + @param param_1: Parameter of the calculator. (Epydoc) + @type param_1: str + ''', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass', + 'is_public': True, + 'methods': list([ + 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func', + ]), + 'name': 'EpydocDocstringClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }) +# --- +# name: test_classes_FourthReexportClass + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_4/FourthReexportClass', + 'is_public': True, + 'methods': list([ + ]), + 'name': 'FourthReexportClass', + 'reexported_by': list([ + 'test_package/__init__', + ]), + 'superclasses': list([ + ]), + }) +# --- +# name: test_classes_GoogleDocstringClass + dict({ + 'attributes': list([ + 'test_package/test_docstrings/GoogleDocstringClass/attr_1', + ]), + 'classes': list([ + ]), + 'constructor': dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_docstrings/GoogleDocstringClass/__init__/self', + 'test_package/test_docstrings/GoogleDocstringClass/__init__/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + 'docstring': dict({ + 'description': ''' + A class that calculates stuff. (Google Style). + + A class with a variety of different methods for calculations. (Google Style) + ''', + 'full_docstring': ''' + A class that calculates stuff. (Google Style). + + A class with a variety of different methods for calculations. (Google Style) + + Attributes: + attr_1 (str): Attribute of the calculator. (Google Style) + + Args: + param_1 (str): Parameter of the calculator. (Google Style) + ''', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass', + 'is_public': True, + 'methods': list([ + 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func', + ]), + 'name': 'GoogleDocstringClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }) +# --- +# name: test_classes_NestedClass + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/SomeClass/NestedClass', + 'is_public': True, + 'methods': list([ + 'test_package/test_module/SomeClass/NestedClass/nested_class_function', + ]), + 'name': 'NestedClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + 'tests.data.test_package.another_path.another_module.AnotherClass', + ]), + }) +# --- +# name: test_classes_NestedNestedPrivateClass + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_PrivateClass/NestedPrivateClass/NestedNestedPrivateClass', + 'is_public': True, + 'methods': list([ + ]), + 'name': 'NestedNestedPrivateClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }) +# --- +# name: test_classes_NestedPrivateClass + dict({ + 'attributes': list([ + 'test_package/test_module/_PrivateClass/NestedPrivateClass/nested_class_attr', + ]), + 'classes': list([ + 'test_package/test_module/_PrivateClass/NestedPrivateClass/NestedNestedPrivateClass', + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_PrivateClass/NestedPrivateClass', + 'is_public': True, + 'methods': list([ + 'test_package/test_module/_PrivateClass/NestedPrivateClass/static_nested_private_class_function', + ]), + 'name': 'NestedPrivateClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }) +# --- +# name: test_classes_NumpyDocstringClass + dict({ + 'attributes': list([ + 'test_package/test_docstrings/NumpyDocstringClass/attr_1', + ]), + 'classes': list([ + ]), + 'constructor': dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_docstrings/NumpyDocstringClass/__init__/self', + 'test_package/test_docstrings/NumpyDocstringClass/__init__/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + 'docstring': dict({ + 'description': ''' + A class that calculates stuff. (Numpy). + + A class with a variety of different methods for calculations. (Numpy) + ''', + 'full_docstring': ''' + A class that calculates stuff. (Numpy). + + A class with a variety of different methods for calculations. (Numpy) + + Attributes + ---------- + attr_1 : str + Attribute of the calculator. (Numpy) + + Parameters + ---------- + param_1 : str + Parameter of the calculator. (Numpy) + ''', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass', + 'is_public': True, + 'methods': list([ + 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func', + ]), + 'name': 'NumpyDocstringClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }) +# --- +# name: test_classes_ReexportClass + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_1/ReexportClass', + 'is_public': True, + 'methods': list([ + 'test_package/_reexport_module_1/ReexportClass/_private_class_method_of_reexported_class', + ]), + 'name': 'ReexportClass', + 'reexported_by': list([ + 'test_package/__init__', + ]), + 'superclasses': list([ + ]), + }) +# --- +# name: test_classes_RestDocstringClass + dict({ + 'attributes': list([ + 'test_package/test_docstrings/RestDocstringClass/attr_1', + ]), + 'classes': list([ + ]), + 'constructor': dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_docstrings/RestDocstringClass/__init__/self', + 'test_package/test_docstrings/RestDocstringClass/__init__/param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + 'docstring': dict({ + 'description': 'A class with a variety of different methods for calculations. (ReST).', + 'full_docstring': ''' + A class with a variety of different methods for calculations. (ReST). + + :param attr_1: Attribute of the calculator. (ReST) + :type attr_1: str + :param param_1: Parameter of the calculator. (ReST) + :type param_1: str + ''', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass', + 'is_public': True, + 'methods': list([ + 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func', + ]), + 'name': 'RestDocstringClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }) +# --- +# name: test_classes_SomeClass + dict({ + 'attributes': list([ + 'test_package/test_module/SomeClass/type_hint_public', + 'test_package/test_module/SomeClass/_type_hint_private', + 'test_package/test_module/SomeClass/no_type_hint_public', + 'test_package/test_module/SomeClass/_no_type_hint_private', + 'test_package/test_module/SomeClass/object_attr', + 'test_package/test_module/SomeClass/object_attr_2', + 'test_package/test_module/SomeClass/tuple_attr_1', + 'test_package/test_module/SomeClass/tuple_attr_2', + 'test_package/test_module/SomeClass/tuple_attr_3', + 'test_package/test_module/SomeClass/list_attr_1', + 'test_package/test_module/SomeClass/list_attr_2', + 'test_package/test_module/SomeClass/list_attr_3', + 'test_package/test_module/SomeClass/list_attr_4', + 'test_package/test_module/SomeClass/dict_attr_1', + 'test_package/test_module/SomeClass/dict_attr_2', + 'test_package/test_module/SomeClass/dict_attr_3', + 'test_package/test_module/SomeClass/bool_attr', + 'test_package/test_module/SomeClass/none_attr', + 'test_package/test_module/SomeClass/flaot_attr', + 'test_package/test_module/SomeClass/int_or_bool_attr', + 'test_package/test_module/SomeClass/str_attr_with_none_value', + 'test_package/test_module/SomeClass/mulit_attr_1', + 'test_package/test_module/SomeClass/_mulit_attr_2_private', + 'test_package/test_module/SomeClass/mulit_attr_3', + 'test_package/test_module/SomeClass/_mulit_attr_4_private', + 'test_package/test_module/SomeClass/init_attr', + 'test_package/test_module/SomeClass/_init_attr_private', + ]), + 'classes': list([ + 'test_package/test_module/SomeClass/NestedClass', + ]), + 'constructor': dict({ + 'docstring': dict({ + 'description': ''' + Summary of the init description. + + Full init description. + ''', + 'full_docstring': ''' + Summary of the init description. + + Full init description. + ''', + }), + 'id': 'test_package/test_module/SomeClass/__init__', + 'is_public': True, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_module/SomeClass/__init__/self', + 'test_package/test_module/SomeClass/__init__/init_param_1', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + 'docstring': dict({ + 'description': ''' + Summary of the description. + + Full description + ''', + 'full_docstring': ''' + Summary of the description. + + Full description + ''', + }), + 'id': 'test_package/test_module/SomeClass', + 'is_public': True, + 'methods': list([ + 'test_package/test_module/SomeClass/_some_function', + 'test_package/test_module/SomeClass/static_function', + 'test_package/test_module/SomeClass/test_position', + 'test_package/test_module/SomeClass/test_params', + 'test_package/test_module/SomeClass/multiple_results', + 'test_package/test_module/SomeClass/no_return_1', + 'test_package/test_module/SomeClass/no_return_2', + ]), + 'name': 'SomeClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + 'tests.data.test_package.test_module.AcDoubleAlias', + ]), + }) +# --- +# name: test_classes__PrivateClass + dict({ + 'attributes': list([ + 'test_package/test_module/_PrivateClass/public_attr_in_private_class', + 'test_package/test_module/_PrivateClass/public_init_attr_in_private_class', + ]), + 'classes': list([ + 'test_package/test_module/_PrivateClass/NestedPrivateClass', + ]), + 'constructor': dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_PrivateClass/__init__', + 'is_public': False, + 'is_static': False, + 'name': '__init__', + 'parameters': list([ + 'test_package/test_module/_PrivateClass/__init__/self', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_PrivateClass', + 'is_public': False, + 'methods': list([ + 'test_package/test_module/_PrivateClass/public_func_in_private_class', + ]), + 'name': '_PrivateClass', + 'reexported_by': list([ + ]), + 'superclasses': list([ + ]), + }) +# --- +# name: test_classes__ThirdReexportClass + dict({ + 'attributes': list([ + ]), + 'classes': list([ + ]), + 'constructor': None, + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_3/_ThirdReexportClass', + 'is_public': False, + 'methods': list([ + ]), + 'name': '_ThirdReexportClass', + 'reexported_by': list([ + 'test_package/__init__', + ]), + 'superclasses': list([ + ]), + }) +# --- +# name: test_enum_instances_AnotherTestEnum + list([ + dict({ + 'id': 'test_package/test_enums/AnotherTestEnum/ELEVEN', + 'name': 'ELEVEN', + }), + ]) +# --- +# name: test_enum_instances_EnumTest + list([ + dict({ + 'id': 'test_package/test_enums/EnumTest/EIGHT', + 'name': 'EIGHT', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/FIVE', + 'name': 'FIVE', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/FOUR', + 'name': 'FOUR', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/NINE', + 'name': 'NINE', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/ONE', + 'name': 'ONE', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/SEVEN', + 'name': 'SEVEN', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/SIX', + 'name': 'SIX', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/TEN', + 'name': 'TEN', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/THREE', + 'name': 'THREE', + }), + dict({ + 'id': 'test_package/test_enums/EnumTest/TWO', + 'name': 'TWO', + }), + ]) +# --- +# name: test_enum_instances__ReexportedEmptyEnum + list([ + ]) +# --- +# name: test_enums_AnotherTestEnum + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_enums/AnotherTestEnum', + 'instances': list([ + 'test_package/test_enums/AnotherTestEnum/ELEVEN', + ]), + 'name': 'AnotherTestEnum', + }) +# --- +# name: test_enums_EnumTest + dict({ + 'docstring': dict({ + 'description': ''' + Enum Docstring. + + Full Docstring Description + ''', + 'full_docstring': ''' + Enum Docstring. + + Full Docstring Description + ''', + }), + 'id': 'test_package/test_enums/EnumTest', + 'instances': list([ + 'test_package/test_enums/EnumTest/ONE', + 'test_package/test_enums/EnumTest/TWO', + 'test_package/test_enums/EnumTest/THREE', + 'test_package/test_enums/EnumTest/FOUR', + 'test_package/test_enums/EnumTest/FIVE', + 'test_package/test_enums/EnumTest/SIX', + 'test_package/test_enums/EnumTest/SEVEN', + 'test_package/test_enums/EnumTest/EIGHT', + 'test_package/test_enums/EnumTest/NINE', + 'test_package/test_enums/EnumTest/TEN', + ]), + 'name': 'EnumTest', + }) +# --- +# name: test_enums__ReexportedEmptyEnum + dict({ + 'docstring': dict({ + 'description': "Nothing's here.", + 'full_docstring': "Nothing's here.", + }), + 'id': 'test_package/test_enums/_ReexportedEmptyEnum', + 'instances': list([ + ]), + 'name': '_ReexportedEmptyEnum', + }) +# --- +# name: test_function_parameters_EpydocDocstringClass___init__ + list([ + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': 'Parameter of the calculator. (Epydoc)', + 'type': 'str', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/__init__/param_1', + 'is_optional': False, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/__init__/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'EpydocDocstringClass', + }), + }), + ]) +# --- +# name: test_function_parameters_GoogleDocstringClass___init__ + list([ + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': 'Parameter of the calculator. (Google Style)', + 'type': 'str', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/__init__/param_1', + 'is_optional': False, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/__init__/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'GoogleDocstringClass', + }), + }), + ]) +# --- +# name: test_function_parameters_NumpyDocstringClass___init__ + list([ + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': 'Parameter of the calculator. (Numpy)', + 'type': 'str', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/__init__/param_1', + 'is_optional': False, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/__init__/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'NumpyDocstringClass', + }), + }), + ]) +# --- +# name: test_function_parameters_RestDocstringClass___init__ + list([ + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': 'Parameter of the calculator. (ReST)', + 'type': 'str', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/__init__/param_1', + 'is_optional': False, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/__init__/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'RestDocstringClass', + }), + }), + ]) +# --- +# name: test_function_parameters_SomeClass___init__ + list([ + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/__init__/init_param_1', + 'is_optional': False, + 'name': 'init_param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/__init__/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'SomeClass', + }), + }), + ]) +# --- +# name: test_function_parameters_epydoc_docstring_func + list([ + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'EpydocDocstringClass', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': 'First integer value for the calculation. (Epydoc)', + 'type': 'int', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/x', + 'is_optional': False, + 'name': 'x', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': 'Second integer value for the calculation. (Epydoc)', + 'type': 'int', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/y', + 'is_optional': False, + 'name': 'y', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + ]) +# --- +# name: test_function_parameters_global_func + list([ + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': 'first param', + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/global_func/param_1', + 'is_optional': True, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/global_func/param_2', + 'is_optional': True, + 'name': 'param_2', + 'type': dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + dict({ + 'kind': 'NamedType', + 'name': 'None', + }), + ]), + }), + }), + ]) +# --- +# name: test_function_parameters_google_docstring_func + list([ + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'GoogleDocstringClass', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': 'First integer value for the calculation. (Google Style)', + 'type': 'int', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/x', + 'is_optional': False, + 'name': 'x', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': 'Second integer value for the calculation. (Google Style)', + 'type': 'int', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/y', + 'is_optional': False, + 'name': 'y', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + ]) +# --- +# name: test_function_parameters_nested_class_function + list([ + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/NestedClass/nested_class_function/param_1', + 'is_optional': False, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/NestedClass/nested_class_function/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'NestedClass', + }), + }), + ]) +# --- +# name: test_function_parameters_numpy_docstring_func + list([ + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'NumpyDocstringClass', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': 'First integer value for the calculation. (Numpy)', + 'type': 'int', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/x', + 'is_optional': False, + 'name': 'x', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': 'Second integer value for the calculation. (Numpy)', + 'type': 'int', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/y', + 'is_optional': False, + 'name': 'y', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + ]) +# --- +# name: test_function_parameters_rest_docstring_func + list([ + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'RestDocstringClass', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': 'First integer value for the calculation. (ReST)', + 'type': 'int', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/x', + 'is_optional': False, + 'name': 'x', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': 'Second integer value for the calculation. (ReST)', + 'type': 'int', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/y', + 'is_optional': False, + 'name': 'y', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + ]) +# --- +# name: test_function_parameters_static_function + list([ + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/static_function/param_1', + 'is_optional': False, + 'name': 'param_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': 123456, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/static_function/param_2', + 'is_optional': True, + 'name': 'param_2', + 'type': dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + ]), + }), + }), + ]) +# --- +# name: test_function_parameters_test_params + list([ + dict({ + 'assigned_by': 'POSITIONAL_VARARG', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_params/args', + 'is_optional': False, + 'name': 'args', + 'type': dict({ + 'kind': 'TupleType', + 'types': list([ + ]), + }), + }), + dict({ + 'assigned_by': 'NAMED_VARARG', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_params/kwargs', + 'is_optional': False, + 'name': 'kwargs', + 'type': dict({ + 'key_type': dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + 'kind': 'DictType', + 'value_type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + }), + }), + ]) +# --- +# name: test_function_parameters_test_position + list([ + dict({ + 'assigned_by': 'POSITION_ONLY', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_position/param1', + 'is_optional': False, + 'name': 'param1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_position/param2', + 'is_optional': False, + 'name': 'param2', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'assigned_by': 'POSITION_OR_NAME', + 'default_value': 1, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_position/param3', + 'is_optional': True, + 'name': 'param3', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + }), + dict({ + 'assigned_by': 'NAME_ONLY', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_position/param4', + 'is_optional': True, + 'name': 'param4', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + }), + dict({ + 'assigned_by': 'NAME_ONLY', + 'default_value': 1, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_position/param5', + 'is_optional': True, + 'name': 'param5', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + dict({ + 'assigned_by': 'IMPLICIT', + 'default_value': None, + 'docstring': dict({ + 'default_value': '', + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_position/self', + 'is_optional': False, + 'name': 'self', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'SomeClass', + }), + }), + ]) +# --- +# name: test_function_results_epydoc_docstring_func + list([ + dict({ + 'docstring': dict({ + 'description': 'Checks if the sum of x and y is greater than 10. (Epydoc)', + 'type': 'bool', + }), + 'id': 'test_package/test_docstrings/EpydocDocstringClass/epydoc_docstring_func/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + ]) +# --- +# name: test_function_results_global_func + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/global_func/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'AnotherClass', + }), + }), + ]) +# --- +# name: test_function_results_google_docstring_func + list([ + dict({ + 'docstring': dict({ + 'description': ''' + Checks if the sum of x and y is greater than 10 and returns + a boolean value. (Google Style) + ''', + 'type': 'bool', + }), + 'id': 'test_package/test_docstrings/GoogleDocstringClass/google_docstring_func/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + ]) +# --- +# name: test_function_results_multiple_results + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/multiple_results/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + dict({ + 'kind': 'TupleType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + dict({ + 'kind': 'NamedType', + 'name': 'str', + }), + ]), + }), + ]), + }), + }), + ]) +# --- +# name: test_function_results_nested_class_function + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/NestedClass/nested_class_function/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'SetType', + 'types': list([ + dict({ + 'kind': 'UnionType', + 'types': list([ + dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + dict({ + 'kind': 'NamedType', + 'name': 'None', + }), + ]), + }), + ]), + }), + }), + ]) +# --- +# name: test_function_results_numpy_docstring_func + list([ + dict({ + 'docstring': dict({ + 'description': 'Checks if the sum of `x` and `y` is greater than 10. (Numpy)', + 'type': 'bool', + }), + 'id': 'test_package/test_docstrings/NumpyDocstringClass/numpy_docstring_func/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + ]) +# --- +# name: test_function_results_rest_docstring_func + list([ + dict({ + 'docstring': dict({ + 'description': 'Checks if the sum of x and y is greater than 10. (ReST)', + 'type': 'bool', + }), + 'id': 'test_package/test_docstrings/RestDocstringClass/rest_docstring_func/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + ]) +# --- +# name: test_function_results_static_function + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/static_function/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'bool', + }), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/static_function/result_2', + 'name': 'result_2', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'int', + }), + }), + ]) +# --- +# name: test_function_results_test_position + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'type': '', + }), + 'id': 'test_package/test_module/SomeClass/test_position/result_1', + 'name': 'result_1', + 'type': dict({ + 'kind': 'NamedType', + 'name': 'Any', + }), + }), + ]) +# --- +# name: test_global_functions__reexport_module_1 + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_1/reexported_function', + 'is_public': False, + 'is_static': False, + 'name': 'reexported_function', + 'parameters': list([ + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + ]) +# --- +# name: test_global_functions__reexport_module_2 + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_2/reexported_function_2', + 'is_public': True, + 'is_static': False, + 'name': 'reexported_function_2', + 'parameters': list([ + ]), + 'reexported_by': list([ + 'test_package/__init__', + ]), + 'results': list([ + ]), + }), + ]) +# --- +# name: test_global_functions__reexport_module_3 + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_3/reexported_function_3', + 'is_public': False, + 'is_static': False, + 'name': 'reexported_function_3', + 'parameters': list([ + ]), + 'reexported_by': list([ + 'test_package/__init__', + ]), + 'results': list([ + ]), + }), + ]) +# --- +# name: test_global_functions__reexport_module_4 + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_4/_reexported_function_4', + 'is_public': False, + 'is_static': False, + 'name': '_reexported_function_4', + 'parameters': list([ + ]), + 'reexported_by': list([ + 'test_package/__init__', + ]), + 'results': list([ + ]), + }), + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/_reexport_module_4/_unreexported_function', + 'is_public': False, + 'is_static': False, + 'name': '_unreexported_function', + 'parameters': list([ + ]), + 'reexported_by': list([ + ]), + 'results': list([ + ]), + }), + ]) +# --- +# name: test_global_functions_test_module + list([ + dict({ + 'docstring': dict({ + 'description': '', + 'full_docstring': '', + }), + 'id': 'test_package/test_module/_private_global_func', + 'is_public': False, + 'is_static': False, + 'name': '_private_global_func', + 'parameters': list([ + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_module/_private_global_func/result_1', + ]), + }), + dict({ + 'docstring': dict({ + 'description': ''' + Docstring 1. + + Docstring 2. + ''', + 'full_docstring': ''' + Docstring 1. + + Docstring 2. + ''', + }), + 'id': 'test_package/test_module/global_func', + 'is_public': True, + 'is_static': False, + 'name': 'global_func', + 'parameters': list([ + 'test_package/test_module/global_func/param_1', + 'test_package/test_module/global_func/param_2', + ]), + 'reexported_by': list([ + ]), + 'results': list([ + 'test_package/test_module/global_func/result_1', + ]), + }), + ]) +# --- +# name: test_imports___init___qualified_imports + list([ + dict({ + 'alias': 'reex_1', + 'qualified_name': '._reexport_module_1', + }), + dict({ + 'alias': None, + 'qualified_name': '_reexport_module_1.ReexportClass', + }), + dict({ + 'alias': None, + 'qualified_name': '_reexport_module_2.reexported_function_2', + }), + dict({ + 'alias': None, + 'qualified_name': '_reexport_module_4.FourthReexportClass', + }), + dict({ + 'alias': None, + 'qualified_name': '_reexport_module_4._reexported_function_4', + }), + dict({ + 'alias': None, + 'qualified_name': 'test_enums._ReexportedEmptyEnum', + }), + ]) +# --- +# name: test_imports___init___wildcard_imports + list([ + dict({ + 'module_name': '_reexport_module_3', + }), + ]) +# --- +# name: test_imports_test_emums_qualified_imports + list([ + dict({ + 'alias': None, + 'qualified_name': 'enum.Enum', + }), + dict({ + 'alias': None, + 'qualified_name': 'enum.IntEnum', + }), + dict({ + 'alias': '_Enum', + 'qualified_name': 'enum.Enum', + }), + dict({ + 'alias': '_AcImportAlias', + 'qualified_name': 'another_path.another_module.AnotherClass', + }), + ]) +# --- +# name: test_imports_test_enums_wildcard_imports + list([ + ]) +# --- +# name: test_imports_test_module_qualified_imports + list([ + dict({ + 'alias': 'mathematics', + 'qualified_name': 'math', + }), + dict({ + 'alias': None, + 'qualified_name': 'mypy', + }), + dict({ + 'alias': None, + 'qualified_name': 'another_path.another_module.AnotherClass', + }), + dict({ + 'alias': '_AcImportAlias', + 'qualified_name': 'another_path.another_module.AnotherClass', + }), + ]) +# --- +# name: test_imports_test_module_wildcard_imports + list([ + dict({ + 'module_name': 'typing', + }), + dict({ + 'module_name': 'docstring_parser', + }), + ]) +# --- +# name: test_modules___init__ + dict({ + 'classes': list([ + ]), + 'docstring': '', + 'enums': list([ + ]), + 'functions': list([ + ]), + 'id': 'test_package/__init__', + 'name': '__init__', + 'qualified_imports': list([ + dict({ + 'alias': 'reex_1', + 'qualified_name': '._reexport_module_1', + }), + dict({ + 'alias': None, + 'qualified_name': '_reexport_module_1.ReexportClass', + }), + dict({ + 'alias': None, + 'qualified_name': '_reexport_module_2.reexported_function_2', + }), + dict({ + 'alias': None, + 'qualified_name': '_reexport_module_4.FourthReexportClass', + }), + dict({ + 'alias': None, + 'qualified_name': '_reexport_module_4._reexported_function_4', + }), + dict({ + 'alias': None, + 'qualified_name': 'test_enums._ReexportedEmptyEnum', + }), + ]), + 'wildcard_imports': list([ + dict({ + 'module_name': '_reexport_module_3', + }), + ]), + }) +# --- +# name: test_modules_another_module + dict({ + 'classes': list([ + 'test_package/another_module/AnotherClass', + ]), + 'docstring': ''' + Another Module Docstring. + + Full Docstring Description + + ''', + 'enums': list([ + ]), + 'functions': list([ + ]), + 'id': 'test_package/another_module', + 'name': 'another_module', + 'qualified_imports': list([ + ]), + 'wildcard_imports': list([ + ]), + }) +# --- +# name: test_modules_test_docstrings + dict({ + 'classes': list([ + 'test_package/test_docstrings/EpydocDocstringClass', + 'test_package/test_docstrings/RestDocstringClass', + 'test_package/test_docstrings/NumpyDocstringClass', + 'test_package/test_docstrings/GoogleDocstringClass', + ]), + 'docstring': ''' + Test module for docstring tests. + + A module for testing the various docstring types. + + ''', + 'enums': list([ + ]), + 'functions': list([ + ]), + 'id': 'test_package/test_docstrings', + 'name': 'test_docstrings', + 'qualified_imports': list([ + ]), + 'wildcard_imports': list([ + ]), + }) +# --- +# name: test_modules_test_enums + dict({ + 'classes': list([ + ]), + 'docstring': '', + 'enums': list([ + 'test_package/test_enums/EnumTest', + 'test_package/test_enums/_ReexportedEmptyEnum', + 'test_package/test_enums/AnotherTestEnum', + ]), + 'functions': list([ + ]), + 'id': 'test_package/test_enums', + 'name': 'test_enums', + 'qualified_imports': list([ + dict({ + 'alias': None, + 'qualified_name': 'enum.Enum', + }), + dict({ + 'alias': None, + 'qualified_name': 'enum.IntEnum', + }), + dict({ + 'alias': '_Enum', + 'qualified_name': 'enum.Enum', + }), + dict({ + 'alias': '_AcImportAlias', + 'qualified_name': 'another_path.another_module.AnotherClass', + }), + ]), + 'wildcard_imports': list([ + ]), + }) +# --- +# name: test_modules_test_module + dict({ + 'classes': list([ + 'test_package/test_module/SomeClass', + 'test_package/test_module/_PrivateClass', + ]), + 'docstring': 'Docstring of the some_class.py module.', + 'enums': list([ + ]), + 'functions': list([ + 'test_package/test_module/global_func', + 'test_package/test_module/_private_global_func', + ]), + 'id': 'test_package/test_module', + 'name': 'test_module', + 'qualified_imports': list([ + dict({ + 'alias': 'mathematics', + 'qualified_name': 'math', + }), + dict({ + 'alias': None, + 'qualified_name': 'mypy', + }), + dict({ + 'alias': None, + 'qualified_name': 'another_path.another_module.AnotherClass', + }), + dict({ + 'alias': '_AcImportAlias', + 'qualified_name': 'another_path.another_module.AnotherClass', + }), + ]), + 'wildcard_imports': list([ + dict({ + 'module_name': 'typing', + }), + dict({ + 'module_name': 'docstring_parser', + }), + ]), + }) +# --- diff --git a/tests/safeds_stubgen/api_analyzer/test__get_api.py b/tests/safeds_stubgen/api_analyzer/test__get_api.py new file mode 100644 index 00000000..68024ceb --- /dev/null +++ b/tests/safeds_stubgen/api_analyzer/test__get_api.py @@ -0,0 +1,561 @@ +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +from safeds_stubgen.api_analyzer import get_api +from safeds_stubgen.docstring_parsing import DocstringStyle + +if TYPE_CHECKING: + from syrupy import SnapshotAssertion + +# Setup: API data +_test_dir = Path(__file__).parent.parent.parent +_test_package_name = "test_package" +_main_test_module_name = "test_module" + +api_data_paintext = get_api( + package_name=_test_package_name, + root=Path(_test_dir / "data" / _test_package_name), + is_test_run=True, +).to_dict() + +api_data_epydoc = get_api( + package_name=_test_package_name, + root=Path(_test_dir / "data" / _test_package_name), + docstring_style=DocstringStyle.EPYDOC, + is_test_run=True, +).to_dict() + +api_data_numpy = get_api( + package_name=_test_package_name, + root=Path(_test_dir / "data" / _test_package_name), + docstring_style=DocstringStyle.NUMPYDOC, + is_test_run=True, +).to_dict() + +api_data_rest = get_api( + package_name=_test_package_name, + root=Path(_test_dir / "data" / _test_package_name), + docstring_style=DocstringStyle.REST, + is_test_run=True, +).to_dict() + +api_data_google = get_api( + package_name=_test_package_name, + root=Path(_test_dir / "data" / _test_package_name), + docstring_style=DocstringStyle.GOOGLE, + is_test_run=True, +).to_dict() + + +# Utilites +def _get_specific_module_data(module_name: str, docstring_style: str = "plaintext") -> dict: + api_data = get_api_data(docstring_style) + + for module in api_data["modules"]: + if module["name"] == module_name: + return module + raise AssertionError + + +def _get_specific_class_data(class_name: str, docstring_style: str = "plaintext", is_enum: bool = False) -> dict: + data_type = "enums" if is_enum else "classes" + api_data = get_api_data(docstring_style) + for class_ in api_data[data_type]: + if class_["id"].endswith(f"/{class_name}"): + return class_ + raise AssertionError + + +def get_api_data(docstring_style: str) -> dict: + return { + "plaintext": api_data_paintext, + "epydoc": api_data_epydoc, + "numpydoc": api_data_numpy, + "rest": api_data_rest, + "google": api_data_google, + }[docstring_style] + + +def _get_specific_function_data( + function_name: str, + parent_class_name: str = "", + test_module_name: str = _main_test_module_name, + docstring_style: str = "plaintext", +) -> dict: + api_data = get_api_data(docstring_style) + + if parent_class_name == "": + parent_class_name = test_module_name + + for function in api_data["functions"]: + if function["id"].endswith(f"{parent_class_name}/{function_name}"): + return function + raise AssertionError + + +# ############################## Module ############################## # +def test_modules_test_module(snapshot: SnapshotAssertion) -> None: + module_data = _get_specific_module_data(_main_test_module_name) + assert module_data == snapshot + + +def test_modules_another_module(snapshot: SnapshotAssertion) -> None: + module_data = _get_specific_module_data("another_module") + assert module_data == snapshot + + +def test_modules_test_enums(snapshot: SnapshotAssertion) -> None: + module_data = _get_specific_module_data("test_enums") + assert module_data == snapshot + + +def test_modules___init__(snapshot: SnapshotAssertion) -> None: + module_data = _get_specific_module_data("__init__") + assert module_data == snapshot + + +def test_modules_test_docstrings(snapshot: SnapshotAssertion) -> None: + module_data = _get_specific_module_data("test_docstrings") + assert module_data == snapshot + + +# ############################## Imports ############################## # +def get_import_data(module_name: str, import_type: str) -> list[dict]: + module_data = _get_specific_module_data(module_name) + return module_data.get(import_type, []) + + +def test_imports_test_module_qualified_imports(snapshot: SnapshotAssertion) -> None: + import_data = get_import_data(_main_test_module_name, "qualified_imports") + assert import_data == snapshot + + +def test_imports_test_module_wildcard_imports(snapshot: SnapshotAssertion) -> None: + import_data = get_import_data(_main_test_module_name, "wildcard_imports") + assert import_data == snapshot + + +def test_imports_test_emums_qualified_imports(snapshot: SnapshotAssertion) -> None: + import_data = get_import_data("test_enums", "qualified_imports") + assert import_data == snapshot + + +def test_imports_test_enums_wildcard_imports(snapshot: SnapshotAssertion) -> None: + import_data = get_import_data("test_enums", "wildcard_imports") + assert import_data == snapshot + + +def test_imports___init___qualified_imports(snapshot: SnapshotAssertion) -> None: + import_data = get_import_data("__init__", "qualified_imports") + assert import_data == snapshot + + +def test_imports___init___wildcard_imports(snapshot: SnapshotAssertion) -> None: + import_data = get_import_data("__init__", "wildcard_imports") + assert import_data == snapshot + + +# ############################## Classes ############################## # +def test_classes_SomeClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = _get_specific_class_data("SomeClass", "plaintext") + assert class_data == snapshot + + +def test_classes_NestedClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = _get_specific_class_data("NestedClass", "plaintext") + assert class_data == snapshot + + +def test_classes__PrivateClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = _get_specific_class_data("_PrivateClass", "plaintext") + assert class_data == snapshot + + +def test_classes_NestedPrivateClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = _get_specific_class_data("NestedPrivateClass", "plaintext") + assert class_data == snapshot + + +def test_classes_NestedNestedPrivateClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = _get_specific_class_data("NestedNestedPrivateClass", "plaintext") + assert class_data == snapshot + + +def test_classes_EpydocDocstringClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = _get_specific_class_data("EpydocDocstringClass", "epydoc") + assert class_data == snapshot + + +def test_classes_RestDocstringClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = _get_specific_class_data("RestDocstringClass", "rest") + assert class_data == snapshot + + +def test_classes_NumpyDocstringClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = _get_specific_class_data("NumpyDocstringClass", "numpydoc") + assert class_data == snapshot + + +def test_classes_GoogleDocstringClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = _get_specific_class_data("GoogleDocstringClass", "google") + assert class_data == snapshot + + +def test_classes_ReexportClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = _get_specific_class_data("ReexportClass", "plaintext") + assert class_data == snapshot + + +def test_classes_AnotherReexportClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = _get_specific_class_data("AnotherReexportClass", "plaintext") + assert class_data == snapshot + + +def test_classes__ThirdReexportClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = _get_specific_class_data("_ThirdReexportClass", "plaintext") + assert class_data == snapshot + + +def test_classes_FourthReexportClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = _get_specific_class_data("FourthReexportClass", "plaintext") + assert class_data == snapshot + + +# ############################## Class Attributes ############################## # +def get_class_attribute_data(class_name: str, docstring_style: str) -> list: + class_data: dict = _get_specific_class_data(class_name) + class_attr_ids: list[str] = class_data["attributes"] + + # Sort out the class attribute data we need and return + api_data = get_api_data(docstring_style) + return [attr for attr in api_data["attributes"] if attr["id"] in class_attr_ids] + + +def test_class_attributes_SomeClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = get_class_attribute_data("SomeClass", "plaintext") + assert class_data == snapshot + + +def test_class_attributes_NestedClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = get_class_attribute_data("NestedClass", "plaintext") + assert class_data == snapshot + + +def test_class_attributes__PrivateClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = get_class_attribute_data("_PrivateClass", "plaintext") + assert class_data == snapshot + + +def test_class_attributes_NestedPrivateClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = get_class_attribute_data("NestedPrivateClass", "plaintext") + assert class_data == snapshot + + +def test_class_attributes_NestedNestedPrivateClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = get_class_attribute_data("NestedNestedPrivateClass", "plaintext") + assert class_data == snapshot + + +# Todo Epydoc Tests are deactivated right now, since attribute handling is not implemented yet in the +# docstring_parser library +def xtest_class_attributes_EpydocDocstringClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = get_class_attribute_data("EpydocDocstringClass", "epydoc") + assert class_data == snapshot + + +def test_class_attributes_RestDocstringClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = get_class_attribute_data("RestDocstringClass", "rest") + assert class_data == snapshot + + +def test_class_attributes_NumpyDocstringClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = get_class_attribute_data("NumpyDocstringClass", "numpydoc") + assert class_data == snapshot + + +def test_class_attributes_GoogleDocstringClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_data = get_class_attribute_data("GoogleDocstringClass", "google") + assert class_data == snapshot + + +# ############################## Enums ############################## # +def test_enums_EnumTest(snapshot: SnapshotAssertion) -> None: # noqa: N802 + enum_data = _get_specific_class_data("EnumTest", is_enum=True) + assert enum_data == snapshot + + +def test_enums__ReexportedEmptyEnum(snapshot: SnapshotAssertion) -> None: # noqa: N802 + enum_data = _get_specific_class_data("_ReexportedEmptyEnum", is_enum=True) + assert enum_data == snapshot + + +def test_enums_AnotherTestEnum(snapshot: SnapshotAssertion) -> None: # noqa: N802 + enum_data = _get_specific_class_data("AnotherTestEnum", is_enum=True) + assert enum_data == snapshot + + +# ############################## Enum Instances ############################## # +def get_enum_instance_data(enum_name: str) -> list: + # Get enum data + enum_data = _get_specific_class_data(enum_name, is_enum=True) + enum_instance_ids = enum_data["instances"] + + all_enum_instances = api_data_paintext["enum_instances"] + + # Sort out the enum instances we need and return + return [enum_instance for enum_instance in all_enum_instances if enum_instance["id"] in enum_instance_ids] + + +def test_enum_instances_EnumTest(snapshot: SnapshotAssertion) -> None: # noqa: N802 + enum_instance_data = get_enum_instance_data("EnumTest") + assert enum_instance_data == snapshot + + +def test_enum_instances__ReexportedEmptyEnum(snapshot: SnapshotAssertion) -> None: # noqa: N802 + enum_instance_data = get_enum_instance_data("_ReexportedEmptyEnum") + assert enum_instance_data == snapshot + + +def test_enum_instances_AnotherTestEnum(snapshot: SnapshotAssertion) -> None: # noqa: N802 + enum_instance_data = get_enum_instance_data("AnotherTestEnum") + assert enum_instance_data == snapshot + + +# ############################## Global Functions ############################## # +def get_global_function_data(module_name: str) -> list: + # Get function data + module_data = _get_specific_module_data(module_name) + global_function_ids = module_data["functions"] + + all_functions: list[dict] = api_data_paintext["functions"] + + # Sort out the functions we need and return + return [function for function in all_functions if function["id"] in global_function_ids] + + +def test_global_functions_test_module(snapshot: SnapshotAssertion) -> None: + global_function_data = get_global_function_data(_main_test_module_name) + assert global_function_data == snapshot + + +def test_global_functions__reexport_module_1(snapshot: SnapshotAssertion) -> None: + global_function_data = get_global_function_data("_reexport_module_1") + assert global_function_data == snapshot + + +def test_global_functions__reexport_module_2(snapshot: SnapshotAssertion) -> None: + global_function_data = get_global_function_data("_reexport_module_2") + assert global_function_data == snapshot + + +def test_global_functions__reexport_module_3(snapshot: SnapshotAssertion) -> None: + global_function_data = get_global_function_data("_reexport_module_3") + assert global_function_data == snapshot + + +def test_global_functions__reexport_module_4(snapshot: SnapshotAssertion) -> None: + global_function_data = get_global_function_data("_reexport_module_4") + assert global_function_data == snapshot + + +# ############################## Class Methods ############################## # +def get_class_methods_data(class_name: str, docstring_style: str) -> list: + class_data: dict = _get_specific_class_data(class_name) + class_method_ids: list[str] = class_data["methods"] + + api_data = get_api_data(docstring_style) + all_functions: list[dict] = api_data["functions"] + + # Sort out the functions we need and return + return [method for method in all_functions if method["id"] in class_method_ids] + + +def test_class_methods_SomeClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_methods_data = get_class_methods_data("SomeClass", "plaintext") + assert class_methods_data == snapshot + + +def test_class_methods_NestedClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_methods_data = get_class_methods_data("NestedClass", "plaintext") + assert class_methods_data == snapshot + + +def test_class_methods__PrivateClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_methods_data = get_class_methods_data("_PrivateClass", "plaintext") + assert class_methods_data == snapshot + + +def test_class_methods_NestedPrivateClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_methods_data = get_class_methods_data("NestedPrivateClass", "plaintext") + assert class_methods_data == snapshot + + +def test_class_methods_NestedNestedPrivateClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_methods_data = get_class_methods_data("NestedNestedPrivateClass", "plaintext") + assert class_methods_data == snapshot + + +def test_class_methods_ReexportClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_methods_data = get_class_methods_data("ReexportClass", "plaintext") + assert class_methods_data == snapshot + + +def test_class_methods_EpydocDocstringClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_methods_data = get_class_methods_data("EpydocDocstringClass", "epydoc") + assert class_methods_data == snapshot + + +def test_class_methods_RestDocstringClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_methods_data = get_class_methods_data("RestDocstringClass", "rest") + assert class_methods_data == snapshot + + +def test_class_methods_NumpyDocstringClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_methods_data = get_class_methods_data("NumpyDocstringClass", "numpydoc") + assert class_methods_data == snapshot + + +def test_class_methods_GoogleDocstringClass(snapshot: SnapshotAssertion) -> None: # noqa: N802 + class_methods_data = get_class_methods_data("GoogleDocstringClass", "google") + assert class_methods_data == snapshot + + +# ############################## Function Parameters ############################## # +def get_function_parameter_data(function_name: str, parent_class_name: str, docstring_style: str) -> list: + function_data: dict = _get_specific_function_data(function_name, parent_class_name) + function_parameter_ids: list[str] = function_data["parameters"] + + api_data = get_api_data(docstring_style) + all_parameters: list[dict] = api_data["parameters"] + + # Sort out the parameters we need and return + return [parameter for parameter in all_parameters if parameter["id"] in function_parameter_ids] + + +def test_function_parameters_global_func(snapshot: SnapshotAssertion) -> None: + function_parameter_data = get_function_parameter_data("global_func", "", "plaintext") + assert function_parameter_data == snapshot + + +def test_function_parameters_SomeClass___init__(snapshot: SnapshotAssertion) -> None: # noqa: N802 + function_parameter_data = get_function_parameter_data("__init__", "SomeClass", "plaintext") + assert function_parameter_data == snapshot + + +def test_function_parameters_static_function(snapshot: SnapshotAssertion) -> None: + function_parameter_data = get_function_parameter_data("static_function", "SomeClass", "plaintext") + assert function_parameter_data == snapshot + + +def test_function_parameters_test_position(snapshot: SnapshotAssertion) -> None: + function_parameter_data = get_function_parameter_data("test_position", "SomeClass", "plaintext") + assert function_parameter_data == snapshot + + +def test_function_parameters_test_params(snapshot: SnapshotAssertion) -> None: + function_parameter_data = get_function_parameter_data("test_params", "SomeClass", "plaintext") + assert function_parameter_data == snapshot + + +def test_function_parameters_nested_class_function(snapshot: SnapshotAssertion) -> None: + function_parameter_data = get_function_parameter_data("nested_class_function", "NestedClass", "plaintext") + assert function_parameter_data == snapshot + + +def test_function_parameters_epydoc_docstring_func(snapshot: SnapshotAssertion) -> None: + function_parameter_data = get_function_parameter_data("epydoc_docstring_func", "EpydocDocstringClass", "epydoc") + assert function_parameter_data == snapshot + + +def test_function_parameters_EpydocDocstringClass___init__(snapshot: SnapshotAssertion) -> None: # noqa: N802 + function_parameter_data = get_function_parameter_data("__init__", "EpydocDocstringClass", "epydoc") + assert function_parameter_data == snapshot + + +def test_function_parameters_rest_docstring_func(snapshot: SnapshotAssertion) -> None: + function_parameter_data = get_function_parameter_data("rest_docstring_func", "RestDocstringClass", "rest") + assert function_parameter_data == snapshot + + +def test_function_parameters_RestDocstringClass___init__(snapshot: SnapshotAssertion) -> None: # noqa: N802 + function_parameter_data = get_function_parameter_data("__init__", "RestDocstringClass", "rest") + assert function_parameter_data == snapshot + + +def test_function_parameters_numpy_docstring_func(snapshot: SnapshotAssertion) -> None: + function_parameter_data = get_function_parameter_data("numpy_docstring_func", "NumpyDocstringClass", "numpydoc") + assert function_parameter_data == snapshot + + +def test_function_parameters_NumpyDocstringClass___init__(snapshot: SnapshotAssertion) -> None: # noqa: N802 + function_parameter_data = get_function_parameter_data("__init__", "NumpyDocstringClass", "numpydoc") + assert function_parameter_data == snapshot + + +def test_function_parameters_google_docstring_func(snapshot: SnapshotAssertion) -> None: + function_parameter_data = get_function_parameter_data("google_docstring_func", "GoogleDocstringClass", "google") + assert function_parameter_data == snapshot + + +def test_function_parameters_GoogleDocstringClass___init__(snapshot: SnapshotAssertion) -> None: # noqa: N802 + function_parameter_data = get_function_parameter_data("__init__", "GoogleDocstringClass", "google") + assert function_parameter_data == snapshot + + +# ############################## Function Results ############################## # +def get_function_result_data(function_name: str, parent_class_name: str, docstring_style: str) -> list: + function_data: dict = _get_specific_function_data(function_name, parent_class_name) + function_result_ids: list[str] = function_data["results"] + + api_data = get_api_data(docstring_style) + all_results: list[dict] = api_data["results"] + + # Sort out the results we need and return + return [result for result in all_results if result["id"] in function_result_ids] + + +def test_function_results_global_func(snapshot: SnapshotAssertion) -> None: + function_result_data = get_function_result_data("global_func", "", "plaintext") + assert function_result_data == snapshot + + +def test_function_results_multiple_results(snapshot: SnapshotAssertion) -> None: + function_result_data = get_function_result_data("multiple_results", "SomeClass", "plaintext") + assert function_result_data == snapshot + + +def test_function_results_static_function(snapshot: SnapshotAssertion) -> None: + function_result_data = get_function_result_data("static_function", "SomeClass", "plaintext") + assert function_result_data == snapshot + + +def test_function_results_test_position(snapshot: SnapshotAssertion) -> None: + function_result_data = get_function_result_data("test_position", "SomeClass", "plaintext") + assert function_result_data == snapshot + + +def test_function_results_nested_class_function(snapshot: SnapshotAssertion) -> None: + function_result_data = get_function_result_data("nested_class_function", "NestedClass", "plaintext") + assert function_result_data == snapshot + + +def test_function_results_epydoc_docstring_func(snapshot: SnapshotAssertion) -> None: + function_result_data = get_function_result_data("epydoc_docstring_func", "EpydocDocstringClass", "epydoc") + assert function_result_data == snapshot + + +def test_function_results_rest_docstring_func(snapshot: SnapshotAssertion) -> None: + function_result_data = get_function_result_data("rest_docstring_func", "RestDocstringClass", "rest") + assert function_result_data == snapshot + + +def test_function_results_numpy_docstring_func(snapshot: SnapshotAssertion) -> None: + function_result_data = get_function_result_data("numpy_docstring_func", "NumpyDocstringClass", "numpydoc") + assert function_result_data == snapshot + + +def test_function_results_google_docstring_func(snapshot: SnapshotAssertion) -> None: + function_result_data = get_function_result_data("google_docstring_func", "GoogleDocstringClass", "google") + assert function_result_data == snapshot diff --git a/tests/safeds_stubgen/api_analyzer/test_types.py b/tests/safeds_stubgen/api_analyzer/test_types.py new file mode 100644 index 00000000..02a7206e --- /dev/null +++ b/tests/safeds_stubgen/api_analyzer/test_types.py @@ -0,0 +1,542 @@ +from copy import deepcopy + +import pytest +from safeds_stubgen.api_analyzer import ( + AbstractType, + Attribute, + BoundaryType, + DictType, + EnumType, + FinalType, + ListType, + LiteralType, + NamedType, + OptionalType, + Parameter, + ParameterAssignment, + SetType, + TupleType, + UnionType, +) +from safeds_stubgen.docstring_parsing import AttributeDocstring, ParameterDocstring + + +def test_correct_hash() -> None: + parameter = Parameter( + id="test/test.Test/test/test_parameter_for_hashing", + name="test_parameter_for_hashing", + is_optional=True, + default_value="test_str", + assigned_by=ParameterAssignment.POSITION_OR_NAME, + docstring=ParameterDocstring("'hashvalue'", "r", "r"), + type=NamedType("str"), + ) + assert hash(parameter) == hash(deepcopy(parameter)) + enum_values = frozenset({"a", "b", "c"}) + enum_type = EnumType(enum_values, "full_match") + assert enum_type == deepcopy(enum_type) + assert hash(enum_type) == hash(deepcopy(enum_type)) + assert enum_type == EnumType(deepcopy(enum_values), "full_match") + assert hash(enum_type) == hash(EnumType(deepcopy(enum_values), "full_match")) + assert enum_type != EnumType(frozenset({"a", "b"}), "full_match") + assert hash(enum_type) != hash(EnumType(frozenset({"a", "b"}), "full_match")) + assert NamedType("a") == NamedType("a") + assert hash(NamedType("a")) == hash(NamedType("a")) + assert NamedType("a") != NamedType("b") + assert hash(NamedType("a")) != hash(NamedType("b")) + attribute = Attribute( + id="boundary", + name="boundary", + type=BoundaryType( + base_type="int", + min=0, + max=1, + min_inclusive=True, + max_inclusive=True, + ), + is_public=True, + is_static=True, + docstring=AttributeDocstring(), + ) + assert attribute == deepcopy(attribute) + assert hash(attribute) == hash(deepcopy(attribute)) + + +def test_named_type() -> None: + name = "str" + named_type = NamedType(name) + named_type_dict = {"kind": "NamedType", "name": name} + + assert AbstractType.from_dict(named_type_dict) == named_type + + assert NamedType.from_dict(named_type_dict) == named_type + assert NamedType.from_string(name) == named_type + + assert named_type.to_dict() == named_type_dict + + +def test_enum_type() -> None: + value = frozenset({"a", "b"}) + type_ = EnumType(value, "a, b") + type_dict = {"kind": "EnumType", "values": {"a", "b"}} + + assert AbstractType.from_dict(type_dict) == type_ + assert EnumType.from_dict(type_dict) == type_ + assert type_.to_dict() == type_dict + + +def test_boundary_type() -> None: + type_ = BoundaryType( + base_type="int", + min=1, + max="b", + min_inclusive=True, + max_inclusive=True, + ) + type_dict = { + "kind": "BoundaryType", + "base_type": "int", + "min": 1, + "max": "b", + "min_inclusive": True, + "max_inclusive": True, + } + + assert AbstractType.from_dict(type_dict) == type_ + assert BoundaryType.from_dict(type_dict) == type_ + assert type_.to_dict() == type_dict + + +def test_union_type() -> None: + union_type = UnionType([NamedType("str"), NamedType("int")]) + union_type_dict = { + "kind": "UnionType", + "types": [{"kind": "NamedType", "name": "str"}, {"kind": "NamedType", "name": "int"}], + } + + assert AbstractType.from_dict(union_type_dict) == union_type + assert UnionType.from_dict(union_type_dict) == union_type + assert union_type.to_dict() == union_type_dict + + assert UnionType([NamedType("a")]) == UnionType([NamedType("a")]) + assert hash(UnionType([NamedType("a")])) == hash(UnionType([NamedType("a")])) + assert UnionType([NamedType("a")]) != UnionType([NamedType("b")]) + assert hash(UnionType([NamedType("a")])) != hash(UnionType([NamedType("b")])) + + +def test_list_type() -> None: + list_type = ListType([NamedType("str"), NamedType("int")]) + list_type_dict = { + "kind": "ListType", + "types": [{"kind": "NamedType", "name": "str"}, {"kind": "NamedType", "name": "int"}], + } + + assert AbstractType.from_dict(list_type_dict) == list_type + assert ListType.from_dict(list_type_dict) == list_type + assert list_type.to_dict() == list_type_dict + + assert ListType([NamedType("a")]) == ListType([NamedType("a")]) + assert hash(ListType([NamedType("a")])) == hash(ListType([NamedType("a")])) + assert ListType([NamedType("a")]) != ListType([NamedType("b")]) + assert hash(ListType([NamedType("a")])) != hash(ListType([NamedType("b")])) + + +def test_dict_type() -> None: + dict_type = DictType( + key_type=UnionType([NamedType("str"), NamedType("int")]), + value_type=UnionType([NamedType("str"), NamedType("int")]), + ) + dict_type_dict = { + "kind": "DictType", + "key_type": { + "kind": "UnionType", + "types": [{"kind": "NamedType", "name": "str"}, {"kind": "NamedType", "name": "int"}], + }, + "value_type": { + "kind": "UnionType", + "types": [{"kind": "NamedType", "name": "str"}, {"kind": "NamedType", "name": "int"}], + }, + } + + assert AbstractType.from_dict(dict_type_dict) == dict_type + assert DictType.from_dict(dict_type_dict) == dict_type + assert dict_type.to_dict() == dict_type_dict + + assert DictType(NamedType("a"), NamedType("a")) == DictType(NamedType("a"), NamedType("a")) + assert hash(DictType(NamedType("a"), NamedType("a"))) == hash(DictType(NamedType("a"), NamedType("a"))) + assert DictType(NamedType("a"), NamedType("a")) != DictType(NamedType("b"), NamedType("a")) + assert hash(DictType(NamedType("a"), NamedType("a"))) != hash(DictType(NamedType("b"), NamedType("a"))) + + +def test_set_type() -> None: + set_type = SetType([NamedType("str"), NamedType("int")]) + set_type_dict = { + "kind": "SetType", + "types": [{"kind": "NamedType", "name": "str"}, {"kind": "NamedType", "name": "int"}], + } + + assert AbstractType.from_dict(set_type_dict) == set_type + assert SetType.from_dict(set_type_dict) == set_type + assert set_type.to_dict() == set_type_dict + + assert SetType([NamedType("a")]) == SetType([NamedType("a")]) + assert hash(SetType([NamedType("a")])) == hash(SetType([NamedType("a")])) + assert SetType([NamedType("a")]) != SetType([NamedType("b")]) + assert hash(SetType([NamedType("a")])) != hash(SetType([NamedType("b")])) + + +def test_optional_type() -> None: + type_ = OptionalType(NamedType("some_type")) + type_dict = { + "kind": "OptionalType", + "type": {"kind": "NamedType", "name": "some_type"}, + } + + assert AbstractType.from_dict(type_dict) == type_ + assert OptionalType.from_dict(type_dict) == type_ + assert type_.to_dict() == type_dict + + assert OptionalType(NamedType("a")) == OptionalType(NamedType("a")) + assert hash(OptionalType(NamedType("a"))) == hash(OptionalType(NamedType("a"))) + assert OptionalType(NamedType("a")) != OptionalType(NamedType("b")) + assert hash(OptionalType(NamedType("a"))) != hash(OptionalType(NamedType("b"))) + + +def test_literal_type() -> None: + type_ = LiteralType(["Literal_1", 2]) + type_dict = { + "kind": "LiteralType", + "literals": ["Literal_1", 2], + } + + assert AbstractType.from_dict(type_dict) == type_ + assert LiteralType.from_dict(type_dict) == type_ + assert type_.to_dict() == type_dict + + assert LiteralType(["a"]) == LiteralType(["a"]) + assert hash(LiteralType(["a"])) == hash(LiteralType(["a"])) + assert LiteralType(["a"]) != LiteralType(["b"]) + assert hash(LiteralType(["a"])) != hash(LiteralType(["b"])) + + +def test_final_type() -> None: + type_ = FinalType(NamedType("some_type")) + type_dict = { + "kind": "FinalType", + "type": {"kind": "NamedType", "name": "some_type"}, + } + + assert AbstractType.from_dict(type_dict) == type_ + assert FinalType.from_dict(type_dict) == type_ + assert type_.to_dict() == type_dict + + assert FinalType(NamedType("a")) == FinalType(NamedType("a")) + assert hash(FinalType(NamedType("a"))) == hash(FinalType(NamedType("a"))) + assert FinalType(NamedType("a")) != FinalType(NamedType("b")) + assert hash(FinalType(NamedType("a"))) != hash(FinalType(NamedType("b"))) + + +def test_tuple_type() -> None: + set_type = TupleType([NamedType("str"), NamedType("int")]) + set_type_dict = { + "kind": "TupleType", + "types": [{"kind": "NamedType", "name": "str"}, {"kind": "NamedType", "name": "int"}], + } + + assert AbstractType.from_dict(set_type_dict) == set_type + assert TupleType.from_dict(set_type_dict) == set_type + assert set_type.to_dict() == set_type_dict + + assert TupleType([NamedType("a")]) == TupleType([NamedType("a")]) + assert hash(TupleType([NamedType("a")])) == hash(TupleType([NamedType("a")])) + assert TupleType([NamedType("a")]) != TupleType([NamedType("b")]) + assert hash(TupleType([NamedType("a")])) != hash(TupleType([NamedType("b")])) + + +def test_abstract_type_from_dict_exception() -> None: + with pytest.raises(ValueError, match="Cannot parse unknown_type value."): + AbstractType.from_dict({"kind": "unknown_type"}) + + +@pytest.mark.parametrize( + ("string", "expected"), + [ + ( + ( + "float, default=0.0 Tolerance for singular values computed by svd_solver == 'arpack'.\nMust be of range" + " [0.0, infinity).\n\n.. versionadded:: 0.18.0" + ), + BoundaryType( + base_type="float", + min=0, + max="Infinity", + min_inclusive=True, + max_inclusive=True, + ), + ), + ( + """If bootstrap is True, the number of samples to draw from X\nto train each base estimator.\n\n + - If None (default), then draw `X.shape[0]` samples.\n- If int, then draw `max_samples` samples.\n + - If float, then draw `max_samples * X.shape[0]` samples. Thus,\n `max_samples` should be in the interval `(0.0, 1.0]`.\n\n.. + versionadded:: 0.22""", + BoundaryType( + base_type="float", + min=0, + max=1, + min_inclusive=False, + max_inclusive=True, + ), + ), + ( + """When building the vocabulary ignore terms that have a document\nfrequency strictly lower than the given threshold. This value is also\n + called cut-off in the literature.\nIf float in range of [0.0, 1.0], the parameter represents a proportion\nof documents, integer absolute counts.\n + This parameter is ignored if vocabulary is not None.""", + BoundaryType( + base_type="float", + min=0, + max=1, + min_inclusive=True, + max_inclusive=True, + ), + ), + ( + """float in range [0.0, 1.0] or int, default=1.0 When building the vocabulary ignore terms that have a document\n + frequency strictly higher than the given threshold (corpus-specific\nstop words).\nIf float, the parameter represents a proportion of documents, integer\n + absolute counts.\nThis parameter is ignored if vocabulary is not None.""", + BoundaryType( + base_type="float", + min=0, + max=1, + min_inclusive=True, + max_inclusive=True, + ), + ), + ( + ( + "Tolerance for singular values computed by svd_solver == 'arpack'.\nMust be of range [-2, -1].\n\n.." + " versionadded:: 0.18.0" + ), + BoundaryType( + base_type="float", + min=-2, + max=-1, + min_inclusive=True, + max_inclusive=True, + ), + ), + ( + "Damping factor in the range (-1, -0.5)", + BoundaryType( + base_type="float", + min=-1, + max=-0.5, + min_inclusive=False, + max_inclusive=False, + ), + ), + ( + "'max_samples' should be in the interval (-1.0, -0.5]", + BoundaryType( + base_type="float", + min=-1.0, + max=-0.5, + min_inclusive=False, + max_inclusive=True, + ), + ), + ], +) +def test_boundaries_from_string(string: str, expected: BoundaryType) -> None: + ref_type = BoundaryType.from_string(string) + assert ref_type == expected + + +@pytest.mark.parametrize( + ("docstring_type", "expected"), + [ + ("", ""), + ('{"frobenius", "spectral"}, default="frobenius"', {"frobenius", "spectral"}), + ( + "{'strict', 'ignore', 'replace'}, default='strict'", + {"strict", "ignore", "replace"}, + ), + ( + "{'linear', 'poly', 'rbf', 'sigmoid', 'cosine', 'precomputed'}, default='linear'", + {"linear", "poly", "rbf", "sigmoid", "cosine", "precomputed"}, + ), + # https://github.com/lars-reimann/sem21/pull/30#discussion_r771288528 + (r"{\"frobenius\", \'spectral\'}", set()), + (r"""{"frobenius'}""", set()), + (r"""{'spectral"}""", set()), + (r"""{'text\", \"that'}""", {'text", "that'}), + (r"""{'text", "that'}""", {'text", "that'}), + (r"{'text\', \'that'}", {"text', 'that"}), + (r"{'text', 'that'}", {"text", "that"}), + (r"""{"text\', \'that"}""", {"text', 'that"}), + (r"""{"text', 'that"}""", {"text', 'that"}), + (r"""{"text\", \"that"}""", {'text", "that'}), + (r'{"text", "that"}', {"text", "that"}), + (r"""{\"not', 'be', 'matched'}""", {", "}), + ("""{"gini\\", \\"entropy"}""", {'gini", "entropy'}), + ("""{'best\\', \\'random'}""", {"best', 'random"}), + ], +) +def test_enum_from_string(docstring_type: str, expected: set[str] | None) -> None: + result = EnumType.from_string(docstring_type) + if result is not None: + assert result.values == expected + + +# Todo create_type Tests deactivated since create_type is not in use yet +# @pytest.mark.parametrize( +# ("docstring_type", "expected"), +# [ +# ( +# "", +# {"kind": "NamedType", "name": "None"} +# ), +# ( +# "int, or None, 'manual', {'auto', 'sqrt', 'log2'}, default='auto'", +# { +# "kind": "UnionType", +# "types": [ +# {"kind": "EnumType", "values": {"auto", "log2", "sqrt"}}, +# {"kind": "NamedType", "name": "int"}, +# {"kind": "NamedType", "name": "None"}, +# {"kind": "NamedType", "name": "'manual'"}, +# ], +# }, +# ), +# ( +# "tuple of slice, AUTO or array of shape (12,2), default=(slice(70, 195), slice(78, 172))", +# { +# "kind": "UnionType", +# "types": [ +# {"kind": "NamedType", "name": "tuple of slice"}, +# {"kind": "NamedType", "name": "AUTO"}, +# {"kind": "NamedType", "name": "array of shape (12,2)"}, +# ], +# }, +# ), +# ("object", {"kind": "NamedType", "name": "object"}), +# ( +# "ndarray, shape (n_samples,), default=None", +# { +# "kind": "UnionType", +# "types": [ +# {"kind": "NamedType", "name": "ndarray"}, +# {"kind": "NamedType", "name": "shape (n_samples,)"}, +# ], +# }, +# ), +# ( +# "estor adventus or None", +# { +# "kind": "UnionType", +# "types": [ +# {"kind": "NamedType", "name": "estor adventus"}, +# {"kind": "NamedType", "name": "None"}, +# ], +# }, +# ), +# ( +# "int or array-like, shape (n_samples, n_classes) or (n_samples, 1) when binary.", +# { +# "kind": "UnionType", +# "types": [ +# {"kind": "NamedType", "name": "int"}, +# {"kind": "NamedType", "name": "array-like"}, +# { +# "kind": "NamedType", +# "name": "shape (n_samples, n_classes) or (n_samples, 1) when binary.", +# }, +# ], +# }, +# ), +# ], +# ) +# def test_union_from_string(docstring_type: str, expected: dict[str, Any]) -> None: +# result = create_type(docstring_type, docstring_type) +# if result is None: +# assert expected == {} +# else: +# assert result.to_dict() == expected + + +# @pytest.mark.parametrize( +# ("description", "expected"), +# [ +# ( +# "Scale factor between inner and outer circle in the range `[0, 1)`", +# { +# "base_type": "float", +# "kind": "BoundaryType", +# "max": 1.0, +# "max_inclusive": False, +# "min": 0.0, +# "min_inclusive": True, +# }, +# ), +# ( +# ( +# "Tolerance for singular values computed by svd_solver == 'arpack'.\nMust be of range [1," +# " infinity].\n\n.. versionadded:: 0.18.0" +# ), +# { +# "base_type": "float", +# "kind": "BoundaryType", +# "max": "Infinity", +# "max_inclusive": True, +# "min": 1.0, +# "min_inclusive": True, +# }, +# ), +# ("", {}), +# ], +# ) +# def test_boundary_from_string(description: str, expected: dict[str, Any]) -> None: +# result = create_type(ParameterDocstring("", "", description)) +# if result is None: +# assert expected == {} +# else: +# assert result.to_dict() == expected + + +# @pytest.mark.parametrize( +# ("docstring_type", "docstring_description", "expected"), +# [ +# ( +# "int or 'Auto', or {'today', 'yesterday'}", +# "int in the range `[0, 10]`", +# { +# "kind": "UnionType", +# "types": [ +# { +# "base_type": "int", +# "kind": "BoundaryType", +# "max": 10.0, +# "max_inclusive": True, +# "min": 0.0, +# "min_inclusive": True, +# }, +# {"kind": "EnumType", "values": ["today", "yesterday"]}, +# {"kind": "NamedType", "name": "int"}, +# {"kind": "NamedType", "name": "'Auto'"}, +# ], +# }, +# ), +# ], +# ) +# def test_boundary_and_union_from_string( +# docstring_type: str, +# docstring_description: str, +# expected: dict[str, Any], +# ) -> None: +# result = create_type( +# ParameterDocstring(type=docstring_type, default_value="", description=docstring_description), +# ) +# +# if result is None: +# assert expected == {} +# else: +# assert result.to_dict() == expected diff --git a/tests/safeds_stubgen/docstring_parsing/__init__.py b/tests/safeds_stubgen/docstring_parsing/__init__.py new file mode 100644 index 00000000..9d48db4f --- /dev/null +++ b/tests/safeds_stubgen/docstring_parsing/__init__.py @@ -0,0 +1 @@ +from __future__ import annotations diff --git a/tests/safeds_stubgen/docstring_parsing/test_docstring_style.py b/tests/safeds_stubgen/docstring_parsing/test_docstring_style.py new file mode 100644 index 00000000..dfe59d68 --- /dev/null +++ b/tests/safeds_stubgen/docstring_parsing/test_docstring_style.py @@ -0,0 +1,13 @@ +import pytest +from safeds_stubgen.docstring_parsing import DocstringStyle + + +def test_from_string() -> None: + assert DocstringStyle.from_string("plaintext") == DocstringStyle.PLAINTEXT + assert DocstringStyle.from_string("epydoc") == DocstringStyle.EPYDOC + assert DocstringStyle.from_string("google") == DocstringStyle.GOOGLE + assert DocstringStyle.from_string("numpydoc") == DocstringStyle.NUMPYDOC + assert DocstringStyle.from_string("rest") == DocstringStyle.REST + + with pytest.raises(ValueError, match="Unknown docstring style: unknown_docstyle"): + DocstringStyle.from_string("unknown_docstyle") diff --git a/tests/safeds_stubgen/docstring_parsing/test_epydoc_parser.py b/tests/safeds_stubgen/docstring_parsing/test_epydoc_parser.py new file mode 100644 index 00000000..954a4dc9 --- /dev/null +++ b/tests/safeds_stubgen/docstring_parsing/test_epydoc_parser.py @@ -0,0 +1,297 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest +from mypy import nodes +from safeds_stubgen.api_analyzer import Class, ParameterAssignment, get_classdef_definitions + +# noinspection PyProtectedMember +from safeds_stubgen.api_analyzer._get_api import _get_mypy_ast +from safeds_stubgen.docstring_parsing import ( + ClassDocstring, + EpydocParser, + FunctionDocstring, + ParameterDocstring, + ResultDocstring, +) + +from tests.safeds_stubgen._helpers import get_specific_mypy_node + +# Setup +_test_dir = Path(__file__).parent.parent.parent +mypy_file = _get_mypy_ast( + files=[ + str(Path(_test_dir / "data" / "test_docstring_parser_package" / "test_epydoc.py")), + ], + package_paths=[], + root=Path(_test_dir / "data" / "test_docstring_parser_package"), +)[0] + + +@pytest.fixture() +def epydoc_parser() -> EpydocParser: + return EpydocParser() + + +# ############################## Class Documentation ############################## # +@pytest.mark.parametrize( + ("class_name", "expected_class_documentation"), + [ + ( + "ClassWithDocumentation", + ClassDocstring( + description="Lorem ipsum. Code::\n\npass\n\nDolor sit amet.", + full_docstring="Lorem ipsum. Code::\n\n pass\n\nDolor sit amet.", + ), + ), + ( + "ClassWithoutDocumentation", + ClassDocstring( + description="", + full_docstring="", + ), + ), + ], + ids=[ + "class with documentation", + "class without documentation", + ], +) +def test_get_class_documentation( + epydoc_parser: EpydocParser, + class_name: str, + expected_class_documentation: ClassDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, class_name) + + assert isinstance(node, nodes.ClassDef) + assert epydoc_parser.get_class_documentation(node) == expected_class_documentation + + +# ############################## Function Documentation ############################## # +@pytest.mark.parametrize( + ("function_name", "expected_function_documentation"), + [ + ( + "function_with_documentation", + FunctionDocstring( + description="Lorem ipsum. Code::\n\npass\n\nDolor sit amet.", + full_docstring="Lorem ipsum. Code::\n\n pass\n\nDolor sit amet.", + ), + ), + ( + "function_without_documentation", + FunctionDocstring( + description="", + full_docstring="", + ), + ), + ], + ids=[ + "function with documentation", + "function without documentation", + ], +) +def test_get_function_documentation( + epydoc_parser: EpydocParser, + function_name: str, + expected_function_documentation: FunctionDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, function_name) + + assert isinstance(node, nodes.FuncDef) + assert epydoc_parser.get_function_documentation(node) == expected_function_documentation + + +# ############################## Parameter Documentation ############################## # +@pytest.mark.parametrize( + ("name", "is_class", "parameter_name", "parameter_assigned_by", "expected_parameter_documentation"), + [ + ( + "ClassWithParameters", + True, + "p", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="1", + description="foo defaults to 1", + ), + ), + ( + "ClassWithParameters", + True, + "missing", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="", + default_value="", + description="", + ), + ), + ( + "function_with_parameters", + False, + "no_type_no_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="", + default_value="", + description="no type and no default", + ), + ), + ( + "function_with_parameters", + False, + "type_no_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="", + description="type but no default", + ), + ), + ( + "function_with_parameters", + False, + "with_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="2", + description="foo that defaults to 2", + ), + ), + ( + "function_with_parameters", + False, + "missing", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring(type="", default_value="", description=""), + ), + ], + ids=[ + "existing class parameter", + "missing class parameter", + "function parameter with no type and no default", + "function parameter with type and no default", + "function parameter with default", + "missing function parameter", + ], +) +def test_get_parameter_documentation( + epydoc_parser: EpydocParser, + name: str, + is_class: bool, + parameter_name: str, + parameter_assigned_by: ParameterAssignment, + expected_parameter_documentation: ParameterDocstring, +) -> None: + parent = None + node = get_specific_mypy_node(mypy_file, name) + if is_class: + assert isinstance(node, nodes.ClassDef) + class_doc = epydoc_parser.get_class_documentation(node) + parent = Class(id=node.fullname, name=node.name, superclasses=[], is_public=True, docstring=class_doc) + else: + assert isinstance(node, nodes.FuncDef) + + # Find the constructor + if isinstance(node, nodes.ClassDef): + for definition in get_classdef_definitions(node): + if isinstance(definition, nodes.FuncDef) and definition.name == "__init__": + node = definition + break + assert isinstance(node, nodes.FuncDef) + + parameter_documentation = epydoc_parser.get_parameter_documentation( + function_node=node, + parameter_name=parameter_name, + parameter_assigned_by=parameter_assigned_by, + parent_class=parent, + ) + + assert parameter_documentation == expected_parameter_documentation + + +# ############################## Attribute Documentation ############################## # +# Todo Epydoc: Attribute handling not yet implemented in dosctring_parser library, thus the tests +# also don't work yet and are therefore deactivated! +@pytest.mark.parametrize( + ("class_name", "attribute_name", "expected_parameter_documentation"), + [ + ( + "ClassWithAttributes", + "p", + ParameterDocstring( + type="int", + default_value="1", + description="foo defaults to 1", + ), + ), + ( + "ClassWithAttributesNoType", + "p", + ParameterDocstring( + type="", + default_value="1", + description="foo defaults to 1", + ), + ), + ], + ids=[ + "existing class attributes", + "existing class attributes no type", + ], +) +def xtest_get_attribute_documentation( + epydoc_parser: EpydocParser, + class_name: str, + attribute_name: str, + expected_parameter_documentation: ParameterDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, class_name) + assert isinstance(node, nodes.ClassDef) + docstring = epydoc_parser.get_class_documentation(node) + fake_class = Class(id="some_id", name="some_class", superclasses=[], is_public=True, docstring=docstring) + + attribute_documentation = epydoc_parser.get_attribute_documentation( + parent_class=fake_class, + attribute_name=attribute_name, + ) + + assert attribute_documentation == expected_parameter_documentation + + +# ############################## Result Documentation ############################## # +@pytest.mark.parametrize( + ("function_name", "expected_result_documentation"), + [ + ( + "function_with_result_value_and_type", + ResultDocstring(type="float", description="return value"), + ), + ( + "function_with_result_value_no_type", + ResultDocstring(type="", description="return value"), + ), + ( + "function_without_result_value", + ResultDocstring(type="", description=""), + ), + ], + ids=[ + "existing return value and type", + "existing return value no type", + "function without return value", + ], +) +def test_get_result_documentation( + epydoc_parser: EpydocParser, + function_name: str, + expected_result_documentation: ResultDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, function_name) + assert isinstance(node, nodes.FuncDef) + assert epydoc_parser.get_result_documentation(node) == expected_result_documentation diff --git a/tests/safeds_stubgen/docstring_parsing/test_get_full_docstring.py b/tests/safeds_stubgen/docstring_parsing/test_get_full_docstring.py new file mode 100644 index 00000000..fcbbe0ff --- /dev/null +++ b/tests/safeds_stubgen/docstring_parsing/test_get_full_docstring.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest +from mypy import nodes + +# noinspection PyProtectedMember +from safeds_stubgen.api_analyzer._get_api import _get_mypy_ast + +# noinspection PyProtectedMember +from safeds_stubgen.docstring_parsing._helpers import get_full_docstring + +from tests.safeds_stubgen._helpers import get_specific_mypy_node + +# Setup +_test_dir = Path(__file__).parent.parent.parent +mypy_file = _get_mypy_ast( + files=[ + str(Path(_test_dir / "data" / "test_docstring_parser_package" / "test_full_docstring.py")), + ], + package_paths=[], + root=Path(_test_dir / "data" / "test_docstring_parser_package"), +)[0] + + +@pytest.mark.parametrize( + ("name", "expected_docstring"), + [ + ( + "ClassWithMultiLineDocumentation", + "Lorem ipsum.\n\nDolor sit amet.", + ), + ( + "ClassWithSingleLineDocumentation", + "Lorem ipsum.", + ), + ( + "ClassWithoutDocumentation", + "", + ), + ( + "function_with_multi_line_documentation", + "Lorem ipsum.\n\nDolor sit amet.", + ), + ( + "function_with_single_line_documentation", + "Lorem ipsum.", + ), + ( + "function_without_documentation", + "", + ), + ], + ids=[ + "class with multi line documentation", + "class with single line documentation", + "class without documentation", + "function with multi line documentation", + "function with single line documentation", + "function without documentation", + ], +) +def test_get_full_docstring(name: str, expected_docstring: str) -> None: + node = get_specific_mypy_node(mypy_file, name) + + assert isinstance(node, nodes.ClassDef | nodes.FuncDef) + assert get_full_docstring(node) == expected_docstring diff --git a/tests/safeds_stubgen/docstring_parsing/test_googledoc_parser.py b/tests/safeds_stubgen/docstring_parsing/test_googledoc_parser.py new file mode 100644 index 00000000..a4255b2d --- /dev/null +++ b/tests/safeds_stubgen/docstring_parsing/test_googledoc_parser.py @@ -0,0 +1,339 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest +from mypy import nodes +from safeds_stubgen.api_analyzer import Class, ParameterAssignment, get_classdef_definitions + +# noinspection PyProtectedMember +from safeds_stubgen.api_analyzer._get_api import _get_mypy_ast +from safeds_stubgen.docstring_parsing import ( + ClassDocstring, + FunctionDocstring, + GoogleDocParser, + ParameterDocstring, + ResultDocstring, +) + +# noinspection PyProtectedMember +from safeds_stubgen.docstring_parsing._docstring import AttributeDocstring + +from tests.safeds_stubgen._helpers import get_specific_mypy_node + +# Setup +_test_dir = Path(__file__).parent.parent.parent +mypy_file = _get_mypy_ast( + files=[ + str(Path(_test_dir / "data" / "test_docstring_parser_package" / "test_googledoc.py")), + ], + package_paths=[], + root=Path(_test_dir / "data" / "test_docstring_parser_package"), +)[0] + + +@pytest.fixture() +def googlestyledoc_parser() -> GoogleDocParser: + return GoogleDocParser() + + +# ############################## Class Documentation ############################## # +@pytest.mark.parametrize( + ("class_name", "expected_class_documentation"), + [ + ( + "ClassWithDocumentation", + ClassDocstring( + description="Lorem ipsum. Code::\n\npass\n\nDolor sit amet.", + full_docstring="Lorem ipsum. Code::\n\n pass\n\nDolor sit amet.", + ), + ), + ( + "ClassWithoutDocumentation", + ClassDocstring( + description="", + full_docstring="", + ), + ), + ], + ids=[ + "class with documentation", + "class without documentation", + ], +) +def test_get_class_documentation( + googlestyledoc_parser: GoogleDocParser, + class_name: str, + expected_class_documentation: ClassDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, class_name) + + assert isinstance(node, nodes.ClassDef) + assert googlestyledoc_parser.get_class_documentation(node) == expected_class_documentation + + +# ############################## Function Documentation ############################## # +@pytest.mark.parametrize( + ("function_name", "expected_function_documentation"), + [ + ( + "function_with_documentation", + FunctionDocstring( + description="Lorem ipsum. Code::\n\npass\n\nDolor sit amet.", + full_docstring="Lorem ipsum. Code::\n\n pass\n\nDolor sit amet.", + ), + ), + ( + "function_without_documentation", + FunctionDocstring( + description="", + full_docstring="", + ), + ), + ], + ids=[ + "function with documentation", + "function without documentation", + ], +) +def test_get_function_documentation( + googlestyledoc_parser: GoogleDocParser, + function_name: str, + expected_function_documentation: FunctionDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, function_name) + + assert isinstance(node, nodes.FuncDef) + assert googlestyledoc_parser.get_function_documentation(node) == expected_function_documentation + + +# ############################## Parameter Documentation ############################## # +@pytest.mark.parametrize( + ("name", "is_class", "parameter_name", "parameter_assigned_by", "expected_parameter_documentation"), + [ + ( + "ClassWithParameters", + True, + "p", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="1", + description="foo. Defaults to 1.", + ), + ), + ( + "ClassWithParameters", + True, + "missing", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="", + default_value="", + description="", + ), + ), + ( + "function_with_parameters", + False, + "no_type_no_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="", + default_value="", + description="no type and no default.", + ), + ), + ( + "function_with_parameters", + False, + "type_no_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="", + description="type but no default.", + ), + ), + ( + "function_with_parameters", + False, + "with_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="2", + description="foo. Defaults to 2.", + ), + ), + ( + "function_with_parameters", + False, + "*args", + ParameterAssignment.POSITIONAL_VARARG, + ParameterDocstring( + type="int", + default_value="", + description="foo: *args", + ), + ), + ( + "function_with_parameters", + False, + "**kwargs", + ParameterAssignment.NAMED_VARARG, + ParameterDocstring( + type="int", + default_value="", + description="foo: **kwargs", + ), + ), + ( + "function_with_parameters", + False, + "missing", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring(type="", default_value="", description=""), + ), + ( + "function_with_attributes_and_parameters", + False, + "q", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="2", + description="foo. Defaults to 2.", + ), + ), + ( + "function_with_attributes_and_parameters", + False, + "p", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="", + default_value="", + description="", + ), + ), + ], + ids=[ + "existing class parameter", + "missing class parameter", + "function parameter with no type and no default", + "function parameter with type and no default", + "function parameter with default", + "function parameter with positional vararg", + "function parameter with named vararg", + "missing function parameter", + "function with attributes and parameters existing parameter", + "function with attributes and parameters missing parameter", + ], +) +def test_get_parameter_documentation( + googlestyledoc_parser: GoogleDocParser, + name: str, + is_class: bool, + parameter_name: str, + parameter_assigned_by: ParameterAssignment, + expected_parameter_documentation: ParameterDocstring, +) -> None: + parent = None + node = get_specific_mypy_node(mypy_file, name) + if is_class: + assert isinstance(node, nodes.ClassDef) + class_doc = googlestyledoc_parser.get_class_documentation(node) + parent = Class(id=node.fullname, name=node.name, superclasses=[], is_public=True, docstring=class_doc) + else: + assert isinstance(node, nodes.FuncDef) + + # Find the constructor + if isinstance(node, nodes.ClassDef): + for definition in get_classdef_definitions(node): + if isinstance(definition, nodes.FuncDef) and definition.name == "__init__": + node = definition + break + assert isinstance(node, nodes.FuncDef) + + parameter_documentation = googlestyledoc_parser.get_parameter_documentation( + function_node=node, + parameter_name=parameter_name, + parameter_assigned_by=parameter_assigned_by, + parent_class=parent, + ) + + assert parameter_documentation == expected_parameter_documentation + + +# ############################## Attribute Documentation ############################## # +@pytest.mark.parametrize( + ("class_name", "attribute_name", "expected_attribute_documentation"), + [ + ( + "ClassWithAttributes", + "p", + AttributeDocstring( + type="int", + default_value="1", + description="foo. Defaults to 1.", + ), + ), + ( + "ClassWithAttributes", + "missing", + AttributeDocstring( + type="", + default_value="", + description="", + ), + ), + ], + ids=[ + "existing class attribute", + "missing class attribute", + ], +) +def test_get_attribute_documentation( + googlestyledoc_parser: GoogleDocParser, + class_name: str, + attribute_name: str, + expected_attribute_documentation: AttributeDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, class_name) + assert isinstance(node, nodes.ClassDef) + docstring = googlestyledoc_parser.get_class_documentation(node) + fake_class = Class(id="some_id", name="some_class", superclasses=[], is_public=True, docstring=docstring) + + attribute_documentation = googlestyledoc_parser.get_attribute_documentation( + parent_class=fake_class, + attribute_name=attribute_name, + ) + + assert attribute_documentation == expected_attribute_documentation + + +# ############################## Result Documentation ############################## # +@pytest.mark.parametrize( + ("function_name", "expected_result_documentation"), + [ + ( + "function_with_return_value_and_type", + ResultDocstring(type="int", description="this will be the return value."), + ), + ( + "function_with_return_value_no_type", + ResultDocstring(type="", description="int"), + ), + ("function_without_return_value", ResultDocstring(type="", description="")), + ], + ids=["existing return value and type", "existing return value no description", "function without return value"], +) +def test_get_result_documentation( + googlestyledoc_parser: GoogleDocParser, + function_name: str, + expected_result_documentation: ResultDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, function_name) + assert isinstance(node, nodes.FuncDef) + assert googlestyledoc_parser.get_result_documentation(node) == expected_result_documentation diff --git a/tests/safeds_stubgen/docstring_parsing/test_numpydoc_parser.py b/tests/safeds_stubgen/docstring_parsing/test_numpydoc_parser.py new file mode 100644 index 00000000..ff2ac332 --- /dev/null +++ b/tests/safeds_stubgen/docstring_parsing/test_numpydoc_parser.py @@ -0,0 +1,502 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest +from mypy import nodes +from safeds_stubgen.api_analyzer import Class, ParameterAssignment, get_classdef_definitions + +# noinspection PyProtectedMember +from safeds_stubgen.api_analyzer._get_api import _get_mypy_ast +from safeds_stubgen.docstring_parsing import ( + ClassDocstring, + FunctionDocstring, + NumpyDocParser, + ParameterDocstring, + ResultDocstring, +) + +# noinspection PyProtectedMember +from safeds_stubgen.docstring_parsing._docstring import AttributeDocstring + +from tests.safeds_stubgen._helpers import get_specific_mypy_node + +# Setup +_test_dir = Path(__file__).parent.parent.parent +_test_package_name = "test_docstring_parser_package" +mypy_file = _get_mypy_ast( + files=[ + str(Path(_test_dir / "data" / _test_package_name / "test_numpydoc.py")), + ], + package_paths=[], + root=Path(_test_dir / "data" / _test_package_name), +)[0] + + +@pytest.fixture() +def numpydoc_parser() -> NumpyDocParser: + return NumpyDocParser() + + +# ############################## Class Documentation ############################## # +@pytest.mark.parametrize( + ("class_name", "expected_class_documentation"), + [ + ( + "ClassWithDocumentation", + ClassDocstring( + description="Lorem ipsum. Code::\n\npass\n\nDolor sit amet.", + full_docstring="Lorem ipsum. Code::\n\n pass\n\nDolor sit amet.", + ), + ), + ( + "ClassWithoutDocumentation", + ClassDocstring( + description="", + full_docstring="", + ), + ), + ], + ids=[ + "class with documentation", + "class without documentation", + ], +) +def test_get_class_documentation( + numpydoc_parser: NumpyDocParser, + class_name: str, + expected_class_documentation: ClassDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, class_name) + + assert isinstance(node, nodes.ClassDef) + assert numpydoc_parser.get_class_documentation(node) == expected_class_documentation + + +# ############################## Function Documentation ############################## # +@pytest.mark.parametrize( + ("function_name", "expected_function_documentation"), + [ + ( + "function_with_documentation", + FunctionDocstring( + description="Lorem ipsum. Code::\n\npass\n\nDolor sit amet.", + full_docstring="Lorem ipsum. Code::\n\n pass\n\nDolor sit amet.", + ), + ), + ( + "function_without_documentation", + FunctionDocstring( + description="", + full_docstring="", + ), + ), + ], + ids=[ + "function with documentation", + "function without documentation", + ], +) +def test_get_function_documentation( + numpydoc_parser: NumpyDocParser, + function_name: str, + expected_function_documentation: FunctionDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, function_name) + + assert isinstance(node, nodes.FuncDef) + assert numpydoc_parser.get_function_documentation(node) == expected_function_documentation + + +# ############################## Parameter Documentation ############################## # +@pytest.mark.parametrize( + ("name", "is_class", "parameter_name", "parameter_assigned_by", "expected_parameter_documentation"), + [ + ( + "ClassWithParameters", + True, + "p", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="1", + description="foo", + ), + ), + ( + "ClassWithParameters", + True, + "missing", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="", + default_value="", + description="", + ), + ), + ( + "function_with_parameters", + False, + "no_type_no_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="", + default_value="", + description="foo: no_type_no_default. Code::\n\n pass", + ), + ), + ( + "function_with_parameters", + False, + "type_no_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="", + description="foo: type_no_default", + ), + ), + ( + "function_with_parameters", + False, + "optional_unknown_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="", + description="foo: optional_unknown_default", + ), + ), + ( + "function_with_parameters", + False, + "with_default_syntax_1", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="1", + description="foo: with_default_syntax_1", + ), + ), + ( + "function_with_parameters", + False, + "with_default_syntax_2", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring(type="int", default_value="2", description="foo: with_default_syntax_2"), + ), + ( + "function_with_parameters", + False, + "with_default_syntax_3", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring(type="int", default_value="3", description="foo: with_default_syntax_3"), + ), + ( + "function_with_parameters", + False, + "grouped_parameter_1", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="4", + description="foo: grouped_parameter_1 and grouped_parameter_2", + ), + ), + ( + "function_with_parameters", + False, + "grouped_parameter_2", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="4", + description="foo: grouped_parameter_1 and grouped_parameter_2", + ), + ), + ( + "function_with_parameters", + False, + "args", + ParameterAssignment.POSITIONAL_VARARG, + ParameterDocstring( + type="int", + default_value="", + description="foo: *args", + ), + ), + ( + "function_with_parameters", + False, + "kwargs", + ParameterAssignment.NAMED_VARARG, + ParameterDocstring( + type="int", + default_value="", + description="foo: **kwargs", + ), + ), + ( + "function_with_parameters", + False, + "missing", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring(type="", default_value="", description=""), + ), + ( + "ClassAndFunctionWithParameters", + True, + "x", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="str", + default_value="", + description="Lorem ipsum 1.", + ), + ), + ( + "ClassAndFunctionWithParameters", + True, + "y", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="str", + default_value="", + description="Lorem ipsum 2.", + ), + ), + ( + "ClassAndFunctionWithParameters", + True, + "z", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="5", + description="Lorem ipsum 3.", + ), + ), + ( + "ClassWithParametersAndAttributes", + True, + "p", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="1", + description="foo", + ), + ), + ( + "ClassWithParametersAndAttributes", + True, + "q", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="", + default_value="", + description="", + ), + ), + ], + ids=[ + "existing class parameter", + "missing class parameter", + "function parameter with no type and no default", + "function parameter with type and no default", + "function parameter with optional unknown default", + "function parameter with default syntax 1 (just space)", + "function parameter with default syntax 2 (colon)", + "function parameter with default syntax 3 (equals)", + "function parameter with grouped parameters 1", + "function parameter with grouped parameters 2", + "function parameter with positional vararg", + "function parameter with named vararg", + "missing function parameter", + "class and __init__ with params 1", + "class and __init__ with params 2", + "class and __init__ with params 3", + "class with parameter and attribute existing parameter", + "class with parameter and attribute missing parameter", + ], +) +def test_get_parameter_documentation( + numpydoc_parser: NumpyDocParser, + name: str, + is_class: bool, + parameter_name: str, + parameter_assigned_by: ParameterAssignment, + expected_parameter_documentation: ParameterDocstring, +) -> None: + parent = None + node = get_specific_mypy_node(mypy_file, name) + if is_class: + assert isinstance(node, nodes.ClassDef) + class_doc = numpydoc_parser.get_class_documentation(node) + parent = Class(id=node.fullname, name=node.name, superclasses=[], is_public=True, docstring=class_doc) + else: + assert isinstance(node, nodes.FuncDef) + + # Find the constructor + if isinstance(node, nodes.ClassDef): + for definition in get_classdef_definitions(node): + if isinstance(definition, nodes.FuncDef) and definition.name == "__init__": + node = definition + break + assert isinstance(node, nodes.FuncDef) + + parameter_documentation = numpydoc_parser.get_parameter_documentation( + function_node=node, + parameter_name=parameter_name, + parameter_assigned_by=parameter_assigned_by, + parent_class=parent, + ) + + assert parameter_documentation == expected_parameter_documentation + + +# ############################## Attribute Documentation ############################## # +@pytest.mark.parametrize( + ("class_name", "attribute_name", "expected_attribute_documentation"), + [ + ( + "ClassWithAttributes", + "no_type_no_default", + AttributeDocstring( + type="", + default_value="", + description="foo: no_type_no_default. Code::\n\n pass", + ), + ), + ( + "ClassWithAttributes", + "type_no_default", + AttributeDocstring( + type="int", + default_value="", + description="foo: type_no_default", + ), + ), + ( + "ClassWithAttributes", + "optional_unknown_default", + AttributeDocstring( + type="int", + default_value="", + description="foo: optional_unknown_default", + ), + ), + ( + "ClassWithAttributes", + "with_default_syntax_1", + AttributeDocstring( + type="int", + default_value="1", + description="foo: with_default_syntax_1", + ), + ), + ( + "ClassWithAttributes", + "with_default_syntax_2", + AttributeDocstring(type="int", default_value="2", description="foo: with_default_syntax_2"), + ), + ( + "ClassWithAttributes", + "with_default_syntax_3", + AttributeDocstring(type="int", default_value="3", description="foo: with_default_syntax_3"), + ), + ( + "ClassWithAttributes", + "grouped_attribute_1", + AttributeDocstring( + type="int", + default_value="4", + description="foo: grouped_attribute_1 and grouped_attribute_2", + ), + ), + ( + "ClassWithAttributes", + "grouped_attribute_2", + AttributeDocstring( + type="int", + default_value="4", + description="foo: grouped_attribute_1 and grouped_attribute_2", + ), + ), + ( + "ClassWithAttributes", + "missing", + AttributeDocstring(type="", default_value="", description=""), + ), + ( + "ClassWithParametersAndAttributes", + "p", + AttributeDocstring( + type="", + default_value="", + description="", + ), + ), + ( + "ClassWithParametersAndAttributes", + "q", + AttributeDocstring( + type="int", + default_value="1", + description="foo", + ), + ), + ], + ids=[ + "class attribute with no type and no default", + "class attribute with type and no default", + "class attribute with optional unknown default", + "class attribute with default syntax 1 (just space)", + "class attribute with default syntax 2 (colon)", + "class attribute with default syntax 3 (equals)", + "class attribute with grouped attributes 1", + "class attribute with grouped attributes 2", + "missing function parameter", + "class with parameter and attribute missing attribute", + "class with parameter and attribute existing attribute", + ], +) +def test_get_attribute_documentation( + numpydoc_parser: NumpyDocParser, + class_name: str, + attribute_name: str, + expected_attribute_documentation: AttributeDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, class_name) + assert isinstance(node, nodes.ClassDef) + docstring = numpydoc_parser.get_class_documentation(node) + fake_class = Class(id="some_id", name="some_class", superclasses=[], is_public=True, docstring=docstring) + + attribute_documentation = numpydoc_parser.get_attribute_documentation( + parent_class=fake_class, + attribute_name=attribute_name, + ) + + assert attribute_documentation == expected_attribute_documentation + + +# ############################## Result Documentation ############################## # +@pytest.mark.parametrize( + ("function_name", "expected_result_documentation"), + [ + ( + "function_with_result_value_and_type", + ResultDocstring(type="int", description="this will be the return value"), + ), + ("function_without_result_value", ResultDocstring(type="", description="")), + ], + ids=["existing return value and type", "function without return value"], +) +def test_get_result_documentation( + numpydoc_parser: NumpyDocParser, + function_name: str, + expected_result_documentation: ResultDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, function_name) + assert isinstance(node, nodes.FuncDef) + assert numpydoc_parser.get_result_documentation(node) == expected_result_documentation diff --git a/tests/safeds_stubgen/docstring_parsing/test_plaintext_docstring_parser.py b/tests/safeds_stubgen/docstring_parsing/test_plaintext_docstring_parser.py new file mode 100644 index 00000000..709d428e --- /dev/null +++ b/tests/safeds_stubgen/docstring_parsing/test_plaintext_docstring_parser.py @@ -0,0 +1,209 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest +from mypy import nodes +from safeds_stubgen.api_analyzer import Class, ParameterAssignment + +# noinspection PyProtectedMember +from safeds_stubgen.api_analyzer._get_api import _get_mypy_ast +from safeds_stubgen.docstring_parsing import ( + ClassDocstring, + FunctionDocstring, + ParameterDocstring, + PlaintextDocstringParser, +) + +# noinspection PyProtectedMember +from safeds_stubgen.docstring_parsing._docstring import AttributeDocstring, ResultDocstring + +from tests.safeds_stubgen._helpers import get_specific_mypy_node + +# Setup +_test_dir = Path(__file__).parent.parent.parent +mypy_file = _get_mypy_ast( + files=[ + str(Path(_test_dir / "data" / "test_docstring_parser_package" / "test_plaintext.py")), + ], + package_paths=[], + root=Path(_test_dir / "data" / "test_docstring_parser_package"), +)[0] + + +@pytest.fixture() +def plaintext_docstring_parser() -> PlaintextDocstringParser: + return PlaintextDocstringParser() + + +# ############################## Class Documentation ############################## # +@pytest.mark.parametrize( + ("class_name", "expected_class_documentation"), + [ + ( + "ClassWithDocumentation", + ClassDocstring( + description="Lorem ipsum.\n\nDolor sit amet.", + full_docstring="Lorem ipsum.\n\nDolor sit amet.", + ), + ), + ( + "ClassWithoutDocumentation", + ClassDocstring( + description="", + full_docstring="", + ), + ), + ], + ids=[ + "class with documentation", + "class without documentation", + ], +) +def test_get_class_documentation( + plaintext_docstring_parser: PlaintextDocstringParser, + class_name: str, + expected_class_documentation: ClassDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, class_name) + + assert isinstance(node, nodes.ClassDef) + assert plaintext_docstring_parser.get_class_documentation(node) == expected_class_documentation + + +# ############################## Function Documentation ############################## # +@pytest.mark.parametrize( + ("function_name", "expected_function_documentation"), + [ + ( + "function_with_documentation", + FunctionDocstring( + description="Lorem ipsum.\n\nDolor sit amet.", + full_docstring="Lorem ipsum.\n\nDolor sit amet.", + ), + ), + ( + "function_without_documentation", + FunctionDocstring(description=""), + ), + ], + ids=[ + "function with documentation", + "function without documentation", + ], +) +def test_get_function_documentation( + plaintext_docstring_parser: PlaintextDocstringParser, + function_name: str, + expected_function_documentation: FunctionDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, function_name) + + assert isinstance(node, nodes.FuncDef) + assert plaintext_docstring_parser.get_function_documentation(node) == expected_function_documentation + + +# ############################## Parameter Documentation ############################## # +@pytest.mark.parametrize( + ("function_name", "parameter_name", "expected_parameter_documentation"), + [ + ( + "function_with_documentation", + "p", + ParameterDocstring(), + ), + ( + "function_without_documentation", + "p", + ParameterDocstring(), + ), + ], + ids=[ + "function with documentation", + "function without documentation", + ], +) +def test_get_parameter_documentation( + plaintext_docstring_parser: PlaintextDocstringParser, + function_name: str, + parameter_name: str, + expected_parameter_documentation: ParameterDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, function_name) + assert isinstance(node, nodes.FuncDef) + assert ( + plaintext_docstring_parser.get_parameter_documentation( + node, + parameter_name, + ParameterAssignment.POSITION_OR_NAME, + parent_class=Class(id="", name="", superclasses=[], is_public=True, docstring=ClassDocstring()), + ) + == expected_parameter_documentation + ) + + +# ############################## Attribute Documentation ############################## # +@pytest.mark.parametrize( + ("class_name", "attribute_name", "expected_attribute_documentation"), + [ + ( + "ClassWithDocumentation", + "p", + AttributeDocstring(), + ), + ( + "ClassWithoutDocumentation", + "p", + AttributeDocstring(), + ), + ], + ids=[ + "function with documentation", + "function without documentation", + ], +) +def test_get_attribute_documentation( + plaintext_docstring_parser: PlaintextDocstringParser, + class_name: str, + attribute_name: str, + expected_attribute_documentation: ParameterDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, class_name) + assert isinstance(node, nodes.ClassDef) + docstring = plaintext_docstring_parser.get_class_documentation(node) + fake_class = Class(id="some_id", name="some_class", superclasses=[], is_public=True, docstring=docstring) + + attribute_documentation = plaintext_docstring_parser.get_attribute_documentation( + parent_class=fake_class, + attribute_name=attribute_name, + ) + + assert attribute_documentation == expected_attribute_documentation + + +# ############################## Result Documentation ############################## # +@pytest.mark.parametrize( + ("function_name", "expected_result_documentation"), + [ + ( + "function_with_documentation", + ResultDocstring(), + ), + ( + "function_without_documentation", + ResultDocstring(), + ), + ], + ids=[ + "class with documentation", + "class without documentation", + ], +) +def test_get_result_documentation( + plaintext_docstring_parser: PlaintextDocstringParser, + function_name: str, + expected_result_documentation: ParameterDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, function_name) + assert isinstance(node, nodes.FuncDef) + assert plaintext_docstring_parser.get_result_documentation(node) == expected_result_documentation diff --git a/tests/safeds_stubgen/docstring_parsing/test_restdoc_parser.py b/tests/safeds_stubgen/docstring_parsing/test_restdoc_parser.py new file mode 100644 index 00000000..cdd35229 --- /dev/null +++ b/tests/safeds_stubgen/docstring_parsing/test_restdoc_parser.py @@ -0,0 +1,265 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest +from mypy import nodes +from safeds_stubgen.api_analyzer import Class, ParameterAssignment, get_classdef_definitions + +# noinspection PyProtectedMember +from safeds_stubgen.api_analyzer._get_api import _get_mypy_ast +from safeds_stubgen.docstring_parsing import ( + ClassDocstring, + FunctionDocstring, + ParameterDocstring, + RestDocParser, + ResultDocstring, +) + +from tests.safeds_stubgen._helpers import get_specific_mypy_node + +# Setup +_test_dir = Path(__file__).parent.parent.parent +mypy_file = _get_mypy_ast( + files=[ + str(Path(_test_dir / "data" / "test_docstring_parser_package" / "test_restdoc.py")), + ], + package_paths=[], + root=Path(_test_dir / "data" / "test_docstring_parser_package"), +)[0] + + +@pytest.fixture() +def restdoc_parser() -> RestDocParser: + return RestDocParser() + + +# ############################## Class Documentation ############################## # +@pytest.mark.parametrize( + ("class_name", "expected_class_documentation"), + [ + ( + "ClassWithDocumentation", + ClassDocstring( + description="Lorem ipsum. Code::\n\npass\n\nDolor sit amet.", + full_docstring="Lorem ipsum. Code::\n\n pass\n\nDolor sit amet.", + ), + ), + ( + "ClassWithoutDocumentation", + ClassDocstring( + description="", + full_docstring="", + ), + ), + ], + ids=[ + "class with documentation", + "class without documentation", + ], +) +def test_get_class_documentation( + restdoc_parser: RestDocParser, + class_name: str, + expected_class_documentation: ClassDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, class_name) + + assert isinstance(node, nodes.ClassDef) + assert restdoc_parser.get_class_documentation(node) == expected_class_documentation + + +# ############################## Function Documentation ############################## # +@pytest.mark.parametrize( + ("function_name", "expected_function_documentation"), + [ + ( + "function_with_documentation", + FunctionDocstring( + description="Lorem ipsum. Code::\n\npass\n\nDolor sit amet.", + full_docstring="Lorem ipsum. Code::\n\n pass\n\nDolor sit amet.", + ), + ), + ( + "function_without_documentation", + FunctionDocstring( + description="", + full_docstring="", + ), + ), + ], + ids=[ + "function with documentation", + "function without documentation", + ], +) +def test_get_function_documentation( + restdoc_parser: RestDocParser, + function_name: str, + expected_function_documentation: FunctionDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, function_name) + + assert isinstance(node, nodes.FuncDef) + assert restdoc_parser.get_function_documentation(node) == expected_function_documentation + + +# ############################## Parameter Documentation ############################## # +@pytest.mark.parametrize( + ("name", "is_class", "parameter_name", "parameter_assigned_by", "expected_parameter_documentation"), + [ + ( + "ClassWithParameters", + True, + "p", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="1", + description="foo defaults to 1", + ), + ), + ( + "ClassWithParameters", + True, + "missing", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="", + default_value="", + description="", + ), + ), + ( + "function_with_parameters", + False, + "no_type_no_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="", + default_value="", + description="no type and no default", + ), + ), + ( + "function_with_parameters", + False, + "type_no_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="", + description="type but no default", + ), + ), + ( + "function_with_parameters", + False, + "with_default", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring( + type="int", + default_value="2", + description="foo that defaults to 2", + ), + ), + ( + "function_with_parameters", + False, + "*args", + ParameterAssignment.POSITIONAL_VARARG, + ParameterDocstring( + type="int", + default_value="", + description="foo: *args", + ), + ), + ( + "function_with_parameters", + False, + "**kwargs", + ParameterAssignment.NAMED_VARARG, + ParameterDocstring( + type="int", + default_value="", + description="foo: **kwargs", + ), + ), + ( + "function_with_parameters", + False, + "missing", + ParameterAssignment.POSITION_OR_NAME, + ParameterDocstring(type="", default_value="", description=""), + ), + ], + ids=[ + "existing class parameter", + "missing class parameter", + "function parameter with no type and no default", + "function parameter with type and no default", + "function parameter with default", + "function parameter with positional vararg", + "function parameter with named vararg", + "missing function parameter", + ], +) +def test_get_parameter_documentation( + restdoc_parser: RestDocParser, + name: str, + is_class: bool, + parameter_name: str, + parameter_assigned_by: ParameterAssignment, + expected_parameter_documentation: ParameterDocstring, +) -> None: + parent = None + node = get_specific_mypy_node(mypy_file, name) + if is_class: + assert isinstance(node, nodes.ClassDef) + class_doc = restdoc_parser.get_class_documentation(node) + parent = Class(id=node.fullname, name=node.name, superclasses=[], is_public=True, docstring=class_doc) + else: + assert isinstance(node, nodes.FuncDef) + + # Find the constructor + if isinstance(node, nodes.ClassDef): + for definition in get_classdef_definitions(node): + if isinstance(definition, nodes.FuncDef) and definition.name == "__init__": + node = definition + break + assert isinstance(node, nodes.FuncDef) + + parameter_documentation = restdoc_parser.get_parameter_documentation( + function_node=node, + parameter_name=parameter_name, + parameter_assigned_by=parameter_assigned_by, + parent_class=parent, + ) + + assert parameter_documentation == expected_parameter_documentation + + +# ############################## Result Documentation ############################## # +@pytest.mark.parametrize( + ("function_name", "expected_result_documentation"), + [ + ( + "function_with_return_value_and_type", + ResultDocstring(type="bool", description="return value"), + ), + ( + "function_with_return_value_no_type", + ResultDocstring(type="", description="return value"), + ), + ("function_without_return_value", ResultDocstring(type="", description="")), + ], + ids=["existing return value and type", "existing return value no type", "function without return value"], +) +def test_get_result_documentation( + restdoc_parser: RestDocParser, + function_name: str, + expected_result_documentation: ResultDocstring, +) -> None: + node = get_specific_mypy_node(mypy_file, function_name) + assert isinstance(node, nodes.FuncDef) + assert restdoc_parser.get_result_documentation(node) == expected_result_documentation diff --git a/tests/safeds_stubgen/test_dummy.py b/tests/safeds_stubgen/test_dummy.py index f4f53619..1cd5d9d0 100644 --- a/tests/safeds_stubgen/test_dummy.py +++ b/tests/safeds_stubgen/test_dummy.py @@ -1,2 +1,5 @@ -def test_dummy(): +from __future__ import annotations + + +def test_dummy() -> None: assert True diff --git a/tests/safeds_stubgen/test_main.py b/tests/safeds_stubgen/test_main.py new file mode 100644 index 00000000..79a6615d --- /dev/null +++ b/tests/safeds_stubgen/test_main.py @@ -0,0 +1,57 @@ +import json +import sys +from pathlib import Path + +import pytest +from safeds_stubgen.main import main +from syrupy import SnapshotAssertion + +_lib_dir = Path(__file__).parent.parent.parent +_test_package_name = "test_package" +_main_dir = Path(_lib_dir / "src" / "main.py") +_test_package_dir = Path(_lib_dir / "tests" / "data" / _test_package_name) +_out_dir = Path(_lib_dir / "tests" / "data" / "out") +_out_file_dir = Path(_out_dir / f"{_test_package_name}__api.json") + + +def test_main(snapshot: SnapshotAssertion) -> None: + # Overwrite system arguments + sys.argv = [ + str(_main_dir), + "-v", + "-p", + str(_test_package_name), + "-s", + str(_test_package_dir), + "-o", + str(_out_dir), + "-tr", + "--docstyle", + "plaintext", + ] + + main() + + with Path.open(_out_file_dir) as f: + json_data = json.load(f) + + assert json_data == snapshot + + +def test_main_empty() -> None: + # Overwrite system arguments + sys.argv = [ + str(_main_dir), + "-v", + "-p", + str(_test_package_name), + "-s", + str(_test_package_dir), + "-o", + str(_out_dir), + "--docstyle", + "plaintext", + ] + + with pytest.raises(ValueError, match="No files found to analyse."): + main()