From 8133cd0c07d3e71f626a8c1d068b39d873ff00ad Mon Sep 17 00:00:00 2001 From: Enrico Stragiotti Date: Tue, 25 Feb 2025 10:30:35 +0100 Subject: [PATCH 01/20] Separate ruff rules from pyproject toml file and upgrade pre commit --- .pre-commit-config.yaml | 4 ++-- poetry.lock | 42 ++++++++++++++++++++--------------------- pyproject.toml | 28 +-------------------------- ruff.toml | 29 ++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 50 deletions(-) create mode 100644 ruff.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 63ebf385a..93b472d93 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: ruff name: ruff description: "Run 'ruff' for extremely fast Python linting" - entry: ruff check --force-exclude + entry: poetry run ruff check --force-exclude language: python types_or: [ python, pyi, jupyter ] args: [ ] @@ -22,7 +22,7 @@ repos: - id: ruff-format name: ruff-format description: "Run 'ruff format' for extremely fast Python formatting" - entry: ruff format --force-exclude + entry: poetry run ruff format --force-exclude language: python types_or: [ python, pyi, jupyter ] args: [ ] diff --git a/poetry.lock b/poetry.lock index 5c6d74b11..19f1228c9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "aenum" @@ -3064,29 +3064,29 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [[package]] name = "ruff" -version = "0.6.9" +version = "0.9.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"}, - {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"}, - {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"}, - {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"}, - {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"}, - {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"}, - {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"}, + {file = "ruff-0.9.7-py3-none-linux_armv6l.whl", hash = "sha256:99d50def47305fe6f233eb8dabfd60047578ca87c9dcb235c9723ab1175180f4"}, + {file = "ruff-0.9.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d59105ae9c44152c3d40a9c40d6331a7acd1cdf5ef404fbe31178a77b174ea66"}, + {file = "ruff-0.9.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f313b5800483770bd540cddac7c90fc46f895f427b7820f18fe1822697f1fec9"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042ae32b41343888f59c0a4148f103208bf6b21c90118d51dc93a68366f4e903"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87862589373b33cc484b10831004e5e5ec47dc10d2b41ba770e837d4f429d721"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a17e1e01bee0926d351a1ee9bc15c445beae888f90069a6192a07a84af544b6b"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7c1f880ac5b2cbebd58b8ebde57069a374865c73f3bf41f05fe7a179c1c8ef22"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e63fc20143c291cab2841dbb8260e96bafbe1ba13fd3d60d28be2c71e312da49"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91ff963baed3e9a6a4eba2a02f4ca8eaa6eba1cc0521aec0987da8d62f53cbef"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88362e3227c82f63eaebf0b2eff5b88990280fb1ecf7105523883ba8c3aaf6fb"}, + {file = "ruff-0.9.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0372c5a90349f00212270421fe91874b866fd3626eb3b397ede06cd385f6f7e0"}, + {file = "ruff-0.9.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d76b8ab60e99e6424cd9d3d923274a1324aefce04f8ea537136b8398bbae0a62"}, + {file = "ruff-0.9.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0c439bdfc8983e1336577f00e09a4e7a78944fe01e4ea7fe616d00c3ec69a3d0"}, + {file = "ruff-0.9.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:115d1f15e8fdd445a7b4dc9a30abae22de3f6bcabeb503964904471691ef7606"}, + {file = "ruff-0.9.7-py3-none-win32.whl", hash = "sha256:e9ece95b7de5923cbf38893f066ed2872be2f2f477ba94f826c8defdd6ec6b7d"}, + {file = "ruff-0.9.7-py3-none-win_amd64.whl", hash = "sha256:3770fe52b9d691a15f0b87ada29c45324b2ace8f01200fb0c14845e499eb0c2c"}, + {file = "ruff-0.9.7-py3-none-win_arm64.whl", hash = "sha256:b075a700b2533feb7a01130ff656a4ec0d5f340bb540ad98759b8401c32c2037"}, + {file = "ruff-0.9.7.tar.gz", hash = "sha256:643757633417907510157b206e490c3aa11cab0c087c912f60e07fbafa87a4c6"}, ] [[package]] @@ -3922,4 +3922,4 @@ mpi4py = ["mpi4py"] [metadata] lock-version = "2.0" python-versions = ">=3.9, <3.13" -content-hash = "ffeebe041e24e3397395acf29196c40ca90c9529cd74ea89991ae046b6fc5dc0" +content-hash = "47f0a2bb266ddd97be6af711ddad64889c63e0e64923fee0a07c9b94192b86f4" diff --git a/pyproject.toml b/pyproject.toml index 81609b8ae..2f71726b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,7 +123,7 @@ sphinxcontrib-bibtex = "^2.6.3" [tool.poetry.group.lint.dependencies] pre-commit = "^3.5.0" nbstripout = "^0.6.0" -ruff = "0.6.9" +ruff = "0.9.7" # Entry points ----------------------------------------------------------------- [tool.poetry.scripts] @@ -151,32 +151,6 @@ requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] build-backend = "poetry_dynamic_versioning.backend" -# Ruff settings ================================================================ -[tool.ruff] -line-length = 100 -target-version = "py39" -extend-include = ["*.ipynb"] -exclude = ["tests/dummy_plugins/"] - -[tool.ruff.lint.isort] # Add optional configurations for import organization -case-sensitive = true -relative-imports-order = "closest-to-furthest" - -[tool.ruff.lint] -# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. -# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or -# McCabe complexity (`C901`) by default. "I" is for isort rules. -select = ["E4", "E7", "E9", "F", "I"] -ignore = [] - -# Allow fix for all enabled rules (when `--fix`) is provided. -fixable = ["ALL"] -unfixable = [] - -# Allow unused variables when underscore-prefixed. -dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" - - # Pytest settings ============================================================== [tool.pytest.ini_options] minversion = "8.0" diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..fd2c16e8c --- /dev/null +++ b/ruff.toml @@ -0,0 +1,29 @@ +line-length = 100 +target-version = "py39" +extend-include = ["*.ipynb"] +exclude = ["tests/dummy_plugins/"] + +[lint] +# Specify which rules to enforce. Each code corresponds to a specific linting category: +select = [ + "F", # Enable Pyflakes for identifying logical errors. + "I", # Import sorting using isort rules + "E4", + "E7", + "E9", +] + +# No rules are ignored by default. Specify rule codes here to suppress them. +ignore = [] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] + +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[lint.isort] # Add optional configurations for import organization +case-sensitive = true +relative-imports-order = "closest-to-furthest" From f446c6d6774eb015667fb731c794c8a8527b4610 Mon Sep 17 00:00:00 2001 From: Enrico Stragiotti Date: Tue, 25 Feb 2025 10:30:35 +0100 Subject: [PATCH 02/20] Separate ruff rules from pyproject toml file and upgrade pre commit --- .pre-commit-config.yaml | 4 +- poetry.lock | 42 +++++++++---------- pyproject.toml | 28 +------------ ruff.toml | 29 +++++++++++++ src/fastoad/cmd/api.py | 6 +-- .../mission/openmdao/mission_run.py | 2 +- .../mission/openmdao/payload_range.py | 4 +- src/fastoad/module_management/exceptions.py | 3 +- tests/memory_tests/test_multiple_runs.py | 2 +- 9 files changed, 60 insertions(+), 60 deletions(-) create mode 100644 ruff.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 63ebf385a..93b472d93 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: ruff name: ruff description: "Run 'ruff' for extremely fast Python linting" - entry: ruff check --force-exclude + entry: poetry run ruff check --force-exclude language: python types_or: [ python, pyi, jupyter ] args: [ ] @@ -22,7 +22,7 @@ repos: - id: ruff-format name: ruff-format description: "Run 'ruff format' for extremely fast Python formatting" - entry: ruff format --force-exclude + entry: poetry run ruff format --force-exclude language: python types_or: [ python, pyi, jupyter ] args: [ ] diff --git a/poetry.lock b/poetry.lock index 5c6d74b11..19f1228c9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "aenum" @@ -3064,29 +3064,29 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [[package]] name = "ruff" -version = "0.6.9" +version = "0.9.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"}, - {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"}, - {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"}, - {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"}, - {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"}, - {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"}, - {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"}, + {file = "ruff-0.9.7-py3-none-linux_armv6l.whl", hash = "sha256:99d50def47305fe6f233eb8dabfd60047578ca87c9dcb235c9723ab1175180f4"}, + {file = "ruff-0.9.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d59105ae9c44152c3d40a9c40d6331a7acd1cdf5ef404fbe31178a77b174ea66"}, + {file = "ruff-0.9.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f313b5800483770bd540cddac7c90fc46f895f427b7820f18fe1822697f1fec9"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042ae32b41343888f59c0a4148f103208bf6b21c90118d51dc93a68366f4e903"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87862589373b33cc484b10831004e5e5ec47dc10d2b41ba770e837d4f429d721"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a17e1e01bee0926d351a1ee9bc15c445beae888f90069a6192a07a84af544b6b"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7c1f880ac5b2cbebd58b8ebde57069a374865c73f3bf41f05fe7a179c1c8ef22"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e63fc20143c291cab2841dbb8260e96bafbe1ba13fd3d60d28be2c71e312da49"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91ff963baed3e9a6a4eba2a02f4ca8eaa6eba1cc0521aec0987da8d62f53cbef"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88362e3227c82f63eaebf0b2eff5b88990280fb1ecf7105523883ba8c3aaf6fb"}, + {file = "ruff-0.9.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0372c5a90349f00212270421fe91874b866fd3626eb3b397ede06cd385f6f7e0"}, + {file = "ruff-0.9.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d76b8ab60e99e6424cd9d3d923274a1324aefce04f8ea537136b8398bbae0a62"}, + {file = "ruff-0.9.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0c439bdfc8983e1336577f00e09a4e7a78944fe01e4ea7fe616d00c3ec69a3d0"}, + {file = "ruff-0.9.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:115d1f15e8fdd445a7b4dc9a30abae22de3f6bcabeb503964904471691ef7606"}, + {file = "ruff-0.9.7-py3-none-win32.whl", hash = "sha256:e9ece95b7de5923cbf38893f066ed2872be2f2f477ba94f826c8defdd6ec6b7d"}, + {file = "ruff-0.9.7-py3-none-win_amd64.whl", hash = "sha256:3770fe52b9d691a15f0b87ada29c45324b2ace8f01200fb0c14845e499eb0c2c"}, + {file = "ruff-0.9.7-py3-none-win_arm64.whl", hash = "sha256:b075a700b2533feb7a01130ff656a4ec0d5f340bb540ad98759b8401c32c2037"}, + {file = "ruff-0.9.7.tar.gz", hash = "sha256:643757633417907510157b206e490c3aa11cab0c087c912f60e07fbafa87a4c6"}, ] [[package]] @@ -3922,4 +3922,4 @@ mpi4py = ["mpi4py"] [metadata] lock-version = "2.0" python-versions = ">=3.9, <3.13" -content-hash = "ffeebe041e24e3397395acf29196c40ca90c9529cd74ea89991ae046b6fc5dc0" +content-hash = "47f0a2bb266ddd97be6af711ddad64889c63e0e64923fee0a07c9b94192b86f4" diff --git a/pyproject.toml b/pyproject.toml index 81609b8ae..2f71726b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,7 +123,7 @@ sphinxcontrib-bibtex = "^2.6.3" [tool.poetry.group.lint.dependencies] pre-commit = "^3.5.0" nbstripout = "^0.6.0" -ruff = "0.6.9" +ruff = "0.9.7" # Entry points ----------------------------------------------------------------- [tool.poetry.scripts] @@ -151,32 +151,6 @@ requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] build-backend = "poetry_dynamic_versioning.backend" -# Ruff settings ================================================================ -[tool.ruff] -line-length = 100 -target-version = "py39" -extend-include = ["*.ipynb"] -exclude = ["tests/dummy_plugins/"] - -[tool.ruff.lint.isort] # Add optional configurations for import organization -case-sensitive = true -relative-imports-order = "closest-to-furthest" - -[tool.ruff.lint] -# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. -# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or -# McCabe complexity (`C901`) by default. "I" is for isort rules. -select = ["E4", "E7", "E9", "F", "I"] -ignore = [] - -# Allow fix for all enabled rules (when `--fix`) is provided. -fixable = ["ALL"] -unfixable = [] - -# Allow unused variables when underscore-prefixed. -dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" - - # Pytest settings ============================================================== [tool.pytest.ini_options] minversion = "8.0" diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..fd2c16e8c --- /dev/null +++ b/ruff.toml @@ -0,0 +1,29 @@ +line-length = 100 +target-version = "py39" +extend-include = ["*.ipynb"] +exclude = ["tests/dummy_plugins/"] + +[lint] +# Specify which rules to enforce. Each code corresponds to a specific linting category: +select = [ + "F", # Enable Pyflakes for identifying logical errors. + "I", # Import sorting using isort rules + "E4", + "E7", + "E9", +] + +# No rules are ignored by default. Specify rule codes here to suppress them. +ignore = [] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] + +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[lint.isort] # Add optional configurations for import organization +case-sensitive = true +relative-imports-order = "closest-to-furthest" diff --git a/src/fastoad/cmd/api.py b/src/fastoad/cmd/api.py index b2016fed2..7f9888755 100644 --- a/src/fastoad/cmd/api.py +++ b/src/fastoad/cmd/api.py @@ -390,8 +390,7 @@ def list_variables( out = as_path(out).absolute() if not overwrite and out.exists(): raise FastPathExistsError( - f"File {out} not written because it already exists. " - "Use overwrite=True to bypass.", + f"File {out} not written because it already exists. Use overwrite=True to bypass.", out, ) make_parent_dir(out) @@ -498,8 +497,7 @@ def list_modules( out = as_path(out).absolute() if not overwrite and out.exists(): raise FastPathExistsError( - f"File {out} not written because it already exists. " - "Use overwrite=True to bypass.", + f"File {out} not written because it already exists. Use overwrite=True to bypass.", out, ) diff --git a/src/fastoad/models/performances/mission/openmdao/mission_run.py b/src/fastoad/models/performances/mission/openmdao/mission_run.py index f6b48410c..193e27e47 100644 --- a/src/fastoad/models/performances/mission/openmdao/mission_run.py +++ b/src/fastoad/models/performances/mission/openmdao/mission_run.py @@ -106,7 +106,7 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): def _postprocess_flight_points(self, flight_points): flight_points = flight_points.copy() # local copy for renaming columns before CSV export rename_dict = { - field_name: f"{field_name}{' ['+unit+']' if unit else ''}" + field_name: f"{field_name}{' [' + unit + ']' if unit else ''}" for field_name, unit in FlightPoint.get_units().items() } flight_points.rename(columns=rename_dict, inplace=True) diff --git a/src/fastoad/models/performances/mission/openmdao/payload_range.py b/src/fastoad/models/performances/mission/openmdao/payload_range.py index 15dd7ab39..0d1bbd3fa 100644 --- a/src/fastoad/models/performances/mission/openmdao/payload_range.py +++ b/src/fastoad/models/performances/mission/openmdao/payload_range.py @@ -205,12 +205,12 @@ def _add_payload_range_grid_group(self): for i in range(2): self.connect( self._contour_names.range, - f"mux_grid.range_{nb_grid_points+i}", + f"mux_grid.range_{nb_grid_points + i}", src_indices=[i + 1], ) self.connect( self._contour_names.duration, - f"mux_grid.duration_{nb_grid_points+i}", + f"mux_grid.duration_{nb_grid_points + i}", src_indices=om.slicer[i + 1], ) diff --git a/src/fastoad/module_management/exceptions.py b/src/fastoad/module_management/exceptions.py index a435aed4c..3a2d8e4e7 100644 --- a/src/fastoad/module_management/exceptions.py +++ b/src/fastoad/module_management/exceptions.py @@ -158,8 +158,7 @@ class FastSeveralDistPluginsError(FastError): def __init__(self): super().__init__( - "Several installed packages with FAST-OAD plugins are available. " - "One must be specified." + "Several installed packages with FAST-OAD plugins are available. One must be specified." ) diff --git a/tests/memory_tests/test_multiple_runs.py b/tests/memory_tests/test_multiple_runs.py index 3a0d9f1bc..8ade27ff4 100644 --- a/tests/memory_tests/test_multiple_runs.py +++ b/tests/memory_tests/test_multiple_runs.py @@ -79,7 +79,7 @@ def test_runs(cleanup): count = 2 for i in range(count): - print(f"RUN {i+1}/{count}") + print(f"RUN {i + 1}/{count}") run_problem() print_memory_state("Before garbage collector") From bde9c051056ecc1a3a77ce51e7a09169260e96ea Mon Sep 17 00:00:00 2001 From: Enrico Stragiotti Date: Mon, 10 Mar 2025 12:36:22 +0100 Subject: [PATCH 03/20] Add UP RET FBT FA PTH ruff linting rules --- devutils/rename_vars.py | 33 ++--- docs/conf.py | 17 +-- docs/directives/segment_attributes.py | 3 +- poetry.lock | 40 +++--- pyproject.toml | 2 +- ruff.toml | 15 ++- src/conftest.py | 14 +- src/fastoad/_utils/__init__.py | 1 - src/fastoad/_utils/dicts.py | 3 +- src/fastoad/_utils/files.py | 8 +- src/fastoad/_utils/pandas.py | 6 +- .../_utils/resource_management/contents.py | 14 +- .../_utils/resource_management/copy.py | 16 ++- src/fastoad/_utils/strings.py | 4 +- src/fastoad/api.py | 72 +++++----- src/fastoad/cmd/api.py | 123 ++++++++++-------- src/fastoad/cmd/calc_runner.py | 28 ++-- src/fastoad/cmd/cli.py | 2 +- src/fastoad/cmd/cli_utils.py | 6 +- src/fastoad/cmd/tests/test_api.py | 20 +-- src/fastoad/exceptions.py | 2 +- src/fastoad/gui/__init__.py | 13 +- src/fastoad/gui/analysis_and_plots.py | 27 ++-- src/fastoad/gui/mission_viewer.py | 19 ++- src/fastoad/gui/optimization_viewer.py | 30 ++--- .../gui/tests/test_analysis_and_plots.py | 2 +- src/fastoad/gui/variable_viewer.py | 36 ++--- src/fastoad/io/__init__.py | 3 +- src/fastoad/io/configuration/__init__.py | 3 +- src/fastoad/io/configuration/configuration.py | 66 +++++----- src/fastoad/io/configuration/exceptions.py | 8 +- .../configuration/tests/test_configuration.py | 15 +-- src/fastoad/io/formatter.py | 8 +- src/fastoad/io/tests/test_data_file.py | 12 +- src/fastoad/io/variable_io.py | 37 ++++-- src/fastoad/io/xml/__init__.py | 7 +- src/fastoad/io/xml/exceptions.py | 4 +- .../resources/remove_duplicate_variables.py | 8 +- src/fastoad/io/xml/tests/test_translator.py | 10 +- .../io/xml/tests/test_variable_io_base.py | 6 +- .../io/xml/tests/test_variable_io_legacy.py | 4 +- .../io/xml/tests/test_variable_io_standard.py | 4 +- src/fastoad/io/xml/translator.py | 22 ++-- src/fastoad/io/xml/variable_io_base.py | 12 +- src/fastoad/io/xml/variable_io_standard.py | 16 +-- src/fastoad/model_base/__init__.py | 3 +- src/fastoad/model_base/atmosphere.py | 41 +++--- src/fastoad/model_base/flight_point.py | 28 ++-- src/fastoad/model_base/openmdao/group.py | 9 +- src/fastoad/model_base/propulsion.py | 7 +- .../models/performances/mission/base.py | 19 +-- .../models/performances/mission/mission.py | 23 ++-- .../mission_builder/__init__.py | 4 +- .../mission_builder/input_definition.py | 33 ++--- .../mission_builder/mission_builder.py | 50 ++++--- .../mission_builder/structure_builders.py | 52 ++++++-- .../mission/mission_definition/schema.py | 15 ++- .../mission_definition/tests/test_schema.py | 9 +- .../performances/mission/openmdao/base.py | 11 +- .../mission/openmdao/mission_run.py | 5 +- .../mission/openmdao/mission_wrapper.py | 11 +- .../mission/openmdao/payload_range.py | 15 +-- .../performances/mission/polar_modifier.py | 4 +- .../models/performances/mission/routes.py | 13 +- .../performances/mission/segments/base.py | 20 +-- .../mission/segments/macro_segments.py | 3 +- .../mission/segments/registered/__init__.py | 16 ++- .../segments/registered/altitude_change.py | 9 +- .../mission/segments/registered/cruise.py | 3 +- .../registered/ground_speed_change.py | 3 +- .../segments/registered/speed_change.py | 5 +- .../segments/registered/takeoff/__init__.py | 5 +- .../registered/takeoff/end_of_takeoff.py | 5 +- .../segments/registered/takeoff/rotation.py | 3 +- .../mission/segments/registered/taxi.py | 5 +- .../segments/registered/tests/conftest.py | 4 +- .../segments/registered/tests/test_cruise.py | 4 +- .../mission/segments/time_step_base.py | 26 ++-- .../performances/mission/tests/conftest.py | 14 +- .../mission/tests/test_flight_sequence.py | 3 + .../models/performances/mission/util.py | 2 +- .../module_management/_bundle_loader.py | 67 +++++----- src/fastoad/module_management/_plugins.py | 34 ++--- src/fastoad/module_management/exceptions.py | 23 ++-- .../module_management/service_registry.py | 45 +++---- .../hello_world_with_decorators.py | 8 +- .../hello_world_without_decorators.py | 4 +- .../tests/test_bundle_loader.py | 18 ++- .../01_Quick_start/beam_problem.ipynb | 23 ++-- src/fastoad/openmdao/_utils.py | 12 +- src/fastoad/openmdao/exceptions.py | 2 +- src/fastoad/openmdao/problem.py | 31 +++-- src/fastoad/openmdao/tests/test_problem.py | 4 +- src/fastoad/openmdao/tests/test_utils.py | 49 +++++-- .../openmdao/tests/test_validity_checker.py | 8 +- src/fastoad/openmdao/tests/test_variables.py | 7 +- src/fastoad/openmdao/validity_checker.py | 18 +-- src/fastoad/openmdao/variables/__init__.py | 3 +- src/fastoad/openmdao/variables/_util.py | 4 +- src/fastoad/openmdao/variables/variable.py | 26 ++-- .../openmdao/variables/variable_list.py | 41 +++--- src/fastoad/openmdao/whatsopt.py | 8 +- src/fastoad/testing.py | 8 +- tests/__init__.py | 4 +- tests/conftest.py | 4 +- .../oad_process/test_oad_process.py | 57 ++++---- tests/memory_tests/test_multiple_runs.py | 19 ++- 107 files changed, 980 insertions(+), 830 deletions(-) diff --git a/devutils/rename_vars.py b/devutils/rename_vars.py index ff2ab625d..318647618 100644 --- a/devutils/rename_vars.py +++ b/devutils/rename_vars.py @@ -18,8 +18,8 @@ import fileinput import os -import os.path as pth import re +from pathlib import Path import numpy as np @@ -33,10 +33,10 @@ from fastoad.io.xml.variable_io_standard import BasicVarXpathTranslator from tests import root_folder_path -SRC_PATH = pth.join(root_folder_path, "src") -TEST_PATH = pth.join(root_folder_path, "tests") -NOTEBOOK_PATH = pth.join(root_folder_path, "notebooks") -VAR_NAME_FILE = pth.join(pth.dirname(__file__), "rename_vars.txt") +SRC_PATH = Path(root_folder_path) / "src" +TEST_PATH = Path(root_folder_path) / "tests" +NOTEBOOK_PATH = Path(root_folder_path) / "notebooks" +VAR_NAME_FILE = Path(__file__).parent / "rename_vars.txt" def build_translator(var_names_match: np.ndarray) -> VarXpathTranslator: @@ -73,21 +73,22 @@ def replace_var_names(file_path, var_names_match): Modifies provided text file by modifying old OpenMDAO variable names to new ones. - :param file_path: - :param var_names_match: + :param file_path: Path to the file to be modified. + :param var_names_match: List of (old_name, new_name) tuples. """ - (_, ext) = pth.splitext(file_path) + ext = Path(file_path).suffix # Extract file extension using pathlib + with fileinput.FileInput(file_path, inplace=True) as file: for line in file: modified_line = line for old_name, new_name in var_names_match: if ext == ".py": # Python file: replacement is done only between (double) quotes - regex = r"""(?<=['"])\b%s\b(?=['"])""" % old_name + regex = rf"""(?<=['"])\b{old_name}\b(?=['"])""" else: # other files: just ensuring it is not part of a larger variable name # by avoiding having ':' before or after. - regex = r"(? str: "src/fastoad/notebooks/tutorial/data/CeRAS01_baseline.xml", ] for xml_file_path in file_list: - print("processing %s" % xml_file_path) - convert_xml(pth.join(root_folder_path, xml_file_path), old_new_translator) + print(f"processing {xml_file_path}") + convert_xml(root_folder_path / xml_file_path, old_new_translator) # replace var names for root_path in [SRC_PATH, TEST_PATH, NOTEBOOK_PATH]: for dir_path, dir_names, file_names in os.walk(root_path): for filename in file_names: - _, ext = pth.splitext(filename) + ext = Path(filename).suffix if ext not in [ ".xml", ".pyc", @@ -147,9 +148,9 @@ def get_xpath(self, var_name: str) -> str: ".png", "", ]: # avoid processing useless files - file_path = pth.join(dir_path, filename) - print("processing %s" % file_path) + file_path = dir_path / filename + print(f"processing {file_path}") try: replace_var_names(file_path, old_new_names) except UnicodeDecodeError: - print("SKIPPED %s" % file_path) + print(f"SKIPPED {file_path}") diff --git a/docs/conf.py b/docs/conf.py index 387d6f9fe..5f9467e48 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,9 +17,9 @@ # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html -import os import sys from os import environ +from pathlib import Path from sphinx.ext import apidoc @@ -29,10 +29,10 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # For custom directives -sys.path.insert(0, os.path.abspath("./directives")) +sys.path.insert(0, str(Path("./directives").resolve())) # For autodoc... and custom directives -sys.path.insert(0, os.path.abspath("../src")) +sys.path.insert(0, str(Path("../src").resolve())) # Overload apidoc options, to add "inherited-members" (which was deactivated because of a bug # in earlier sphinx releases) @@ -41,11 +41,12 @@ # -- Run sphinx-apidoc ------------------------------------------------------ def run_apidoc(_): - sys.path.append(os.path.join(os.path.dirname(__file__), "..")) - cur_dir = os.path.abspath(os.path.dirname(__file__)) - output_dir = os.path.join(cur_dir, "api") - module = os.path.join(cur_dir, "..", "src", "fastoad") - apidoc.main(["-d", "1", "-e", "-o", output_dir, module, "--force"]) + sys.path.append(str(Path(__file__).parent.parent)) # Append project root + cur_dir = Path(__file__).parent.resolve() + output_dir = cur_dir / "api" + module = cur_dir.parent / "src" / "fastoad" + + apidoc.main(["-d", "1", "-e", "-o", str(output_dir), str(module), "--force"]) def setup(app): diff --git a/docs/directives/segment_attributes.py b/docs/directives/segment_attributes.py index a0f3471af..50e9bc0b7 100644 --- a/docs/directives/segment_attributes.py +++ b/docs/directives/segment_attributes.py @@ -17,7 +17,6 @@ from abc import ABC, abstractmethod from dataclasses import fields -from typing import List, Tuple from docutils import nodes from sphinx.util.docutils import SphinxDirective @@ -39,7 +38,7 @@ class AbstractLinkList(SphinxDirective, ABC): header_text = None @abstractmethod - def get_text_and_targets(self) -> List[Tuple[str, str]]: + def get_text_and_targets(self) -> list[tuple[str, str]]: """ :return: a list of tuples for future hyperlinks (displayed text, rst target) """ diff --git a/poetry.lock b/poetry.lock index 19f1228c9..784244412 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3064,29 +3064,29 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [[package]] name = "ruff" -version = "0.9.7" +version = "0.9.9" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.9.7-py3-none-linux_armv6l.whl", hash = "sha256:99d50def47305fe6f233eb8dabfd60047578ca87c9dcb235c9723ab1175180f4"}, - {file = "ruff-0.9.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d59105ae9c44152c3d40a9c40d6331a7acd1cdf5ef404fbe31178a77b174ea66"}, - {file = "ruff-0.9.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f313b5800483770bd540cddac7c90fc46f895f427b7820f18fe1822697f1fec9"}, - {file = "ruff-0.9.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042ae32b41343888f59c0a4148f103208bf6b21c90118d51dc93a68366f4e903"}, - {file = "ruff-0.9.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87862589373b33cc484b10831004e5e5ec47dc10d2b41ba770e837d4f429d721"}, - {file = "ruff-0.9.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a17e1e01bee0926d351a1ee9bc15c445beae888f90069a6192a07a84af544b6b"}, - {file = "ruff-0.9.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7c1f880ac5b2cbebd58b8ebde57069a374865c73f3bf41f05fe7a179c1c8ef22"}, - {file = "ruff-0.9.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e63fc20143c291cab2841dbb8260e96bafbe1ba13fd3d60d28be2c71e312da49"}, - {file = "ruff-0.9.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91ff963baed3e9a6a4eba2a02f4ca8eaa6eba1cc0521aec0987da8d62f53cbef"}, - {file = "ruff-0.9.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88362e3227c82f63eaebf0b2eff5b88990280fb1ecf7105523883ba8c3aaf6fb"}, - {file = "ruff-0.9.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0372c5a90349f00212270421fe91874b866fd3626eb3b397ede06cd385f6f7e0"}, - {file = "ruff-0.9.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d76b8ab60e99e6424cd9d3d923274a1324aefce04f8ea537136b8398bbae0a62"}, - {file = "ruff-0.9.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0c439bdfc8983e1336577f00e09a4e7a78944fe01e4ea7fe616d00c3ec69a3d0"}, - {file = "ruff-0.9.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:115d1f15e8fdd445a7b4dc9a30abae22de3f6bcabeb503964904471691ef7606"}, - {file = "ruff-0.9.7-py3-none-win32.whl", hash = "sha256:e9ece95b7de5923cbf38893f066ed2872be2f2f477ba94f826c8defdd6ec6b7d"}, - {file = "ruff-0.9.7-py3-none-win_amd64.whl", hash = "sha256:3770fe52b9d691a15f0b87ada29c45324b2ace8f01200fb0c14845e499eb0c2c"}, - {file = "ruff-0.9.7-py3-none-win_arm64.whl", hash = "sha256:b075a700b2533feb7a01130ff656a4ec0d5f340bb540ad98759b8401c32c2037"}, - {file = "ruff-0.9.7.tar.gz", hash = "sha256:643757633417907510157b206e490c3aa11cab0c087c912f60e07fbafa87a4c6"}, + {file = "ruff-0.9.9-py3-none-linux_armv6l.whl", hash = "sha256:628abb5ea10345e53dff55b167595a159d3e174d6720bf19761f5e467e68d367"}, + {file = "ruff-0.9.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6cd1428e834b35d7493354723543b28cc11dc14d1ce19b685f6e68e07c05ec7"}, + {file = "ruff-0.9.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5ee162652869120ad260670706f3cd36cd3f32b0c651f02b6da142652c54941d"}, + {file = "ruff-0.9.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aa0f6b75082c9be1ec5a1db78c6d4b02e2375c3068438241dc19c7c306cc61a"}, + {file = "ruff-0.9.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:584cc66e89fb5f80f84b05133dd677a17cdd86901d6479712c96597a3f28e7fe"}, + {file = "ruff-0.9.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf3369325761a35aba75cd5c55ba1b5eb17d772f12ab168fbfac54be85cf18c"}, + {file = "ruff-0.9.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3403a53a32a90ce929aa2f758542aca9234befa133e29f4933dcef28a24317be"}, + {file = "ruff-0.9.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:18454e7fa4e4d72cffe28a37cf6a73cb2594f81ec9f4eca31a0aaa9ccdfb1590"}, + {file = "ruff-0.9.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fadfe2c88724c9617339f62319ed40dcdadadf2888d5afb88bf3adee7b35bfb"}, + {file = "ruff-0.9.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6df104d08c442a1aabcfd254279b8cc1e2cbf41a605aa3e26610ba1ec4acf0b0"}, + {file = "ruff-0.9.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d7c62939daf5b2a15af48abbd23bea1efdd38c312d6e7c4cedf5a24e03207e17"}, + {file = "ruff-0.9.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9494ba82a37a4b81b6a798076e4a3251c13243fc37967e998efe4cce58c8a8d1"}, + {file = "ruff-0.9.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4efd7a96ed6d36ef011ae798bf794c5501a514be369296c672dab7921087fa57"}, + {file = "ruff-0.9.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ab90a7944c5a1296f3ecb08d1cbf8c2da34c7e68114b1271a431a3ad30cb660e"}, + {file = "ruff-0.9.9-py3-none-win32.whl", hash = "sha256:6b4c376d929c25ecd6d87e182a230fa4377b8e5125a4ff52d506ee8c087153c1"}, + {file = "ruff-0.9.9-py3-none-win_amd64.whl", hash = "sha256:837982ea24091d4c1700ddb2f63b7070e5baec508e43b01de013dc7eff974ff1"}, + {file = "ruff-0.9.9-py3-none-win_arm64.whl", hash = "sha256:3ac78f127517209fe6d96ab00f3ba97cafe38718b23b1db3e96d8b2d39e37ddf"}, + {file = "ruff-0.9.9.tar.gz", hash = "sha256:0062ed13f22173e85f8f7056f9a24016e692efeea8704d1a5e8011b8aa850933"}, ] [[package]] @@ -3922,4 +3922,4 @@ mpi4py = ["mpi4py"] [metadata] lock-version = "2.0" python-versions = ">=3.9, <3.13" -content-hash = "47f0a2bb266ddd97be6af711ddad64889c63e0e64923fee0a07c9b94192b86f4" +content-hash = "6a57f48c476da569541ad17056abbf16c2fc2e3a8fa3b4a8122ecb667458ebcd" diff --git a/pyproject.toml b/pyproject.toml index 2f71726b1..084536b03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,7 +123,7 @@ sphinxcontrib-bibtex = "^2.6.3" [tool.poetry.group.lint.dependencies] pre-commit = "^3.5.0" nbstripout = "^0.6.0" -ruff = "0.9.7" +ruff = "0.9.9" # Entry points ----------------------------------------------------------------- [tool.poetry.scripts] diff --git a/ruff.toml b/ruff.toml index fd2c16e8c..165a47801 100644 --- a/ruff.toml +++ b/ruff.toml @@ -6,8 +6,14 @@ exclude = ["tests/dummy_plugins/"] [lint] # Specify which rules to enforce. Each code corresponds to a specific linting category: select = [ - "F", # Enable Pyflakes for identifying logical errors. - "I", # Import sorting using isort rules + "F", # Enable Pyflakes for identifying logical errors. + "I", # Import sorting using isort rules + "UP", # Check for Python version upgrade compatibility. + "RET", # Flake8 plugin that checks return values. + "FBT", # flake8 plugin to detect boolean traps. + "FA", # Check if a type is used in the module that can be rewritten using PEP 563. + "PTH", # A plugin for flake8 finding use of functions that can be replaced by pathlib module. + "RUF", # Ruff-specific rules. "E4", "E7", "E9", @@ -24,6 +30,11 @@ unfixable = [] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +[lint.per-file-ignores] +# We prefer clarity over efficency in notebook examples +"*.ipynb" = ["RET504"] +"*/fastoad/notebooks/*" = ["RET504"] + [lint.isort] # Add optional configurations for import organization case-sensitive = true relative-imports-order = "closest-to-furthest" diff --git a/src/conftest.py b/src/conftest.py index 15dcdc685..9a506b188 100644 --- a/src/conftest.py +++ b/src/conftest.py @@ -16,12 +16,13 @@ # Note: this file has to be put in src/, not in project root folder, to ensure that # `pytest src` will run OK after a `pip install .` +from __future__ import annotations import sys from pathlib import Path from platform import system from shutil import which -from typing import List, Optional +from typing import ClassVar from unittest.mock import Mock import pytest @@ -47,7 +48,7 @@ def no_xfoil_skip(request, xfoil_path): @pytest.fixture -def xfoil_path() -> Optional[str]: +def xfoil_path() -> str | None: """ On a system that is not Windows, a XFOIL executable with name "xfoil" can be put in "/tests/xfoil_exe/". @@ -284,7 +285,7 @@ def with_dummy_plugins(): _teardown() -def _update_entry_map(new_plugin_entry_points: List[importlib_metadata.EntryPoint]): +def _update_entry_map(new_plugin_entry_points: list[importlib_metadata.EntryPoint]): """ Modified plugin entry_points of FAST-OAD distribution. @@ -315,15 +316,14 @@ def _BypassEntryPointReading_enabled(): class BypassEntryPointReading: - active = False - entry_points = [] + active: bool = False + entry_points: ClassVar[list] = [] @wrapt.decorator(enabled=_BypassEntryPointReading_enabled) def __call__(self, wrapped, instance, args, kwargs): if kwargs.get("group") == MODEL_PLUGIN_ID: return self.entry_points - else: - return wrapped(*args, **kwargs) + return wrapped(*args, **kwargs) importlib_metadata.entry_points = BypassEntryPointReading()(importlib_metadata.entry_points) diff --git a/src/fastoad/_utils/__init__.py b/src/fastoad/_utils/__init__.py index e4e9eb8cc..a97edb5c7 100644 --- a/src/fastoad/_utils/__init__.py +++ b/src/fastoad/_utils/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ This package contains various utilities """ diff --git a/src/fastoad/_utils/dicts.py b/src/fastoad/_utils/dicts.py index b6deab0cb..4e4dcf448 100644 --- a/src/fastoad/_utils/dicts.py +++ b/src/fastoad/_utils/dicts.py @@ -15,7 +15,8 @@ # along with this program. If not, see . from abc import ABC, abstractmethod -from typing import Any, Iterable, Mapping, TypeVar +from collections.abc import Iterable, Mapping +from typing import Any, TypeVar _KT = TypeVar("_KT", bound=Any) _VT = TypeVar("_VT", bound=Any) diff --git a/src/fastoad/_utils/files.py b/src/fastoad/_utils/files.py index 2546c4794..a3b569979 100644 --- a/src/fastoad/_utils/files.py +++ b/src/fastoad/_utils/files.py @@ -1,6 +1,7 @@ """ Convenience functions for file and directories """ + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2024 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -13,10 +14,11 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations from os import PathLike from pathlib import Path -from typing import Union, overload +from typing import overload @overload @@ -24,7 +26,7 @@ def as_path(path: None) -> None: ... @overload -def as_path(path: Union[str, PathLike]) -> Path: ... +def as_path(path: str | PathLike) -> Path: ... def as_path(path): @@ -43,6 +45,6 @@ def as_path(path): return None -def make_parent_dir(path: Union[str, PathLike]): +def make_parent_dir(path: str | PathLike): """Ensures parent directory of provided path exists or is created""" as_path(path).parent.mkdir(parents=True, exist_ok=True) diff --git a/src/fastoad/_utils/pandas.py b/src/fastoad/_utils/pandas.py index a663bb58d..135a7f258 100644 --- a/src/fastoad/_utils/pandas.py +++ b/src/fastoad/_utils/pandas.py @@ -1,6 +1,7 @@ """ Module for pandas-related operations """ + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2024 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -13,8 +14,9 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations -from typing import Callable, Optional +from typing import Callable import pandas as pd from packaging.version import Version @@ -29,7 +31,7 @@ # TODO: remove me when pandas <2.1 is no longer used. def apply_map( - dataframe: pd.DataFrame, func: Callable, na_action: Optional[str] = None, **kwargs + dataframe: pd.DataFrame, func: Callable, na_action: str | None = None, **kwargs ) -> pd.DataFrame: """ Convenience function for using DataFrame.applymap or DataFrame.map diff --git a/src/fastoad/_utils/resource_management/contents.py b/src/fastoad/_utils/resource_management/contents.py index 802bb57c8..697eedc3f 100644 --- a/src/fastoad/_utils/resource_management/contents.py +++ b/src/fastoad/_utils/resource_management/contents.py @@ -13,10 +13,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + from importlib.resources import as_file, files from os import PathLike from types import ModuleType -from typing import List, TextIO, Union +from typing import TextIO class PackageReader: @@ -29,7 +31,7 @@ class PackageReader: :param package_name: Name of package to inspect. """ - def __init__(self, package_name: Union[str, ModuleType]): + def __init__(self, package_name: str | ModuleType): """ :param package_name: """ @@ -37,7 +39,7 @@ def __init__(self, package_name: Union[str, ModuleType]): self.exists = True self.is_module = False self.has_error = False - self._contents: List[str] = [] + self._contents: list[str] = [] self.package_name = package_name @property @@ -69,7 +71,7 @@ def package_name(self, package_name: str): self.has_error = True @property - def contents(self) -> List[str]: + def contents(self) -> list[str]: """ The list. """ @@ -87,7 +89,7 @@ def is_resource(self, name: str): def open_text( self, - resource: Union[str, PathLike], + resource: str | PathLike, encoding: str = "utf-8", errors: str = "strict", ) -> TextIO: @@ -102,7 +104,7 @@ def open_text( errors=errors, ) - def path(self, resource: Union[str, PathLike]): + def path(self, resource: str | PathLike): """ Replaces legacy importlib.resources.path(). diff --git a/src/fastoad/_utils/resource_management/copy.py b/src/fastoad/_utils/resource_management/copy.py index e5f7ec5de..428532679 100644 --- a/src/fastoad/_utils/resource_management/copy.py +++ b/src/fastoad/_utils/resource_management/copy.py @@ -1,6 +1,7 @@ """ Helper module for copying resources """ + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2024 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -13,20 +14,21 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations import shutil +from collections.abc import Iterable from os import PathLike from types import ModuleType -from typing import Iterable, Union from .contents import PackageReader from ..files import as_path, make_parent_dir def copy_resource( - package: Union[str, ModuleType], - resource: Union[str, PathLike], - target_path: Union[str, PathLike], + package: str | ModuleType, + resource: str | PathLike, + target_path: str | PathLike, ): """ Copies the indicated resource file to provided target path. @@ -45,9 +47,9 @@ def copy_resource( def copy_resource_folder( - package: Union[str, ModuleType], - destination_path: Union[str, PathLike], - exclude: Iterable[str] = None, + package: str | ModuleType, + destination_path: str | PathLike, + exclude: Iterable[str] | None = None, ): """ Copies the full content of provided package in destination folder. diff --git a/src/fastoad/_utils/strings.py b/src/fastoad/_utils/strings.py index b003844d4..5c7914317 100644 --- a/src/fastoad/_utils/strings.py +++ b/src/fastoad/_utils/strings.py @@ -92,7 +92,7 @@ def __init__(self, parsed_text, original_exception): self.original_exception = original_exception def __str__(self): - msg = 'Could not parse "%s"' % self.text + msg = f'Could not parse "{self.text}"' if self.original_exception: - msg += ': got Error "%s"' % self.original_exception + msg += f': got Error "{self.original_exception}"' return msg diff --git a/src/fastoad/api.py b/src/fastoad/api.py index f57c759d7..80208390d 100644 --- a/src/fastoad/api.py +++ b/src/fastoad/api.py @@ -72,56 +72,56 @@ from fastoad.openmdao.variables import Variable, VariableList __all__ = [ - "__version__", - "evaluate_problem", - "generate_configuration_file", - "generate_inputs", - "generate_notebooks", - "generate_source_data_file", - "get_plugin_information", - "list_modules", - "list_variables", - "optimization_viewer", - "optimize_problem", - "variable_viewer", - "write_n2", - "write_xdsm", - "CalcRunner", - "aircraft_geometry_plot", - "drag_polar_plot", - "mass_breakdown_bar_plot", - "mass_breakdown_sun_plot", - "payload_range_plot", - "wing_geometry_plot", - "MissionViewer", - "OptimizationViewer", - "VariableViewer", - "DataFile", - "FASTOADProblemConfigurator", - "Atmosphere", - "AtmosphereSI", - "FlightPoint", "MANDATORY_FIELD", - "BaseCycleGroup", - "CycleGroup", - "IOMPropulsionWrapper", - "AbstractFlightSegment", - "IFlightPart", - "RegisterSegment", "AbstractFixedDurationSegment", + "AbstractFlightSegment", "AbstractGroundSegment", "AbstractManualThrustSegment", "AbstractPolarModifier", "AbstractRegulatedThrustSegment", "AbstractTakeOffSegment", "AbstractTimeStepFlightSegment", + "Atmosphere", + "AtmosphereSI", + "BaseCycleGroup", + "CalcRunner", + "CycleGroup", + "DataFile", + "FASTOADProblem", + "FASTOADProblemConfigurator", + "FlightPoint", "FlightSegment", + "IFlightPart", + "IOMPropulsionWrapper", + "MissionViewer", + "OptimizationViewer", "RegisterOpenMDAOSystem", "RegisterPropulsion", + "RegisterSegment", "RegisterSpecializedService", "RegisterSubmodel", - "FASTOADProblem", "ValidityDomainChecker", "Variable", "VariableList", + "VariableViewer", + "__version__", + "aircraft_geometry_plot", + "drag_polar_plot", + "evaluate_problem", + "generate_configuration_file", + "generate_inputs", + "generate_notebooks", + "generate_source_data_file", + "get_plugin_information", + "list_modules", + "list_variables", + "mass_breakdown_bar_plot", + "mass_breakdown_sun_plot", + "optimization_viewer", + "optimize_problem", + "payload_range_plot", + "variable_viewer", + "wing_geometry_plot", + "write_n2", + "write_xdsm", ] diff --git a/src/fastoad/cmd/api.py b/src/fastoad/cmd/api.py index 7f9888755..8eb94b8cd 100644 --- a/src/fastoad/cmd/api.py +++ b/src/fastoad/cmd/api.py @@ -1,6 +1,7 @@ """ API """ + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2024 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -13,9 +14,9 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations import logging -import os import shutil import sys import textwrap as tw @@ -25,7 +26,7 @@ from os import PathLike from pathlib import Path from time import time -from typing import Dict, List, TextIO, Union +from typing import TextIO import openmdao.api as om import pandas as pd @@ -64,7 +65,7 @@ class UserFileType(Enum): SOURCE_DATA = "source_data" -def get_plugin_information(print_data=False) -> Dict[str, DistributionPluginDefinition]: +def get_plugin_information(print_data=False) -> dict[str, DistributionPluginDefinition]: # noqa: FBT002 no breaking changes in API functions """ Provides information about available FAST-OAD plugins. @@ -89,9 +90,9 @@ def get_plugin_information(print_data=False) -> Dict[str, DistributionPluginDefi def generate_notebooks( - destination_path: Union[str, PathLike], - overwrite: bool = False, + destination_path: str | PathLike, distribution_name=None, + overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions ): """ Copies notebook folder(s) from available plugin(s). @@ -99,8 +100,8 @@ def generate_notebooks( :param destination_path: the inner structure of the folders will depend on the number of installed package and the number of plugins they contain. - :param overwrite: if True and `destination_path` exists, it will be removed before writing. :param distribution_name: the name of an installed package that provides notebooks + :param overwrite: if True and `destination_path` exists, it will be removed before writing. """ destination_path = as_path(destination_path) @@ -141,7 +142,7 @@ def generate_notebooks( target_path_elements.append(folder_info.plugin_name) target_path = Path(*target_path_elements) - os.makedirs(target_path) + target_path.mkdir(parents=True, exist_ok=True) copy_resource_folder(folder_info.package_name, target_path) return destination_path @@ -149,10 +150,11 @@ def generate_notebooks( def _generate_user_file( user_file_type: UserFileType, - user_file_path: Union[str, PathLike], - overwrite: bool = False, + user_file_path: str | PathLike, distribution_name=None, sample_user_file_name=None, + *, + overwrite: bool = False, ): """ Copies a sample user file from an available plugin. Since there are a lot of similarities @@ -163,11 +165,11 @@ def _generate_user_file( :param user_file_type: the type of user file that needs to be written, should match one available in _AVAILABLE_USER_FILE_TYPE :param user_file_path: the path of the user file to be written - :param overwrite: if True, the file will be written, even if it already exists :param distribution_name: the name of the installed package that provides the sample user file (can be omitted if only one plugin is available) :param sample_user_file_name: the name of the sample user file (can be omitted if the plugin provides only one user file) + :param overwrite: if True, the file will be written, even if it already exists :return: path of generated file :raise FastPathExistsError: if overwrite==False and user_file_path already exists """ @@ -240,20 +242,20 @@ def _generate_user_file( def generate_configuration_file( - configuration_file_path: Union[str, PathLike], - overwrite: bool = False, + configuration_file_path: str | PathLike, distribution_name=None, sample_file_name=None, + overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions ): """ Copies a sample configuration file from an available plugin. :param configuration_file_path: the path of file to be written - :param overwrite: if True, the file will be written, even if it already exists :param distribution_name: the name of the installed package that provides the sample configuration file (can be omitted if only one plugin is available) :param sample_file_name: the name of the sample configuration file (can be omitted if the plugin provides only one configuration file) + :param overwrite: if True, the file will be written, even if it already exists :return: path of generated file :raise FastPathExistsError: if overwrite==False and configuration_file_path already exists """ @@ -268,20 +270,20 @@ def generate_configuration_file( def generate_source_data_file( - source_data_file_path: Union[str, PathLike], - overwrite: bool = False, + source_data_file_path: str | PathLike, distribution_name=None, sample_file_name=None, + overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions ): """ Copies a sample source data file from an available plugin. :param source_data_file_path: the path of file to be written - :param overwrite: if True, the file will be written, even if it already exists :param distribution_name: the name of the installed package that provides the sample source data file (can be omitted if only one plugin is available) :param sample_file_name: the name of the sample source data file (can be omitted if the plugin provides only one source data file) + :param overwrite: if True, the file will be written, even if it already exists :return: path of generated file :raise FastPathExistsError: if overwrite==False and source_file_path already exists """ @@ -296,10 +298,10 @@ def generate_source_data_file( def generate_inputs( - configuration_file_path: Union[str, PathLike], - source_data_path: Union[str, PathLike] = None, + configuration_file_path: str | PathLike, + source_data_path: str | PathLike | None = None, source_data_path_schema="native", - overwrite: bool = False, + overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions ) -> str: """ Generates input file for the problem specified in configuration_file_path. @@ -332,11 +334,11 @@ def generate_inputs( def list_variables( - configuration_file_path: Union[str, PathLike], - out: Union[str, PathLike, TextIO] = None, - overwrite: bool = False, - force_text_output: bool = False, + configuration_file_path: str | PathLike, + out: str | PathLike | TextIO | None = None, tablefmt: str = "grid", + overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions + force_text_output: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions ): """ Writes list of variables for the problem specified in configuration_file_path. @@ -348,14 +350,14 @@ def list_variables( :param configuration_file_path: :param out: the output stream or a path for the output file (None means sys.stdout) + :param tablefmt: The formatting of the requested table. Options are the same as those available + to the tabulate package. See tabulate.tabulate_formats for a complete list. + If "var_desc" the file will use the variable_descriptions.txt format. :param overwrite: if True and out parameter is a file path, the file will be written even if one already exists :param force_text_output: if True, list will be written as text, even if command is used in an interactive IPython shell (Jupyter notebook). Has no effect in other shells or if out parameter is not sys.stdout - :param tablefmt: The formatting of the requested table. Options are the same as those available - to the tabulate package. See tabulate.tabulate_formats for a complete list. - If "var_desc" the file will use the variable_descriptions.txt format. :return: path of generated file, or None if no file was generated. :raise FastPathExistsError: if `overwrite==False` and `out` is a file path and the file exists """ @@ -394,7 +396,7 @@ def list_variables( out, ) make_parent_dir(out) - out_file = open(out, "w", encoding="utf-8") + out_file = Path(out).open("w", encoding="utf-8") else: if out == sys.stdout and InteractiveShell.initialized() and not force_text_output: display(HTML(variables_df.to_html(index=False))) @@ -425,8 +427,7 @@ def _generate_var_desc_format(variables_df): else: kwargs["lineterminator"] = "\n" - content = variables_df.to_csv(**kwargs).replace("|", " || ") - return content + return variables_df.to_csv(**kwargs).replace("|", " || ") # Contenent def _generate_table_format(variables_df, tablefmt="grid"): @@ -441,11 +442,11 @@ def _generate_table_format(variables_df, tablefmt="grid"): def list_modules( - source_path: Union[List[Union[str, PathLike]], str, PathLike] = None, - out: Union[str, PathLike, TextIO] = None, - overwrite: bool = False, - verbose: bool = False, - force_text_output: bool = False, + source_path: list[str | PathLike] | str | PathLike | None = None, + out: str | PathLike | TextIO | None = None, + overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions + verbose: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions + force_text_output: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions ): """ Writes list of available systems. @@ -502,7 +503,7 @@ def list_modules( ) make_parent_dir(out) - out_file = open(out, "w", encoding="utf-8") + out_file = Path(out).open("w", encoding="utf-8") else: if ( out == sys.stdout @@ -579,9 +580,9 @@ def _get_detailed_system_list(): def write_n2( - configuration_file_path: Union[str, PathLike], - n2_file_path: Union[str, PathLike] = None, - overwrite: bool = False, + configuration_file_path: str | PathLike, + n2_file_path: str | PathLike | None = None, + overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions ): """ Write the N2 diagram of the problem in file n2.html @@ -621,12 +622,12 @@ def write_n2( def write_xdsm( - configuration_file_path: Union[str, PathLike], - xdsm_file_path: Union[str, PathLike] = None, - overwrite: bool = False, + configuration_file_path: str | PathLike, + xdsm_file_path: str | PathLike | None = None, depth: int = 2, - wop_server_url: str = None, - dry_run: bool = False, + wop_server_url: str | None = None, + overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions + dry_run: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions ): """ @@ -649,8 +650,8 @@ def write_xdsm( if not overwrite and xdsm_file_path.exists(): raise FastPathExistsError( - "XDSM-diagram file %s not written because it already exists. " - "Use overwrite=True to bypass." % xdsm_file_path, + f"XDSM-diagram file {xdsm_file_path} not written because it already exists. " + "Use overwrite=True to bypass.", xdsm_file_path, ) @@ -667,17 +668,18 @@ def write_xdsm( def _run_problem( - configuration_file_path: Union[str, PathLike], - overwrite: bool = False, + configuration_file_path: str | PathLike, mode="run_model", + *, + overwrite: bool = False, auto_scaling: bool = False, ) -> FASTOADProblem: """ Runs problem according to provided file :param configuration_file_path: problem definition - :param overwrite: if True, output file will be overwritten :param mode: 'run_model' or 'run_driver' + :param overwrite: if True, output file will be overwritten :param auto_scaling: if True, automatic scaling is performed for design variables and constraints :return: the OpenMDAO problem after run @@ -723,7 +725,8 @@ def _run_problem( def evaluate_problem( - configuration_file_path: Union[str, PathLike], overwrite: bool = False + configuration_file_path: str | PathLike, + overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions ) -> FASTOADProblem: """ Runs model according to provided problem file @@ -733,13 +736,17 @@ def evaluate_problem( :return: the OpenMDAO problem after run :raise FastPathExistsError: if overwrite==False and output data file of problem already exists """ - return _run_problem(configuration_file_path, overwrite, "run_model") + return _run_problem( + configuration_file_path, + "run_model", + overwrite=overwrite, + ) def optimize_problem( - configuration_file_path: Union[str, PathLike], - overwrite: bool = False, - auto_scaling: bool = False, + configuration_file_path: str | PathLike, + overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions + auto_scaling: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions ) -> FASTOADProblem: """ Runs driver according to provided problem file @@ -751,10 +758,12 @@ def optimize_problem( :return: the OpenMDAO problem after run :raise FastPathExistsError: if overwrite==False and output data file of problem already exists """ - return _run_problem(configuration_file_path, overwrite, "run_driver", auto_scaling=auto_scaling) + return _run_problem( + configuration_file_path, "run_driver", overwrite=overwrite, auto_scaling=auto_scaling + ) -def optimization_viewer(configuration_file_path: Union[str, PathLike]): +def optimization_viewer(configuration_file_path: str | PathLike): """ Displays optimization information and enables its editing @@ -770,7 +779,9 @@ def optimization_viewer(configuration_file_path: Union[str, PathLike]): def variable_viewer( - file_path: Union[str, PathLike], file_formatter: IVariableIOFormatter = None, editable=True + file_path: str | PathLike, + file_formatter: IVariableIOFormatter = None, + editable=True, # noqa: FBT002 no breaking changes in API functions ): """ Displays a widget that enables to visualize variables information and edit their values. diff --git a/src/fastoad/cmd/calc_runner.py b/src/fastoad/cmd/calc_runner.py index c913df498..804391f5f 100644 --- a/src/fastoad/cmd/calc_runner.py +++ b/src/fastoad/cmd/calc_runner.py @@ -1,4 +1,5 @@ """Tools for running multiple computations""" + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2024 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -11,6 +12,7 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations import logging import multiprocessing as mp @@ -19,7 +21,6 @@ from math import ceil, log10 from os import PathLike from pathlib import Path -from typing import List, Optional, Union from openmdao.utils.mpi import FakeComm @@ -51,11 +52,11 @@ class CalcRunner: """ #: Configuration file, common to all computations - configuration_file_path: Union[str, PathLike] + configuration_file_path: str | PathLike #: Input file for the computation (will supersede the input file setting in # configuration file) - input_file_path: Optional[Union[str, PathLike]] = None + input_file_path: str | PathLike | None = None #: For activating MDO instead MDA optimize: bool = False @@ -68,8 +69,8 @@ def __post_init__(self): def run( self, - input_values: Optional[VariableList] = None, - calculation_folder: Optional[Union[str, PathLike]] = None, + input_values: VariableList | None = None, + calculation_folder: str | PathLike | None = None, ) -> DataFile: """ Run the computation. @@ -115,16 +116,14 @@ def run( else: problem.run_model() - output_data = problem.write_outputs() - - return output_data + return problem.write_outputs() # output_data def run_cases( self, - input_list: List[VariableList], - destination_folder: Union[str, PathLike], + input_list: list[VariableList], + destination_folder: str | PathLike, *, - max_workers: Optional[int] = None, + max_workers: int | None = None, use_MPI_if_available: bool = True, overwrite_subfolders: bool = False, ): @@ -173,7 +172,9 @@ def run_cases( with pool_cls(max_workers) as pool: pool.starmap( CalcRunner.run, - self._calculation_inputs(input_list, destination_folder, overwrite_subfolders), + self._calculation_inputs( + input_list, destination_folder, overwrite_subfolders=overwrite_subfolders + ), # If a computation crashes, the whole chunk stops. # chunksize=1 ensures all computations will be launched. chunksize=1, @@ -181,8 +182,9 @@ def run_cases( def _calculation_inputs( self, - input_list: List[VariableList], + input_list: list[VariableList], destination_folder: Path, + *, overwrite_subfolders: bool, ): """Iterator for providing inputs of :meth:`run`.""" diff --git a/src/fastoad/cmd/cli.py b/src/fastoad/cmd/cli.py index d7636bac4..523b0a2e7 100644 --- a/src/fastoad/cmd/cli.py +++ b/src/fastoad/cmd/cli.py @@ -205,7 +205,7 @@ def list_modules(out_file, force, verbose, source_path): "table_format", default="grid", show_default=True, - help=f"format of the list. Available options are {['var_desc'] + tabulate.tabulate_formats}. " + help=f"format of the list. Available options are {['var_desc', *tabulate.tabulate_formats]}. " '"var_desc" is the variable_descriptions.txt format. Other formats are part of the ' "tabulate package.", ) diff --git a/src/fastoad/cmd/cli_utils.py b/src/fastoad/cmd/cli_utils.py index d198ade32..51b65f923 100644 --- a/src/fastoad/cmd/cli_utils.py +++ b/src/fastoad/cmd/cli_utils.py @@ -12,6 +12,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + from typing import Callable import click @@ -47,7 +49,7 @@ def out_file_option(func): )(overwrite_option(func)) -def manage_overwrite(func: Callable, filename_func: Callable = None, **kwargs): +def manage_overwrite(func: Callable, filename_func: Callable | None = None, **kwargs): """ Runs `func`, that is expected to write a file, with provided keyword arguments `args`. @@ -76,7 +78,7 @@ def manage_overwrite(func: Callable, filename_func: Callable = None, **kwargs): return written -def _run_write_func(func: Callable, filename_func: Callable = None, **kwargs): +def _run_write_func(func: Callable, filename_func: Callable | None = None, **kwargs): result = func(**kwargs) if result: if filename_func: diff --git a/src/fastoad/cmd/tests/test_api.py b/src/fastoad/cmd/tests/test_api.py index 5b67a342b..9e3beea12 100644 --- a/src/fastoad/cmd/tests/test_api.py +++ b/src/fastoad/cmd/tests/test_api.py @@ -405,8 +405,8 @@ def test_write_n2(cleanup): api.write_n2(CONFIGURATION_FILE_PATH, n2_file_path) # Running again without forcing overwrite of outputs will make it fail with pytest.raises(FastPathExistsError): - api.write_n2(CONFIGURATION_FILE_PATH, n2_file_path, False) - api.write_n2(CONFIGURATION_FILE_PATH, n2_file_path, True) + api.write_n2(CONFIGURATION_FILE_PATH, n2_file_path, overwrite=False) + api.write_n2(CONFIGURATION_FILE_PATH, n2_file_path, overwrite=True) assert n2_file_path.exists() @@ -419,7 +419,7 @@ def test_write_xdsm(cleanup): default_xdsm_file_path = DATA_FOLDER_PATH / "xdsm.html" api.write_xdsm(CONFIGURATION_FILE_PATH, overwrite=True, dry_run=True) assert default_xdsm_file_path.exists() - os.remove(default_xdsm_file_path) + Path.unlink(default_xdsm_file_path) xdsm_file_path = RESULTS_FOLDER_PATH / "other_xdsm.html" api.write_xdsm(CONFIGURATION_FILE_PATH, xdsm_file_path) @@ -432,11 +432,11 @@ def test_write_xdsm(cleanup): def test_evaluate_problem(cleanup): api.generate_inputs(CONFIGURATION_FILE_PATH, DATA_FOLDER_PATH / "inputs.xml", overwrite=True) - api.evaluate_problem(CONFIGURATION_FILE_PATH, False) + api.evaluate_problem(CONFIGURATION_FILE_PATH, overwrite=False) # Running again without forcing overwrite of outputs will make it fail with pytest.raises(FastPathExistsError): - api.evaluate_problem(CONFIGURATION_FILE_PATH, False) - problem = api.evaluate_problem(CONFIGURATION_FILE_PATH, True) + api.evaluate_problem(CONFIGURATION_FILE_PATH, overwrite=False) + problem = api.evaluate_problem(CONFIGURATION_FILE_PATH, overwrite=True) assert problem["f"] == pytest.approx(32.56910089, abs=1e-8) # Move output file because it will be overwritten by the optim test @@ -445,11 +445,11 @@ def test_evaluate_problem(cleanup): def test_optimize_problem(cleanup): api.generate_inputs(CONFIGURATION_FILE_PATH, DATA_FOLDER_PATH / "inputs.xml", overwrite=True) - api.optimize_problem(CONFIGURATION_FILE_PATH, False) + api.optimize_problem(CONFIGURATION_FILE_PATH, overwrite=False) # Running again without forcing overwrite of outputs will make it fail with pytest.raises(FastPathExistsError): - api.optimize_problem(CONFIGURATION_FILE_PATH, False) - problem = api.optimize_problem(CONFIGURATION_FILE_PATH, True) + api.optimize_problem(CONFIGURATION_FILE_PATH, overwrite=False) + problem = api.optimize_problem(CONFIGURATION_FILE_PATH, overwrite=True) assert problem["f"] == pytest.approx(3.18339395, abs=1e-8) @@ -460,7 +460,7 @@ def test_optimization_viewer(cleanup): # Before a run api.optimization_viewer(CONFIGURATION_FILE_PATH) - api.optimize_problem(CONFIGURATION_FILE_PATH, True) + api.optimize_problem(CONFIGURATION_FILE_PATH, overwrite=True) # After a run api.optimization_viewer(CONFIGURATION_FILE_PATH) diff --git a/src/fastoad/exceptions.py b/src/fastoad/exceptions.py index 3130b7446..c9473d30f 100644 --- a/src/fastoad/exceptions.py +++ b/src/fastoad/exceptions.py @@ -52,5 +52,5 @@ class FastUnexpectedKeywordArgument(FastError): """ def __init__(self, bad_keyword): - super().__init__("Unexpected keyword argument: %s" % bad_keyword) + super().__init__(f"Unexpected keyword argument: {bad_keyword}") self.bad_keyword = bad_keyword diff --git a/src/fastoad/gui/__init__.py b/src/fastoad/gui/__init__.py index 2ac704dbc..565d26b17 100644 --- a/src/fastoad/gui/__init__.py +++ b/src/fastoad/gui/__init__.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# flake8: noqa from .analysis_and_plots import ( aircraft_geometry_plot, drag_polar_plot, @@ -26,3 +25,15 @@ from .mission_viewer import MissionViewer from .optimization_viewer import OptimizationViewer from .variable_viewer import VariableViewer + +__all__ = [ + "MissionViewer", + "OptimizationViewer", + "VariableViewer", + "aircraft_geometry_plot", + "drag_polar_plot", + "mass_breakdown_bar_plot", + "mass_breakdown_sun_plot", + "payload_range_plot", + "wing_geometry_plot", +] diff --git a/src/fastoad/gui/analysis_and_plots.py b/src/fastoad/gui/analysis_and_plots.py index 5807ed1c3..ff0db8715 100644 --- a/src/fastoad/gui/analysis_and_plots.py +++ b/src/fastoad/gui/analysis_and_plots.py @@ -1,6 +1,7 @@ """ Defines the analysis and plotting functions for postprocessing """ + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2024 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -13,9 +14,9 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations from os import PathLike -from typing import Dict, Union import numpy as np import plotly.express as px @@ -29,9 +30,8 @@ COLS = px.colors.qualitative.Plotly -# pylint: disable-msg=too-many-locals def wing_geometry_plot( - aircraft_file_path: Union[str, PathLike], name=None, fig=None, *, file_formatter=None + aircraft_file_path: str | PathLike, name=None, fig=None, *, file_formatter=None ) -> go.FigureWidget: """ Returns a figure plot of the top view of the wing. @@ -101,9 +101,8 @@ def wing_geometry_plot( return fig -# pylint: disable-msg=too-many-locals def aircraft_geometry_plot( - aircraft_file_path: Union[str, PathLike], name=None, fig=None, *, file_formatter=None + aircraft_file_path: str | PathLike, name=None, fig=None, *, file_formatter=None ) -> go.FigureWidget: """ Returns a figure plot of the top view of the wing. @@ -232,7 +231,7 @@ def aircraft_geometry_plot( def drag_polar_plot( - aircraft_file_path: Union[str, PathLike], name=None, fig=None, *, file_formatter=None + aircraft_file_path: str | PathLike, name=None, fig=None, *, file_formatter=None ) -> go.FigureWidget: """ Returns a figure plot of the aircraft drag polar. @@ -272,7 +271,7 @@ def drag_polar_plot( def mass_breakdown_bar_plot( - aircraft_file_path: Union[str, PathLike], + aircraft_file_path: str | PathLike, name=None, fig=None, *, @@ -357,7 +356,7 @@ def mass_breakdown_bar_plot( def mass_breakdown_sun_plot( - aircraft_file_path: Union[str, PathLike], + aircraft_file_path: str | PathLike, *, file_formatter=None, input_mass_name="data:weight:aircraft:MTOW", @@ -530,11 +529,11 @@ def _get_sunburst_mass_label(quantity_name, value, parent_value=None, unit="kg") def payload_range_plot( - aircraft_file_path: Union[str, PathLike], + aircraft_file_path: str | PathLike, name="Payload-Range", mission_name="operational", - variable_of_interest: str = None, - variable_of_interest_legend: str = None, + variable_of_interest: str | None = None, + variable_of_interest_legend: str | None = None, ): """ Returns a figure of the payload-range diagram. @@ -653,7 +652,7 @@ def _get_variable_value_with_new_units(variable: Variable, new_units): def _get_variable_values_with_new_units( - variables: VariableList, var_names_and_new_units: Dict[str, str] + variables: VariableList, var_names_and_new_units: dict[str, str] ): """ Returns the value of the requested variable names with respect to their new units in the order @@ -663,13 +662,11 @@ def _get_variable_values_with_new_units( :param var_names_and_new_units: dictionary of the variable names as keys and units as value :return: values of the requested variables with respect to their new units """ - new_values = [ + return [ # new_values _get_variable_value_with_new_units(variables[variable_name], unit) for variable_name, unit in var_names_and_new_units.items() ] - return new_values - def _data_weight_decomposition(variables: VariableList, owe=None): """ diff --git a/src/fastoad/gui/mission_viewer.py b/src/fastoad/gui/mission_viewer.py index 96f6be923..abb076065 100644 --- a/src/fastoad/gui/mission_viewer.py +++ b/src/fastoad/gui/mission_viewer.py @@ -15,8 +15,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + from os import PathLike -from typing import Union import ipywidgets as widgets import pandas as pd @@ -45,7 +46,7 @@ def __init__(self): # The y selector self._y_widget = None - def add_mission(self, mission_data: Union[str, PathLike, pd.DataFrame], name=None): + def add_mission(self, mission_data: str | PathLike | pd.DataFrame, name=None): """ Adds the mission to the mission database (self.missions) :param mission_data: path of the mission file or Dataframe containing the mission data @@ -64,7 +65,7 @@ def add_mission(self, mission_data: Union[str, PathLike, pd.DataFrame], name=Non else: raise TypeError("Unknown type for mission data, please use .csv of DataFrame") - def display(self, layout_dict=None, layout_overwrite=False, **kwargs): + def display(self, layout_dict=None, layout_overwrite=False, **kwargs): # noqa: FBT002 no breaking changes in API functions """ Display the user interface @@ -77,7 +78,7 @@ def display(self, layout_dict=None, layout_overwrite=False, **kwargs): :return the display object """ - key = list(self.missions)[0] + key = next(iter(self.missions)) # Single element slice keys = self.missions[key].keys() self._output_widget = widgets.Output() @@ -98,12 +99,10 @@ def display(self, layout_dict=None, layout_overwrite=False, **kwargs): [widgets.Label(value="x:"), self._x_widget, widgets.Label(value="y:"), self._y_widget] ) - ui = display(toolbar, self._output_widget) - - return ui + return display(toolbar, self._output_widget) # UI # pylint: disable=unused-argument # change has to be there for observe() to work - def _show_plot(self, change=None, layout_dict=None, layout_overwrite=False, **kwargs): + def _show_plot(self, change=None, layout_dict=None, *, layout_overwrite=False, **kwargs): """ Updates and shows the plots @@ -151,6 +150,4 @@ def _get_label(keys: pd.Index, quantity_name: str, default_idx: int): unit_quantity = FlightPoint.get_unit(quantity_name) column_quantity = f"{quantity_name} [{unit_quantity}]" - label_quantity = column_quantity if column_quantity in keys else keys[default_idx] - - return label_quantity + return column_quantity if column_quantity in keys else keys[default_idx] # label_quantity diff --git a/src/fastoad/gui/optimization_viewer.py b/src/fastoad/gui/optimization_viewer.py index 3fba50807..17e4481ce 100644 --- a/src/fastoad/gui/optimization_viewer.py +++ b/src/fastoad/gui/optimization_viewer.py @@ -14,9 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + from math import isnan from pathlib import Path -from typing import Dict +from typing import ClassVar import ipysheet as sh import ipywidgets as widgets @@ -45,7 +47,7 @@ class OptimizationViewer: # When getting a dataframe from a VariableList, the dictionary keys tell what columns # are kept and values tell what name will be displayed. - _DEFAULT_COLUMN_RENAMING = { + _DEFAULT_COLUMN_RENAMING: ClassVar[dict] = { "type": "Type", "name": "Name", "initial_value": "Initial Value", @@ -176,7 +178,7 @@ def save(self): conf.save() @staticmethod - def _update_optim_variable(variable: Variable, optim_definition: Dict): + def _update_optim_variable(variable: Variable, optim_definition: dict): """ Updates optim_definition with metadata of provided variable. @@ -215,7 +217,9 @@ def display(self): self._create_save_load_buttons() return self._render_ui() - def load_variables(self, variables: VariableList, attribute_to_column: Dict[str, str] = None): + def load_variables( + self, variables: VariableList, attribute_to_column: dict[str, str] | None = None + ): """ Loads provided variable list and replace current data set. @@ -226,7 +230,7 @@ def load_variables(self, variables: VariableList, attribute_to_column: Dict[str, """ if not attribute_to_column: - attribute_to_column = self._DEFAULT_COLUMN_RENAMING + attribute_to_column = OptimizationViewer._DEFAULT_COLUMN_RENAMING self.dataframe = ( variables.to_dataframe() @@ -234,7 +238,7 @@ def load_variables(self, variables: VariableList, attribute_to_column: Dict[str, .reset_index(drop=True) ) - def get_variables(self, column_to_attribute: Dict[str, str] = None) -> VariableList: + def get_variables(self, column_to_attribute: dict[str, str] | None = None) -> VariableList: """ :param column_to_attribute: dictionary keys tell what columns are kept and the values @@ -244,7 +248,7 @@ def get_variables(self, column_to_attribute: Dict[str, str] = None) -> VariableL """ if not column_to_attribute: column_to_attribute = { - value: key for key, value in self._DEFAULT_COLUMN_RENAMING.items() + value: key for key, value in OptimizationViewer._DEFAULT_COLUMN_RENAMING.items() } return VariableList.from_dataframe( @@ -322,8 +326,7 @@ def _sheet_to_df(sheet: sh.Sheet) -> pd.DataFrame: :param sheet: the ipysheet Sheet to be converted :return: the equivalent pandas DataFrame """ - df = sh.to_dataframe(sheet) - return df + return sh.to_dataframe(sheet) # pylint: disable=unused-argument # args has to be there for observe() to work def _update_df(self, change=None): @@ -339,7 +342,7 @@ def _update_df(self, change=None): df = pd.concat(frames, sort=True) columns = {} - columns.update(self._DEFAULT_COLUMN_RENAMING) + columns.update(OptimizationViewer._DEFAULT_COLUMN_RENAMING) columns.pop("type") column_to_attribute = {value: key for key, value in columns.items()} @@ -455,7 +458,7 @@ def _objective_ui(self): return widgets.VBox([widgets.Label(value="Objectives"), self._objective_sheet]) @staticmethod - def _cell_styling(df) -> Dict: + def _cell_styling(df) -> dict: """ Returns bound activities in the form of cell style dictionary. @@ -509,10 +512,7 @@ def highlight_active_bounds(df, threshold=1e-6): return style - style = highlight_active_bounds(df, threshold=0.1) - # style.update(another_styling_method()) - - return style + return highlight_active_bounds(df, threshold=0.1) # Style def _update_style(self, change=None): """ diff --git a/src/fastoad/gui/tests/test_analysis_and_plots.py b/src/fastoad/gui/tests/test_analysis_and_plots.py index c14a70e87..860991afa 100644 --- a/src/fastoad/gui/tests/test_analysis_and_plots.py +++ b/src/fastoad/gui/tests/test_analysis_and_plots.py @@ -149,7 +149,7 @@ def test_mass_breakdown_sun_plot_specific_mission(): # Plot 1 # Specific mission plot - f = mass_breakdown_sun_plot(filename, mission_name=mission_1) # noqa: F841 + f = mass_breakdown_sun_plot(filename, mission_name=mission_1) # f.show() mission_2 = "MTOW_mission" diff --git a/src/fastoad/gui/variable_viewer.py b/src/fastoad/gui/variable_viewer.py index 615f07b09..cd97794a9 100644 --- a/src/fastoad/gui/variable_viewer.py +++ b/src/fastoad/gui/variable_viewer.py @@ -1,6 +1,7 @@ """ Defines the variable viewer for postprocessing """ + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2024 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -13,9 +14,10 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations from os import PathLike -from typing import Dict, List, Set, Union +from typing import ClassVar import ipysheet as sh import ipywidgets as widgets @@ -51,7 +53,7 @@ class VariableViewer: # When getting a dataframe from a VariableList, the dictionary keys tell # what columns are kept and the values tell what name will be displayed. - _DEFAULT_COLUMN_RENAMING = { + _DEFAULT_COLUMN_RENAMING: ClassVar[dict] = { "name": "Name", "val": "Value", "units": "Unit", @@ -84,7 +86,7 @@ def __init__(self): # The complete user interface self._ui = None - def load(self, file_path: Union[str, PathLike], file_formatter: IVariableIOFormatter = None): + def load(self, file_path: str | PathLike, file_formatter: IVariableIOFormatter = None): """ Loads the file and stores its data. @@ -97,7 +99,7 @@ def load(self, file_path: Union[str, PathLike], file_formatter: IVariableIOForma self.load_variables(VariableIO(file_path, file_formatter).read()) def save( - self, file_path: Union[str, PathLike] = None, file_formatter: IVariableIOFormatter = None + self, file_path: str | PathLike | None = None, file_formatter: IVariableIOFormatter = None ): """ Save the dataframe to the file. @@ -123,7 +125,9 @@ def display(self): self._create_io_selector() return self._render_sheet() - def load_variables(self, variables: VariableList, attribute_to_column: Dict[str, str] = None): + def load_variables( + self, variables: VariableList, attribute_to_column: dict[str, str] | None = None + ): """ Loads provided variable list and replace current data set. @@ -134,7 +138,7 @@ def load_variables(self, variables: VariableList, attribute_to_column: Dict[str, """ if not attribute_to_column: - attribute_to_column = self._DEFAULT_COLUMN_RENAMING + attribute_to_column = VariableViewer._DEFAULT_COLUMN_RENAMING self.dataframe = ( variables.to_dataframe() @@ -147,7 +151,7 @@ def load_variables(self, variables: VariableList, attribute_to_column: Dict[str, self.dataframe.loc[self.dataframe["is_input"] == 0, "I/O"] = OUTPUT self.dataframe.drop(columns=["is_input"], inplace=True) - def get_variables(self, column_to_attribute: Dict[str, str] = None) -> VariableList: + def get_variables(self, column_to_attribute: dict[str, str] | None = None) -> VariableList: """ :param column_to_attribute: dictionary keys tell what columns are kept and the values @@ -157,7 +161,7 @@ def get_variables(self, column_to_attribute: Dict[str, str] = None) -> VariableL """ if not column_to_attribute: column_to_attribute = { - value: key for key, value in self._DEFAULT_COLUMN_RENAMING.items() + value: key for key, value in VariableViewer._DEFAULT_COLUMN_RENAMING.items() } dataframe = self.dataframe.copy() @@ -166,10 +170,9 @@ def get_variables(self, column_to_attribute: Dict[str, str] = None) -> VariableL dataframe.loc[dataframe["I/O"] == INPUT, "is_input"] = True dataframe.loc[dataframe["I/O"] == OUTPUT, "is_input"] = False dataframe.drop(columns=["I/O"], inplace=True) - variables = VariableList.from_dataframe( + return VariableList.from_dataframe( # Variables dataframe[column_to_attribute.keys()].rename(columns=column_to_attribute) ) - return variables # pylint: disable=invalid-name # df is a common naming for dataframes @staticmethod @@ -209,8 +212,7 @@ def _sheet_to_df(sheet: sh.Sheet) -> pd.DataFrame: :param sheet: the ipysheet Sheet to be converted :return the equivalent pandas DataFrame """ - df = sh.to_dataframe(sheet) - return df + return sh.to_dataframe(sheet) # pylint: disable=unused-argument # args has to be there for observe() to work def _update_df(self, change=None): @@ -229,7 +231,7 @@ def _render_sheet(self) -> display: :return display of the user interface """ self._filter_widgets = [] - modules_item = [TAG_ALL] + sorted(self._find_submodules(self.dataframe)) + modules_item = [TAG_ALL, *sorted(self._find_submodules(self.dataframe))] w = widgets.Dropdown(options=modules_item) self._filter_widgets.append(w) return self._render_ui() @@ -372,7 +374,7 @@ def _render_ui(self, change=None) -> display: return display(self._ui) @staticmethod - def _find_submodules(df: pd.DataFrame, modules: List[str] = None) -> Set[str]: + def _find_submodules(df: pd.DataFrame, modules: list[str] | None = None) -> set[str]: """ Search for submodules at root or provided modules. @@ -404,7 +406,7 @@ def get_next_module(path): @staticmethod def _filter_variables( - df: pd.DataFrame, modules: List[str], var_io_type: str = None + df: pd.DataFrame, modules: list[str], var_io_type: str | None = None ) -> pd.DataFrame: """ Returns a filtered dataframe with respect to a set of modules and variable type. @@ -430,6 +432,4 @@ def _filter_variables( [True] * len(df) if var_io_type == TAG_ALL else df["I/O"] == var_io_type ) - filtered_df = df[path_filter & io_filter] - - return filtered_df + return df[path_filter & io_filter] # Filtered_df diff --git a/src/fastoad/io/__init__.py b/src/fastoad/io/__init__.py index 89bfb15d9..43b62f34b 100644 --- a/src/fastoad/io/__init__.py +++ b/src/fastoad/io/__init__.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# flake8: noqa from .formatter import IVariableIOFormatter from .variable_io import DataFile, VariableIO + +__all__ = ["DataFile", "IVariableIOFormatter", "VariableIO"] diff --git a/src/fastoad/io/configuration/__init__.py b/src/fastoad/io/configuration/__init__.py index 58f99b1cc..b5c691a13 100644 --- a/src/fastoad/io/configuration/__init__.py +++ b/src/fastoad/io/configuration/__init__.py @@ -14,5 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# flake8: noqa from .configuration import FASTOADProblemConfigurator + +__all__ = ["FASTOADProblemConfigurator"] diff --git a/src/fastoad/io/configuration/configuration.py b/src/fastoad/io/configuration/configuration.py index a888a046b..c8b623cd4 100644 --- a/src/fastoad/io/configuration/configuration.py +++ b/src/fastoad/io/configuration/configuration.py @@ -1,6 +1,7 @@ """ Module for building OpenMDAO problem from configuration file """ + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2024 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -13,6 +14,7 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations import json import logging @@ -22,7 +24,6 @@ from importlib import import_module from os import PathLike from pathlib import Path -from typing import Dict, List, Optional, Union import openmdao.api as om import tomlkit @@ -70,7 +71,7 @@ class for configuring an OpenMDAO problem from a configuration file :param conf_file_path: if provided, configuration will be read directly from it """ - def __init__(self, conf_file_path: Union[str, PathLike] = None): + def __init__(self, conf_file_path: str | PathLike | None = None): self._serializer: _IDictSerializer = _YAMLSerializer() # for storing imported classes @@ -78,9 +79,9 @@ def __init__(self, conf_file_path: Union[str, PathLike] = None): # self._configuration_modifier offers a way to modify problems after # they have been generated from configuration (private usage for now) - self._configuration_modifier: Optional["_IConfigurationModifier"] = None + self._configuration_modifier: _IConfigurationModifier | None = None - self._conf_file_path: Optional[Path] = None + self._conf_file_path: Path | None = None if conf_file_path: self.load(conf_file_path) @@ -90,7 +91,7 @@ def input_file_path(self) -> str: return self._make_absolute(self._data[KEY_INPUT_FILE]).as_posix() @input_file_path.setter - def input_file_path(self, file_path: Union[str, PathLike]): + def input_file_path(self, file_path: str | PathLike): self._data[KEY_INPUT_FILE] = str(file_path) @property @@ -99,14 +100,16 @@ def output_file_path(self) -> str: return self._make_absolute(self._data[KEY_OUTPUT_FILE]).as_posix() @output_file_path.setter - def output_file_path(self, file_path: Union[str, PathLike]): + def output_file_path(self, file_path: str | PathLike): self._data[KEY_OUTPUT_FILE] = str(file_path) @property def _data(self) -> dict: return self._serializer.data - def get_problem(self, read_inputs: bool = False, auto_scaling: bool = False) -> FASTOADProblem: + def get_problem( + self, *, read_inputs: bool = False, auto_scaling: bool = False + ) -> FASTOADProblem: """ Builds the OpenMDAO problem from current configuration. @@ -149,7 +152,7 @@ def get_problem(self, read_inputs: bool = False, auto_scaling: bool = False) -> return problem - def load(self, conf_file: Union[str, PathLike]): + def load(self, conf_file: str | PathLike): """ Reads the problem definition @@ -207,7 +210,7 @@ def load(self, conf_file: Union[str, PathLike]): for submodel_requirement, submodel_id in submodel_specs.items(): RegisterSubmodel.active_models[submodel_requirement] = submodel_id - def save(self, filename: Union[str, PathLike] = None): + def save(self, filename: str | PathLike | None = None): """ Saves the current configuration If no filename is provided, the initially read file is used. @@ -224,7 +227,7 @@ def save(self, filename: Union[str, PathLike] = None): def write_needed_inputs( self, - source_file_path: Union[str, PathLike] = None, + source_file_path: str | PathLike | None = None, source_formatter: IVariableIOFormatter = None, ): """ @@ -243,7 +246,7 @@ def write_needed_inputs( problem = self.get_problem(read_inputs=False) problem.write_needed_inputs(source_file_path, source_formatter) - def get_optimization_definition(self) -> Dict: + def get_optimization_definition(self) -> dict: """ Returns information related to the optimization problem: - Design Variables @@ -260,7 +263,7 @@ def get_optimization_definition(self) -> Dict: optimization_definition[sec] = {elem["name"]: elem for elem in elements} return optimization_definition - def set_optimization_definition(self, optimization_definition: Dict): + def set_optimization_definition(self, optimization_definition: dict): """ Updates configuration with the list of design variables, constraints, objectives contained in the optimization_definition dictionary. @@ -277,7 +280,7 @@ def set_optimization_definition(self, optimization_definition: Dict): subpart = {"optimization": subpart} self._data.update(subpart) - def make_local(self, new_folder_path: Union[str, PathLike], copy_models: bool = False): + def make_local(self, new_folder_path: str | PathLike, *, copy_models: bool = False): """ Modify the current configurator so that all input and output files will be in the indicated folder. @@ -320,7 +323,7 @@ def _make_options_local( self, structure: dict, new_root_path: Path, - local_path: Path = Path("."), + local_path: Path = Path("."), # noqa: PTH201 ): """ Recursively modifies `structure` to make each path-like value local with respect to @@ -383,7 +386,7 @@ def _configure_driver(self, prob): if key != "instance": getattr(prob.driver, key).update(value) - def _make_absolute(self, path: Union[str, PathLike]) -> Path: + def _make_absolute(self, path: str | PathLike) -> Path: """ Make the provided path absolute using configuration file folder as base. @@ -394,7 +397,7 @@ def _make_absolute(self, path: Union[str, PathLike]) -> Path: path = (self._conf_file_path.parent / path).resolve() return path - def _get_module_folder_paths(self) -> List[Path]: + def _get_module_folder_paths(self) -> list[Path]: module_folder_paths = self._data.get(KEY_FOLDERS) # Key may be present, but with None value if not module_folder_paths: @@ -405,9 +408,9 @@ def _get_module_folder_paths(self) -> List[Path]: @staticmethod def _make_path_local( - original_path: Union[str, PathLike], - new_folder_path: Union[str, PathLike], - local_path: Optional[Union[str, PathLike]] = None, + original_path: str | PathLike, + new_folder_path: str | PathLike, + local_path: str | PathLike | None = None, ) -> str: """ For 'original_path' "/foo/bar/baz[.ext]", returns "./baz[.ext]" or @@ -431,8 +434,7 @@ def _make_path_local( shutil.copy(original_path, new_path) if original_path.is_dir(): shutil.copytree(original_path, new_path, dirs_exist_ok=True) - new_relative_path = new_path.relative_to(new_folder_path).as_posix() - return new_relative_path + return new_path.relative_to(new_folder_path).as_posix() # new_relative_path def _build_model(self, problem: FASTOADProblem): """ @@ -581,7 +583,7 @@ def _add_design_vars(self, model, auto_scaling): design_var_table["ref"] = design_var_table["upper"] model.add_design_var(**design_var_table) - def _set_configuration_modifier(self, modifier: "_IConfigurationModifier"): + def _set_configuration_modifier(self, modifier: _IConfigurationModifier): self._configuration_modifier = modifier def _om_eval(self, string_to_eval: str): @@ -612,14 +614,14 @@ def data(self) -> dict: """ @abstractmethod - def read(self, file_path: Union[str, PathLike]): + def read(self, file_path: str | PathLike): """ Reads data from provided file. :param file_path: """ @abstractmethod - def write(self, file_path: Union[str, PathLike]): + def write(self, file_path: str | PathLike): """ Writes data to provided file. :param file_path: @@ -636,12 +638,12 @@ def __init__(self): def data(self): return self._data - def read(self, file_path: Union[str, PathLike]): - with open(file_path, "r") as toml_file: + def read(self, file_path: str | PathLike): + with Path.open(file_path, "r") as toml_file: self._data = tomlkit.loads(toml_file.read()).value - def write(self, file_path: Union[str, PathLike]): - with open(file_path, "w") as file: + def write(self, file_path: str | PathLike): + with Path.open(file_path, "w") as file: file.write(tomlkit.dumps(self._data)) @@ -655,15 +657,15 @@ def __init__(self): def data(self): return self._data - def read(self, file_path: Union[str, PathLike]): + def read(self, file_path: str | PathLike): yaml = YAML(typ="safe") - with open(file_path) as yaml_file: + with Path.open(file_path, "r") as yaml_file: self._data = yaml.load(yaml_file) - def write(self, file_path: Union[str, PathLike]): + def write(self, file_path: str | PathLike): yaml = YAML() yaml.default_flow_style = False - with open(file_path, "w") as file: + with Path.open(file_path, "w") as file: yaml.dump(self._data, file) diff --git a/src/fastoad/io/configuration/exceptions.py b/src/fastoad/io/configuration/exceptions.py index 143fb640f..5370d00f6 100644 --- a/src/fastoad/io/configuration/exceptions.py +++ b/src/fastoad/io/configuration/exceptions.py @@ -54,11 +54,11 @@ def __init__(self, original_exception: Exception, key: str, value=None): """ the original error, when eval failed """ if hasattr(original_exception, "key"): - self.key = "%s.%s" % (key, original_exception.key) + self.key = f"{key}.{original_exception.key}" else: self.key = key if hasattr(original_exception, "value"): - self.value = "%s.%s" % (value, original_exception.value) + self.value = f"{value}.{original_exception.value}" else: self.value = value if hasattr(original_exception, "original_exception"): @@ -67,8 +67,8 @@ def __init__(self, original_exception: Exception, key: str, value=None): self.original_exception = original_exception super().__init__( self, - 'Attribute or value not recognized : %s = "%s"\nOriginal error: %s' - % (self.key, self.value, self.original_exception), + f'Attribute or value not recognized : {self.key} = "{self.value}"\n' + "Original error: {self.original_exception}", ) diff --git a/src/fastoad/io/configuration/tests/test_configuration.py b/src/fastoad/io/configuration/tests/test_configuration.py index 6bc85803f..48474174d 100644 --- a/src/fastoad/io/configuration/tests/test_configuration.py +++ b/src/fastoad/io/configuration/tests/test_configuration.py @@ -15,7 +15,6 @@ # along with this program. If not, see . import filecmp -import os import shutil import sys import tempfile @@ -244,7 +243,7 @@ def test_problem_definition_with_custom_xml(cleanup): conf.output_file_path = result_folder_path / "outputs.xml" input_data = DATA_FOLDER_PATH / "ref_inputs.xml" - os.makedirs(result_folder_path, exist_ok=True) + result_folder_path.mkdir(parents=True, exist_ok=True) shutil.copy(input_data, conf.input_file_path) problem = conf.get_problem(read_inputs=True, auto_scaling=True) @@ -327,7 +326,7 @@ def test_set_optimization_definition(cleanup): read = tomlkit.loads if extension == "toml" else YAML(typ="safe").load - with open(reference_file, "r") as file: + with Path.open(reference_file, "r") as file: d = file.read() conf_dict = read(d) conf_dict_opt = conf_dict["optimization"] @@ -336,7 +335,7 @@ def test_set_optimization_definition(cleanup): conf.set_optimization_definition(optimization_def) conf.save(editable_file) - with open(editable_file, "r") as file: + with Path.open(editable_file, "r") as file: d = file.read() conf_dict = read(d) conf_dict_opt = conf_dict["optimization"] @@ -475,7 +474,7 @@ def test_driver_configuration(added_sys_path): with tempfile.NamedTemporaryFile(delete=False, mode="w", suffix=".yaml") as temp_config_file: yaml.dump(config_data_new, temp_config_file) - temp_config_file_path = temp_config_file.name + temp_config_file_path = Path(temp_config_file.name) configurator = FASTOADProblemConfigurator() configurator.load(temp_config_file_path) @@ -487,7 +486,7 @@ def test_driver_configuration(added_sys_path): assert problem.driver.options["tol"] == 1e-2 assert problem.driver.opt_settings["maxtime"] == 10 - os.remove(temp_config_file_path) + Path.unlink(temp_config_file_path) # Test standard syntax config_data_old = { @@ -499,7 +498,7 @@ def test_driver_configuration(added_sys_path): with tempfile.NamedTemporaryFile(delete=False, mode="w", suffix=".yaml") as temp_config_file: yaml.dump(config_data_old, temp_config_file) - temp_config_file_path = temp_config_file.name + temp_config_file_path = Path(temp_config_file.name) configurator = FASTOADProblemConfigurator() configurator.load(temp_config_file_path) @@ -509,4 +508,4 @@ def test_driver_configuration(added_sys_path): assert problem.driver.options["optimizer"] == "COBYLA" assert problem.driver.options["tol"] == 1e-2 - os.remove(temp_config_file_path) + Path.unlink(temp_config_file_path) diff --git a/src/fastoad/io/formatter.py b/src/fastoad/io/formatter.py index 646864328..cfbc1ffc2 100644 --- a/src/fastoad/io/formatter.py +++ b/src/fastoad/io/formatter.py @@ -14,9 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + from abc import ABC, abstractmethod from os import PathLike -from typing import IO, Union +from typing import IO from fastoad.openmdao.variables import VariableList @@ -29,7 +31,7 @@ class IVariableIOFormatter(ABC): """ @abstractmethod - def read_variables(self, data_source: Union[str, PathLike, IO]) -> VariableList: + def read_variables(self, data_source: str | PathLike | IO) -> VariableList: """ Reads variables from provided data source file. @@ -38,7 +40,7 @@ def read_variables(self, data_source: Union[str, PathLike, IO]) -> VariableList: """ @abstractmethod - def write_variables(self, data_source: Union[str, PathLike, IO], variables: VariableList): + def write_variables(self, data_source: str | PathLike | IO, variables: VariableList): """ Writes variables to defined data source file. diff --git a/src/fastoad/io/tests/test_data_file.py b/src/fastoad/io/tests/test_data_file.py index 59d011c0b..5dea6ebdf 100644 --- a/src/fastoad/io/tests/test_data_file.py +++ b/src/fastoad/io/tests/test_data_file.py @@ -11,10 +11,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + import shutil from os import PathLike from pathlib import Path -from typing import IO, Union +from typing import IO import openmdao.api as om import pytest @@ -44,12 +46,12 @@ class DummyFormatter(IVariableIOFormatter): def __init__(self, variables): self.variables = variables - def read_variables(self, data_source: Union[str, PathLike, IO]) -> VariableList: + def read_variables(self, data_source: str | PathLike | IO) -> VariableList: var_list = VariableList() var_list.update(self.variables, add_variables=True) return var_list - def write_variables(self, data_source: Union[str, PathLike, IO], variables: VariableList): + def write_variables(self, data_source: str | PathLike | IO, variables: VariableList): self.variables.update(variables, add_variables=True) @@ -78,12 +80,12 @@ def test_datafile_save_read(cleanup, variables_ref): assert set(data_file_2) == set(variables_ref) # Check using text file object -------------------- - with open(file_path) as text_file_io: + with Path.open(file_path) as text_file_io: data_file_3 = DataFile(text_file_io) assert data_file_3 == data_file_2 # Check using binary file object -------------------- - with open(file_path, "rb") as binary_file_io: + with Path.open(file_path, "rb") as binary_file_io: data_file_4 = DataFile(binary_file_io) assert data_file_4 == data_file_2 diff --git a/src/fastoad/io/variable_io.py b/src/fastoad/io/variable_io.py index 48199e5a2..5948b4399 100644 --- a/src/fastoad/io/variable_io.py +++ b/src/fastoad/io/variable_io.py @@ -11,10 +11,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + +from collections.abc import Sequence from fnmatch import fnmatchcase from os import PathLike from pathlib import Path -from typing import IO, List, Optional, Sequence, Union +from typing import IO from fastoad.openmdao.variables import VariableList @@ -37,7 +40,7 @@ class VariableIO: def __init__( self, - data_source: Optional[Union[str, PathLike, IO]], + data_source: str | PathLike | IO | None, formatter: IVariableIOFormatter = None, ): if isinstance(data_source, (str, PathLike)): @@ -57,7 +60,7 @@ def formatter(self) -> IVariableIOFormatter: def formatter(self, formatter: IVariableIOFormatter): self._formatter = formatter if formatter else VariableXmlStandardFormatter() - def read(self, only: List[str] = None, ignore: List[str] = None) -> Optional[VariableList]: + def read(self, only: list[str] | None = None, ignore: list[str] | None = None) -> VariableList: """ Reads variables from provided data source. @@ -75,10 +78,14 @@ def read(self, only: List[str] = None, ignore: List[str] = None) -> Optional[Var ) from FastError() variables = self.formatter.read_variables(self.data_source) - used_variables = self._filter_variables(variables, only=only, ignore=ignore) - return used_variables + return self._filter_variables(variables, only=only, ignore=ignore) # used_variables - def write(self, variables: VariableList, only: List[str] = None, ignore: List[str] = None): + def write( + self, + variables: VariableList, + only: list[str] | None = None, + ignore: list[str] | None = None, + ): """ Writes variables from provided VariableList instance. @@ -94,13 +101,15 @@ def write(self, variables: VariableList, only: List[str] = None, ignore: List[st # Before writing, variables are sorted to have short paths first. With equal path length # alphanumeric order will be used. - used_variables.sort(key=lambda var: "%02i_%s" % (len(var.name.split(":")), var.name)) + used_variables.sort(key=lambda var: f"{len(var.name.split(':')):02}_{var.name}") self.formatter.write_variables(self.data_source, used_variables) @staticmethod def _filter_variables( - variables: VariableList, only: Sequence[str] = None, ignore: Sequence[str] = None + variables: VariableList, + only: Sequence[str] | None = None, + ignore: Sequence[str] | None = None, ) -> VariableList: """ filters the variables such that the ones in arg only are kept and the ones in @@ -149,9 +158,9 @@ class DataFile(VariableList): def __init__( self, - data_source: Union[str, PathLike, IO, list] = None, + data_source: str | PathLike | IO | list | None = None, formatter: IVariableIOFormatter = None, - load_data: bool = True, + load_data: bool = True, # noqa: FBT001, FBT002 no breaking changes in API functions ): """ If variable list is specified for data_source, :attr:`file_path` will have to be set before @@ -217,18 +226,18 @@ def save(self): def save_as( self, - file_path: Union[str, PathLike], - overwrite=False, + file_path: str | PathLike, formatter: IVariableIOFormatter = None, + overwrite=False, # noqa: FBT002 no breaking changes in API functions ): """ Sets the associated file path as specified and saves current state of variables. :param file_path: - :param overwrite: if specified file already exists and overwrite is False, an error is - triggered. :param formatter: a class that determines the file format to be used. Defaults to FAST-OAD native format. See :class:`VariableIO` for more information. + :param overwrite: if specified file already exists and overwrite is False, an error is + triggered. """ file_path = as_path(file_path) if not overwrite and file_path.exists(): diff --git a/src/fastoad/io/xml/__init__.py b/src/fastoad/io/xml/__init__.py index cf71f199c..a175c2fe3 100644 --- a/src/fastoad/io/xml/__init__.py +++ b/src/fastoad/io/xml/__init__.py @@ -14,7 +14,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# flake8: noqa from .variable_io_base import VariableXmlBaseFormatter from .variable_io_legacy import VariableLegacy1XmlFormatter from .variable_io_standard import VariableXmlStandardFormatter + +__all__ = [ + "VariableLegacy1XmlFormatter", + "VariableXmlBaseFormatter", + "VariableXmlStandardFormatter", +] diff --git a/src/fastoad/io/xml/exceptions.py b/src/fastoad/io/xml/exceptions.py index 16ee7358e..cb8672025 100644 --- a/src/fastoad/io/xml/exceptions.py +++ b/src/fastoad/io/xml/exceptions.py @@ -40,7 +40,7 @@ class FastXpathTranslatorVariableError(FastError): """ def __init__(self, variable): - super().__init__("Unknown variable %s" % variable) + super().__init__(f"Unknown variable {variable}") self.variable = variable @@ -50,7 +50,7 @@ class FastXpathTranslatorXPathError(FastError): """ def __init__(self, xpath): - super().__init__("Unknown xpath %s" % xpath) + super().__init__(f"Unknown xpath {xpath}") self.xpath = xpath diff --git a/src/fastoad/io/xml/resources/remove_duplicate_variables.py b/src/fastoad/io/xml/resources/remove_duplicate_variables.py index 223261217..e762b3184 100644 --- a/src/fastoad/io/xml/resources/remove_duplicate_variables.py +++ b/src/fastoad/io/xml/resources/remove_duplicate_variables.py @@ -14,7 +14,7 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from os import remove +from pathlib import Path def remove_duplicate(input_file_name: str): @@ -22,7 +22,7 @@ def remove_duplicate(input_file_name: str): Removes duplicate lines in a text file """ - input_file = open(input_file_name, "r") + input_file = Path.open(input_file_name, "r") # Store all the variables stored_lines = [] @@ -31,10 +31,10 @@ def remove_duplicate(input_file_name: str): stored_lines.append(line) # Delete the file input_file.close() - remove(input_file_name) + Path.unlink(input_file_name) # Create new file - input_file = open(input_file_name, "w") + input_file = Path.open(input_file_name, "w") # Write non duplicate lines for line in stored_lines: diff --git a/src/fastoad/io/xml/tests/test_translator.py b/src/fastoad/io/xml/tests/test_translator.py index a4dabf070..0ee7bf01c 100644 --- a/src/fastoad/io/xml/tests/test_translator.py +++ b/src/fastoad/io/xml/tests/test_translator.py @@ -34,11 +34,11 @@ def test_translator_with_set(): translator = VarXpathTranslator() indices = range(10) - var_list = ["var%i" % i for i in indices] - xpath_list = ["xpath%i" % i for i in indices] + var_list = [f"var{i}" for i in indices] + xpath_list = [f"xpath{i}" for i in indices] # test with lists of different lengths -> error - var_list2 = ["var0"] + var_list + var_list2 = ["var0", *var_list] with pytest.raises(FastXpathTranslatorInconsistentLists): translator.set(var_list2, xpath_list) @@ -67,8 +67,8 @@ def test_translator_with_set(): assert translator.xpaths == xpath_list for i in indices: - assert translator.get_xpath("var%i" % i) == "xpath%i" % i - assert translator.get_variable_name("xpath%i" % i) == "var%i" % i + assert translator.get_xpath(f"var{i}") == f"xpath{i}" + assert translator.get_variable_name(f"xpath{i}") == f"var{i}" with pytest.raises(FastXpathTranslatorVariableError) as exc_info: _ = translator.get_xpath("unknown_var") diff --git a/src/fastoad/io/xml/tests/test_variable_io_base.py b/src/fastoad/io/xml/tests/test_variable_io_base.py index fd7d306ac..1acf2a5a8 100644 --- a/src/fastoad/io/xml/tests/test_variable_io_base.py +++ b/src/fastoad/io/xml/tests/test_variable_io_base.py @@ -97,7 +97,7 @@ def test_custom_xml_read_and_write_from_ivc(cleanup): file_path, formatter=VariableXmlBaseFormatter( VarXpathTranslator( - variable_names=var_names + ["additional_var"], xpaths=xpaths + ["bad:xpath"] + variable_names=[*var_names, "additional_var"], xpaths=[*xpaths, "bad:xpath"] ) ), ) @@ -105,12 +105,12 @@ def test_custom_xml_read_and_write_from_ivc(cleanup): _check_basic_vars(var_list) # Check using text file object -------------------- - with open(file_path) as text_file_io: + with Path.open(file_path) as text_file_io: var_list_2 = VariableIO(text_file_io, formatter=VariableXmlBaseFormatter(translator)).read() assert var_list_2 == var_list # Check using binary file object -------------------- - with open(file_path, "rb") as binary_file_io: + with Path.open(file_path, "rb") as binary_file_io: var_list_3 = VariableIO( binary_file_io, formatter=VariableXmlBaseFormatter(translator) ).read() diff --git a/src/fastoad/io/xml/tests/test_variable_io_legacy.py b/src/fastoad/io/xml/tests/test_variable_io_legacy.py index ca36e2325..222153e81 100644 --- a/src/fastoad/io/xml/tests/test_variable_io_legacy.py +++ b/src/fastoad/io/xml/tests/test_variable_io_legacy.py @@ -56,12 +56,12 @@ def test_legacy1(cleanup): assert var_list["data:geometry:wing:wetted_area"].units == "m**2" # Check using text file object -------------------- - with open(file_path, encoding="utf-8") as text_file_io: + with Path.open(file_path, encoding="utf-8") as text_file_io: var_list_2 = VariableIO(text_file_io, formatter=VariableLegacy1XmlFormatter()).read() assert var_list_2 == var_list # Check using binary file object -------------------- - with open(file_path, "rb") as binary_file_io: + with Path.open(file_path, "rb") as binary_file_io: var_list_3 = VariableIO(binary_file_io, formatter=VariableLegacy1XmlFormatter()).read() assert var_list_3 == var_list diff --git a/src/fastoad/io/xml/tests/test_variable_io_standard.py b/src/fastoad/io/xml/tests/test_variable_io_standard.py index 7ef953c7d..49587bc04 100644 --- a/src/fastoad/io/xml/tests/test_variable_io_standard.py +++ b/src/fastoad/io/xml/tests/test_variable_io_standard.py @@ -157,12 +157,12 @@ def test_basic_xml_read_and_write_from_vars(cleanup): xml_write.write(var_list) # Check using text file object -------------------- - with open(file_path) as text_file_io: + with Path.open(file_path) as text_file_io: var_list_2 = VariableIO(text_file_io, formatter=VariableXmlStandardFormatter()).read() assert var_list_2 == var_list # Check using binary file object -------------------- - with open(file_path, "rb") as binary_file_io: + with Path.open(file_path, "rb") as binary_file_io: var_list_3 = VariableIO(binary_file_io, formatter=VariableXmlStandardFormatter()).read() assert var_list_3 == var_list diff --git a/src/fastoad/io/xml/translator.py b/src/fastoad/io/xml/translator.py index 9037b52ba..d34a97d67 100644 --- a/src/fastoad/io/xml/translator.py +++ b/src/fastoad/io/xml/translator.py @@ -14,7 +14,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from typing import IO, Sequence, Set, Union +from __future__ import annotations + +from collections.abc import Sequence +from typing import IO import numpy as np @@ -38,9 +41,9 @@ class VarXpathTranslator: def __init__( self, *, - variable_names: Sequence[str] = None, - xpaths: Sequence[str] = None, - source: Union[str, IO] = None, + variable_names: Sequence[str] | None = None, + xpaths: Sequence[str] | None = None, + source: str | IO | None = None, ): if variable_names is not None and xpaths is not None: self.set(variable_names, xpaths) @@ -58,29 +61,28 @@ def set(self, variable_names: Sequence[str], xpaths: Sequence[str]): """ if len(variable_names) != len(xpaths): raise FastXpathTranslatorInconsistentLists( - "lists var_names and xpaths have not the same length (%i and %i)" - % (len(variable_names), len(xpaths)) + f"lists var_names and xpaths have not the same length ({len(variable_names)} and {len(xpaths)})" ) # check duplicate variable names dupe_vars = self._get_duplicates(variable_names) if dupe_vars: raise FastXpathTranslatorDuplicates( - "Following variable names are provided more than once: %s" % dupe_vars, dupe_vars + f"Following variable names are provided more than once: {dupe_vars}", dupe_vars ) # check duplicate XPaths dupe_xpaths = self._get_duplicates(xpaths) if dupe_xpaths: raise FastXpathTranslatorDuplicates( - "Following variable names are provided more than once: %s" % dupe_xpaths, + f"Following variable names are provided more than once: {dupe_xpaths}", dupe_xpaths, ) self._variable_names = list(variable_names) self._xpaths = list(xpaths) - def read_translation_table(self, source: Union[str, IO]): + def read_translation_table(self, source: str | IO): """ Reads a file that sets how OpenMDAO variable are matched to XML Path. Provided file should have 2 comma-separated columns: @@ -128,7 +130,7 @@ def get_variable_name(self, xpath: str) -> str: raise FastXpathTranslatorXPathError(xpath) @staticmethod - def _get_duplicates(seq: Sequence) -> Set: + def _get_duplicates(seq: Sequence) -> set: dupes = set() seen = set() for elem in seq: diff --git a/src/fastoad/io/xml/variable_io_base.py b/src/fastoad/io/xml/variable_io_base.py index 719a98dc1..cbc0771f9 100644 --- a/src/fastoad/io/xml/variable_io_base.py +++ b/src/fastoad/io/xml/variable_io_base.py @@ -14,12 +14,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + import json import logging import re from os import PathLike from pathlib import Path -from typing import IO, Optional, Union +from typing import IO import numpy as np from lxml import etree @@ -90,7 +92,7 @@ def __init__(self, translator: VarXpathTranslator): r"\bin\b": "inch", } - def read_variables(self, data_source: Union[str, PathLike, IO]) -> VariableList: + def read_variables(self, data_source: str | PathLike | IO) -> VariableList: variables = VariableList() # If there is a comment, it will be used as description if the previous @@ -141,7 +143,7 @@ def read_variables(self, data_source: Union[str, PathLike, IO]) -> VariableList: return variables - def write_variables(self, data_source: Union[str, PathLike, IO], variables: VariableList): + def write_variables(self, data_source: str | PathLike | IO, variables: VariableList): root = etree.Element(ROOT_TAG) for variable in variables: @@ -178,7 +180,7 @@ def write_variables(self, data_source: Union[str, PathLike, IO], variables: Vari tree.write(data_source, pretty_print=True) - def _read_units(self, elem) -> Optional[str]: + def _read_units(self, elem) -> str | None: units = elem.attrib.get(self.xml_unit_attribute, None) if units: # Ensures compatibility with OpenMDAO units @@ -187,7 +189,7 @@ def _read_units(self, elem) -> Optional[str]: units = units.replace(legacy_chars, om_chars) return units - def _get_matching_variable_name(self, elem: _Element) -> Optional[str]: + def _get_matching_variable_name(self, elem: _Element) -> str | None: path_tags = [ancestor.tag for ancestor in elem.iterancestors()] path_tags.reverse() path_tags.append(elem.tag) diff --git a/src/fastoad/io/xml/variable_io_standard.py b/src/fastoad/io/xml/variable_io_standard.py index f445c2492..0263ccebc 100644 --- a/src/fastoad/io/xml/variable_io_standard.py +++ b/src/fastoad/io/xml/variable_io_standard.py @@ -14,9 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + import logging from os import PathLike -from typing import IO, Union +from typing import IO from fastoad.openmdao.variables import VariableList @@ -77,7 +79,7 @@ def path_separator(self): def path_separator(self, separator): self._translator.path_separator = separator - def read_variables(self, data_source: Union[str, PathLike, IO]) -> VariableList: + def read_variables(self, data_source: str | PathLike | IO) -> VariableList: # Check separator, as OpenMDAO won't accept the dot. if self.path_separator == ".": _LOGGER.warning( @@ -85,13 +87,13 @@ def read_variables(self, data_source: Union[str, PathLike, IO]) -> VariableList: ) return super().read_variables(data_source) - def write_variables(self, data_source: Union[str, PathLike, IO], variables: VariableList): + def write_variables(self, data_source: str | PathLike | IO, variables: VariableList): try: super().write_variables(data_source, variables) except FastXPathEvalError as err: # Trying to help... raise FastXPathEvalError( - err.args[0] + ' : self.path_separator is "%s". It is correct?' % self.path_separator + err.args[0] + f' : self.path_separator is "{self.path_separator}". It is correct?' ) @@ -107,10 +109,8 @@ def __init__(self, path_separator): def get_variable_name(self, xpath: str) -> str: path_components = xpath.split("/") - name = self.path_separator.join(path_components) - return name + return self.path_separator.join(path_components) # Name def get_xpath(self, var_name: str) -> str: path_components = var_name.split(self.path_separator) - xpath = "/".join(path_components) - return xpath + return "/".join(path_components) # Xpath diff --git a/src/fastoad/model_base/__init__.py b/src/fastoad/model_base/__init__.py index bab98f0a5..70d2b9586 100644 --- a/src/fastoad/model_base/__init__.py +++ b/src/fastoad/model_base/__init__.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# flake8: noqa from .atmosphere import Atmosphere, AtmosphereSI from .flight_point import FlightPoint + +__all__ = ["Atmosphere", "AtmosphereSI", "FlightPoint"] diff --git a/src/fastoad/model_base/atmosphere.py b/src/fastoad/model_base/atmosphere.py index 16214eda5..33d7dcb3b 100644 --- a/src/fastoad/model_base/atmosphere.py +++ b/src/fastoad/model_base/atmosphere.py @@ -14,8 +14,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + +from collections.abc import Sequence from numbers import Number -from typing import Sequence, Union import numpy as np from deprecated import deprecated @@ -61,8 +63,9 @@ class Atmosphere: # pylint: disable=too-many-instance-attributes # Needed for avoiding redoing computations def __init__( self, - altitude: Union[float, Sequence[float]], + altitude: float | Sequence[float], delta_t: float = 0.0, + *, altitude_in_feet: bool = True, ): """ @@ -95,7 +98,7 @@ def __init__( self._true_airspeed = None self._unitary_reynolds = None - def get_altitude(self, altitude_in_feet: bool = True) -> Union[float, Sequence[float]]: + def get_altitude(self, *, altitude_in_feet: bool = True) -> float | Sequence[float]: """ :param altitude_in_feet: if True, altitude is returned in feet. Otherwise, it is returned in meters @@ -106,16 +109,16 @@ def get_altitude(self, altitude_in_feet: bool = True) -> Union[float, Sequence[f return self._return_value(self._altitude) @property - def delta_t(self) -> Union[float, Sequence[float]]: + def delta_t(self) -> float | Sequence[float]: """Temperature increment applied to whole temperature profile.""" return self._delta_t @delta_t.setter - def delta_t(self, value: Union[float, Sequence[float]]): + def delta_t(self, value: float | Sequence[float]): self._delta_t = np.asarray(value) @property - def temperature(self) -> Union[float, Sequence[float]]: + def temperature(self) -> float | Sequence[float]: """Temperature in K.""" if self._temperature is None: self._temperature = np.zeros(self._altitude.shape) @@ -126,7 +129,7 @@ def temperature(self) -> Union[float, Sequence[float]]: return self._return_value(self._temperature) @property - def pressure(self) -> Union[float, Sequence[float]]: + def pressure(self) -> float | Sequence[float]: """Pressure in Pa.""" if self._pressure is None: self._pressure = np.zeros(self._altitude.shape) @@ -140,21 +143,21 @@ def pressure(self) -> Union[float, Sequence[float]]: return self._return_value(self._pressure) @property - def density(self) -> Union[float, Sequence[float]]: + def density(self) -> float | Sequence[float]: """Density in kg/m3.""" if self._density is None: self._density = self.pressure / AIR_GAS_CONSTANT / self.temperature return self._return_value(self._density) @property - def speed_of_sound(self) -> Union[float, Sequence[float]]: + def speed_of_sound(self) -> float | Sequence[float]: """Speed of sound in m/s.""" if self._speed_of_sound is None: self._speed_of_sound = (1.4 * AIR_GAS_CONSTANT * self.temperature) ** 0.5 return self._return_value(self._speed_of_sound) @property - def kinematic_viscosity(self) -> Union[float, Sequence[float]]: + def kinematic_viscosity(self) -> float | Sequence[float]: """Kinematic viscosity in m2/s.""" if self._kinematic_viscosity is None: self._kinematic_viscosity = ( @@ -164,14 +167,14 @@ def kinematic_viscosity(self) -> Union[float, Sequence[float]]: return self._return_value(self._kinematic_viscosity) @property - def mach(self) -> Union[float, Sequence[float]]: + def mach(self) -> float | Sequence[float]: """Mach number.""" if self._mach is None and self.true_airspeed is not None: self._mach = self.true_airspeed / self.speed_of_sound return self._return_value(self._mach) @property - def true_airspeed(self) -> Union[float, Sequence[float]]: + def true_airspeed(self) -> float | Sequence[float]: """True airspeed (TAS) in m/s.""" # Dev note: true_airspeed is the "hub". Other speed values will be calculated # from this true_airspeed. @@ -188,7 +191,7 @@ def true_airspeed(self) -> Union[float, Sequence[float]]: return self._return_value(self._true_airspeed) @property - def equivalent_airspeed(self) -> Union[float, Sequence[float]]: + def equivalent_airspeed(self) -> float | Sequence[float]: """Equivalent airspeed (EAS) in m/s.""" if self._equivalent_airspeed is None and self.true_airspeed is not None: sea_level = Atmosphere(0) @@ -199,29 +202,29 @@ def equivalent_airspeed(self) -> Union[float, Sequence[float]]: return self._return_value(self._equivalent_airspeed) @property - def unitary_reynolds(self) -> Union[float, Sequence[float]]: + def unitary_reynolds(self) -> float | Sequence[float]: """Unitary Reynolds number in 1/m.""" if self._unitary_reynolds is None and self.true_airspeed is not None: self._unitary_reynolds = self.true_airspeed / self.kinematic_viscosity return self._return_value(self._unitary_reynolds) @mach.setter - def mach(self, value: Union[float, Sequence[float]]): + def mach(self, value: float | Sequence[float]): self._reset_speeds() self._mach = np.asarray(value) @true_airspeed.setter - def true_airspeed(self, value: Union[float, Sequence[float]]): + def true_airspeed(self, value: float | Sequence[float]): self._reset_speeds() self._true_airspeed = np.asarray(value) @equivalent_airspeed.setter - def equivalent_airspeed(self, value: Union[float, Sequence[float]]): + def equivalent_airspeed(self, value: float | Sequence[float]): self._reset_speeds() self._equivalent_airspeed = np.asarray(value) @unitary_reynolds.setter - def unitary_reynolds(self, value: Union[float, Sequence[float]]): + def unitary_reynolds(self, value: float | Sequence[float]): self._reset_speeds() self._unitary_reynolds = np.asarray(value) @@ -253,7 +256,7 @@ def _return_value(self, value): class AtmosphereSI(Atmosphere): """Same as :class:`Atmosphere` except that altitudes are always in meters.""" - def __init__(self, altitude: Union[float, Sequence[float]], delta_t: float = 0.0): + def __init__(self, altitude: float | Sequence[float], delta_t: float = 0.0): """ :param altitude: altitude in meters :param delta_t: temperature increment (°C) applied to whole temperature profile diff --git a/src/fastoad/model_base/flight_point.py b/src/fastoad/model_base/flight_point.py index 11e30dcc0..30f267726 100644 --- a/src/fastoad/model_base/flight_point.py +++ b/src/fastoad/model_base/flight_point.py @@ -13,10 +13,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + +from collections.abc import Mapping, Sequence from copy import deepcopy from dataclasses import asdict, dataclass, field, fields from numbers import Number -from typing import Any, Dict, List, Mapping, Optional, Sequence, Union +from typing import Any, ClassVar import pandas as pd @@ -32,8 +35,8 @@ class _FieldDescriptor: Class to be used as dataclass field metadata. """ - is_cumulative: Optional[bool] = False - unit: Optional[str] = None + is_cumulative: bool | None = False + unit: str | None = None @dataclass @@ -191,12 +194,12 @@ class FlightPoint: name: str = field(default=None, metadata={FIELD_DESCRIPTOR: _FieldDescriptor()}) # Will store field metadata when needed. Must be accessed through _get_field_descriptors() - __field_descriptors = {} + __field_descriptors: ClassVar[dict] = {} def __post_init__(self): self._relative_parameters = {"ground_distance", "time"} - def set_as_relative(self, field_names: Union[Sequence[str], str]): + def set_as_relative(self, field_names: Sequence[str] | str): """ Makes that values for given field_names will be considered as relative when calling :meth:`make_absolute`. @@ -208,7 +211,7 @@ def set_as_relative(self, field_names: Union[Sequence[str], str]): else: self._relative_parameters |= set(field_names) - def set_as_absolute(self, field_names: Union[Sequence[str], str]): + def set_as_absolute(self, field_names: Sequence[str] | str): """ Makes that values for given field_names will be considered as absolute when calling :meth:`make_absolute`. @@ -229,7 +232,7 @@ def is_relative(self, field_name) -> bool: """ return field_name in self._relative_parameters - def make_absolute(self, reference_point: "FlightPoint") -> "FlightPoint": + def make_absolute(self, reference_point: FlightPoint) -> FlightPoint: """ Computes a copy flight point where no field is relative. @@ -275,7 +278,7 @@ def get_units(cls) -> dict: } @classmethod - def get_unit(cls, field_name) -> Optional[str]: + def get_unit(cls, field_name) -> str: """ Returns unit for asked field. @@ -284,7 +287,7 @@ def get_unit(cls, field_name) -> Optional[str]: return cls._get_field_descriptor(field_name).unit @classmethod - def is_cumulative(cls, field_name) -> Optional[bool]: + def is_cumulative(cls, field_name) -> bool | None: """ Tells if asked field is cumulative (sums up during mission). @@ -293,7 +296,7 @@ def is_cumulative(cls, field_name) -> Optional[bool]: return cls._get_field_descriptor(field_name).is_cumulative @classmethod - def create(cls, data: Mapping) -> "FlightPoint": + def create(cls, data: Mapping) -> FlightPoint: """ Instantiate FlightPoint from provided data. @@ -305,7 +308,7 @@ def create(cls, data: Mapping) -> "FlightPoint": return cls(**dict(data)) @classmethod - def create_list(cls, data: pd.DataFrame) -> List["FlightPoint"]: + def create_list(cls, data: pd.DataFrame) -> list[FlightPoint]: """ Creates a list of FlightPoint instances from provided DataFrame. @@ -321,6 +324,7 @@ def add_field( annotation_type=float, default_value: Any = None, unit="-", + *, is_cumulative=False, ): """ @@ -366,7 +370,7 @@ def remove_field(cls, name): dataclass(cls) @classmethod - def _get_field_descriptors(cls) -> Dict[str, _FieldDescriptor]: + def _get_field_descriptors(cls) -> dict[str, _FieldDescriptor]: """ Uses this method instead of accessing cls.__field_descriptors to ensure it will always be correctly populated. diff --git a/src/fastoad/model_base/openmdao/group.py b/src/fastoad/model_base/openmdao/group.py index c63724abd..32e99b677 100644 --- a/src/fastoad/model_base/openmdao/group.py +++ b/src/fastoad/model_base/openmdao/group.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + import abc import openmdao.api as om @@ -51,11 +53,12 @@ class MyGroup( def __init_subclass__( cls, - use_solvers_by_default: bool = True, default_linear_solver: str = "om.DirectSolver", default_nonlinear_solver: str = "om.NonlinearBlockGS", - default_linear_options: dict = None, - default_nonlinear_options: dict = None, + default_linear_options: dict | None = None, + default_nonlinear_options: dict | None = None, + *, + use_solvers_by_default: bool = True, ): cls.use_solvers_by_default = use_solvers_by_default cls.default_linear_solver = default_linear_solver diff --git a/src/fastoad/model_base/propulsion.py b/src/fastoad/model_base/propulsion.py index c67dda4f1..8c3a4e0fe 100644 --- a/src/fastoad/model_base/propulsion.py +++ b/src/fastoad/model_base/propulsion.py @@ -1,6 +1,7 @@ """ Base classes for propulsion components. """ + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2021 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -13,9 +14,9 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations from abc import ABC, abstractmethod -from typing import Union import numpy as np import pandas as pd @@ -48,7 +49,7 @@ class IPropulsion(ABC): """ @abstractmethod - def compute_flight_points(self, flight_points: Union[FlightPoint, pd.DataFrame]): + def compute_flight_points(self, flight_points: FlightPoint | pd.DataFrame): """ Computes Specific Fuel Consumption according to provided conditions. @@ -213,7 +214,7 @@ def __init__(self, engine: IPropulsion, engine_count): self.engine = engine self.engine_count = engine_count - def compute_flight_points(self, flight_points: Union[FlightPoint, pd.DataFrame]): + def compute_flight_points(self, flight_points: FlightPoint | pd.DataFrame): if flight_points.thrust is not None: flight_points.thrust = flight_points.thrust / self.engine_count diff --git a/src/fastoad/models/performances/mission/base.py b/src/fastoad/models/performances/mission/base.py index 6ffaf7096..126e7141e 100644 --- a/src/fastoad/models/performances/mission/base.py +++ b/src/fastoad/models/performances/mission/base.py @@ -12,10 +12,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + from abc import ABC, abstractmethod from copy import deepcopy from dataclasses import dataclass, field -from typing import Dict, List, Optional +from typing import ClassVar import pandas as pd @@ -58,11 +60,11 @@ class FlightSequence(IFlightPart): #: Consumed mass between sequence start and target mass, if any defined consumed_mass_before_input_weight: float = field(default=0.0, init=False) - #: List of flight points for each part of the sequence, obtained after + #: list of flight points for each part of the sequence, obtained after # running :meth:`compute_from` - part_flight_points: List[pd.DataFrame] = field(default_factory=list, init=False) + part_flight_points: list[pd.DataFrame] = field(default_factory=list, init=False) - _sequence: List[IFlightPart] = field(default_factory=list, init=False) + _sequence: list[IFlightPart] = field(default_factory=list, init=False) _target: FlightPoint = None @@ -118,9 +120,10 @@ def compute_from(self, start: FlightPoint) -> pd.DataFrame: if self.part_flight_points: return pd.concat(self.part_flight_points).reset_index(drop=True) + return None @property - def target(self) -> Optional[FlightPoint]: + def target(self) -> FlightPoint | None: """Target of the last element of current sequence.""" if hasattr(self, "_sequence") and len(self._sequence) > 0: return self._sequence[-1].target @@ -189,7 +192,7 @@ class RegisterElement: """ _base_class = object - _keyword_vs_implementation: Dict[str, type] = {} + _keyword_vs_implementation: ClassVar[dict[str, type]] = {} @classmethod def __init_subclass__(cls, *, base_class=object): @@ -220,7 +223,7 @@ def _add_element(cls, keyword: str, element_class: type): ) @classmethod - def get_class(cls, keyword: str) -> Optional[type]: + def get_class(cls, keyword: str) -> type | None: """ Provides the element implementation for provided name. @@ -236,7 +239,7 @@ def get_class(cls, keyword: str) -> Optional[type]: return element_class @classmethod - def get_classes(cls) -> Dict[str, type]: + def get_classes(cls) -> dict[str, type]: """ :return: dict that associates keywords to their registered class. diff --git a/src/fastoad/models/performances/mission/mission.py b/src/fastoad/models/performances/mission/mission.py index c714734ce..b562d7047 100644 --- a/src/fastoad/models/performances/mission/mission.py +++ b/src/fastoad/models/performances/mission/mission.py @@ -13,9 +13,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + from copy import deepcopy from dataclasses import dataclass, field -from typing import Optional import pandas as pd from scipy.optimize import root_scalar @@ -36,19 +37,19 @@ class Mission(FlightSequence): """ #: If not None, the mission will adjust the first - target_fuel_consumption: Optional[float] = None + target_fuel_consumption: float | None = None - reserve_ratio: Optional[float] = 0.0 - reserve_base_route_name: Optional[str] = None + reserve_ratio: float | None = 0.0 + reserve_base_route_name: str | None = None #: Accuracy on actual consumed fuel for the solver. In kg fuel_accuracy: float = 10.0 - _flight_points: Optional[pd.DataFrame] = field(init=False, default=None) - _first_cruise_segment: Optional[CruiseSegment] = field(init=False, default=None) + _flight_points: pd.DataFrame | None = field(init=False, default=None) + _first_cruise_segment: CruiseSegment | None = field(init=False, default=None) @property - def consumed_fuel(self) -> Optional[float]: + def consumed_fuel(self) -> float | None: """Total consumed fuel for the whole mission (after launching :meth:`compute_from`)""" if self._flight_points is None: return None @@ -60,9 +61,7 @@ def first_route(self) -> RangedRoute: """First route in the mission.""" return self._get_first_route_in_sequence(self) - def _get_first_route_in_sequence( - self, flight_sequence: FlightSequence - ) -> Optional[RangedRoute]: + def _get_first_route_in_sequence(self, flight_sequence: FlightSequence) -> RangedRoute | None: for part in flight_sequence: if isinstance(part, RangedRoute): return part @@ -93,7 +92,9 @@ def get_reserve_fuel(self): return reserve_points.iloc[0].mass - reserve_points.iloc[-1].mass def _get_consumed_mass_in_route(self, route_name: str) -> float: - route = [part for part in self if part.name == route_name][0] + route = next( + part for part in self if part.name == route_name + ) # return only the first element route_idx = self.index(route) route_points = self.part_flight_points[route_idx] return route_points.mass.iloc[0] - route_points.mass.iloc[-1] diff --git a/src/fastoad/models/performances/mission/mission_definition/mission_builder/__init__.py b/src/fastoad/models/performances/mission/mission_definition/mission_builder/__init__.py index 92d03116c..3ed0110b0 100644 --- a/src/fastoad/models/performances/mission/mission_definition/mission_builder/__init__.py +++ b/src/fastoad/models/performances/mission/mission_definition/mission_builder/__init__.py @@ -12,4 +12,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from .mission_builder import MissionBuilder # noqa: F401 +from .mission_builder import MissionBuilder + +__all__ = ["MissionBuilder"] diff --git a/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py b/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py index 2a0b8e044..22839a606 100644 --- a/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py +++ b/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py @@ -12,9 +12,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + +from collections.abc import Iterable, Mapping from dataclasses import InitVar, dataclass, field from numbers import Number -from typing import Iterable, Mapping, Optional, Tuple, Union import numpy as np from openmdao import api as om @@ -40,10 +42,10 @@ class InputDefinition: parameter_name: str #: Value, matching `input_unit`. At instantiation, it can also be the variable name. - input_value: Optional[Union[Number, Iterable, str]] + input_value: Number | Iterable | str | None #: Unit used for self.input_value. - input_unit: Optional[str] = None + input_unit: str | None = None #: Default value. Used if value is a variable name. default_value: Number = np.nan @@ -56,10 +58,10 @@ class InputDefinition: #: Unit used for self.value. Automatically determined from self.parameter_name, #: mainly from unit definition for FlightPoint class. - output_unit: Optional[str] = field(default=None, init=False, repr=False) + output_unit: str | None = field(default=None, init=False, repr=False) #: Value of the "shape" openMDAO flag for input declaration. - shape: Optional[Tuple[int]] = None + shape: tuple[int] | None = None #: Value of the "shape_by_conn" openMDAO flag for input declaration. shape_by_conn: bool = False @@ -68,17 +70,17 @@ class InputDefinition: prefix: str = "" #: Used only for tests - variable_name_: InitVar[Optional[str]] = None + variable_name_: InitVar[str | None] = None #: Used only for tests - use_opposite_: InitVar[Optional[bool]] = None + use_opposite_: InitVar[bool | None] = None #: True if the opposite value should be used, if input is defined by a variable. _use_opposite: bool = field(default=False, init=False, repr=True) - _variable_name: Optional[str] = field(default=None, init=False, repr=True) + _variable_name: str | None = field(default=None, init=False, repr=True) - def __post_init__(self, variable_name_: Optional[str], use_opposite_: Optional[bool]): + def __post_init__(self, variable_name_: str | None, use_opposite_: bool | None): if self.parameter_name.startswith("delta_"): self.is_relative = True self.parameter_name = self.parameter_name[6:] @@ -114,7 +116,9 @@ def __post_init__(self, variable_name_: Optional[str], use_opposite_: Optional[b self._use_opposite = use_opposite_ @classmethod - def from_dict(cls, parameter_name, definition_dict: dict, part_identifier=None, prefix=None): + def from_dict( + cls, parameter_name, definition_dict: dict, part_identifier=None, prefix=None + ) -> InputDefinition | None: """ Instantiates InputDefinition from definition_dict. @@ -130,7 +134,7 @@ def from_dict(cls, parameter_name, definition_dict: dict, part_identifier=None, if "value" not in definition_dict: return None - input_def = cls( + return cls( # input_def parameter_name, definition_dict["value"], input_unit=definition_dict.get("unit"), @@ -139,7 +143,6 @@ def from_dict(cls, parameter_name, definition_dict: dict, part_identifier=None, part_identifier=part_identifier, prefix=prefix, ) - return input_def @property def value(self): @@ -155,12 +158,12 @@ def value(self): return self.input_value @property - def variable_name(self): # noqa: F811 # the variable_name field is an InitVar. + def variable_name(self): # The variable_name field is an InitVar. """Associated variable name.""" return self._variable_name @variable_name.setter - def variable_name(self, var_name: Optional[str]): + def variable_name(self, var_name: str | None): if isinstance(var_name, str): self._use_opposite = var_name.startswith("-") var_name = var_name.strip("- ") @@ -214,7 +217,7 @@ def set_variable_value(self, inputs: Mapping): else: self.input_value = value - def get_input_definition(self) -> Optional[Variable]: + def get_input_definition(self) -> Variable | None: """ Provides information for input definition in OpenMDAO. diff --git a/src/fastoad/models/performances/mission/mission_definition/mission_builder/mission_builder.py b/src/fastoad/models/performances/mission/mission_definition/mission_builder/mission_builder.py index 9678acaf5..31e46a8da 100644 --- a/src/fastoad/models/performances/mission/mission_definition/mission_builder/mission_builder.py +++ b/src/fastoad/models/performances/mission/mission_definition/mission_builder/mission_builder.py @@ -12,11 +12,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + from collections import ChainMap +from collections.abc import Mapping from copy import deepcopy from dataclasses import fields from os import PathLike -from typing import Dict, List, Mapping, Optional, Union import pandas as pd from deprecated import deprecated @@ -80,11 +82,11 @@ class MissionBuilder: def __init__( self, - mission_definition: Union[str, PathLike, MissionDefinition], + mission_definition: str | PathLike | MissionDefinition, *, propulsion: IPropulsion = None, - reference_area: float = None, - mission_name: Optional[str] = None, + reference_area: float | None = None, + mission_name: str | None = None, variable_prefix: str = "data:mission", ): """ @@ -96,7 +98,7 @@ def __init__( :param mission_name: name of chosen mission, if already decided. :param variable_prefix: prefix for auto-generated variable names. """ - self._structure_builders: Dict[str, AbstractStructureBuilder] = {} + self._structure_builders: dict[str, AbstractStructureBuilder] = {} self._variable_prefix: str = variable_prefix @@ -117,7 +119,7 @@ def definition(self) -> MissionDefinition: return self._definition @definition.setter - def definition(self, mission_definition: Union[str, PathLike, MissionDefinition]): + def definition(self, mission_definition: str | PathLike | MissionDefinition): if isinstance(mission_definition, MissionDefinition): self._definition = mission_definition else: @@ -172,7 +174,7 @@ def variable_prefix(self, value): self._variable_prefix = value self._update_structure_builders() - def build(self, inputs: Optional[Mapping] = None, mission_name: str = None) -> Mission: + def build(self, inputs: Mapping | None = None, mission_name: str | None = None) -> Mission: """ Builds the flight sequence from definition file. @@ -191,10 +193,9 @@ def build(self, inputs: Optional[Mapping] = None, mission_name: str = None) -> M for input_def in self._structure_builders[mission_name].get_input_definitions(): input_def.set_variable_value(inputs) - mission = self._build_mission(self._structure_builders[mission_name].structure) - return mission + return self._build_mission(self._structure_builders[mission_name].structure) # Mission - def get_route_names(self, mission_name: str = None) -> List[str]: + def get_route_names(self, mission_name: str | None = None) -> list[str]: """ :param mission_name: @@ -204,13 +205,11 @@ def get_route_names(self, mission_name: str = None) -> List[str]: mission_name = self.mission_name mission_parts = self.definition[MISSION_DEFINITION_TAG][mission_name][PARTS_TAG] - route_names = [part[ROUTE_TAG] for part in mission_parts if ROUTE_TAG in part] - - return route_names + return [part[ROUTE_TAG] for part in mission_parts if ROUTE_TAG in part] # Rooute names def get_route_ranges( - self, inputs: Optional[Mapping] = None, mission_name: str = None - ) -> List[float]: + self, inputs: Mapping | None = None, mission_name: str | None = None + ) -> list[float]: """ :param inputs: if provided, any input parameter that is a string which matches @@ -225,7 +224,7 @@ def get_route_ranges( routes = self.build(inputs, mission_name) return [route.flight_distance for route in routes if isinstance(route, RangedRoute)] - def get_reserve(self, flight_points: pd.DataFrame, mission_name: str = None) -> float: + def get_reserve(self, flight_points: pd.DataFrame, mission_name: str | None = None) -> float: """ Computes the reserve fuel according to definition in mission input file. @@ -279,13 +278,13 @@ def get_unique_mission_name(self) -> str: file """ if len(self._structure_builders) == 1: - return list(self._structure_builders.keys())[0] + return next(iter(self._structure_builders.keys())) # return only the first item raise FastMissionFileMissingMissionNameError( "Mission name must be specified if several missions are defined in mission file." ) - def get_input_weight_variable_name(self, mission_name: str = None) -> Optional[str]: + def get_input_weight_variable_name(self, mission_name: str | None = None) -> str | None: """ Search the mission structure for a segment that has a target absolute mass defined and returns the associated variable name. @@ -348,7 +347,7 @@ def _build_mission(self, mission_structure: dict) -> Mission: return mission - def _build_route(self, route_structure: dict, kwargs: Mapping = None): + def _build_route(self, route_structure: dict, kwargs: Mapping | None = None): """ Builds route instance. @@ -401,7 +400,7 @@ def _build_route(self, route_structure: dict, kwargs: Mapping = None): route.name = route_structure[NAME_TAG] return route - def _build_phase(self, phase_structure: Mapping, kwargs: Mapping = None): + def _build_phase(self, phase_structure: Mapping, kwargs: Mapping | None = None): """ Builds phase instance @@ -470,8 +469,7 @@ def _build_segment(self, segment_definition: Mapping, kwargs: dict) -> AbstractF class_field.name for class_field in fields(segment_class) if class_field.init ] part_kwargs = {key: value for key, value in part_kwargs.items() if key in input_field_names} - segment = segment_class(**part_kwargs) - return segment + return segment_class(**part_kwargs) # Segment @staticmethod def _replace_input_definitions_by_values(part_kwargs): @@ -483,23 +481,21 @@ def _get_mission_part_structures(self, mission_name: str): return self._structure_builders[mission_name].structure[PARTS_TAG] def _get_part_kwargs( - self, kwargs: Mapping, phase_structure: Mapping, specific_exclude: List[str] = None + self, kwargs: Mapping, phase_structure: Mapping, specific_exclude: list[str] | None = None ): part_kwargs = {} if kwargs is not None: part_kwargs.update(kwargs) if specific_exclude is None: specific_exclude = [] - exclude = [NAME_TAG, PARTS_TAG, TYPE_TAG] + specific_exclude + exclude = [NAME_TAG, PARTS_TAG, TYPE_TAG, *specific_exclude] part_kwargs.update( {name: value for name, value in phase_structure.items() if name not in exclude} ) self._replace_input_definitions_by_values(part_kwargs) return part_kwargs - def _get_input_weight_variable_name_in_structure( - self, structure: Union[list, dict] - ) -> Optional[str]: + def _get_input_weight_variable_name_in_structure(self, structure: list | dict) -> str | None: for tag in [PARTS_TAG, CLIMB_PARTS_TAG, DESCENT_PARTS_TAG]: if tag in structure: return self._get_input_weight_variable_name_in_structure(structure[tag]) diff --git a/src/fastoad/models/performances/mission/mission_definition/mission_builder/structure_builders.py b/src/fastoad/models/performances/mission/mission_definition/mission_builder/structure_builders.py index 1bfa5107c..0f902f2e9 100644 --- a/src/fastoad/models/performances/mission/mission_definition/mission_builder/structure_builders.py +++ b/src/fastoad/models/performances/mission/mission_definition/mission_builder/structure_builders.py @@ -18,11 +18,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import typing from abc import ABC, abstractmethod from copy import deepcopy -from dataclasses import InitVar, dataclass, field, fields +from dataclasses import InitVar, dataclass, field from itertools import chain -from typing import List, Tuple +from typing import ClassVar, get_type_hints import numpy as np @@ -69,8 +70,8 @@ class AbstractStructureBuilder(ABC): _structure: dict = field(default=None, init=False) - _input_definitions: List[InputDefinition] = field(default_factory=list, init=False) - _builders: List[Tuple["AbstractStructureBuilder", dict]] = field( + _input_definitions: list[InputDefinition] = field(default_factory=list, init=False) + _builders: list[tuple["AbstractStructureBuilder", dict]] = field( default_factory=list, init=False ) @@ -97,8 +98,8 @@ def structure(self) -> dict: self._builders = [] # Builders have been used and can be forgotten. return self._structure - def get_input_definitions(self) -> List[InputDefinition]: - """List of InputDefinition instances in the structure.""" + def get_input_definitions(self) -> list[InputDefinition]: + """list of InputDefinition instances in the structure.""" return self._input_definitions + list( chain(*[builder.get_input_definitions() for builder, _ in self._builders]) ) @@ -154,7 +155,7 @@ def qualified_name(self): def _parse_inputs( self, structure, - input_definitions: List[InputDefinition], + input_definitions: list[InputDefinition], parent=None, part_identifier="", segment_class=None, @@ -227,11 +228,40 @@ def _parse_inputs( @staticmethod def _is_shape_by_conn(key, segment_class) -> bool: """ - Here variables that are expected to be arrays or lists in the provided segment class are - attributed the "shape_by_conn=True" property. + Check if a field in `segment_class` is a list or NumPy array. + Works with `from __future__ import annotations` and handles ClassVar properly. """ - segment_fields = [fld for fld in fields(segment_class) if fld.name == key] - return len(segment_fields) == 1 and issubclass(segment_fields[0].type, (list, np.ndarray)) + # Retrieve type annotations using get_type_hints(). + # WHY? Because `from __future__ import annotations` makes type hints **lazy** (stored as strings), + # and get_type_hints() converts them back into real types. + # TODO https://peps.python.org/pep-0649/ will simplify all this + try: + type_hints = get_type_hints(segment_class) + if key not in type_hints: + return False + + field_type = type_hints[key] + + # Handle ClassVar if present, issubclass() + # does not accept ClassVar as a valid type + origin = typing.get_origin(field_type) + if origin is ClassVar: + field_type = typing.get_args(field_type)[0] + origin = typing.get_origin(field_type) + + # Check for list + if origin is not None and origin is list: + return True + + # Direct type check for list or ndarray + if isinstance(field_type, type): + try: + return issubclass(field_type, (list, np.ndarray)) + except TypeError: + pass + return False + except (TypeError, NameError): + return False def _process_polar(self, structure): """ diff --git a/src/fastoad/models/performances/mission/mission_definition/schema.py b/src/fastoad/models/performances/mission/mission_definition/schema.py index a9ff170dd..dd5a5b1fa 100644 --- a/src/fastoad/models/performances/mission/mission_definition/schema.py +++ b/src/fastoad/models/performances/mission/mission_definition/schema.py @@ -1,6 +1,7 @@ """ Schema for mission definition files. """ + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2022 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -13,16 +14,18 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations import json from collections import OrderedDict from os import PathLike -from typing import Union +from pathlib import Path from ensure import Ensure from jsonschema import validate from ruamel.yaml import YAML +from fastoad._utils.files import as_path from fastoad._utils.resource_management.contents import PackageReader from . import resources @@ -45,7 +48,7 @@ class MissionDefinition(OrderedDict): - def __init__(self, file_path: Union[str, PathLike] = None): + def __init__(self, file_path: str | PathLike | None = None): """ Class for reading a mission definition from a YAML file. @@ -58,7 +61,7 @@ def __init__(self, file_path: Union[str, PathLike] = None): if file_path: self.load(file_path) - def load(self, file_path: Union[str, PathLike]): + def load(self, file_path: str | PathLike): """ Loads a mission definition from provided file path. @@ -69,7 +72,9 @@ def load(self, file_path: Union[str, PathLike]): self.clear() yaml = YAML(typ="safe", pure=True) - with open(file_path) as yaml_file: + file_path = as_path(file_path) + + with Path.open(file_path) as yaml_file: data = yaml.load(yaml_file) with PackageReader(resources).open_text(JSON_SCHEMA_NAME) as json_file: @@ -129,7 +134,7 @@ def _validate(cls, content: dict): Ensure(part_type).equals(RESERVE_TAG) @classmethod - def _convert_none_values(cls, struct: Union[dict, list]): + def _convert_none_values(cls, struct: dict | list): """ Recursively transforms any None value in struct to "~" """ diff --git a/src/fastoad/models/performances/mission/mission_definition/tests/test_schema.py b/src/fastoad/models/performances/mission/mission_definition/tests/test_schema.py index 0c5bb4c8b..3469f44ef 100644 --- a/src/fastoad/models/performances/mission/mission_definition/tests/test_schema.py +++ b/src/fastoad/models/performances/mission/mission_definition/tests/test_schema.py @@ -38,11 +38,10 @@ def _to_ordered_dict(item): for key, value in ordered_dict.items(): ordered_dict[key] = _to_ordered_dict(value) return ordered_dict - else: - if isinstance(item, list): - for i, value in enumerate(item): - item[i] = _to_ordered_dict(value) - return item + if isinstance(item, list): + for i, value in enumerate(item): + item[i] = _to_ordered_dict(value) + return item def _get_expected_dict(): diff --git a/src/fastoad/models/performances/mission/openmdao/base.py b/src/fastoad/models/performances/mission/openmdao/base.py index 3b5d27902..8e9ffbc5b 100644 --- a/src/fastoad/models/performances/mission/openmdao/base.py +++ b/src/fastoad/models/performances/mission/openmdao/base.py @@ -14,10 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + from abc import ABCMeta from enum import Enum from os import PathLike -from typing import Optional, Union from openmdao.core.system import System @@ -78,7 +79,7 @@ class BaseMissionComp(System, metaclass=ABCMeta): def __init__(self, **kwargs): # These attributes will be updated automatically wrt to option values # (see method '_update_mission_wrapper') - self._mission_wrapper: Optional[MissionWrapper] = None + self._mission_wrapper: MissionWrapper | None = None self._name_provider = None super().__init__(**kwargs) @@ -141,7 +142,7 @@ def mission_name(self) -> str: return self._mission_wrapper.mission_name @property - def first_route_name(self) -> Optional[str]: + def first_route_name(self) -> str | None: """The name of first route (and normally the main one) in the mission.""" try: return self._mission_wrapper.get_route_names()[0] @@ -150,7 +151,7 @@ def first_route_name(self) -> Optional[str]: @staticmethod def get_mission_definition( - mission_file_path: Optional[Union[str, PathLike, MissionDefinition]], + mission_file_path: str | PathLike | MissionDefinition | None, ) -> MissionDefinition: """ @@ -217,7 +218,7 @@ def _update_mission_wrapper(self, name, value): except FastMissionFileMissingMissionNameError: return - def _get_variable_name_provider(self) -> Optional[type]: + def _get_variable_name_provider(self) -> type | None: """Factory that returns an enum class that provide mission variable names.""" def get_variable_name(suffix): diff --git a/src/fastoad/models/performances/mission/openmdao/mission_run.py b/src/fastoad/models/performances/mission/openmdao/mission_run.py index 193e27e47..e07d97e30 100644 --- a/src/fastoad/models/performances/mission/openmdao/mission_run.py +++ b/src/fastoad/models/performances/mission/openmdao/mission_run.py @@ -11,9 +11,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + import logging from os import PathLike -from typing import Optional import numpy as np from openmdao import api as om @@ -129,7 +130,7 @@ def _compute_outputs(self, outputs, flight_points): self._mission_wrapper.consumed_fuel_before_input_weight ) - def get_engine_wrapper(self) -> Optional[IOMPropulsionWrapper]: + def get_engine_wrapper(self) -> IOMPropulsionWrapper | None: """ Overloading this method allows to define the engine without relying on the propulsion option. diff --git a/src/fastoad/models/performances/mission/openmdao/mission_wrapper.py b/src/fastoad/models/performances/mission/openmdao/mission_wrapper.py index 9b8b115a4..10dbb3291 100644 --- a/src/fastoad/models/performances/mission/openmdao/mission_wrapper.py +++ b/src/fastoad/models/performances/mission/openmdao/mission_wrapper.py @@ -15,8 +15,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + from os import PathLike -from typing import Dict, Optional, Tuple, Union import numpy as np import openmdao.api as om @@ -51,11 +52,11 @@ class MissionWrapper(MissionBuilder): def __init__( self, - mission_definition: Union[str, PathLike, MissionDefinition], + mission_definition: str | PathLike | MissionDefinition, *, propulsion: IPropulsion = None, - reference_area: float = None, - mission_name: Optional[str] = None, + reference_area: float | None = None, + mission_name: str | None = None, variable_prefix: str = "data:mission", force_all_block_fuel_usage: bool = False, ): @@ -174,7 +175,7 @@ def get_reserve_variable_name(self) -> str: """ return f"{self.variable_prefix}:{self.mission_name}:reserve:fuel" - def _identify_outputs(self) -> Dict[str, Tuple[str, str]]: + def _identify_outputs(self) -> dict[str, tuple[str, str]]: """ Builds names of OpenMDAO outputs from names of mission, route and phases. diff --git a/src/fastoad/models/performances/mission/openmdao/payload_range.py b/src/fastoad/models/performances/mission/openmdao/payload_range.py index 0d1bbd3fa..1c54a89d2 100644 --- a/src/fastoad/models/performances/mission/openmdao/payload_range.py +++ b/src/fastoad/models/performances/mission/openmdao/payload_range.py @@ -13,7 +13,6 @@ # along with this program. If not, see . from dataclasses import dataclass -from typing import Dict import numpy as np import openmdao.api as om @@ -250,7 +249,7 @@ def _add_payload_range_grid_group(self): return group def _add_mission_runs( - self, group: om.Group, nb_missions: int, input_var_connections: Dict[str, str], mux_name + self, group: om.Group, nb_missions: int, input_var_connections: dict[str, str], mux_name ): """Adds MissionRun components to the provided group.""" @@ -384,21 +383,19 @@ def _calculate_block_fuel_at_max_takeoff_weight(self, inputs, payload): - payload - inputs[self.options["OWE_variable"]].item() ) - block_fuel_at_max_takeoff_weight = ( + return ( # block_fuel_at_max_takeoff_weight fuel_at_takeoff + inputs[self.name_provider.CONSUMED_FUEL_BEFORE_INPUT_WEIGHT.value].item() ) - return block_fuel_at_max_takeoff_weight def _calculate_takeoff_weight_at_max_fuel_weight(self, inputs, payload): fuel_at_takeoff = ( inputs[self.options["MFW_variable"]].item() - inputs[self.name_provider.CONSUMED_FUEL_BEFORE_INPUT_WEIGHT.value].item() ) - takeoff_weight_at_max_fuel_weight = ( + return ( # takeoff_weight_at_max_fuel_weight fuel_at_takeoff + payload + inputs[self.options["OWE_variable"]].item() ) - return takeoff_weight_at_max_fuel_weight def _calculate_payload_at_max_takeoff_weight_and_max_fuel_weight(self, inputs): consumed_fuel_before_takeoff = inputs[ @@ -406,12 +403,11 @@ def _calculate_payload_at_max_takeoff_weight_and_max_fuel_weight(self, inputs): ] fuel_at_takeoff = inputs[self.options["MFW_variable"]].item() - consumed_fuel_before_takeoff - payload_at_max_takeoff_weight_and_max_fuel_weight = ( + return ( # payload_at_max_takeoff_weight_and_max_fuel_weight inputs[self.options["MTOW_variable"]].item() - fuel_at_takeoff - inputs[self.options["OWE_variable"]].item() ) - return payload_at_max_takeoff_weight_and_max_fuel_weight class PayloadRangeGridInputValues(om.ExplicitComponent, BaseMissionComp, NeedsOWE): @@ -528,10 +524,9 @@ def _calculate_takeoff_weight(self, inputs, payload, block_fuel): fuel_at_takeoff = ( block_fuel - inputs[self.name_provider.CONSUMED_FUEL_BEFORE_INPUT_WEIGHT.value].item() ) - takeoff_weight_at_max_fuel_weight = ( + return ( # takeoff_weight_at_max_fuel_weight fuel_at_takeoff + payload + inputs[self.options["OWE_variable"]].item() ) - return takeoff_weight_at_max_fuel_weight @dataclass diff --git a/src/fastoad/models/performances/mission/polar_modifier.py b/src/fastoad/models/performances/mission/polar_modifier.py index 7381d21f7..27c1a0861 100644 --- a/src/fastoad/models/performances/mission/polar_modifier.py +++ b/src/fastoad/models/performances/mission/polar_modifier.py @@ -118,10 +118,8 @@ def modify_polar(self, polar: Polar, flight_point: FlightPoint) -> Polar: ) # Update polar interpolation - modified_polar = Polar( + return Polar( # modified_polar polar.definition_cl, polar.definition_cd + cd_ground, polar.definition_alpha, ) - - return modified_polar diff --git a/src/fastoad/models/performances/mission/routes.py b/src/fastoad/models/performances/mission/routes.py index e4884fd9d..fc432cf02 100644 --- a/src/fastoad/models/performances/mission/routes.py +++ b/src/fastoad/models/performances/mission/routes.py @@ -14,8 +14,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + from dataclasses import dataclass -from typing import List, Optional, Tuple import numpy as np import pandas as pd @@ -42,13 +43,13 @@ class RangedRoute(FlightSequence): """ #: Any number of flight phases that will occur before cruise. - climb_phases: List[FlightSequence] = MANDATORY_FIELD + climb_phases: list[FlightSequence] = MANDATORY_FIELD #: The cruise phase. cruise_segment: CruiseSegment = MANDATORY_FIELD #: Any number of flight phases that will occur after cruise. - descent_phases: List[FlightSequence] = MANDATORY_FIELD + descent_phases: list[FlightSequence] = MANDATORY_FIELD #: Target ground distance for whole route flight_distance: float = MANDATORY_FIELD @@ -79,7 +80,7 @@ def cruise_distance(self, cruise_distance): self.cruise_segment.target.set_as_relative("ground_distance") @property - def cruise_speed(self) -> Optional[Tuple[str, float]]: + def cruise_speed(self) -> tuple[str, float] | None: """ Type (among `true_airspeed`, `equivalent_airspeed` and `mach`) and value of cruise speed. """ @@ -117,7 +118,7 @@ def compute_from(self, start: FlightPoint) -> pd.DataFrame: self.cruise_distance = self.flight_distance - np.sum(climb_descent_distances) return super().compute_from(start) - def _get_flight_sequence(self) -> List[IFlightPart]: + def _get_flight_sequence(self) -> list[IFlightPart]: # The preliminary climb segment of the cruise segment is set to the # last segment before cruise. cruise_climb = self.climb_phases[-1] @@ -125,7 +126,7 @@ def _get_flight_sequence(self) -> List[IFlightPart]: cruise_climb = cruise_climb[-1] self.cruise_segment.climb_segment = cruise_climb - return self.climb_phases + [self.cruise_segment] + self.descent_phases + return [*self.climb_phases, self.cruise_segment, *self.descent_phases] @classmethod def _get_ground_distances(cls, phase: FlightSequence) -> list: diff --git a/src/fastoad/models/performances/mission/segments/base.py b/src/fastoad/models/performances/mission/segments/base.py index 01d5f8adf..d15d14ecf 100644 --- a/src/fastoad/models/performances/mission/segments/base.py +++ b/src/fastoad/models/performances/mission/segments/base.py @@ -12,10 +12,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + from abc import ABC, abstractmethod from copy import deepcopy from dataclasses import dataclass, field -from typing import Optional, Type +from typing import ClassVar import numpy as np import pandas as pd @@ -54,7 +56,7 @@ class SegmentDefinitions: """ @classmethod - def add_segment(cls, segment_name: str, segment_class: Type[IFlightPart]): + def add_segment(cls, segment_name: str, segment_class: type[IFlightPart]): """ Adds a segment definition. @@ -64,7 +66,7 @@ def add_segment(cls, segment_name: str, segment_class: Type[IFlightPart]): RegisterSegment(segment_name)(segment_class) @classmethod - def get_segment_class(cls, segment_name) -> Optional[Type["IFlightPart"]]: + def get_segment_class(cls, segment_name) -> type[IFlightPart] | None: """ Provides the segment implementation for provided name. @@ -139,7 +141,7 @@ class AbstractFlightSegment(IFlightPart, ABC): CONSTANT_VALUE = "constant" # pylint: disable=invalid-name # used as constant # To be noted: this one is not a dataclass field, but an actual class attribute - _attribute_units = dict(reference_area="m**2", time_step="s") + _attribute_units: ClassVar[dict] = dict(reference_area="m**2", time_step="s") @abstractmethod def compute_from_start_to_target(self, start, target) -> pd.DataFrame: @@ -219,9 +221,7 @@ def compute_from(self, start: FlightPoint) -> pd.DataFrame: if start_copy.ground_distance is None: start_copy.ground_distance = 0.0 - flight_points = self.compute_from_start_to_target(start_copy, target_copy) - - return flight_points + return self.compute_from_start_to_target(start_copy, target_copy) # flight_points def complete_flight_point(self, flight_point: FlightPoint): """ @@ -269,8 +269,8 @@ def complete_flight_point_from(flight_point: FlightPoint, source: FlightPoint): def consume_fuel( flight_point: FlightPoint, previous: FlightPoint, - fuel_consumption: float = None, - mass_ratio: float = None, + fuel_consumption: float | None = None, + mass_ratio: float | None = None, ): """ This method should be used whenever fuel consumption has to be stored. @@ -297,7 +297,7 @@ def consume_fuel( flight_point.consumed_fuel += previous.mass - flight_point.mass def _complete_speed_values( - self, flight_point: FlightPoint, raise_error_on_missing_speeds=True + self, flight_point: FlightPoint, *, raise_error_on_missing_speeds=True ) -> bool: """ Computes consistent values between TAS, EAS and Mach, assuming one of them is defined. diff --git a/src/fastoad/models/performances/mission/segments/macro_segments.py b/src/fastoad/models/performances/mission/segments/macro_segments.py index 7b615bd75..41cc86f45 100644 --- a/src/fastoad/models/performances/mission/segments/macro_segments.py +++ b/src/fastoad/models/performances/mission/segments/macro_segments.py @@ -14,6 +14,7 @@ # along with this program. If not, see . from abc import ABCMeta from dataclasses import dataclass, field, fields, make_dataclass +from typing import ClassVar from fastoad.model_base import FlightPoint from fastoad.model_base.datacls import MANDATORY_FIELD @@ -42,7 +43,7 @@ class MacroSegmentBase(FlightSequence): target: FlightPoint = MANDATORY_FIELD #: List of segment classes that will compose this macro-segment. - cls_sequence = [] + cls_sequence: ClassVar[list] = [] # Flag that is set to True after instantiation is finished. _initialized: bool = field(default=False, init=False) diff --git a/src/fastoad/models/performances/mission/segments/registered/__init__.py b/src/fastoad/models/performances/mission/segments/registered/__init__.py index bec359f19..b8a6294fa 100644 --- a/src/fastoad/models/performances/mission/segments/registered/__init__.py +++ b/src/fastoad/models/performances/mission/segments/registered/__init__.py @@ -16,9 +16,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - -# flake8: noqa - # With these imports, importing only the current package ensures to have all # these segments available when interpreting a mission input file from . import ( @@ -33,3 +30,16 @@ taxi, transition, ) + +__all__ = [ + "altitude_change", + "cruise", + "ground_speed_change", + "hold", + "mass_input", + "speed_change", + "start", + "takeoff", + "taxi", + "transition", +] diff --git a/src/fastoad/models/performances/mission/segments/registered/altitude_change.py b/src/fastoad/models/performances/mission/segments/registered/altitude_change.py index 5b5128875..d28039711 100644 --- a/src/fastoad/models/performances/mission/segments/registered/altitude_change.py +++ b/src/fastoad/models/performances/mission/segments/registered/altitude_change.py @@ -1,4 +1,5 @@ """Classes for climb/descent segments.""" + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2024 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -11,10 +12,10 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations from copy import copy from dataclasses import dataclass, field -from typing import List, Optional, Tuple, Union import pandas as pd from scipy.constants import foot, g @@ -77,7 +78,7 @@ class AltitudeChangeSegment(AbstractManualThrustSegment, AbstractLiftFromWeightS maximum_flight_level: float = 500.0 # To keep track of originally instructed target (used for "optimal_altitude" and so on) - _original_target_altitude: Optional[Union[str]] = field(default=None, init=False) + _original_target_altitude: str | None = field(default=None, init=False) #: Using this value will tell to target the altitude with max lift/drag ratio. OPTIMAL_ALTITUDE = "optimal_altitude" # pylint: disable=invalid-name # used as constant @@ -119,7 +120,7 @@ def compute_from_start_to_target(self, start: FlightPoint, target: FlightPoint) return super().compute_from_start_to_target(start, target) def get_distance_to_target( - self, flight_points: List[FlightPoint], target: FlightPoint + self, flight_points: list[FlightPoint], target: FlightPoint ) -> float: current = flight_points[-1] @@ -152,7 +153,7 @@ def get_distance_to_target( ) return distance_to_target - def get_gamma_and_acceleration(self, flight_point: FlightPoint) -> Tuple[float, float]: + def get_gamma_and_acceleration(self, flight_point: FlightPoint) -> tuple[float, float]: gamma = (flight_point.thrust - flight_point.drag) / flight_point.mass / g return gamma, 0.0 diff --git a/src/fastoad/models/performances/mission/segments/registered/cruise.py b/src/fastoad/models/performances/mission/segments/registered/cruise.py index e8d36c193..1f597d8ab 100644 --- a/src/fastoad/models/performances/mission/segments/registered/cruise.py +++ b/src/fastoad/models/performances/mission/segments/registered/cruise.py @@ -14,7 +14,6 @@ from copy import deepcopy from dataclasses import dataclass -from typing import List import numpy as np import pandas as pd @@ -58,7 +57,7 @@ def __post_init__(self): self.target.mach = AbstractTimeStepFlightSegment.CONSTANT_VALUE def get_distance_to_target( - self, flight_points: List[FlightPoint], target: FlightPoint + self, flight_points: list[FlightPoint], target: FlightPoint ) -> float: current = flight_points[-1] return target.ground_distance - current.ground_distance diff --git a/src/fastoad/models/performances/mission/segments/registered/ground_speed_change.py b/src/fastoad/models/performances/mission/segments/registered/ground_speed_change.py index 266563cf3..78e0dac6e 100644 --- a/src/fastoad/models/performances/mission/segments/registered/ground_speed_change.py +++ b/src/fastoad/models/performances/mission/segments/registered/ground_speed_change.py @@ -13,7 +13,6 @@ # along with this program. If not, see . from dataclasses import dataclass -from typing import List from fastoad.model_base import FlightPoint from fastoad.models.performances.mission.exceptions import FastFlightSegmentIncompleteFlightPoint @@ -31,7 +30,7 @@ class GroundSpeedChangeSegment(AbstractGroundSegment): """ def get_distance_to_target( - self, flight_points: List[FlightPoint], target: FlightPoint + self, flight_points: list[FlightPoint], target: FlightPoint ) -> float: if target.true_airspeed is not None: return target.true_airspeed - flight_points[-1].true_airspeed diff --git a/src/fastoad/models/performances/mission/segments/registered/speed_change.py b/src/fastoad/models/performances/mission/segments/registered/speed_change.py index fbd0916a6..5c4a806c3 100644 --- a/src/fastoad/models/performances/mission/segments/registered/speed_change.py +++ b/src/fastoad/models/performances/mission/segments/registered/speed_change.py @@ -13,7 +13,6 @@ # along with this program. If not, see . from dataclasses import dataclass -from typing import List, Tuple from fastoad.model_base import FlightPoint from fastoad.models.performances.mission.exceptions import FastFlightSegmentIncompleteFlightPoint @@ -37,7 +36,7 @@ class SpeedChangeSegment(AbstractManualThrustSegment, AbstractLiftFromWeightSegm """ def get_distance_to_target( - self, flight_points: List[FlightPoint], target: FlightPoint + self, flight_points: list[FlightPoint], target: FlightPoint ) -> float: if target.true_airspeed is not None: return target.true_airspeed - flight_points[-1].true_airspeed @@ -50,6 +49,6 @@ def get_distance_to_target( "No valid target definition for altitude change." ) - def get_gamma_and_acceleration(self, flight_point: FlightPoint) -> Tuple[float, float]: + def get_gamma_and_acceleration(self, flight_point: FlightPoint) -> tuple[float, float]: acceleration = (flight_point.thrust - flight_point.drag) / flight_point.mass return 0.0, acceleration diff --git a/src/fastoad/models/performances/mission/segments/registered/takeoff/__init__.py b/src/fastoad/models/performances/mission/segments/registered/takeoff/__init__.py index fd6b76be7..62af27df6 100644 --- a/src/fastoad/models/performances/mission/segments/registered/takeoff/__init__.py +++ b/src/fastoad/models/performances/mission/segments/registered/takeoff/__init__.py @@ -16,9 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - -# flake8: noqa - # With these imports, importing only the current package ensures to have all # these segments available when interpreting a mission input file from . import end_of_takeoff, rotation, takeoff + +__all__ = ["end_of_takeoff", "rotation", "takeoff"] diff --git a/src/fastoad/models/performances/mission/segments/registered/takeoff/end_of_takeoff.py b/src/fastoad/models/performances/mission/segments/registered/takeoff/end_of_takeoff.py index 2fb8ed406..72ec69807 100644 --- a/src/fastoad/models/performances/mission/segments/registered/takeoff/end_of_takeoff.py +++ b/src/fastoad/models/performances/mission/segments/registered/takeoff/end_of_takeoff.py @@ -13,7 +13,6 @@ # along with this program. If not, see . from dataclasses import dataclass -from typing import List from fastoad.model_base import FlightPoint from fastoad.models.performances.mission.exceptions import FastFlightSegmentIncompleteFlightPoint @@ -41,7 +40,7 @@ class EndOfTakeoffSegment(AbstractTakeOffSegment): """ def compute_next_flight_point( - self, flight_points: List[FlightPoint], time_step: float + self, flight_points: list[FlightPoint], time_step: float ) -> FlightPoint: """ Computes time, altitude, speed, mass and ground distance of next flight point. @@ -57,7 +56,7 @@ def compute_next_flight_point( return next_point def get_distance_to_target( - self, flight_points: List[FlightPoint], target: FlightPoint + self, flight_points: list[FlightPoint], target: FlightPoint ) -> float: current = flight_points[-1] diff --git a/src/fastoad/models/performances/mission/segments/registered/takeoff/rotation.py b/src/fastoad/models/performances/mission/segments/registered/takeoff/rotation.py index fe941efce..7c1ed155a 100644 --- a/src/fastoad/models/performances/mission/segments/registered/takeoff/rotation.py +++ b/src/fastoad/models/performances/mission/segments/registered/takeoff/rotation.py @@ -14,7 +14,6 @@ import logging from dataclasses import dataclass -from typing import List import numpy as np from numpy import cos, sin @@ -47,7 +46,7 @@ class RotationSegment(AbstractGroundSegment): alpha_limit: float = np.radians(13.5) def get_distance_to_target( - self, flight_points: List[FlightPoint], target: FlightPoint + self, flight_points: list[FlightPoint], target: FlightPoint ) -> float: # compute lift, including thrust projection, compare with weight current = flight_points[-1] diff --git a/src/fastoad/models/performances/mission/segments/registered/taxi.py b/src/fastoad/models/performances/mission/segments/registered/taxi.py index a441aabdf..bbdc9cd14 100644 --- a/src/fastoad/models/performances/mission/segments/registered/taxi.py +++ b/src/fastoad/models/performances/mission/segments/registered/taxi.py @@ -13,7 +13,6 @@ # along with this program. If not, see . from dataclasses import dataclass -from typing import Tuple import pandas as pd @@ -33,7 +32,7 @@ @dataclass class TaxiSegment( AbstractManualThrustSegment, AbstractFixedDurationSegment, AbstractLiftFromAoASegment -): # noqa: F821 +): """ Class for computing Taxi phases. @@ -46,7 +45,7 @@ class TaxiSegment( time_step: float = 60.0 true_airspeed: float = 0.0 - def get_gamma_and_acceleration(self, flight_point: FlightPoint) -> Tuple[float, float]: + def get_gamma_and_acceleration(self, flight_point: FlightPoint) -> tuple[float, float]: return 0.0, 0.0 def compute_from_start_to_target(self, start: FlightPoint, target: FlightPoint) -> pd.DataFrame: diff --git a/src/fastoad/models/performances/mission/segments/registered/tests/conftest.py b/src/fastoad/models/performances/mission/segments/registered/tests/conftest.py index aaab73e5a..994dccc66 100644 --- a/src/fastoad/models/performances/mission/segments/registered/tests/conftest.py +++ b/src/fastoad/models/performances/mission/segments/registered/tests/conftest.py @@ -11,6 +11,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from pathlib import Path + import numpy as np import pandas as pd import pytest @@ -68,7 +70,7 @@ def __init__(self, max_thrust, max_sfc, file_name): Unpickable dummy engine model, inherites from DummyEngine. """ DummyEngine.__init__(self, max_thrust, max_sfc) - self.data = open(file_name, "r") + self.data = Path.open(file_name, "r") def close_file(self): """Utility function to manually close the datafile.""" diff --git a/src/fastoad/models/performances/mission/segments/registered/tests/test_cruise.py b/src/fastoad/models/performances/mission/segments/registered/tests/test_cruise.py index f82c54c0c..40a6a1cc4 100644 --- a/src/fastoad/models/performances/mission/segments/registered/tests/test_cruise.py +++ b/src/fastoad/models/performances/mission/segments/registered/tests/test_cruise.py @@ -11,6 +11,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from pathlib import Path + import pytest from numpy.testing import assert_allclose @@ -195,7 +197,7 @@ def test_climb_and_cruise_at_optimal_flight_level_with_unpickable(polar, tmp_pat d = tmp_path / "sub" d.mkdir() file = d / "data.txt" - with open(file, "w") as f: + with Path.open(file, "w") as f: f.write("This is a test file for unpickable propulsion test.") # Actually try to run the cruise segment diff --git a/src/fastoad/models/performances/mission/segments/time_step_base.py b/src/fastoad/models/performances/mission/segments/time_step_base.py index 47412f7b0..f30ad3c31 100644 --- a/src/fastoad/models/performances/mission/segments/time_step_base.py +++ b/src/fastoad/models/performances/mission/segments/time_step_base.py @@ -12,10 +12,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + import logging from abc import ABC, abstractmethod from dataclasses import dataclass, field -from typing import List, Optional, Tuple import numpy as np import pandas as pd @@ -92,7 +93,7 @@ class AbstractTimeStepFlightSegment( @abstractmethod def get_distance_to_target( - self, flight_points: List[FlightPoint], target: FlightPoint + self, flight_points: list[FlightPoint], target: FlightPoint ) -> float: """ Computes a "distance" from last flight point to target. @@ -132,7 +133,7 @@ def compute_propulsion(self, flight_point: FlightPoint): """ @abstractmethod - def get_gamma_and_acceleration(self, flight_point: FlightPoint) -> Tuple[float, float]: + def get_gamma_and_acceleration(self, flight_point: FlightPoint) -> tuple[float, float]: """ Computes slope angle (gamma) and acceleration. @@ -227,11 +228,10 @@ def replace_last_point(time_step): previous_point_to_target = last_point_to_target - flight_points_df = pd.DataFrame(flight_points) - return flight_points_df + return pd.DataFrame(flight_points) # flight_points_df def compute_next_flight_point( - self, flight_points: List[FlightPoint], time_step: float + self, flight_points: list[FlightPoint], time_step: float ) -> FlightPoint: """ Computes time, altitude, speed, mass and ground distance of next flight point. @@ -284,7 +284,7 @@ def _compute_lift_and_drag(self, flight_point: FlightPoint): else: flight_point.CL = flight_point.CD = flight_point.lift = flight_point.drag = 0.0 - def _check_values(self, flight_point: FlightPoint) -> Optional[str]: + def _check_values(self, flight_point: FlightPoint) -> str | None: """ Checks that computed values are consistent. @@ -302,7 +302,7 @@ def _check_values(self, flight_point: FlightPoint) -> Optional[str]: return "Negative mass value." return None - def _add_new_flight_point(self, flight_points: List[FlightPoint], time_step): + def _add_new_flight_point(self, flight_points: list[FlightPoint], time_step): """ Appends a new flight point to provided flight point list. @@ -322,7 +322,7 @@ def _compute_next_altitude(next_point: FlightPoint, previous_point: FlightPoint) ) def _get_optimal_altitude( - self, mass: float, mach: float, altitude_guess: float = None + self, mass: float, mach: float, altitude_guess: float | None = None ) -> float: """ Computes optimal altitude for provided mass and Mach number. @@ -347,12 +347,10 @@ def distance_to_optimum(altitude): ) return (atm.density - optimal_air_density) * 100.0 - optimal_altitude = root_scalar( + return root_scalar( # optimal_altitude distance_to_optimum, x0=altitude_guess, x1=altitude_guess - 1000.0 ).root - return optimal_altitude - @dataclass class AbstractManualThrustSegment(AbstractTimeStepFlightSegment, ABC): @@ -387,7 +385,7 @@ def compute_propulsion(self, flight_point: FlightPoint): flight_point.thrust_is_regulated = True self.propulsion.compute_flight_points(flight_point) - def get_gamma_and_acceleration(self, flight_point: FlightPoint) -> Tuple[float, float]: + def get_gamma_and_acceleration(self, flight_point: FlightPoint) -> tuple[float, float]: return 0.0, 0.0 @@ -400,7 +398,7 @@ class AbstractFixedDurationSegment(AbstractTimeStepFlightSegment, ABC): time_step: float = 60.0 def get_distance_to_target( - self, flight_points: List[FlightPoint], target: FlightPoint + self, flight_points: list[FlightPoint], target: FlightPoint ) -> float: current = flight_points[-1] return target.time - current.time diff --git a/src/fastoad/models/performances/mission/tests/conftest.py b/src/fastoad/models/performances/mission/tests/conftest.py index 6d6507ea0..56a4ed485 100644 --- a/src/fastoad/models/performances/mission/tests/conftest.py +++ b/src/fastoad/models/performances/mission/tests/conftest.py @@ -11,9 +11,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + from dataclasses import InitVar, dataclass, field from pathlib import Path -from typing import Union import numpy as np import pandas as pd @@ -130,8 +131,8 @@ def __post_init__( propulsion: IPropulsion, reference_area: float, polar: Polar, - thrust_rate: float = 1.0, - time_step=None, + thrust_rate: float, + time_step, ): """ @@ -151,7 +152,7 @@ def __post_init__( "time_step": time_step, } - def compute_from(self, start: FlightPoint) -> pd.DataFrame: + def compute_from(self, start: FlightPoint) -> pd.DataFrame | None: parts = [] part_start = start for part in self: @@ -169,6 +170,7 @@ def compute_from(self, start: FlightPoint) -> pd.DataFrame: if parts: return pd.concat(parts).reset_index(drop=True) + return None @dataclass @@ -237,7 +239,7 @@ class ClimbPhase(AbstractManualThrustFlightPhase): """ maximum_mach: float = field(default=5.0) - target_altitude: Union[float, str] = MANDATORY_FIELD + target_altitude: float | str = MANDATORY_FIELD def __post_init__(self, *args, **kwargs): super().__post_init__(*args, **kwargs) @@ -275,7 +277,7 @@ class DescentPhase(AbstractManualThrustFlightPhase): - Descends down to target altitude at constant EAS """ - target_altitude: Union[float, str] = MANDATORY_FIELD + target_altitude: float | str = MANDATORY_FIELD def __post_init__(self, *args, **kwargs): super().__post_init__(*args, **kwargs) diff --git a/src/fastoad/models/performances/mission/tests/test_flight_sequence.py b/src/fastoad/models/performances/mission/tests/test_flight_sequence.py index 556c391b3..0b5e9160a 100644 --- a/src/fastoad/models/performances/mission/tests/test_flight_sequence.py +++ b/src/fastoad/models/performances/mission/tests/test_flight_sequence.py @@ -11,6 +11,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . + import pytest from numpy.testing import assert_allclose @@ -20,6 +21,8 @@ from ..segments.registered.mass_input import MassTargetSegment from ..segments.registered.taxi import TaxiSegment +# ruff: noqa: RUF005 + def get_taxi_definition(propulsion, target_mass=None): return TaxiSegment( diff --git a/src/fastoad/models/performances/mission/util.py b/src/fastoad/models/performances/mission/util.py index 115b10f83..77c3a1a5f 100644 --- a/src/fastoad/models/performances/mission/util.py +++ b/src/fastoad/models/performances/mission/util.py @@ -20,7 +20,7 @@ FLIGHT_LEVEL = 100 * foot -def get_closest_flight_level(altitude, base_level=0, level_step=10, up_direction=True): +def get_closest_flight_level(altitude, base_level=0, level_step=10, *, up_direction=True): """ Computes the altitude (in meters) of a flight level close to provided altitude. diff --git a/src/fastoad/module_management/_bundle_loader.py b/src/fastoad/module_management/_bundle_loader.py index 3b67a813d..1a9ae6ab7 100644 --- a/src/fastoad/module_management/_bundle_loader.py +++ b/src/fastoad/module_management/_bundle_loader.py @@ -14,11 +14,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + import gc import logging import re from os import PathLike -from typing import Any, Dict, List, Optional, Set, Tuple, Type, TypeVar, Union +from typing import Any, TypeVar import pelix from pelix.constants import BundleException @@ -77,8 +79,8 @@ def context(self) -> BundleContext: return self.framework.get_bundle_context() def explore_folder( - self, folder_path: Union[str, PathLike], is_package: bool = False - ) -> Tuple[Set[Bundle], Set[str]]: + self, folder_path: str | PathLike, *, is_package: bool = False + ) -> tuple[set[Bundle], set[str]]: """ Installs bundles found in *folder_path*. @@ -95,7 +97,9 @@ def explore_folder( if is_package: bundles, failed = self._install_python_package(folder_path) else: - bundles, failed = self.framework.install_package(as_path(folder_path).as_posix(), True) + bundles, failed = self.framework.install_package( + as_path(folder_path).as_posix(), recursive=True + ) for bundle in bundles: _LOGGER.info( @@ -108,8 +112,8 @@ def explore_folder( return bundles, failed def get_services( - self, service_name: str, properties: dict = None, case_sensitive: bool = False - ) -> Optional[list]: + self, service_name: str, properties: dict | None = None, *, case_sensitive: bool = False + ) -> list | None: """ Returns the services that match *service_name* and provided *properties* (if provided). @@ -120,7 +124,9 @@ def get_services( ignored :return: the list of service instances """ - references = self._get_service_references(service_name, properties, case_sensitive) + references = self._get_service_references( + service_name, properties, case_sensitive=case_sensitive + ) services = None if references is not None: services = [self.context.get_service(ref) for ref in references] @@ -129,11 +135,11 @@ def get_services( def register_factory( self, - component_class: Type[T], + component_class: type[T], factory_name: str, - service_names: Union[List[str], str], - properties: dict = None, - ) -> Type[T]: + service_names: list[str] | str, + properties: dict | None = None, + ) -> type[T]: """ Registers provided class as iPOPO component factory. @@ -154,13 +160,15 @@ def register_factory( for key, value in properties.items(): obj = Property(field="_" + self._fieldify(key), name=key, value=value)(obj) - factory = ComponentFactory(factory_name)(obj) - - return factory + return ComponentFactory(factory_name)(obj) # Factory def get_factory_names( - self, service_name: str = None, properties: dict = None, case_sensitive: bool = False - ) -> List[str]: + self, + service_name: str | None = None, + properties: dict | None = None, + *, + case_sensitive: bool = False, + ) -> list[str]: """ Browses the available factory names to find what factories provide `service_name` (if provided) and match provided `properties` (if provided). @@ -224,8 +232,7 @@ def get_factory_properties(self, factory_name: str) -> dict: """ details = self.get_factory_details(factory_name) - properties = details["properties"] - return properties + return details["properties"] # Properties def get_factory_property(self, factory_name: str, property_name: str) -> Any: """ @@ -237,7 +244,7 @@ def get_factory_property(self, factory_name: str, property_name: str) -> Any: properties = self.get_factory_properties(factory_name) return properties.get(property_name) - def get_factory_details(self, factory_name: str) -> Dict[str, Any]: + def get_factory_details(self, factory_name: str) -> dict[str, Any]: """ :param factory_name: name of the factory :return: factory details as in iPOPO @@ -261,7 +268,7 @@ def get_instance_property(self, instance: Any, property_name: str) -> Any: except AttributeError: return None - def instantiate_component(self, factory_name: str, properties: dict = None) -> Any: + def instantiate_component(self, factory_name: str, properties: dict | None = None) -> Any: """ Instantiates a component from given factory @@ -301,16 +308,16 @@ def _get_instance_name(self, base_name: str): instances = ipopo.get_instances() instance_names = [i[0] for i in instances] i = 0 - name = "%s_%i" % (base_name, i) + name = f"{base_name}_{i}" while name in instance_names: i = i + 1 - name = "%s_%i" % (base_name, i) + name = f"{base_name}_{i}" return name def _get_service_references( - self, service_name: str, properties: dict = None, case_sensitive: bool = False - ) -> Optional[List[ServiceReference]]: + self, service_name: str, properties: dict | None = None, *, case_sensitive: bool = False + ) -> list[ServiceReference] | None: """ Returns the service references that match *service_name* and provided *properties* (if provided) @@ -334,20 +341,14 @@ def _get_service_references( else: ldap_filter = ( "(&" - + "".join( - [ - "({0}{1}{2})".format(key, operator, value) - for key, value in properties.items() - ] - ) + + "".join([f"({key}{operator}{value})" for key, value in properties.items()]) + ")" ) _LOGGER.debug(ldap_filter) - references = self.framework.find_service_references(service_name, ldap_filter) - return references + return self.framework.find_service_references(service_name, ldap_filter) # references - def _install_python_package(self, package_name: str) -> Tuple[Set[Bundle], Set[str]]: + def _install_python_package(self, package_name: str) -> tuple[set[Bundle], set[str]]: """ Recursively loads indicated package. diff --git a/src/fastoad/module_management/_plugins.py b/src/fastoad/module_management/_plugins.py index f159ceba1..b1dfd11e0 100644 --- a/src/fastoad/module_management/_plugins.py +++ b/src/fastoad/module_management/_plugins.py @@ -14,13 +14,15 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + import logging import sys import warnings from dataclasses import dataclass, field from enum import Enum from pathlib import Path -from typing import Callable, Dict, List, Optional +from typing import Callable import numpy as np @@ -62,7 +64,7 @@ class DistributionNameDict(AbstractNormalizedDict): """ @staticmethod - def normalize(dist_name: Optional[str]): # pylint: disable=arguments-differ + def normalize(dist_name: str | None): # pylint: disable=arguments-differ """ Returns a normalized distribution name for PEP-426-compliant comparison of distribution names. @@ -105,7 +107,7 @@ class PluginDefinition: dist_name: str plugin_name: str package_name: str = "" - subpackages: Dict[SubPackageNames, str] = field(default_factory=dict) + subpackages: dict[SubPackageNames, str] = field(default_factory=dict) def detect_subfolders(self): """ @@ -118,7 +120,7 @@ def detect_subfolders(self): [self.package_name, subpackage_name.value] ) - def get_configuration_file_list(self) -> List[ResourceInfo]: + def get_configuration_file_list(self) -> list[ResourceInfo]: """ :return: List of configuration files that are provided by the distribution. """ @@ -136,7 +138,7 @@ def get_configuration_file_list(self) -> List[ResourceInfo]: return [] - def get_source_data_file_list(self) -> List[ResourceInfo]: + def get_source_data_file_list(self) -> list[ResourceInfo]: """ :return: List of data files that are provided by the distribution. """ @@ -206,7 +208,7 @@ def read_entry_point(self, entry_point: importlib_metadata.EntryPoint, group: st if group == MODEL_PLUGIN_ID: self[entry_point.name].detect_subfolders() - def get_source_data_file_list(self, plugin_name=None) -> List[ResourceInfo]: + def get_source_data_file_list(self, plugin_name=None) -> list[ResourceInfo]: """ :param plugin_name: If provided, only file names provided by the plugin in the distribution will be returned, or an empty list if @@ -255,7 +257,7 @@ def get_source_data_file_info(self, file_name=None, plugin_name=None) -> Resourc return file_info - def get_configuration_file_list(self, plugin_name=None) -> List[ResourceInfo]: + def get_configuration_file_list(self, plugin_name=None) -> list[ResourceInfo]: """ :param plugin_name: If provided, only file names provided by the plugin in the distribution will be returned, or an empty list if @@ -303,7 +305,7 @@ def get_configuration_file_info(self, file_name=None, plugin_name=None) -> Resou return file_info - def get_notebook_folder_list(self, plugin_name=None) -> List[ResourceInfo]: + def get_notebook_folder_list(self, plugin_name=None) -> list[ResourceInfo]: """ :param plugin_name: If provided, only notebook folder provided by the plugin (if any) will be returned, or an empty list if the plugin is not in the @@ -329,7 +331,7 @@ def get_notebook_folder_list(self, plugin_name=None) -> List[ResourceInfo]: ) return info - def to_dict(self) -> Dict: + def to_dict(self) -> dict: """ :return: a dict that contains plugin information """ @@ -374,12 +376,12 @@ def __init__(self): self.load() @property - def distribution_plugin_definitions(self) -> Dict[str, DistributionPluginDefinition]: + def distribution_plugin_definitions(self) -> dict[str, DistributionPluginDefinition]: """Stores plugin definitions with distribution names as dict keys.""" return self._dist_plugin_definitions.copy() def get_distribution_plugin_definition( - self, dist_name: str = None + self, dist_name: str | None = None ) -> DistributionPluginDefinition: """ :param dist_name: needed if more than one distribution with FAST-OAD plugin is installed. @@ -403,7 +405,7 @@ def get_distribution_plugin_definition( return self._dist_plugin_definitions[dist_name] - def get_configuration_file_list(self, dist_name: str) -> List[ResourceInfo]: + def get_configuration_file_list(self, dist_name: str) -> list[ResourceInfo]: """ :param dist_name: the distribution to inspect :return: list of configuration files available for named distribution, @@ -414,7 +416,7 @@ def get_configuration_file_list(self, dist_name: str) -> List[ResourceInfo]: dist_name, ) - def get_source_data_file_list(self, dist_name: str) -> List[ResourceInfo]: + def get_source_data_file_list(self, dist_name: str) -> list[ResourceInfo]: """ :param dist_name: the distribution to inspect :return: list of source data files available for named distribution, or an empty list if the @@ -425,7 +427,7 @@ def get_source_data_file_list(self, dist_name: str) -> List[ResourceInfo]: dist_name, ) - def get_notebook_folder_list(self, dist_name: str = None) -> List[ResourceInfo]: + def get_notebook_folder_list(self, dist_name: str | None = None) -> list[ResourceInfo]: """ Returns the list of notebook folders available for named distribution and optionally the named plugin of this distribution. @@ -439,7 +441,9 @@ def get_notebook_folder_list(self, dist_name: str = None) -> List[ResourceInfo]: dist_name, ) - def _get_resource_list(self, method: Callable, dist_name: str = None) -> List[ResourceInfo]: + def _get_resource_list( + self, method: Callable, dist_name: str | None = None + ) -> list[ResourceInfo]: infos = [] if dist_name: if dist_name not in self._dist_plugin_definitions: diff --git a/src/fastoad/module_management/exceptions.py b/src/fastoad/module_management/exceptions.py index 3a2d8e4e7..e2d4790b9 100644 --- a/src/fastoad/module_management/exceptions.py +++ b/src/fastoad/module_management/exceptions.py @@ -12,7 +12,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from typing import List, Sequence +from collections.abc import Sequence from fastoad.exceptions import FastError @@ -26,7 +26,7 @@ def __init__(self, factory_name: str): """ :param factory_name: """ - super().__init__('Name "%s" is already used.' % factory_name) + super().__init__(f'Name "{factory_name}" is already used.') self.factory_name = factory_name @@ -39,7 +39,7 @@ def __init__(self, factory_name: str): """ :param factory_name: """ - super().__init__('"%s" is not registered.' % factory_name) + super().__init__(f'"{factory_name}" is not registered.') self.factory_name = factory_name @@ -54,8 +54,7 @@ def __init__(self, identifier, option_names): :param option_names: incorrect option names """ super().__init__( - "OpenMDAO system %s does not accept following option(s): %s" - % (identifier, option_names) + f"OpenMDAO system {identifier} does not accept following option(s): {option_names}" ) self.identifier = identifier self.option_names = option_names @@ -74,8 +73,7 @@ def __init__(self, registered_class: type, service_id: str, base_class: type): :param base_class: the unmatched interface """ super().__init__( - 'Trying to register %s as service "%s" but it does not inherit from %s' - % (str(registered_class), service_id, str(base_class)) + f'Trying to register {registered_class!s} as service "{service_id}" but it does not inherit from {base_class!s}' ) self.registered_class = registered_class self.service_id = service_id @@ -91,7 +89,7 @@ def __init__(self, service_id: str): """ :param service_id: """ - super().__init__('No submodel found for requirement "%s"' % service_id) + super().__init__(f'No submodel found for requirement "{service_id}"') self.service_id = service_id @@ -107,8 +105,7 @@ def __init__(self, service_id: str, candidates: Sequence[str]): :param candidates: """ super().__init__( - 'Submodel requirement "%s" needs a choice among following candidates: %s' - % (service_id, candidates) + f'Submodel requirement "{service_id}" needs a choice among following candidates: {candidates}' ) self.service_id = service_id self.candidates = candidates @@ -119,15 +116,15 @@ class FastUnknownSubmodelError(FastError): Raised when a submodel identifier is unknown for given required service. """ - def __init__(self, service_id: str, submodel_id: str, submodel_ids: List[str]): + def __init__(self, service_id: str, submodel_id: str, submodel_ids: list[str]): """ :param service_id: :param submodel_id: :param submodel_ids: """ - msg = '"%s" is not a submodel identifier for requirement "%s"' % (submodel_id, service_id) - msg += "\nValid identifiers are %s" % submodel_ids + msg = f'"{submodel_id}" is not a submodel identifier for requirement "{service_id}"' + msg += f"\nValid identifiers are {submodel_ids}" super().__init__(msg) self.service_id = service_id self.submodel_id = submodel_id diff --git a/src/fastoad/module_management/service_registry.py b/src/fastoad/module_management/service_registry.py index 9844d9a0c..33b0653fc 100644 --- a/src/fastoad/module_management/service_registry.py +++ b/src/fastoad/module_management/service_registry.py @@ -1,4 +1,5 @@ """Module for registering services.""" + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2024 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -11,10 +12,11 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations from os import PathLike from types import MethodType -from typing import Any, Dict, List, Optional, Type, TypeVar, Union +from typing import Any, ClassVar, TypeVar import openmdao.api as om from openmdao.core.system import System @@ -83,7 +85,7 @@ def __init__(self, service_id: str, provider_id: str, desc=None): self._id = provider_id self._desc = desc - def __call__(self, service_class: Type[T]) -> Type[T]: + def __call__(self, service_class: type[T]) -> type[T]: if not issubclass(service_class, self._base_class): raise FastIncompatibleServiceClassError( service_class, self._service_id, self._base_class @@ -93,7 +95,7 @@ def __call__(self, service_class: Type[T]) -> Type[T]: service_class, self._id, self._service_id, self.get_properties(service_class) ) - def get_properties(self, service_class: Type[T]) -> dict: + def get_properties(self, service_class: type[T]) -> dict: """ Override this method to modify the properties that will be associated to the registered service provider. @@ -118,7 +120,7 @@ def explore_folder(cls, folder_path: str): FastoadLoader().explore_folder(folder_path) @classmethod - def get_provider_ids(cls, service_id: str) -> List[str]: + def get_provider_ids(cls, service_id: str) -> list[str]: """ :param service_id: :return: the list of identifiers of providers of the service. @@ -126,7 +128,7 @@ def get_provider_ids(cls, service_id: str) -> List[str]: return FastoadLoader().get_factory_names(service_id) @classmethod - def get_provider(cls, service_provider_id: str, options: dict = None) -> Any: + def get_provider(cls, service_provider_id: str, options: dict | None = None) -> Any: """ Instantiates the desired service provider. @@ -144,7 +146,7 @@ def get_provider(cls, service_provider_id: str, options: dict = None) -> Any: return FastoadLoader().instantiate_component(service_provider_id, properties) @classmethod - def get_provider_description(cls, instance_or_id: Union[str, T]) -> str: + def get_provider_description(cls, instance_or_id: str | T) -> str: """ :param instance_or_id: an identifier or an instance of a registered service provider :return: the description associated to given instance or identifier @@ -156,7 +158,7 @@ def get_provider_description(cls, instance_or_id: Union[str, T]) -> str: return description @classmethod - def get_provider_domain(cls, instance_or_id: Union[str, System]) -> ModelDomain: + def get_provider_domain(cls, instance_or_id: str | System) -> ModelDomain: """ :param instance_or_id: an identifier or an instance of a registered service provider :return: the model domain associated to given instance or identifier @@ -189,7 +191,7 @@ class _RegisterOpenMDAOService(RegisterService, base_class=System): or when instantiating the system with :class:`get_system`. """ - def __init__(self, service_id: str, provider_id: str, desc=None, options: dict = None): + def __init__(self, service_id: str, provider_id: str, desc=None, options: dict | None = None): """ :param service_id: the identifier of the provided service :param provider_id: the identifier of the service provider to register @@ -199,12 +201,12 @@ def __init__(self, service_id: str, provider_id: str, desc=None, options: dict = super().__init__(service_id, provider_id, desc) self._options = options - def get_properties(self, service_class: Type[T]) -> dict: + def get_properties(self, service_class: type[T]) -> dict: properties = super().get_properties(service_class) properties.update({OPTION_PROPERTY_NAME: self._options if self._options else {}}) return properties - def __call__(self, service_class: Type[T]) -> Type[T]: + def __call__(self, service_class: type[T]) -> type[T]: # service_class.__module__ provides the name for the .py file, but # we want just the parent package name. package_name = ".".join(service_class.__module__.split(".")[:-1]) @@ -215,7 +217,7 @@ def __call__(self, service_class: Type[T]) -> Type[T]: return super().__call__(service_class) @classmethod - def explore_folder(cls, folder_path: Union[str, PathLike]): + def explore_folder(cls, folder_path: str | PathLike): """ Explores provided folder and looks for service providers to register. @@ -225,7 +227,7 @@ def explore_folder(cls, folder_path: Union[str, PathLike]): super().explore_folder(folder_path) @classmethod - def get_system(cls, identifier: str, options: dict = None) -> System: + def get_system(cls, identifier: str, options: dict | None = None) -> System: """ Specialized version of :meth:`RegisterSpecializedService.get_provider` that allows to define OpenMDAO options on-the-fly. @@ -245,8 +247,7 @@ def get_system(cls, identifier: str, options: dict = None) -> System: if invalid_options: raise FastBadSystemOptionError(identifier, invalid_options) - decorated_system = cls._option_decorator(system) - return decorated_system + return cls._option_decorator(system) # decorated_system @staticmethod def _option_decorator(instance: System) -> System: @@ -331,7 +332,7 @@ class ParticularService(ISomeService): @classmethod def __init_subclass__( - cls, *, base_class: type = object, service_id: str = None, domain: ModelDomain = None + cls, *, base_class: type = object, service_id: str | None = None, domain: ModelDomain = None ): """ @@ -341,17 +342,17 @@ def __init_subclass__( :param domain: a category that can be associated to the registered service """ - super(RegisterSpecializedService, cls).__init_subclass__(base_class=base_class) + super().__init_subclass__(base_class=base_class) if service_id: cls.service_id = service_id else: - cls.service_id = "%s.%s" % (__name__, cls.__name__) + cls.service_id = f"{__name__}.{cls.__name__}" cls._domain = domain def __init__( - self, provider_id: str, desc=None, domain: ModelDomain = None, options: dict = None + self, provider_id: str, desc=None, domain: ModelDomain = None, options: dict | None = None ): """ :param provider_id: the identifier of the service provider to register @@ -364,7 +365,7 @@ def __init__( if domain: self._domain = domain - def get_properties(self, service_class: Type[T]) -> dict: + def get_properties(self, service_class: type[T]) -> dict: properties = super().get_properties(service_class) properties.update( { @@ -375,7 +376,7 @@ def get_properties(self, service_class: Type[T]) -> dict: return properties @classmethod - def get_provider_ids(cls) -> List[str]: + def get_provider_ids(cls) -> list[str]: """ :return: the list of identifiers of providers of the service. """ @@ -440,10 +441,10 @@ class MyService: #: Dictionary (key = service id, value=provider id) that defines submodels to #: be used for associated services. - active_models: Dict[str, Optional[str]] = {} + active_models: ClassVar[dict[str, str | None]] = {} @classmethod - def get_submodel(cls, service_id: str, options: dict = None): + def get_submodel(cls, service_id: str, options: dict | None = None): """ Provides a submodel for the given service identifier. diff --git a/src/fastoad/module_management/tests/data/dummy_pelix_bundles/hello_world_with_decorators.py b/src/fastoad/module_management/tests/data/dummy_pelix_bundles/hello_world_with_decorators.py index c92bfaa94..fc1c735ca 100644 --- a/src/fastoad/module_management/tests/data/dummy_pelix_bundles/hello_world_with_decorators.py +++ b/src/fastoad/module_management/tests/data/dummy_pelix_bundles/hello_world_with_decorators.py @@ -21,19 +21,19 @@ @Provides("hello.world") @Property("_Prop1", "Prop1", 1) @Property("_Prop_2", "Prop 2", "Says.Hello") -@Property("Instantiated", None, True) +@Property("Instantiated", None, value=True) @Instantiate("provider") class Greetings1: def hello(self, name="World"): - return "Hello, {0}!".format(name) + return f"Hello, {name}!" @ComponentFactory("hello-world-factory2") @Provides("hello.world") @Property("_Prop1", "Prop1", 2) @Property("_Prop_2", "Prop 2", "Says.Hi") -@Property("Instantiated", None, True) +@Property("Instantiated", None, value=True) @Instantiate("provider2") class Greetings2: def hello(self, name="World"): - return "Hi, {0}!".format(name) + return f"Hi, {name}!" diff --git a/src/fastoad/module_management/tests/data/dummy_pelix_bundles/hello_world_without_decorators.py b/src/fastoad/module_management/tests/data/dummy_pelix_bundles/hello_world_without_decorators.py index 4ae61075f..ca04a3e78 100644 --- a/src/fastoad/module_management/tests/data/dummy_pelix_bundles/hello_world_without_decorators.py +++ b/src/fastoad/module_management/tests/data/dummy_pelix_bundles/hello_world_without_decorators.py @@ -21,7 +21,7 @@ # Register factories without instantiating with our wrapping of iPOPO class OtherGreetings: def hello(self, name="World"): - return "Hello again, {0}!".format(name) + return f"Hello again, {name}!" BundleLoader().register_factory( @@ -34,7 +34,7 @@ def hello(self, name="World"): class OtherGreetings2: def hello(self, name="Universe"): - return "Hello again, {0}!".format(name) + return f"Hello again, {name}!" # This one provides a different service and tests registering without properties diff --git a/src/fastoad/module_management/tests/test_bundle_loader.py b/src/fastoad/module_management/tests/test_bundle_loader.py index 9d1dc141f..8fa44228c 100644 --- a/src/fastoad/module_management/tests/test_bundle_loader.py +++ b/src/fastoad/module_management/tests/test_bundle_loader.py @@ -37,12 +37,12 @@ def delete_framework(): """Ensures framework is deleted before and after running tests""" if FrameworkFactory.is_framework_running(): - FrameworkFactory.get_framework().delete(True) + FrameworkFactory.get_framework().delete(force=True) yield if FrameworkFactory.is_framework_running(): - FrameworkFactory.get_framework().delete(True) + FrameworkFactory.get_framework().delete(force=True) def test_init_bundle_loader_from_scratch(delete_framework): @@ -146,7 +146,7 @@ def test_register_factory(delete_framework): class Greetings1: def hello(self, name="World"): - return "Hello, {0}!".format(name) + return f"Hello, {name}!" with pytest.raises(FastBundleLoaderDuplicateFactoryError) as exc_info: loader.register_factory(Greetings1, "hello-universe-factory", "hello.world") @@ -191,9 +191,9 @@ def test_get_services(delete_framework): assert greet_service.hello() == "Hi, World!" # Existing service, case sensitivity - services = loader.get_services("hello.world", {"Prop 2": "SAYS.HELLO"}, True) + services = loader.get_services("hello.world", {"Prop 2": "SAYS.HELLO"}, case_sensitive=True) assert services is None - services = loader.get_services("hello.world", {"Prop 2": "Says.Hello"}, True) + services = loader.get_services("hello.world", {"Prop 2": "Says.Hello"}, case_sensitive=True) assert len(services) == 1 river = services[0] assert river.hello("Sweetie") == "Hello, Sweetie!" @@ -271,7 +271,11 @@ def test_get_factory_names(delete_framework): assert greet_service.hello() == "Hi, World!" # Existing service, case sensitivity - factory_names = loader.get_factory_names("hello.world", {"Prop 2": "SAYS.HELLO"}, True) + factory_names = loader.get_factory_names( + "hello.world", {"Prop 2": "SAYS.HELLO"}, case_sensitive=True + ) assert not factory_names - factory_names = loader.get_factory_names("hello.world", {"Prop 2": "Says.Hello"}, True) + factory_names = loader.get_factory_names( + "hello.world", {"Prop 2": "Says.Hello"}, case_sensitive=True + ) assert len(factory_names) == 2 diff --git a/src/fastoad/notebooks/01_Quick_start/beam_problem.ipynb b/src/fastoad/notebooks/01_Quick_start/beam_problem.ipynb index c79e180b1..06a3e589f 100644 --- a/src/fastoad/notebooks/01_Quick_start/beam_problem.ipynb +++ b/src/fastoad/notebooks/01_Quick_start/beam_problem.ipynb @@ -54,7 +54,7 @@ "outputs": [], "source": [ "import logging\n", - "import os.path as pth\n", + "from pathlib import Path\n", "\n", "import fastoad.api as oad" ] @@ -72,11 +72,11 @@ "metadata": {}, "outputs": [], "source": [ - "DATA_FOLDER = \"data\"\n", + "DATA_FOLDER = Path(\"data\")\n", "\n", - "WORK_FOLDER = \"workdir\"\n", + "WORK_FOLDER = Path(\"workdir\")\n", "\n", - "CONFIGURATION_FILE_NAME = pth.join(DATA_FOLDER, \"beam_problem.yml\")\n", + "CONFIGURATION_FILE_NAME = DATA_FOLDER / \"beam_problem.yml\"\n", "\n", "CUSTOM_MODULES_FOLDER_PATH = \"./modules\"\n", "\n", @@ -230,7 +230,7 @@ "source": [ "from IPython.display import IFrame\n", "\n", - "N2_FILE = pth.join(WORK_FOLDER, \"n2.html\")\n", + "N2_FILE = WORK_FOLDER / \"n2.html\"\n", "oad.write_n2(CONFIGURATION_FILE_NAME, N2_FILE, overwrite=True)\n", "\n", "IFrame(src=N2_FILE, width=\"100%\", height=\"500px\")" @@ -253,7 +253,7 @@ "source": [ "# UNCOMMENT THE FOLLOWING LINE TO GENERATE A BLANK INPUT FILE\n", "# oad.generate_inputs(CONFIGURATION_FILE_NAME, overwrite=True)\n", - "INPUT_FILE_NAME = pth.join(DATA_FOLDER, \"problem_inputs.xml\")\n", + "INPUT_FILE_NAME = DATA_FOLDER / \"problem_inputs.xml\"\n", "oad.variable_viewer(INPUT_FILE_NAME)" ] }, @@ -318,10 +318,10 @@ "metadata": {}, "outputs": [], "source": [ - "CONFIGURATION_FILE_STRESS_NAME = pth.join(DATA_FOLDER, \"beam_problem_stress.yml\")\n", + "CONFIGURATION_FILE_STRESS_NAME = DATA_FOLDER / \"beam_problem_stress.yml\"\n", "# UNCOMMENT THE FOLLOWING LINE TO GENERATE A BLANK INPUT FILE\n", "# oad.generate_inputs(CONFIGURATION_FILE_STRESS_NAME, overwrite=True)\n", - "INPUT_FILE_NAME = pth.join(DATA_FOLDER, \"problem_inputs_stress.xml\")\n", + "INPUT_FILE_NAME = DATA_FOLDER / \"problem_inputs_stress.xml\"\n", "oad.variable_viewer(INPUT_FILE_NAME)" ] }, @@ -370,13 +370,6 @@ "\n", "Those descriptions are now printed in the dedicated column when the `variable_viewer` is called. For more information please refer to the [documentation](https://fast-oad.readthedocs.io/en/stable/documentation/custom_modules/add_variable_documentation.html)." ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/src/fastoad/openmdao/_utils.py b/src/fastoad/openmdao/_utils.py index b58335a64..114033ce9 100644 --- a/src/fastoad/openmdao/_utils.py +++ b/src/fastoad/openmdao/_utils.py @@ -16,7 +16,7 @@ from contextlib import contextmanager from copy import deepcopy -from typing import List, Tuple, TypeVar +from typing import TypeVar import numpy as np import openmdao.api as om @@ -37,9 +37,7 @@ def get_mpi_safe_problem_copy(problem: T) -> T: :return: a copy of the problem with a FakeComm object as problem.comm """ with copyable_problem(problem) as no_mpi_problem: - problem_copy = deepcopy(no_mpi_problem) - - return problem_copy + return deepcopy(no_mpi_problem) @contextmanager @@ -74,11 +72,11 @@ def copyable_problem(problem: om.Problem) -> om.Problem: @deprecated( version="1.3.0", - reason="Will be removed in version 2.0. Please use VariableList.from_problem() instead", + reason="Will be removed in version 2.0. Please use Variable list.from_problem() instead", ) def get_unconnected_input_names( - problem: om.Problem, promoted_names=False -) -> Tuple[List[str], List[str]]: + problem: om.Problem, *, promoted_names=False +) -> tuple[list[str], list[str]]: """ For provided OpenMDAO problem, looks for inputs that are connected to no output. diff --git a/src/fastoad/openmdao/exceptions.py b/src/fastoad/openmdao/exceptions.py index 78d4eab3f..7eb626d3f 100644 --- a/src/fastoad/openmdao/exceptions.py +++ b/src/fastoad/openmdao/exceptions.py @@ -14,8 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from collections.abc import Iterable from os import PathLike -from typing import Iterable from fastoad._utils.files import as_path from fastoad.exceptions import FastError diff --git a/src/fastoad/openmdao/problem.py b/src/fastoad/openmdao/problem.py index eea4bc0e3..6aec42598 100644 --- a/src/fastoad/openmdao/problem.py +++ b/src/fastoad/openmdao/problem.py @@ -11,10 +11,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + import logging from dataclasses import dataclass, field from os import PathLike -from typing import Optional, Tuple, Union import numpy as np import openmdao.api as om @@ -72,15 +73,15 @@ def __init__(self, *args, **kwargs): self._copy = None - self._analysis: Optional[ProblemAnalysis] = None + self._analysis: ProblemAnalysis | None = None - def run_model(self, case_prefix=None, reset_iter_counts=True): + def run_model(self, case_prefix=None, *, reset_iter_counts=True): status = super().run_model(case_prefix, reset_iter_counts) ValidityDomainChecker.check_problem_variables(self) BundleLoader().clean_memory() return status - def run_driver(self, case_prefix=None, reset_iter_counts=True): + def run_driver(self, case_prefix=None, *, reset_iter_counts=True): status = super().run_driver(case_prefix, reset_iter_counts) ValidityDomainChecker.check_problem_variables(self) BundleLoader().clean_memory() @@ -100,7 +101,7 @@ def setup(self, *args, **kwargs): def write_needed_inputs( self, - source_file_path: Union[str, PathLike] = None, + source_file_path: str | PathLike | None = None, source_formatter: IVariableIOFormatter = None, ): """ @@ -138,7 +139,7 @@ def write_needed_inputs( _LOGGER.warning("The following variables have NaN values: %s", nan_variable_names) variables.save() - def write_outputs(self) -> Optional[DataFile]: + def write_outputs(self) -> DataFile | None: """ Writes all outputs in the configured output file. """ @@ -173,7 +174,7 @@ def read_inputs(self): self._set_input_values_after_setup = True @property - def analysis(self) -> "ProblemAnalysis": + def analysis(self) -> ProblemAnalysis: """ Information about inner structure of this problem. @@ -194,7 +195,7 @@ def reset_analysis(self): """ self._analysis = None - def _get_problem_inputs(self) -> Tuple[VariableList, VariableList]: + def _get_problem_inputs(self) -> tuple[VariableList, VariableList]: """ Reads input file for the configured problem. @@ -239,10 +240,9 @@ def _get_remaining_nan_variable_names(self, input_file_variables): nan_input_file_variable_names = { var.name for var in input_file_variables if np.any(np.isnan(var.value)) } - non_filled_variable_names = ( + return ( # non_filled_variable_names default_nan_problem_variable_names - non_nan_input_file_variable_names ) | nan_input_file_variable_names - return non_filled_variable_names def _set_input_values_post_setup(self): """ @@ -286,7 +286,7 @@ def _set_input_values_pre_setup(self): def _insert_input_ivc(self, ivc: om.IndepVarComp, subsystem_name=INPUT_SYSTEM_NAME): self.model.add_subsystem(subsystem_name, ivc, promotes=["*"]) - self.model.set_order([subsystem_name] + self.analysis.subsystem_order) + self.model.set_order([subsystem_name, *self.analysis.subsystem_order]) class AutoUnitsDefaultGroup(om.Group): @@ -333,10 +333,11 @@ def setup(self): def get_variable_list_from_system( system: System, + io_status: str = "all", + *, get_promoted_names: bool = True, promoted_only: bool = True, - io_status: str = "all", -) -> "VariableList": +) -> VariableList: """ Creates a VariableList instance containing inputs and outputs of any OpenMDAO System. @@ -461,12 +462,10 @@ def _get_undetermined_dynamic_vars(problem) -> VariableList: } ) - dynamic_vars = VariableList( + return VariableList( # dynamic_vars [Variable(meta["prom_name"], **meta) for meta in dynamic_vars_metadata.values()] ) - return dynamic_vars - @staticmethod def _get_order_of_subsystems(problem, ignored_system_names=("_auto_ivc", SHAPER_SYSTEM_NAME)): return [ diff --git a/src/fastoad/openmdao/tests/test_problem.py b/src/fastoad/openmdao/tests/test_problem.py index 2166d9d72..be47fd68f 100644 --- a/src/fastoad/openmdao/tests/test_problem.py +++ b/src/fastoad/openmdao/tests/test_problem.py @@ -140,10 +140,10 @@ def test_problem_read_inputs_before_setup(cleanup): assert_allclose(problem["f"], 21.7572, atol=1.0e-4) # We start from solution, so we should converge with only 2 iterations. - iter_count = [ + iter_count = next( iter_desc[2] for iter_desc in problem.iter_count_iter(include_driver=False, include_solvers=True) - ][0] + ) assert iter_count == 2 diff --git a/src/fastoad/openmdao/tests/test_utils.py b/src/fastoad/openmdao/tests/test_utils.py index 1c22583fe..75c4ac4db 100644 --- a/src/fastoad/openmdao/tests/test_utils.py +++ b/src/fastoad/openmdao/tests/test_utils.py @@ -36,10 +36,16 @@ def test_get_unconnected_input_names_single_component_group(): expected_mandatory_variables = {"disc1.x"} expected_optional_variables = {"disc1.y2", "disc1.z"} _test_problem( - om.Problem(group), expected_mandatory_variables, expected_optional_variables, False + om.Problem(group), + expected_mandatory_variables, + expected_optional_variables, + get_promoted_names=False, ) _test_problem( - om.Problem(group), expected_mandatory_variables, expected_optional_variables, True + om.Problem(group), + expected_mandatory_variables, + expected_optional_variables, + get_promoted_names=True, ) @@ -54,10 +60,16 @@ def test_get_unconnected_input_names_one_component_and_ivc(): expected_mandatory_variables = {"disc1.x"} expected_optional_variables = {"disc1.z"} _test_problem( - om.Problem(group), expected_mandatory_variables, expected_optional_variables, False + om.Problem(group), + expected_mandatory_variables, + expected_optional_variables, + get_promoted_names=False, ) _test_problem( - om.Problem(group), expected_mandatory_variables, expected_optional_variables, True + om.Problem(group), + expected_mandatory_variables, + expected_optional_variables, + get_promoted_names=True, ) @@ -72,13 +84,19 @@ def test_get_unconnected_input_names_sellar_components(): expected_mandatory_variables = {"disc1.x", "functions.z"} expected_optional_variables = {"disc1.z", "disc2.z", "functions.x"} _test_problem( - om.Problem(group), expected_mandatory_variables, expected_optional_variables, False + om.Problem(group), + expected_mandatory_variables, + expected_optional_variables, + get_promoted_names=False, ) expected_mandatory_variables = {"z", "x"} expected_optional_variables = set() _test_problem( - om.Problem(group), expected_mandatory_variables, expected_optional_variables, True + om.Problem(group), + expected_mandatory_variables, + expected_optional_variables, + get_promoted_names=True, ) @@ -86,20 +104,27 @@ def test_get_unconnected_input_names_full_sellar(): expected_mandatory_variables = {"objective.z"} expected_optional_variables = {"disc1.z", "disc2.z"} _test_problem( - om.Problem(SellarModel()), expected_mandatory_variables, expected_optional_variables, False + om.Problem(SellarModel()), + expected_mandatory_variables, + expected_optional_variables, + get_promoted_names=False, ) expected_mandatory_variables = {"z"} expected_optional_variables = set() _test_problem( - om.Problem(SellarModel()), expected_mandatory_variables, expected_optional_variables, True + om.Problem(SellarModel()), + expected_mandatory_variables, + expected_optional_variables, + get_promoted_names=True, ) def _test_problem( - problem, - expected_missing_mandatory_variables, - expected_missing_optional_variables, - get_promoted_names, + problem: om.Problem, + expected_missing_mandatory_variables: set, + expected_missing_optional_variables: set, + *, + get_promoted_names: bool, ): """Tests get_unconnected_inputs for provided problem""" diff --git a/src/fastoad/openmdao/tests/test_validity_checker.py b/src/fastoad/openmdao/tests/test_validity_checker.py index 0c735dcd6..69ccca915 100644 --- a/src/fastoad/openmdao/tests/test_validity_checker.py +++ b/src/fastoad/openmdao/tests/test_validity_checker.py @@ -105,7 +105,7 @@ def test_register_checks_instantiation(cleanup): ] ValidityDomainChecker.log_records(records) - with open(log_file_path) as log_file: + with Path.open(log_file_path) as log_file: assert len(log_file.readlines()) == 4 # Check 2 -------------------------------------------------------------------------------------- @@ -149,7 +149,7 @@ def test_register_checks_instantiation(cleanup): ] ValidityDomainChecker.log_records(records) - with open(log_file_path) as log_file: + with Path.open(log_file_path) as log_file: assert len(log_file.readlines()) == 4 # Check 3 -------------------------------------------------------------------------------------- @@ -184,7 +184,7 @@ def test_register_checks_instantiation(cleanup): ] ValidityDomainChecker.log_records(records) - with open(log_file_path) as log_file: + with Path.open(log_file_path) as log_file: assert len(log_file.readlines()) == 0 @@ -243,7 +243,7 @@ def setup(self): ] ValidityDomainChecker.log_records(records) - with open(log_file_path) as log_file: + with Path.open(log_file_path) as log_file: assert len(log_file.readlines()) == 4 diff --git a/src/fastoad/openmdao/tests/test_variables.py b/src/fastoad/openmdao/tests/test_variables.py index 482822a0d..849a6e64e 100644 --- a/src/fastoad/openmdao/tests/test_variables.py +++ b/src/fastoad/openmdao/tests/test_variables.py @@ -15,7 +15,6 @@ # along with this program. If not, see . from pathlib import Path -from typing import List import numpy as np import openmdao.api as om @@ -191,7 +190,7 @@ def test_df_from_to_variables(): assert var == new_var -def _compare_variable_lists(vars: List[Variable], expected_vars: List[Variable]): +def _compare_variable_lists(vars: list[Variable], expected_vars: list[Variable]): def sort_key(v): return v.name @@ -632,8 +631,8 @@ def test_get_variables_from_problem_sellar_without_promotion_with_computation(): def _test_and_check_from_unconnected_inputs( problem: om.Problem, - expected_mandatory_vars: List[Variable], - expected_optional_vars: List[Variable], + expected_mandatory_vars: list[Variable], + expected_optional_vars: list[Variable], ): problem.setup() vars = VariableList.from_unconnected_inputs(problem, with_optional_inputs=False) diff --git a/src/fastoad/openmdao/validity_checker.py b/src/fastoad/openmdao/validity_checker.py index c021c9125..01b450747 100644 --- a/src/fastoad/openmdao/validity_checker.py +++ b/src/fastoad/openmdao/validity_checker.py @@ -1,4 +1,5 @@ """For checking validity domain of OpenMDAO variables.""" + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2024 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -11,6 +12,7 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations import inspect import logging @@ -18,7 +20,7 @@ from collections import namedtuple from dataclasses import dataclass from enum import IntEnum -from typing import Dict, List +from typing import ClassVar from uuid import UUID import numpy as np @@ -111,9 +113,9 @@ class MyComponent(om.ExplicitComponent): - Validity check currently only applies to scalar values """ - _limit_definitions: Dict[UUID, "_LimitDefinitions"] = {} + _limit_definitions: ClassVar[dict[UUID, _LimitDefinitions]] = {} - def __init__(self, limits: Dict[str, tuple] = None, logger_name: str = None): + def __init__(self, limits: dict[str, tuple] | None = None, logger_name: str | None = None): """ :param limits: a dictionary where keys are variable names and values are two-values tuples that give lower and upper bound. One bound can be set to None. @@ -147,7 +149,7 @@ def __call__(self, om_class: type): return om_class @classmethod - def check_problem_variables(cls, problem: om.Problem) -> List[CheckRecord]: + def check_problem_variables(cls, problem: om.Problem) -> list[CheckRecord]: """ Checks variable values in provided problem. @@ -170,8 +172,8 @@ def check_problem_variables(cls, problem: om.Problem) -> List[CheckRecord]: @classmethod def check_variables( - cls, variables: VariableList, activated_only: bool = True - ) -> List[CheckRecord]: + cls, variables: VariableList, *, activated_only: bool = True + ) -> list[CheckRecord]: """ Check values of provided variables against registered limits. @@ -179,7 +181,7 @@ def check_variables( :param activated_only: if True, only activated checkers are considered. :return: the list of checks """ - records: List[CheckRecord] = [] + records: list[CheckRecord] = [] for var in variables: for limit_definitions in cls._limit_definitions.values(): @@ -213,7 +215,7 @@ def check_variables( return records @staticmethod - def log_records(records: List[CheckRecord]): + def log_records(records: list[CheckRecord]): """ Logs warnings through Python logging module for each CheckRecord in provided list if it is not OK. diff --git a/src/fastoad/openmdao/variables/__init__.py b/src/fastoad/openmdao/variables/__init__.py index 7dff4d28b..026d0cd3b 100644 --- a/src/fastoad/openmdao/variables/__init__.py +++ b/src/fastoad/openmdao/variables/__init__.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# flake8: noqa from .variable import Variable from .variable_list import VariableList + +__all__ = ["Variable", "VariableList"] diff --git a/src/fastoad/openmdao/variables/_util.py b/src/fastoad/openmdao/variables/_util.py index 2ebc9970d..5feba8667 100644 --- a/src/fastoad/openmdao/variables/_util.py +++ b/src/fastoad/openmdao/variables/_util.py @@ -13,7 +13,6 @@ # along with this program. If not, see . import itertools -from typing import Tuple import numpy as np from openmdao.core.constants import _SetupStatus @@ -23,9 +22,10 @@ def get_problem_variables( problem, + *, get_promoted_names: bool = True, promoted_only: bool = True, -) -> Tuple[dict, dict]: +) -> tuple[dict, dict]: """ Creates dict instances (var_name vs metadata) containing inputs and outputs of an OpenMDAO Problem. diff --git a/src/fastoad/openmdao/variables/variable.py b/src/fastoad/openmdao/variables/variable.py index ba5f59c3b..dd29ebc29 100644 --- a/src/fastoad/openmdao/variables/variable.py +++ b/src/fastoad/openmdao/variables/variable.py @@ -14,9 +14,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + import logging +from collections.abc import Hashable, Iterable, Mapping from os import PathLike -from typing import Dict, Hashable, Iterable, Mapping, Optional, Tuple, Union +from pathlib import Path +from typing import ClassVar import numpy as np import openmdao.api as om @@ -83,13 +87,13 @@ class Variable(Hashable): """ # Will store content of description files - _variable_descriptions = {} + _variable_descriptions: ClassVar[dict] = {} # The list of modules of path where description files have been read - _loaded_descriptions = set() + _loaded_descriptions: ClassVar[set] = set() # Default metadata - _base_metadata = {} + _base_metadata: ClassVar[dict] = {} def __init__(self, name, **kwargs): super().__init__() @@ -97,7 +101,7 @@ def __init__(self, name, **kwargs): self.name = name """ Name of the variable """ - self.metadata: Dict = {} + self.metadata: dict = {} """ Dictionary for metadata of the variable """ # Initialize class attributes once at first instantiation ------------- @@ -137,7 +141,7 @@ def __init__(self, name, **kwargs): if not self.description and self.name in self._variable_descriptions: self.description = self._variable_descriptions[self.name] - def get_val(self, new_units: Optional[str] = None) -> Union[float, np.ndarray]: + def get_val(self, new_units: str | None = None) -> float | np.ndarray: """Returns the variable value converted in the `new_units`""" if new_units: return scalarize(convert_units(np.asarray(self.value), self.units, new_units)) @@ -145,7 +149,7 @@ def get_val(self, new_units: Optional[str] = None) -> Union[float, np.ndarray]: @classmethod def read_variable_descriptions( - cls, file_parent: Union[str, PathLike], update_existing: bool = True + cls, file_parent: str | PathLike, *, update_existing: bool = True ): """ Reads variable descriptions in indicated folder or package, if it contains some. @@ -174,7 +178,7 @@ def read_variable_descriptions( if file_parent.is_dir(): file_path = file_parent / DESCRIPTION_FILENAME if file_path.is_file(): - description_file = open(file_path) + description_file = Path.open(file_path) else: # Then it is a module name pack_reader = PackageReader(str(file_parent)) @@ -208,7 +212,7 @@ def read_variable_descriptions( @classmethod def update_variable_descriptions( - cls, variable_descriptions: Union[Mapping[str, str], Iterable[Tuple[str, str]]] + cls, variable_descriptions: Mapping[str, str] | Iterable[tuple[str, str]] ): """ Updates description of variables. @@ -290,7 +294,7 @@ def is_input(self): def is_input(self, value): self.metadata["is_input"] = value - def get_openmdao_kwargs(self, keys: Iterable = None) -> dict: + def get_openmdao_kwargs(self, keys: Iterable | None = None) -> dict: """ Provides a dict usable as keyword args by OpenMDAO add_input()/add_output(). @@ -349,7 +353,7 @@ def __eq__(self, other): ) def __repr__(self): - return "Variable(name=%s, metadata=%s)" % (self.name, self.metadata) + return f"Variable(name={self.name}, metadata={self.metadata})" def __hash__(self) -> int: return hash("var=" + self.name) # Name is normally unique diff --git a/src/fastoad/openmdao/variables/variable_list.py b/src/fastoad/openmdao/variables/variable_list.py index 61b2ea472..96ee1f118 100644 --- a/src/fastoad/openmdao/variables/variable_list.py +++ b/src/fastoad/openmdao/variables/variable_list.py @@ -14,8 +14,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations + +from collections.abc import Iterable, Mapping from copy import deepcopy -from typing import Iterable, List, Mapping, Tuple, Union import numpy as np import openmdao.api as om @@ -73,13 +75,13 @@ class VariableList(list): print( 'var/2' in vars_A.names() ) """ - def names(self) -> List[str]: + def names(self) -> list[str]: """ :return: names of variables """ return [var.name for var in self] - def metadata_keys(self) -> List[str]: + def metadata_keys(self) -> list[str]: """ :return: the metadata keys that are common to all variables in the list """ @@ -110,7 +112,7 @@ def add_var(self, name, **kwargs): """ self.append(Variable(name, **kwargs)) - def update(self, other_var_list: list, add_variables: bool = True): + def update(self, other_var_list: list, *, add_variables: bool = True): """ Uses variables in other_var_list to update the current VariableList instance. @@ -175,14 +177,10 @@ def to_dataframe(self) -> pd.DataFrame: metadata = variable.metadata[metadata_name] var_dict[metadata_name].append(metadata) - df = pd.DataFrame.from_dict(var_dict) - - return df + return pd.DataFrame.from_dict(var_dict) @classmethod - def from_dict( - cls, var_dict: Union[Mapping[str, dict], Iterable[Tuple[str, dict]]] - ) -> "VariableList": + def from_dict(cls, var_dict: Mapping[str, dict] | Iterable[tuple[str, dict]]) -> VariableList: """ Creates a VariableList instance from a dict-like object. @@ -197,7 +195,7 @@ def from_dict( return variables @classmethod - def from_ivc(cls, ivc: om.IndepVarComp) -> "VariableList": + def from_ivc(cls, ivc: om.IndepVarComp) -> VariableList: """ Creates a VariableList instance from an OpenMDAO IndepVarComp instance @@ -237,7 +235,7 @@ def _as_list_or_item(cls, value): return value.tolist() @classmethod - def from_dataframe(cls, df: pd.DataFrame) -> "VariableList": + def from_dataframe(cls, df: pd.DataFrame) -> VariableList: """ Creates a VariableList instance from a pandas DataFrame instance. @@ -265,11 +263,12 @@ def _get_variable(row): def from_problem( cls, problem: om.Problem, + io_status: str = "all", + *, use_initial_values: bool = False, get_promoted_names: bool = True, promoted_only: bool = True, - io_status: str = "all", - ) -> "VariableList": + ) -> VariableList: """ Creates a VariableList instance containing inputs and outputs of an OpenMDAO Problem. @@ -281,12 +280,12 @@ def from_problem( Variables from _auto_ivc are ignored. :param problem: OpenMDAO Problem instance to inspect + :param io_status: to choose with type of variable we return ("all", "inputs, "outputs") :param use_initial_values: if True, or if problem has not been run, returned instance will contain values before computation :param get_promoted_names: if True, promoted names will be returned instead of absolute ones (if no promotion, absolute name will be returned) :param promoted_only: if True, only promoted variable names will be returned - :param io_status: to choose with type of variable we return ("all", "inputs, "outputs") :return: VariableList instance """ @@ -331,8 +330,8 @@ def from_problem( reason="Will be removed in version 2.0. Please use VariableList.from_problem() instead", ) def from_unconnected_inputs( - cls, problem: om.Problem, with_optional_inputs: bool = False - ) -> "VariableList": + cls, problem: om.Problem, *, with_optional_inputs: bool = False + ) -> VariableList: """ Creates a VariableList instance containing unconnected inputs of an OpenMDAO Problem. @@ -389,8 +388,7 @@ def _add_outputs(unconnected_names): def __getitem__(self, key) -> Variable: if isinstance(key, str): return self[self.names().index(key)] - else: - return super().__getitem__(key) + return super().__getitem__(key) def __setitem__(self, key, value): if isinstance(key, str): @@ -416,11 +414,10 @@ def __delitem__(self, key): else: super().__delitem__(key) - def __add__(self, other) -> Union[List, "VariableList"]: + def __add__(self, other) -> list | VariableList: if isinstance(other, VariableList): return type(self)(super().__add__(other)) - else: - return super().__add__(other) + return super().__add__(other) def __eq__(self, other) -> bool: return set(self) == set(other) diff --git a/src/fastoad/openmdao/whatsopt.py b/src/fastoad/openmdao/whatsopt.py index 1d988150c..2a0d37be3 100644 --- a/src/fastoad/openmdao/whatsopt.py +++ b/src/fastoad/openmdao/whatsopt.py @@ -1,4 +1,5 @@ """WhatsOpt-related operations.""" + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2024 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -11,9 +12,9 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import annotations from os import PathLike -from typing import Union import openmdao.api as om import whatsopt.whatsopt_client as wop @@ -24,9 +25,10 @@ def write_xdsm( problem: om.Problem, - xdsm_file_path: Union[str, PathLike] = None, + xdsm_file_path: str | PathLike | None = None, depth: int = 2, - wop_server_url: str = None, + wop_server_url: str | None = None, + *, dry_run: bool = False, ): """ diff --git a/src/fastoad/testing.py b/src/fastoad/testing.py index 2c5b50eec..8911fe6bf 100644 --- a/src/fastoad/testing.py +++ b/src/fastoad/testing.py @@ -1,4 +1,5 @@ """Convenience utilities for testing.""" + # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design # Copyright (C) 2024 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify @@ -11,8 +12,7 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . - -from typing import Union +from __future__ import annotations import numpy as np from openmdao import api as om @@ -24,7 +24,7 @@ def run_system( - component: System, input_vars: Union[om.IndepVarComp, VariableList], **kwargs + component: System, input_vars: om.IndepVarComp | VariableList, **kwargs ) -> FASTOADProblem: """ Runs and returns an OpenMDAO problem with provided component and data. @@ -62,7 +62,7 @@ def run_system( if np.any(np.isnan(var.val)) ] - assert not variable_names, "These inputs are not provided: %s" % variable_names + assert not variable_names, f"These inputs are not provided: {variable_names}" problem.run_model() diff --git a/tests/__init__.py b/tests/__init__.py index 1d7fd53cd..5fe298360 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os.path as pth +from pathlib import Path -root_folder_path = pth.dirname(pth.dirname(__file__)) +root_folder_path = Path(__file__).parent.parent """ Path of the whole project folder """ diff --git a/tests/conftest.py b/tests/conftest.py index 06b7581ad..b93314077 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,4 +11,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from src.conftest import no_xfoil_skip, xfoil_path # noqa: F401 +from src.conftest import no_xfoil_skip, xfoil_path + +__all__ = ["no_xfoil_skip", "xfoil_path"] diff --git a/tests/integration_tests/oad_process/test_oad_process.py b/tests/integration_tests/oad_process/test_oad_process.py index ca0e73261..cc5425aee 100644 --- a/tests/integration_tests/oad_process/test_oad_process.py +++ b/tests/integration_tests/oad_process/test_oad_process.py @@ -14,10 +14,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os -import os.path as pth import shutil from dataclasses import dataclass +from pathlib import Path from platform import system from shutil import rmtree @@ -33,8 +32,8 @@ _IConfigurationModifier, ) -DATA_FOLDER_PATH = pth.join(pth.dirname(__file__), "data") -RESULTS_FOLDER_PATH = pth.join(pth.dirname(__file__), "results") +DATA_FOLDER_PATH = Path(__file__).parent / "data" +RESULTS_FOLDER_PATH = Path(__file__).parent / "results" @pytest.fixture(scope="module") @@ -47,10 +46,10 @@ def test_oad_process(cleanup): Test for the overall aircraft design process. """ - configurator = FASTOADProblemConfigurator(pth.join(DATA_FOLDER_PATH, "oad_process.yml")) - configurator.make_local(pth.join(RESULTS_FOLDER_PATH, "test_oad_process")) + configurator = FASTOADProblemConfigurator(DATA_FOLDER_PATH / "oad_process.yml") + configurator.make_local(RESULTS_FOLDER_PATH / "test_oad_process") - ref_inputs = pth.join(DATA_FOLDER_PATH, "CeRAS01_legacy.xml") + ref_inputs = DATA_FOLDER_PATH / "CeRAS01_legacy.xml" problem = configurator.get_problem() problem.write_needed_inputs(ref_inputs) problem.read_inputs() @@ -59,12 +58,11 @@ def test_oad_process(cleanup): problem.run_model() problem.write_outputs() - if not pth.exists(RESULTS_FOLDER_PATH): - os.mkdir(RESULTS_FOLDER_PATH) + RESULTS_FOLDER_PATH.mkdir(parents=True, exist_ok=True) om.view_connections( - problem, outfile=pth.join(RESULTS_FOLDER_PATH, "connections.html"), show_browser=False + problem, outfile=str(RESULTS_FOLDER_PATH / "connections.html"), show_browser=False ) - om.n2(problem, outfile=pth.join(RESULTS_FOLDER_PATH, "n2.html"), show_browser=False) + om.n2(problem, outfile=str(RESULTS_FOLDER_PATH / "n2.html"), show_browser=False) # Check that weight-performances loop correctly converged _check_weight_performance_loop(problem) @@ -114,11 +112,12 @@ def run_non_regression_test( conf_file, legacy_result_file, result_dir, - use_xfoil=False, xfoil_path=None, global_tolerance=1e-2, vars_to_check=None, specific_tolerance=5.0e-3, + *, + use_xfoil=False, check_weight_perfo_loop=True, ): """ @@ -126,26 +125,26 @@ def run_non_regression_test( :param conf_file: FAST-OAD configuration file :param legacy_result_file: reference data for inputs and outputs :param result_dir: relative name, folder will be in RESULTS_FOLDER_PATH - :param use_xfoil: if True, XFOIL computation will be activated :param xfoil_path: used if use_xfoil==True :param vars_to_check: variables that will be concerned by specific_tolerance :param specific_tolerance: test will fail if absolute relative error between computed and reference values is beyond this value for variables in vars_to_check :param global_tolerance: test will fail if absolute relative error between computed and reference values is beyond this value for ANY variable + :param use_xfoil: if True, XFOIL computation will be activated :param check_weight_perfo_loop: if True, consistency of weights will be checked """ - results_folder_path = pth.join(RESULTS_FOLDER_PATH, result_dir) - configuration_file_path = pth.join(results_folder_path, conf_file) + results_folder_path = RESULTS_FOLDER_PATH / result_dir + configuration_file_path = results_folder_path / conf_file # Copy of configuration file and generation of problem instance ------------------ oad.generate_configuration_file(configuration_file_path) # just ensure folders are created... - shutil.copy(pth.join(DATA_FOLDER_PATH, conf_file), configuration_file_path) + shutil.copy(DATA_FOLDER_PATH / conf_file, configuration_file_path) configurator = FASTOADProblemConfigurator(configuration_file_path) configurator._set_configuration_modifier(XFOILConfigurator(use_xfoil, xfoil_path)) # Generation of inputs ---------------------------------------- - ref_inputs = pth.join(DATA_FOLDER_PATH, legacy_result_file) + ref_inputs = DATA_FOLDER_PATH / legacy_result_file configurator.write_needed_inputs(ref_inputs) # Get problem with inputs ------------------------------------- @@ -157,13 +156,13 @@ def run_non_regression_test( problem.write_outputs() om.view_connections( - problem, outfile=pth.join(results_folder_path, "connections.html"), show_browser=False + problem, outfile=str(results_folder_path / "connections.html"), show_browser=False ) if check_weight_perfo_loop: _check_weight_performance_loop(problem) - ref_data = oad.DataFile(pth.join(DATA_FOLDER_PATH, legacy_result_file)) + ref_data = oad.DataFile(DATA_FOLDER_PATH / legacy_result_file) row_list = [] for ref_var in ref_data: @@ -200,19 +199,19 @@ def run_non_regression_test( def test_api_eval_breguet(cleanup): - results_folder_path = pth.join(RESULTS_FOLDER_PATH, "api_eval_breguet") - configuration_file_path = pth.join(results_folder_path, "oad_process.yml") + results_folder_path = RESULTS_FOLDER_PATH / "api_eval_breguet" + configuration_file_path = results_folder_path / "oad_process.yml" # Generation of configuration file ---------------------------------------- - oad.generate_configuration_file(configuration_file_path, True) + oad.generate_configuration_file(configuration_file_path, overwrite=True) # Generation of inputs ---------------------------------------------------- # We get the same inputs as in tutorial notebook - source_xml = pth.join(DATA_FOLDER_PATH, "CeRAS01_notebooks.xml") + source_xml = DATA_FOLDER_PATH / "CeRAS01_notebooks.xml" oad.generate_inputs(configuration_file_path, source_xml, overwrite=True) # Run model --------------------------------------------------------------- - problem = oad.evaluate_problem(configuration_file_path, True) + problem = oad.evaluate_problem(configuration_file_path, overwrite=True) # Check that weight-performances loop correctly converged _check_weight_performance_loop(problem) @@ -229,19 +228,19 @@ def test_api_eval_breguet(cleanup): def test_api_optim(cleanup): - results_folder_path = pth.join(RESULTS_FOLDER_PATH, "api_optim") - configuration_file_path = pth.join(results_folder_path, "oad_process.yml") + results_folder_path = RESULTS_FOLDER_PATH / "api_optim" + configuration_file_path = results_folder_path / "oad_process.yml" # Generation of configuration file ---------------------------------------- - oad.generate_configuration_file(configuration_file_path, True) + oad.generate_configuration_file(configuration_file_path, overwrite=True) # Generation of inputs ---------------------------------------------------- # We get the same inputs as in tutorial notebook - source_xml = pth.join(DATA_FOLDER_PATH, "CeRAS01_notebooks.xml") + source_xml = DATA_FOLDER_PATH / "CeRAS01_notebooks.xml" oad.generate_inputs(configuration_file_path, source_xml, overwrite=True) # Run optim --------------------------------------------------------------- - problem = oad.optimize_problem(configuration_file_path, True) + problem = oad.optimize_problem(configuration_file_path, overwrite=True) assert not problem.optim_failed # Check that weight-performances loop correctly converged diff --git a/tests/memory_tests/test_multiple_runs.py b/tests/memory_tests/test_multiple_runs.py index 8ade27ff4..9d18bc52c 100644 --- a/tests/memory_tests/test_multiple_runs.py +++ b/tests/memory_tests/test_multiple_runs.py @@ -11,9 +11,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import gc -import os.path as pth import tracemalloc -from os import makedirs +from pathlib import Path from shutil import rmtree import openmdao.api as om @@ -21,16 +20,14 @@ import fastoad.api as oad -DATA_FOLDER_PATH = pth.join(pth.dirname(__file__), "data") -RESULTS_FOLDER_PATH = pth.join( - pth.dirname(__file__), "results", pth.splitext(pth.basename(__file__))[0] -) +DATA_FOLDER_PATH = Path(__file__).parent / "data" +RESULTS_FOLDER_PATH = Path(__file__).parent / "results" / Path(__file__).stem @pytest.fixture(scope="module") def cleanup(): rmtree(RESULTS_FOLDER_PATH, ignore_errors=True) - makedirs(RESULTS_FOLDER_PATH) + RESULTS_FOLDER_PATH.mkdir(parents=True, exist_ok=True) def print_stat_diffs(snapshot1, snapshot2): @@ -49,12 +46,12 @@ def print_memory_state(tag): def run_problem(): - configurator = oad.FASTOADProblemConfigurator(pth.join(DATA_FOLDER_PATH, "oad_process.yml")) - configurator.input_file_path = pth.join(RESULTS_FOLDER_PATH, "inputs.xml") - configurator.output_file_path = pth.join(RESULTS_FOLDER_PATH, "outputs.xml") + configurator = oad.FASTOADProblemConfigurator(DATA_FOLDER_PATH / "oad_process.yml") + configurator.input_file_path = RESULTS_FOLDER_PATH / "inputs.xml" + configurator.output_file_path = RESULTS_FOLDER_PATH / "outputs.xml" print_memory_state("After reading configuration file") - ref_inputs = pth.join(DATA_FOLDER_PATH, "CeRAS01.xml") + ref_inputs = DATA_FOLDER_PATH / "CeRAS01.xml" configurator.write_needed_inputs(ref_inputs) print_memory_state("After writing input file") From fedffa95b0de1f511ae2f7bb1e4240ed45071e77 Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Wed, 2 Apr 2025 15:51:21 +0200 Subject: [PATCH 04/20] Better commenting on hard to understand code --- src/fastoad/io/configuration/configuration.py | 2 +- .../mission_builder/structure_builders.py | 42 +++++++++++-------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/fastoad/io/configuration/configuration.py b/src/fastoad/io/configuration/configuration.py index c8b623cd4..c8990bfa1 100644 --- a/src/fastoad/io/configuration/configuration.py +++ b/src/fastoad/io/configuration/configuration.py @@ -323,7 +323,7 @@ def _make_options_local( self, structure: dict, new_root_path: Path, - local_path: Path = Path("."), # noqa: PTH201 + local_path: Path = Path(), ): """ Recursively modifies `structure` to make each path-like value local with respect to diff --git a/src/fastoad/models/performances/mission/mission_definition/mission_builder/structure_builders.py b/src/fastoad/models/performances/mission/mission_definition/mission_builder/structure_builders.py index 0f902f2e9..f3d8d8f8f 100644 --- a/src/fastoad/models/performances/mission/mission_definition/mission_builder/structure_builders.py +++ b/src/fastoad/models/performances/mission/mission_definition/mission_builder/structure_builders.py @@ -18,12 +18,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import typing from abc import ABC, abstractmethod from copy import deepcopy from dataclasses import InitVar, dataclass, field from itertools import chain -from typing import ClassVar, get_type_hints +from typing import ClassVar, get_args, get_origin, get_type_hints import numpy as np @@ -228,40 +227,47 @@ def _parse_inputs( @staticmethod def _is_shape_by_conn(key, segment_class) -> bool: """ + Here variables that are expected to be arrays or lists in the provided segment class are + attributed the "shape_by_conn=True" property. Check if a field in `segment_class` is a list or NumPy array. - Works with `from __future__ import annotations` and handles ClassVar properly. + Works with `from __future__ import annotations` and properly handles the ClassVars + defined in `segment_class`. """ - # Retrieve type annotations using get_type_hints(). - # WHY? Because `from __future__ import annotations` makes type hints **lazy** (stored as strings), - # and get_type_hints() converts them back into real types. - # TODO https://peps.python.org/pep-0649/ will simplify all this + try: + # Resolve type annotations to handle cases where they are stored as strings due to + # `from __future__ import annotations`. This ensures we work with real types. + # TODO once https://peps.python.org/pep-0649/ is implemented, get_type_hints should + # not be necessary anymore. type_hints = get_type_hints(segment_class) + if key not in type_hints: - return False + return False # If the field does not exist, return False immediately. field_type = type_hints[key] - # Handle ClassVar if present, issubclass() - # does not accept ClassVar as a valid type - origin = typing.get_origin(field_type) + # Handle ClassVar, which wraps types but is not itself a valid type for `issubclass()`. + origin = get_origin(field_type) if origin is ClassVar: - field_type = typing.get_args(field_type)[0] - origin = typing.get_origin(field_type) + field_type = get_args(field_type)[0] # Extract the real type inside ClassVar. + origin = get_origin(field_type) # Re-evaluate origin in case it's a generic type. - # Check for list + # If the field is explicitly declared as a `list` (e.g., `list[int]`), return True. if origin is not None and origin is list: return True - # Direct type check for list or ndarray + # Check if the field is a subclass of `list` or `np.ndarray`. + # This handles cases where the type is directly `list` or `np.ndarray` without generics. if isinstance(field_type, type): try: return issubclass(field_type, (list, np.ndarray)) except TypeError: - pass - return False + pass # Some types cannot be used in `issubclass()`, so we ignore errors. + + return False # If no conditions matched, return False. + except (TypeError, NameError): - return False + return False # Catch errors from missing imports or invalid type hints. def _process_polar(self, structure): """ From 3e854ade1545611dead24d6a0c4f1426065fce08 Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Wed, 2 Apr 2025 17:14:19 +0200 Subject: [PATCH 05/20] Fixed CS25 errors, no more breaking changes --- src/fastoad/cmd/api.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/fastoad/cmd/api.py b/src/fastoad/cmd/api.py index 8eb94b8cd..d670b129b 100644 --- a/src/fastoad/cmd/api.py +++ b/src/fastoad/cmd/api.py @@ -91,8 +91,8 @@ def get_plugin_information(print_data=False) -> dict[str, DistributionPluginDefi def generate_notebooks( destination_path: str | PathLike, - distribution_name=None, overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions + distribution_name=None, ): """ Copies notebook folder(s) from available plugin(s). @@ -100,8 +100,8 @@ def generate_notebooks( :param destination_path: the inner structure of the folders will depend on the number of installed package and the number of plugins they contain. - :param distribution_name: the name of an installed package that provides notebooks :param overwrite: if True and `destination_path` exists, it will be removed before writing. + :param distribution_name: the name of an installed package that provides notebooks """ destination_path = as_path(destination_path) @@ -243,19 +243,19 @@ def _generate_user_file( def generate_configuration_file( configuration_file_path: str | PathLike, + overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions distribution_name=None, sample_file_name=None, - overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions ): """ Copies a sample configuration file from an available plugin. :param configuration_file_path: the path of file to be written + :param overwrite: if True, the file will be written, even if it already exists :param distribution_name: the name of the installed package that provides the sample configuration file (can be omitted if only one plugin is available) :param sample_file_name: the name of the sample configuration file (can be omitted if the plugin provides only one configuration file) - :param overwrite: if True, the file will be written, even if it already exists :return: path of generated file :raise FastPathExistsError: if overwrite==False and configuration_file_path already exists """ @@ -271,19 +271,19 @@ def generate_configuration_file( def generate_source_data_file( source_data_file_path: str | PathLike, + overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions distribution_name=None, sample_file_name=None, - overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions ): """ Copies a sample source data file from an available plugin. :param source_data_file_path: the path of file to be written + :param overwrite: if True, the file will be written, even if it already exists :param distribution_name: the name of the installed package that provides the sample source data file (can be omitted if only one plugin is available) :param sample_file_name: the name of the sample source data file (can be omitted if the plugin provides only one source data file) - :param overwrite: if True, the file will be written, even if it already exists :return: path of generated file :raise FastPathExistsError: if overwrite==False and source_file_path already exists """ @@ -336,9 +336,9 @@ def generate_inputs( def list_variables( configuration_file_path: str | PathLike, out: str | PathLike | TextIO | None = None, - tablefmt: str = "grid", overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions force_text_output: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions + tablefmt: str = "grid", ): """ Writes list of variables for the problem specified in configuration_file_path. @@ -350,14 +350,14 @@ def list_variables( :param configuration_file_path: :param out: the output stream or a path for the output file (None means sys.stdout) - :param tablefmt: The formatting of the requested table. Options are the same as those available - to the tabulate package. See tabulate.tabulate_formats for a complete list. - If "var_desc" the file will use the variable_descriptions.txt format. :param overwrite: if True and out parameter is a file path, the file will be written even if one already exists :param force_text_output: if True, list will be written as text, even if command is used in an interactive IPython shell (Jupyter notebook). Has no effect in other shells or if out parameter is not sys.stdout + :param tablefmt: The formatting of the requested table. Options are the same as those available + to the tabulate package. See tabulate.tabulate_formats for a complete list. + If "var_desc" the file will use the variable_descriptions.txt format. :return: path of generated file, or None if no file was generated. :raise FastPathExistsError: if `overwrite==False` and `out` is a file path and the file exists """ From 9d4b868df5b1f512dfd0e6ee8131214a06c45fc6 Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Thu, 3 Apr 2025 09:56:52 +0200 Subject: [PATCH 06/20] Completely reworking and reverting the proposed _is_shape_by_conn method --- .../mission_builder/structure_builders.py | 45 +++---------------- 1 file changed, 7 insertions(+), 38 deletions(-) diff --git a/src/fastoad/models/performances/mission/mission_definition/mission_builder/structure_builders.py b/src/fastoad/models/performances/mission/mission_definition/mission_builder/structure_builders.py index f3d8d8f8f..296ed937d 100644 --- a/src/fastoad/models/performances/mission/mission_definition/mission_builder/structure_builders.py +++ b/src/fastoad/models/performances/mission/mission_definition/mission_builder/structure_builders.py @@ -20,9 +20,8 @@ from abc import ABC, abstractmethod from copy import deepcopy -from dataclasses import InitVar, dataclass, field +from dataclasses import InitVar, dataclass, field, fields from itertools import chain -from typing import ClassVar, get_args, get_origin, get_type_hints import numpy as np @@ -230,44 +229,14 @@ def _is_shape_by_conn(key, segment_class) -> bool: Here variables that are expected to be arrays or lists in the provided segment class are attributed the "shape_by_conn=True" property. Check if a field in `segment_class` is a list or NumPy array. - Works with `from __future__ import annotations` and properly handles the ClassVars - defined in `segment_class`. """ - + segment_fields = [fld for fld in fields(segment_class) if fld.name == key] try: - # Resolve type annotations to handle cases where they are stored as strings due to - # `from __future__ import annotations`. This ensures we work with real types. - # TODO once https://peps.python.org/pep-0649/ is implemented, get_type_hints should - # not be necessary anymore. - type_hints = get_type_hints(segment_class) - - if key not in type_hints: - return False # If the field does not exist, return False immediately. - - field_type = type_hints[key] - - # Handle ClassVar, which wraps types but is not itself a valid type for `issubclass()`. - origin = get_origin(field_type) - if origin is ClassVar: - field_type = get_args(field_type)[0] # Extract the real type inside ClassVar. - origin = get_origin(field_type) # Re-evaluate origin in case it's a generic type. - - # If the field is explicitly declared as a `list` (e.g., `list[int]`), return True. - if origin is not None and origin is list: - return True - - # Check if the field is a subclass of `list` or `np.ndarray`. - # This handles cases where the type is directly `list` or `np.ndarray` without generics. - if isinstance(field_type, type): - try: - return issubclass(field_type, (list, np.ndarray)) - except TypeError: - pass # Some types cannot be used in `issubclass()`, so we ignore errors. - - return False # If no conditions matched, return False. - - except (TypeError, NameError): - return False # Catch errors from missing imports or invalid type hints. + return len(segment_fields) == 1 and issubclass( + segment_fields[0].type, (list, np.ndarray) + ) + except TypeError: + return len(segment_fields) == 1 and isinstance(segment_fields[0], (list, np.ndarray)) def _process_polar(self, structure): """ From 6e14aaac45655c1070fe92ae013b0eee9f5d0b6a Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Wed, 11 Jun 2025 11:53:53 +0200 Subject: [PATCH 07/20] Ruff dependency update --- poetry.lock | 40 +++++++++++----------- pyproject.toml | 2 +- src/fastoad/openmdao/variables/variable.py | 6 ++-- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/poetry.lock b/poetry.lock index 762476e47..210af4c8b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3066,29 +3066,29 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [[package]] name = "ruff" -version = "0.9.9" +version = "0.12.0" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.9.9-py3-none-linux_armv6l.whl", hash = "sha256:628abb5ea10345e53dff55b167595a159d3e174d6720bf19761f5e467e68d367"}, - {file = "ruff-0.9.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6cd1428e834b35d7493354723543b28cc11dc14d1ce19b685f6e68e07c05ec7"}, - {file = "ruff-0.9.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5ee162652869120ad260670706f3cd36cd3f32b0c651f02b6da142652c54941d"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aa0f6b75082c9be1ec5a1db78c6d4b02e2375c3068438241dc19c7c306cc61a"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:584cc66e89fb5f80f84b05133dd677a17cdd86901d6479712c96597a3f28e7fe"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf3369325761a35aba75cd5c55ba1b5eb17d772f12ab168fbfac54be85cf18c"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3403a53a32a90ce929aa2f758542aca9234befa133e29f4933dcef28a24317be"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:18454e7fa4e4d72cffe28a37cf6a73cb2594f81ec9f4eca31a0aaa9ccdfb1590"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fadfe2c88724c9617339f62319ed40dcdadadf2888d5afb88bf3adee7b35bfb"}, - {file = "ruff-0.9.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6df104d08c442a1aabcfd254279b8cc1e2cbf41a605aa3e26610ba1ec4acf0b0"}, - {file = "ruff-0.9.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d7c62939daf5b2a15af48abbd23bea1efdd38c312d6e7c4cedf5a24e03207e17"}, - {file = "ruff-0.9.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9494ba82a37a4b81b6a798076e4a3251c13243fc37967e998efe4cce58c8a8d1"}, - {file = "ruff-0.9.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4efd7a96ed6d36ef011ae798bf794c5501a514be369296c672dab7921087fa57"}, - {file = "ruff-0.9.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ab90a7944c5a1296f3ecb08d1cbf8c2da34c7e68114b1271a431a3ad30cb660e"}, - {file = "ruff-0.9.9-py3-none-win32.whl", hash = "sha256:6b4c376d929c25ecd6d87e182a230fa4377b8e5125a4ff52d506ee8c087153c1"}, - {file = "ruff-0.9.9-py3-none-win_amd64.whl", hash = "sha256:837982ea24091d4c1700ddb2f63b7070e5baec508e43b01de013dc7eff974ff1"}, - {file = "ruff-0.9.9-py3-none-win_arm64.whl", hash = "sha256:3ac78f127517209fe6d96ab00f3ba97cafe38718b23b1db3e96d8b2d39e37ddf"}, - {file = "ruff-0.9.9.tar.gz", hash = "sha256:0062ed13f22173e85f8f7056f9a24016e692efeea8704d1a5e8011b8aa850933"}, + {file = "ruff-0.12.0-py3-none-linux_armv6l.whl", hash = "sha256:5652a9ecdb308a1754d96a68827755f28d5dfb416b06f60fd9e13f26191a8848"}, + {file = "ruff-0.12.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:05ed0c914fabc602fc1f3b42c53aa219e5736cb030cdd85640c32dbc73da74a6"}, + {file = "ruff-0.12.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:07a7aa9b69ac3fcfda3c507916d5d1bca10821fe3797d46bad10f2c6de1edda0"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7731c3eec50af71597243bace7ec6104616ca56dda2b99c89935fe926bdcd48"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:952d0630eae628250ab1c70a7fffb641b03e6b4a2d3f3ec6c1d19b4ab6c6c807"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c021f04ea06966b02614d442e94071781c424ab8e02ec7af2f037b4c1e01cc82"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d235618283718ee2fe14db07f954f9b2423700919dc688eacf3f8797a11315c"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0758038f81beec8cc52ca22de9685b8ae7f7cc18c013ec2050012862cc9165"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:139b3d28027987b78fc8d6cfb61165447bdf3740e650b7c480744873688808c2"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68853e8517b17bba004152aebd9dd77d5213e503a5f2789395b25f26acac0da4"}, + {file = "ruff-0.12.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3a9512af224b9ac4757f7010843771da6b2b0935a9e5e76bb407caa901a1a514"}, + {file = "ruff-0.12.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b08df3d96db798e5beb488d4df03011874aff919a97dcc2dd8539bb2be5d6a88"}, + {file = "ruff-0.12.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6a315992297a7435a66259073681bb0d8647a826b7a6de45c6934b2ca3a9ed51"}, + {file = "ruff-0.12.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e55e44e770e061f55a7dbc6e9aed47feea07731d809a3710feda2262d2d4d8a"}, + {file = "ruff-0.12.0-py3-none-win32.whl", hash = "sha256:7162a4c816f8d1555eb195c46ae0bd819834d2a3f18f98cc63819a7b46f474fb"}, + {file = "ruff-0.12.0-py3-none-win_amd64.whl", hash = "sha256:d00b7a157b8fb6d3827b49d3324da34a1e3f93492c1f97b08e222ad7e9b291e0"}, + {file = "ruff-0.12.0-py3-none-win_arm64.whl", hash = "sha256:8cd24580405ad8c1cc64d61725bca091d6b6da7eb3d36f72cc605467069d7e8b"}, + {file = "ruff-0.12.0.tar.gz", hash = "sha256:4d047db3662418d4a848a3fdbfaf17488b34b62f527ed6f10cb8afd78135bc5c"}, ] [[package]] @@ -3924,4 +3924,4 @@ mpi4py = ["mpi4py"] [metadata] lock-version = "2.0" python-versions = ">=3.9, <3.13" -content-hash = "cc816b8c94f5f8ad9f23b321bddad913c3833efd2931b98b2c02c35ef59ac4c7" +content-hash = "fac91f0e6908266809312b669ac69c892108058141e6a1f53cba75956a4a43a6" diff --git a/pyproject.toml b/pyproject.toml index d9ee8fdcd..36775cdd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,7 +123,7 @@ sphinxcontrib-bibtex = "^2.6.3" [tool.poetry.group.lint.dependencies] pre-commit = "^3.5.0" nbstripout = "^0.6.0" -ruff = "0.9.9" +ruff = "0.12.00" # Entry points ----------------------------------------------------------------- [tool.poetry.scripts] diff --git a/src/fastoad/openmdao/variables/variable.py b/src/fastoad/openmdao/variables/variable.py index d044549c1..fa6a36521 100644 --- a/src/fastoad/openmdao/variables/variable.py +++ b/src/fastoad/openmdao/variables/variable.py @@ -348,10 +348,8 @@ def __eq__(self, other): # Let's also ignore unimportant keys for key in METADATA_TO_IGNORE: - if key in my_metadata: - del my_metadata[key] - if key in other_metadata: - del other_metadata[key] + my_metadata.pop(key, default=None) + other_metadata.pop(key, default=None) return ( isinstance(other, Variable) From 3286df762b9dfabd5105a91336ebf8df879fe044 Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:19:15 +0200 Subject: [PATCH 08/20] [skip ci] added new batch of rules --- ruff.toml | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/ruff.toml b/ruff.toml index 165a47801..3fb152f3d 100644 --- a/ruff.toml +++ b/ruff.toml @@ -14,18 +14,43 @@ select = [ "FA", # Check if a type is used in the module that can be rewritten using PEP 563. "PTH", # A plugin for flake8 finding use of functions that can be replaced by pathlib module. "RUF", # Ruff-specific rules. + "C4", # Catch incorrect use of comprehensions, dict, list, etc. + "A", # Detect shadowed builtins. + "COM", # Detect missing trailing comma (usually is implemented in the ruff formatter). + "ISC", # Good use of string concatenation. + "ERA", # Find large commented-out code. + "PD", # Provides opinionated linting for pandas code. + "SIM", # flake8-simplify. + "ICN", # Use common import conventions eg. no import numpy as nump. + "NPY", # Some numpy-specific things. + "PL", # Pylint checks for errors, enforces a coding standard, looks for code smells, and can make suggestions about how the code could be refactored + "S", # Bandit: automated security testing built right into your workflow. + "N", # Enforce PEP8 naming convention. + "E", # pycodestyle is a tool to check your Python code against some of the style conventions in PEP 8. + # "ANN", # Enforce function annotations. + # "D", # Pydocstyle is a static analysis tool for checking compliance with Python docstring conventions. + # "DOC", # Pydoclint (still in preview so not yet active). "E4", "E7", "E9", ] # No rules are ignored by default. Specify rule codes here to suppress them. -ignore = [] +ignore = [ + "ANN002", # No type annotation for *args + "ANN003", # No type annotation for *kwargs + "ANN204", # No type annotation on "special" methods, like __init__ + "N806", # Checks for the use of non-lowercase variable names in functions. + "D105", # Missing docstring in magic method + "D212", #Multi-line docstring summary should start at the first line +] # Allow fix for all enabled rules (when `--fix`) is provided. fixable = ["ALL"] -unfixable = [] +unfixable = [ + "D", # Dont fix docstyle +] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" @@ -34,7 +59,46 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" # We prefer clarity over efficency in notebook examples "*.ipynb" = ["RET504"] "*/fastoad/notebooks/*" = ["RET504"] +# Tests +"tests/*" = [ + "PLR0913", # Too many arguments in function definition + "PLR0915", # Too many statements + "PLR2004", # Enabling magic value to be used in test comparison + "S101", # Enable assert in tests + "ANN", # No annotation on tests + "D", # No doc in tests +] +"*/test*.py" = [ + "PLR0913", # Too many arguments in function definition + "PLR0915", # Too many statements + "PLR2004", # Enabling magic value to be used in test comparison + "S101", # Enable assert in tests + "ANN", # No annotation on tests + "D", # No doc in tests +] [lint.isort] # Add optional configurations for import organization case-sensitive = true relative-imports-order = "closest-to-furthest" + +[lint.pylint] # Add optional configurations for pylint +max-args = 8 + +[lint.pep8-naming] # A list of names (or patterns) to ignore when considering pep8-naming violations. Supports glob patterns. +ignore-names = [ + "*MTOW*", + "*TOW*", + "*OWE*", + "*2D*", + "*3D*", + "*AR*", + "*CL*", + "*CD*", + "*TAS*", + "*EAS*", + "*IAS*", + "*CAS*", + "*Mach*", + "*MPI*", + "I_*", +] From d163dcf011050c5153aedfe8daf67d610123e83d Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Fri, 4 Jul 2025 16:58:21 +0200 Subject: [PATCH 09/20] Implemented the fix for the new rules. Tests pass in local --- devutils/rename_vars.py | 4 +- docs/conf.py | 2 +- docs/directives/segment_attributes.py | 7 +- ruff.toml | 21 +- src/conftest.py | 19 +- .../_utils/resource_management/contents.py | 2 +- src/fastoad/_utils/sellar/disc1.py | 1 - src/fastoad/_utils/sellar/disc2.py | 1 - .../tests/test_get_float_list_from_string.py | 12 +- src/fastoad/cmd/api.py | 74 ++-- src/fastoad/cmd/cli.py | 2 +- src/fastoad/cmd/tests/test_cli.py | 14 +- src/fastoad/exceptions.py | 2 +- src/fastoad/gui/analysis_and_plots.py | 69 ++-- src/fastoad/gui/exceptions.py | 2 +- src/fastoad/gui/mission_viewer.py | 3 - src/fastoad/gui/optimization_viewer.py | 67 ++-- .../gui/tests/test_analysis_and_plots.py | 12 +- .../gui/tests/test_optimization_viewer.py | 4 +- src/fastoad/gui/tests/test_variable_viewer.py | 1 - src/fastoad/gui/variable_viewer.py | 21 +- src/fastoad/io/configuration/configuration.py | 12 +- .../configuration/tests/test_configuration.py | 9 +- src/fastoad/io/variable_io.py | 8 +- src/fastoad/io/xml/exceptions.py | 4 +- src/fastoad/io/xml/tests/test_translator.py | 10 +- .../io/xml/tests/test_variable_io_base.py | 16 +- .../io/xml/tests/test_variable_io_standard.py | 114 +++--- src/fastoad/io/xml/translator.py | 13 +- src/fastoad/io/xml/variable_io_base.py | 2 +- src/fastoad/model_base/atmosphere.py | 1 - src/fastoad/model_base/flight_point.py | 2 - src/fastoad/model_base/propulsion.py | 15 +- .../model_base/tests/test_atmosphere.py | 3 +- .../model_base/tests/test_flight_point.py | 4 +- .../models/performances/mission/exceptions.py | 8 +- .../models/performances/mission/mission.py | 4 +- .../mission_builder/input_definition.py | 9 +- .../mission_builder/mission_builder.py | 12 +- .../tests/test_mission_builder.py | 8 +- .../performances/mission/openmdao/base.py | 3 - .../mission/openmdao/mission_run.py | 15 +- .../mission/openmdao/tests/test_mission.py | 54 +-- .../openmdao/tests/test_mission_run.py | 3 +- .../openmdao/tests/test_mission_takeoff.py | 14 +- .../openmdao/tests/test_sizing_mission.py | 3 + .../performances/mission/segments/base.py | 13 +- .../mission/segments/macro_segments.py | 6 +- .../segments/registered/altitude_change.py | 10 +- .../registered/ground_speed_change.py | 6 +- .../segments/registered/speed_change.py | 6 +- .../mission/segments/registered/start.py | 6 +- .../registered/takeoff/end_of_takeoff.py | 6 +- .../registered/tests/test_altitude_change.py | 2 +- .../registered/tests/test_takeoff_segments.py | 4 +- .../mission/segments/time_step_base.py | 2 +- .../performances/mission/tests/conftest.py | 50 +-- .../mission/tests/test_flight_sequence.py | 2 +- .../mission/tests/test_mission.py | 8 +- .../performances/mission/tests/test_routes.py | 5 +- .../models/performances/mission/util.py | 5 +- .../module_management/_bundle_loader.py | 9 +- src/fastoad/module_management/_plugins.py | 16 +- src/fastoad/module_management/exceptions.py | 6 +- .../module_management/service_registry.py | 7 +- .../tests/test_bundle_loader.py | 2 +- .../01_Quick_start/modules/stresses.py | 5 +- .../02_pure_Python.ipynb | 2 +- .../05_FAST-OAD.ipynb | 4 +- .../sub_components/compute_profile_drag.py | 3 +- .../compute_lift_to_drag_ratio.py | 3 +- .../python_for_scipy/compute_fuel_scipy.py | 4 +- src/fastoad/openmdao/exceptions.py | 2 +- src/fastoad/openmdao/tests/test_variables.py | 339 +++++++++--------- src/fastoad/openmdao/variables/_util.py | 6 +- src/fastoad/openmdao/variables/variable.py | 5 +- .../openmdao/variables/variable_list.py | 28 +- .../oad_process/test_oad_process.py | 13 +- 78 files changed, 629 insertions(+), 642 deletions(-) diff --git a/devutils/rename_vars.py b/devutils/rename_vars.py index 318647618..4ca682152 100644 --- a/devutils/rename_vars.py +++ b/devutils/rename_vars.py @@ -64,8 +64,8 @@ def convert_xml(file_path: str, translator: VarXpathTranslator): :param translator: """ reader = VariableIO(file_path, formatter=VariableXmlBaseFormatter(translator)) - vars = reader.read() - VariableIO(file_path).write(vars) + variables = reader.read() + VariableIO(file_path).write(variables) def replace_var_names(file_path, var_names_match): diff --git a/docs/conf.py b/docs/conf.py index 5f9467e48..a2471228a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,7 +55,7 @@ def setup(app): # -- Project information ----------------------------------------------------- project = "FAST-OAD" -copyright = "2025, ONERA & ISAE-SUPAERO" +copyright = "2025, ONERA & ISAE-SUPAERO" # noqa: A001 copyright is a keyword for the sphinx setup # -- General configuration --------------------------------------------------- diff --git a/docs/directives/segment_attributes.py b/docs/directives/segment_attributes.py index 50e9bc0b7..6eeec0732 100644 --- a/docs/directives/segment_attributes.py +++ b/docs/directives/segment_attributes.py @@ -96,7 +96,7 @@ def get_text_and_targets(self): attribute_name = self.content[0].strip() class_dict = RegisterSegment.get_classes() - segment_keywords = sorted(list(class_dict)) + segment_keywords = sorted(class_dict) valid_keywords = [ keyword for keyword in segment_keywords if hasattr(class_dict[keyword], attribute_name) @@ -153,8 +153,9 @@ def check_targets(app, doctree): continue child_nodes = _generate_hyperlink_list(app, doctree, target_list) - - directive_location += child_nodes + # Reassigning directive_location by appending children directly to the docutils node. + # This is intentional and required to inject generated content into the document tree. + directive_location += child_nodes # noqa: PLW2901 def _generate_hyperlink_list(app, doctree, target_list): diff --git a/ruff.toml b/ruff.toml index 3fb152f3d..0c5d83891 100644 --- a/ruff.toml +++ b/ruff.toml @@ -16,7 +16,6 @@ select = [ "RUF", # Ruff-specific rules. "C4", # Catch incorrect use of comprehensions, dict, list, etc. "A", # Detect shadowed builtins. - "COM", # Detect missing trailing comma (usually is implemented in the ruff formatter). "ISC", # Good use of string concatenation. "ERA", # Find large commented-out code. "PD", # Provides opinionated linting for pandas code. @@ -42,7 +41,9 @@ ignore = [ "ANN204", # No type annotation on "special" methods, like __init__ "N806", # Checks for the use of non-lowercase variable names in functions. "D105", # Missing docstring in magic method - "D212", #Multi-line docstring summary should start at the first line + "D212", # Multi-line docstring summary should start at the first line + "PD901", # df is ok as a name + "PD002", # We want to use inplace for pandas ] # Allow fix for all enabled rules (when `--fix`) is provided. @@ -56,15 +57,24 @@ unfixable = [ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" [lint.per-file-ignores] -# We prefer clarity over efficency in notebook examples -"*.ipynb" = ["RET504"] -"*/fastoad/notebooks/*" = ["RET504"] + +"*.ipynb" = [ + "RET504", # We prefer clarity over efficency in notebook examples + "E501", # no long line check + "ERA001", # no check for commented code +] +"*/fastoad/notebooks/*" = [ + "RET504", # We prefer clarity over efficency in notebook examples + "E501", # no long line check + "ERA001", # no check for commented code +] # Tests "tests/*" = [ "PLR0913", # Too many arguments in function definition "PLR0915", # Too many statements "PLR2004", # Enabling magic value to be used in test comparison "S101", # Enable assert in tests + "PD901", # Avoid using the generic variable name `df` for DataFrames "ANN", # No annotation on tests "D", # No doc in tests ] @@ -73,6 +83,7 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" "PLR0915", # Too many statements "PLR2004", # Enabling magic value to be used in test comparison "S101", # Enable assert in tests + "PD901", # Avoid using the generic variable name `df` for DataFrames "ANN", # No annotation on tests "D", # No doc in tests ] diff --git a/src/conftest.py b/src/conftest.py index 9a506b188..dcd9b036a 100644 --- a/src/conftest.py +++ b/src/conftest.py @@ -33,6 +33,8 @@ else: import importlib_metadata +import contextlib + from fastoad.module_management._plugins import MODEL_PLUGIN_ID, FastoadLoader @@ -42,9 +44,12 @@ def no_xfoil_skip(request, xfoil_path): Use @pytest.mark.skip_if_no_xfoil() before a test to skip it if xfoil_path fixture returns None and OS is not Windows. """ - if request.node.get_closest_marker("skip_if_no_xfoil"): - if xfoil_path is None and system() != "Windows": - pytest.skip("No XFOIL executable available") + if ( + request.node.get_closest_marker("skip_if_no_xfoil") + and (xfoil_path is None) + and (system() != "Windows") + ): + pytest.skip("No XFOIL executable available") @pytest.fixture @@ -311,7 +316,7 @@ def _teardown(): # Monkey-patching using wrapt module ########################################### -def _BypassEntryPointReading_enabled(): +def _BypassEntryPointReading_enabled(): # noqa: N802 more readable with camelcase return BypassEntryPointReading.active @@ -329,7 +334,7 @@ def __call__(self, wrapped, instance, args, kwargs): importlib_metadata.entry_points = BypassEntryPointReading()(importlib_metadata.entry_points) -def _MakeEntryPointMutable_enabled(): +def _MakeEntryPointMutable_enabled(): # noqa: N802 more readable with camelcase return MakeEntryPointMutable.active @@ -342,10 +347,8 @@ def _enabled(cls): @wrapt.decorator(enabled=_MakeEntryPointMutable_enabled) def __call__(self, wrapped, instance, args, kwargs): - try: + with contextlib.suppress(AttributeError): delattr(wrapped, "__setattr__") - except AttributeError: - pass return wrapped(*args, **kwargs) diff --git a/src/fastoad/_utils/resource_management/contents.py b/src/fastoad/_utils/resource_management/contents.py index 697eedc3f..06810476a 100644 --- a/src/fastoad/_utils/resource_management/contents.py +++ b/src/fastoad/_utils/resource_management/contents.py @@ -65,7 +65,7 @@ def package_name(self, package_name: str): else: # Here we assume non-existence self.exists = False - except Exception: # pylint: disable = W0703 + except Exception: # Here we catch any Python error that may happen when reading the loaded code. # Thus, we ensure to not break the application if a module is incorrectly written. self.has_error = True diff --git a/src/fastoad/_utils/sellar/disc1.py b/src/fastoad/_utils/sellar/disc1.py index 378a6474e..876cfeb00 100644 --- a/src/fastoad/_utils/sellar/disc1.py +++ b/src/fastoad/_utils/sellar/disc1.py @@ -34,7 +34,6 @@ def setup(self): def setup_partials(self): self.declare_partials("*", "*", method="fd") - # pylint: disable=invalid-name def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): """ Evaluates the equation diff --git a/src/fastoad/_utils/sellar/disc2.py b/src/fastoad/_utils/sellar/disc2.py index e0c360101..7c68a83b3 100644 --- a/src/fastoad/_utils/sellar/disc2.py +++ b/src/fastoad/_utils/sellar/disc2.py @@ -27,7 +27,6 @@ def setup(self): def setup_partials(self): self.declare_partials("*", "*", method="fd") - # pylint: disable=invalid-name def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): """ Evaluates the equation diff --git a/src/fastoad/_utils/tests/test_get_float_list_from_string.py b/src/fastoad/_utils/tests/test_get_float_list_from_string.py index aeb9634dc..cc5e2100f 100644 --- a/src/fastoad/_utils/tests/test_get_float_list_from_string.py +++ b/src/fastoad/_utils/tests/test_get_float_list_from_string.py @@ -15,11 +15,11 @@ def test_get_float_list_from_string(): - assert [1.0, 2.0, 3.0] == get_float_list_from_string("[ 1, 2., 3]") - assert [[1.0, 2.0], [3.0, 4.0]] == get_float_list_from_string("[[ 1, 2.],[ 3, 4]]") - assert [[1.0, 2.0], [3.0, 4.0]] == get_float_list_from_string("[[ 1,\n 2.],\n[ 3, 4]]\n") - assert [1.0, 2.0, 3.0] == get_float_list_from_string(" 1, 2., 3") - assert [1.0, 2.0] == get_float_list_from_string(" 1 2 ") - assert [1.0] == get_float_list_from_string(" 1 ") + assert get_float_list_from_string("[ 1, 2., 3]") == [1.0, 2.0, 3.0] + assert get_float_list_from_string("[[ 1, 2.],[ 3, 4]]") == [[1.0, 2.0], [3.0, 4.0]] + assert get_float_list_from_string("[[ 1,\n 2.],\n[ 3, 4]]\n") == [[1.0, 2.0], [3.0, 4.0]] + assert get_float_list_from_string(" 1, 2., 3") == [1.0, 2.0, 3.0] + assert get_float_list_from_string(" 1 2 ") == [1.0, 2.0] + assert get_float_list_from_string(" 1 ") == [1.0] assert get_float_list_from_string(" dummy ") is None assert get_float_list_from_string("") is None diff --git a/src/fastoad/cmd/api.py b/src/fastoad/cmd/api.py index 837a7a0f5..e5aa3167b 100644 --- a/src/fastoad/cmd/api.py +++ b/src/fastoad/cmd/api.py @@ -397,32 +397,32 @@ def list_variables( out, ) make_parent_dir(out) - out_file = Path(out).open("w", encoding="utf-8") - else: - if out == sys.stdout and InteractiveShell.initialized() and not force_text_output: - display(HTML(variables_df.to_html(index=False))) - return None + with Path(out).open("w", encoding="utf-8") as out_file: + if tablefmt == "var_desc": + content = _generate_var_desc_format(variables_df) + else: + content = _generate_table_format(variables_df, tablefmt=tablefmt) + out_file.write(content) + _LOGGER.info("Output list written in %s", out) + return out - # Here we continue with text output - out_file = out + if out == sys.stdout and InteractiveShell.initialized() and not force_text_output: + display(HTML(variables_df.to_html(index=False))) + return None + # Here we continue with text output if tablefmt == "var_desc": content = _generate_var_desc_format(variables_df) else: content = _generate_table_format(variables_df, tablefmt=tablefmt) - out_file.write(content) - - if isinstance(out, str): - out_file.close() - _LOGGER.info("Output list written in %s", out) - return out + out.write(content) return None def _generate_var_desc_format(variables_df): - kwargs = dict(columns=["NAME", "DESCRIPTION"], sep="|", index=False, header=False) + kwargs = {"columns": ["NAME", "DESCRIPTION"], "sep": "|", "index": False, "header": False} if Version(pd.__version__) < Version("1.5"): kwargs["line_terminator"] = "\n" else: @@ -442,7 +442,7 @@ def _generate_table_format(variables_df, tablefmt="grid"): return content + "\n" -def list_modules( +def list_modules( # noqa: PLR0912 Here is ok to have a more complex function source_path: list[str | PathLike] | str | PathLike | None = None, out: str | PathLike | TextIO | None = None, overwrite: bool = False, # noqa: FBT001, FBT002 no breaking changes in API functions @@ -482,7 +482,7 @@ def list_modules( raise FileNotFoundError(f"Could not find {source_path}") elif isinstance(source_path, Iterable): for folder_path in source_path: - folder_path = as_path(folder_path) + folder_path = as_path(folder_path) # noqa: PLW2901 if not folder_path.is_dir(): _LOGGER.warning("SKIPPED %s: folder does not exist.", folder_path) else: @@ -490,10 +490,7 @@ def list_modules( elif source_path is not None: raise RuntimeError("Unexpected type for source_path") - if verbose: - cell_list = _get_detailed_system_list() - else: - cell_list = _get_simple_system_list() + cell_list = _get_detailed_system_list() if verbose else _get_simple_system_list() if isinstance(out, (str, PathLike)): out = as_path(out).absolute() @@ -502,29 +499,28 @@ def list_modules( f"File {out} not written because it already exists. Use overwrite=True to bypass.", out, ) - make_parent_dir(out) - out_file = Path(out).open("w", encoding="utf-8") - else: - if ( - out == sys.stdout - and InteractiveShell.initialized() - and not force_text_output - and not verbose - ): - display(HTML(tabulate(cell_list, tablefmt="html"))) - return None - - out_file = out - - out_file.write(tabulate(cell_list, tablefmt="grid")) - out_file.write("\n") - - if isinstance(out, str): - out_file.close() + + with Path(out).open("w", encoding="utf-8") as out_file: + out_file.write(tabulate(cell_list, tablefmt="grid")) + out_file.write("\n") + _LOGGER.info("System list written in %s", out) return out + if ( + out == sys.stdout + and InteractiveShell.initialized() + and not force_text_output + and not verbose + ): + display(HTML(tabulate(cell_list, tablefmt="html"))) + return None + + # Fallback: write to provided file-like object (stdout or custom stream) + out.write(tabulate(cell_list, tablefmt="grid")) + out.write("\n") + return None diff --git a/src/fastoad/cmd/cli.py b/src/fastoad/cmd/cli.py index 523b0a2e7..2903afca5 100644 --- a/src/fastoad/cmd/cli.py +++ b/src/fastoad/cmd/cli.py @@ -41,7 +41,7 @@ NOTEBOOK_FOLDER_NAME = "FAST-OAD_notebooks" -@click.group(context_settings=dict(help_option_names=["-h", "--help"])) +@click.group(context_settings={"help_option_names": ["-h", "--help"]}) @click.version_option(fastoad.__version__, "-v", "--version") def fast_oad(): """FAST-OAD main program""" diff --git a/src/fastoad/cmd/tests/test_cli.py b/src/fastoad/cmd/tests/test_cli.py index c3e0bdf85..cf955f33a 100644 --- a/src/fastoad/cmd/tests/test_cli.py +++ b/src/fastoad/cmd/tests/test_cli.py @@ -48,16 +48,16 @@ def test_plugin_info(with_dummy_plugins): assert result.exit_code == 0 expected_info = pd.DataFrame( - dict( - installed_package=[f"dummy-dist-{i}" for i in [1, 2, 3]], - has_models=[False, True, True], - has_notebooks=[True, False, True], - configurations=[ + { + "installed_package": [f"dummy-dist-{i}" for i in [1, 2, 3]], + "has_models": [False, True, True], + "has_notebooks": [True, False, True], + "configurations": [ ["dummy_conf_1-1.yml"], ["dummy_conf_2-1.yml", "dummy_conf_3-1.yml", "dummy_conf_3-2.yaml"], [], ], - source_data_files=[ + "source_data_files": [ ["dummy_source_data_4-1.xml"], [ "dummy_source_data_3-1.xml", @@ -66,7 +66,7 @@ def test_plugin_info(with_dummy_plugins): ], [], ], - ) + } ) assert result.output == expected_info.to_markdown(index=False) + "\n" diff --git a/src/fastoad/exceptions.py b/src/fastoad/exceptions.py index c9473d30f..b6b156192 100644 --- a/src/fastoad/exceptions.py +++ b/src/fastoad/exceptions.py @@ -46,7 +46,7 @@ class FastUnknownEngineSettingError(FastError): """ -class FastUnexpectedKeywordArgument(FastError): +class FastUnexpectedKeywordArgumentError(FastError): """ Raised when an instantiation is done with an incorrect keyword argument. """ diff --git a/src/fastoad/gui/analysis_and_plots.py b/src/fastoad/gui/analysis_and_plots.py index ff0db8715..0aebfcd7c 100644 --- a/src/fastoad/gui/analysis_and_plots.py +++ b/src/fastoad/gui/analysis_and_plots.py @@ -59,14 +59,11 @@ def wing_geometry_plot( mean_aerodynamic_chord = variables["data:geometry:wing:MAC:length"].value[0] mac25_x_position = variables["data:geometry:wing:MAC:at25percent:x"].value[0] distance_root_mac_chords = variables["data:geometry:wing:MAC:leading_edge:x:local"].value[0] - # pylint: disable=invalid-name # that's a common naming y = np.array( [0, wing_root_y, wing_kink_y, wing_tip_y, wing_tip_y, wing_kink_y, wing_root_y, 0, 0] ) - # pylint: disable=invalid-name # that's a common naming y = np.concatenate((-y, y)) - # pylint: disable=invalid-name # that's a common naming x = np.array( [ 0, @@ -82,7 +79,6 @@ def wing_geometry_plot( ) x = x + mac25_x_position - 0.25 * mean_aerodynamic_chord - distance_root_mac_chords - # pylint: disable=invalid-name # that's a common naming x = np.concatenate((x, x)) if fig is None: @@ -92,7 +88,7 @@ def wing_geometry_plot( fig.add_trace(scatter) - fig.layout = go.Layout(yaxis=dict(scaleanchor="x", scaleratio=1)) + fig.layout = go.Layout(yaxis={"scaleanchor": "x", "scaleratio": 1}) fig = go.FigureWidget(fig) @@ -204,14 +200,10 @@ def aircraft_geometry_plot( x_wing = x_wing + wing_25mac_x - 0.25 * wing_mac_length - local_wing_mac_le_x x_ht = x_ht + wing_25mac_x + ht_distance_from_wing - local_ht_25mac_x - # pylint: disable=invalid-name # that's a common naming x = np.concatenate((x_fuselage, x_wing, x_ht)) - # pylint: disable=invalid-name # that's a common naming y = np.concatenate((y_fuselage, y_wing, y_ht)) - # pylint: disable=invalid-name # that's a common naming y = np.concatenate((-y, y)) - # pylint: disable=invalid-name # that's a common naming x = np.concatenate((x, x)) if fig is None: @@ -221,7 +213,7 @@ def aircraft_geometry_plot( fig.add_trace(scatter) - fig.layout = go.Layout(yaxis=dict(scaleanchor="x", scaleratio=1)) + fig.layout = go.Layout(yaxis={"scaleanchor": "x", "scaleratio": 1}) fig = go.FigureWidget(fig) @@ -247,14 +239,13 @@ def drag_polar_plot( """ variables = VariableIO(aircraft_file_path, file_formatter).read() - # pylint: disable=invalid-name # that's a common naming cd = np.asarray(variables["data:aerodynamics:aircraft:cruise:CD"].value) - # pylint: disable=invalid-name # that's a common naming cl = np.asarray(variables["data:aerodynamics:aircraft:cruise:CL"].value) # TODO: remove filtering one models provide proper bounds - cd_short = cd[cd <= 2.0] - cl_short = cl[cd <= 2.0] + CD_UPPER = 2.0 + cd_short = cd[cd <= CD_UPPER] + cl_short = cl[cd <= CD_UPPER] if fig is None: fig = go.Figure() @@ -301,7 +292,6 @@ def mass_breakdown_bar_plot( "data:weight:aircraft:sizing_onboard_fuel_at_input_weight": "kg", } - # pylint: disable=unbalanced-tuple-unpacking # It is balanced for the parameters provided mtow, owe, payload, fuel_mission = _get_variable_values_with_new_units( variables, var_names_and_new_units ) @@ -416,7 +406,7 @@ def _get_TOW_sunburst_plot(variables: VariableList, input_mass_name, mission_nam if mission_tow_var not in variables.names(): # Check if the provided mission_name exists raise ValueError( f"The provided mission_name {mission_name} does not correspond to an existing " - + "mission. The available mission(s) are: {missions_set}." + "mission. The available mission(s) are: {missions_set}." ) payload_var_name = f"data:mission:{mission_name}:payload" if payload_var_name not in variables.names(): # If mission_name is a sizing mission @@ -485,7 +475,7 @@ def _get_OWE_sunburst_plot(variables: VariableList): sub_categories_parent = [] for variable in variables.names(): name_split = variable.split(":") - if isinstance(name_split, list) and len(name_split) >= 5: + if isinstance(name_split, list) and len(name_split) >= 5: # noqa: PLR2004 parent_name = name_split[2] if parent_name in categories_names and name_split[-1] == "mass": variable_name = "_".join(name_split[3:-1]) @@ -563,7 +553,7 @@ def payload_range_plot( x=convert_units(range_, "m", "NM"), y=convert_units(payload, "kg", "t"), mode="lines+markers", - line=dict(color="black", width=3), + line={"color": "black", "width": 3}, showlegend=False, name=name, ) @@ -576,7 +566,7 @@ def payload_range_plot( x=convert_units(range_mask, "m", "NM"), y=convert_units(payload_mask, "kg", "t"), mode="lines", - line=dict(color="#E5ECF6", width=3), + line={"color": "#E5ECF6", "width": 3}, showlegend=False, name=name, fill="toself", @@ -616,13 +606,13 @@ def payload_range_plot( x=x, y=y, z=z, - contours=dict(start=min_z, end=max_z, size=(max_z - min_z) / 20), - colorbar=dict( - title=f"{variable_of_interest_legend} [{variable_of_interest_unit}]", - titleside="right", - titlefont=dict(size=15, family="Arial, sans-serif"), - tickformat=".1e", - ), + contours={"start": min_z, "end": max_z, "size": (max_z - min_z) / 20}, + colorbar={ + "title": f"{variable_of_interest_legend} [{variable_of_interest_unit}]", + "titleside": "right", + "titlefont": {"size": 15, "family": "Arial, sans-serif"}, + "tickformat": ".1e", + }, colorscale="RdBu_r", contours_coloring="heatmap", ) @@ -682,21 +672,24 @@ def _data_weight_decomposition(variables: VariableList, owe=None): owe_subcategory_names = [] for variable in variables.names(): name_split = variable.split(":") - if isinstance(name_split, list) and len(name_split) == 4: - if ( + if ( + isinstance(name_split, list) + and len(name_split) == 4 # noqa: PLR2004 + and ( name_split[0] + name_split[1] + name_split[3] == "dataweightmass" and "aircraft" not in name_split[2] - ): - category_values.append( - convert_units(variables[variable].value[0], variables[variable].units, "kg") + ) + ): + category_values.append( + convert_units(variables[variable].value[0], variables[variable].units, "kg") + ) + category_names.append(name_split[2]) + if owe: + owe_subcategory_names.append( + _get_sunburst_mass_label( + name_split[2], variables[variable].value[0], parent_value=owe + ), ) - category_names.append(name_split[2]) - if owe: - owe_subcategory_names.append( - _get_sunburst_mass_label( - name_split[2], variables[variable].value[0], parent_value=owe - ), - ) if owe: result = category_values, category_names, owe_subcategory_names else: diff --git a/src/fastoad/gui/exceptions.py b/src/fastoad/gui/exceptions.py index 3e74a1f5a..ba6dc8e3b 100644 --- a/src/fastoad/gui/exceptions.py +++ b/src/fastoad/gui/exceptions.py @@ -17,5 +17,5 @@ from fastoad.exceptions import FastError -class FastMissingFile(FastError): +class FastMissingFileError(FastError): """Raised when a file does not exist""" diff --git a/src/fastoad/gui/mission_viewer.py b/src/fastoad/gui/mission_viewer.py index abb076065..1779a27fa 100644 --- a/src/fastoad/gui/mission_viewer.py +++ b/src/fastoad/gui/mission_viewer.py @@ -101,7 +101,6 @@ def display(self, layout_dict=None, layout_overwrite=False, **kwargs): # noqa: return display(toolbar, self._output_widget) # UI - # pylint: disable=unused-argument # change has to be there for observe() to work def _show_plot(self, change=None, layout_dict=None, *, layout_overwrite=False, **kwargs): """ Updates and shows the plots @@ -124,9 +123,7 @@ def _show_plot(self, change=None, layout_dict=None, *, layout_overwrite=False, * for mission_name in self.missions: if fig is None: fig = go.Figure() - # pylint: disable=invalid-name # that's a common naming x = self.missions[mission_name][x_name] - # pylint: disable=invalid-name # that's a common naming y = self.missions[mission_name][y_name] scatter = go.Scatter(x=x, y=y, mode="lines", name=mission_name) diff --git a/src/fastoad/gui/optimization_viewer.py b/src/fastoad/gui/optimization_viewer.py index 17e4481ce..6eb4d8248 100644 --- a/src/fastoad/gui/optimization_viewer.py +++ b/src/fastoad/gui/optimization_viewer.py @@ -35,7 +35,7 @@ ) from fastoad.openmdao.variables import Variable, VariableList -from .exceptions import FastMissingFile +from .exceptions import FastMissingFileError pd.set_option("display.max_rows", None) @@ -93,7 +93,9 @@ def load(self, problem_configuration: FASTOADProblemConfigurator): input_variables = DataFile(self.problem_configuration.input_file_path) else: # TODO: generate the input file by default ? - raise FastMissingFile("Please generate input file before using the optimization viewer") + raise FastMissingFileError( + "Please generate input file before using the optimization viewer" + ) if Path(self.problem_configuration.output_file_path).is_file(): self._MISSING_OUTPUT_FILE = False @@ -255,7 +257,6 @@ def get_variables(self, column_to_attribute: dict[str, str] | None = None) -> Va self.dataframe[column_to_attribute.keys()].rename(columns=column_to_attribute) ) - # pylint: disable=invalid-name # df is a common naming for dataframes def _df_to_sheet(self, df: pd.DataFrame) -> sh.Sheet: """ Transforms a pandas DataFrame into a ipysheet Sheet. @@ -273,10 +274,8 @@ def _df_to_sheet(self, df: pd.DataFrame) -> sh.Sheet: read_only_cells = ["Name", "Unit", "Description", "Value"] style = self._cell_styling(df) - row_idx = 0 - for r in rows: - col_idx = 0 - for c in columns: + for row_idx, r in enumerate(rows): + for col_idx, c in enumerate(columns): value = df.loc[r, c] if c in read_only_cells: read_only = True @@ -303,8 +302,6 @@ def _df_to_sheet(self, df: pd.DataFrame) -> sh.Sheet: style=style[(r, c)], ) ) - col_idx += 1 - row_idx += 1 sheet = sh.Sheet( rows=len(rows), columns=len(columns), @@ -328,7 +325,7 @@ def _sheet_to_df(sheet: sh.Sheet) -> pd.DataFrame: """ return sh.to_dataframe(sheet) - # pylint: disable=unused-argument # args has to be there for observe() to work + # change has to be there for observe() to work def _update_df(self, change=None): """ Updates the stored DataFrame with respect to the actual values of the Sheet. @@ -428,7 +425,7 @@ def _update_sheet(self): cell.observe(self._update_df, "value") cell.observe(self._update_style, "value") - # pylint: disable=unused-argument # args has to be there for observe() to work + # change has to be there for observe() to work def _render_ui(self, change=None) -> display: """ Renders the dropdown menus for the variable selector and the corresponding @@ -473,33 +470,31 @@ def highlight_active_bounds(df, threshold=1e-6): s = df.loc[r] is_active = pd.Series(data=False, index=s.index) is_violated = pd.Series(data=False, index=s.index) - if "Lower" in s: + if ("Lower" in s) and (s.loc["Lower"] is not None): # Constraints might only have a upper bound - if s.loc["Lower"] is not None: - if np.all(s.loc["Lower"] + threshold >= s.loc["Value"]) & np.all( - s.loc["Value"] >= s.loc["Lower"] - threshold - ): - is_active["Lower"] = True - is_active["Value"] = True - elif np.all(s.loc["Value"] < s.loc["Lower"] - threshold): - is_violated["Lower"] = True - is_violated["Value"] = True - else: - pass - - if "Upper" in s: + if np.all(s.loc["Lower"] + threshold >= s.loc["Value"]) & np.all( + s.loc["Value"] >= s.loc["Lower"] - threshold + ): + is_active["Lower"] = True + is_active["Value"] = True + elif np.all(s.loc["Value"] < s.loc["Lower"] - threshold): + is_violated["Lower"] = True + is_violated["Value"] = True + else: + pass + + if ("Upper" in s) and (s.loc["Upper"] is not None): # Constraints might only have a lower bound - if s.loc["Upper"] is not None: - if np.all(s.loc["Upper"] + threshold >= s.loc["Value"]) & np.all( - s.loc["Value"] >= s.loc["Upper"] - threshold - ): - is_active["Upper"] = True - is_active["Value"] = True - elif np.all(s.loc["Value"] > s.loc["Upper"] + threshold): - is_violated["Upper"] = True - is_violated["Value"] = True - else: - pass + if np.all(s.loc["Upper"] + threshold >= s.loc["Value"]) & np.all( + s.loc["Value"] >= s.loc["Upper"] - threshold + ): + is_active["Upper"] = True + is_active["Value"] = True + elif np.all(s.loc["Value"] > s.loc["Upper"] + threshold): + is_violated["Upper"] = True + is_violated["Value"] = True + else: + pass yellow = ["yellow" if v else None for v in is_active] red = ["red" if v else None for v in is_violated] diff --git a/src/fastoad/gui/tests/test_analysis_and_plots.py b/src/fastoad/gui/tests/test_analysis_and_plots.py index 860991afa..bb41c7295 100644 --- a/src/fastoad/gui/tests/test_analysis_and_plots.py +++ b/src/fastoad/gui/tests/test_analysis_and_plots.py @@ -149,15 +149,19 @@ def test_mass_breakdown_sun_plot_specific_mission(): # Plot 1 # Specific mission plot - f = mass_breakdown_sun_plot(filename, mission_name=mission_1) - # f.show() + mass_breakdown_sun_plot(filename, mission_name=mission_1) + # Debug + # f = mass_breakdown_sun_plot(filename, mission_name=mission_1) # noqa: ERA001 + # f.show() # noqa: ERA001 mission_2 = "MTOW_mission" # Plot 2 # Specific mission plot (sizing mission) - f = mass_breakdown_sun_plot(filename, mission_name=mission_2) # noqa: F841 - # f.show() + mass_breakdown_sun_plot(filename, mission_name=mission_2) + # Debug + # f = mass_breakdown_sun_plot(filename, mission_name=mission_2) # noqa: ERA001 + # f.show() # noqa: ERA001 mission_3 = "not_a_mission_name" diff --git a/src/fastoad/gui/tests/test_optimization_viewer.py b/src/fastoad/gui/tests/test_optimization_viewer.py index cb94edec7..f0c5af36a 100644 --- a/src/fastoad/gui/tests/test_optimization_viewer.py +++ b/src/fastoad/gui/tests/test_optimization_viewer.py @@ -23,7 +23,7 @@ from fastoad.io.configuration.configuration import FASTOADProblemConfigurator from .. import OptimizationViewer -from ..exceptions import FastMissingFile +from ..exceptions import FastMissingFileError DATA_FOLDER_PATH = Path(__file__).parent / "data" RESULTS_FOLDER_PATH = Path(__file__).parent / "results" @@ -46,7 +46,7 @@ def test_optimization_viewer_load(cleanup): optim_viewer = OptimizationViewer() # No input file exists - with pytest.raises(FastMissingFile): + with pytest.raises(FastMissingFileError): optim_viewer.load(problem_configuration) api.generate_inputs(filename, DATA_FOLDER_PATH / "inputs.xml", overwrite=True) diff --git a/src/fastoad/gui/tests/test_variable_viewer.py b/src/fastoad/gui/tests/test_variable_viewer.py index 63ce5c0b2..5ec7b8b15 100644 --- a/src/fastoad/gui/tests/test_variable_viewer.py +++ b/src/fastoad/gui/tests/test_variable_viewer.py @@ -38,7 +38,6 @@ def test_variable_reader_display(): """ filename = DATA_FOLDER_PATH / "problem_outputs.xml" - # pylint: disable=invalid-name # that's a common naming df = VariableViewer() df.load(filename) diff --git a/src/fastoad/gui/variable_viewer.py b/src/fastoad/gui/variable_viewer.py index cd97794a9..bc5931ee7 100644 --- a/src/fastoad/gui/variable_viewer.py +++ b/src/fastoad/gui/variable_viewer.py @@ -34,6 +34,8 @@ NA = "N/A" TAG_ALL = "--ALL--" +# ruff:noqa PD901 df is ok as a name here + class VariableViewer: """ @@ -174,7 +176,6 @@ def get_variables(self, column_to_attribute: dict[str, str] | None = None) -> Va dataframe[column_to_attribute.keys()].rename(columns=column_to_attribute) ) - # pylint: disable=invalid-name # df is a common naming for dataframes @staticmethod def _df_to_sheet(df: pd.DataFrame) -> sh.Sheet: """ @@ -214,7 +215,6 @@ def _sheet_to_df(sheet: sh.Sheet) -> pd.DataFrame: """ return sh.to_dataframe(sheet) - # pylint: disable=unused-argument # args has to be there for observe() to work def _update_df(self, change=None): """ Updates the stored DataFrame with respect to the actual values of the Sheet. @@ -222,7 +222,7 @@ def _update_df(self, change=None): """ df = self._sheet_to_df(self._sheet) for i in df.index: - self.dataframe.loc[int(i), :] = df.loc[i, :].values + self.dataframe.loc[int(i), :] = df.loc[i, :].to_numpy() def _render_sheet(self) -> display: """ @@ -236,7 +236,6 @@ def _render_sheet(self) -> display: self._filter_widgets.append(w) return self._render_ui() - # pylint: disable=unused-argument # args has to be there for observe() to work def _update_items(self, change=None): """ Updates the filter_widgets with respect to higher level filter_widgets values. @@ -261,17 +260,15 @@ def _update_items(self, change=None): self._filter_widgets.append(widget) else: if (TAG_ALL not in modules_item) and ( - len(modules_item) > 1 or var_name in self.dataframe["Name"].values + len(modules_item) > 1 or var_name in self.dataframe["Name"].to_numpy() ): modules_item.insert(0, TAG_ALL) self._filter_widgets[i].options = modules_item - else: - if i < len(self._filter_widgets): - self._filter_widgets.pop(i) + elif i < len(self._filter_widgets): + self._filter_widgets.pop(i) else: break - # pylint: disable=unused-argument # args has to be there for observe() to work def _update_variable_selector(self, change=None): """ Updates the variable selector with respect to the @@ -350,7 +347,6 @@ def _update_sheet(self): for cell in self._sheet.cells: cell.observe(self._update_df, "value") - # pylint: disable=unused-argument # args has to be there for observe() to work def _render_ui(self, change=None) -> display: """ Renders the dropdown menus for the variable selector and the corresponding @@ -423,10 +419,7 @@ def _filter_variables( var_io_type = TAG_ALL path = "" for _ in modules: - if modules[-1] == TAG_ALL: - path = ":".join(modules[:-1]) - else: - path = ":".join(modules) + path = ":".join(modules[:-1]) if modules[-1] == TAG_ALL else ":".join(modules) path_filter = df.Name.str.startswith(path) io_filter = pd.Series( [True] * len(df) if var_io_type == TAG_ALL else df["I/O"] == var_io_type diff --git a/src/fastoad/io/configuration/configuration.py b/src/fastoad/io/configuration/configuration.py index c8990bfa1..908e73211 100644 --- a/src/fastoad/io/configuration/configuration.py +++ b/src/fastoad/io/configuration/configuration.py @@ -194,7 +194,7 @@ def load(self, conf_file: str | PathLike): # Issue a simple warning for unknown keys at root level for key in self._data: - if key not in json_schema["properties"].keys(): + if key not in json_schema["properties"]: _LOGGER.warning('Configuration file: "%s" is not a FAST-OAD key.', key) # Looking for modules to register @@ -276,7 +276,7 @@ def set_optimization_definition(self, optimization_definition: dict): """ subpart = {} for key, value in optimization_definition.items(): - subpart[key] = [value for _, value in optimization_definition[key].items()] + subpart[key] = [val for _, val in value.items()] subpart = {"optimization": subpart} self._data.update(subpart) @@ -463,15 +463,13 @@ def _build_model(self, problem: FASTOADProblem): _LOGGER.error(log_err) raise log_err - def _parse_problem_table(self, group: om.Group, table: dict): + def _parse_problem_table(self, group: om.Group, table: dict): # noqa: PLR0912 """ Feeds provided *group*, using definition in provided TOML *table*. :param group: :param table: """ - # assert isinstance(table, dict), "table should be a dictionary" - for key, value in table.items(): if isinstance(value, dict): # value defines a sub-component if KEY_COMPONENT_ID in value: @@ -500,7 +498,7 @@ def _parse_problem_table(self, group: om.Group, table: dict): # value may have to be literally interpreted if key.endswith(("solver", "driver")): try: - value = self._om_eval(str(value)) + value = self._om_eval(str(value)) # noqa: PLW2901 except Exception as err: raise FASTConfigurationBadOpenMDAOInstructionError(err, key, value) @@ -600,7 +598,7 @@ def _om_eval(self, string_to_eval: str): raise ValueError( "No double underscore allowed in evaluated string for security reasons" ) - return eval(string_to_eval, {"__builtins__": {}}, {"om": om, **self._imported_classes}) + return eval(string_to_eval, {"__builtins__": {}}, {"om": om, **self._imported_classes}) # noqa: S307 class _IDictSerializer(ABC): diff --git a/src/fastoad/io/configuration/tests/test_configuration.py b/src/fastoad/io/configuration/tests/test_configuration.py index 48474174d..f4cc87f10 100644 --- a/src/fastoad/io/configuration/tests/test_configuration.py +++ b/src/fastoad/io/configuration/tests/test_configuration.py @@ -206,7 +206,10 @@ def test_problem_definition_with_xml_ref(cleanup, caplog): def test_problem_definition_with_xml_ref_with_indep(cleanup): - """Tests what happens when writing inputs of a problem with indeps using data from existing XML file""" + """ + Tests what happens when writing inputs of a problem with indeps using data from existing XML + file + """ for extension in ["toml", "yml"]: clear_openmdao_registry() conf = FASTOADProblemConfigurator(DATA_FOLDER_PATH / f"valid_sellar_with_indep.{extension}") @@ -448,8 +451,8 @@ def test_imports_handling(added_sys_path): # Imports are done here to be sure the interpreter does not know # these paths when the code above is run. - from .data.to_be_imported.my_driver_1 import MyDriver1 - from .data.to_be_imported.my_driver_2 import MyDriver2 + from .data.to_be_imported.my_driver_1 import MyDriver1 # noqa: PLC0415 + from .data.to_be_imported.my_driver_2 import MyDriver2 # noqa: PLC0415 assert conf._imported_classes["MyDriver1"] == MyDriver1 assert conf._imported_classes["MyDriver2"] == MyDriver2 diff --git a/src/fastoad/io/variable_io.py b/src/fastoad/io/variable_io.py index 5948b4399..3b24a2558 100644 --- a/src/fastoad/io/variable_io.py +++ b/src/fastoad/io/variable_io.py @@ -41,7 +41,7 @@ class VariableIO: def __init__( self, data_source: str | PathLike | IO | None, - formatter: IVariableIOFormatter = None, + formatter: IVariableIOFormatter | None = None, ): if isinstance(data_source, (str, PathLike)): data_source = as_path(data_source) @@ -57,7 +57,7 @@ def formatter(self) -> IVariableIOFormatter: return self._formatter @formatter.setter - def formatter(self, formatter: IVariableIOFormatter): + def formatter(self, formatter: IVariableIOFormatter | None): self._formatter = formatter if formatter else VariableXmlStandardFormatter() def read(self, only: list[str] | None = None, ignore: list[str] | None = None) -> VariableList: @@ -159,7 +159,7 @@ class DataFile(VariableList): def __init__( self, data_source: str | PathLike | IO | list | None = None, - formatter: IVariableIOFormatter = None, + formatter: IVariableIOFormatter | None = None, load_data: bool = True, # noqa: FBT001, FBT002 no breaking changes in API functions ): """ @@ -227,7 +227,7 @@ def save(self): def save_as( self, file_path: str | PathLike, - formatter: IVariableIOFormatter = None, + formatter: IVariableIOFormatter | None = None, overwrite=False, # noqa: FBT002 no breaking changes in API functions ): """ diff --git a/src/fastoad/io/xml/exceptions.py b/src/fastoad/io/xml/exceptions.py index cb8672025..de7425ef4 100644 --- a/src/fastoad/io/xml/exceptions.py +++ b/src/fastoad/io/xml/exceptions.py @@ -22,13 +22,13 @@ class FastXPathEvalError(FastError): """ -class FastXpathTranslatorInconsistentLists(FastError): +class FastXpathTranslatorInconsistentListsError(FastError): """ Raised when list of variable names and list of XPaths have not the same length """ -class FastXpathTranslatorDuplicates(FastError): +class FastXpathTranslatorDuplicatesError(FastError): """ Raised when list of variable names or list of XPaths have duplicate entries """ diff --git a/src/fastoad/io/xml/tests/test_translator.py b/src/fastoad/io/xml/tests/test_translator.py index 0ee7bf01c..b0d4b5f61 100644 --- a/src/fastoad/io/xml/tests/test_translator.py +++ b/src/fastoad/io/xml/tests/test_translator.py @@ -19,8 +19,8 @@ import pytest from ..exceptions import ( - FastXpathTranslatorDuplicates, - FastXpathTranslatorInconsistentLists, + FastXpathTranslatorDuplicatesError, + FastXpathTranslatorInconsistentListsError, FastXpathTranslatorVariableError, FastXpathTranslatorXPathError, ) @@ -39,7 +39,7 @@ def test_translator_with_set(): # test with lists of different lengths -> error var_list2 = ["var0", *var_list] - with pytest.raises(FastXpathTranslatorInconsistentLists): + with pytest.raises(FastXpathTranslatorInconsistentListsError): translator.set(var_list2, xpath_list) # test with duplicate var names -> error @@ -47,7 +47,7 @@ def test_translator_with_set(): other_xpaths = ["xpath42", "xpath404", "xpath0"] var_list3 = var_list + duplicate_vars xpath_list3 = xpath_list + other_xpaths - with pytest.raises(FastXpathTranslatorDuplicates) as exc_info: + with pytest.raises(FastXpathTranslatorDuplicatesError) as exc_info: translator.set(var_list3, xpath_list3) assert exc_info.value.args[1] == set(duplicate_vars) @@ -56,7 +56,7 @@ def test_translator_with_set(): duplicate_xpaths = ["xpath5", "xpath2"] var_list4 = var_list + other_vars xpath_list4 = xpath_list + duplicate_xpaths - with pytest.raises(FastXpathTranslatorDuplicates) as exc_info: + with pytest.raises(FastXpathTranslatorDuplicatesError) as exc_info: translator.set(var_list4, xpath_list4) assert exc_info.value.args[1] == set(duplicate_xpaths) diff --git a/src/fastoad/io/xml/tests/test_variable_io_base.py b/src/fastoad/io/xml/tests/test_variable_io_base.py index 1acf2a5a8..e0731e158 100644 --- a/src/fastoad/io/xml/tests/test_variable_io_base.py +++ b/src/fastoad/io/xml/tests/test_variable_io_base.py @@ -35,7 +35,7 @@ def cleanup(): shutil.rmtree(RESULTS_FOLDER_PATH, ignore_errors=True) -def _check_basic_vars(outputs: VariableList): +def _check_basic_variables(outputs: VariableList): """Checks that provided IndepVarComp instance matches content of data/custom.xml file""" assert len(outputs) == 5 @@ -82,14 +82,14 @@ def test_custom_xml_read_and_write_from_ivc(cleanup): translator = VarXpathTranslator(variable_names=var_names, xpaths=xpaths) xml_read = VariableIO(file_path, formatter=VariableXmlBaseFormatter(translator)) var_list = xml_read.read() - _check_basic_vars(var_list) + _check_basic_variables(var_list) # test with a non-exhaustive translation table (missing variable name in the translator) # we expect that the variable is not included in the ivc file_path = DATA_FOLDER_PATH / "custom_additional_var.xml" xml_read = VariableIO(file_path, formatter=VariableXmlBaseFormatter(translator)) var_list = xml_read.read() - _check_basic_vars(var_list) + _check_basic_variables(var_list) # test with setting a translation with an additional var not present in the xml file_path = DATA_FOLDER_PATH / "custom.xml" @@ -102,7 +102,7 @@ def test_custom_xml_read_and_write_from_ivc(cleanup): ), ) var_list = xml_read.read() - _check_basic_vars(var_list) + _check_basic_variables(var_list) # Check using text file object -------------------- with Path.open(file_path) as text_file_io: @@ -127,7 +127,7 @@ def test_custom_xml_read_and_write_from_ivc(cleanup): translator.set(var_names, xpaths) xml_check = VariableIO(new_filename, formatter=VariableXmlBaseFormatter(translator)) new_ivc = xml_check.read() - _check_basic_vars(new_ivc) + _check_basic_variables(new_ivc) def test_custom_xml_read_and_write_with_translation_table(cleanup): @@ -142,12 +142,12 @@ def test_custom_xml_read_and_write_with_translation_table(cleanup): filename = DATA_FOLDER_PATH / "custom.xml" translator = VarXpathTranslator(source=DATA_FOLDER_PATH / "custom_translation.txt") xml_read = VariableIO(filename, formatter=VariableXmlBaseFormatter(translator)) - vars = xml_read.read() - _check_basic_vars(vars) + variables = xml_read.read() + _check_basic_variables(variables) new_filename = result_folder / "custom.xml" xml_write = VariableIO(new_filename, formatter=VariableXmlBaseFormatter(translator)) - xml_write.write(vars) + xml_write.write(variables) def test_custom_xml_read_and_write_with_only_or_ignore(cleanup): diff --git a/src/fastoad/io/xml/tests/test_variable_io_standard.py b/src/fastoad/io/xml/tests/test_variable_io_standard.py index 49587bc04..cec7dc01f 100644 --- a/src/fastoad/io/xml/tests/test_variable_io_standard.py +++ b/src/fastoad/io/xml/tests/test_variable_io_standard.py @@ -37,59 +37,59 @@ def cleanup(): shutil.rmtree(RESULTS_FOLDER_PATH, ignore_errors=True) -def _check_basic_vars(vars: VariableList): +def _check_basic_variables(variables: VariableList): """Checks that provided IndepVarComp instance matches content of data/basic.xml file""" # Using pytest.approx for numerical reason, but also because it works even if sequence types # are different (lists, tuples, numpy arrays) - assert_allclose(780.3, vars["geometry:total_surface"].value) - assert vars["geometry:total_surface"].units == "m**2" - assert vars["geometry:total_surface"].description == "scalar 1" + assert_allclose(780.3, variables["geometry:total_surface"].value) + assert variables["geometry:total_surface"].units == "m**2" + assert variables["geometry:total_surface"].description == "scalar 1" - assert_allclose(42, vars["geometry:wing:span"].value) - assert vars["geometry:wing:span"].units == "m" - assert vars["geometry:wing:span"].description == "scalar 2" + assert_allclose(42, variables["geometry:wing:span"].value) + assert variables["geometry:wing:span"].units == "m" + assert variables["geometry:wing:span"].description == "scalar 2" - assert_allclose(9.8, vars["geometry:wing:aspect_ratio"].value) - assert vars["geometry:wing:aspect_ratio"].units is None - assert vars["geometry:wing:aspect_ratio"].description == "" + assert_allclose(9.8, variables["geometry:wing:aspect_ratio"].value) + assert variables["geometry:wing:aspect_ratio"].units is None + assert variables["geometry:wing:aspect_ratio"].description == "" - assert_allclose(40.0, vars["geometry:fuselage:length"].value) - assert vars["geometry:fuselage:length"].units == "m" - assert vars["geometry:fuselage:length"].description == "" + assert_allclose(40.0, variables["geometry:fuselage:length"].value) + assert variables["geometry:fuselage:length"].units == "m" + assert variables["geometry:fuselage:length"].description == "" - assert_allclose(-42.0, vars["constants"].value) - assert vars["constants"].units is None - assert vars["constants"].description == "value with children tags" + assert_allclose(-42.0, variables["constants"].value) + assert variables["constants"].units is None + assert variables["constants"].description == "value with children tags" - assert_allclose([1.0, 2.0, 3.0], vars["constants:k1"].value) - assert vars["constants:k1"].units == "kg" - assert vars["constants:k1"].description == "" + assert_allclose([1.0, 2.0, 3.0], variables["constants:k1"].value) + assert variables["constants:k1"].units == "kg" + assert variables["constants:k1"].description == "" - assert_allclose([10.0, 20.0], vars["constants:k2"].value) - assert vars["constants:k2"].units is None - assert vars["constants:k2"].description == "" + assert_allclose([10.0, 20.0], variables["constants:k2"].value) + assert variables["constants:k2"].units is None + assert variables["constants:k2"].description == "" - assert_allclose([100.0, 200.0, 300.0, 400.0], vars["constants:k3"].value) - assert vars["constants:k3"].units == "m/s" - assert vars["constants:k3"].description == "value list, space-separated" + assert_allclose([100.0, 200.0, 300.0, 400.0], variables["constants:k3"].value) + assert variables["constants:k3"].units == "m/s" + assert variables["constants:k3"].description == "value list, space-separated" - assert_allclose([-1, -2, -3], vars["constants:k4"].value) - assert vars["constants:k4"].units is None - assert vars["constants:k4"].description == "value list, brackets + comma-separated" + assert_allclose([-1, -2, -3], variables["constants:k4"].value) + assert variables["constants:k4"].units is None + assert variables["constants:k4"].description == "value list, brackets + comma-separated" - assert_allclose([100, 200, 400, 500, 600], vars["constants:k5"].value) - assert vars["constants:k5"].units is None - assert vars["constants:k5"].description == "value list, comma-separated" + assert_allclose([100, 200, 400, 500, 600], variables["constants:k5"].value) + assert variables["constants:k5"].units is None + assert variables["constants:k5"].description == "value list, comma-separated" - assert_allclose([[1e2, 3.4e5], [5.4e3, 2.1]], vars["constants:k8"].value) - assert vars["constants:k8"].units is None - assert vars["constants:k8"].description == "2D list" + assert_allclose([[1e2, 3.4e5], [5.4e3, 2.1]], variables["constants:k8"].value) + assert variables["constants:k8"].units is None + assert variables["constants:k8"].description == "2D list" - assert len(vars) == 11 + assert len(variables) == 11 -def test_basic_xml_read_and_write_from_vars(cleanup): +def test_basic_xml_read_and_write_from_variables(cleanup): """ Tests the creation of an XML file from a VariableList instance """ @@ -130,14 +130,14 @@ def test_basic_xml_read_and_write_from_vars(cleanup): xml_check = VariableIO(file_path, formatter=VariableXmlStandardFormatter()) xml_check.path_separator = ":" new_var_list = xml_check.read() - _check_basic_vars(new_var_list) + _check_basic_variables(new_var_list) # Check reading hand-made XML (with some format twists) file_path = DATA_FOLDER_PATH / "basic.xml" xml_read = VariableIO(file_path, formatter=VariableXmlStandardFormatter()) xml_read.path_separator = ":" var_list = xml_read.read() - _check_basic_vars(var_list) + _check_basic_variables(var_list) # write it (with existing destination folder) new_file_path = result_folder / "basic.xml" @@ -149,7 +149,7 @@ def test_basic_xml_read_and_write_from_vars(cleanup): xml_check = VariableIO(new_file_path, formatter=VariableXmlStandardFormatter()) xml_check.path_separator = ":" new_var_list = xml_check.read() - _check_basic_vars(new_var_list) + _check_basic_variables(new_var_list) # try to write with bad separator xml_write.formatter.path_separator = "/" @@ -167,7 +167,7 @@ def test_basic_xml_read_and_write_from_vars(cleanup): assert var_list_3 == var_list -def test_basic_xml_partial_read_and_write_from_vars(cleanup): +def test_basic_xml_partial_read_and_write_from_variables(cleanup): """ Tests the creation of an XML file from an IndepVarComp instance with only and ignore options """ @@ -176,16 +176,18 @@ def test_basic_xml_partial_read_and_write_from_vars(cleanup): # Read full IndepVarComp filename = DATA_FOLDER_PATH / "basic.xml" xml_read = VariableIO(filename, formatter=VariableXmlStandardFormatter()) - vars = xml_read.read(ignore=["does_not_exist"]) - _check_basic_vars(vars) + variables = xml_read.read(ignore=["does_not_exist"]) + _check_basic_variables(variables) # Add something to ignore and write it - vars["should_be_ignored:pointless"] = {"value": 0.0} - vars["should_also_be_ignored"] = {"value": -10.0} + variables["should_be_ignored:pointless"] = {"value": 0.0} + variables["should_also_be_ignored"] = {"value": -10.0} badvar_filename = result_folder / "with_bad_var.xml" xml_write = VariableIO(badvar_filename, formatter=VariableXmlStandardFormatter()) - xml_write.write(vars, ignore=["does_not_exist"]) # Check with non-existent var in ignore list + xml_write.write( + variables, ignore=["does_not_exist"] + ) # Check with non-existent var in ignore list tree = etree.parse(badvar_filename.as_posix()) assert float(tree.xpath("should_be_ignored/pointless")[0].text.strip()) == 0.0 @@ -193,11 +195,11 @@ def test_basic_xml_partial_read_and_write_from_vars(cleanup): # Check partial reading with 'ignore' xml_read = VariableIO(badvar_filename, formatter=VariableXmlStandardFormatter()) - new_vars = xml_read.read(ignore=["should_be_ignored:pointless", "should_also_be_ignored"]) - _check_basic_vars(new_vars) + new_variables = xml_read.read(ignore=["should_be_ignored:pointless", "should_also_be_ignored"]) + _check_basic_variables(new_variables) # Check partial reading with 'only' - ok_vars = [ + ok_variables = [ "geometry:total_surface", "geometry:wing:span", "geometry:wing:aspect_ratio", @@ -210,23 +212,23 @@ def test_basic_xml_partial_read_and_write_from_vars(cleanup): "constants:k5", "constants:k8", ] - new_vars2 = xml_read.read(only=ok_vars) - _check_basic_vars(new_vars2) + new_variables2 = xml_read.read(only=ok_variables) + _check_basic_variables(new_variables2) # Check partial writing with 'ignore' varok_filename = result_folder / "with_bad_var.xml" xml_write = VariableIO(varok_filename, formatter=VariableXmlStandardFormatter()) - xml_write.write(vars, ignore=["should_be_ignored:pointless", "should_also_be_ignored"]) + xml_write.write(variables, ignore=["should_be_ignored:pointless", "should_also_be_ignored"]) xml_read = VariableIO(varok_filename, formatter=VariableXmlStandardFormatter()) - new_vars = xml_read.read() - _check_basic_vars(new_vars) + new_variables = xml_read.read() + _check_basic_variables(new_variables) # Check partial writing with 'only' varok2_filename = result_folder / "with_bad_var.xml" xml_write = VariableIO(varok2_filename, formatter=VariableXmlStandardFormatter()) - xml_write.write(vars, only=ok_vars) + xml_write.write(variables, only=ok_variables) xml_read = VariableIO(varok2_filename, formatter=VariableXmlStandardFormatter()) - new_vars = xml_read.read() - _check_basic_vars(new_vars) + new_variables = xml_read.read() + _check_basic_variables(new_variables) diff --git a/src/fastoad/io/xml/translator.py b/src/fastoad/io/xml/translator.py index d34a97d67..3ca6735a0 100644 --- a/src/fastoad/io/xml/translator.py +++ b/src/fastoad/io/xml/translator.py @@ -22,8 +22,8 @@ import numpy as np from fastoad.io.xml.exceptions import ( - FastXpathTranslatorDuplicates, - FastXpathTranslatorInconsistentLists, + FastXpathTranslatorDuplicatesError, + FastXpathTranslatorInconsistentListsError, FastXpathTranslatorVariableError, FastXpathTranslatorXPathError, ) @@ -60,21 +60,22 @@ def set(self, variable_names: Sequence[str], xpaths: Sequence[str]): :param xpaths: List of XML Paths """ if len(variable_names) != len(xpaths): - raise FastXpathTranslatorInconsistentLists( - f"lists var_names and xpaths have not the same length ({len(variable_names)} and {len(xpaths)})" + raise FastXpathTranslatorInconsistentListsError( + f"lists var_names and xpaths have not the same length ({len(variable_names)} " + f"and {len(xpaths)})" ) # check duplicate variable names dupe_vars = self._get_duplicates(variable_names) if dupe_vars: - raise FastXpathTranslatorDuplicates( + raise FastXpathTranslatorDuplicatesError( f"Following variable names are provided more than once: {dupe_vars}", dupe_vars ) # check duplicate XPaths dupe_xpaths = self._get_duplicates(xpaths) if dupe_xpaths: - raise FastXpathTranslatorDuplicates( + raise FastXpathTranslatorDuplicatesError( f"Following variable names are provided more than once: {dupe_xpaths}", dupe_xpaths, ) diff --git a/src/fastoad/io/xml/variable_io_base.py b/src/fastoad/io/xml/variable_io_base.py index cbc0771f9..72e9f8935 100644 --- a/src/fastoad/io/xml/variable_io_base.py +++ b/src/fastoad/io/xml/variable_io_base.py @@ -29,7 +29,7 @@ XPathEvalError, _Comment, _Element, -) # pylint: disable=protected-access # Useful for type hinting +) # Useful for type hinting from openmdao.vectors.vector import Vector from fastoad._utils.files import make_parent_dir diff --git a/src/fastoad/model_base/atmosphere.py b/src/fastoad/model_base/atmosphere.py index 33d7dcb3b..aa7434eef 100644 --- a/src/fastoad/model_base/atmosphere.py +++ b/src/fastoad/model_base/atmosphere.py @@ -60,7 +60,6 @@ class Atmosphere: >>> viscosities = atm.kinematic_viscosity # viscosities for all defined altitudes """ - # pylint: disable=too-many-instance-attributes # Needed for avoiding redoing computations def __init__( self, altitude: float | Sequence[float], diff --git a/src/fastoad/model_base/flight_point.py b/src/fastoad/model_base/flight_point.py index 30f267726..1b48043bb 100644 --- a/src/fastoad/model_base/flight_point.py +++ b/src/fastoad/model_base/flight_point.py @@ -141,11 +141,9 @@ class FlightPoint: default=None, metadata={FIELD_DESCRIPTOR: _FieldDescriptor()} ) - # pylint: disable=invalid-name #: Lift coefficient. CL: float = field(default=None, metadata={FIELD_DESCRIPTOR: _FieldDescriptor(unit="-")}) - # pylint: disable=invalid-name #: Drag coefficient. CD: float = field(default=None, metadata={FIELD_DESCRIPTOR: _FieldDescriptor(unit="-")}) diff --git a/src/fastoad/model_base/propulsion.py b/src/fastoad/model_base/propulsion.py index 8c3a4e0fe..8cb2e3957 100644 --- a/src/fastoad/model_base/propulsion.py +++ b/src/fastoad/model_base/propulsion.py @@ -70,13 +70,14 @@ def compute_flight_points(self, flight_points: FlightPoint | pd.DataFrame): defined one between :code:`thrust_rate` and :code:`thrust` (if both are provided, :code:`thrust_rate` will be used) - - if :code:`thrust_is_regulated` is :code:`True` or :code:`False` (i.e., not a sequence), - the considered input will be taken accordingly, and should of course be defined. - - - if there are several flight points, :code:`thrust_is_regulated` is a sequence or array, - :code:`thrust_rate` and :code:`thrust` should be provided and have the same shape as - :code:`thrust_is_regulated:code:`. The method will consider for each element which input - will be used according to :code:`thrust_is_regulated`. + - if :code:`thrust_is_regulated` is :code:`True` or :code:`False` (i.e., not a + sequence), the considered input will be taken accordingly, and should of course + be defined. + + - if there are several flight points, :code:`thrust_is_regulated` is a sequence or + array, :code:`thrust_rate` and :code:`thrust` should be provided and have the same + shape as :code:`thrust_is_regulated:code:`. The method will consider for each element + which input will be used according to :code:`thrust_is_regulated`. :param flight_points: FlightPoint or DataFrame instance diff --git a/src/fastoad/model_base/tests/test_atmosphere.py b/src/fastoad/model_base/tests/test_atmosphere.py index d83d276b3..e695b376c 100644 --- a/src/fastoad/model_base/tests/test_atmosphere.py +++ b/src/fastoad/model_base/tests/test_atmosphere.py @@ -157,7 +157,8 @@ def test_speed_conversions(): [270, 266.0652, 150.4692], [400, 394.1707, 222.9173], ] - # expected_CAS = [ # currently unused + # ruff:noqa ERA001 + # expected_CAS = [ # currently unused TODO erase it # [100.0, 98.5799, 56.3313], # [200.0, 197.3624, 116.1973], # [270, 161.8971, 266.6984], diff --git a/src/fastoad/model_base/tests/test_flight_point.py b/src/fastoad/model_base/tests/test_flight_point.py index 65e1030a3..0641e6d70 100644 --- a/src/fastoad/model_base/tests/test_flight_point.py +++ b/src/fastoad/model_base/tests/test_flight_point.py @@ -43,7 +43,7 @@ def test_add_remove_field(): def test_create(): - df = pd.DataFrame(dict(time=[0.0, 1.0, 2.0], mach=[0.0, 0.02, 0.05])) + df = pd.DataFrame({"time": [0.0, 1.0, 2.0], "mach": [0.0, 0.02, 0.05]}) flight_point = FlightPoint.create(df.iloc[1]) assert flight_point.time == 1.0 @@ -51,7 +51,7 @@ def test_create(): def test_create_list(): - df = pd.DataFrame(dict(time=[0.0, 1.0, 2.0], mach=[0.0, 0.02, 0.05])) + df = pd.DataFrame({"time": [0.0, 1.0, 2.0], "mach": [0.0, 0.02, 0.05]}) flight_points = FlightPoint.create_list(df) assert flight_points[0].time == 0.0 diff --git a/src/fastoad/models/performances/mission/exceptions.py b/src/fastoad/models/performances/mission/exceptions.py index 7ff8eaf18..ae1f8c2a0 100644 --- a/src/fastoad/models/performances/mission/exceptions.py +++ b/src/fastoad/models/performances/mission/exceptions.py @@ -12,22 +12,22 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from fastoad.exceptions import FastError, FastUnexpectedKeywordArgument +from fastoad.exceptions import FastError, FastUnexpectedKeywordArgumentError -class FastFlightSegmentUnexpectedKeywordArgument(FastUnexpectedKeywordArgument): +class FastFlightSegmentUnexpectedKeywordArgumentError(FastUnexpectedKeywordArgumentError): """ Raised when a segment is instantiated with an incorrect keyword argument. """ -class FastFlightPointUnexpectedKeywordArgument(FastUnexpectedKeywordArgument): +class FastFlightPointUnexpectedKeywordArgumentError(FastUnexpectedKeywordArgumentError): """ Raised when a FlightPoint is instantiated with an incorrect keyword argument. """ -class FastFlightSegmentIncompleteFlightPoint(FastError): +class FastFlightSegmentIncompleteFlightPointError(FastError): """ Raised when a segment computation encounters a FlightPoint instance without needed parameters. """ diff --git a/src/fastoad/models/performances/mission/mission.py b/src/fastoad/models/performances/mission/mission.py index b562d7047..07cab9cae 100644 --- a/src/fastoad/models/performances/mission/mission.py +++ b/src/fastoad/models/performances/mission/mission.py @@ -75,7 +75,7 @@ def _get_first_route_in_sequence(self, flight_sequence: FlightSequence) -> Range def compute_from(self, start: FlightPoint) -> pd.DataFrame: if self.target_fuel_consumption is None: self._flight_points = super().compute_from(start) - self._flight_points.loc[self._flight_points.name.isnull(), "name"] = "" + self._flight_points.loc[self._flight_points.name.isna(), "name"] = "" self._compute_reserve(self._flight_points) else: self._solve_cruise_distance(start) @@ -152,7 +152,7 @@ def _compute_flight(self, cruise_distance, start: FlightPoint): """ self.first_route.cruise_distance = cruise_distance flight_points = super().compute_from(start) - flight_points.loc[flight_points.name.isnull(), "name"] = "" + flight_points.loc[flight_points.name.isna(), "name"] = "" self._compute_reserve(flight_points) self._flight_points = flight_points return self.target_fuel_consumption - self.consumed_fuel diff --git a/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py b/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py index 22839a606..8426cdd95 100644 --- a/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py +++ b/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py @@ -20,6 +20,7 @@ import numpy as np from openmdao import api as om +from openmdao.vectors.vector import Vector from fastoad._utils.arrays import scalarize from fastoad.model_base import FlightPoint @@ -172,7 +173,7 @@ def variable_name(self, var_name: str | None): # We authorize colons next to "~", but we do as they were not present. var_name = var_name.replace(":~", "~").replace("~:", "~") parts = var_name.replace(":~", "~").split("~") - if len(parts) > 2: + if len(parts) > 2: # noqa: PLR2004 # Hidden feature for now: using a double tilda (~~) will trigger # a replacement by the mission name only # (useful for backward compatibility since we need to provide @@ -198,7 +199,7 @@ def variable_name(self, var_name: str | None): else: self._variable_name = None - def set_variable_value(self, inputs: Mapping): + def set_variable_value(self, inputs: Mapping | Vector): """ Sets numerical value from OpenMDAO inputs. @@ -208,8 +209,8 @@ def set_variable_value(self, inputs: Mapping): """ if self.variable_name: value = scalarize( - # Note: OpenMDAO `inputs` object has no `get()` method, so we need to do this: - inputs[self.variable_name] if self.variable_name in inputs else self.default_value + # Note: OpenMDAO `Vector` object has no `get()` method, so we need to do this: + inputs[self.variable_name] if self.variable_name in inputs else self.default_value # noqa: SIM401 ) if self._use_opposite: diff --git a/src/fastoad/models/performances/mission/mission_definition/mission_builder/mission_builder.py b/src/fastoad/models/performances/mission/mission_definition/mission_builder/mission_builder.py index 31e46a8da..490fefbb1 100644 --- a/src/fastoad/models/performances/mission/mission_definition/mission_builder/mission_builder.py +++ b/src/fastoad/models/performances/mission/mission_definition/mission_builder/mission_builder.py @@ -435,7 +435,7 @@ def _build_segment(self, segment_definition: Mapping, kwargs: dict) -> AbstractF for key, value in part_kwargs.items(): if key == POLAR_TAG: modifier_kwargs = deepcopy(value.get("modifier")) - value = Polar( + new_value = Polar( cl=value["CL"].value, cd=value["CD"].value, alpha=value["alpha"].value if "alpha" in value else None, @@ -455,10 +455,14 @@ def _build_segment(self, segment_definition: Mapping, kwargs: dict) -> AbstractF relative_fields = [ param.parameter_name for param in value.values() if param.is_relative ] - value = FlightPoint(**target_parameters) - value.set_as_relative(relative_fields) + new_value = FlightPoint(**target_parameters) + new_value.set_as_relative(relative_fields) + else: + new_value = value + else: + new_value = value - part_kwargs[key] = value + part_kwargs[key] = new_value self._replace_input_definitions_by_values(part_kwargs) diff --git a/src/fastoad/models/performances/mission/mission_definition/mission_builder/tests/test_mission_builder.py b/src/fastoad/models/performances/mission/mission_definition/mission_builder/tests/test_mission_builder.py index c578223ba..538fbfba1 100644 --- a/src/fastoad/models/performances/mission/mission_definition/mission_builder/tests/test_mission_builder.py +++ b/src/fastoad/models/performances/mission/mission_definition/mission_builder/tests/test_mission_builder.py @@ -366,15 +366,15 @@ def test_get_reserve(custom_segment_class): ) flight_points = pd.DataFrame( - dict( - mass=[70000, 65000, 55000, 45000], - name=[ + { + "mass": [70000, 65000, 55000, 45000], + "name": [ "data:mission:sizing:main:start", "data:mission:sizing:main:climb", "data:mission:sizing:other:start", "data:mission:sizing:other:climb", ], - ) + } ) assert_allclose(mission_builder.get_reserve(flight_points, "sizing"), 5000 * 0.03) diff --git a/src/fastoad/models/performances/mission/openmdao/base.py b/src/fastoad/models/performances/mission/openmdao/base.py index 8e9ffbc5b..1cbddfd59 100644 --- a/src/fastoad/models/performances/mission/openmdao/base.py +++ b/src/fastoad/models/performances/mission/openmdao/base.py @@ -31,7 +31,6 @@ from fastoad.models.performances.mission.openmdao.mission_wrapper import MissionWrapper -# pylint: disable=too-few-public-methods class NeedsOWE(System, metaclass=ABCMeta): """To be inherited when Operating Weight Empty variable is used.""" @@ -45,7 +44,6 @@ def initialize(self): ) -# pylint: disable=too-few-public-methods class NeedsMTOW(System, metaclass=ABCMeta): """To be inherited when Max TakeOff Weight variable is used.""" @@ -59,7 +57,6 @@ def initialize(self): ) -# pylint: disable=too-few-public-methods class NeedsMFW(System, metaclass=ABCMeta): """To be inherited when Max Fuel Weight variable is used.""" diff --git a/src/fastoad/models/performances/mission/openmdao/mission_run.py b/src/fastoad/models/performances/mission/openmdao/mission_run.py index e07d97e30..426db19d3 100644 --- a/src/fastoad/models/performances/mission/openmdao/mission_run.py +++ b/src/fastoad/models/performances/mission/openmdao/mission_run.py @@ -13,6 +13,7 @@ from __future__ import annotations +import contextlib import logging from os import PathLike @@ -30,6 +31,8 @@ _LOGGER = logging.getLogger(__name__) # Logger for this module +DUMMY_MAX_LOD = 10.0 # Used as initial LoD in _get_initial_polar() + class MissionComp(om.ExplicitComponent, BaseMissionComp): """ @@ -64,10 +67,8 @@ def setup(self): self.mission_name ) - try: + with contextlib.suppress(ValueError): self.add_input(self.options["reference_area_variable"], np.nan, units="m**2") - except ValueError: - pass # Global mission outputs self.add_output( @@ -224,8 +225,8 @@ def _get_initial_polar(inputs) -> Polar: """ At computation start, polar may be irrelevant and give a very low lift/drag ratio. - In that case, this method returns a fake polar that has 10.0 as max lift drag ratio. - Otherwise, the actual cruise polar is returned. + In that case, this method returns a fake polar that has DUMMY_MAX_LOD as max lift drag + ratio. Otherwise, the actual cruise polar is returned. """ high_speed_polar = Polar( inputs["data:aerodynamics:aircraft:cruise:CL"], @@ -235,13 +236,13 @@ def _get_initial_polar(inputs) -> Polar: try: if ( high_speed_polar.optimal_cl / high_speed_polar.cd(high_speed_polar.optimal_cl) - < 10.0 + < DUMMY_MAX_LOD ): use_minimum_l_d_ratio = True except ZeroDivisionError: use_minimum_l_d_ratio = True if use_minimum_l_d_ratio: - # We replace by a polar that has at least 10.0 as max L/D ratio + # We replace by a polar that has at least DUMMY_MAX_LOD as max L/D ratio high_speed_polar = Polar(np.array([0.0, 0.5, 1.0]), np.array([0.1, 0.05, 1.0])) return high_speed_polar diff --git a/src/fastoad/models/performances/mission/openmdao/tests/test_mission.py b/src/fastoad/models/performances/mission/openmdao/tests/test_mission.py index 890edb6d9..5f63c0dc3 100644 --- a/src/fastoad/models/performances/mission/openmdao/tests/test_mission.py +++ b/src/fastoad/models/performances/mission/openmdao/tests/test_mission.py @@ -36,8 +36,9 @@ def cleanup(): def plot_flight(flight_points, fig_filename): - from matplotlib import pyplot as plt - from matplotlib.ticker import MultipleLocator + # This function is used only for debug, so lazy imports are accepted + from matplotlib import pyplot as plt # noqa: PLC0415 + from matplotlib.ticker import MultipleLocator # noqa: PLC0415 plt.figure(figsize=(12, 12)) ax1 = plt.subplot(2, 1, 1) @@ -108,7 +109,8 @@ def test_mission_component(cleanup, with_dummy_plugin_2): ), ivc, ) - # plot_flight(problem.model.component.flight_points, "test_mission.png") + # Useful for debugging + # plot_flight(problem.model.component.flight_points, "test_mission.png") # noqa: ERA001 # Note: tested value are obtained by asking 1 meter of accuracy for distance routes assert_allclose(problem["data:mission:operational:taxi_out:fuel"], 100.0, atol=1.0) @@ -160,8 +162,8 @@ def test_mission_component(cleanup, with_dummy_plugin_2): def test_mission_component_breguet(cleanup, with_dummy_plugin_2): input_file_path = DATA_FOLDER_PATH / "test_mission.xml" - vars = DataFile(input_file_path) - ivc = vars.to_ivc() + variables = DataFile(input_file_path) + ivc = variables.to_ivc() ivc.add_output("data:mission:operational:ramp_weight", 70100.0, units="kg") problem = run_system( @@ -177,7 +179,8 @@ def test_mission_component_breguet(cleanup, with_dummy_plugin_2): ), ivc, ) - # plot_flight(problem.model.component.flight_points, "test_mission_component_breguet.png") + # Useful for debugging + # plot_flight(problem.model.component.flight_points, "test_mission_component_breguet.png") # noqa: E501, ERA001 assert_allclose(problem["data:mission:operational:needed_block_fuel"], 6256.0, atol=1.0) assert_allclose(problem["data:mission:operational:taxi_out:fuel"], 100.0, atol=1.0) @@ -265,9 +268,9 @@ def test_mission_group_breguet_without_fuel_adjustment(cleanup, with_dummy_plugi def test_mission_group_with_fuel_adjustment(cleanup, with_dummy_plugin_2): input_file_path = DATA_FOLDER_PATH / "test_mission.xml" - vars = DataFile(input_file_path) - del vars["data:mission:operational:TOW"] - ivc = vars.to_ivc() + variables = DataFile(input_file_path) + del variables["data:mission:operational:TOW"] + ivc = variables.to_ivc() problem = run_system( OMMission( @@ -312,9 +315,9 @@ def test_mission_group_breguet_with_fuel_adjustment(cleanup, with_dummy_plugin_2 # Also checking behavior when is_sizing is True input_file_path = DATA_FOLDER_PATH / "test_breguet.xml" - vars = DataFile(input_file_path) - del vars["data:mission:operational:ramp_weight"] - ivc = vars.to_ivc() + variables = DataFile(input_file_path) + del variables["data:mission:operational:ramp_weight"] + ivc = variables.to_ivc() problem = run_system( OMMission( @@ -357,8 +360,8 @@ def test_mission_group_breguet_with_fuel_adjustment(cleanup, with_dummy_plugin_2 def test_mission_group_with_fuel_objective(cleanup, with_dummy_plugin_2): input_file_path = DATA_FOLDER_PATH / "test_mission.xml" - vars = DataFile(input_file_path) - ivc = vars.to_ivc() + variables = DataFile(input_file_path) + ivc = variables.to_ivc() problem = run_system( OMMission( @@ -395,10 +398,10 @@ def test_mission_group_with_fuel_objective(cleanup, with_dummy_plugin_2): def test_mission_group_with_CL_limitation(cleanup, with_dummy_plugin_2): input_file_path = DATA_FOLDER_PATH / "test_mission.xml" - vars = DataFile(input_file_path) + variables = DataFile(input_file_path) # Activate CL limitation during cruise and climb - vars["data:mission:operational:max_CL"].value = 0.45 + variables["data:mission:operational:max_CL"].value = 0.45 problem = run_system( AdvancedMissionComp( @@ -411,7 +414,7 @@ def test_mission_group_with_CL_limitation(cleanup, with_dummy_plugin_2): ), reference_area_variable="data:geometry:aircraft:reference_area", ), - vars, + variables, ) flight_points = problem.model.component.flight_points @@ -419,17 +422,18 @@ def test_mission_group_with_CL_limitation(cleanup, with_dummy_plugin_2): CL_end_climb = climb_points.CL.iloc[-1] altitude_end_climb = climb_points.altitude.iloc[-1] - # check CL and flight level, CL is lower than 0.45 because the climb phase stops at the closest flight level. + # check CL and flight level, CL is lower than 0.45 because the climb phase stops at + # the closest flight level. assert_allclose(CL_end_climb, 0.445, atol=1e-3) assert_allclose(altitude_end_climb, 9753.6, atol=1e-1) # Now check climbing cruise with constant CL - vars.add_var("data:mission:operational_optimal:max_CL", val=0.45) - vars.add_var("data:mission:operational_optimal:taxi_out:thrust_rate", val=0.3) - vars.add_var("data:mission:operational_optimal:taxi_out:duration", val=300, units="s") - vars.add_var("data:mission:operational_optimal:takeoff:fuel", val=100, units="kg") - vars.add_var("data:mission:operational_optimal:takeoff:V2", val=70, units="m/s") - vars.add_var("data:mission:operational_optimal:TOW", val=70000, units="kg") + variables.add_var("data:mission:operational_optimal:max_CL", val=0.45) + variables.add_var("data:mission:operational_optimal:taxi_out:thrust_rate", val=0.3) + variables.add_var("data:mission:operational_optimal:taxi_out:duration", val=300, units="s") + variables.add_var("data:mission:operational_optimal:takeoff:fuel", val=100, units="kg") + variables.add_var("data:mission:operational_optimal:takeoff:V2", val=70, units="m/s") + variables.add_var("data:mission:operational_optimal:TOW", val=70000, units="kg") problem = run_system( AdvancedMissionComp( @@ -442,7 +446,7 @@ def test_mission_group_with_CL_limitation(cleanup, with_dummy_plugin_2): ), reference_area_variable="data:geometry:aircraft:reference_area", ), - vars, + variables, ) flight_points = problem.model.component.flight_points diff --git a/src/fastoad/models/performances/mission/openmdao/tests/test_mission_run.py b/src/fastoad/models/performances/mission/openmdao/tests/test_mission_run.py index 25fca65f5..b4c74b51a 100644 --- a/src/fastoad/models/performances/mission/openmdao/tests/test_mission_run.py +++ b/src/fastoad/models/performances/mission/openmdao/tests/test_mission_run.py @@ -47,7 +47,8 @@ def test_mission_run(cleanup, with_dummy_plugin_2): ), ivc, ) - # plot_flight(problem.model.component.flight_points, "test_mission.png") + # Useful for debugging + # plot_flight(problem.model.component.flight_points, "test_mission.png") # noqa: ERA001 # Note: tested value are obtained by asking 1 meter of accuracy for distance routes diff --git a/src/fastoad/models/performances/mission/openmdao/tests/test_mission_takeoff.py b/src/fastoad/models/performances/mission/openmdao/tests/test_mission_takeoff.py index 2aa2359b7..3611e1426 100644 --- a/src/fastoad/models/performances/mission/openmdao/tests/test_mission_takeoff.py +++ b/src/fastoad/models/performances/mission/openmdao/tests/test_mission_takeoff.py @@ -36,8 +36,9 @@ def cleanup(): def plot_flight(flight_points, fig_filename): - from matplotlib import pyplot as plt - from matplotlib.ticker import MultipleLocator + # This function is used only for debug, so lazy imports are accepted + from matplotlib import pyplot as plt # noqa: PLC0415 + from matplotlib.ticker import MultipleLocator # noqa: PLC0415 plt.figure(figsize=(12, 12)) ax1 = plt.subplot(2, 1, 1) @@ -111,7 +112,8 @@ def test_mission_component(cleanup, with_dummy_plugin_2): ), ivc, ) - # plot_flight(problem.model.component.flight_points, "test_mission.png") + # Useful for debugging + # plot_flight(problem.model.component.flight_points, "test_mission.png") # noqa: ERA001 take_off_distance = problem[ "data:mission:operational_wo_gnd_effect:takeoff_wo_gnd_effect:distance" ] @@ -156,7 +158,8 @@ def test_ground_effect(cleanup, with_dummy_plugin_2): ), ivc, ) - # plot_flight(problem.model.component.flight_points, "test_mission.png") + # Useful for debugging + # plot_flight(problem.model.component.flight_points, "test_mission.png") # noqa: ERA001 take_off_distance = problem["data:mission:operational:takeoff:distance"] assert_allclose(take_off_distance, 1762, atol=1.0) assert_allclose(problem["data:mission:operational:takeoff:fuel"], 122.1, atol=1e-1) @@ -188,7 +191,8 @@ def test_start_stop(cleanup, with_dummy_plugin_2): ), ivc, ) - # plot_flight(problem.model.component.flight_points, "test_mission.png") + # Useful for debugging + # plot_flight(problem.model.component.flight_points, "test_mission.png") # noqa: ERA001 start_stop_distance = problem["data:mission:start_stop_mission:start_stop:distance"] assert_allclose(start_stop_distance, 1659, atol=1.0) assert_allclose(problem["data:mission:start_stop_mission:start_stop:duration"], 42.8, atol=1e-1) diff --git a/src/fastoad/models/performances/mission/openmdao/tests/test_sizing_mission.py b/src/fastoad/models/performances/mission/openmdao/tests/test_sizing_mission.py index 890c256bd..fab703cbf 100644 --- a/src/fastoad/models/performances/mission/openmdao/tests/test_sizing_mission.py +++ b/src/fastoad/models/performances/mission/openmdao/tests/test_sizing_mission.py @@ -24,6 +24,8 @@ RESULTS_FOLDER_PATH = Path(__file__).parent / "results" / Path(__file__).stem +# ruff:noqa ERA001 + @pytest.fixture(scope="module") def cleanup(): @@ -71,6 +73,7 @@ def test_sizing_mission(cleanup, with_dummy_plugin_2): ), ivc, ) + # Useful for debugging # import pandas as pd # # plot_flight( diff --git a/src/fastoad/models/performances/mission/segments/base.py b/src/fastoad/models/performances/mission/segments/base.py index d15d14ecf..158a760f5 100644 --- a/src/fastoad/models/performances/mission/segments/base.py +++ b/src/fastoad/models/performances/mission/segments/base.py @@ -14,6 +14,7 @@ from __future__ import annotations +import contextlib from abc import ABC, abstractmethod from copy import deepcopy from dataclasses import dataclass, field @@ -28,7 +29,7 @@ from fastoad.model_base.datacls import MANDATORY_FIELD from ..base import IFlightPart, RegisterElement -from ..exceptions import FastFlightSegmentIncompleteFlightPoint +from ..exceptions import FastFlightSegmentIncompleteFlightPointError class RegisterSegment(RegisterElement, base_class=IFlightPart): @@ -138,10 +139,10 @@ class AbstractFlightSegment(IFlightPart, ABC): isa_offset: float = 0.0 #: Using this value will tell to keep the associated parameter constant. - CONSTANT_VALUE = "constant" # pylint: disable=invalid-name # used as constant + CONSTANT_VALUE = "constant" # To be noted: this one is not a dataclass field, but an actual class attribute - _attribute_units: ClassVar[dict] = dict(reference_area="m**2", time_step="s") + _attribute_units: ClassVar[dict] = {"reference_area": "m**2", "time_step": "s"} @abstractmethod def compute_from_start_to_target(self, start, target) -> pd.DataFrame: @@ -206,10 +207,8 @@ def compute_from(self, start: FlightPoint) -> pd.DataFrame: start_copy = deepcopy(start) if start_copy.altitude is not None: - try: + with contextlib.suppress(FastFlightSegmentIncompleteFlightPointError): self.complete_flight_point(start_copy) - except FastFlightSegmentIncompleteFlightPoint: - pass start_copy.scalarize() start_copy.isa_offset = self.isa_offset @@ -310,7 +309,7 @@ def _complete_speed_values( elif flight_point.equivalent_airspeed is not None: atm.equivalent_airspeed = flight_point.equivalent_airspeed elif raise_error_on_missing_speeds: - raise FastFlightSegmentIncompleteFlightPoint( + raise FastFlightSegmentIncompleteFlightPointError( "Flight point should be defined for true_airspeed, " "equivalent_airspeed, or mach." ) diff --git a/src/fastoad/models/performances/mission/segments/macro_segments.py b/src/fastoad/models/performances/mission/segments/macro_segments.py index 41cc86f45..0af32dca7 100644 --- a/src/fastoad/models/performances/mission/segments/macro_segments.py +++ b/src/fastoad/models/performances/mission/segments/macro_segments.py @@ -86,9 +86,9 @@ def build_sequence(self): } segment_kwargs = { name: value - for name, value in dict( - (cls_field.name, getattr(self, cls_field.name)) for cls_field in fields(self) - ).items() + for name, value in { + cls_field.name: getattr(self, cls_field.name) for cls_field in fields(self) + }.items() if name in segment_field_names } segment_kwargs["target"] = FlightPoint() diff --git a/src/fastoad/models/performances/mission/segments/registered/altitude_change.py b/src/fastoad/models/performances/mission/segments/registered/altitude_change.py index d28039711..2aa4c5a89 100644 --- a/src/fastoad/models/performances/mission/segments/registered/altitude_change.py +++ b/src/fastoad/models/performances/mission/segments/registered/altitude_change.py @@ -21,7 +21,9 @@ from scipy.constants import foot, g from fastoad.model_base import FlightPoint -from fastoad.models.performances.mission.exceptions import FastFlightSegmentIncompleteFlightPoint +from fastoad.models.performances.mission.exceptions import ( + FastFlightSegmentIncompleteFlightPointError, +) from fastoad.models.performances.mission.segments.base import ( RegisterSegment, ) @@ -81,11 +83,11 @@ class AltitudeChangeSegment(AbstractManualThrustSegment, AbstractLiftFromWeightS _original_target_altitude: str | None = field(default=None, init=False) #: Using this value will tell to target the altitude with max lift/drag ratio. - OPTIMAL_ALTITUDE = "optimal_altitude" # pylint: disable=invalid-name # used as constant + OPTIMAL_ALTITUDE = "optimal_altitude" # used as constant #: Using this value will tell to target the nearest flight level to altitude #: with max lift/drag ratio. - OPTIMAL_FLIGHT_LEVEL = "optimal_flight_level" # pylint: disable=invalid-name # used as constant + OPTIMAL_FLIGHT_LEVEL = "optimal_flight_level" # used as constant def compute_from_start_to_target(self, start: FlightPoint, target: FlightPoint) -> pd.DataFrame: if target.altitude is not None: @@ -148,7 +150,7 @@ def get_distance_to_target( distance_to_target = target.mach - current.mach if distance_to_target is None: - raise FastFlightSegmentIncompleteFlightPoint( + raise FastFlightSegmentIncompleteFlightPointError( "No valid target definition for altitude change." ) return distance_to_target diff --git a/src/fastoad/models/performances/mission/segments/registered/ground_speed_change.py b/src/fastoad/models/performances/mission/segments/registered/ground_speed_change.py index 78e0dac6e..56adffd60 100644 --- a/src/fastoad/models/performances/mission/segments/registered/ground_speed_change.py +++ b/src/fastoad/models/performances/mission/segments/registered/ground_speed_change.py @@ -15,7 +15,9 @@ from dataclasses import dataclass from fastoad.model_base import FlightPoint -from fastoad.models.performances.mission.exceptions import FastFlightSegmentIncompleteFlightPoint +from fastoad.models.performances.mission.exceptions import ( + FastFlightSegmentIncompleteFlightPointError, +) from fastoad.models.performances.mission.segments.base import RegisterSegment from fastoad.models.performances.mission.segments.time_step_base import AbstractGroundSegment @@ -39,6 +41,6 @@ def get_distance_to_target( if target.mach is not None: return target.mach - flight_points[-1].mach - raise FastFlightSegmentIncompleteFlightPoint( + raise FastFlightSegmentIncompleteFlightPointError( "No valid target definition for airspeed change at takeoff." ) diff --git a/src/fastoad/models/performances/mission/segments/registered/speed_change.py b/src/fastoad/models/performances/mission/segments/registered/speed_change.py index 5c4a806c3..39f5dbaf8 100644 --- a/src/fastoad/models/performances/mission/segments/registered/speed_change.py +++ b/src/fastoad/models/performances/mission/segments/registered/speed_change.py @@ -15,7 +15,9 @@ from dataclasses import dataclass from fastoad.model_base import FlightPoint -from fastoad.models.performances.mission.exceptions import FastFlightSegmentIncompleteFlightPoint +from fastoad.models.performances.mission.exceptions import ( + FastFlightSegmentIncompleteFlightPointError, +) from fastoad.models.performances.mission.segments.base import ( RegisterSegment, ) @@ -45,7 +47,7 @@ def get_distance_to_target( if target.mach is not None: return target.mach - flight_points[-1].mach - raise FastFlightSegmentIncompleteFlightPoint( + raise FastFlightSegmentIncompleteFlightPointError( "No valid target definition for altitude change." ) diff --git a/src/fastoad/models/performances/mission/segments/registered/start.py b/src/fastoad/models/performances/mission/segments/registered/start.py index acd3c5231..923dd36c0 100644 --- a/src/fastoad/models/performances/mission/segments/registered/start.py +++ b/src/fastoad/models/performances/mission/segments/registered/start.py @@ -17,7 +17,9 @@ import pandas as pd from fastoad.model_base import FlightPoint -from fastoad.models.performances.mission.exceptions import FastFlightSegmentIncompleteFlightPoint +from fastoad.models.performances.mission.exceptions import ( + FastFlightSegmentIncompleteFlightPointError, +) from fastoad.models.performances.mission.segments.base import AbstractFlightSegment, RegisterSegment @@ -40,7 +42,7 @@ def compute_from_start_to_target(self, start: FlightPoint, target: FlightPoint) try: self.complete_flight_point(target) - except FastFlightSegmentIncompleteFlightPoint: + except FastFlightSegmentIncompleteFlightPointError: target.true_airspeed = 0.0 self.complete_flight_point(target) diff --git a/src/fastoad/models/performances/mission/segments/registered/takeoff/end_of_takeoff.py b/src/fastoad/models/performances/mission/segments/registered/takeoff/end_of_takeoff.py index 72ec69807..0f266518f 100644 --- a/src/fastoad/models/performances/mission/segments/registered/takeoff/end_of_takeoff.py +++ b/src/fastoad/models/performances/mission/segments/registered/takeoff/end_of_takeoff.py @@ -15,7 +15,9 @@ from dataclasses import dataclass from fastoad.model_base import FlightPoint -from fastoad.models.performances.mission.exceptions import FastFlightSegmentIncompleteFlightPoint +from fastoad.models.performances.mission.exceptions import ( + FastFlightSegmentIncompleteFlightPointError, +) from fastoad.models.performances.mission.segments.base import RegisterSegment from fastoad.models.performances.mission.segments.time_step_base import AbstractTakeOffSegment @@ -63,7 +65,7 @@ def get_distance_to_target( if target.altitude is not None: return target.altitude - current.altitude - raise FastFlightSegmentIncompleteFlightPoint( + raise FastFlightSegmentIncompleteFlightPointError( "No valid target definition for altitude change." ) diff --git a/src/fastoad/models/performances/mission/segments/registered/tests/test_altitude_change.py b/src/fastoad/models/performances/mission/segments/registered/tests/test_altitude_change.py index 78c40a29d..f28cb0c1f 100644 --- a/src/fastoad/models/performances/mission/segments/registered/tests/test_altitude_change.py +++ b/src/fastoad/models/performances/mission/segments/registered/tests/test_altitude_change.py @@ -714,7 +714,7 @@ def test_descent_to_fixed_EAS_at_constant_mach(polar): reference_area=100.0, polar=polar, thrust_rate=0.1, - # time_step=5.0, # we use default time step + # time_step=5.0, # we use default time step # noqa: ERA001 ) def run(): diff --git a/src/fastoad/models/performances/mission/segments/registered/tests/test_takeoff_segments.py b/src/fastoad/models/performances/mission/segments/registered/tests/test_takeoff_segments.py index db02bfdc6..f8c4b10f5 100644 --- a/src/fastoad/models/performances/mission/segments/registered/tests/test_takeoff_segments.py +++ b/src/fastoad/models/performances/mission/segments/registered/tests/test_takeoff_segments.py @@ -208,11 +208,11 @@ def test_takeoff(polar, polar_modifier): reference_area=120.0, polar=polar, polar_modifier=polar_modifier, - # engine_setting=EngineSetting.CLIMB, # using default value + # engine_setting=EngineSetting.CLIMB, # using default value # noqa: ERA001 rotation_equivalent_airspeed=75.0, rotation_rate=0.05, rotation_alpha_limit=0.1, - # thrust_rate=1.0, # using default value + # thrust_rate=1.0, # using default value # noqa: ERA001 time_step=0.2, ) diff --git a/src/fastoad/models/performances/mission/segments/time_step_base.py b/src/fastoad/models/performances/mission/segments/time_step_base.py index f30ad3c31..6b6ab7a1d 100644 --- a/src/fastoad/models/performances/mission/segments/time_step_base.py +++ b/src/fastoad/models/performances/mission/segments/time_step_base.py @@ -145,7 +145,7 @@ def get_gamma_and_acceleration(self, flight_point: FlightPoint) -> tuple[float, def get_next_alpha( self, previous_point: FlightPoint, - time_step: float, # pylint: disable=unused-argument + time_step: float, ) -> float: """ Determine the next angle of attack. diff --git a/src/fastoad/models/performances/mission/tests/conftest.py b/src/fastoad/models/performances/mission/tests/conftest.py index 56a4ed485..1fcf42bf4 100644 --- a/src/fastoad/models/performances/mission/tests/conftest.py +++ b/src/fastoad/models/performances/mission/tests/conftest.py @@ -14,7 +14,6 @@ from __future__ import annotations from dataclasses import InitVar, dataclass, field -from pathlib import Path import numpy as np import pandas as pd @@ -26,6 +25,7 @@ from fastoad.model_base.datacls import MANDATORY_FIELD from fastoad.model_base.propulsion import FuelEngineSet, IPropulsion from fastoad.models.performances.mission.base import FlightSequence +from tests.dummy_plugins.dist_2.dummy_plugin_2.models.subpackage.dummy_engine import DummyEngine from ..polar import Polar from ..segments.registered.altitude_change import AltitudeChangeSegment @@ -35,8 +35,6 @@ @pytest.fixture(scope="module") def propulsion(): - from tests.dummy_plugins.dist_2.dummy_plugin_2.models.subpackage.dummy_engine import DummyEngine - return FuelEngineSet(DummyEngine(1.0e5, 1.0e-4), 2) @@ -66,52 +64,6 @@ def print_dataframe(df, max_rows=20): print(df) -def plot_flight(flight_points, fig_filename, results_folder_path): - """Utility for plotting mission profile.""" - from matplotlib import pyplot as plt - from matplotlib.ticker import MultipleLocator - - plt.figure(figsize=(12, 12)) - ax1 = plt.subplot(2, 1, 1) - plt.plot(flight_points.ground_distance / 1000.0, flight_points.altitude / foot, "o-") - plt.xlabel("distance [km]") - plt.ylabel("altitude [ft]") - ax1.xaxis.set_minor_locator(MultipleLocator(50)) - ax1.yaxis.set_minor_locator(MultipleLocator(500)) - plt.grid(which="major", color="k") - plt.grid(which="minor") - - ax2 = plt.subplot(2, 1, 2) - lines = [] - lines += plt.plot( - flight_points.ground_distance / 1000.0, flight_points.true_airspeed, "b-", label="TAS [m/s]" - ) - lines += plt.plot( - flight_points.ground_distance / 1000.0, - flight_points.equivalent_airspeed / knot, - "g--", - label="EAS [kt]", - ) - plt.xlabel("distance [km]") - plt.ylabel("speed") - ax2.xaxis.set_minor_locator(MultipleLocator(50)) - ax2.yaxis.set_minor_locator(MultipleLocator(5)) - plt.grid(which="major", color="k") - plt.grid(which="minor") - - plt.twinx(ax2) - lines += plt.plot( - flight_points.ground_distance / 1000.0, flight_points.mach, "r.-", label="Mach" - ) - plt.ylabel("Mach") - - labels = [line.get_label() for line in lines] - plt.legend(lines, labels, loc=0) - - plt.savefig(Path(results_folder_path, fig_filename)) - plt.close() - - # We define here in Python the flight phases that feed the test of RangedRoute =========== @dataclass class AbstractManualThrustFlightPhase(FlightSequence): diff --git a/src/fastoad/models/performances/mission/tests/test_flight_sequence.py b/src/fastoad/models/performances/mission/tests/test_flight_sequence.py index 0b5e9160a..a35d45773 100644 --- a/src/fastoad/models/performances/mission/tests/test_flight_sequence.py +++ b/src/fastoad/models/performances/mission/tests/test_flight_sequence.py @@ -21,7 +21,7 @@ from ..segments.registered.mass_input import MassTargetSegment from ..segments.registered.taxi import TaxiSegment -# ruff: noqa: RUF005 +# ruff: noqa: RUF005 In this test it is clearer with + def get_taxi_definition(propulsion, target_mass=None): diff --git a/src/fastoad/models/performances/mission/tests/test_mission.py b/src/fastoad/models/performances/mission/tests/test_mission.py index b5af582dd..57d50a162 100644 --- a/src/fastoad/models/performances/mission/tests/test_mission.py +++ b/src/fastoad/models/performances/mission/tests/test_mission.py @@ -27,7 +27,7 @@ def test_mission(low_speed_polar, high_speed_polar, propulsion): first_route_distance = 2.0e6 second_route_distance = 5.0e5 - kwargs = dict(propulsion=propulsion, reference_area=120.0) + kwargs = {"propulsion": propulsion, "reference_area": 120.0} taxi_out = TaxiPhase( # This phase is here to test when mission do not start with route time=300.0, @@ -120,7 +120,8 @@ def test_mission(low_speed_polar, high_speed_polar, propulsion): flight_points = mission_1.compute_from(start) - # plot_flight(flight_points, "test_ranged_flight.png") + # Useful for debugging + # plot_flight(flight_points, "test_ranged_flight.png") # noqa: ERA001 assert_allclose( flight_points.iloc[-1].ground_distance, @@ -145,7 +146,8 @@ def test_mission(low_speed_polar, high_speed_polar, propulsion): flight_points = mission_2.compute_from(start) - # plot_flight(flight_points, "test_ranged_flight.png") + # Useful for debugging + # plot_flight(flight_points, "test_ranged_flight.png") # noqa: ERA001 assert_allclose( flight_points.iloc[-1].ground_distance, diff --git a/src/fastoad/models/performances/mission/tests/test_routes.py b/src/fastoad/models/performances/mission/tests/test_routes.py index a2fa0b70a..9437aaeee 100644 --- a/src/fastoad/models/performances/mission/tests/test_routes.py +++ b/src/fastoad/models/performances/mission/tests/test_routes.py @@ -37,7 +37,7 @@ def cleanup(): def test_ranged_route(low_speed_polar, high_speed_polar, propulsion, cleanup): total_distance = 2.0e6 - kwargs = dict(propulsion=propulsion, reference_area=120.0) + kwargs = {"propulsion": propulsion, "reference_area": 120.0} initial_climb = InitialClimbPhase( **kwargs, polar=low_speed_polar, thrust_rate=1.0, name="initial_climb", time_step=0.2 ) @@ -79,7 +79,8 @@ def test_ranged_route(low_speed_polar, high_speed_polar, propulsion, cleanup): ) flight_points = flight_calculator.compute_from(start) - # plot_flight(flight_points, "test_ranged_flight.png", RESULTS_FOLDER_PATH) + # Useful for debugging + # plot_flight(flight_points, "test_ranged_flight.png", RESULTS_FOLDER_PATH) # noqa: ERA001 assert_allclose( flight_points.iloc[-1].ground_distance, diff --git a/src/fastoad/models/performances/mission/util.py b/src/fastoad/models/performances/mission/util.py index 77c3a1a5f..fe64a0c67 100644 --- a/src/fastoad/models/performances/mission/util.py +++ b/src/fastoad/models/performances/mission/util.py @@ -47,10 +47,7 @@ def get_closest_flight_level(altitude, base_level=0, level_step=10, *, up_direct :param up_direction: True if next flight level is upper. False if lower :return: the altitude in meters of the asked flight level. """ - if up_direction: - func = np.ceil - else: - func = np.floor + func = np.ceil if up_direction else np.floor base_altitude = FLIGHT_LEVEL * base_level return base_altitude + FLIGHT_LEVEL * level_step * func( diff --git a/src/fastoad/module_management/_bundle_loader.py b/src/fastoad/module_management/_bundle_loader.py index 1a9ae6ab7..df3aef860 100644 --- a/src/fastoad/module_management/_bundle_loader.py +++ b/src/fastoad/module_management/_bundle_loader.py @@ -200,12 +200,12 @@ def get_factory_names( ) else: for prop_name, prop_value in properties.items(): - if prop_name not in factory_properties.keys(): + if prop_name not in factory_properties: to_be_kept = False break factory_prop_value = factory_properties[prop_name] if isinstance(prop_value, str): - prop_value = prop_value.lower() + prop_value = prop_value.lower() # noqa: PLW2901 factory_prop_value = factory_prop_value.lower() if prop_value != factory_prop_value: to_be_kept = False @@ -331,10 +331,7 @@ def _get_service_references( # Dev Note: simple wrapper for BundleContext.get_all_service_references() - if case_sensitive: - operator = "=" - else: - operator = "~=" + operator = "=" if case_sensitive else "~=" if not properties: ldap_filter = None diff --git a/src/fastoad/module_management/_plugins.py b/src/fastoad/module_management/_plugins.py index b1dfd11e0..c00f74d9e 100644 --- a/src/fastoad/module_management/_plugins.py +++ b/src/fastoad/module_management/_plugins.py @@ -64,7 +64,7 @@ class DistributionNameDict(AbstractNormalizedDict): """ @staticmethod - def normalize(dist_name: str | None): # pylint: disable=arguments-differ + def normalize(dist_name: str | None): """ Returns a normalized distribution name for PEP-426-compliant comparison of distribution names. @@ -343,13 +343,13 @@ def to_dict(self) -> dict: ) conf_files = [item.name for item in self.get_configuration_file_list()] source_data_files = [item.name for item in self.get_source_data_file_list()] - return dict( - installed_package=self.dist_name, - has_models=has_models, - has_notebooks=has_notebooks, - configurations=sorted(conf_files), - source_data_files=sorted(source_data_files), - ) + return { + "installed_package": self.dist_name, + "has_models": has_models, + "has_notebooks": has_notebooks, + "configurations": sorted(conf_files), + "source_data_files": sorted(source_data_files), + } class FastoadLoader(BundleLoader): diff --git a/src/fastoad/module_management/exceptions.py b/src/fastoad/module_management/exceptions.py index e2d4790b9..045f78f71 100644 --- a/src/fastoad/module_management/exceptions.py +++ b/src/fastoad/module_management/exceptions.py @@ -73,7 +73,8 @@ def __init__(self, registered_class: type, service_id: str, base_class: type): :param base_class: the unmatched interface """ super().__init__( - f'Trying to register {registered_class!s} as service "{service_id}" but it does not inherit from {base_class!s}' + f'Trying to register {registered_class!s} as service "{service_id}" but it does not ' + f"inherit from {base_class!s}" ) self.registered_class = registered_class self.service_id = service_id @@ -105,7 +106,8 @@ def __init__(self, service_id: str, candidates: Sequence[str]): :param candidates: """ super().__init__( - f'Submodel requirement "{service_id}" needs a choice among following candidates: {candidates}' + f'Submodel requirement "{service_id}" needs a choice among following ' + f"candidates: {candidates}" ) self.service_id = service_id self.candidates = candidates diff --git a/src/fastoad/module_management/service_registry.py b/src/fastoad/module_management/service_registry.py index 33b0653fc..6c256eb89 100644 --- a/src/fastoad/module_management/service_registry.py +++ b/src/fastoad/module_management/service_registry.py @@ -481,12 +481,7 @@ def get_submodel(cls, service_id: str, options: dict | None = None): submodel_id = submodel_ids[0] - if submodel_id: - instance = super().get_system(submodel_id, options) - else: - instance = om.Group() - - return instance + return super().get_system(submodel_id, options) if submodel_id else om.Group() # istance @classmethod def cancel_submodel_deactivations(cls): diff --git a/src/fastoad/module_management/tests/test_bundle_loader.py b/src/fastoad/module_management/tests/test_bundle_loader.py index 8fa44228c..163ec2c48 100644 --- a/src/fastoad/module_management/tests/test_bundle_loader.py +++ b/src/fastoad/module_management/tests/test_bundle_loader.py @@ -121,7 +121,7 @@ def test_install_packages_on_faulty_install(delete_framework): loader = BundleLoader() # Create the buggy numpy install - import sys + import sys # noqa: PLC0415 # import should stay here sys.modules["numpy.random.mtrand"].__path__ = None diff --git a/src/fastoad/notebooks/01_Quick_start/modules/stresses.py b/src/fastoad/notebooks/01_Quick_start/modules/stresses.py index 8c12a8c12..12c205b39 100644 --- a/src/fastoad/notebooks/01_Quick_start/modules/stresses.py +++ b/src/fastoad/notebooks/01_Quick_start/modules/stresses.py @@ -47,8 +47,5 @@ def compute(self, inputs, outputs): s = inputs["data:material:yield_stress"] # Max bending location along the beam - if w * L - F > 0: - y_max = (w * L - F) / w - else: - y_max = 0 + y_max = (w * L - F) / w if w * L - F > 0 else 0 outputs["data:geometry:Ixx"] = (L - y_max) * (F - 0.5 * w * (L - y_max)) * h / (2 * s) diff --git a/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/02_pure_Python.ipynb b/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/02_pure_Python.ipynb index 78fe6e0bb..94a89617c 100644 --- a/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/02_pure_Python.ipynb +++ b/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/02_pure_Python.ipynb @@ -331,7 +331,7 @@ ")\n", "fig.add_trace(old_mtow_scatter)\n", "fig.layout = go.Layout(\n", - " yaxis=dict(scaleanchor=\"x\", scaleratio=0.8),\n", + " yaxis={\"scaleanchor\": \"x\", \"scaleratio\": 0.8},\n", " height=800,\n", " title_text=\"Graphic resolution\",\n", " title_x=0.42,\n", diff --git a/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/05_FAST-OAD.ipynb b/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/05_FAST-OAD.ipynb index 8bf3107f8..881224ba8 100644 --- a/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/05_FAST-OAD.ipynb +++ b/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/05_FAST-OAD.ipynb @@ -419,7 +419,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": ".venv (3.12.9)", "language": "python", "name": "python3" }, @@ -433,7 +433,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.9" } }, "nbformat": 4, diff --git a/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/modules/OpenMDAO/aerodynamics/sub_components/compute_profile_drag.py b/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/modules/OpenMDAO/aerodynamics/sub_components/compute_profile_drag.py index a6f56d7dc..df39d73a9 100644 --- a/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/modules/OpenMDAO/aerodynamics/sub_components/compute_profile_drag.py +++ b/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/modules/OpenMDAO/aerodynamics/sub_components/compute_profile_drag.py @@ -26,7 +26,8 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): # Profile drag coefficient of the aircraft without the wings cd0_other = 0.022 - # Constant linking the wing profile drag to its wet area, and by extension, its reference area + # Constant linking the wing profile drag to its wet area, and by extension, its reference + # area c = 0.0004 # Computation of the profile drag diff --git a/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/modules/pure_python/aerodynamics/sub_components/compute_lift_to_drag_ratio.py b/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/modules/pure_python/aerodynamics/sub_components/compute_lift_to_drag_ratio.py index 47468201b..dcb5adb8e 100644 --- a/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/modules/pure_python/aerodynamics/sub_components/compute_lift_to_drag_ratio.py +++ b/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/modules/pure_python/aerodynamics/sub_components/compute_lift_to_drag_ratio.py @@ -5,7 +5,8 @@ def compute_l_d(cruise_altitude, cruise_speed, cd0, k, mtow, wing_area): """ - Computes the lift to drag ratio considering a lift equilibrium in cruise and a simple quadratic model + Computes the lift to drag ratio considering a lift equilibrium in cruise and a simple quadratic + model :param cruise_altitude: Cruise altitude, in m :param cruise_speed: Cruise speed, in m/s diff --git a/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/modules/python_for_scipy/compute_fuel_scipy.py b/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/modules/python_for_scipy/compute_fuel_scipy.py index 0a2f9a536..ea1a7e1d0 100644 --- a/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/modules/python_for_scipy/compute_fuel_scipy.py +++ b/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/modules/python_for_scipy/compute_fuel_scipy.py @@ -8,8 +8,8 @@ def compute_fuel_scipy( x, wing_loading, cruise_altitude, cruise_speed, mission_range, payload, tsfc ): """ - Gather all the module main functions in the program main function that will compute the fuel consumed for the given - MTOW + Gather all the module main functions in the program main function that will compute the fuel + consumed for the given MTOW :param x: Tuple containing the Old Max Take-Off Weight, in kg and the aspect ration with no unit :param wing_loading: Wing loading, in kg/m2 diff --git a/src/fastoad/openmdao/exceptions.py b/src/fastoad/openmdao/exceptions.py index 7eb626d3f..74329dfdf 100644 --- a/src/fastoad/openmdao/exceptions.py +++ b/src/fastoad/openmdao/exceptions.py @@ -26,7 +26,7 @@ class FASTNanInInputsError(FastError): def __init__(self, input_file_path: [str, PathLike], nan_variable_names: Iterable[str]): self.input_file_path = as_path(input_file_path) - self.nan_variable_names = sorted(list(nan_variable_names)) + self.nan_variable_names = sorted(nan_variable_names) msg = ( f"NaN values found in inputs. Please check that {input_file_path} contains " diff --git a/src/fastoad/openmdao/tests/test_variables.py b/src/fastoad/openmdao/tests/test_variables.py index 770c5d723..f0a01b5ff 100644 --- a/src/fastoad/openmdao/tests/test_variables.py +++ b/src/fastoad/openmdao/tests/test_variables.py @@ -130,12 +130,12 @@ def test_ivc_from_to_variables(): """ Tests VariableList.to_ivc() and VariableList.from_ivc() """ - vars = VariableList() - vars["a"] = {"value": 5} - vars["b"] = {"value": 2.5, "units": "m"} - vars["c"] = {"value": -3.2, "units": "kg/s", "desc": "some test"} + variables = VariableList() + variables["a"] = {"value": 5} + variables["b"] = {"value": 2.5, "units": "m"} + variables["c"] = {"value": -3.2, "units": "kg/s", "desc": "some test"} - ivc = vars.to_ivc() + ivc = variables.to_ivc() problem = om.Problem(reports=False) problem.model.add_subsystem("ivc", ivc, promotes=["*"]) problem.setup() @@ -144,10 +144,10 @@ def test_ivc_from_to_variables(): assert problem.get_val("b", units="cm") == 250 assert problem.get_val("c", units="kg/ms") == -0.0032 - ivc = vars.to_ivc() - new_vars = VariableList.from_ivc(ivc) - assert vars.names() == new_vars.names() - for var, new_var in zip(vars, new_vars): + ivc = variables.to_ivc() + new_variables = VariableList.from_ivc(ivc) + assert variables.names() == new_variables.names() + for var, new_var in zip(variables, new_variables): assert var == new_var @@ -155,13 +155,13 @@ def test_df_from_to_variables(): """ Tests VariableList.to_dataframe() and VariableList.from_dataframe() """ - vars = VariableList() - vars["a"] = {"val": 5} - vars["b"] = {"val": np.array([1.0, 2.0, 3.0]), "units": "m"} - vars["c"] = {"val": [1.0, 2.0, 3.0], "units": "kg/s", "desc": "some test"} - vars["d"] = {"val": "my value is a string"} + variables = VariableList() + variables["a"] = {"val": 5} + variables["b"] = {"val": np.array([1.0, 2.0, 3.0]), "units": "m"} + variables["c"] = {"val": [1.0, 2.0, 3.0], "units": "kg/s", "desc": "some test"} + variables["d"] = {"val": "my value is a string"} - df = vars.to_dataframe() + df = variables.to_dataframe() assert np.all(df["name"] == ["a", "b", "c", "d"]) assert np.all( df["val"] @@ -170,51 +170,53 @@ def test_df_from_to_variables(): assert np.all(df["units"].to_list() == [None, "m", "kg/s", None]) assert np.all(df["desc"].to_list() == ["", "", "some test", ""]) - new_vars = VariableList.from_dataframe(df) + new_variables = VariableList.from_dataframe(df) - assert vars.names() == new_vars.names() - for var, new_var in zip(vars, new_vars): + assert variables.names() == new_variables.names() + for var, new_var in zip(variables, new_variables): assert var == new_var -def _compare_variable_lists(vars: list[Variable], expected_vars: list[Variable]): +def _compare_variable_lists(variables: list[Variable], expected_variables: list[Variable]): def sort_key(v): return v.name - vars.sort(key=sort_key) - expected_vars.sort(key=sort_key) - assert vars == expected_vars + variables.sort(key=sort_key) + expected_variables.sort(key=sort_key) + assert variables == expected_variables # is_input is willingly ignored when checking equality of variables, but here, we want to # test it. - vars_dict = {var.name: var.is_input for var in vars} - expected_vars_dict = {var.name: var.is_input for var in expected_vars} - assert vars_dict == expected_vars_dict + variables_dict = {var.name: var.is_input for var in variables} + expected_variables_dict = {var.name: var.is_input for var in expected_variables} + assert variables_dict == expected_variables_dict - vars_dict = {var.name: var.description for var in vars} - expected_vars_dict = {var.name: var.description for var in expected_vars} - assert vars_dict == expected_vars_dict + variables_dict = {var.name: var.description for var in variables} + expected_variables_dict = {var.name: var.description for var in expected_variables} + assert variables_dict == expected_variables_dict def test_get_variables_from_problem_with_an_explicit_component(): problem = om.Problem(reports=False) problem.model.add_subsystem("disc1", Disc1(), promotes=["*"]) - vars_before_setup = VariableList.from_problem( + variables_before_setup = VariableList.from_problem( problem, use_initial_values=False, get_promoted_names=True ) problem.setup() - vars = VariableList.from_problem(problem, use_initial_values=False, get_promoted_names=True) - assert vars_before_setup == vars + variables = VariableList.from_problem( + problem, use_initial_values=False, get_promoted_names=True + ) + assert variables_before_setup == variables - expected_vars = [ + expected_variables = [ Variable(name="x", val=np.array([np.nan]), units=None, desc="input x", is_input=True), Variable(name="y2", val=np.array([1.0]), units=None, is_input=True, desc="variable y2"), Variable(name="z", val=np.array([5.0, 2.0]), units="m**2", is_input=True), Variable(name="y1", val=np.array([1.0]), units=None, is_input=False, desc="variable y1"), ] - _compare_variable_lists(vars, expected_vars) + _compare_variable_lists(variables, expected_variables) def test_get_variables_from_problem_with_a_group(): @@ -222,14 +224,16 @@ def test_get_variables_from_problem_with_a_group(): group.add_subsystem("disc1", Disc1(), promotes=["*"]) group.add_subsystem("disc2", Disc2(), promotes=["*"]) problem = om.Problem(group, reports=False) - vars_before_setup = VariableList.from_problem( + variables_before_setup = VariableList.from_problem( problem, use_initial_values=False, get_promoted_names=True ) problem.setup() - vars = VariableList.from_problem(problem, use_initial_values=False, get_promoted_names=True) - assert vars_before_setup == vars + variables = VariableList.from_problem( + problem, use_initial_values=False, get_promoted_names=True + ) + assert variables_before_setup == variables - expected_vars = [ + expected_variables = [ Variable(name="x", val=np.array([np.nan]), units=None, is_input=True, desc="input x"), Variable(name="y1", val=np.array([1.0]), units=None, is_input=False, desc="variable y1"), Variable(name="y2", val=np.array([1.0]), units=None, is_input=False, desc="variable y2"), @@ -238,7 +242,7 @@ def test_get_variables_from_problem_with_a_group(): ), ] - _compare_variable_lists(vars, expected_vars) + _compare_variable_lists(variables, expected_variables) def test_get_variables_from_problem_sellar_with_promotion_without_computation(): @@ -257,7 +261,7 @@ def test_get_variables_from_problem_sellar_with_promotion_without_computation(): problem.setup() problem.final_setup() - expected_vars_promoted = [ + expected_variables_promoted = [ Variable(name="x", val=np.array([1.0]), units="Pa", is_input=True, desc="input x"), Variable( name="z", val=np.array([5.0, 2.0]), units="m**2", is_input=True, desc="variable z" @@ -268,7 +272,7 @@ def test_get_variables_from_problem_sellar_with_promotion_without_computation(): Variable(name="g2", val=np.array([1.0]), units=None, is_input=False), Variable(name="f", val=np.array([1.0]), units=None, is_input=False), ] - expected_vars_non_promoted = [ + expected_variables_non_promoted = [ Variable(name="indeps.x", val=np.array([1.0]), units="Pa", is_input=True), Variable(name="indeps.z", val=np.array([5.0, 2.0]), units="m**2", is_input=True), Variable(name="disc1.x", val=np.array([np.nan]), units=None, is_input=True, desc="input x"), @@ -295,18 +299,24 @@ def test_get_variables_from_problem_sellar_with_promotion_without_computation(): Variable(name="constraint2.g2", val=np.array([1.0]), units=None, is_input=False), ] - vars = VariableList.from_problem(problem, use_initial_values=True, get_promoted_names=True) - _compare_variable_lists(vars, expected_vars_promoted) + variables = VariableList.from_problem(problem, use_initial_values=True, get_promoted_names=True) + _compare_variable_lists(variables, expected_variables_promoted) - vars = VariableList.from_problem(problem, use_initial_values=True, get_promoted_names=False) - _compare_variable_lists(vars, expected_vars_non_promoted) + variables = VariableList.from_problem( + problem, use_initial_values=True, get_promoted_names=False + ) + _compare_variable_lists(variables, expected_variables_non_promoted) # use_initial_values=False should have no effect while problem has not been run - vars = VariableList.from_problem(problem, use_initial_values=False, get_promoted_names=True) - _compare_variable_lists(vars, expected_vars_promoted) + variables = VariableList.from_problem( + problem, use_initial_values=False, get_promoted_names=True + ) + _compare_variable_lists(variables, expected_variables_promoted) - vars = VariableList.from_problem(problem, use_initial_values=False, get_promoted_names=False) - _compare_variable_lists(vars, expected_vars_non_promoted) + variables = VariableList.from_problem( + problem, use_initial_values=False, get_promoted_names=False + ) + _compare_variable_lists(variables, expected_variables_non_promoted) def test_get_variables_from_problem_sellar_with_promotion_with_computation(): @@ -325,7 +335,7 @@ def test_get_variables_from_problem_sellar_with_promotion_with_computation(): problem.setup() problem.final_setup() - expected_vars_promoted_initial = [ + expected_variables_promoted_initial = [ Variable(name="x", val=np.array([1.0]), units="Pa", is_input=True, desc="input x"), Variable( name="z", val=np.array([5.0, 2.0]), units="m**2", is_input=True, desc="variable z" @@ -336,7 +346,7 @@ def test_get_variables_from_problem_sellar_with_promotion_with_computation(): Variable(name="g2", val=np.array([1.0]), units=None, is_input=False), Variable(name="f", val=np.array([1.0]), units=None, is_input=False), ] - expected_vars_promoted_computed = [ + expected_variables_promoted_computed = [ Variable(name="x", val=np.array([1.0]), units="Pa", is_input=True, desc="input x"), Variable( name="z", val=np.array([5.0, 2.0]), units="m**2", is_input=True, desc="variable z" @@ -351,7 +361,7 @@ def test_get_variables_from_problem_sellar_with_promotion_with_computation(): Variable(name="g1", val=np.array([-22.42830237]), units=None, is_input=False), Variable(name="g2", val=np.array([-11.94151185]), units=None, is_input=False), ] - expected_vars_non_promoted_initial = [ + expected_variables_non_promoted_initial = [ Variable(name="indeps.x", val=np.array([1.0]), units="Pa", is_input=True), Variable(name="indeps.z", val=np.array([5.0, 2.0]), units="m**2", is_input=True), Variable(name="disc1.x", val=np.array([np.nan]), units=None, is_input=True, desc="input x"), @@ -377,7 +387,7 @@ def test_get_variables_from_problem_sellar_with_promotion_with_computation(): Variable(name="constraint2.y2", val=np.array([1.0]), units=None, is_input=True), Variable(name="constraint2.g2", val=np.array([1.0]), units=None, is_input=False), ] - expected_vars_non_promoted_computed = [ + expected_variables_non_promoted_computed = [ Variable(name="indeps.x", val=np.array([1.0]), units="Pa", is_input=True), Variable(name="indeps.z", val=np.array([5.0, 2.0]), units="m**2", is_input=True), Variable(name="disc1.x", val=np.array([1.0]), units=None, is_input=True, desc="input x"), @@ -414,17 +424,23 @@ def test_get_variables_from_problem_sellar_with_promotion_with_computation(): problem.run_model() - vars = VariableList.from_problem(problem, use_initial_values=True, get_promoted_names=True) - _compare_variable_lists(vars, expected_vars_promoted_initial) + variables = VariableList.from_problem(problem, use_initial_values=True, get_promoted_names=True) + _compare_variable_lists(variables, expected_variables_promoted_initial) - vars = VariableList.from_problem(problem, use_initial_values=True, get_promoted_names=False) - _compare_variable_lists(vars, expected_vars_non_promoted_initial) + variables = VariableList.from_problem( + problem, use_initial_values=True, get_promoted_names=False + ) + _compare_variable_lists(variables, expected_variables_non_promoted_initial) - vars = VariableList.from_problem(problem, use_initial_values=False, get_promoted_names=True) - _compare_variable_lists(vars, expected_vars_promoted_computed) + variables = VariableList.from_problem( + problem, use_initial_values=False, get_promoted_names=True + ) + _compare_variable_lists(variables, expected_variables_promoted_computed) - vars = VariableList.from_problem(problem, use_initial_values=False, get_promoted_names=False) - _compare_variable_lists(vars, expected_vars_non_promoted_computed) + variables = VariableList.from_problem( + problem, use_initial_values=False, get_promoted_names=False + ) + _compare_variable_lists(variables, expected_variables_non_promoted_computed) def test_get_variables_from_problem_sellar_without_promotion_without_computation(): @@ -447,7 +463,7 @@ def test_get_variables_from_problem_sellar_without_promotion_without_computation problem.setup() problem.final_setup() - expected_vars_initial = [ + expected_variables_initial = [ Variable(name="indeps.x", val=np.array([1.0]), is_input=True), Variable(name="indeps.z", val=np.array([5.0, 2.0]), units="m**2", is_input=True), Variable(name="disc1.x", val=np.array([np.nan]), units=None, is_input=True, desc="input x"), @@ -474,36 +490,36 @@ def test_get_variables_from_problem_sellar_without_promotion_without_computation Variable(name="constraint2.g2", val=np.array([1.0]), units=None, is_input=False), ] - vars = VariableList.from_problem( + variables = VariableList.from_problem( problem, use_initial_values=True, get_promoted_names=True, promoted_only=True ) - _compare_variable_lists(vars, []) + _compare_variable_lists(variables, []) - vars = VariableList.from_problem( + variables = VariableList.from_problem( problem, use_initial_values=True, get_promoted_names=True, promoted_only=False ) - _compare_variable_lists(vars, expected_vars_initial) + _compare_variable_lists(variables, expected_variables_initial) - vars = VariableList.from_problem( + variables = VariableList.from_problem( problem, use_initial_values=True, get_promoted_names=False, promoted_only=False ) - _compare_variable_lists(vars, expected_vars_initial) + _compare_variable_lists(variables, expected_variables_initial) # use_initial_values=False should have no effect while problem has not been run - vars = VariableList.from_problem( + variables = VariableList.from_problem( problem, use_initial_values=False, get_promoted_names=True, promoted_only=True ) - _compare_variable_lists(vars, []) + _compare_variable_lists(variables, []) - vars = VariableList.from_problem( + variables = VariableList.from_problem( problem, use_initial_values=False, get_promoted_names=True, promoted_only=False ) - _compare_variable_lists(vars, expected_vars_initial) + _compare_variable_lists(variables, expected_variables_initial) - vars = VariableList.from_problem( + variables = VariableList.from_problem( problem, use_initial_values=False, get_promoted_names=False, promoted_only=False ) - _compare_variable_lists(vars, expected_vars_initial) + _compare_variable_lists(variables, expected_variables_initial) def test_get_variables_from_problem_sellar_without_promotion_with_computation(): @@ -525,7 +541,7 @@ def test_get_variables_from_problem_sellar_without_promotion_with_computation(): problem = om.Problem(group, reports=False) problem.setup() - expected_vars_initial = [ + expected_variables_initial = [ Variable(name="indeps.x", val=np.array([1.0]), is_input=True), Variable(name="indeps.z", val=np.array([5.0, 2.0]), units="m**2", is_input=True), Variable(name="disc1.x", val=np.array([np.nan]), units=None, is_input=True, desc="input x"), @@ -551,7 +567,7 @@ def test_get_variables_from_problem_sellar_without_promotion_with_computation(): Variable(name="constraint2.y2", val=np.array([1.0]), units=None, is_input=True), Variable(name="constraint2.g2", val=np.array([1.0]), units=None, is_input=False), ] - expected_vars_computed = [ + expected_variables_computed = [ Variable(name="indeps.x", val=np.array([1.0]), is_input=True), Variable(name="indeps.z", val=np.array([5.0, 2.0]), units="m**2", is_input=True), Variable(name="disc1.x", val=np.array([1.0]), units=None, is_input=True, desc="input x"), @@ -587,30 +603,30 @@ def test_get_variables_from_problem_sellar_without_promotion_with_computation(): ] problem.run_model() - vars = VariableList.from_problem( + variables = VariableList.from_problem( problem, use_initial_values=True, get_promoted_names=True, promoted_only=True ) - _compare_variable_lists(vars, []) + _compare_variable_lists(variables, []) - vars = VariableList.from_problem( + variables = VariableList.from_problem( problem, use_initial_values=True, get_promoted_names=True, promoted_only=False ) - _compare_variable_lists(vars, expected_vars_initial) + _compare_variable_lists(variables, expected_variables_initial) - vars = VariableList.from_problem( + variables = VariableList.from_problem( problem, use_initial_values=True, get_promoted_names=False, promoted_only=False ) - _compare_variable_lists(vars, expected_vars_initial) + _compare_variable_lists(variables, expected_variables_initial) - vars = VariableList.from_problem( + variables = VariableList.from_problem( problem, use_initial_values=False, get_promoted_names=True, promoted_only=False ) - _compare_variable_lists(vars, expected_vars_computed) + _compare_variable_lists(variables, expected_variables_computed) - vars = VariableList.from_problem( + variables = VariableList.from_problem( problem, use_initial_values=False, get_promoted_names=False, promoted_only=False ) - _compare_variable_lists(vars, expected_vars_computed) + _compare_variable_lists(variables, expected_variables_computed) pytestmark = pytest.mark.filterwarnings( @@ -621,26 +637,27 @@ def test_get_variables_from_problem_sellar_without_promotion_with_computation(): def _test_and_check_from_unconnected_inputs( problem: om.Problem, - expected_mandatory_vars: list[Variable], - expected_optional_vars: list[Variable], + expected_mandatory_variables: list[Variable], + expected_optional_variables: list[Variable], ): problem.setup() problem.final_setup() - vars = VariableList.from_unconnected_inputs(problem, with_optional_inputs=False) - assert set(vars) == set(expected_mandatory_vars) + variables = VariableList.from_unconnected_inputs(problem, with_optional_inputs=False) + assert set(variables) == set(expected_mandatory_variables) - vars_dict = {var.name: var.description for var in vars} - expected_vars_dict = {var.name: var.description for var in expected_mandatory_vars} - assert vars_dict == expected_vars_dict + variables_dict = {var.name: var.description for var in variables} + expected_variables_dict = {var.name: var.description for var in expected_mandatory_variables} + assert variables_dict == expected_variables_dict - vars = VariableList.from_unconnected_inputs(problem, with_optional_inputs=True) - assert set(vars) == set(expected_mandatory_vars + expected_optional_vars) + variables = VariableList.from_unconnected_inputs(problem, with_optional_inputs=True) + assert set(variables) == set(expected_mandatory_variables + expected_optional_variables) - vars_dict = {var.name: var.description for var in vars} - expected_vars_dict = { - var.name: var.description for var in (expected_mandatory_vars + expected_optional_vars) + variables_dict = {var.name: var.description for var in variables} + expected_variables_dict = { + var.name: var.description + for var in (expected_mandatory_variables + expected_optional_variables) } - assert vars_dict == expected_vars_dict + assert variables_dict == expected_variables_dict def test_variables_from_unconnected_inputs_with_an_explicit_component(): @@ -648,7 +665,7 @@ def test_variables_from_unconnected_inputs_with_an_explicit_component(): group.add_subsystem("disc1", Disc1(), promotes=["*"]) problem = om.Problem(group, reports=False) - expected_mandatory_vars = [ + expected_mandatory_variables = [ Variable( name="x", val=np.array([np.nan]), @@ -658,7 +675,7 @@ def test_variables_from_unconnected_inputs_with_an_explicit_component(): desc="input x", ) ] - expected_optional_vars = [ + expected_optional_variables = [ Variable(name="z", val=np.array([5.0, 2.0]), units="m**2", is_input=True, prom_name="z"), Variable( name="y2", @@ -670,7 +687,7 @@ def test_variables_from_unconnected_inputs_with_an_explicit_component(): ), ] _test_and_check_from_unconnected_inputs( - problem, expected_mandatory_vars, expected_optional_vars + problem, expected_mandatory_variables, expected_optional_variables ) @@ -680,7 +697,7 @@ def test_variables_from_unconnected_inputs_with_a_group(cleanup): group.add_subsystem("disc2", Disc2(), promotes=["*"]) problem = om.Problem(group, reports=False) - expected_mandatory_vars = [ + expected_mandatory_variables = [ Variable( name="x", val=np.array([np.nan]), @@ -690,7 +707,7 @@ def test_variables_from_unconnected_inputs_with_a_group(cleanup): desc="input x", ) ] - expected_optional_vars = [ + expected_optional_variables = [ Variable( name="z", val=np.array([5.0, 2.0]), @@ -701,7 +718,7 @@ def test_variables_from_unconnected_inputs_with_a_group(cleanup): ) ] _test_and_check_from_unconnected_inputs( - problem, expected_mandatory_vars, expected_optional_vars + problem, expected_mandatory_variables, expected_optional_variables ) @@ -715,7 +732,7 @@ def test_variables_from_unconnected_inputs_with_sellar_problem(cleanup): group.add_subsystem("constaint2", FunctionG2(), promotes=["*"]) problem = om.Problem(group, reports=False) - expected_mandatory_vars = [ + expected_mandatory_variables = [ Variable( name="x", val=np.array([np.nan]), @@ -732,7 +749,7 @@ def test_variables_from_unconnected_inputs_with_sellar_problem(cleanup): # is provided in Disc2. We test here that if the kwarg "with_optional_inputs" is true the # description of the variable is updated. - expected_optional_vars = [ + expected_optional_variables = [ Variable( name="z", value=np.array([np.nan, np.nan]), @@ -743,7 +760,7 @@ def test_variables_from_unconnected_inputs_with_sellar_problem(cleanup): ) ] _test_and_check_from_unconnected_inputs( - problem, expected_mandatory_vars, expected_optional_vars + problem, expected_mandatory_variables, expected_optional_variables ) @@ -766,95 +783,97 @@ def test_get_variables_from_problem_sellar_with_promotion_and_connect(): group.connect("disc2.y2", "y2") problem = om.Problem(group, reports=False) - vars_before_setup = VariableList.from_problem( + variables_before_setup = VariableList.from_problem( problem, use_initial_values=False, get_promoted_names=True ) problem.setup() problem.final_setup() - vars = VariableList.from_problem(problem, use_initial_values=False, get_promoted_names=True) - assert vars_before_setup == vars + variables = VariableList.from_problem( + problem, use_initial_values=False, get_promoted_names=True + ) + assert variables_before_setup == variables # x should be an output - assert not vars["x"].is_input + assert not variables["x"].is_input # y1 and y2 should be outputs - assert not vars["y1"].is_input - assert not vars["y2"].is_input + assert not variables["y1"].is_input + assert not variables["y2"].is_input # f, g1 and g2 should be outputs - assert not vars["f"].is_input - assert not vars["g1"].is_input - assert not vars["g2"].is_input + assert not variables["f"].is_input + assert not variables["g1"].is_input + assert not variables["g2"].is_input # indep:x and z as indeps should be inputs - assert vars["indep:x"].is_input - assert vars["z"].is_input + assert variables["indep:x"].is_input + assert variables["z"].is_input # Test for io_status # Check that all variables are returned - vars = VariableList.from_problem( + variables = VariableList.from_problem( problem, use_initial_values=False, get_promoted_names=True, io_status="all" ) - assert "y1" in vars.names() - assert "y2" in vars.names() - assert "f" in vars.names() - assert "g1" in vars.names() - assert "g2" in vars.names() - assert "x" in vars.names() - assert "indep:x" in vars.names() - assert "z" in vars.names() + assert "y1" in variables.names() + assert "y2" in variables.names() + assert "f" in variables.names() + assert "g1" in variables.names() + assert "g2" in variables.names() + assert "x" in variables.names() + assert "indep:x" in variables.names() + assert "z" in variables.names() # Check that only inputs are returned - vars = VariableList.from_problem( + variables = VariableList.from_problem( problem, use_initial_values=False, get_promoted_names=True, io_status="inputs" ) - assert "y1" not in vars.names() - assert "y2" not in vars.names() - assert "f" not in vars.names() - assert "g1" not in vars.names() - assert "g2" not in vars.names() - assert "indep:x" in vars.names() - assert "z" in vars.names() + assert "y1" not in variables.names() + assert "y2" not in variables.names() + assert "f" not in variables.names() + assert "g1" not in variables.names() + assert "g2" not in variables.names() + assert "indep:x" in variables.names() + assert "z" in variables.names() # Check that only outputs are returned - vars = VariableList.from_problem( + variables = VariableList.from_problem( problem, use_initial_values=False, get_promoted_names=True, io_status="outputs" ) - assert "y1" in vars.names() - assert "y2" in vars.names() - assert "f" in vars.names() - assert "g1" in vars.names() - assert "g2" in vars.names() - assert "x" in vars.names() - assert "indep:x" not in vars.names() - assert "z" not in vars.names() + assert "y1" in variables.names() + assert "y2" in variables.names() + assert "f" in variables.names() + assert "g1" in variables.names() + assert "g2" in variables.names() + assert "x" in variables.names() + assert "indep:x" not in variables.names() + assert "z" not in variables.names() def test_get_val(): - vars = VariableList() - vars["bar"] = {"value": 1.0, "units": "m"} - vars["baq"] = {"value": np.array([1.0])} - vars["foo"] = {"value": 1.0, "units": "m**2"} - vars["baz"] = {"value": [1.0, 2.0], "units": "m"} - vars["bat"] = {"value": (1.0, 2.0), "units": "m"} - vars["qux"] = {"value": np.array([1.0, 2.0]), "units": "m"} - vars["quux"] = {"value": [[1.0, 2.0], [2.0, 3.0]], "units": "m"} - data = vars["bar"].get_val() + variables = VariableList() + variables["bar"] = {"value": 1.0, "units": "m"} + variables["baq"] = {"value": np.array([1.0])} + variables["foo"] = {"value": 1.0, "units": "m**2"} + variables["baz"] = {"value": [1.0, 2.0], "units": "m"} + variables["bat"] = {"value": (1.0, 2.0), "units": "m"} + variables["qux"] = {"value": np.array([1.0, 2.0]), "units": "m"} + variables["quux"] = {"value": [[1.0, 2.0], [2.0, 3.0]], "units": "m"} + data = variables["bar"].get_val() assert not isinstance(data, list) and not isinstance(data, np.ndarray) assert_allclose(data, 1, rtol=1e-3, atol=1e-5) units = "km" - data = vars["bar"].get_val(new_units=units) + data = variables["bar"].get_val(new_units=units) assert_allclose(data, 1e-3, rtol=1e-3, atol=1e-5) - data = vars["baq"].get_val() + data = variables["baq"].get_val() assert not isinstance(data, list) and not isinstance(data, np.ndarray) assert_allclose(data, 1, rtol=1e-3, atol=1e-5) with pytest.raises(TypeError): - data = vars["foo"].get_val(new_units=units) - data = vars["baz"].get_val(new_units=units) + data = variables["foo"].get_val(new_units=units) + data = variables["baz"].get_val(new_units=units) assert_allclose(data, [1e-3, 2e-3], rtol=1e-3, atol=1e-5) - data = vars["bat"].get_val(new_units=units) + data = variables["bat"].get_val(new_units=units) assert_allclose(data, [1e-3, 2e-3], rtol=1e-3, atol=1e-5) - data = vars["qux"].get_val(new_units=units) + data = variables["qux"].get_val(new_units=units) assert_allclose(data, [1e-3, 2e-3], rtol=1e-3, atol=1e-5) - data = vars["quux"].get_val(new_units=units) + data = variables["quux"].get_val(new_units=units) assert_allclose(data, [[1e-3, 2e-3], [2e-3, 3e-3]], rtol=1e-3, atol=1e-5) diff --git a/src/fastoad/openmdao/variables/_util.py b/src/fastoad/openmdao/variables/_util.py index 4316c2c1c..6e4be559d 100644 --- a/src/fastoad/openmdao/variables/_util.py +++ b/src/fastoad/openmdao/variables/_util.py @@ -12,6 +12,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import contextlib import itertools import numpy as np @@ -46,11 +47,10 @@ def get_problem_variables( if not problem._metadata or problem._metadata["setup_status"] < _SetupStatus.POST_SETUP: problem = get_mpi_safe_problem_copy(problem) problem.setup() - try: # This block will execute only if openMDAO >= 3.38 + with contextlib.suppress(AttributeError): + # This block will execute only if openMDAO >= 3.38 # TODO clean this code once versions < 3.38 are deprecated problem.set_setup_status(_SetupStatus.POST_SETUP2) - except AttributeError: - pass # Get inputs and outputs metadata_keys = ( diff --git a/src/fastoad/openmdao/variables/variable.py b/src/fastoad/openmdao/variables/variable.py index fa6a36521..3a999925b 100644 --- a/src/fastoad/openmdao/variables/variable.py +++ b/src/fastoad/openmdao/variables/variable.py @@ -348,8 +348,9 @@ def __eq__(self, other): # Let's also ignore unimportant keys for key in METADATA_TO_IGNORE: - my_metadata.pop(key, default=None) - other_metadata.pop(key, default=None) + default_value = None + my_metadata.pop(key, default_value) + other_metadata.pop(key, default_value) return ( isinstance(other, Variable) diff --git a/src/fastoad/openmdao/variables/variable_list.py b/src/fastoad/openmdao/variables/variable_list.py index ee1498812..d294b6aec 100644 --- a/src/fastoad/openmdao/variables/variable_list.py +++ b/src/fastoad/openmdao/variables/variable_list.py @@ -16,6 +16,7 @@ from __future__ import annotations +import contextlib from collections.abc import Iterable, Mapping from copy import deepcopy @@ -75,6 +76,14 @@ class VariableList(list): print( 'var/2' in vars_A.names() ) """ + # We override __eq__, so we must explicitly set __hash__ = None. + # This makes the class unhashable, which is the correct behavior for mutable types. + # It prevents inconsistent behavior when using VariableList in sets or as dict keys. + # See: https://docs.astral.sh/ruff/rules/eq-without-hash/ + # Even though we inherit from list (which is unhashable), + # overriding __eq__ disables the inherited __hash__. + __hash__ = None + def names(self) -> list[str]: """ :return: names of variables @@ -87,7 +96,7 @@ def metadata_keys(self) -> list[str]: """ keys = list(self[0].metadata.keys()) for var in self: - keys = [key for key in var.metadata.keys() if key in keys] + keys = [key for key in var.metadata if key in keys] return keys def append(self, var: Variable) -> None: @@ -213,7 +222,6 @@ def from_ivc(cls, ivc: om.IndepVarComp) -> VariableList: for name, metadata in ivc.get_io_metadata( metadata_keys=["val", "units", "upper", "lower"] ).items(): - metadata = metadata.copy() value = metadata.pop("val") value = cls._as_list_or_item(value) metadata.update({"val": value}) @@ -226,10 +234,8 @@ def _as_list_or_item(cls, value): value = np.asarray(value) if np.size(value) == 1: value = value.item() - try: + with contextlib.suppress(TypeError, ValueError): value = float(value) - except (TypeError, ValueError): - pass return value return value.tolist() @@ -245,10 +251,10 @@ def from_dataframe(cls, df: pd.DataFrame) -> VariableList: :param df: a DataFrame instance :return: a VariableList instance """ - column_names = [name for name in df.columns] + column_names = list(df.columns) def _get_variable(row): - var_as_dict = {key: val for key, val in zip(column_names, row)} + var_as_dict = dict(zip(column_names, row)) # TODO: make this more generic for key, val in var_as_dict.items(): if key in ["val", "initial_value", "lower", "upper"]: @@ -257,7 +263,7 @@ def _get_variable(row): pass return Variable(**var_as_dict) - return cls([_get_variable(row) for row in df[column_names].values]) + return cls([_get_variable(row) for row in df[column_names].to_numpy()]) @classmethod def from_problem( @@ -313,14 +319,12 @@ def from_problem( # behaviour when actually running the problem. if not use_initial_values and problem.model.iter_count > 0: for variable in variables: - try: - # Maybe useless, but we force units to ensure it is consistent + # Maybe useless, but we force units to ensure it is consistent + with contextlib.suppress(RuntimeError): variable.value = problem.get_val(variable.name, units=variable.units) - except RuntimeError: # In case problem is incompletely set, problem.get_val() will fail. # In such case, falling back to the method for initial values # should be enough. - pass return variables diff --git a/tests/integration_tests/oad_process/test_oad_process.py b/tests/integration_tests/oad_process/test_oad_process.py index 30643ad2e..aac352cb5 100644 --- a/tests/integration_tests/oad_process/test_oad_process.py +++ b/tests/integration_tests/oad_process/test_oad_process.py @@ -84,7 +84,7 @@ def test_non_regression_mission_only(cleanup): "CeRAS01_legacy_mission_only_result.xml", "non_regression_mission_only", use_xfoil=False, - vars_to_check=["data:mission:sizing:needed_block_fuel"], + variables_to_check=["data:mission:sizing:needed_block_fuel"], specific_tolerance=1.0e-2, global_tolerance=10.0e-2, check_weight_perfo_loop=False, @@ -114,7 +114,7 @@ def run_non_regression_test( result_dir, xfoil_path=None, global_tolerance=1e-2, - vars_to_check=None, + variables_to_check=None, specific_tolerance=5.0e-3, *, use_xfoil=False, @@ -126,9 +126,10 @@ def run_non_regression_test( :param legacy_result_file: reference data for inputs and outputs :param result_dir: relative name, folder will be in RESULTS_FOLDER_PATH :param xfoil_path: used if use_xfoil==True - :param vars_to_check: variables that will be concerned by specific_tolerance + :param variables_to_check: variables that will be concerned by specific_tolerance :param specific_tolerance: test will fail if absolute relative error between computed and - reference values is beyond this value for variables in vars_to_check + reference values is beyond this value for variables + in variables_to_check :param global_tolerance: test will fail if absolute relative error between computed and reference values is beyond this value for ANY variable :param use_xfoil: if True, XFOIL computation will be activated @@ -192,8 +193,8 @@ def run_non_regression_test( print(df.sort_values(by=["abs_rel_delta"])) assert_allclose(df.value, df.ref_value, rtol=global_tolerance, atol=1.0e-9) - if vars_to_check is not None: - for name in vars_to_check: + if variables_to_check is not None: + for name in variables_to_check: row = df.loc[df.name == name] assert_allclose(row.value, row.ref_value, rtol=specific_tolerance, atol=1.0e-9) From 61a424b54d76ba0fadf2805ddcf9120d4f195a4a Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:37:22 +0100 Subject: [PATCH 10/20] removed old commented code --- src/fastoad/model_base/tests/test_atmosphere.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/fastoad/model_base/tests/test_atmosphere.py b/src/fastoad/model_base/tests/test_atmosphere.py index e695b376c..5a8203b3e 100644 --- a/src/fastoad/model_base/tests/test_atmosphere.py +++ b/src/fastoad/model_base/tests/test_atmosphere.py @@ -157,13 +157,6 @@ def test_speed_conversions(): [270, 266.0652, 150.4692], [400, 394.1707, 222.9173], ] - # ruff:noqa ERA001 - # expected_CAS = [ # currently unused TODO erase it - # [100.0, 98.5799, 56.3313], - # [200.0, 197.3624, 116.1973], - # [270, 161.8971, 266.6984], - # [400, 395.5782, 252.6355], - # ] expected_Mach = [ [0.2939, 0.2949, 0.3371], [0.5877, 0.5898, 0.6743], From 0fd852f6a337f4c19a7bb69a93bd98ec4fa391a4 Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:43:29 +0100 Subject: [PATCH 11/20] updated the noqa comments --- .../mission_builder/input_definition.py | 9 +++++---- .../mission/openmdao/tests/test_sizing_mission.py | 9 +++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py b/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py index 8426cdd95..61b95aada 100644 --- a/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py +++ b/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py @@ -208,10 +208,11 @@ def set_variable_value(self, inputs: Mapping | Vector): :param inputs: """ if self.variable_name: - value = scalarize( - # Note: OpenMDAO `Vector` object has no `get()` method, so we need to do this: - inputs[self.variable_name] if self.variable_name in inputs else self.default_value # noqa: SIM401 - ) + # Note: OpenMDAO `Vector` object has no `get()` method, so we need to do this: + if self.variable_name in inputs: + value = scalarize(inputs[self.variable_name]) + else: + value = scalarize(self.default_value) if self._use_opposite: self.input_value = -value diff --git a/src/fastoad/models/performances/mission/openmdao/tests/test_sizing_mission.py b/src/fastoad/models/performances/mission/openmdao/tests/test_sizing_mission.py index fab703cbf..1e5d3c323 100644 --- a/src/fastoad/models/performances/mission/openmdao/tests/test_sizing_mission.py +++ b/src/fastoad/models/performances/mission/openmdao/tests/test_sizing_mission.py @@ -24,8 +24,6 @@ RESULTS_FOLDER_PATH = Path(__file__).parent / "results" / Path(__file__).stem -# ruff:noqa ERA001 - @pytest.fixture(scope="module") def cleanup(): @@ -74,12 +72,11 @@ def test_sizing_mission(cleanup, with_dummy_plugin_2): ivc, ) # Useful for debugging - # import pandas as pd + # import pandas as pd # noqa: ERA001 # # plot_flight( - # pd.read_csv(RESULTS_FOLDER_PATH / "sizing_mission.csv"), - # RESULTS_FOLDER_PATH / "sizing_mission.png", - # ) + # pd.read_csv(RESULTS_FOLDER_PATH / "sizing_mission.csv"), # noqa: ERA001 + # RESULTS_FOLDER_PATH / "sizing_mission.png") assert_allclose(problem["data:mission:sizing:taxi_out:fuel"], 351.0, atol=1) assert_allclose(problem["data:mission:sizing:taxi_out:duration"], 500.0, atol=1) From 3a0d494b2fa159b0698c2d440c124182f6bd8947 Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:23:26 +0100 Subject: [PATCH 12/20] rollback variable test eq --- src/fastoad/openmdao/variables/variable.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/fastoad/openmdao/variables/variable.py b/src/fastoad/openmdao/variables/variable.py index 3a999925b..fa6a36521 100644 --- a/src/fastoad/openmdao/variables/variable.py +++ b/src/fastoad/openmdao/variables/variable.py @@ -348,9 +348,8 @@ def __eq__(self, other): # Let's also ignore unimportant keys for key in METADATA_TO_IGNORE: - default_value = None - my_metadata.pop(key, default_value) - other_metadata.pop(key, default_value) + my_metadata.pop(key, default=None) + other_metadata.pop(key, default=None) return ( isinstance(other, Variable) From 058680571f0a4c7338b7a2d89d2b21fee8ef0517 Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:44:27 +0100 Subject: [PATCH 13/20] rollback wrong commit on ipynb metadata --- .../notebooks/02_From_Python_to_FAST-OAD/05_FAST-OAD.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/05_FAST-OAD.ipynb b/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/05_FAST-OAD.ipynb index 881224ba8..78d0838ed 100644 --- a/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/05_FAST-OAD.ipynb +++ b/src/fastoad/notebooks/02_From_Python_to_FAST-OAD/05_FAST-OAD.ipynb @@ -419,7 +419,7 @@ ], "metadata": { "kernelspec": { - "display_name": ".venv (3.12.9)", + "display_name": "fast-oad-core-py3.10 (3.10.11)", "language": "python", "name": "python3" }, @@ -433,7 +433,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.9" + "version": "3.10.11" } }, "nbformat": 4, From 74f3e44703ba0cd90d54a25246ae16b8ec70a593 Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Wed, 12 Nov 2025 18:03:08 +0100 Subject: [PATCH 14/20] reimplemented default_val --- src/fastoad/openmdao/variables/variable.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/fastoad/openmdao/variables/variable.py b/src/fastoad/openmdao/variables/variable.py index fa6a36521..3a999925b 100644 --- a/src/fastoad/openmdao/variables/variable.py +++ b/src/fastoad/openmdao/variables/variable.py @@ -348,8 +348,9 @@ def __eq__(self, other): # Let's also ignore unimportant keys for key in METADATA_TO_IGNORE: - my_metadata.pop(key, default=None) - other_metadata.pop(key, default=None) + default_value = None + my_metadata.pop(key, default_value) + other_metadata.pop(key, default_value) return ( isinstance(other, Variable) From 63d94161536d143515a9a949262bdb2c372d39d6 Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:33:26 +0100 Subject: [PATCH 15/20] Added BLE set rules --- ruff.toml | 1 + .../_utils/resource_management/contents.py | 2 +- src/fastoad/io/configuration/configuration.py | 15 +++++++++++++-- src/fastoad/openmdao/variables/variable.py | 9 ++++++++- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/ruff.toml b/ruff.toml index 0c5d83891..666bfaafa 100644 --- a/ruff.toml +++ b/ruff.toml @@ -26,6 +26,7 @@ select = [ "S", # Bandit: automated security testing built right into your workflow. "N", # Enforce PEP8 naming convention. "E", # pycodestyle is a tool to check your Python code against some of the style conventions in PEP 8. + "BLE", # Flake8 blind except. # "ANN", # Enforce function annotations. # "D", # Pydocstyle is a static analysis tool for checking compliance with Python docstring conventions. # "DOC", # Pydoclint (still in preview so not yet active). diff --git a/src/fastoad/_utils/resource_management/contents.py b/src/fastoad/_utils/resource_management/contents.py index 06810476a..3b5d11b9c 100644 --- a/src/fastoad/_utils/resource_management/contents.py +++ b/src/fastoad/_utils/resource_management/contents.py @@ -65,7 +65,7 @@ def package_name(self, package_name: str): else: # Here we assume non-existence self.exists = False - except Exception: + except Exception: # noqa: BLE001 We catch any error raised while loading or inspecting user code. # Here we catch any Python error that may happen when reading the loaded code. # Thus, we ensure to not break the application if a module is incorrectly written. self.has_error = True diff --git a/src/fastoad/io/configuration/configuration.py b/src/fastoad/io/configuration/configuration.py index 908e73211..ad2913a65 100644 --- a/src/fastoad/io/configuration/configuration.py +++ b/src/fastoad/io/configuration/configuration.py @@ -499,7 +499,14 @@ def _parse_problem_table(self, group: om.Group, table: dict): # noqa: PLR0912 if key.endswith(("solver", "driver")): try: value = self._om_eval(str(value)) # noqa: PLW2901 - except Exception as err: + except (NameError, AttributeError, SyntaxError, ValueError) as err: + # Expected failures during evaluation: + # - NameError / AttributeError: references missing or not yet defined + # - SyntaxError: malformed expression + # - ValueError: invalid literal or conversion inside eval + # + # Other exceptions indicate an internal or environment problem and + # should not be swallowed here. raise FASTConfigurationBadOpenMDAOInstructionError(err, key, value) # value is an option or an attribute @@ -508,7 +515,11 @@ def _parse_problem_table(self, group: om.Group, table: dict): # noqa: PLR0912 group.options[key] = value else: setattr(group, key, value) - except Exception as err: + except (KeyError, AttributeError, TypeError, ValueError) as err: + # Typical parsing failures: + # - KeyError: unknown option name + # - AttributeError: attribute does not exist or is read-only + # - TypeError / ValueError: invalid type or incompatible value raise FASTConfigurationBadOpenMDAOInstructionError(err, key, value) def _make_option_path_values_absolute(self, options): diff --git a/src/fastoad/openmdao/variables/variable.py b/src/fastoad/openmdao/variables/variable.py index 3a999925b..e2b56cdbc 100644 --- a/src/fastoad/openmdao/variables/variable.py +++ b/src/fastoad/openmdao/variables/variable.py @@ -201,7 +201,14 @@ def read_variable_descriptions( variable_descriptions = np.genfromtxt( description_file, delimiter="||", dtype=str, autostrip=True ) - except Exception as exc: + except (OSError, ValueError, UnicodeDecodeError) as exc: + # We explicitly catch: + # - OSError: file not found, permission denied, or other I/O issues + # - ValueError: malformed file contents (e.g. inconsistent columns, bad delimiter) + # - UnicodeDecodeError: unexpected encoding while reading the file + # + # Other exceptions (e.g. TypeError, AttributeError) would indicate a programming + # error and should not be silently caught here. # Reading the file is not mandatory, so let's just log the error. _LOGGER.error( "Could not read file %s in %s. Error log is:\n%s", From 6df26c886a2ac8319e05636b61a05b53b0d7c52b Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Thu, 13 Nov 2025 12:03:56 +0100 Subject: [PATCH 16/20] review fixing --- src/fastoad/gui/analysis_and_plots.py | 6 +++--- .../io/xml/tests/test_variable_io_standard.py | 4 ++-- src/fastoad/models/performances/mission/routes.py | 2 +- .../models/performances/mission/segments/base.py | 4 ++-- .../segments/registered/altitude_change.py | 15 +++++++++------ .../mission/segments/registered/cruise.py | 4 ++-- .../mission/segments/time_step_base.py | 8 ++++---- 7 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/fastoad/gui/analysis_and_plots.py b/src/fastoad/gui/analysis_and_plots.py index 0aebfcd7c..892833550 100644 --- a/src/fastoad/gui/analysis_and_plots.py +++ b/src/fastoad/gui/analysis_and_plots.py @@ -243,9 +243,9 @@ def drag_polar_plot( cl = np.asarray(variables["data:aerodynamics:aircraft:cruise:CL"].value) # TODO: remove filtering one models provide proper bounds - CD_UPPER = 2.0 - cd_short = cd[cd <= CD_UPPER] - cl_short = cl[cd <= CD_UPPER] + cutoff_cd = 2.0 + cd_short = cd[cd <= cutoff_cd] + cl_short = cl[cd <= cutoff_cd] if fig is None: fig = go.Figure() diff --git a/src/fastoad/io/xml/tests/test_variable_io_standard.py b/src/fastoad/io/xml/tests/test_variable_io_standard.py index cec7dc01f..6977f7a45 100644 --- a/src/fastoad/io/xml/tests/test_variable_io_standard.py +++ b/src/fastoad/io/xml/tests/test_variable_io_standard.py @@ -212,8 +212,8 @@ def test_basic_xml_partial_read_and_write_from_variables(cleanup): "constants:k5", "constants:k8", ] - new_variables2 = xml_read.read(only=ok_variables) - _check_basic_variables(new_variables2) + new_variables_2 = xml_read.read(only=ok_variables) + _check_basic_variables(new_variables_2) # Check partial writing with 'ignore' varok_filename = result_folder / "with_bad_var.xml" diff --git a/src/fastoad/models/performances/mission/routes.py b/src/fastoad/models/performances/mission/routes.py index fc432cf02..1d2a352ef 100644 --- a/src/fastoad/models/performances/mission/routes.py +++ b/src/fastoad/models/performances/mission/routes.py @@ -92,7 +92,7 @@ def cruise_speed(self) -> tuple[str, float] | None: for segment in climb_segments: for speed_param in ["true_airspeed", "equivalent_airspeed", "mach"]: speed_value = getattr(segment.target, speed_param) - if speed_value and speed_value != AbstractFlightSegment.CONSTANT_VALUE: + if speed_value and speed_value != AbstractFlightSegment.constant_value_name: return speed_param, speed_value return None diff --git a/src/fastoad/models/performances/mission/segments/base.py b/src/fastoad/models/performances/mission/segments/base.py index 158a760f5..4d84198df 100644 --- a/src/fastoad/models/performances/mission/segments/base.py +++ b/src/fastoad/models/performances/mission/segments/base.py @@ -128,7 +128,7 @@ class AbstractFlightSegment(IFlightPart, ABC): #: A FlightPoint instance that provides parameter values that should all be reached at the #: end of :meth:`~fastoad.models.performances.mission.segments.base.FlightSegment.compute_from`. #: Possible parameters depend on the current segment. A parameter can also be set to - #: :attr:`~fastoad.models.performances.mission.segments.base.FlightSegment.CONSTANT_VALUE` + #: :attr:`~fastoad.models.performances.mission.segments.base.FlightSegment.constant_value_name` #: to tell that initial value should be kept during all segment. target: FlightPoint = MANDATORY_FIELD @@ -139,7 +139,7 @@ class AbstractFlightSegment(IFlightPart, ABC): isa_offset: float = 0.0 #: Using this value will tell to keep the associated parameter constant. - CONSTANT_VALUE = "constant" + constant_value_name = "constant" # To be noted: this one is not a dataclass field, but an actual class attribute _attribute_units: ClassVar[dict] = {"reference_area": "m**2", "time_step": "s"} diff --git a/src/fastoad/models/performances/mission/segments/registered/altitude_change.py b/src/fastoad/models/performances/mission/segments/registered/altitude_change.py index 2aa4c5a89..dcf2a15e0 100644 --- a/src/fastoad/models/performances/mission/segments/registered/altitude_change.py +++ b/src/fastoad/models/performances/mission/segments/registered/altitude_change.py @@ -112,10 +112,10 @@ def compute_from_start_to_target(self, start: FlightPoint, target: FlightPoint) self.interrupt_if_getting_further_from_target = True atm = self._get_atmosphere_point(start.altitude) - if target.equivalent_airspeed == self.CONSTANT_VALUE: + if target.equivalent_airspeed == self.constant_value_name: atm.equivalent_airspeed = start.equivalent_airspeed start.true_airspeed = atm.true_airspeed - elif target.mach == self.CONSTANT_VALUE: + elif target.mach == self.constant_value_name: atm.mach = start.mach start.true_airspeed = atm.true_airspeed @@ -142,11 +142,14 @@ def get_distance_to_target( if target.altitude is not None: distance_to_target = target.altitude - current.altitude - elif target.true_airspeed and target.true_airspeed != self.CONSTANT_VALUE: + elif target.true_airspeed and target.true_airspeed != self.constant_value_name: distance_to_target = target.true_airspeed - current.true_airspeed - elif target.equivalent_airspeed and target.equivalent_airspeed != self.CONSTANT_VALUE: + elif ( + target.equivalent_airspeed + and target.equivalent_airspeed != self.constant_value_name + ): distance_to_target = target.equivalent_airspeed - current.equivalent_airspeed - elif target.mach is not None and target.mach != self.CONSTANT_VALUE: + elif target.mach is not None and target.mach != self.constant_value_name: distance_to_target = target.mach - current.mach if distance_to_target is None: @@ -163,7 +166,7 @@ def _manage_optimal_altitude(self, current, start, target): # Optimal altitude is based on a target Mach number, though target speed # may be specified as TAS or EAS. If so, Mach number has to be computed # for target altitude and speed. - # First, as target speed is expected to be set to self.CONSTANT_VALUE for one + # First, as target speed is expected to be set to self.constant_value_name for one # parameter. Let's get the real value from start point. target_speed = copy(target) for speed_param in ["true_airspeed", "equivalent_airspeed", "mach"]: diff --git a/src/fastoad/models/performances/mission/segments/registered/cruise.py b/src/fastoad/models/performances/mission/segments/registered/cruise.py index 1f597d8ab..81815f809 100644 --- a/src/fastoad/models/performances/mission/segments/registered/cruise.py +++ b/src/fastoad/models/performances/mission/segments/registered/cruise.py @@ -50,11 +50,11 @@ def __post_init__(self): # Constant speed at constant altitude is necessarily constant Mach, but # subclasses can be at variable altitude, so Mach is considered constant # if no other constant speed parameter is set to "constant". - if AbstractTimeStepFlightSegment.CONSTANT_VALUE not in [ + if AbstractTimeStepFlightSegment.constant_value_name not in [ self.target.true_airspeed, self.target.equivalent_airspeed, ]: - self.target.mach = AbstractTimeStepFlightSegment.CONSTANT_VALUE + self.target.mach = AbstractTimeStepFlightSegment.constant_value_name def get_distance_to_target( self, flight_points: list[FlightPoint], target: FlightPoint diff --git a/src/fastoad/models/performances/mission/segments/time_step_base.py b/src/fastoad/models/performances/mission/segments/time_step_base.py index 6b6ab7a1d..c4f72d6d8 100644 --- a/src/fastoad/models/performances/mission/segments/time_step_base.py +++ b/src/fastoad/models/performances/mission/segments/time_step_base.py @@ -257,11 +257,11 @@ def compute_next_flight_point( next_point.alpha = self.get_next_alpha(previous, time_step) self._compute_next_altitude(next_point, previous) - if self.target.true_airspeed == self.CONSTANT_VALUE: + if self.target.true_airspeed == self.constant_value_name: next_point.true_airspeed = previous.true_airspeed - elif self.target.equivalent_airspeed == self.CONSTANT_VALUE: + elif self.target.equivalent_airspeed == self.constant_value_name: next_point.equivalent_airspeed = start.equivalent_airspeed - elif self.target.mach == self.CONSTANT_VALUE: + elif self.target.mach == self.constant_value_name: next_point.mach = start.mach else: next_point.true_airspeed = previous.true_airspeed + time_step * previous.acceleration @@ -378,7 +378,7 @@ class AbstractRegulatedThrustSegment(AbstractTimeStepFlightSegment, ABC): def __post_init__(self): super().__post_init__() - self.target.mach = self.CONSTANT_VALUE + self.target.mach = self.constant_value_name def compute_propulsion(self, flight_point: FlightPoint): flight_point.thrust = flight_point.drag From 94e25507c46d04c50259eaeb4ed4c6a007abc8c2 Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Thu, 13 Nov 2025 12:09:28 +0100 Subject: [PATCH 17/20] added KeyError in FASTConfigurationBadOpenMDAOInstructionError in om eval --- src/fastoad/io/configuration/configuration.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fastoad/io/configuration/configuration.py b/src/fastoad/io/configuration/configuration.py index ad2913a65..7a012f078 100644 --- a/src/fastoad/io/configuration/configuration.py +++ b/src/fastoad/io/configuration/configuration.py @@ -499,11 +499,12 @@ def _parse_problem_table(self, group: om.Group, table: dict): # noqa: PLR0912 if key.endswith(("solver", "driver")): try: value = self._om_eval(str(value)) # noqa: PLW2901 - except (NameError, AttributeError, SyntaxError, ValueError) as err: + except (NameError, AttributeError, SyntaxError, ValueError, KeyError) as err: # Expected failures during evaluation: # - NameError / AttributeError: references missing or not yet defined # - SyntaxError: malformed expression # - ValueError: invalid literal or conversion inside eval + # - KeyError: user type error # # Other exceptions indicate an internal or environment problem and # should not be swallowed here. From c02f8342e5035df08504082eb7c230664fd055dd Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Thu, 13 Nov 2025 12:27:56 +0100 Subject: [PATCH 18/20] Ruff dependency update --- poetry.lock | 319 ++++++++++++++++++++++++----- pyproject.toml | 2 +- ruff.toml | 3 - src/fastoad/gui/variable_viewer.py | 2 - 4 files changed, 267 insertions(+), 59 deletions(-) diff --git a/poetry.lock b/poetry.lock index 210af4c8b..3207adff9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "aenum" @@ -6,6 +6,7 @@ version = "3.1.0" description = "Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "aenum-3.1.0-py2-none-any.whl", hash = "sha256:1f92fb906e3d745064e85f9a1937006ee341e00a35ecd8b7f899041b8e1d67d7"}, {file = "aenum-3.1.0-py3-none-any.whl", hash = "sha256:f8401f1a258436719ed013444ab37ff22a72517e0e3097058dd1511cf284447c"}, @@ -18,6 +19,8 @@ version = "22.1.0" description = "File support for asyncio." optional = false python-versions = ">=3.7,<4.0" +groups = ["main"] +markers = "python_version <= \"3.11\"" files = [ {file = "aiofiles-22.1.0-py3-none-any.whl", hash = "sha256:1142fa8e80dbae46bb6339573ad4c8c0841358f79c6eb50a493dceca14621bad"}, {file = "aiofiles-22.1.0.tar.gz", hash = "sha256:9107f1ca0b2a5553987a94a3c9959fe5b491fdf731389aa5b7b1bd0733e32de6"}, @@ -29,6 +32,8 @@ version = "0.20.0" description = "asyncio bridge to the standard sqlite3 module" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\"" files = [ {file = "aiosqlite-0.20.0-py3-none-any.whl", hash = "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6"}, {file = "aiosqlite-0.20.0.tar.gz", hash = "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7"}, @@ -47,6 +52,7 @@ version = "0.7.12" description = "A configurable sidebar-enabled Sphinx theme" optional = false python-versions = "*" +groups = ["doc"] files = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, @@ -58,6 +64,7 @@ version = "3.3.3" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.6.2" +groups = ["main"] files = [ {file = "anyio-3.3.3-py3-none-any.whl", hash = "sha256:56ceaeed2877723578b1341f4f68c29081db189cfb40a97d1922b9513f6d7db6"}, {file = "anyio-3.3.3.tar.gz", hash = "sha256:8eccec339cb4a856c94a75d50fc1d451faf32a05ef406be462e2efc59c9838b0"}, @@ -69,7 +76,7 @@ sniffio = ">=1.1" [package.extras] doc = ["sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] +test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4) ; python_version < \"3.8\"", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15) ; python_version < \"3.7\" and platform_python_implementation == \"CPython\" and platform_system != \"Windows\"", "uvloop (>=0.15) ; python_version >= \"3.7\" and platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] trio = ["trio (>=0.16)"] [[package]] @@ -78,6 +85,8 @@ version = "0.1.4" description = "Disable App Nap on macOS >= 10.9" optional = false python-versions = ">=3.6" +groups = ["main", "test"] +markers = "platform_system == \"Darwin\" or sys_platform == \"darwin\"" files = [ {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, @@ -89,6 +98,7 @@ version = "21.1.0" description = "The secure Argon2 password hashing algorithm." optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "argon2-cffi-21.1.0.tar.gz", hash = "sha256:f710b61103d1a1f692ca3ecbd1373e28aa5e545ac625ba067ff2feca1b2bb870"}, {file = "argon2_cffi-21.1.0-cp35-abi3-macosx_10_14_x86_64.whl", hash = "sha256:217b4f0f853ccbbb5045242946ad2e162e396064575860141b71a85eb47e475a"}, @@ -117,6 +127,7 @@ version = "1.3.0" description = "Better dates & times for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, @@ -136,6 +147,8 @@ version = "2.0.4" description = "Simple LRU cache for asyncio" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.12\"" files = [ {file = "async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627"}, {file = "async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224"}, @@ -147,18 +160,19 @@ version = "24.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" +groups = ["main", "lint", "test"] files = [ {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\""] [[package]] name = "babel" @@ -166,6 +180,7 @@ version = "2.16.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" +groups = ["main", "doc"] files = [ {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, @@ -180,6 +195,7 @@ version = "0.2.0" description = "Specifications for callback functions passed in to an API" optional = false python-versions = "*" +groups = ["main", "test"] files = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, @@ -191,6 +207,7 @@ version = "4.11.1" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" +groups = ["main"] files = [ {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, @@ -209,6 +226,7 @@ version = "4.1.0" description = "An easy safelist-based HTML-sanitizing tool." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "bleach-4.1.0-py2.py3-none-any.whl", hash = "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"}, {file = "bleach-4.1.0.tar.gz", hash = "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da"}, @@ -225,6 +243,7 @@ version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = "*" +groups = ["main", "doc"] files = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, @@ -236,6 +255,7 @@ version = "1.15.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = "*" +groups = ["main", "test"] files = [ {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, @@ -288,6 +308,7 @@ files = [ {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] +markers = {test = "implementation_name == \"pypy\""} [package.dependencies] pycparser = "*" @@ -298,6 +319,7 @@ version = "3.3.1" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.6.1" +groups = ["lint"] files = [ {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, @@ -309,6 +331,7 @@ version = "2.0.7" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.5.0" +groups = ["main", "doc"] files = [ {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, @@ -323,6 +346,7 @@ version = "8.0.3" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, @@ -337,10 +361,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "doc", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "sys_platform == \"win32\" or platform_system == \"Windows\"", doc = "sys_platform == \"win32\"", test = "sys_platform == \"win32\""} [[package]] name = "comm" @@ -348,6 +374,7 @@ version = "0.2.2" description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, @@ -365,6 +392,7 @@ version = "1.2.1" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "contourpy-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040"}, {file = "contourpy-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd"}, @@ -428,6 +456,7 @@ version = "7.0.5" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.7" +groups = ["test"] files = [ {file = "coverage-7.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a7f23bbaeb2a87f90f607730b45564076d870f1fb07b9318d0c21f36871932b"}, {file = "coverage-7.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c18d47f314b950dbf24a41787ced1474e01ca816011925976d90a88b27c22b89"}, @@ -486,7 +515,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cycler" @@ -494,6 +523,7 @@ version = "0.10.0" description = "Composable style cycles" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"}, {file = "cycler-0.10.0.tar.gz", hash = "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"}, @@ -508,6 +538,7 @@ version = "1.8.7" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "debugpy-1.8.7-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95fe04a573b8b22896c404365e03f4eda0ce0ba135b7667a1e57bd079793b96b"}, {file = "debugpy-1.8.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:628a11f4b295ffb4141d8242a9bb52b77ad4a63a2ad19217a93be0f77f2c28c9"}, @@ -543,6 +574,7 @@ version = "5.1.0" description = "Decorators for Humans" optional = false python-versions = ">=3.5" +groups = ["main", "test"] files = [ {file = "decorator-5.1.0-py3-none-any.whl", hash = "sha256:7b12e7c3c6ab203a29e157335e9122cb03de9ab7264b137594103fd4a683b374"}, {file = "decorator-5.1.0.tar.gz", hash = "sha256:e59913af105b9860aa2c8d3272d9de5a56a4e608db9a2f167a8480b323d529a7"}, @@ -554,6 +586,7 @@ version = "0.7.1" description = "XML bomb protection for Python stdlib modules" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] files = [ {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, @@ -565,6 +598,7 @@ version = "1.2.13" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] files = [ {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, @@ -574,7 +608,7 @@ files = [ wrapt = ">=1.10,<2" [package.extras] -dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"] +dev = ["PyTest (<5) ; python_version < \"3.6\"", "PyTest ; python_version >= \"3.6\"", "PyTest-Cov (<2.6) ; python_version < \"3.6\"", "PyTest-Cov ; python_version >= \"3.6\"", "bump2version (<1)", "configparser (<5) ; python_version < \"3\"", "importlib-metadata (<3) ; python_version < \"3\"", "importlib-resources (<4) ; python_version < \"3\"", "sphinx (<2)", "sphinxcontrib-websupport (<2) ; python_version < \"3\"", "tox", "zipp (<2) ; python_version < \"3\""] [[package]] name = "distlib" @@ -582,6 +616,7 @@ version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["lint"] files = [ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, @@ -593,6 +628,7 @@ version = "0.20.1" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.7" +groups = ["doc"] files = [ {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, @@ -604,6 +640,7 @@ version = "1.0.4" description = "Literate BDD assertions in Python with no magic" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "ensure-1.0.4-py3-none-any.whl", hash = "sha256:ad88628482eac9bf91dd147ae677f26aeac6c65e1c91a161a0306dbd83f6efd9"}, {file = "ensure-1.0.4.tar.gz", hash = "sha256:41b9093d591b5c25ee3c65c36da153b89abfcb7acb21ecebf21944704934f9b0"}, @@ -621,6 +658,8 @@ version = "1.1.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["test"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, @@ -635,6 +674,7 @@ version = "0.7.2" description = "FAST-OAD_CS25 is a FAST-OAD plugin with CS25/FAR25-related models." optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "fast_oad_cs25-0.7.2-py3-none-any.whl", hash = "sha256:c8adae643e735a603ae6afa248524b1910024f467c9613363202a9b58612ed56"}, {file = "fast_oad_cs25-0.7.2.tar.gz", hash = "sha256:0cd52a73de6f12dbf863327af5271ca9e329820afe74d4fcbcc23fbd7a336b6e"}, @@ -650,6 +690,7 @@ version = "2.16.2" description = "Fastest Python implementation of JSON schema" optional = false python-versions = "*" +groups = ["main", "lint", "test"] files = [ {file = "fastjsonschema-2.16.2-py3-none-any.whl", hash = "sha256:21f918e8d9a1a4ba9c22e09574ba72267a6762d47822db9add95f6454e51cc1c"}, {file = "fastjsonschema-2.16.2.tar.gz", hash = "sha256:01e366f25d9047816fe3d288cbfc3e10541daf0af2044763f3d0ade42476da18"}, @@ -664,6 +705,7 @@ version = "3.15.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" +groups = ["lint"] files = [ {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, @@ -672,7 +714,7 @@ files = [ [package.extras] docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8)"] +typing = ["typing-extensions (>=4.8) ; python_version < \"3.11\""] [[package]] name = "fonttools" @@ -680,6 +722,7 @@ version = "4.40.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "fonttools-4.40.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b802dcbf9bcff74672f292b2466f6589ab8736ce4dcf36f48eb994c2847c4b30"}, {file = "fonttools-4.40.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f6e3fa3da923063c286320e728ba2270e49c73386e3a711aa680f4b0747d692"}, @@ -718,18 +761,18 @@ files = [ ] [package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0) ; python_version <= \"3.11\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "scipy"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "scipy ; platform_python_implementation != \"PyPy\""] lxml = ["lxml (>=4.0,<5)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] repacker = ["uharfbuzz (>=0.23.0)"] symfont = ["sympy"] -type1 = ["xattr"] +type1 = ["xattr ; sys_platform == \"darwin\""] ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.0.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] +unicode = ["unicodedata2 (>=15.0.0) ; python_version <= \"3.11\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] [[package]] name = "fqdn" @@ -737,6 +780,7 @@ version = "1.5.1" description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" optional = false python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" +groups = ["main"] files = [ {file = "fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"}, {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, @@ -748,6 +792,8 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version == \"3.12\"" files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -759,6 +805,8 @@ version = "1.0.6" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.12\"" files = [ {file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"}, {file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"}, @@ -780,6 +828,8 @@ version = "0.27.2" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.12\"" files = [ {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, @@ -793,7 +843,7 @@ idna = "*" sniffio = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -805,6 +855,7 @@ version = "2.3.0" description = "File identification library for Python" optional = false python-versions = ">=3.6.1" +groups = ["lint"] files = [ {file = "identify-2.3.0-py2.py3-none-any.whl", hash = "sha256:d1e82c83d063571bb88087676f81261a4eae913c492dafde184067c584bc7c05"}, {file = "identify-2.3.0.tar.gz", hash = "sha256:fd08c97f23ceee72784081f1ce5125c8f53a02d3f2716dde79a6ab8f1039fea5"}, @@ -819,6 +870,7 @@ version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" +groups = ["main", "doc"] files = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, @@ -830,6 +882,7 @@ version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["doc"] files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, @@ -841,6 +894,8 @@ version = "4.13.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.7" +groups = ["main", "doc", "test"] +markers = "python_version == \"3.9\"" files = [ {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"}, {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"}, @@ -852,7 +907,7 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)"] [[package]] name = "importlib-resources" @@ -860,6 +915,8 @@ version = "5.13.0" description = "Read resources from Python packages" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version == \"3.9\"" files = [ {file = "importlib_resources-5.13.0-py3-none-any.whl", hash = "sha256:9f7bd0c97b79972a6cce36a366356d16d5e13b09679c11a58f1014bfdf8e64b2"}, {file = "importlib_resources-5.13.0.tar.gz", hash = "sha256:82d5c6cca930697dbbd86c93333bb2c2e72861d4789a11c2662b933e5ad2b528"}, @@ -870,7 +927,7 @@ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-ruff"] [[package]] name = "iniconfig" @@ -878,6 +935,7 @@ version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" optional = false python-versions = "*" +groups = ["test"] files = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, @@ -889,6 +947,8 @@ version = "1.0.0" description = "A service-oriented component model framework" optional = false python-versions = "*" +groups = ["main"] +markers = "python_version == \"3.9\"" files = [ {file = "iPOPO-1.0.0-py2.py3-none-any.whl", hash = "sha256:d4f822b67ebfbf89edfadeabcb9be9dc61dd0241486d31f4fd1a59d39f3d7da6"}, {file = "iPOPO-1.0.0.tar.gz", hash = "sha256:f84d4afcf6f0f4c952d5f6e729c6d6c5e34baa527cd6d7727159fe3738f2ed34"}, @@ -911,6 +971,8 @@ version = "3.0.0" description = "A service-oriented component model framework" optional = false python-versions = "*" +groups = ["main"] +markers = "python_version >= \"3.10\"" files = [ {file = "iPOPO-3.0.0-py2.py3-none-any.whl", hash = "sha256:d4efe853067430307942dc1c8ad66dcda430f235add4c4b826d746e0fbebf9d0"}, {file = "iPOPO-3.0.0.tar.gz", hash = "sha256:ba26c0bca5a404959c7128156a57325aba402a2512593426daa3f219cc8dff28"}, @@ -935,6 +997,7 @@ version = "6.28.0" description = "IPython Kernel for Jupyter" optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "ipykernel-6.28.0-py3-none-any.whl", hash = "sha256:c6e9a9c63a7f4095c0a22a79f765f079f9ec7be4f2430a898ddea889e8665661"}, {file = "ipykernel-6.28.0.tar.gz", hash = "sha256:69c11403d26de69df02225916f916b37ea4b9af417da0a8c827f84328d88e5f3"}, @@ -968,6 +1031,7 @@ version = "0.7.0" description = "Spreadsheet in the Jupyter notebook." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "ipysheet-0.7.0-py2.py3-none-any.whl", hash = "sha256:0a5179815444977fbc2317bab6133a3af179c4155996dd3f723b4d10f2d5a970"}, {file = "ipysheet-0.7.0.tar.gz", hash = "sha256:34587a02d3bc304724126f9a6dbdf8237a04a6a7aec98a8ffbda16f93fe4ec27"}, @@ -982,6 +1046,7 @@ version = "7.28.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.7" +groups = ["main", "test"] files = [ {file = "ipython-7.28.0-py3-none-any.whl", hash = "sha256:f16148f9163e1e526f1008d7c8d966d9c15600ca20d1a754287cf96d00ba6f1d"}, {file = "ipython-7.28.0.tar.gz", hash = "sha256:2097be5c814d1b974aea57673176a924c4c8c9583890e7a5f082f547b9975b11"}, @@ -1018,6 +1083,7 @@ version = "0.2.0" description = "Vestigial utilities from IPython" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, @@ -1029,6 +1095,7 @@ version = "7.7.2" description = "IPython HTML widgets for Jupyter" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "ipywidgets-7.7.2-py2.py3-none-any.whl", hash = "sha256:3d47a7826cc6e2644d7cb90db26699451f8b42379cf63b761431b63d19984ca2"}, {file = "ipywidgets-7.7.2.tar.gz", hash = "sha256:449ab8e7872d0f388ee5c5b3666b9d6af5e5618a5749fd62652680be37dff2af"}, @@ -1043,7 +1110,7 @@ traitlets = ">=4.3.1" widgetsnbextension = ">=3.6.0,<3.7.0" [package.extras] -test = ["mock", "pytest (>=3.6.0)", "pytest-cov"] +test = ["mock ; python_version == \"2.7\"", "pytest (>=3.6.0)", "pytest-cov"] [[package]] name = "isoduration" @@ -1051,6 +1118,7 @@ version = "20.11.0" description = "Operations with ISO 8601 durations" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"}, {file = "isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9"}, @@ -1065,6 +1133,7 @@ version = "0.18.0" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" +groups = ["main", "test"] files = [ {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"}, @@ -1083,6 +1152,7 @@ version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main", "doc"] files = [ {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, @@ -1100,6 +1170,7 @@ version = "0.9.6" description = "A Python implementation of the JSON5 data format." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "json5-0.9.6-py2.py3-none-any.whl", hash = "sha256:823e510eb355949bed817e1f3e2d682455dc6af9daf6066d5698d6a2ca4481c2"}, {file = "json5-0.9.6.tar.gz", hash = "sha256:9175ad1bc248e22bb8d95a8e8d765958bf0008fef2fe8abab5bc04e0f1ac8302"}, @@ -1114,6 +1185,7 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, @@ -1125,6 +1197,7 @@ version = "0.4.3.1" description = "This project is an implementation of the JSON-RPC v2.0 specification (backwards-compatible) as a client library, for Python 2.7 and Python 3. This version is a fork of jsonrpclib by Josh Marshall, made to be also usable with Pelix/iPOPO remote services." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "jsonrpclib-pelix-0.4.3.1.tar.gz", hash = "sha256:f6f376c72ec1c0dfd69fcc2721d711f6ca1fe22bf71f99e6884c5e43e9b58c95"}, {file = "jsonrpclib_pelix-0.4.3.1-py2.py3-none-any.whl", hash = "sha256:36c44cc584113088e9b9c15e968fe5258708c26baf86e4ee140745c006c8719d"}, @@ -1136,6 +1209,7 @@ version = "4.23.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" +groups = ["main", "lint", "test"] files = [ {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, @@ -1165,6 +1239,7 @@ version = "2023.12.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.8" +groups = ["main", "lint", "test"] files = [ {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, @@ -1179,6 +1254,7 @@ version = "8.6.3" description = "Jupyter protocol implementation and client libraries" optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, @@ -1194,7 +1270,7 @@ traitlets = ">=5.3" [package.extras] docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] -test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko ; sys_platform == \"win32\"", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] [[package]] name = "jupyter-core" @@ -1202,6 +1278,7 @@ version = "5.7.2" description = "Jupyter core package. A base package on which Jupyter projects rely." optional = false python-versions = ">=3.8" +groups = ["main", "lint", "test"] files = [ {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, @@ -1222,6 +1299,7 @@ version = "0.10.0" description = "Jupyter Event System library" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jupyter_events-0.10.0-py3-none-any.whl", hash = "sha256:4b72130875e59d57716d327ea70d3ebc3af1944d3717e5a498b8a06c6c159960"}, {file = "jupyter_events-0.10.0.tar.gz", hash = "sha256:670b8229d3cc882ec782144ed22e0d29e1c2d639263f92ca8383e66682845e22"}, @@ -1247,6 +1325,8 @@ version = "2.2.5" description = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.12\"" files = [ {file = "jupyter-lsp-2.2.5.tar.gz", hash = "sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001"}, {file = "jupyter_lsp-2.2.5-py3-none-any.whl", hash = "sha256:45fbddbd505f3fbfb0b6cb2f1bc5e15e83ab7c79cd6e89416b248cb3c00c11da"}, @@ -1261,6 +1341,7 @@ version = "2.7.0" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jupyter_server-2.7.0-py3-none-any.whl", hash = "sha256:6a77912aff643e53fa14bdb2634884b52b784a4be77ce8e93f7283faed0f0849"}, {file = "jupyter_server-2.7.0.tar.gz", hash = "sha256:36da0a266d31a41ac335a366c88933c17dfa5bb817a48f5c02c16d303bc9477f"}, @@ -1297,6 +1378,8 @@ version = "0.9.3" description = "Jupyter Server extension providing an implementation of the File ID service." optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\"" files = [ {file = "jupyter_server_fileid-0.9.3-py3-none-any.whl", hash = "sha256:f73c01c19f90005d3fff93607b91b4955ba4e1dccdde9bfe8026646f94053791"}, {file = "jupyter_server_fileid-0.9.3.tar.gz", hash = "sha256:521608bb87f606a8637fcbdce2f3d24a8b3cc89d2eef61751cb40e468d4e54be"}, @@ -1316,6 +1399,7 @@ version = "0.5.3" description = "A Jupyter Server Extension Providing Terminals." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa"}, {file = "jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269"}, @@ -1335,6 +1419,8 @@ version = "0.8.0" description = "A Jupyter Server Extension Providing Y Documents." optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\"" files = [ {file = "jupyter_server_ydoc-0.8.0-py3-none-any.whl", hash = "sha256:969a3a1a77ed4e99487d60a74048dc9fa7d3b0dcd32e60885d835bbf7ba7be11"}, {file = "jupyter_server_ydoc-0.8.0.tar.gz", hash = "sha256:a6fe125091792d16c962cc3720c950c2b87fcc8c3ecf0c54c84e9a20b814526c"}, @@ -1354,6 +1440,8 @@ version = "0.2.5" description = "Document structures for collaborative editing using Ypy" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\"" files = [ {file = "jupyter_ydoc-0.2.5-py3-none-any.whl", hash = "sha256:5759170f112c70320a84217dd98d287699076ae65a7f88d458d57940a9f2b882"}, {file = "jupyter_ydoc-0.2.5.tar.gz", hash = "sha256:5a02ca7449f0d875f73e8cb8efdf695dddef15a8e71378b1f4eda6b7c90f5382"}, @@ -1373,6 +1461,8 @@ version = "3.6.8" description = "JupyterLab computational environment" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\"" files = [ {file = "jupyterlab-3.6.8-py3-none-any.whl", hash = "sha256:891284e75158998e23eb7a23ecc4caaf27b365e41adca374109b1305b9f769db"}, {file = "jupyterlab-3.6.8.tar.gz", hash = "sha256:a2477383e23f20009188bd9dac7e6e38dbc54307bc36d716bea6ced450647c97"}, @@ -1402,6 +1492,8 @@ version = "4.2.0" description = "JupyterLab computational environment" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.12\"" files = [ {file = "jupyterlab-4.2.0-py3-none-any.whl", hash = "sha256:0dfe9278e25a145362289c555d9beb505697d269c10e99909766af7c440ad3cc"}, {file = "jupyterlab-4.2.0.tar.gz", hash = "sha256:356e9205a6a2ab689c47c8fe4919dba6c076e376d03f26baadc05748c2435dd5"}, @@ -1434,6 +1526,7 @@ version = "0.2.2" description = "Pygments theme using JupyterLab CSS variables" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jupyterlab_pygments-0.2.2-py2.py3-none-any.whl", hash = "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f"}, {file = "jupyterlab_pygments-0.2.2.tar.gz", hash = "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d"}, @@ -1445,6 +1538,7 @@ version = "2.27.3" description = "A set of server components for JupyterLab and JupyterLab like applications." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4"}, {file = "jupyterlab_server-2.27.3.tar.gz", hash = "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4"}, @@ -1471,6 +1565,7 @@ version = "1.1.1" description = "A JupyterLab extension." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "jupyterlab_widgets-1.1.1-py3-none-any.whl", hash = "sha256:90ab47d99da03a3697074acb23b2975ead1d6171aa41cb2812041a7f2a08177a"}, {file = "jupyterlab_widgets-1.1.1.tar.gz", hash = "sha256:67d0ef1e407e0c42c8ab60b9d901cd7a4c68923650763f75bf17fb06c1943b79"}, @@ -1482,6 +1577,7 @@ version = "1.3.2" description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "kiwisolver-1.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1d819553730d3c2724582124aee8a03c846ec4362ded1034c16fb3ef309264e6"}, {file = "kiwisolver-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d93a1095f83e908fc253f2fb569c2711414c0bfd451cab580466465b235b470"}, @@ -1535,6 +1631,7 @@ version = "2.0.1" description = "A lexer and codec to work with LaTeX code in Python." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["doc"] files = [ {file = "latexcodec-2.0.1-py2.py3-none-any.whl", hash = "sha256:c277a193638dc7683c4c30f6684e3db728a06efb0dc9cf346db8bd0aa6c5d271"}, {file = "latexcodec-2.0.1.tar.gz", hash = "sha256:2aa2551c373261cefe2ad3a8953a6d6533e68238d180eb4bb91d7964adb3fe9a"}, @@ -1549,6 +1646,7 @@ version = "4.9.3" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +groups = ["main"] files = [ {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, @@ -1656,6 +1754,7 @@ version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.6" +groups = ["main", "doc"] files = [ {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, @@ -1734,6 +1833,7 @@ version = "3.9.0" description = "Python plotting package" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "matplotlib-3.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2bcee1dffaf60fe7656183ac2190bd630842ff87b3153afb3e384d966b57fe56"}, {file = "matplotlib-3.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3f988bafb0fa39d1074ddd5bacd958c853e11def40800c5824556eb630f94d3b"}, @@ -1787,6 +1887,7 @@ version = "0.1.3" description = "Inline Matplotlib backend for Jupyter" optional = false python-versions = ">=3.5" +groups = ["main", "test"] files = [ {file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"}, {file = "matplotlib_inline-0.1.3-py3-none-any.whl", hash = "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c"}, @@ -1801,6 +1902,7 @@ version = "2.0.4" description = "A sane Markdown parser with useful plugins and renderers" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "mistune-2.0.4-py2.py3-none-any.whl", hash = "sha256:182cc5ee6f8ed1b807de6b7bb50155df7b66495412836b9a74c8fbdfc75fe36d"}, {file = "mistune-2.0.4.tar.gz", hash = "sha256:9ee0a66053e2267aba772c71e06891fa8f1af6d4b01d5e84e267b4570d4d9808"}, @@ -1812,6 +1914,8 @@ version = "4.0.2" description = "Python bindings for MPI" optional = true python-versions = ">=3.6" +groups = ["main"] +markers = "extra == \"mpi\" or extra == \"mpi4py\"" files = [ {file = "mpi4py-4.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:1a5c91c99b3c0c86144581624f73d31d8b87e8854f2116d8b7fd3fb65072802a"}, {file = "mpi4py-4.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:57060d0bb4246fecf53e10fca6ebaa540992b7eb7c1b901ddbc57114b10aa499"}, @@ -1834,6 +1938,8 @@ version = "1.1.0" description = "Jupyter Notebook as a Jupyter Server extension." optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\"" files = [ {file = "nbclassic-1.1.0-py3-none-any.whl", hash = "sha256:8c0fd6e36e320a18657ff44ed96c3a400f17a903a3744fc322303a515778f2ba"}, {file = "nbclassic-1.1.0.tar.gz", hash = "sha256:77b77ba85f9e988f9bad85df345b514e9e64c7f0e822992ab1df4a78ac64fc1e"}, @@ -1848,7 +1954,7 @@ notebook-shim = ">=0.2.3" [package.extras] docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] json-logging = ["json-logging"] -test = ["coverage", "nbval", "pytest", "pytest-cov", "pytest-jupyter", "pytest-playwright", "pytest-tornasync", "requests", "requests-unixsocket", "testpath"] +test = ["coverage", "nbval", "pytest", "pytest-cov", "pytest-jupyter", "pytest-playwright", "pytest-tornasync", "requests", "requests-unixsocket ; sys_platform != \"win32\"", "testpath"] [[package]] name = "nbclient" @@ -1856,6 +1962,7 @@ version = "0.6.3" description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "nbclient-0.6.3-py3-none-any.whl", hash = "sha256:2747ac9b385720d8a6c34f2f71e72cbe64aec6cadaadcc064a4df0b0e99c5874"}, {file = "nbclient-0.6.3.tar.gz", hash = "sha256:b80726fc1fb89a0e8f8be1e77e28d0026b1e8ed90bc143c8a0c7622e4f8cdd9e"}, @@ -1877,6 +1984,7 @@ version = "7.0.0" description = "Converting Jupyter Notebooks" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "nbconvert-7.0.0-py3-none-any.whl", hash = "sha256:26843ae233167e8aae31c20e3e1d91f431f04c9f34363bbe2dd0d247f772641c"}, {file = "nbconvert-7.0.0.tar.gz", hash = "sha256:fd1e361da30e30e4c5a5ae89f7cae95ca2a4d4407389672473312249a7ba0060"}, @@ -1916,6 +2024,7 @@ version = "5.6.1" description = "The Jupyter Notebook format" optional = false python-versions = ">=3.7" +groups = ["main", "lint", "test"] files = [ {file = "nbformat-5.6.1-py3-none-any.whl", hash = "sha256:9c071f0f615c1b0f4f9bf6745ecfd3294fc02daf279a05c76004c901e9dc5893"}, {file = "nbformat-5.6.1.tar.gz", hash = "sha256:146b5b9969391387c2089256359f5da7c718b1d8a88ba814320273ea410e646e"}, @@ -1936,6 +2045,7 @@ version = "0.6.1" description = "Strips outputs from Jupyter and IPython notebooks" optional = false python-versions = ">=3.6" +groups = ["lint"] files = [ {file = "nbstripout-0.6.1-py2.py3-none-any.whl", hash = "sha256:5ff6eb0debbcd656c4a64db8e082a24fabcfc753a9e8c9f6d786971e8f29e110"}, {file = "nbstripout-0.6.1.tar.gz", hash = "sha256:9065bcdd1488b386e4f3c081ffc1d48f4513a2f8d8bf4d0d9a28208c5dafe9d3"}, @@ -1950,6 +2060,7 @@ version = "0.11.0" description = "A py.test plugin to validate Jupyter notebooks" optional = false python-versions = ">=3.7, <4" +groups = ["test"] files = [ {file = "nbval-0.11.0-py2.py3-none-any.whl", hash = "sha256:307aecc866c9a1e8a13bb5bbb008a702bacfda2394dff6fe504a3108a58042a0"}, {file = "nbval-0.11.0.tar.gz", hash = "sha256:77c95797607b0a968babd2597ee3494102d25c3ad37435debbdac0e46e379094"}, @@ -1968,6 +2079,7 @@ version = "1.5.5" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" +groups = ["main", "test"] files = [ {file = "nest_asyncio-1.5.5-py3-none-any.whl", hash = "sha256:b98e3ec1b246135e4642eceffa5a6c23a3ab12c82ff816a92c612d68205813b2"}, {file = "nest_asyncio-1.5.5.tar.gz", hash = "sha256:e442291cd942698be619823a17a86a5759eabe1f8613084790de189fe9e16d65"}, @@ -1979,6 +2091,7 @@ version = "2.6.3" description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "networkx-2.6.3-py3-none-any.whl", hash = "sha256:80b6b89c77d1dfb64a4c7854981b60aeea6360ac02c6d4e4913319e0a313abef"}, {file = "networkx-2.6.3.tar.gz", hash = "sha256:c0946ed31d71f1b732b5aaa6da5a0388a345019af232ce2f49c766e2d6795c51"}, @@ -1997,6 +2110,7 @@ version = "1.6.0" description = "Node.js virtual environment builder" optional = false python-versions = "*" +groups = ["lint"] files = [ {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, @@ -2008,6 +2122,7 @@ version = "6.0.0" description = "A web-based notebook environment for interactive computing" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "notebook-6.0.0-py3-none-any.whl", hash = "sha256:0be97e939cec73cde37fc4d2a606a6f497a9addf3afcf61a09a21b0c35e699c5"}, {file = "notebook-6.0.0.tar.gz", hash = "sha256:5c16dbf4fa824db19de43637ebfb24bcbd3b4f646e5d6a0414ed3a376d6bc951"}, @@ -2029,7 +2144,7 @@ tornado = ">=5.0" traitlets = ">=4.2.1" [package.extras] -test = ["coverage", "mock", "nbval", "nose", "nose-exclude", "nose-exclude", "nose-warnings-filters", "pytest", "pytest-cov", "requests", "selenium"] +test = ["coverage", "mock ; python_version == \"2.7\"", "nbval", "nose", "nose-exclude", "nose-exclude ; sys_platform == \"win32\"", "nose-warnings-filters", "pytest", "pytest-cov", "requests", "selenium"] [[package]] name = "notebook-shim" @@ -2037,6 +2152,7 @@ version = "0.2.4" description = "A shim layer for notebook traits and config" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef"}, {file = "notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb"}, @@ -2054,6 +2170,7 @@ version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -2099,6 +2216,7 @@ version = "3.38.0" description = "OpenMDAO framework infrastructure" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "openmdao-3.38.0.tar.gz", hash = "sha256:9f95f20b9708907b8fc791177c3206d272b59524718f277315f1b376f8d12184"}, ] @@ -2126,6 +2244,7 @@ version = "1.1.0" description = "Additional solvers and drivers for OpenMDAO framework" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "openmdao_extensions-1.1.0.tar.gz", hash = "sha256:2a46552f7ea05151f0118171d21e718ceb220fbeb0a01d50d29e80fe971dde99"}, ] @@ -2139,6 +2258,7 @@ version = "7.7.0" description = "A decorator to automatically detect mismatch when overriding a method." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"}, {file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"}, @@ -2150,6 +2270,7 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "doc", "test"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -2161,6 +2282,7 @@ version = "2.2.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, @@ -2247,6 +2369,7 @@ version = "1.5.0" description = "Utilities for writing pandoc filters in python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] files = [ {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"}, {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"}, @@ -2258,6 +2381,7 @@ version = "0.8.2" description = "A Python Parser" optional = false python-versions = ">=3.6" +groups = ["main", "test"] files = [ {file = "parso-0.8.2-py2.py3-none-any.whl", hash = "sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22"}, {file = "parso-0.8.2.tar.gz", hash = "sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398"}, @@ -2273,6 +2397,8 @@ version = "4.8.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" +groups = ["main", "test"] +markers = "sys_platform != \"win32\"" files = [ {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, @@ -2287,6 +2413,7 @@ version = "0.7.5" description = "Tiny 'shelve'-like database with concurrency support" optional = false python-versions = "*" +groups = ["main", "test"] files = [ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, @@ -2298,6 +2425,7 @@ version = "10.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"}, {file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"}, @@ -2375,7 +2503,7 @@ docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] +typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] [[package]] @@ -2384,6 +2512,7 @@ version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["main", "lint", "test"] files = [ {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, @@ -2400,6 +2529,7 @@ version = "5.19.0" description = "An open-source, interactive data visualization library for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "plotly-5.19.0-py3-none-any.whl", hash = "sha256:906abcc5f15945765328c5d47edaa884bc99f5985fbc61e8cd4dc361f4ff8f5a"}, {file = "plotly-5.19.0.tar.gz", hash = "sha256:5ea91a56571292ade3e3bc9bf712eba0b95a1fb0a941375d978cc79432e055f4"}, @@ -2415,6 +2545,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -2430,6 +2561,7 @@ version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.8" +groups = ["lint"] files = [ {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, @@ -2448,6 +2580,7 @@ version = "0.11.0" description = "Python client for the Prometheus monitoring system." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] files = [ {file = "prometheus_client-0.11.0-py2.py3-none-any.whl", hash = "sha256:b014bc76815eb1399da8ce5fc84b7717a3e63652b0c0f8804092c9363acab1b2"}, {file = "prometheus_client-0.11.0.tar.gz", hash = "sha256:3a8baade6cb80bcfe43297e33e7623f3118d660d41387593758e2fb1ea173a86"}, @@ -2462,6 +2595,7 @@ version = "3.0.20" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.6.2" +groups = ["main", "test"] files = [ {file = "prompt_toolkit-3.0.20-py3-none-any.whl", hash = "sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c"}, {file = "prompt_toolkit-3.0.20.tar.gz", hash = "sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c"}, @@ -2476,6 +2610,7 @@ version = "6.0.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main", "test"] files = [ {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, @@ -2497,7 +2632,7 @@ files = [ ] [package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +test = ["enum34 ; python_version <= \"3.4\"", "ipaddress ; python_version < \"3.0\"", "mock ; python_version < \"3.0\"", "pywin32 ; sys_platform == \"win32\"", "wmi ; sys_platform == \"win32\""] [[package]] name = "ptyprocess" @@ -2505,10 +2640,12 @@ version = "0.7.0" description = "Run a subprocess in a pseudo terminal" optional = false python-versions = "*" +groups = ["main", "test"] files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] +markers = {main = "sys_platform != \"win32\" or os_name != \"nt\"", test = "sys_platform != \"win32\""} [[package]] name = "pybtex" @@ -2516,6 +2653,7 @@ version = "0.24.0" description = "A BibTeX-compatible bibliography processor in Python" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" +groups = ["doc"] files = [ {file = "pybtex-0.24.0-py2.py3-none-any.whl", hash = "sha256:e1e0c8c69998452fea90e9179aa2a98ab103f3eed894405b7264e517cc2fcc0f"}, {file = "pybtex-0.24.0.tar.gz", hash = "sha256:818eae35b61733e5c007c3fcd2cfb75ed1bc8b4173c1f70b56cc4c0802d34755"}, @@ -2535,6 +2673,7 @@ version = "1.0.3" description = "A docutils backend for pybtex." optional = false python-versions = ">=3.7" +groups = ["doc"] files = [ {file = "pybtex-docutils-1.0.3.tar.gz", hash = "sha256:3a7ebdf92b593e00e8c1c538aa9a20bca5d92d84231124715acc964d51d93c6b"}, {file = "pybtex_docutils-1.0.3-py3-none-any.whl", hash = "sha256:8fd290d2ae48e32fcb54d86b0efb8d573198653c7e2447d5bec5847095f430b9"}, @@ -2550,10 +2689,12 @@ version = "2.20" description = "C parser in Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main", "test"] files = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] +markers = {test = "implementation_name == \"pypy\""} [[package]] name = "pydoe3" @@ -2561,6 +2702,7 @@ version = "1.0.4" description = "Design of experiments for Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pydoe3-1.0.4-py2.py3-none-any.whl", hash = "sha256:233b15a2191c70a0d35a1306d23c45423b2995472c2c1402a60fdf4408caf3dd"}, {file = "pydoe3-1.0.4.tar.gz", hash = "sha256:904f52eba87d9a1b9e0c14705cd07ff8fd53af01568c77dca5f4fe7a5ce7fd36"}, @@ -2576,6 +2718,7 @@ version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["main", "doc", "test"] files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, @@ -2590,6 +2733,7 @@ version = "3.1.4" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" +groups = ["dev"] files = [ {file = "pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c"}, {file = "pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032"}, @@ -2604,6 +2748,7 @@ version = "8.2.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, @@ -2626,6 +2771,7 @@ version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, @@ -2644,6 +2790,7 @@ version = "2.8.2" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "dev", "test"] files = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, @@ -2658,6 +2805,7 @@ version = "2.0.7" description = "A python library adding a json log formatter" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "python-json-logger-2.0.7.tar.gz", hash = "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c"}, {file = "python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"}, @@ -2669,6 +2817,7 @@ version = "2021.3" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, @@ -2680,6 +2829,8 @@ version = "306" description = "Python for Window Extensions" optional = false python-versions = "*" +groups = ["main", "lint", "test"] +markers = "platform_python_implementation != \"PyPy\" and sys_platform == \"win32\"" files = [ {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, @@ -2703,6 +2854,8 @@ version = "2.0.11" description = "Pseudo terminal support for Windows from Python." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "os_name == \"nt\"" files = [ {file = "pywinpty-2.0.11-cp310-none-win_amd64.whl", hash = "sha256:452f10ac9ff8ab9151aa8cea9e491a9612a12250b1899278c6a56bc184afb47f"}, {file = "pywinpty-2.0.11-cp311-none-win_amd64.whl", hash = "sha256:6701867d42aec1239bc0fedf49a336570eb60eb886e81763db77ea2b6c533cc3"}, @@ -2717,6 +2870,7 @@ version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" +groups = ["main", "doc", "lint"] files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, @@ -2777,6 +2931,7 @@ version = "25.1.1" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.6" +groups = ["main", "test"] files = [ {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:381469297409c5adf9a0e884c5eb5186ed33137badcbbb0560b86e910a2f1e76"}, {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:955215ed0604dac5b01907424dfa28b40f2b2292d6493445dd34d0dfa72586a8"}, @@ -2882,6 +3037,7 @@ version = "0.35.1" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" +groups = ["main", "lint", "test"] files = [ {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, @@ -2897,6 +3053,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main", "doc"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -2918,6 +3075,7 @@ version = "0.1.4" description = "A pure python RFC3339 validator" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] files = [ {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"}, {file = "rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b"}, @@ -2932,6 +3090,7 @@ version = "0.1.1" description = "Pure python rfc3986 validator" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] files = [ {file = "rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9"}, {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, @@ -2943,6 +3102,7 @@ version = "0.20.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" +groups = ["main", "lint", "test"] files = [ {file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"}, {file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"}, @@ -3055,6 +3215,7 @@ version = "0.16.0" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "ruamel.yaml-0.16.0-py2.py3-none-any.whl", hash = "sha256:eb4b1ffd095785c50f110118cb6f7bb9cd011ecc013b014436d38b7f8fb7afa9"}, {file = "ruamel.yaml-0.16.0.tar.gz", hash = "sha256:4c1dbad22790b5ea8587c2a0cae97ebc7a9d0d88de5d0edb70dea2eab7d8534a"}, @@ -3066,29 +3227,31 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [[package]] name = "ruff" -version = "0.12.0" +version = "0.14.4" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" -files = [ - {file = "ruff-0.12.0-py3-none-linux_armv6l.whl", hash = "sha256:5652a9ecdb308a1754d96a68827755f28d5dfb416b06f60fd9e13f26191a8848"}, - {file = "ruff-0.12.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:05ed0c914fabc602fc1f3b42c53aa219e5736cb030cdd85640c32dbc73da74a6"}, - {file = "ruff-0.12.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:07a7aa9b69ac3fcfda3c507916d5d1bca10821fe3797d46bad10f2c6de1edda0"}, - {file = "ruff-0.12.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7731c3eec50af71597243bace7ec6104616ca56dda2b99c89935fe926bdcd48"}, - {file = "ruff-0.12.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:952d0630eae628250ab1c70a7fffb641b03e6b4a2d3f3ec6c1d19b4ab6c6c807"}, - {file = "ruff-0.12.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c021f04ea06966b02614d442e94071781c424ab8e02ec7af2f037b4c1e01cc82"}, - {file = "ruff-0.12.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d235618283718ee2fe14db07f954f9b2423700919dc688eacf3f8797a11315c"}, - {file = "ruff-0.12.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0758038f81beec8cc52ca22de9685b8ae7f7cc18c013ec2050012862cc9165"}, - {file = "ruff-0.12.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:139b3d28027987b78fc8d6cfb61165447bdf3740e650b7c480744873688808c2"}, - {file = "ruff-0.12.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68853e8517b17bba004152aebd9dd77d5213e503a5f2789395b25f26acac0da4"}, - {file = "ruff-0.12.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3a9512af224b9ac4757f7010843771da6b2b0935a9e5e76bb407caa901a1a514"}, - {file = "ruff-0.12.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b08df3d96db798e5beb488d4df03011874aff919a97dcc2dd8539bb2be5d6a88"}, - {file = "ruff-0.12.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6a315992297a7435a66259073681bb0d8647a826b7a6de45c6934b2ca3a9ed51"}, - {file = "ruff-0.12.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e55e44e770e061f55a7dbc6e9aed47feea07731d809a3710feda2262d2d4d8a"}, - {file = "ruff-0.12.0-py3-none-win32.whl", hash = "sha256:7162a4c816f8d1555eb195c46ae0bd819834d2a3f18f98cc63819a7b46f474fb"}, - {file = "ruff-0.12.0-py3-none-win_amd64.whl", hash = "sha256:d00b7a157b8fb6d3827b49d3324da34a1e3f93492c1f97b08e222ad7e9b291e0"}, - {file = "ruff-0.12.0-py3-none-win_arm64.whl", hash = "sha256:8cd24580405ad8c1cc64d61725bca091d6b6da7eb3d36f72cc605467069d7e8b"}, - {file = "ruff-0.12.0.tar.gz", hash = "sha256:4d047db3662418d4a848a3fdbfaf17488b34b62f527ed6f10cb8afd78135bc5c"}, +groups = ["lint"] +files = [ + {file = "ruff-0.14.4-py3-none-linux_armv6l.whl", hash = "sha256:e6604613ffbcf2297cd5dcba0e0ac9bd0c11dc026442dfbb614504e87c349518"}, + {file = "ruff-0.14.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d99c0b52b6f0598acede45ee78288e5e9b4409d1ce7f661f0fa36d4cbeadf9a4"}, + {file = "ruff-0.14.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9358d490ec030f1b51d048a7fd6ead418ed0826daf6149e95e30aa67c168af33"}, + {file = "ruff-0.14.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b40d27924f1f02dfa827b9c0712a13c0e4b108421665322218fc38caf615c2"}, + {file = "ruff-0.14.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f5e649052a294fe00818650712083cddc6cc02744afaf37202c65df9ea52efa5"}, + {file = "ruff-0.14.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa082a8f878deeba955531f975881828fd6afd90dfa757c2b0808aadb437136e"}, + {file = "ruff-0.14.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1043c6811c2419e39011890f14d0a30470f19d47d197c4858b2787dfa698f6c8"}, + {file = "ruff-0.14.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a9f3a936ac27fb7c2a93e4f4b943a662775879ac579a433291a6f69428722649"}, + {file = "ruff-0.14.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95643ffd209ce78bc113266b88fba3d39e0461f0cbc8b55fb92505030fb4a850"}, + {file = "ruff-0.14.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:456daa2fa1021bc86ca857f43fe29d5d8b3f0e55e9f90c58c317c1dcc2afc7b5"}, + {file = "ruff-0.14.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f911bba769e4a9f51af6e70037bb72b70b45a16db5ce73e1f72aefe6f6d62132"}, + {file = "ruff-0.14.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:76158a7369b3979fa878612c623a7e5430c18b2fd1c73b214945c2d06337db67"}, + {file = "ruff-0.14.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f3b8f3b442d2b14c246e7aeca2e75915159e06a3540e2f4bed9f50d062d24469"}, + {file = "ruff-0.14.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c62da9a06779deecf4d17ed04939ae8b31b517643b26370c3be1d26f3ef7dbde"}, + {file = "ruff-0.14.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a443a83a1506c684e98acb8cb55abaf3ef725078be40237463dae4463366349"}, + {file = "ruff-0.14.4-py3-none-win32.whl", hash = "sha256:643b69cb63cd996f1fc7229da726d07ac307eae442dd8974dbc7cf22c1e18fff"}, + {file = "ruff-0.14.4-py3-none-win_amd64.whl", hash = "sha256:26673da283b96fe35fa0c939bf8411abec47111644aa9f7cfbd3c573fb125d2c"}, + {file = "ruff-0.14.4-py3-none-win_arm64.whl", hash = "sha256:dd09c292479596b0e6fec8cd95c65c3a6dc68e9ad17b8f2382130f87ff6a75bb"}, + {file = "ruff-0.14.4.tar.gz", hash = "sha256:f459a49fe1085a749f15414ca76f61595f1a2cc8778ed7c279b6ca2e1fd19df3"}, ] [[package]] @@ -3097,6 +3260,7 @@ version = "1.11.2" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = "<3.13,>=3.9" +groups = ["main"] files = [ {file = "scipy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2b997a5369e2d30c97995dcb29d638701f8000d04df01b8e947f206e5d0ac788"}, {file = "scipy-1.11.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:95763fbda1206bec41157582bea482f50eb3702c85fffcf6d24394b071c0e87a"}, @@ -3139,15 +3303,16 @@ version = "1.8.0" description = "Send file to trash natively under Mac OS X, Windows and Linux." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "Send2Trash-1.8.0-py3-none-any.whl", hash = "sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08"}, {file = "Send2Trash-1.8.0.tar.gz", hash = "sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d"}, ] [package.extras] -nativelib = ["pyobjc-framework-Cocoa", "pywin32"] -objc = ["pyobjc-framework-Cocoa"] -win32 = ["pywin32"] +nativelib = ["pyobjc-framework-Cocoa ; sys_platform == \"darwin\"", "pywin32 ; sys_platform == \"win32\""] +objc = ["pyobjc-framework-Cocoa ; sys_platform == \"darwin\""] +win32 = ["pywin32 ; sys_platform == \"win32\""] [[package]] name = "setuptools" @@ -3155,14 +3320,16 @@ version = "65.3.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.7" +groups = ["main", "doc", "test"] files = [ {file = "setuptools-65.3.0-py3-none-any.whl", hash = "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82"}, {file = "setuptools-65.3.0.tar.gz", hash = "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57"}, ] +markers = {doc = "python_version == \"3.12\""} [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov ; platform_python_implementation != \"PyPy\"", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] @@ -3171,6 +3338,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main", "dev", "doc", "test"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -3182,6 +3350,7 @@ version = "1.2.0" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, @@ -3193,6 +3362,7 @@ version = "2.1.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = false python-versions = "*" +groups = ["doc"] files = [ {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, @@ -3204,6 +3374,7 @@ version = "2.3.2.post1" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, @@ -3215,6 +3386,7 @@ version = "7.1.2" description = "Python documentation generator" optional = false python-versions = ">=3.8" +groups = ["doc"] files = [ {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, @@ -3250,6 +3422,7 @@ version = "2.0.0" description = "Read the Docs theme for Sphinx" optional = false python-versions = ">=3.6" +groups = ["doc"] files = [ {file = "sphinx_rtd_theme-2.0.0-py2.py3-none-any.whl", hash = "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586"}, {file = "sphinx_rtd_theme-2.0.0.tar.gz", hash = "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b"}, @@ -3269,6 +3442,7 @@ version = "1.0.2" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.5" +groups = ["doc"] files = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, @@ -3284,6 +3458,7 @@ version = "2.6.3" description = "Sphinx extension for BibTeX style citations." optional = false python-versions = ">=3.7" +groups = ["doc"] files = [ {file = "sphinxcontrib_bibtex-2.6.3-py3-none-any.whl", hash = "sha256:ff016b738fcc867df0f75c29e139b3b2158d26a2c802db27963cb128be3b75fb"}, {file = "sphinxcontrib_bibtex-2.6.3.tar.gz", hash = "sha256:7c790347ef1cb0edf30de55fc324d9782d085e89c52c2b8faafa082e08e23946"}, @@ -3306,6 +3481,7 @@ version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." optional = false python-versions = ">=3.5" +groups = ["doc"] files = [ {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, @@ -3321,6 +3497,7 @@ version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.6" +groups = ["doc"] files = [ {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, @@ -3336,6 +3513,7 @@ version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" optional = false python-versions = ">=2.7" +groups = ["doc"] files = [ {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, @@ -3350,6 +3528,7 @@ version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = false python-versions = ">=3.5" +groups = ["doc"] files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, @@ -3364,6 +3543,7 @@ version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." optional = false python-versions = ">=3.5" +groups = ["doc"] files = [ {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, @@ -3379,6 +3559,7 @@ version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." optional = false python-versions = ">=3.5" +groups = ["doc"] files = [ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, @@ -3394,6 +3575,7 @@ version = "0.4.3" description = "Numpy-oriented Standard Atmosphere model" optional = false python-versions = "<4.0,>=3.8" +groups = ["main"] files = [ {file = "stdatm-0.4.3-py3-none-any.whl", hash = "sha256:66483d5635c5e5a2994466e8c44899a7eac8a2598acfec23846909e87ec82f94"}, {file = "stdatm-0.4.3.tar.gz", hash = "sha256:6e94139daaeb485d14ccf4ae5f358730599c963443c5922467dd52054534edfd"}, @@ -3415,6 +3597,7 @@ version = "0.9.0" description = "Pretty-print tabular data" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, @@ -3429,6 +3612,7 @@ version = "8.0.1" description = "Retry code until it succeeds" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "tenacity-8.0.1-py3-none-any.whl", hash = "sha256:f78f4ea81b0fabc06728c11dc2a8c01277bfc5181b321a4770471902e3eb844a"}, {file = "tenacity-8.0.1.tar.gz", hash = "sha256:43242a20e3e73291a28bcbcacfd6e000b02d3857a9a9fff56b297a27afdc932f"}, @@ -3443,6 +3627,7 @@ version = "0.12.1" description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "terminado-0.12.1-py3-none-any.whl", hash = "sha256:09fdde344324a1c9c6e610ee4ca165c4bb7f5bbf982fceeeb38998a988ef8452"}, {file = "terminado-0.12.1.tar.gz", hash = "sha256:b20fd93cc57c1678c799799d117874367cc07a3d2d55be95205b1a88fa08393f"}, @@ -3462,6 +3647,7 @@ version = "1.1.1" description = "A tiny CSS parser" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "tinycss2-1.1.1-py3-none-any.whl", hash = "sha256:fe794ceaadfe3cf3e686b22155d0da5780dd0e273471a51846d0a02bc204fec8"}, {file = "tinycss2-1.1.1.tar.gz", hash = "sha256:b2e44dd8883c360c35dd0d1b5aad0b610e5156c2cb3b33434634e539ead9d8bf"}, @@ -3480,10 +3666,12 @@ version = "1.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.6" +groups = ["main", "test"] files = [ {file = "tomli-1.2.1-py3-none-any.whl", hash = "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f"}, {file = "tomli-1.2.1.tar.gz", hash = "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"}, ] +markers = {test = "python_version < \"3.11\""} [[package]] name = "tomli-w" @@ -3491,6 +3679,7 @@ version = "1.0.0" description = "A lil' TOML writer" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tomli_w-1.0.0-py3-none-any.whl", hash = "sha256:9f2a07e8be30a0729e533ec968016807069991ae2fd921a78d42f429ae5f4463"}, {file = "tomli_w-1.0.0.tar.gz", hash = "sha256:f463434305e0336248cac9c2dc8076b707d8a12d019dd349f5c1e382dd1ae1b9"}, @@ -3502,6 +3691,7 @@ version = "0.5.3" description = "Style preserving TOML library" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] files = [ {file = "tomlkit-0.5.3-py2.py3-none-any.whl", hash = "sha256:f077456d35303e7908cc233b340f71e0bec96f63429997f38ca9272b7d64029e"}, {file = "tomlkit-0.5.3.tar.gz", hash = "sha256:d6506342615d051bc961f70bfcfa3d29b6616cc08a3ddfd4bc24196f16fd4ec2"}, @@ -3513,6 +3703,7 @@ version = "6.4.1" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, @@ -3533,6 +3724,7 @@ version = "5.14.3" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" +groups = ["main", "lint", "test"] files = [ {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, @@ -3548,6 +3740,7 @@ version = "2.9.0.20241003" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "types-python-dateutil-2.9.0.20241003.tar.gz", hash = "sha256:58cb85449b2a56d6684e41aeefb4c4280631246a0da1a719bdbe6f3fb0317446"}, {file = "types_python_dateutil-2.9.0.20241003-py3-none-any.whl", hash = "sha256:250e1d8e80e7bbc3a6c99b907762711d1a1cdd00e978ad39cb5940f6f0a87f3d"}, @@ -3559,6 +3752,8 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\"" files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -3570,6 +3765,7 @@ version = "2024.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main"] files = [ {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, @@ -3581,6 +3777,7 @@ version = "1.3.0" description = "RFC 6570 URI Template Processor" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7"}, {file = "uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363"}, @@ -3595,6 +3792,7 @@ version = "1.26.7" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +groups = ["main", "doc"] files = [ {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, @@ -3602,7 +3800,7 @@ files = [ [package.extras] brotli = ["brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress ; python_version == \"2.7\"", "pyOpenSSL (>=0.14)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -3611,6 +3809,7 @@ version = "20.26.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" +groups = ["lint"] files = [ {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, @@ -3623,7 +3822,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "wcwidth" @@ -3631,6 +3830,7 @@ version = "0.2.5" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" +groups = ["main", "test"] files = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, @@ -3642,6 +3842,7 @@ version = "24.8.0" description = "A library for working with the color formats defined by HTML and CSS." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "webcolors-24.8.0-py3-none-any.whl", hash = "sha256:fc4c3b59358ada164552084a8ebee637c221e4059267d0f8325b3b560f6c7f0a"}, {file = "webcolors-24.8.0.tar.gz", hash = "sha256:08b07af286a01bcd30d583a7acadf629583d1f79bfef27dd2c2c5c263817277d"}, @@ -3657,6 +3858,7 @@ version = "0.5.1" description = "Character encoding aliases for legacy web content" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, @@ -3668,6 +3870,7 @@ version = "1.2.1" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "websocket-client-1.2.1.tar.gz", hash = "sha256:8dfb715d8a992f5712fff8c843adae94e22b22a99b2c5e6b0ec4a1a981cc4e0d"}, {file = "websocket_client-1.2.1-py2.py3-none-any.whl", hash = "sha256:0133d2f784858e59959ce82ddac316634229da55b498aac311f1620567a710ec"}, @@ -3683,6 +3886,7 @@ version = "3.6.1" description = "IPython HTML widgets for Jupyter" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "widgetsnbextension-3.6.1-py2.py3-none-any.whl", hash = "sha256:954e0faefdd414e4e013f17dbc7fd86f24cf1d243a3ac85d5f0fc2c2d2b50c66"}, {file = "widgetsnbextension-3.6.1.tar.gz", hash = "sha256:9c84ae64c2893c7cbe2eaafc7505221a795c27d68938454034ac487319a75b10"}, @@ -3697,6 +3901,7 @@ version = "2.2.0" description = "WhatsOpt web application command line client" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "wop-2.2.0.tar.gz", hash = "sha256:e72b4dd29139574fa48b4aaa508e075284fbd0e06b66dedf36fd175e73ec84df"}, ] @@ -3717,6 +3922,7 @@ version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.6" +groups = ["main", "test"] files = [ {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, @@ -3796,6 +4002,7 @@ version = "1.0.1" description = "XDSMjs Python module" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "xdsmjs-1.0.1.tar.gz", hash = "sha256:982411616e7cfd8ac703fd0af7369bf7c01269d69db2dbbcd65672d227e65e8e"}, ] @@ -3806,6 +4013,8 @@ version = "0.6.2" description = "Python bindings for the Y-CRDT built from yrs (Rust)" optional = false python-versions = "*" +groups = ["main"] +markers = "python_version <= \"3.11\"" files = [ {file = "y_py-0.6.2-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:c26bada6cd109095139237a46f50fc4308f861f0d304bc9e70acbc6c4503d158"}, {file = "y_py-0.6.2-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:bae1b1ad8d2b8cf938a60313f8f7461de609621c5dcae491b6e54975f76f83c5"}, @@ -3889,6 +4098,8 @@ version = "0.8.4" description = "WebSocket connector for Ypy" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\"" files = [ {file = "ypy_websocket-0.8.4-py3-none-any.whl", hash = "sha256:b1ba0dfcc9762f0ca168d2378062d3ca1299d39076b0f145d961359121042be5"}, {file = "ypy_websocket-0.8.4.tar.gz", hash = "sha256:43a001473f5c8abcf182f603049cf305cbc855ad8deaa9dfa0f3b5a7cea9d0ff"}, @@ -3908,6 +4119,8 @@ version = "3.6.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.6" +groups = ["main", "dev", "doc", "test"] +markers = "python_version == \"3.9\"" files = [ {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, @@ -3915,13 +4128,13 @@ files = [ [package.extras] docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] -testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] +testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy ; platform_python_implementation != \"PyPy\""] [extras] mpi = ["mpi4py"] mpi4py = ["mpi4py"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.9, <3.13" -content-hash = "fac91f0e6908266809312b669ac69c892108058141e6a1f53cba75956a4a43a6" +content-hash = "09755cfa5bf2ba71da1615ec0a5bfbf4395a095a8a03fb4a4f6a8a93e496d28e" diff --git a/pyproject.toml b/pyproject.toml index 36775cdd3..4b2811ab0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,7 +123,7 @@ sphinxcontrib-bibtex = "^2.6.3" [tool.poetry.group.lint.dependencies] pre-commit = "^3.5.0" nbstripout = "^0.6.0" -ruff = "0.12.00" +ruff = "0.14.4" # Entry points ----------------------------------------------------------------- [tool.poetry.scripts] diff --git a/ruff.toml b/ruff.toml index 666bfaafa..86e95925f 100644 --- a/ruff.toml +++ b/ruff.toml @@ -43,7 +43,6 @@ ignore = [ "N806", # Checks for the use of non-lowercase variable names in functions. "D105", # Missing docstring in magic method "D212", # Multi-line docstring summary should start at the first line - "PD901", # df is ok as a name "PD002", # We want to use inplace for pandas ] @@ -75,7 +74,6 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" "PLR0915", # Too many statements "PLR2004", # Enabling magic value to be used in test comparison "S101", # Enable assert in tests - "PD901", # Avoid using the generic variable name `df` for DataFrames "ANN", # No annotation on tests "D", # No doc in tests ] @@ -84,7 +82,6 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" "PLR0915", # Too many statements "PLR2004", # Enabling magic value to be used in test comparison "S101", # Enable assert in tests - "PD901", # Avoid using the generic variable name `df` for DataFrames "ANN", # No annotation on tests "D", # No doc in tests ] diff --git a/src/fastoad/gui/variable_viewer.py b/src/fastoad/gui/variable_viewer.py index bc5931ee7..1c758a625 100644 --- a/src/fastoad/gui/variable_viewer.py +++ b/src/fastoad/gui/variable_viewer.py @@ -34,8 +34,6 @@ NA = "N/A" TAG_ALL = "--ALL--" -# ruff:noqa PD901 df is ok as a name here - class VariableViewer: """ From 484d903492ca1d1808d49b62def3f983191de92e Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Thu, 13 Nov 2025 12:59:35 +0100 Subject: [PATCH 19/20] linted origin/master, ready to merge in master --- poetry.lock | 587 +++++++++++++----- src/fastoad/gui/analysis_and_plots.py | 18 +- .../module_management/_bundle_loader.py | 57 +- src/fastoad/openmdao/tests/test_variables.py | 11 +- src/fastoad/openmdao/variables/variable.py | 15 +- 5 files changed, 467 insertions(+), 221 deletions(-) diff --git a/poetry.lock b/poetry.lock index d30413fd3..db3e6cfd0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "aenum" @@ -30,7 +30,9 @@ name = "aiosqlite" version = "0.21.0" description = "asyncio bridge to the standard sqlite3 module" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\"" files = [ {file = "aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0"}, {file = "aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3"}, @@ -48,7 +50,9 @@ name = "alabaster" version = "0.7.16" description = "A light, configurable Sphinx theme" optional = false -python-versions = "*" +python-versions = ">=3.9" +groups = ["doc"] +markers = "python_version == \"3.9\"" files = [ {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, @@ -72,7 +76,8 @@ name = "anyio" version = "4.10.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.9" +groups = ["main"] files = [ {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, @@ -85,9 +90,27 @@ sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] -trio = ["trio (>=0.16)"] +trio = ["trio (>=0.26.1)"] + +[[package]] +name = "anywidget" +version = "0.9.21" +description = "custom jupyter widgets made easy" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "anywidget-0.9.21-py3-none-any.whl", hash = "sha256:78c268e0fbdb1dfd15da37fb578f9cf0a0df58a430e68d9156942b7a9391a761"}, + {file = "anywidget-0.9.21.tar.gz", hash = "sha256:b8d0172029ac426573053c416c6a587838661612208bb390fa0607862e594b27"}, +] + +[package.dependencies] +ipywidgets = ">=7.6.0" +psygnal = ">=0.8.1" +typing-extensions = ">=4.2.0" + +[package.extras] +dev = ["watchfiles (>=0.18.0)"] [[package]] name = "appnope" @@ -95,6 +118,8 @@ version = "0.1.4" description = "Disable App Nap on macOS >= 10.9" optional = false python-versions = ">=3.6" +groups = ["main", "test"] +markers = "platform_system == \"Darwin\"" files = [ {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, @@ -105,7 +130,8 @@ name = "argon2-cffi" version = "25.1.0" description = "Argon2 for Python" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" +groups = ["main"] files = [ {file = "argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741"}, {file = "argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1"}, @@ -194,7 +220,9 @@ name = "async-lru" version = "2.0.5" description = "Simple LRU cache for asyncio" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.12\"" files = [ {file = "async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943"}, {file = "async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb"}, @@ -205,19 +233,20 @@ name = "attrs" version = "25.3.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main", "lint", "test"] files = [ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "babel" @@ -232,25 +261,15 @@ files = [ ] [package.extras] -dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] - -[[package]] -name = "backcall" -version = "0.2.0" -description = "Specifications for callback functions passed in to an API" -optional = false -python-versions = "*" -files = [ - {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, - {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, -] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] [[package]] name = "beautifulsoup4" version = "4.13.4" description = "Screen-scraping library" optional = false -python-versions = ">=3.6.0" +python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, @@ -272,7 +291,8 @@ name = "bleach" version = "6.2.0" description = "An easy safelist-based HTML-sanitizing tool." optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" +groups = ["main"] files = [ {file = "bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e"}, {file = "bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f"}, @@ -313,7 +333,8 @@ name = "certifi" version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = "*" +python-versions = ">=3.7" +groups = ["main", "doc"] files = [ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, @@ -324,7 +345,8 @@ name = "cffi" version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false -python-versions = "*" +python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -404,7 +426,8 @@ name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.8" +groups = ["lint"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -415,7 +438,8 @@ name = "charset-normalizer" version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false -python-versions = ">=3.5.0" +python-versions = ">=3.7" +groups = ["main", "doc"] files = [ {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, @@ -532,7 +556,9 @@ name = "click" version = "8.2.1" description = "Composable command line interface toolkit" optional = false -python-versions = ">=3.6" +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" files = [ {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, @@ -552,6 +578,7 @@ files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "sys_platform == \"win32\" or os_name == \"nt\" or platform_system == \"Windows\"", doc = "sys_platform == \"win32\"", test = "sys_platform == \"win32\""} [[package]] name = "comm" @@ -574,6 +601,8 @@ version = "1.3.0" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version == \"3.9\"" files = [ {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, @@ -828,7 +857,8 @@ name = "coverage" version = "7.10.2" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["test"] files = [ {file = "coverage-7.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:79f0283ab5e6499fd5fe382ca3d62afa40fb50ff227676a3125d18af70eabf65"}, {file = "coverage-7.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4545e906f595ee8ab8e03e21be20d899bfc06647925bc5b224ad7e8c40e08b8"}, @@ -931,7 +961,8 @@ name = "cycler" version = "0.12.1" description = "Composable style cycles" optional = false -python-versions = "*" +python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, @@ -982,7 +1013,8 @@ name = "decorator" version = "5.2.1" description = "Decorators for Humans" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, @@ -1005,7 +1037,8 @@ name = "deprecated" version = "1.2.18" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +groups = ["main"] files = [ {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, @@ -1015,7 +1048,7 @@ files = [ wrapt = ">=1.10,<2" [package.extras] -dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] [[package]] name = "distlib" @@ -1034,7 +1067,8 @@ name = "docutils" version = "0.21.2" description = "Docutils -- Python Documentation Utilities" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["doc"] files = [ {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, @@ -1077,6 +1111,8 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "test"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -1139,23 +1175,25 @@ name = "filelock" version = "3.18.0" description = "A platform independent file lock." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["lint"] files = [ {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "fonttools" version = "4.59.0" description = "Tools to manipulate font files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "fonttools-4.59.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:524133c1be38445c5c0575eacea42dbd44374b310b1ffc4b60ff01d881fabb96"}, {file = "fonttools-4.59.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21e606b2d38fed938dde871c5736822dd6bda7a4631b92e509a1f5cd1b90c5df"}, @@ -1202,18 +1240,17 @@ files = [ ] [package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "scipy"] -lxml = ["lxml (>=4.0,<5)"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] +lxml = ["lxml (>=4.0)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] repacker = ["uharfbuzz (>=0.23.0)"] symfont = ["sympy"] -type1 = ["xattr"] -ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.0.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] +type1 = ["xattr ; sys_platform == \"darwin\""] +unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] [[package]] name = "fqdn" @@ -1232,7 +1269,9 @@ name = "h11" version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.12\"" files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -1292,7 +1331,8 @@ name = "identify" version = "2.6.12" description = "File identification library for Python" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.9" +groups = ["lint"] files = [ {file = "identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2"}, {file = "identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6"}, @@ -1306,7 +1346,8 @@ name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" +groups = ["main", "doc"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1333,10 +1374,12 @@ version = "4.13.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.7" +groups = ["main", "doc"] files = [ {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"}, {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"}, ] +markers = {main = "python_full_version < \"3.10.2\"", doc = "python_version == \"3.9\""} [package.dependencies] zipp = ">=0.5" @@ -1344,14 +1387,16 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)"] [[package]] name = "importlib-resources" version = "6.5.2" description = "Read resources from Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version == \"3.9\"" files = [ {file = "importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec"}, {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}, @@ -1361,15 +1406,20 @@ files = [ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +type = ["pytest-mypy"] [[package]] name = "iniconfig" version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = "*" +python-versions = ">=3.8" +groups = ["test"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -1404,7 +1454,9 @@ name = "ipopo" version = "3.1.0" description = "A service-oriented component model framework" optional = false -python-versions = "*" +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" files = [ {file = "ipopo-3.1.0-py3-none-any.whl", hash = "sha256:018623fdb1bd740fe68daec57e765a7332fac35c5d3101e4d5ce90c9adb83514"}, {file = "ipopo-3.1.0.tar.gz", hash = "sha256:f2f0ed6b7153561bd247b436adecbaa271d8a679626e9beb9a4045c01015576d"}, @@ -1429,6 +1481,8 @@ version = "6.29.5" description = "IPython Kernel for Jupyter" optional = false python-versions = ">=3.8" +groups = ["main", "test"] +markers = "python_version <= \"3.11\"" files = [ {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, @@ -1511,7 +1565,9 @@ name = "ipython" version = "8.18.1" description = "IPython: Productive Interactive Computing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main", "test"] +markers = "python_version == \"3.9\"" files = [ {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, @@ -1623,6 +1679,8 @@ version = "0.2.0" description = "Vestigial utilities from IPython" optional = false python-versions = "*" +groups = ["main"] +markers = "python_version <= \"3.11\"" files = [ {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, @@ -1649,7 +1707,8 @@ name = "ipywidgets" version = "8.1.7" description = "Jupyter interactive widgets" optional = false -python-versions = "*" +python-versions = ">=3.7" +groups = ["main"] files = [ {file = "ipywidgets-8.1.7-py3-none-any.whl", hash = "sha256:764f2602d25471c213919b8a1997df04bef869251db4ca8efba1b76b1bd9f7bb"}, {file = "ipywidgets-8.1.7.tar.gz", hash = "sha256:15f1ac050b9ccbefd45dccfbb2ef6bed0029d8278682d569d71b8dd96bee0376"}, @@ -1663,7 +1722,7 @@ traitlets = ">=4.3.1" widgetsnbextension = ">=4.0.14,<4.1.0" [package.extras] -test = ["mock", "pytest (>=3.6.0)", "pytest-cov"] +test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] [[package]] name = "isoduration" @@ -1723,7 +1782,8 @@ name = "json5" version = "0.12.0" description = "A Python implementation of the JSON5 data format." optional = false -python-versions = "*" +python-versions = ">=3.8.0" +groups = ["main"] files = [ {file = "json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db"}, {file = "json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a"}, @@ -1749,7 +1809,8 @@ name = "jsonrpclib-pelix" version = "0.4.3.4" description = "JSON-RPC v2.0 client and server for Python 2.7 and Python 3.6+ usable with Pelix/iPOPO remote services." optional = false -python-versions = "*" +python-versions = ">=3.6" +groups = ["main"] files = [ {file = "jsonrpclib_pelix-0.4.3.4-py3-none-any.whl", hash = "sha256:56da1fd8fdfe040811cd6f0b76a1b8f40e4144ac03a2a9d03dfeefadda76f71c"}, {file = "jsonrpclib_pelix-0.4.3.4.tar.gz", hash = "sha256:e82d6f4da907a7d111ef93fd2361c8c20b79d248be4fe99678e08626aa8fcbef"}, @@ -1760,7 +1821,8 @@ name = "jsonschema" version = "4.25.0" description = "An implementation of JSON Schema validation for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "lint", "test"] files = [ {file = "jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716"}, {file = "jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f"}, @@ -1790,7 +1852,8 @@ name = "jsonschema-specifications" version = "2025.4.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "lint", "test"] files = [ {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"}, {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"}, @@ -1831,6 +1894,8 @@ version = "8.6.3" description = "Jupyter protocol implementation and client libraries" optional = false python-versions = ">=3.8" +groups = ["main", "test"] +markers = "python_version == \"3.12\"" files = [ {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, @@ -1873,7 +1938,8 @@ name = "jupyter-events" version = "0.12.0" description = "Jupyter Event System library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ {file = "jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb"}, {file = "jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b"}, @@ -1915,7 +1981,8 @@ name = "jupyter-server" version = "2.16.0" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ {file = "jupyter_server-2.16.0-py3-none-any.whl", hash = "sha256:3d8db5be3bc64403b1c65b400a1d7f4647a5ce743f3b20dbdefe8ddb7b55af9e"}, {file = "jupyter_server-2.16.0.tar.gz", hash = "sha256:65d4b44fdf2dcbbdfe0aa1ace4a842d4aaf746a2b7b168134d5aaed35621b7f6"}, @@ -2065,7 +2132,9 @@ name = "jupyterlab" version = "4.4.5" description = "JupyterLab computational environment" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.12\"" files = [ {file = "jupyterlab-4.4.5-py3-none-any.whl", hash = "sha256:e76244cceb2d1fb4a99341f3edc866f2a13a9e14c50368d730d75d8017be0863"}, {file = "jupyterlab-4.4.5.tar.gz", hash = "sha256:0bd6c18e6a3c3d91388af6540afa3d0bb0b2e76287a7b88ddf20ab41b336e595"}, @@ -2098,7 +2167,8 @@ name = "jupyterlab-pygments" version = "0.3.0" description = "Pygments theme using JupyterLab CSS variables" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"}, {file = "jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d"}, @@ -2136,7 +2206,8 @@ name = "jupyterlab-widgets" version = "3.0.15" description = "Jupyter interactive widgets for JupyterLab" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jupyterlab_widgets-3.0.15-py3-none-any.whl", hash = "sha256:d59023d7d7ef71400d51e6fee9a88867f6e65e10a4201605d2d7f3e8f012a31c"}, {file = "jupyterlab_widgets-3.0.15.tar.gz", hash = "sha256:2920888a0c2922351a9202817957a68c07d99673504d6cd37345299e971bb08b"}, @@ -2147,7 +2218,9 @@ name = "kiwisolver" version = "1.4.7" description = "A fast implementation of the Cassowary constraint solver" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version == \"3.9\"" files = [ {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, @@ -2379,7 +2452,8 @@ name = "latexcodec" version = "3.0.1" description = "A lexer and codec to work with LaTeX code in Python." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.9" +groups = ["doc"] files = [ {file = "latexcodec-3.0.1-py3-none-any.whl", hash = "sha256:a9eb8200bff693f0437a69581f7579eb6bca25c4193515c09900ce76451e452e"}, {file = "latexcodec-3.0.1.tar.gz", hash = "sha256:e78a6911cd72f9dec35031c6ec23584de6842bfbc4610a9678868d14cdfb0357"}, @@ -2390,7 +2464,8 @@ name = "lxml" version = "5.4.0" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +python-versions = ">=3.6" +groups = ["main"] files = [ {file = "lxml-5.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e7bc6df34d42322c5289e37e9971d6ed114e3776b45fa879f734bded9d1fea9c"}, {file = "lxml-5.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6854f8bd8a1536f8a1d9a3655e6354faa6406621cf857dc27b681b69860645c7"}, @@ -2563,7 +2638,8 @@ name = "markupsafe" version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" +groups = ["main", "doc"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -2634,6 +2710,8 @@ version = "3.9.4" description = "Python plotting package" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version == \"3.9\"" files = [ {file = "matplotlib-3.9.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fdd7abfb706dfa8d307af64a87f1a862879ec3cd8d0ec8637458f0885b9c50"}, {file = "matplotlib-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d89bc4e85e40a71d1477780366c27fb7c6494d293e1617788986f74e2a03d7ff"}, @@ -2778,7 +2856,8 @@ name = "matplotlib-inline" version = "0.1.7" description = "Inline Matplotlib backend for Jupyter" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, @@ -2804,7 +2883,8 @@ name = "mistune" version = "3.1.3" description = "A sane and fast Markdown parser with useful plugins and renderers" optional = false -python-versions = "*" +python-versions = ">=3.8" +groups = ["main"] files = [ {file = "mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9"}, {file = "mistune-3.1.3.tar.gz", hash = "sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0"}, @@ -2818,7 +2898,9 @@ name = "mpi4py" version = "4.1.0" description = "Python bindings for MPI" optional = true -python-versions = ">=3.6" +python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"mpi\"" files = [ {file = "mpi4py-4.1.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9fd9c94bd9f963e50a884382ce13694f0296f1843760b047c9f64b3f11688330"}, {file = "mpi4py-4.1.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:8dafd7777766a485502c55941bef543ba3a451e943fb38c98d4f9f5cded9ba89"}, @@ -2903,7 +2985,9 @@ name = "nbclassic" version = "1.3.1" description = "Jupyter Notebook as a Jupyter Server extension." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\"" files = [ {file = "nbclassic-1.3.1-py3-none-any.whl", hash = "sha256:96da3b4d7f877b1285e0adc956ea2ea9ea9f70a4ba7b7c03d558f6c9799118fa"}, {file = "nbclassic-1.3.1.tar.gz", hash = "sha256:4c52da8fc88f9f73ef512cc305091d5ce726bdca19f44ed697cb5ba12dcaad3c"}, @@ -2926,7 +3010,8 @@ name = "nbclient" version = "0.10.2" description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.9.0" +groups = ["main"] files = [ {file = "nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d"}, {file = "nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193"}, @@ -2948,7 +3033,8 @@ name = "nbconvert" version = "7.16.6" description = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main"] files = [ {file = "nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b"}, {file = "nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582"}, @@ -2985,7 +3071,8 @@ name = "nbformat" version = "5.10.4" description = "The Jupyter Notebook format" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main", "lint", "test"] files = [ {file = "nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b"}, {file = "nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a"}, @@ -3052,7 +3139,9 @@ name = "networkx" version = "3.2.1" description = "Python package for creating and manipulating graphs and networks" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.9\"" files = [ {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"}, {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"}, @@ -3113,7 +3202,8 @@ name = "nodeenv" version = "1.9.1" description = "Node.js virtual environment builder" optional = false -python-versions = "*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["lint"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -3124,7 +3214,9 @@ name = "notebook" version = "6.5.7" description = "A web-based notebook environment for interactive computing" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\"" files = [ {file = "notebook-6.5.7-py3-none-any.whl", hash = "sha256:a6afa9a4ff4d149a0771ff8b8c881a7a73b3835f9add0606696d6e9d98ac1cd0"}, {file = "notebook-6.5.7.tar.gz", hash = "sha256:04eb9011dfac634fbd4442adaf0a8c27cd26beef831fe1d19faf930c327768e4"}, @@ -3149,7 +3241,9 @@ tornado = ">=6.1" traitlets = ">=4.2.1" [package.extras] -test = ["coverage", "mock", "nbval", "nose", "nose-exclude", "nose-exclude", "nose-warnings-filters", "pytest", "pytest-cov", "requests", "selenium"] +docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] +json-logging = ["json-logging"] +test = ["coverage", "nbval", "pytest", "pytest-cov", "requests", "requests-unixsocket ; sys_platform != \"win32\"", "selenium (==4.1.5)", "testpath"] [[package]] name = "notebook-shim" @@ -3175,6 +3269,8 @@ version = "2.0.2" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] +markers = "python_version == \"3.9\"" files = [ {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"}, {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"}, @@ -3407,7 +3503,8 @@ name = "openmdao-extensions" version = "1.3.2" description = "Additional solvers and drivers for OpenMDAO framework" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main"] files = [ {file = "openmdao_extensions-1.3.2.tar.gz", hash = "sha256:16d5f413313b40ac0cc6696f9ff16084b2ce15365d0f13f891fabb44a1ad3b02"}, ] @@ -3563,6 +3660,8 @@ version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" +groups = ["main", "test"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\" or python_version == \"3.9\" and sys_platform != \"win32\"" files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, @@ -3576,7 +3675,8 @@ name = "pillow" version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false -python-versions = "*" +python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, @@ -3701,25 +3801,22 @@ version = "1.12.1.2" description = "Query metadata from sdists / bdists / installed packages." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pkginfo-1.12.1.2-py3-none-any.whl", hash = "sha256:c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343"}, {file = "pkginfo-1.12.1.2.tar.gz", hash = "sha256:5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] -fpx = ["olefile"] -mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] -xmp = ["defusedxml"] +testing = ["pytest", "pytest-cov", "wheel"] [[package]] name = "platformdirs" version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "lint", "test"] files = [ {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, @@ -3759,7 +3856,8 @@ name = "pluggy" version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["test"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -3774,7 +3872,8 @@ name = "pre-commit" version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["lint"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -3792,7 +3891,8 @@ name = "prometheus-client" version = "0.22.1" description = "Python client for the Prometheus monitoring system." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.9" +groups = ["main"] files = [ {file = "prometheus_client-0.22.1-py3-none-any.whl", hash = "sha256:cca895342e308174341b2cbf99a56bef291fbc0ef7b9e5412a0f26d653ba7094"}, {file = "prometheus_client-0.22.1.tar.gz", hash = "sha256:190f1331e783cf21eb60bca559354e0a4d4378facecf78f5428c39b675d20d28"}, @@ -3806,7 +3906,8 @@ name = "prompt-toolkit" version = "3.0.51" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, @@ -3820,7 +3921,8 @@ name = "psutil" version = "7.0.0" description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.6" +groups = ["main", "test"] files = [ {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, @@ -3835,7 +3937,92 @@ files = [ ] [package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] + +[[package]] +name = "psygnal" +version = "0.14.2" +description = "Fast python callback/event system modeled after Qt Signals" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.9\"" +files = [ + {file = "psygnal-0.14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:84dc83a3d213bf3cef25501b65f13d59ddb793ad59f74006f7e8ffb64a8838dc"}, + {file = "psygnal-0.14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:689933fb364a8885f3b39312f8e0cae698fe5f0bb6911c5c393bdf44a42186aa"}, + {file = "psygnal-0.14.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c85599693955f9bbe2ef635bef823be19fa02e9ac047f32e4ce80e9a270b32dd"}, + {file = "psygnal-0.14.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f2938f6feea3c212bcd47723319299128bedcf02290ec892d281ff6d6760aae"}, + {file = "psygnal-0.14.2-cp310-cp310-win_amd64.whl", hash = "sha256:bfc00d7dd3e7c84005d7af972794ed2f7e683e2c9666a1d2796ba144a315d968"}, + {file = "psygnal-0.14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9fc04ad56b40009d8d0a8c89023fe93a1d39496e88f6eff45fb433aef0283c9c"}, + {file = "psygnal-0.14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2e1febc998fe49cfe81a0d900c1f92cb7fd9c6df0a43cb89e48493e838630e0"}, + {file = "psygnal-0.14.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8968646fa75cf44f8e10d07434928eb7ae63df66398a4cab55ec37ee70165589"}, + {file = "psygnal-0.14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ab578834bd0f872a9d6b01c5efc6b4c77068b9ae9d8f3f360677885af31e67a"}, + {file = "psygnal-0.14.2-cp311-cp311-win_amd64.whl", hash = "sha256:74a05ddc75bf8a6d25f4bfcdc04de4fc5bf4efd2983c3962db723dc83f46d52a"}, + {file = "psygnal-0.14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d05c0b75dac72aeb7b548ffad2d23847e77c97fb50dac8bab7ba407f65fe0111"}, + {file = "psygnal-0.14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:407c8466f0e4a7a55bec3c62f09f547143b99a3386376e838b8438bbaca76c60"}, + {file = "psygnal-0.14.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a0edaaa7b0a2e6717b9e66e6ff0c53043f61d8603dae5aba8e4df3f39a42b5a"}, + {file = "psygnal-0.14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6fa8f7aa1ba19027881c34e1603d808d81c2aeab21c2b74ea6141cb6ed7cef95"}, + {file = "psygnal-0.14.2-cp312-cp312-win_amd64.whl", hash = "sha256:1045c7662e4d4d9a496b47956ecf2ee542aaefc283244aece241a23c6b716e7e"}, + {file = "psygnal-0.14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bd67b1e9ed8cd1210b851baa41c0e1582b89940c04b50c93cd1db18f6f1d8215"}, + {file = "psygnal-0.14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:35aeb647004031951f25a6ec457628b3a7300de6927bec6875f445c373d5974c"}, + {file = "psygnal-0.14.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a27369767302b1202e34dce03c09809d0204a7b2986d8ba9cc7711be739e7e64"}, + {file = "psygnal-0.14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4524e67785f8d7fa276a2ed4a24aa4072f7aace3c6c53546bca8a2ab0241156a"}, + {file = "psygnal-0.14.2-cp313-cp313-win_amd64.whl", hash = "sha256:c03ebf844a5b448875340473e41b5a58e52810e4da9e4bf6d12f18bb8d24b13f"}, + {file = "psygnal-0.14.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ce63479937c437ba4235ac2813346312e9649847cc89baa691efa4c21a2ae743"}, + {file = "psygnal-0.14.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d5deb444641acf67e0ee53f2881e0fb333c63702f7dad4ce757e38f0f26fa907"}, + {file = "psygnal-0.14.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d572e273d6277e00f6eff6cdd3c477103451517738c02fc3546e01f73f5726f"}, + {file = "psygnal-0.14.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:82194598725fca42e5b4f55efa9929f03993d9f13c3b4b61bcb9e543e137f9dd"}, + {file = "psygnal-0.14.2-cp39-cp39-win_amd64.whl", hash = "sha256:a2123965f03b46f5f79fbe047fb6fa3585b74203fafa1969b881951d14997b89"}, + {file = "psygnal-0.14.2-py3-none-any.whl", hash = "sha256:6caa7b1ebab0fcfd9e196cf5269b3be2bb4ee7776a11d60fb6fdf7263143e327"}, + {file = "psygnal-0.14.2.tar.gz", hash = "sha256:588d1a7a0212db8ffc720ef2fb03e849e0280f4f156e5f5922e6b99b13c69689"}, +] + +[package.extras] +proxy = ["wrapt"] +pydantic = ["pydantic"] + +[[package]] +name = "psygnal" +version = "0.15.0" +description = "Fast python callback/event system modeled after Qt Signals" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "psygnal-0.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c33a022d2bdfa68c71f6fe964fb316b8cff36a936a6075bb14378823b5bd28d"}, + {file = "psygnal-0.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:27367d0b47866c6d9c47a19ae9c9570c1525f729314b1d864a7d6e052688645e"}, + {file = "psygnal-0.15.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bafa232672ae1d0f51873629c38aeed85476b6620803e8daa14edf20716054c"}, + {file = "psygnal-0.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:45234b9f6f6a793c3df2867f86c5b5223731eda7734768148175268042c6b7b8"}, + {file = "psygnal-0.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fcade907d3385eb3bc97617f51f275dfc5db45f601cc8ef5c2d17b2f9db1d0d"}, + {file = "psygnal-0.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d83239961c66f0763c26df121d8028eeb1cdebc3ce2d511836b3424dda591f3"}, + {file = "psygnal-0.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:219550f78512cd274ee11966033843426a85ee333fbfed73d0f7ce1b153c547c"}, + {file = "psygnal-0.15.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c29149a5042d79cb9dfb4d7b6b8c624296681b1533d58b7820c0817ffdd81c4"}, + {file = "psygnal-0.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d4c9762102df30530044c5a44cc591240ff3b89bd67292e10c0b73cd694c84e9"}, + {file = "psygnal-0.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f50938b3caf07e34ab044c19d4e9280a53ff65492c285ff211285f0a08934c1"}, + {file = "psygnal-0.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:82eb5767f6cba67fa2d034dab9ec94e8eaf465067666dea3e2f832f2c32debc3"}, + {file = "psygnal-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5dbcc67b2282eebe2e4e55ff9b50dad6b811d4ab698c573a61a725a6296919ba"}, + {file = "psygnal-0.15.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0d65e2686c19997eb4495974abc972ca1661504e73b8b58b1fb8466baf0c7ae"}, + {file = "psygnal-0.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed3ff192cdd14956c2f7a0be4635fa72b2eb2773dfc58a6aa8c14926647041f2"}, + {file = "psygnal-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ed1fd5797df111c9f9b43a1dc01ffb7c76e19ddc9b0de969e0b816034345246"}, + {file = "psygnal-0.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eb11ecb42b4ff9e45d661396399029c41fbd1cfdd5dbd5c31a3f6f52c8fc2b90"}, + {file = "psygnal-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eace624bb6aa7ad42d1c047a2e3a531f68b3bfc63d8b4c3de9dec4cc122bb534"}, + {file = "psygnal-0.15.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0172efeb861280bca05673989a4df21624f44344eff20b873d8c9d0edc01350"}, + {file = "psygnal-0.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c284edb17542dad0114ad2a942799d6526fa72be7d76d078a388469d584d034c"}, + {file = "psygnal-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:c60d36d46c992835608030ff3fa918c06c7f22133391d90500585fef726f5d07"}, + {file = "psygnal-0.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f0125639597d42b8d78fcd61cc306d7ae71a198d8fac83ab64a07742e8bb1ca8"}, + {file = "psygnal-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d3e759e84c9396f4b1f30bf4b5efd83c5fd359745a72df44b639aa0e5e94c51d"}, + {file = "psygnal-0.15.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:876e2f8b22236c0327e3da75a17e40a550d89efed904c1e9db23acdd4a66504d"}, + {file = "psygnal-0.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5108268d08ac176ac6f8a0cad2c76883282d75a14663f806fdf207eb53e38014"}, + {file = "psygnal-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:6034cacebd252776743450be62f25df323f8cb4ed7b01a46fc4dcf540baa64a6"}, + {file = "psygnal-0.15.0-py3-none-any.whl", hash = "sha256:023c361c38e8ada87d0704704e1f2b7e799e9771e00b8e174fb409ff9ddeb502"}, + {file = "psygnal-0.15.0.tar.gz", hash = "sha256:5534f18e2d1536675e181c6f81cf04f4177b25a9e60fdcf724a25ce5cc195765"}, +] + +[package.extras] +proxy = ["wrapt"] +pydantic = ["pydantic"] [[package]] name = "ptyprocess" @@ -3848,13 +4035,30 @@ files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] +markers = {main = "(os_name != \"nt\" or sys_platform != \"win32\" and sys_platform != \"emscripten\" or python_version == \"3.9\") and (os_name != \"nt\" or sys_platform != \"win32\")", test = "sys_platform != \"win32\" and sys_platform != \"emscripten\" or python_version == \"3.9\" and sys_platform != \"win32\""} + +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +groups = ["main", "test"] +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] [[package]] name = "pybtex" version = "0.25.1" description = "A BibTeX-compatible bibliography processor in Python" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" +python-versions = ">=3.6" +groups = ["doc"] files = [ {file = "pybtex-0.25.1-py2.py3-none-any.whl", hash = "sha256:9053b0d619409a0a83f38abad5d9921de5f7b3ede00742beafcd9f10ad0d8c5c"}, {file = "pybtex-0.25.1.tar.gz", hash = "sha256:9eaf90267c7e83e225af89fea65c370afbf65f458220d3946a9e3049e1eca491"}, @@ -3890,7 +4094,8 @@ name = "pycparser" version = "2.22" description = "C parser in Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -3933,7 +4138,8 @@ name = "pyparsing" version = "3.2.3" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false -python-versions = ">=3.6.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, @@ -3959,7 +4165,8 @@ name = "pytest" version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["test"] files = [ {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, @@ -4016,7 +4223,8 @@ name = "python-json-logger" version = "3.3.0" description = "JSON Log Formatter for the Python Logging Package" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7"}, {file = "python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84"}, @@ -4076,7 +4284,9 @@ name = "pywinpty" version = "2.0.15" description = "Pseudo terminal support for Windows from Python." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] +markers = "os_name == \"nt\"" files = [ {file = "pywinpty-2.0.15-cp310-cp310-win_amd64.whl", hash = "sha256:8e7f5de756a615a38b96cd86fa3cd65f901ce54ce147a3179c45907fa11b4c4e"}, {file = "pywinpty-2.0.15-cp311-cp311-win_amd64.whl", hash = "sha256:9a6bcec2df2707aaa9d08b86071970ee32c5026e10bcc3cc5f6f391d85baf7ca"}, @@ -4092,7 +4302,8 @@ name = "pyyaml" version = "6.0.2" description = "YAML parser and emitter for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +groups = ["main", "doc", "lint"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -4154,7 +4365,8 @@ name = "pyzmq" version = "27.0.1" description = "Python bindings for 0MQ" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "pyzmq-27.0.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:90a4da42aa322de8a3522461e3b5fe999935763b27f69a02fced40f4e3cf9682"}, {file = "pyzmq-27.0.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e648dca28178fc879c814cf285048dd22fd1f03e1104101106505ec0eea50a4d"}, @@ -4258,7 +4470,8 @@ name = "referencing" version = "0.36.2" description = "JSON Referencing + Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "lint", "test"] files = [ {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, @@ -4377,7 +4590,8 @@ name = "rpds-py" version = "0.26.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "lint", "test"] files = [ {file = "rpds_py-0.26.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4c70c70f9169692b36307a95f3d8c0a9fcd79f7b4a383aad5eaa0e9718b79b37"}, {file = "rpds_py-0.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:777c62479d12395bfb932944e61e915741e364c843afc3196b694db3d669fcd0"}, @@ -4530,7 +4744,8 @@ name = "ruamel-yaml" version = "0.17.40" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false -python-versions = "*" +python-versions = ">=3" +groups = ["main"] files = [ {file = "ruamel.yaml-0.17.40-py3-none-any.whl", hash = "sha256:b16b6c3816dff0a93dca12acf5e70afd089fa5acb80604afd1ffa8b465b7722c"}, {file = "ruamel.yaml-0.17.40.tar.gz", hash = "sha256:6024b986f06765d482b5b07e086cc4b4cd05dd22ddcbc758fa23d54873cf313d"}, @@ -4634,7 +4849,9 @@ name = "scipy" version = "1.13.1" description = "Fundamental algorithms for scientific computing in Python" optional = false -python-versions = "<3.13,>=3.9" +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.9\"" files = [ {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"}, {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"}, @@ -4815,7 +5032,8 @@ name = "send2trash" version = "1.8.3" description = "Send file to trash natively under Mac OS X, Windows and Linux" optional = false -python-versions = "*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main"] files = [ {file = "Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9"}, {file = "Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf"}, @@ -4831,24 +5049,30 @@ name = "setuptools" version = "80.9.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.12\"" files = [ {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, ] -markers = {doc = "python_version == \"3.12\""} [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "six" version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "dev", "test"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -4859,7 +5083,8 @@ name = "sniffio" version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" +groups = ["main"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -4870,7 +5095,8 @@ name = "snowballstemmer" version = "3.0.1" description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." optional = false -python-versions = "*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*" +groups = ["doc"] files = [ {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, @@ -4881,7 +5107,8 @@ name = "soupsieve" version = "2.7" description = "A modern CSS selector implementation for Beautiful Soup." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +groups = ["main"] files = [ {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, @@ -4892,7 +5119,9 @@ name = "sphinx" version = "7.3.7" description = "Python documentation generator" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["doc"] +markers = "python_version == \"3.9\"" files = [ {file = "sphinx-7.3.7-py3-none-any.whl", hash = "sha256:413f75440be4cacf328f580b4274ada4565fb2187d696a84970c23f77b64d8c3"}, {file = "sphinx-7.3.7.tar.gz", hash = "sha256:a4a7db75ed37531c05002d56ed6948d4c42f473a36f46e1382b0bd76ca9627bc"}, @@ -5002,7 +5231,8 @@ name = "sphinx-rtd-theme" version = "3.0.2" description = "Read the Docs theme for Sphinx" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +groups = ["doc"] files = [ {file = "sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13"}, {file = "sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85"}, @@ -5021,7 +5251,8 @@ name = "sphinxcontrib-applehelp" version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" +groups = ["doc"] files = [ {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, @@ -5037,7 +5268,8 @@ name = "sphinxcontrib-bibtex" version = "2.6.5" description = "Sphinx extension for BibTeX style citations." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["doc"] files = [ {file = "sphinxcontrib_bibtex-2.6.5-py3-none-any.whl", hash = "sha256:455ea4509642ea0b28ede3721550273626f85af65af01f161bfd8e19dc1edd7d"}, {file = "sphinxcontrib_bibtex-2.6.5.tar.gz", hash = "sha256:9b3224dd6fece9268ebd8c905dc0a83ff2f6c54148a9235fe70e9d1e9ff149c0"}, @@ -5058,7 +5290,8 @@ name = "sphinxcontrib-devhelp" version = "2.0.0" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" +groups = ["doc"] files = [ {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, @@ -5074,7 +5307,8 @@ name = "sphinxcontrib-htmlhelp" version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" +groups = ["doc"] files = [ {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, @@ -5120,7 +5354,8 @@ name = "sphinxcontrib-qthelp" version = "2.0.0" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" +groups = ["doc"] files = [ {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, @@ -5136,7 +5371,8 @@ name = "sphinxcontrib-serializinghtml" version = "2.0.0" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" +groups = ["doc"] files = [ {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, @@ -5204,26 +5440,13 @@ files = [ [package.extras] widechars = ["wcwidth"] -[[package]] -name = "tenacity" -version = "8.0.1" -description = "Retry code until it succeeds" -optional = false -python-versions = ">=3.6" -files = [ - {file = "tenacity-8.0.1-py3-none-any.whl", hash = "sha256:f78f4ea81b0fabc06728c11dc2a8c01277bfc5181b321a4770471902e3eb844a"}, - {file = "tenacity-8.0.1.tar.gz", hash = "sha256:43242a20e3e73291a28bcbcacfd6e000b02d3857a9a9fff56b297a27afdc932f"}, -] - -[package.extras] -doc = ["reno", "sphinx", "tornado (>=4.5)"] - [[package]] name = "terminado" version = "0.18.1" description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +groups = ["main"] files = [ {file = "terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0"}, {file = "terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e"}, @@ -5244,7 +5467,8 @@ name = "tinycss2" version = "1.4.0" description = "A tiny CSS parser" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +groups = ["main"] files = [ {file = "tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289"}, {file = "tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7"}, @@ -5262,7 +5486,8 @@ name = "tomli" version = "2.2.1" description = "A lil' TOML parser" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +groups = ["main", "doc", "test"] files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -5297,13 +5522,15 @@ files = [ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] +markers = {doc = "python_version < \"3.11\"", test = "python_version < \"3.11\""} [[package]] name = "tomli-w" version = "1.2.0" description = "A lil' TOML writer" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] files = [ {file = "tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90"}, {file = "tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021"}, @@ -5314,7 +5541,8 @@ name = "tomlkit" version = "0.13.3" description = "Style preserving TOML library" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" +groups = ["main"] files = [ {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, @@ -5325,7 +5553,8 @@ name = "tornado" version = "6.5.1" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "test"] files = [ {file = "tornado-6.5.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d50065ba7fd11d3bd41bcad0825227cc9a95154bad83239357094c36708001f7"}, {file = "tornado-6.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9ca370f717997cb85606d074b0e5b247282cf5e2e1611568b8821afe0342d6"}, @@ -5362,7 +5591,8 @@ name = "types-python-dateutil" version = "2.9.0.20250708" description = "Typing stubs for python-dateutil" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ {file = "types_python_dateutil-2.9.0.20250708-py3-none-any.whl", hash = "sha256:4d6d0cc1cc4d24a2dc3816024e502564094497b713f7befda4d5bc7a8e3fd21f"}, {file = "types_python_dateutil-2.9.0.20250708.tar.gz", hash = "sha256:ccdbd75dab2d6c9696c350579f34cffe2c281e4c5f27a585b2a2438dd1d5c8ab"}, @@ -5373,7 +5603,8 @@ name = "typing-extensions" version = "4.14.1" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "lint", "test"] files = [ {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, @@ -5411,23 +5642,26 @@ name = "urllib3" version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=3.9" +groups = ["main", "doc"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" version = "20.33.0" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["lint"] files = [ {file = "virtualenv-20.33.0-py3-none-any.whl", hash = "sha256:106b6baa8ab1b526d5a9b71165c85c456fbd49b16976c88e2bc9352ee3bc5d3f"}, {file = "virtualenv-20.33.0.tar.gz", hash = "sha256:47e0c0d2ef1801fce721708ccdf2a28b9403fa2307c3268aebd03225976f61d2"}, @@ -5440,7 +5674,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "wcwidth" @@ -5459,7 +5693,8 @@ name = "webcolors" version = "24.11.1" description = "A library for working with the color formats defined by HTML and CSS." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ {file = "webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9"}, {file = "webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6"}, @@ -5482,7 +5717,8 @@ name = "websocket-client" version = "1.8.0" description = "WebSocket client for Python with low level API options" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +groups = ["main"] files = [ {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, @@ -5498,7 +5734,8 @@ name = "widgetsnbextension" version = "4.0.14" description = "Jupyter interactive widgets for Jupyter Notebook" optional = false -python-versions = "*" +python-versions = ">=3.7" +groups = ["main"] files = [ {file = "widgetsnbextension-4.0.14-py3-none-any.whl", hash = "sha256:4875a9eaf72fbf5079dc372a51a9f268fc38d46f767cbf85c43a36da5cb9b575"}, {file = "widgetsnbextension-4.0.14.tar.gz", hash = "sha256:a3629b04e3edb893212df862038c7232f62973373869db5084aed739b437b5af"}, @@ -5536,7 +5773,8 @@ name = "wrapt" version = "1.17.2" description = "Module for decorators, wrappers and monkey patching." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, @@ -5741,7 +5979,8 @@ name = "zipp" version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" +groups = ["main", "dev", "doc"] files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, @@ -5749,13 +5988,17 @@ files = [ markers = {main = "python_full_version < \"3.10.2\"", dev = "python_version == \"3.9\"", doc = "python_version == \"3.9\""} [package.extras] -docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] -testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] [extras] mpi = ["mpi4py"] [metadata] -lock-version = "2.0" -python-versions = ">=3.9, <3.13" -content-hash = "750707d457f19765abd61379210abf95b14e36ab42bfc0f1e75248ca9f209e38" +lock-version = "2.1" +python-versions = ">=3.9,<3.13" +content-hash = "3d209bdaafaad601f3ae2438abb0c2753223116b367f617808fcebad14fd8c20" diff --git a/src/fastoad/gui/analysis_and_plots.py b/src/fastoad/gui/analysis_and_plots.py index 2d1b137fd..eedee2d28 100644 --- a/src/fastoad/gui/analysis_and_plots.py +++ b/src/fastoad/gui/analysis_and_plots.py @@ -606,15 +606,15 @@ def payload_range_plot( x=x, y=y, z=z, - contours=dict(start=min_z, end=max_z, size=(max_z - min_z) / 20), - colorbar=dict( - title=dict( - text=f"{variable_of_interest_legend} [{variable_of_interest_unit}]", - side="right", - font=dict(size=15, family="Arial, sans-serif"), - ), - tickformat=".1e", - ), + contours={"start": min_z, "end": max_z, "size": (max_z - min_z) / 20}, + colorbar={ + "title": { + "text": f"{variable_of_interest_legend} [{variable_of_interest_unit}]", + "side": "right", + "font": {"size": 15, "family": "Arial, sans-serif"}, + }, + "tickformat": ".1e", + }, colorscale="RdBu_r", contours_coloring="heatmap", ) diff --git a/src/fastoad/module_management/_bundle_loader.py b/src/fastoad/module_management/_bundle_loader.py index 11816a42c..a936dc3d3 100644 --- a/src/fastoad/module_management/_bundle_loader.py +++ b/src/fastoad/module_management/_bundle_loader.py @@ -386,7 +386,7 @@ def _get_service_references( return self.framework.find_service_references(service_name, ldap_filter) # references - def _install_python_package(self, package_name: str) -> Tuple[Set[Bundle], dict[str, str]]: + def _install_python_package(self, package_name: str) -> tuple[set[Bundle], dict[str, str]]: """ Recursively loads indicated package and its submodules/subpackages. @@ -413,33 +413,32 @@ def _install_python_package(self, package_name: str) -> Tuple[Set[Bundle], dict[ self._styled_rule(f"[bold red]ERROR: {package_name}[/bold red]") return bundles, failed # Abort recursion early for broken package - elif package.is_package: - # It is a package, let's explore it. - header_printed = False # Ensure the error header is printed only once per package - for item in package.contents: - # Get the bundle name and path - item_package = f"{package_name}.{item}" # Qualified name - item_path = f"{root_package_path}/{item}" # Full path to the file or folder - - if "." in item: - # A file. Considered only if it is a Python file. Ignored otherwise. - if item.endswith(".py"): - try: - bundle = self.context.install_bundle(item_package[:-3]) # Remove .py - bundles.add(bundle) - except BundleException as e: - failed[item_package[:-3]] = item_path - if not header_printed: - _LOGGER.warning("Failed to load package: %s", package_name) - self._styled_rule(f"[bold red]ERROR: {package_name}[/bold red]") - header_printed = True - _LOGGER.warning("%s\nDetailed traceback:", e, exc_info=True) - self._styled_rule(first_newline=False) - else: - # It's a subpackage. Recurse. - sub_bundles, sub_failed = self._install_python_package(item_package) - bundles.update(sub_bundles) - failed.update(sub_failed) + # It is a package, let's explore it. + header_printed = False # Ensure the error header is printed only once per package + for item in package.contents: + # Get the bundle name and path + item_package = f"{package_name}.{item}" # Qualified name + item_path = f"{root_package_path}/{item}" # Full path to the file or folder + + if "." in item: + # A file. Considered only if it is a Python file. Ignored otherwise. + if item.endswith(".py"): + try: + bundle = self.context.install_bundle(item_package[:-3]) # Remove .py + bundles.add(bundle) + except BundleException as e: + failed[item_package[:-3]] = item_path + if not header_printed: + _LOGGER.warning("Failed to load package: %s", package_name) + self._styled_rule(f"[bold red]ERROR: {package_name}[/bold red]") + header_printed = True + _LOGGER.warning("%s\nDetailed traceback:", e, exc_info=True) + self._styled_rule(first_newline=False) + else: + # It's a subpackage. Recurse. + sub_bundles, sub_failed = self._install_python_package(item_package) + bundles.update(sub_bundles) + failed.update(sub_failed) return bundles, failed @@ -464,7 +463,7 @@ def _log_failed_modules(failed_modules: list[dict[str, str]]): _CONSOLE.print(table) @staticmethod - def _styled_rule(title: str = "", first_newline=True): + def _styled_rule(title: str = "", *, first_newline=True): if first_newline: _CONSOLE.print() _CONSOLE.rule(title, style="bright_black") diff --git a/src/fastoad/openmdao/tests/test_variables.py b/src/fastoad/openmdao/tests/test_variables.py index 5664c02a9..2886589d0 100644 --- a/src/fastoad/openmdao/tests/test_variables.py +++ b/src/fastoad/openmdao/tests/test_variables.py @@ -128,7 +128,10 @@ def test_variables(with_dummy_plugin_2): def test_variable_update_missing_metadata(): - """Test that Variable.update_missing_metadata only adds missing metadata keys without modifying existing ones.""" + """ + Test that Variable.update_missing_metadata only adds missing metadata keys without modifying + existing ones. + """ # Create a source variable with several metadata items source_var = Variable( "source_var", @@ -164,9 +167,9 @@ def test_variable_update_missing_metadata(): assert original_var.metadata["upper"] == original_upper, "Upper bound should not change" assert original_var.metadata["ref"] == original_ref, "Ref should not change" assert "units" in original_var.metadata, "units should be added" - assert ( - original_var.metadata["units"] == source_var.metadata["units"] - ), "Units should match source" + assert original_var.metadata["units"] == source_var.metadata["units"], ( + "Units should match source" + ) # Create a variable with no metadata to update from empty_var = Variable("empty_var", val=5.0, init_metadata=False) diff --git a/src/fastoad/openmdao/variables/variable.py b/src/fastoad/openmdao/variables/variable.py index 6df654eca..82fc83baa 100644 --- a/src/fastoad/openmdao/variables/variable.py +++ b/src/fastoad/openmdao/variables/variable.py @@ -163,7 +163,9 @@ def get_val(self, new_units: str | None = None) -> float | np.ndarray: def update_missing_metadata(self, source_variable: Variable): """ - Add metadata from source_variable to this variable, but only for keys that don't already exist. + Add metadata from source_variable to this variable, but only for keys that don't already + exist. + This is used to fill in missing metadata while preserving existing values. :param source_variable: Source for additional metadata @@ -354,12 +356,11 @@ def get_openmdao_kwargs(self, keys: Iterable | None = None) -> dict: def _set_default_shape(self): """Automatically sets shape if not set""" - if "shape" in self.metadata.keys(): - if self.metadata["shape"] is None: - shape = np.shape(self.value) - if not shape: - shape = (1,) - self.metadata["shape"] = shape + if "shape" in self.metadata and self.metadata["shape"] is None: + shape = np.shape(self.value) + if not shape: + shape = (1,) + self.metadata["shape"] = shape def __eq__(self, other): # same arrays with nan are declared non equals, so we need a workaround From 35434a5f86e2b2fb9c01f856c280a7a6546674bf Mon Sep 17 00:00:00 2001 From: enricostragiotti <44843509+enricostragiotti@users.noreply.github.com> Date: Tue, 18 Nov 2025 10:57:43 +0100 Subject: [PATCH 20/20] linting master before merge --- poetry.lock | 2 +- .../mission_builder/input_definition.py | 7 ++++++- tests/memory_tests/test_multiple_runs.py | 21 ++++++++----------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/poetry.lock b/poetry.lock index 24ab406ad..5047260e0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6002,4 +6002,4 @@ mpi = ["mpi4py"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<3.14" -content-hash = "bfc8ac6023b67c2b0a05b79eb2deb031f539048ebfc9941f4093a95a9b31161f" +content-hash = "f1e8d9894e2111c577f8eb4376653809818dc95686bf09254c3a7772a7fc5c3f" diff --git a/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py b/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py index 11137c1d9..ee8dd0ac8 100644 --- a/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py +++ b/src/fastoad/models/performances/mission/mission_definition/mission_builder/input_definition.py @@ -39,6 +39,11 @@ class InputDefinition: - provides information for OpenMDAO declaration """ + # We override __eq__, so we must explicitly disable hashing. + # This class is mutable (values change after initialization), + # and hashable mutable objects break dict/set behavior. + __hash__ = None + #: The parameter this input is defined for. parameter_name: str @@ -238,7 +243,7 @@ def get_input_definition(self) -> Variable | None: def __str__(self): return str(self.value) - def __eq__(self, other: "InputDefinition") -> bool: + def __eq__(self, other: InputDefinition) -> bool: return np.all( [ self.parameter_name == other.parameter_name, diff --git a/tests/memory_tests/test_multiple_runs.py b/tests/memory_tests/test_multiple_runs.py index 2d11e81cf..743cc09a7 100644 --- a/tests/memory_tests/test_multiple_runs.py +++ b/tests/memory_tests/test_multiple_runs.py @@ -15,17 +15,14 @@ import tracemalloc from pathlib import Path from shutil import rmtree -from typing import List, Tuple import openmdao.api as om import pytest import fastoad.api as oad -DATA_FOLDER_PATH = pth.join(pth.dirname(__file__), "data") -RESULTS_FOLDER_PATH = pth.join( - pth.dirname(__file__), "results", pth.splitext(pth.basename(__file__))[0] -) +DATA_FOLDER_PATH = Path(__file__).parent / "data" +RESULTS_FOLDER_PATH = Path(__file__).parent / "results" / Path(__file__).stem # Memory leak threshold in MiB MEMORY_DIFF_THRESHOLD = 20.0 @@ -64,9 +61,9 @@ def get_top_memory_stats( def run_problem() -> None: """Run a single FASTOAD problem instance""" - configurator = oad.FASTOADProblemConfigurator(pth.join(DATA_FOLDER_PATH, "oad_process.yml")) - configurator.input_file_path = pth.join(RESULTS_FOLDER_PATH, "inputs.xml") - configurator.output_file_path = pth.join(RESULTS_FOLDER_PATH, "outputs.xml") + configurator = oad.FASTOADProblemConfigurator(DATA_FOLDER_PATH / "oad_process.yml") + configurator.input_file_path = RESULTS_FOLDER_PATH / "inputs.xml" + configurator.output_file_path = RESULTS_FOLDER_PATH / "outputs.xml" print_memory_state("After reading configuration file") @@ -103,7 +100,7 @@ def test_memory_leak_between_runs(cleanup): print() baseline_memory = print_memory_state("Baseline") - memory_measurements: List[Tuple[int, float]] = [] + memory_measurements: list[tuple[int, float]] = [] run_snapshots = [] run_count = 2 @@ -190,9 +187,9 @@ def test_memory_leak_between_runs(cleanup): ) # Assert that final memory is reasonable - assert ( - final_memory < FINAL_MEMORY_THRESHOLD - ), f"Final memory usage too high: {final_memory:.3f} MiB > {FINAL_MEMORY_THRESHOLD} MiB" + assert final_memory < FINAL_MEMORY_THRESHOLD, ( + f"Final memory usage too high: {final_memory:.3f} MiB > {FINAL_MEMORY_THRESHOLD} MiB" + ) finally: tracemalloc.stop()