From 7334edbc9dc0d2677312de80bad20850f1b035ca Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Thu, 26 Jun 2025 13:02:31 -0700 Subject: [PATCH 01/10] Add type annotations --- .github/workflows/mypy.yaml | 49 ++ mypy-baseline.txt | 551 ++++++++++++++++++ pyproject.toml | 27 + src/rez/__init__.py | 4 +- src/rez/bind/_pymodule.py | 6 +- src/rez/bind/_utils.py | 4 +- src/rez/bind/cmake.py | 6 +- src/rez/bind/gcc.py | 6 +- src/rez/bind/hello_world.py | 6 +- src/rez/bind/python.py | 8 +- src/rez/bind/rez.py | 4 +- src/rez/bind/rezgui.py | 8 +- src/rez/build_process.py | 71 ++- src/rez/build_system.py | 88 ++- src/rez/bundle_context.py | 28 +- src/rez/cli/_complete_util.py | 8 +- src/rez/cli/_entry_points.py | 2 +- src/rez/cli/_main.py | 10 +- src/rez/cli/_util.py | 21 +- src/rez/cli/benchmark.py | 24 +- src/rez/cli/bind.py | 6 +- src/rez/cli/build.py | 16 +- src/rez/cli/bundle.py | 6 +- src/rez/cli/complete.py | 12 +- src/rez/cli/config.py | 4 +- src/rez/cli/context.py | 6 +- src/rez/cli/cp.py | 5 +- src/rez/cli/depends.py | 7 +- src/rez/cli/diff.py | 5 +- src/rez/cli/env.py | 5 +- src/rez/cli/forward.py | 4 +- src/rez/cli/gui.py | 5 +- src/rez/cli/help.py | 2 +- src/rez/cli/interpret.py | 5 +- src/rez/cli/memcache.py | 9 +- src/rez/cli/mv.py | 7 +- src/rez/cli/pip.py | 6 +- src/rez/cli/pkg-cache.py | 12 +- src/rez/cli/pkg-ignore.py | 9 +- src/rez/cli/plugins.py | 5 +- src/rez/cli/python.py | 5 +- src/rez/cli/release.py | 6 +- src/rez/cli/rm.py | 12 +- src/rez/cli/search.py | 6 +- src/rez/cli/selftest.py | 17 +- src/rez/cli/status.py | 5 +- src/rez/cli/suite.py | 7 +- src/rez/cli/test.py | 5 +- src/rez/cli/view.py | 5 +- src/rez/cli/yaml2py.py | 5 +- src/rez/command.py | 6 +- src/rez/config.py | 108 ++-- .../builds/packages/anti/1.0.0/package.py | 2 +- .../tests/builds/packages/bah/2.1/build.py | 2 +- .../builds/packages/build_util/1/build.py | 4 +- .../build_util/1/build_util/__init__.py | 4 +- .../builds/packages/build_util/1/package.py | 2 +- .../data/tests/builds/packages/floob/build.py | 2 +- .../builds/packages/floob/floob/__init__.py | 2 +- .../tests/builds/packages/floob/package.py | 2 +- .../builds/packages/foo/1.0.0/foo/__init__.py | 2 +- .../builds/packages/foo/1.0.0/package.py | 4 +- .../tests/builds/packages/foo/1.1.0/build.py | 2 +- .../builds/packages/foo/1.1.0/foo/__init__.py | 2 +- .../builds/packages/foo/1.1.0/package.py | 2 +- .../builds/packages/hello/1.0/package.py | 2 +- .../builds/packages/sup_world/3.8/package.py | 2 +- .../packages/testing_obj/1.0.0/build.py | 2 +- .../packages/testing_obj/1.0.0/package.py | 2 +- .../testing_obj/1.0.0/testing_obj/__init__.py | 2 +- .../packages/translate_lib/2.2.0/package.py | 2 +- .../commands/packages/rextest/1.3/package.py | 2 +- .../commands/packages/rextest2/2/package.py | 2 +- .../rezplugins/package_repository/memory.py | 2 +- .../rezplugins/package_repository/cloud.py | 2 +- .../rezplugins/package_repository/memory.py | 2 +- .../package.py | 2 +- .../package.py | 4 +- .../package.py | 4 +- .../py_packages/late_binding/1.0/package.py | 2 +- .../py_packages/variants_py/2.0/package.py | 2 +- .../py_packages/versioned/3.0/package.py | 2 +- .../data/tests/python/late_bind/late_utils.py | 2 +- .../python/preprocess/global_preprocess.py | 2 +- src/rez/data/tests/release/build.py | 4 +- .../missing_variant_requires/1/package.py | 2 +- src/rez/deprecations.py | 6 +- src/rez/developer_package.py | 22 +- src/rez/exceptions.py | 10 +- src/rez/package_bind.py | 24 +- src/rez/package_cache.py | 50 +- src/rez/package_copy.py | 46 +- src/rez/package_filter.py | 163 +++--- src/rez/package_help.py | 21 +- src/rez/package_maker.py | 18 +- src/rez/package_move.py | 8 +- src/rez/package_order.py | 183 +++--- src/rez/package_py_utils.py | 16 +- src/rez/package_remove.py | 9 +- src/rez/package_repository.py | 119 ++-- src/rez/package_resources.py | 80 ++- src/rez/package_search.py | 35 +- src/rez/package_serialise.py | 15 +- src/rez/package_test.py | 46 +- src/rez/packages.py | 154 +++-- src/rez/pip.py | 16 +- src/rez/plugin_managers.py | 80 ++- src/rez/release_hook.py | 12 +- src/rez/release_vcs.py | 34 +- src/rez/resolved_context.py | 338 ++++++----- src/rez/resolver.py | 118 ++-- src/rez/rex.py | 206 ++++--- src/rez/rex_bindings.py | 52 +- src/rez/rezconfig.py | 3 +- src/rez/serialise.py | 26 +- src/rez/shells.py | 106 ++-- src/rez/solver.py | 404 +++++++------ src/rez/status.py | 31 +- src/rez/suite.py | 132 +++-- src/rez/system.py | 6 +- src/rez/tests/test_bind.py | 4 +- src/rez/tests/test_build.py | 32 +- src/rez/tests/test_cli.py | 2 +- src/rez/tests/test_commands.py | 18 +- src/rez/tests/test_completion.py | 10 +- src/rez/tests/test_config.py | 30 +- src/rez/tests/test_context.py | 32 +- src/rez/tests/test_copy_package.py | 28 +- src/rez/tests/test_formatter.py | 40 +- src/rez/tests/test_imports.py | 2 +- src/rez/tests/test_package_cache.py | 20 +- src/rez/tests/test_package_filter.py | 24 +- src/rez/tests/test_packages.py | 52 +- src/rez/tests/test_packages_order.py | 78 +-- src/rez/tests/test_pip_utils.py | 30 +- src/rez/tests/test_plugin_manager.py | 20 +- src/rez/tests/test_release.py | 16 +- src/rez/tests/test_resources_.py | 18 +- src/rez/tests/test_rex.py | 56 +- src/rez/tests/test_shells.py | 54 +- src/rez/tests/test_solver.py | 32 +- src/rez/tests/test_suites.py | 14 +- src/rez/tests/test_test.py | 10 +- src/rez/tests/test_util.py | 2 +- src/rez/tests/test_utils.py | 10 +- src/rez/tests/test_utils_elf.py | 8 +- src/rez/tests/test_utils_filesystem.py | 16 +- src/rez/tests/test_utils_formatting.py | 2 +- src/rez/tests/test_utils_resolve_graph.py | 2 +- src/rez/tests/test_version.py | 46 +- src/rez/tests/util.py | 24 +- src/rez/util.py | 33 +- src/rez/utils/__init__.py | 5 +- src/rez/utils/amqp.py | 10 +- src/rez/utils/backcompat.py | 4 +- src/rez/utils/base26.py | 6 +- src/rez/utils/colorize.py | 20 +- src/rez/utils/data_utils.py | 144 ++--- src/rez/utils/diff_packages.py | 6 +- src/rez/utils/elf.py | 6 +- src/rez/utils/execution.py | 20 +- src/rez/utils/filesystem.py | 62 +- src/rez/utils/formatting.py | 62 +- src/rez/utils/graph_utils.py | 11 +- src/rez/utils/installer.py | 8 +- src/rez/utils/lint_helper.py | 4 +- src/rez/utils/logging_.py | 28 +- src/rez/utils/memcached.py | 57 +- src/rez/utils/patching.py | 6 +- src/rez/utils/pip.py | 4 +- src/rez/utils/platform_.py | 35 +- src/rez/utils/py_dist.py | 4 +- src/rez/utils/resolve_graph.py | 4 +- src/rez/utils/resources.py | 94 +-- src/rez/utils/schema.py | 12 +- src/rez/utils/scope.py | 53 +- src/rez/utils/sourcecode.py | 66 ++- src/rez/utils/typing.py | 29 + src/rez/utils/which.py | 4 +- src/rez/utils/yaml.py | 4 +- src/rez/version/_requirement.py | 80 +-- src/rez/version/_util.py | 11 +- src/rez/version/_version.py | 374 ++++++------ src/rez/wrapper.py | 16 +- src/rezgui/app.py | 2 +- src/rezgui/dialogs/AboutDialog.py | 4 +- src/rezgui/dialogs/BrowsePackageDialog.py | 8 +- src/rezgui/dialogs/ImageViewerDialog.py | 2 +- src/rezgui/dialogs/ProcessDialog.py | 8 +- src/rezgui/dialogs/ResolveDialog.py | 24 +- src/rezgui/dialogs/VariantVersionsDialog.py | 2 +- src/rezgui/dialogs/WriteGraphDialog.py | 18 +- src/rezgui/mixins/ContextViewMixin.py | 6 +- src/rezgui/mixins/StoreSizeMixin.py | 4 +- src/rezgui/models/ContextModel.py | 32 +- src/rezgui/objects/App.py | 6 +- src/rezgui/objects/Config.py | 12 +- src/rezgui/objects/LoadPackagesThread.py | 6 +- src/rezgui/objects/ProcessTrackerThread.py | 8 +- src/rezgui/objects/ResolveThread.py | 10 +- src/rezgui/util.py | 10 +- src/rezgui/widgets/BrowsePackagePane.py | 2 +- src/rezgui/widgets/BrowsePackageWidget.py | 10 +- src/rezgui/widgets/ChangelogEdit.py | 8 +- src/rezgui/widgets/ConfiguredSplitter.py | 6 +- src/rezgui/widgets/ContextDetailsWidget.py | 12 +- src/rezgui/widgets/ContextEnvironTable.py | 10 +- src/rezgui/widgets/ContextEnvironWidget.py | 6 +- src/rezgui/widgets/ContextManagerWidget.py | 48 +- src/rezgui/widgets/ContextResolveTimeLabel.py | 6 +- src/rezgui/widgets/ContextSettingsWidget.py | 18 +- src/rezgui/widgets/ContextTableWidget.py | 72 +-- src/rezgui/widgets/ContextToolsWidget.py | 14 +- .../widgets/EffectivePackageCellWidget.py | 2 +- src/rezgui/widgets/FindPopup.py | 6 +- src/rezgui/widgets/IconButton.py | 4 +- src/rezgui/widgets/ImageViewerWidget.py | 20 +- src/rezgui/widgets/PackageLineEdit.py | 24 +- src/rezgui/widgets/PackageLoadingWidget.py | 18 +- src/rezgui/widgets/PackageSelectWidget.py | 18 +- src/rezgui/widgets/PackageTabWidget.py | 10 +- src/rezgui/widgets/PackageVersionsTable.py | 10 +- src/rezgui/widgets/SearchableTextEdit.py | 8 +- src/rezgui/widgets/StreamableTextEdit.py | 10 +- src/rezgui/widgets/TimeSelecterPopup.py | 16 +- src/rezgui/widgets/TimestampWidget.py | 14 +- src/rezgui/widgets/ToolWidget.py | 10 +- src/rezgui/widgets/VariantCellWidget.py | 20 +- src/rezgui/widgets/VariantDetailsWidget.py | 10 +- src/rezgui/widgets/VariantHelpWidget.py | 18 +- src/rezgui/widgets/VariantSummaryWidget.py | 6 +- src/rezgui/widgets/VariantToolsList.py | 10 +- src/rezgui/widgets/VariantVersionsTable.py | 10 +- src/rezgui/widgets/VariantVersionsWidget.py | 28 +- src/rezgui/widgets/VariantsList.py | 6 +- src/rezgui/widgets/ViewGraphButton.py | 12 +- src/rezgui/windows/BrowsePackageSubWindow.py | 4 +- src/rezgui/windows/ContextSubWindow.py | 24 +- src/rezgui/windows/MainWindow.py | 18 +- src/rezplugins/build_process/local.py | 58 +- src/rezplugins/build_process/remote.py | 4 +- src/rezplugins/build_system/cmake.py | 34 +- src/rezplugins/build_system/custom.py | 40 +- src/rezplugins/build_system/make.py | 6 +- .../package_repository/filesystem.py | 175 +++--- src/rezplugins/package_repository/memory.py | 42 +- src/rezplugins/release_hook/amqp.py | 13 +- src/rezplugins/release_hook/command.py | 12 +- src/rezplugins/release_hook/emailer.py | 8 +- src/rezplugins/release_vcs/git.py | 14 +- src/rezplugins/release_vcs/hg.py | 24 +- src/rezplugins/release_vcs/stub.py | 16 +- src/rezplugins/release_vcs/svn.py | 12 +- .../shell/_utils/powershell_base.py | 52 +- src/rezplugins/shell/_utils/windows.py | 40 +- src/rezplugins/shell/bash.py | 10 +- src/rezplugins/shell/cmd.py | 46 +- src/rezplugins/shell/csh.py | 30 +- src/rezplugins/shell/gitbash.py | 6 +- src/rezplugins/shell/powershell.py | 4 +- src/rezplugins/shell/pwsh.py | 4 +- src/rezplugins/shell/sh.py | 24 +- src/rezplugins/shell/tcsh.py | 6 +- src/rezplugins/shell/zsh.py | 8 +- src/support/package_utils/set_authors.py | 2 +- 265 files changed, 4400 insertions(+), 2937 deletions(-) create mode 100644 .github/workflows/mypy.yaml create mode 100644 mypy-baseline.txt create mode 100644 src/rez/utils/typing.py diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml new file mode 100644 index 000000000..d69506dbc --- /dev/null +++ b/.github/workflows/mypy.yaml @@ -0,0 +1,49 @@ +name: mypy +on: + pull_request: + paths: + - 'src/rez/**.py' + - 'src/rezplugins/**.py' + - 'pyproject.toml' + - '!src/rez/utils/_version.py' + - '!src/rez/data/**' + - '!src/rez/vendor/**' + - '!src/rez/backport/**' + push: + paths: + - 'src/rez/**.py' + - 'src/rezplugins/**.py' + - 'pyproject.toml' + - '!src/rez/utils/_version.py' + - '!src/rez/data/**' + - '!src/rez/vendor/**' + - '!src/rez/backport/**' + +permissions: + contents: read + +jobs: + mypy: + name: Run mypy static type analysis + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Set up Python + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: "3.10" + + - name: Install Dependencies + run: | + pip install mypy==1.14.1 mypy_baseline==0.7.1 types-setuptools + + - name: Run mypy + run: >- + echo "To update the baseline run:" + echo "mypy | mypy-baseline sync --ignore-categories=note" + echo "then commit mypy-baseline.txt" + echo + mypy | mypy-baseline filter --ignore-categories=note --allow-unsynced diff --git a/mypy-baseline.txt b/mypy-baseline.txt new file mode 100644 index 000000000..0793f129a --- /dev/null +++ b/mypy-baseline.txt @@ -0,0 +1,551 @@ +src/rez/utils/which.py:0: error: Incompatible types in assignment (expression has type "list[str] | Any", variable has type "str | None") [assignment] +src/rez/utils/which.py:0: error: Item "str" of "str | Any" has no attribute "insert" [union-attr] +src/rez/deprecations.py:0: error: Incompatible types in assignment (expression has type "Callable[[Any, Any, VarArg(Any), KwArg(Any)], str]", variable has type "Callable[[Warning | str, type[Warning], str, int, str | None], str]") [assignment] +src/rez/version/_version.py:0: error: Unsupported left operand type for < ("_Comparable") [operator] +src/rez/version/_version.py:0: error: Unsupported left operand type for < ("_Comparable") [operator] +src/rez/version/_version.py:0: error: Unsupported left operand type for < ("_Comparable") [operator] +src/rez/version/_version.py:0: error: "object" has no attribute "value" [attr-defined] +src/rez/version/_version.py:0: error: "object" has no attribute "value" [attr-defined] +src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "VersionToken"; supertype defines the argument type as "object" [override] +src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] +src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] +src/rez/version/_version.py:0: error: Argument 2 to "map" has incompatible type "list[_SubToken] | None"; expected "Iterable[_SubToken]" [arg-type] +src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "VersionToken"; supertype defines the argument type as "object" [override] +src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] +src/rez/version/_version.py:0: error: Unsupported operand types for < ("list[_SubToken]" and "None") [operator] +src/rez/version/_version.py:0: error: Unsupported operand types for > ("list[_SubToken]" and "None") [operator] +src/rez/version/_version.py:0: error: Unsupported left operand type for < ("None") [operator] +src/rez/version/_version.py:0: error: Value of type "list[_SubToken] | None" is not indexable [index] +src/rez/version/_version.py:0: error: Incompatible return value type (got "None", expected "Version") [return-value] +src/rez/version/_version.py:0: error: Argument 2 to "map" has incompatible type "list[VersionToken] | None"; expected "Iterable[VersionToken]" [arg-type] +src/rez/version/_version.py:0: error: "object" has no attribute "tokens" [attr-defined] +src/rez/version/_version.py:0: error: "object" has no attribute "tokens" [attr-defined] +src/rez/version/_version.py:0: error: Incompatible types in assignment (expression has type "Version", variable has type "None") [assignment] +src/rez/version/_version.py:0: error: "None" has no attribute "tokens" [attr-defined] +src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] +src/rez/version/_version.py:0: error: Incompatible types in assignment (expression has type "_LowerBound", variable has type "None") [assignment] +src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] +src/rez/version/_version.py:0: error: Incompatible types in assignment (expression has type "_UpperBound", variable has type "None") [assignment] +src/rez/version/_version.py:0: error: Argument 1 to "_UpperBound" has incompatible type "None"; expected "Version" [arg-type] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] +src/rez/version/_version.py:0: error: Unsupported operand types for == ("_LowerBound" and "None") [operator] +src/rez/version/_version.py:0: error: Unsupported operand types for == ("_UpperBound" and "None") [operator] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "contains_version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "contains_version" [union-attr] +src/rez/version/_version.py:0: error: Unsupported left operand type for <= ("None") [operator] +src/rez/version/_version.py:0: error: Unsupported left operand type for >= ("None") [operator] +src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "max" cannot be "_LowerBound | None" [type-var] +src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "min" cannot be "_UpperBound | None" [type-var] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "max" cannot be "_LowerBound | None" [type-var] +src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "min" cannot be "_UpperBound | None" [type-var] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Incompatible types in assignment (expression has type "_Bound", variable has type "None") [assignment] +src/rez/version/_version.py:0: error: Argument 1 to "append" of "list" has incompatible type "None"; expected "_Bound" [arg-type] +src/rez/version/_version.py:0: error: Unsupported operand types for == ("_Bound" and "None") [operator] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_Bound | None" has no attribute "upper" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | Any | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "max" cannot be "_UpperBound | None" [type-var] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Unsupported operand types for > ("int" and "None") [operator] +src/rez/version/_version.py:0: error: Invalid index type "int | None" for "list[_Bound]"; expected type "SupportsIndex" [index] +src/rez/version/_version.py:0: error: Item "None" of "_Bound | None" has no attribute "upper_bounded" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_Bound | None" has no attribute "version_containment" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_Bound | None" has no attribute "lower_bounded" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_Bound | None" has no attribute "version_containment" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_Bound | None" has no attribute "version_containment" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "_Bound | None" has no attribute "lower_bounded" [union-attr] +src/rez/version/_requirement.py:0: error: Incompatible return value type (got "str | None", expected "str") [return-value] +src/rez/version/_requirement.py:0: error: Incompatible return value type (got "Version | None", expected "Version") [return-value] +src/rez/version/_requirement.py:0: error: Unsupported left operand type for + ("None") [operator] +src/rez/version/_requirement.py:0: error: Unsupported left operand type for + ("None") [operator] +src/rez/version/_requirement.py:0: error: Incompatible return value type (got "str | None", expected "str") [return-value] +src/rez/version/_requirement.py:0: error: Incompatible return value type (got "VersionRange | None", expected "VersionRange") [return-value] +src/rez/version/_requirement.py:0: error: Item "None" of "VersionRange | None" has no attribute "issuperset" [union-attr] +src/rez/version/_requirement.py:0: error: Argument 1 to "issuperset" of "VersionRange" has incompatible type "VersionRange | None"; expected "VersionRange" [arg-type] +src/rez/version/_requirement.py:0: error: Item "None" of "VersionRange | None" has no attribute "issuperset" [union-attr] +src/rez/version/_requirement.py:0: error: Argument 1 to "issuperset" of "VersionRange" has incompatible type "VersionRange | None"; expected "VersionRange" [arg-type] +src/rez/version/_requirement.py:0: error: Item "None" of "VersionRange | None" has no attribute "intersects" [union-attr] +src/rez/version/_requirement.py:0: error: Argument 1 to "intersects" of "VersionRange" has incompatible type "VersionRange | None"; expected "VersionRange" [arg-type] +src/rez/version/_requirement.py:0: error: Unsupported operand types for in ("Version | None" and "VersionRange") [operator] +src/rez/version/_requirement.py:0: error: Unsupported right operand type for in ("VersionRange | None") [operator] +src/rez/version/_requirement.py:0: error: Unsupported operand types for in ("Version | None" and "VersionRange") [operator] +src/rez/version/_requirement.py:0: error: Unsupported right operand type for in ("VersionRange | None") [operator] +src/rez/version/_requirement.py:0: error: Unsupported operand types for | ("VersionRange" and "None") [operator] +src/rez/version/_requirement.py:0: error: Unsupported left operand type for | ("None") [operator] +src/rez/version/_requirement.py:0: error: Unsupported operand types for - ("VersionRange" and "None") [operator] +src/rez/version/_requirement.py:0: error: Unsupported left operand type for - ("None") [operator] +src/rez/version/_requirement.py:0: error: Unsupported operand types for & ("VersionRange" and "None") [operator] +src/rez/version/_requirement.py:0: error: Unsupported left operand type for & ("None") [operator] +src/rez/version/_requirement.py:0: error: Item "None" of "VersionRange | None" has no attribute "is_any" [union-attr] +src/rez/version/_requirement.py:0: error: Unsupported operand types for + ("str" and "None") [operator] +src/rez/version/_requirement.py:0: error: Incompatible types in assignment (expression has type "str", variable has type "None") [assignment] +src/rez/version/_requirement.py:0: error: Incompatible return value type (got "None", expected "str") [return-value] +src/rez/utils/data_utils.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "tuple[Any, ...]") [assignment] +src/rez/utils/data_utils.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "dict[str, Any]") [assignment] +src/rez/utils/data_utils.py:0: error: Unexpected keyword argument "name" for "property" [call-arg] +src/rez/util.py:0: error: Argument 1 to "module_from_spec" has incompatible type "ModuleSpec | None"; expected "ModuleSpec" [arg-type] +src/rez/util.py:0: error: Item "None" of "ModuleSpec | None" has no attribute "loader" [union-attr] +src/rez/util.py:0: error: Item "None" of "Loader | Any | None" has no attribute "exec_module" [union-attr] +src/rez/utils/backcompat.py:0: error: Item "None" of "Match[str] | None" has no attribute "groupdict" [union-attr] +src/rez/utils/backcompat.py:0: error: Item "None" of "Match[str] | None" has no attribute "groupdict" [union-attr] +src/rez/__init__.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "Callable[[Any, Any], None]") [assignment] +src/rez/__init__.py:0: error: Function "callback" could always be true in boolean context [truthy-function] +src/rez/utils/sourcecode.py:0: error: Item "None" of "Callable[[], T] | None" has no attribute "__name__" [union-attr] +src/rez/utils/sourcecode.py:0: error: Argument 1 to "getsourcelines" has incompatible type "Callable[[], T] | None"; expected Module | type[Any] | MethodType | FunctionType | TracebackType | FrameType | CodeType | Callable[..., Any] [arg-type] +src/rez/utils/sourcecode.py:0: error: "PackageBaseResourceWrapper" has no attribute "base" [attr-defined] +src/rez/utils/platform_.py:0: error: Module has no attribute "windll" [attr-defined] +src/rez/utils/platform_.py:0: error: Module has no attribute "WinError" [attr-defined] +src/rez/utils/platform_.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "Platform") [assignment] +src/rez/utils/filesystem.py:0: error: Argument 2 to "chmod" has incompatible type "int | None"; expected "int" [arg-type] +src/rez/utils/filesystem.py:0: error: Module has no attribute "WindowsError" [attr-defined] +src/rez/utils/filesystem.py:0: error: Name "unicode" is not defined [name-defined] +src/rez/utils/filesystem.py:0: error: Name "unicode" is not defined [name-defined] +src/rez/utils/filesystem.py:0: error: Name "xrange" is not defined [name-defined] +src/rez/config.py:0: error: Incompatible return value type (got "list[str] | None", expected "list[str]") [return-value] +src/rez/rex.py:0: error: Item "None" of "ActionManager | None" has no attribute "environ" [union-attr] +src/rez/rex.py:0: error: Signature of "format" incompatible with supertype "Formatter" [override] +src/rez/utils/resources.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] +src/rez/utils/memcached.py:0: error: "Callable[[VarArg(Any), KwArg(Any)], Any]" has no attribute "forget" [attr-defined] +src/rez/utils/memcached.py:0: error: "Callable[[VarArg(Any), KwArg(Any)], Any]" has no attribute "__wrapped__" [attr-defined] +src/rez/utils/amqp.py:0: error: Incompatible types in assignment (expression has type "PlainCredentials", target has type "dict[str, str]") [assignment] +src/rez/shells.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] +src/rez/shells.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] +src/rez/shells.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] +src/rez/shells.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] +src/rez/shells.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "list[str]") [assignment] +src/rez/plugin_managers.py:0: error: List item 0 has incompatible type "MutableSequence[str]"; expected "str" [list-item] +src/rez/plugin_managers.py:0: error: Item "MetaPathFinderProtocol" of "MetaPathFinderProtocol | PathEntryFinderProtocol" has no attribute "find_module" [union-attr] +src/rez/plugin_managers.py:0: error: Item "PathEntryFinderProtocol" of "MetaPathFinderProtocol | PathEntryFinderProtocol" has no attribute "find_module" [union-attr] +src/rez/plugin_managers.py:0: error: Value of type variable "AnyOrLiteralStr" of "dirname" cannot be "str | None" [type-var] +src/rez/plugin_managers.py:0: error: Item "MetaPathFinderProtocol" of "MetaPathFinderProtocol | PathEntryFinderProtocol" has no attribute "path" [union-attr] +src/rez/plugin_managers.py:0: error: Item "PathEntryFinderProtocol" of "MetaPathFinderProtocol | PathEntryFinderProtocol" has no attribute "path" [union-attr] +src/rez/plugin_managers.py:0: error: Argument 1 to "append" of "list" has incompatible type "tuple[str, str, Any | str, str]"; expected "list[str]" [arg-type] +src/rez/plugin_managers.py:0: error: Argument 1 to "append" of "list" has incompatible type "tuple[str, str, str, str]"; expected "list[str]" [arg-type] +src/rez/package_resources.py:0: error: Incompatible types in assignment (expression has type "type[PackageMetadataError]", base class "Resource" defined the type as "type[Exception]") [assignment] +src/rez/package_resources.py:0: error: Argument 1 to "get_resource" of "PackageRepository" has incompatible type "None"; expected "str" [arg-type] +src/rez/package_resources.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "VariantResourceHelper") [misc] +src/rez/package_resources.py:0: error: "PackageRepositoryResource" has no attribute "hashed_variants" [attr-defined] +src/rez/package_resources.py:0: error: "VariantResourceHelper" has no attribute "base" [attr-defined] +src/rez/package_resources.py:0: error: "VariantResourceHelper" has no attribute "base" [attr-defined] +src/rez/package_resources.py:0: error: "VariantResourceHelper" has no attribute "base" [attr-defined] +src/rez/package_resources.py:0: error: "VariantResourceHelper" has no attribute "base" [attr-defined] +src/rez/package_resources.py:0: error: "VariantResourceHelper" has no attribute "base" [attr-defined] +src/rez/package_resources.py:0: error: "VariantResourceHelper" has no attribute "base" [attr-defined] +src/rez/package_resources.py:0: error: Argument 2 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/package_resources.py:0: error: "PackageRepositoryResource" has no attribute "variants" [attr-defined] +src/rez/serialise.py:0: error: Incompatible default for argument "filepath" (default has type "None", argument has type "str") [assignment] +src/rez/serialise.py:0: error: Incompatible default for argument "filepath" (default has type "None", argument has type "str") [assignment] +src/rez/serialise.py:0: error: Incompatible types in assignment (expression has type "list[FrameSummary]", variable has type "StackSummary") [assignment] +src/rez/serialise.py:0: error: Unsupported right operand type for in (Module) [operator] +src/rez/serialise.py:0: error: Value of type Module is not indexable [index] +src/rez/serialise.py:0: error: Incompatible default for argument "filepath" (default has type "None", argument has type "str") [assignment] +src/rez/serialise.py:0: error: Incompatible default for argument "filepath" (default has type "None", argument has type "str") [assignment] +src/rez/serialise.py:0: error: "Callable[[str, Any, Any], Any]" has no attribute "forget" [attr-defined] +src/rez/package_repository.py:0: error: "PackageFamilyResource" has no attribute "iter_packages" [attr-defined] +src/rez/package_repository.py:0: error: Incompatible types in assignment (expression has type "tuple[str, str]", variable has type "list[str]") [assignment] +src/rezplugins/package_repository/memory.py:0: error: "PackageRepository" has no attribute "data" [attr-defined] +src/rezplugins/package_repository/memory.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "MemoryPackageResource") [misc] +src/rezplugins/package_repository/memory.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "MemoryPackageResource") [misc] +src/rezplugins/package_repository/memory.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageResourceHelper" defined the type as "None") [assignment] +src/rezplugins/package_repository/memory.py:0: error: Argument 1 to "construct" of "VersionedObject" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/memory.py:0: error: Incompatible return value type (got "Resource", expected "PackageRepositoryResource") [return-value] +src/rezplugins/package_repository/memory.py:0: error: "PackageRepository" has no attribute "data" [attr-defined] +src/rezplugins/package_repository/memory.py:0: error: Incompatible return value type (got "Resource", expected "PackageRepositoryResource") [return-value] +src/rezplugins/package_repository/memory.py:0: error: Incompatible return value type (got "Resource", expected "MemoryPackageFamilyResource | None") [return-value] +src/rezplugins/package_repository/memory.py:0: error: Return type "Iterator[MemoryPackageFamilyResource | None]" of "iter_package_families" incompatible with return type "Iterator[PackageFamilyResource]" in supertype "PackageRepository" [override] +src/rezplugins/package_repository/memory.py:0: error: Argument 1 of "iter_packages" is incompatible with supertype "PackageRepository"; supertype defines the argument type as "PackageFamilyResource" [override] +src/rezplugins/package_repository/memory.py:0: error: Argument 1 of "iter_variants" is incompatible with supertype "PackageRepository"; supertype defines the argument type as "PackageResource" [override] +src/rezplugins/package_repository/memory.py:0: error: Incompatible return value type (got "PackageRepositoryResource", expected "PackageFamilyResource") [return-value] +src/rez/packages.py:0: error: Item "None" of "dict[str, Any] | None" has no attribute "items" [union-attr] +src/rez/packages.py:0: error: Incompatible types in assignment (expression has type "set[str]", base class "ResourceWrapper" defined the type as "None") [assignment] +src/rez/packages.py:0: error: Argument 1 to "iter_packages" of "PackageRepository" has incompatible type "Resource"; expected "PackageFamilyResource" [arg-type] +src/rez/packages.py:0: error: "Resource" has no attribute "uri" [attr-defined] +src/rez/packages.py:0: error: "Resource" has no attribute "config" [attr-defined] +src/rez/packages.py:0: error: Item "None" of "dict[str, Any] | None" has no attribute "get" [union-attr] +src/rez/packages.py:0: error: "PackageBaseResourceWrapper" has no attribute "parent" [attr-defined] +src/rez/packages.py:0: error: Incompatible types in assignment (expression has type "set[str]", base class "ResourceWrapper" defined the type as "None") [assignment] +src/rez/packages.py:0: error: Unsupported right operand type for in ("dict[str, Any] | None") [operator] +src/rez/packages.py:0: error: Value of type "dict[str, Any] | None" is not indexable [index] +src/rez/packages.py:0: error: Item "None" of "dict[str, Any] | None" has no attribute "keys" [union-attr] +src/rez/packages.py:0: error: Cannot determine type of "keys" [has-type] +src/rez/packages.py:0: error: Argument 1 to "get_parent_package_family" of "PackageRepository" has incompatible type "Resource"; expected "PackageResourceHelper" [arg-type] +src/rez/packages.py:0: error: Item "None" of "dict[str, Any] | None" has no attribute "get" [union-attr] +src/rez/packages.py:0: error: Argument 1 to "iter_variants" of "PackageRepository" has incompatible type "Resource"; expected "PackageResource" [arg-type] +src/rez/packages.py:0: error: Incompatible types in assignment (expression has type "set[str]", base class "ResourceWrapper" defined the type as "None") [assignment] +src/rez/packages.py:0: error: Cannot determine type of "keys" [has-type] +src/rez/packages.py:0: error: Argument 1 to "get_parent_package" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] +src/rez/packages.py:0: error: Argument 1 to "Package" has incompatible type "PackageRepositoryResource"; expected "PackageResource" [arg-type] +src/rez/packages.py:0: error: Argument 1 to "install_variant" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] +src/rez/packages.py:0: error: "Resource" has no attribute "_subpath" [attr-defined] +src/rez/packages.py:0: error: Argument 1 to "Package" has incompatible type "Resource"; expected "PackageResource" [arg-type] +src/rez/packages.py:0: error: Argument 1 to "Variant" has incompatible type "Resource"; expected "VariantResource" [arg-type] +src/rez/packages.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] +src/rez/packages.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] +src/rez/solver.py:0: error: "object" has no attribute "name" [attr-defined] +src/rez/solver.py:0: error: "object" has no attribute "version" [attr-defined] +src/rez/solver.py:0: error: "object" has no attribute "variant_index" [attr-defined] +src/rez/solver.py:0: error: "object" has no attribute "dependency" [attr-defined] +src/rez/solver.py:0: error: "object" has no attribute "conflicting_request" [attr-defined] +src/rez/solver.py:0: error: "object" has no attribute "dependency" [attr-defined] +src/rez/solver.py:0: error: "object" has no attribute "conflicting_request" [attr-defined] +src/rez/solver.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] +src/rez/solver.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] +src/rez/solver.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] +src/rez/solver.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] +src/rez/solver.py:0: error: Unsupported operand types for < ("int" and "None") [operator] +src/rez/solver.py:0: error: Unsupported operand types for > ("int" and "None") [operator] +src/rez/solver.py:0: error: Unsupported left operand type for < ("None") [operator] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "tuple[int, list[tuple[int, SupportsLessThan]], int, list[tuple[SupportsLessThan, str]], int | None]", variable has type "tuple[list[tuple[int, SupportsLessThan]], int, list[tuple[SupportsLessThan, str]], int | None]") [assignment] +src/rez/solver.py:0: error: Incompatible return value type (got "tuple[list[tuple[int, SupportsLessThan]], int, list[tuple[SupportsLessThan, str]], int | None]", expected "tuple[SupportsLessThan, ...]") [return-value] +src/rez/solver.py:0: error: Incompatible return value type (got "set[str] | None", expected "set[str]") [return-value] +src/rez/solver.py:0: error: Item "None" of "Requirement | None" has no attribute "range" [union-attr] +src/rez/solver.py:0: error: Item "None" of "Requirement | None" has no attribute "range" [union-attr] +src/rez/solver.py:0: error: Item "None" of "Requirement | None" has no attribute "range" [union-attr] +src/rez/solver.py:0: error: Expected iterable as variadic argument [misc] +src/rez/solver.py:0: error: Item "None" of "_PackageVariantSlice | None" has no attribute "intersect" [union-attr] +src/rez/solver.py:0: error: Item "None" of "_PackageVariantSlice | None" has no attribute "extract" [union-attr] +src/rez/solver.py:0: error: Argument 1 to "len" has incompatible type "_PackageVariantSlice | None"; expected "Sized" [arg-type] +src/rez/solver.py:0: error: Item "None" of "_PackageVariantSlice | None" has no attribute "split" [union-attr] +src/rez/solver.py:0: error: Argument 1 to "len" has incompatible type "_PackageVariantSlice | None"; expected "Sized" [arg-type] +src/rez/solver.py:0: error: Item "None" of "_PackageVariantSlice | None" has no attribute "extractable" [union-attr] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "_PackageScope | None", variable has type "_PackageScope") [assignment] +src/rez/solver.py:0: error: Item "None" of "PackageVariant | None" has no attribute "version" [union-attr] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "dict_values[Any, Any]", variable has type "list[Any]") [assignment] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "dict_values[str, _PackageScope]", variable has type "list[_PackageScope]") [assignment] +src/rez/solver.py:0: error: Argument 1 to "_get_dependency_order" has incompatible type "digraph | None"; expected "digraph" [arg-type] +src/rez/solver.py:0: error: Argument 1 to "_add_request_node" has incompatible type "Requirement | None"; expected "Requirement" [arg-type] +src/rez/solver.py:0: error: Item "None" of "_ResolvePhase | None" has no attribute "get_graph" [union-attr] +src/rez/package_order.py:0: error: "object" has no attribute "main_comparable" [attr-defined] +src/rez/package_order.py:0: error: "object" has no attribute "fallback_comparable" [attr-defined] +src/rez/package_order.py:0: error: "object" has no attribute "main_comparable" [attr-defined] +src/rez/package_order.py:0: error: "object" has no attribute "fallback_comparable" [attr-defined] +src/rez/package_order.py:0: error: Argument 1 to "sort_key" of "PackageOrder" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/package_order.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageOrder" defined the type as "None") [assignment] +src/rez/package_order.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageOrder" defined the type as "None") [assignment] +src/rez/package_order.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageOrder" defined the type as "None") [assignment] +src/rez/package_order.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageOrder" defined the type as "None") [assignment] +src/rez/package_order.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageOrder" defined the type as "None") [assignment] +src/rez/package_order.py:0: error: Value of type "list[VersionToken] | None" is not indexable [index] +src/rez/package_maker.py:0: error: Argument 1 to "iter_packages" of "MemoryPackageRepository" has incompatible type "MemoryPackageFamilyResource | None"; expected "MemoryPackageFamilyResource" [arg-type] +src/rez/package_maker.py:0: error: Incompatible types in assignment (expression has type "Iterator[Variant]", variable has type "list[Variant]") [assignment] +src/rez/package_maker.py:0: error: Item "None" of "Variant | None" has no attribute "base" [union-attr] +src/rez/package_maker.py:0: error: Argument 1 has incompatible type "Variant | None"; expected "Variant" [arg-type] +src/rez/package_maker.py:0: error: Item "None" of "Variant | None" has no attribute "root" [union-attr] +src/rez/package_maker.py:0: error: Argument 1 has incompatible type "Variant | None"; expected "Variant" [arg-type] +src/rez/package_filter.py:0: error: Argument 1 to "add_exclusion" of "PackageFilter" has incompatible type "list[Rule]"; expected "Rule" [arg-type] +src/rez/package_filter.py:0: error: Argument 1 to "add_inclusion" of "PackageFilter" has incompatible type "list[Rule]"; expected "Rule" [arg-type] +src/rez/package_filter.py:0: error: Incompatible return value type (got "tuple[str | None, list[Rule]]", expected "tuple[str, list[Rule]]") [return-value] +src/rez/package_filter.py:0: error: "Rule" has no attribute "_family"; maybe "family"? [attr-defined] +src/rez/package_filter.py:0: error: Too many arguments for "RegexRuleBase" [call-arg] +src/rez/package_filter.py:0: error: Incompatible types in assignment (expression has type "str", base class "Rule" defined the type as "None") [assignment] +src/rez/package_filter.py:0: error: Incompatible types in assignment (expression has type "str", base class "Rule" defined the type as "None") [assignment] +src/rez/package_filter.py:0: error: Incompatible types in assignment (expression has type "str", base class "Rule" defined the type as "None") [assignment] +src/rez/package_filter.py:0: error: Cannot determine type of "name" [has-type] +src/rez/package_filter.py:0: error: Incompatible types in assignment (expression has type "str", base class "Rule" defined the type as "None") [assignment] +src/rez/package_cache.py:0: error: Value of type "dict[str, Any] | None" is not indexable [index] +src/rez/package_cache.py:0: error: Module has no attribute "CREATE_NEW_PROCESS_GROUP" [attr-defined] +src/rez/developer_package.py:0: error: Unsupported left operand type for | ("None") [operator] +src/rez/developer_package.py:0: error: Argument 1 to "from_path" of "DeveloperPackage" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/resolver.py:0: error: Name "digraph" is not defined [name-defined] +src/rez/resolver.py:0: error: Name "digraph" is not defined [name-defined] +src/rez/resolver.py:0: error: Name "ResourceHandle" is not defined [name-defined] +src/rez/resolver.py:0: error: Argument 1 to "get_variant_state_handle" of "PackageRepository" has incompatible type "Resource"; expected "PackageResource" [arg-type] +src/rez/resolver.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolver.py:0: error: Argument 1 to "get_variant_state_handle" of "PackageRepository" has incompatible type "Resource | Any"; expected "PackageResource" [arg-type] +src/rez/resolver.py:0: error: Incompatible types in assignment (expression has type "ResolverStatus | None", variable has type "ResolverStatus") [assignment] +src/rez/resolver.py:0: error: Item "None" of "list[PackageVariant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolver.py:0: error: Item "None" of "list[Requirement] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Argument "timestamp" to "Resolver" has incompatible type "float | None"; expected "int | None" [arg-type] +src/rez/resolved_context.py:0: error: Incompatible types in assignment (expression has type "float | None", variable has type "float") [assignment] +src/rez/resolved_context.py:0: error: Incompatible types in assignment (expression has type "float | None", variable has type "float") [assignment] +src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Argument 1 to "get_equivalent_variant" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] +src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "list[PackageRequest] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Incompatible types in assignment (expression has type "str", variable has type "PackageRequest") [assignment] +src/rez/resolved_context.py:0: error: Incompatible return value type (got "list[Requirement | PackageRequest]", expected "list[Requirement | PackageRequest | str]") [return-value] +src/rez/resolved_context.py:0: error: Argument 1 to "read_graph_from_string" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/resolved_context.py:0: error: Argument 1 to "write_dot" has incompatible type "digraph | None"; expected "digraph" [arg-type] +src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "list[Requirement] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Incompatible types in string interpolation (expression has type "str", placeholder has type "int | float | SupportsInt") [str-format] +src/rez/resolved_context.py:0: error: Incompatible types in assignment (expression has type "list[PackageOrder]", variable has type "PackageOrderList | None") [assignment] +src/rez/resolved_context.py:0: error: Argument 1 to "update" of "MutableMapping" has incompatible type "dict[str, Any] | None"; expected "SupportsKeysAndGetItem[str, Any]" [arg-type] +src/rez/resolved_context.py:0: error: Item "_NeverError" of "SourceCodeError | _NeverError" has no attribute "short_msg" [union-attr] +src/rez/wrapper.py:0: error: Incompatible types in assignment (expression has type "Callable[[Any], Any] | None", variable has type "Callable[[Any], Any]") [assignment] +src/rez/suite.py:0: error: Item "None" of "defaultdict[str, list[Tool]] | None" has no attribute "get" [union-attr] +src/rez/suite.py:0: error: Item "None" of "list[Tool] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/suite.py:0: error: Item "None" of "defaultdict[str, list[Tool]] | None" has no attribute "values" [union-attr] +src/rez/suite.py:0: error: Argument 1 to "append" of "list" has incompatible type "tuple[str, str, str, str, str]"; expected "list[str]" [arg-type] +src/rez/suite.py:0: error: Argument 1 to "append" of "list" has incompatible type "Callable[[Any], Any]"; expected "None" [arg-type] +src/rez/suite.py:0: error: Argument 1 to "append" of "list" has incompatible type "Callable[[Any], Any]"; expected "None" [arg-type] +src/rezplugins/shell/cmd.py:0: error: Signature of "spawn_shell" incompatible with supertype "Shell" [override] +src/rezplugins/shell/cmd.py:0: error: "setenv" of "CMD" does not return a value (it only ever returns None) [func-returns-value] +src/rezplugins/shell/_utils/powershell_base.py:0: error: Signature of "spawn_shell" incompatible with supertype "Shell" [override] +src/rezplugins/shell/_utils/powershell_base.py:0: error: "setenv" of "PowerShellBase" does not return a value (it only ever returns None) [func-returns-value] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: "PackageRepository" has no attribute "_get_file" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "FileSystemPackageResource") [misc] +src/rezplugins/package_repository/filesystem.py:0: error: "PackageRepository" has no attribute "_get_version_dirs" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "check_package_definition_files" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: "PackageRepository" has no attribute "_get_file" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "FileSystemPackageResource") [misc] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageResourceHelper" defined the type as "None") [assignment] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Resource", expected "FileSystemPackageFamilyResource") [return-value] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "dict[str, Any] | None" has no attribute "get" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: "PackageRepository" has no attribute "_get_file" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: "PackageRepository" has no attribute "disable_memcache" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Resource", expected "FileSystemPackageResource") [return-value] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: "FileSystemCombinedPackageFamilyResource" has no attribute "versions" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "FileSystemCombinedPackageResource") [misc] +src/rezplugins/package_repository/filesystem.py:0: error: "FileSystemCombinedPackageFamilyResource" has no attribute "versions" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "FileSystemCombinedPackageResource") [misc] +src/rezplugins/package_repository/filesystem.py:0: error: "PackageRepository" has no attribute "disable_memcache" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageResourceHelper" defined the type as "None") [assignment] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Resource", expected "FileSystemCombinedPackageFamilyResource") [return-value] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "dict[str, Any] | None" has no attribute "get" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Cannot determine type of "variant_key" [has-type] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "FileSystemCombinedVariantResource") [misc] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "dict[str, Any] | None" has no attribute "copy" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: "FileSystemCombinedPackageFamilyResource" has no attribute "version_overrides" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Resource", expected "PackageRepositoryResource") [return-value] +src/rezplugins/package_repository/filesystem.py:0: error: Cannot assign to a method [method-assign] +src/rezplugins/package_repository/filesystem.py:0: error: Cannot assign to a method [method-assign] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "PackageFamilyResource | None", expected "PackageFamilyResource") [return-value] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 of "iter_variants" is incompatible with supertype "PackageRepository"; supertype defines the argument type as "PackageResource" [override] +src/rezplugins/package_repository/filesystem.py:0: error: Return type "PackageRepositoryResource" of "get_parent_package_family" incompatible with return type "PackageFamilyResource" in supertype "PackageRepository" [override] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 of "get_variant_state_handle" is incompatible with supertype "PackageRepository"; supertype defines the argument type as "PackageResource" [override] +src/rezplugins/package_repository/filesystem.py:0: error: "PackageRepositoryResource" has no attribute "state_handle" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: "PackageFamilyResource" has no attribute "get_last_release_time" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in assignment (expression has type "str", variable has type "int | None") [assignment] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "remove_package" of "FileSystemPackageRepository" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "file_lock_dir" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 of "pre_variant_install" is incompatible with supertype "PackageRepository"; supertype defines the argument type as "VariantResource" [override] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "VariantResource | None", expected "VariantResource") [return-value] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "_lock_package" of "FileSystemPackageRepository" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "file_lock_type" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "file_lock_type" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible import of "LockFile" (imported name has type "type[MkdirLockFile]", local name has type "type[LinkLockFile]") [assignment] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "file_lock_type" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "file_lock_type" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible import of "LockFile" (imported name has type "type[SymlinkLockFile]", local name has type "type[LinkLockFile]") [assignment] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "file_lock_timeout" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: "Callable[[], list[tuple[str, str | None]]]" has no attribute "forget" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: "Callable[[str], list[str]]" has no attribute "forget" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "check_package_definition_files" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "list[Resource]", expected "list[PackageFamilyResource]") [return-value] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Resource", expected "PackageFamilyResource | None") [return-value] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Resource", expected "PackageFamilyResource | None") [return-value] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "FileFormat | None" has no attribute "extension" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: "PackageFamilyResource" has no attribute "iter_packages" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "package_filenames" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible default for argument "overrides" (default has type "None", argument has type "dict[str, Any]") [assignment] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "get_package_family" of "FileSystemPackageRepository" has incompatible type "Any | str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "_create_family" of "FileSystemPackageRepository" has incompatible type "Any | str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: "VariantResource" has no attribute "uuid" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "_get_package_data" has incompatible type "PackageRepositoryResource"; expected "PackageRepositoryResourceWrapper" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "iter_variants" of "FileSystemPackageRepository" has incompatible type "Package"; expected "PackageResourceHelper" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: "VariantResource" has no attribute "variant_requires" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "iter_variants" of "FileSystemPackageRepository" has incompatible type "Package"; expected "PackageResourceHelper" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "get" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "package_filenames" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: "VariantResource" has no attribute "variant_requires" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "Any | str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "_on_changed" of "FileSystemPackageRepository" has incompatible type "Any | str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "get_package" of "PackageRepository" has incompatible type "Any | str | None"; expected "str" [arg-type] +src/rez/status.py:0: error: Incompatible types in assignment (expression has type "list[Never]", variable has type "str") [assignment] +src/rez/status.py:0: error: "str" has no attribute "append" [attr-defined] +src/rez/status.py:0: error: Argument 1 to "append" of "list" has incompatible type "list[Any]"; expected "tuple[str, str, str, str, str, Callable[[str], str] | None]" [arg-type] +src/rez/status.py:0: error: Argument 2 to "print_colored_columns" has incompatible type "list[tuple[str, str, str, str, str, Callable[[str], str] | None] | list[str | None]]"; expected "Sequence[tuple[Any, ...]]" [arg-type] +src/rez/status.py:0: error: Argument 1 to "append" of "list" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/package_test.py:0: error: Incompatible types in assignment (expression has type "Package | None", variable has type "Package") [assignment] +src/rez/package_test.py:0: error: Item "None" of "ResolvedContext | None" has no attribute "get_resolved_package" [union-attr] +src/rez/package_test.py:0: error: Item "None" of "Variant | Any | None" has no attribute "uri" [union-attr] +src/rez/package_test.py:0: error: Item "None" of "Variant | Any | None" has no attribute "variant_requires" [union-attr] +src/rez/package_test.py:0: error: Item "None" of "Variant | Any | None" has no attribute "handle" [union-attr] +src/rez/package_test.py:0: error: Item "None" of "Variant | Any | None" has no attribute "uri" [union-attr] +src/rez/package_test.py:0: error: Item "None" of "Variant | Any | None" has no attribute "format" [union-attr] +src/rez/package_test.py:0: error: Item "None" of "Variant | Any | None" has no attribute "format" [union-attr] +src/rez/package_search.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] +src/rez/package_search.py:0: error: Incompatible types in assignment (expression has type "Iterator[Package]", variable has type "Iterator[PackageFamily]") [assignment] +src/rez/package_search.py:0: error: "PackageFamily" has no attribute "version" [attr-defined] +src/rez/package_search.py:0: error: "PackageFamily" has no attribute "iter_variants" [attr-defined] +src/rez/package_search.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] +src/rez/package_search.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] +src/rez/package_search.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] +src/rez/package_search.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] +src/rez/package_search.py:0: error: Argument 2 to "expand_abbreviations" has incompatible type "tuple[str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str]"; expected "list[str]" [arg-type] +src/rez/package_search.py:0: error: Incompatible types in assignment (expression has type "tuple[Any, Callable[[Any], Any]]", variable has type "str") [assignment] +src/rez/package_search.py:0: error: Argument 1 to "append" of "list" has incompatible type "str"; expected "tuple[Any, Callable[[Any], Any]]" [arg-type] +src/rez/package_help.py:0: error: Item "None" of "Variant | None" has no attribute "base" [union-attr] +src/rez/package_help.py:0: error: Item "None" of "Variant | None" has no attribute "root" [union-attr] +src/rez/package_copy.py:0: error: Argument 1 to "install_variant" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] +src/rez/package_copy.py:0: error: Argument 1 to "pre_variant_install" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] +src/rez/package_copy.py:0: error: Argument "variant_resource" to "install_variant" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] +src/rez/package_copy.py:0: error: Item "None" of "Variant | None" has no attribute "uri" [union-attr] +src/rez/package_bind.py:0: error: List comprehension has incompatible type List[tuple[Any, Any]]; expected List[list[str]] [misc] +src/rez/utils/pip.py:0: error: Incompatible types in assignment (expression has type "VersionRange | None", variable has type "VersionRange") [assignment] +src/rez/utils/installer.py:0: error: Name "env" is not defined [name-defined] +src/rez/utils/diff_packages.py:0: error: "type" has no attribute "export" [attr-defined] +src/rez/cli/pkg-cache.py:0: error: Argument 1 to "append" of "list" has incompatible type "Callable[[Any], Any]"; expected "str" [arg-type] +src/rez/cli/pkg-cache.py:0: error: Argument 2 to "print_colored_columns" has incompatible type "list[list[Any]]"; expected "Sequence[tuple[Any, ...]]" [arg-type] +src/rez/cli/memcache.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] +src/rez/cli/memcache.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] +src/rez/cli/memcache.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] +src/rez/cli/memcache.py:0: error: Argument 1 to "append" of "list" has incompatible type "tuple[Any, str, str, str, str, str, str]"; expected "list[str]" [arg-type] +src/rez/cli/benchmark.py:0: error: List item 0 has incompatible type "str | None"; expected "str" [list-item] +src/rez/cli/benchmark.py:0: error: Name "_opts" is not defined [name-defined] +src/rez/cli/benchmark.py:0: error: List item 0 has incompatible type "str | None"; expected "str" [list-item] +src/rez/cli/benchmark.py:0: error: Name "_opts" is not defined [name-defined] +src/rez/cli/benchmark.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/cli/benchmark.py:0: error: Argument 1 to "exists" has incompatible type "str | None"; expected "int | str | bytes | PathLike[str] | PathLike[bytes]" [arg-type] +src/rez/cli/benchmark.py:0: error: Argument 1 to "mkdir" has incompatible type "str | None"; expected "str | bytes | PathLike[str] | PathLike[bytes]" [arg-type] +src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/cli/benchmark.py:0: error: Name "_opts" is not defined [name-defined] +src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/cli/benchmark.py:0: error: Name "_opts" is not defined [name-defined] +src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/cli/_util.py:0: error: Module has no attribute "CTRL_C_EVENT" [attr-defined] +src/rez/cli/_complete_util.py:0: error: Incompatible types in assignment (expression has type "Generator[str, None, None]", variable has type "list[str]") [assignment] +src/rezplugins/release_vcs/hg.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] +src/rezplugins/release_vcs/git.py:0: error: Item "None" of "Any | None" has no attribute "allow_no_upstream" [union-attr] +src/rezplugins/release_vcs/git.py:0: error: Name "retain_cwd" is not defined [name-defined] +src/rezplugins/release_vcs/git.py:0: error: Name "git" is not defined [name-defined] +src/rezplugins/release_vcs/git.py:0: error: Name "git" is not defined [name-defined] +src/rezplugins/release_hook/emailer.py:0: error: Signature of "post_release" incompatible with supertype "ReleaseHook" [override] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "body" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "subject" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "recipients" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "smtp_host" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "sender" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "smtp_host" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "smtp_port" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "sender" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "recipients" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "print_error" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "print_output" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Signature of "pre_build" incompatible with supertype "ReleaseHook" [override] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "pre_build_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "cancel_on_error" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Signature of "pre_release" incompatible with supertype "ReleaseHook" [override] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "pre_release_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "cancel_on_error" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Signature of "post_release" incompatible with supertype "ReleaseHook" [override] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "post_release_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Argument 1 to "append" of "list" has incompatible type "dict[Any, Any]"; expected "int" [arg-type] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "print_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "print_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "stop_on_error" [union-attr] +src/rezplugins/release_hook/amqp.py:0: error: Signature of "post_release" incompatible with supertype "ReleaseHook" [override] +src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Any | None" has no attribute "message_attributes" [union-attr] +src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Any | None" has no attribute "host" [union-attr] +src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Any | None" has no attribute "exchange_routing_key" [union-attr] +src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Any | None" has no attribute "host" [union-attr] +src/rez/pip.py:0: error: Item "None" of "Variant | None" has no attribute "parent" [union-attr] +src/rez/build_process.py:0: error: Item "None" of "ReleaseVCS | None" has no attribute "get_changelog" [union-attr] +src/rez/build_system.py:0: error: Unsupported operand types for - ("set[type[BuildSystem]]" and "set[str | None]") [operator] +src/rez/cli/interpret.py:0: error: Incompatible types in assignment (expression has type "Python", variable has type "Shell | None") [assignment] +src/rez/cli/help.py:0: error: Item "None" of "Package | None" has no attribute "uri" [union-attr] +src/rez/cli/help.py:0: error: Item "None" of "Package | None" has no attribute "description" [union-attr] +src/rez/cli/help.py:0: error: Item "None" of "Package | None" has no attribute "description" [union-attr] +src/rez/cli/env.py:0: error: Function "graph" could always be true in boolean context [truthy-function] +src/rez/cli/env.py:0: error: "Popen[Any]" object is not iterable [misc] +src/rez/cli/env.py:0: error: Cannot determine type of "returncode" [has-type] +src/rez/cli/diff.py:0: error: Argument 1 to "diff_packages" has incompatible type "Package | None"; expected "Package" [arg-type] +src/rez/cli/cp.py:0: error: Incompatible types in assignment (expression has type "Any | None", variable has type "list[Any]") [assignment] +src/rez/cli/context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/cli/context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/cli/bind.py:0: error: Argument 1 to "sorted" has incompatible type "dict_items[str, str]"; expected "Iterable[list[str]]" [arg-type] +src/rez/bind/python.py:0: error: Name "this" is not defined [name-defined] +src/rez/bind/_pymodule.py:0: error: Name "env" is not defined [name-defined] +src/rez/bind/_pymodule.py:0: error: Name "env" is not defined [name-defined] +src/rez/bind/_pymodule.py:0: error: Name "env" is not defined [name-defined] +src/rezplugins/build_system/custom.py:0: error: "Popen[Any]" object is not iterable [misc] +src/rezplugins/build_system/custom.py:0: error: Cannot determine type of "retcode" [has-type] +src/rezplugins/build_system/custom.py:0: error: "Popen[Any]" object is not iterable [misc] +src/rezplugins/build_system/custom.py:0: error: Cannot determine type of "retcode" [has-type] +src/rezplugins/build_system/cmake.py:0: error: "Popen[Any]" object is not iterable [misc] +src/rezplugins/build_system/cmake.py:0: error: Cannot determine type of "retcode" [has-type] +src/rezplugins/build_system/cmake.py:0: error: "Popen[Any]" object is not iterable [misc] +src/rezplugins/build_system/cmake.py:0: error: Cannot determine type of "retcode'" [has-type] +src/rezplugins/build_system/cmake.py:0: error: "Popen[Any]" object is not iterable [misc] +src/rezplugins/build_system/cmake.py:0: error: Cannot determine type of "retcode'" [has-type] +src/rezplugins/build_system/cmake.py:0: error: "Popen[Any]" object is not iterable [misc] +src/rezplugins/build_system/cmake.py:0: error: Cannot determine type of "retcode" [has-type] +src/rezplugins/build_process/local.py:0: error: Argument 1 to "join" of "str" has incompatible type "list[str | None]"; expected "Iterable[str]" [arg-type] +src/rezplugins/build_process/local.py:0: error: Argument 1 to "pre_variant_install" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] +src/rezplugins/build_process/local.py:0: error: Value of type "dict[str, Any] | None" is not indexable [index] +src/rezplugins/build_process/local.py:0: error: Argument 1 to "get_repository" of "PackageRepositoryManager" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/build_process/local.py:0: error: Argument 1 to "on_variant_install_cancelled" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] +src/rezplugins/build_process/local.py:0: error: Argument 1 to "on_variant_install_cancelled" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] +src/rez/cli/pip.py:0: error: "Config" has no attribute "debug_package_release" [attr-defined] +src/rez/cli/release.py:0: error: Name "raw_input" is not defined [name-defined] +src/rez/cli/selftest.py:0: error: Argument 1 to "getfile" has incompatible type "FrameType | None"; expected Module | type[Any] | MethodType | FunctionType | TracebackType | FrameType | CodeType | Callable[..., Any] [arg-type] +src/rez/cli/selftest.py:0: error: Item "MetaPathFinderProtocol" of "MetaPathFinderProtocol | PathEntryFinderProtocol" has no attribute "find_module" [union-attr] +src/rez/cli/selftest.py:0: error: Item "PathEntryFinderProtocol" of "MetaPathFinderProtocol | PathEntryFinderProtocol" has no attribute "find_module" [union-attr] diff --git a/pyproject.toml b/pyproject.toml index fed528d4a..85b7f8fe1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,30 @@ [build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" + +[tool.mypy] +python_version = "3.11" +files = ["src/rez/", "src/rezplugins/"] +exclude = [ + '.*/rez/data/.*', + '.*/rez/vendor/.*', + '.*/rez/tests/.*', + '.*/rez/utils/lint_helper.py', +] +disable_error_code = ["var-annotated", "import-not-found"] +check_untyped_defs = true +# allow this for now: +allow_redefinition = true +follow_imports = "silent" + +[[tool.mypy.overrides]] +module = 'rez.utils.lint_helper' +follow_imports = "skip" + +[[tool.mypy.overrides]] +module = "rez.version._version" +disallow_untyped_defs = true + +[[tool.mypy.overrides]] +module = "rez.version._requirement" +disallow_untyped_defs = true diff --git a/src/rez/__init__.py b/src/rez/__init__.py index be0a91c2e..4a75aaef5 100644 --- a/src/rez/__init__.py +++ b/src/rez/__init__.py @@ -18,7 +18,7 @@ # TODO: Revamp logging. For now, this is here for backwards compatibility -def _init_logging(): +def _init_logging() -> None: logging_conf = os.getenv("REZ_LOGGING_CONF") if logging_conf: import logging.config @@ -50,7 +50,7 @@ def _init_logging(): import traceback if action == "print_stack": - def callback(sig, frame): + def callback(sig, frame) -> None: txt = ''.join(traceback.format_stack(frame)) print() print(txt) diff --git a/src/rez/bind/_pymodule.py b/src/rez/bind/_pymodule.py index 0c265bbdd..74d6c0171 100644 --- a/src/rez/bind/_pymodule.py +++ b/src/rez/bind/_pymodule.py @@ -20,11 +20,11 @@ import os.path -def commands(): +def commands() -> None: env.PYTHONPATH.append('{this.root}/python') -def commands_with_bin(): +def commands_with_bin() -> None: env.PYTHONPATH.append('{this.root}/python') env.PATH.append('{this.root}/bin') @@ -88,7 +88,7 @@ def bind(name, path, import_name=None, version_range=None, version=None, except RezBindError as e: print_warning(str(e)) - def make_root(variant, root): + def make_root(variant, root) -> None: pypath = make_dirs(root, "python") copy_module(import_name, pypath) diff --git a/src/rez/bind/_utils.py b/src/rez/bind/_utils.py index 0504b7971..d49e9c9f2 100644 --- a/src/rez/bind/_utils.py +++ b/src/rez/bind/_utils.py @@ -19,7 +19,7 @@ import sys -def log(msg): +def log(msg) -> None: if config.debug("bind_modules"): print_debug(msg) @@ -82,7 +82,7 @@ def find_exe(name, filepath=None): return filepath -def extract_version(exepath, version_arg, word_index=-1, version_rank=3): +def extract_version(exepath, version_arg, word_index=-1, version_rank: int=3): """Run an executable and get the program version. Args: diff --git a/src/rez/bind/cmake.py b/src/rez/bind/cmake.py index 8489a3566..66156a8d4 100644 --- a/src/rez/bind/cmake.py +++ b/src/rez/bind/cmake.py @@ -13,12 +13,12 @@ import os.path -def setup_parser(parser): +def setup_parser(parser) -> None: parser.add_argument("--exe", type=str, metavar="PATH", help="manually specify the cmake executable to bind.") -def commands(): +def commands() -> None: env.PATH.append('{this.root}/bin') @@ -28,7 +28,7 @@ def bind(path, version_range=None, opts=None, parser=None): word_index=2 if os.name == 'nt' else -1) check_version(version, version_range) - def make_root(variant, root): + def make_root(variant, root) -> None: binpath = make_dirs(root, "bin") link = os.path.join(binpath, "cmake") platform_.symlink(exepath, link) diff --git a/src/rez/bind/gcc.py b/src/rez/bind/gcc.py index 8377d7d85..8581deb66 100644 --- a/src/rez/bind/gcc.py +++ b/src/rez/bind/gcc.py @@ -11,14 +11,14 @@ from rez.system import system -def setup_parser(parser): +def setup_parser(parser) -> None: parser.add_argument('--exe', type=str, metavar='PATH', help='bind other gcc version than default') -def commands(): +def commands() -> None: env.PATH.append('{this.root}/bin') @@ -39,7 +39,7 @@ def bind(path, version_range=None, opts=None, parser=None): raise RezBindError("gcc version different than g++ can not continue") # create directories and symlink gcc and g++ - def make_root(variant, root): + def make_root(variant, root) -> None: bin_path = make_dirs(root, 'bin') gcc_link_path = os.path.join(bin_path, 'gcc') diff --git a/src/rez/bind/hello_world.py b/src/rez/bind/hello_world.py index 16a000383..3ace98589 100644 --- a/src/rez/bind/hello_world.py +++ b/src/rez/bind/hello_world.py @@ -19,12 +19,12 @@ import shutil -def commands(): +def commands() -> None: env.PATH.append('{this.root}/bin') env.OH_HAI_WORLD = "hello" -def hello_world_source(): +def hello_world_source() -> None: import sys from optparse import OptionParser @@ -44,7 +44,7 @@ def bind(path, version_range=None, opts=None, parser=None): version = Version("1.0") check_version(version, version_range) - def make_root(variant, root): + def make_root(variant, root) -> None: binpath = make_dirs(root, "bin") binpathtmp = make_dirs(root, "bintmp") diff --git a/src/rez/bind/python.py b/src/rez/bind/python.py index 0c0f3b036..9279f7b93 100644 --- a/src/rez/bind/python.py +++ b/src/rez/bind/python.py @@ -15,17 +15,17 @@ import os.path -def setup_parser(parser): +def setup_parser(parser) -> None: parser.add_argument("--exe", type=str, metavar="PATH", help="bind an interpreter other than the current " "python interpreter") -def commands(): +def commands() -> None: env.PATH.append('{this.root}/bin') -def post_commands(): +def post_commands() -> None: # these are the builtin modules for this python executable. If we don't # include these, some python behavior can be incorrect. import os @@ -67,7 +67,7 @@ def bind(path, version_range=None, opts=None, parser=None): # make the package # - def make_root(variant, root): + def make_root(variant, root) -> None: binpath = make_dirs(root, "bin") link = os.path.join(binpath, "python") platform_.symlink(exepath, link) diff --git a/src/rez/bind/rez.py b/src/rez/bind/rez.py index 52f904303..ea9d51f5d 100644 --- a/src/rez/bind/rez.py +++ b/src/rez/bind/rez.py @@ -14,7 +14,7 @@ import os.path -def commands(): +def commands() -> None: env.PYTHONPATH.append('{this.root}') @@ -22,7 +22,7 @@ def bind(path, version_range=None, opts=None, parser=None): version = rez.__version__ check_version(version, version_range) - def make_root(variant, root): + def make_root(variant, root) -> None: # copy source rez_path = rez.__path__[0] site_path = os.path.dirname(rez_path) diff --git a/src/rez/bind/rezgui.py b/src/rez/bind/rezgui.py index 17fa01b81..25c17984c 100644 --- a/src/rez/bind/rezgui.py +++ b/src/rez/bind/rezgui.py @@ -16,18 +16,18 @@ import os.path -def setup_parser(parser): +def setup_parser(parser) -> None: parser.add_argument( "--gui-lib", type=str, default="PyQt-4", metavar="PKG", help="manually specify the gui lib to use (default: %(default)s).") -def commands(): +def commands() -> None: env.PYTHONPATH.append('{this.root}') env.PATH.append('{this.root}/bin') -def rez_gui_source(): +def rez_gui_source() -> None: from rez.cli._main import run run("gui") @@ -43,7 +43,7 @@ def bind(path, version_range=None, opts=None, parser=None): gui_lib = getattr(opts, "gui_lib", "") - def make_root(variant, root): + def make_root(variant, root) -> None: # copy source rez_path = rez.__path__[0] site_path = os.path.dirname(rez_path) diff --git a/src/rez/build_process.py b/src/rez/build_process.py index a7d3e280d..a5556b186 100644 --- a/src/rez/build_process.py +++ b/src/rez/build_process.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.packages import iter_packages from rez.exceptions import BuildProcessError, BuildContextResolveError, \ ReleaseHookCancellingError, RezError, ReleaseError, BuildError, \ @@ -18,6 +20,13 @@ import getpass import os.path import sys +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from rez.build_system import BuildSystem + from rez.packages import Package, Variant + from rez.release_vcs import ReleaseVCS + from rez.developer_package import DeveloperPackage debug_print = config.debug_printer("package_release") @@ -29,9 +38,16 @@ def get_build_process_types(): return plugin_manager.get_plugins('build_process') -def create_build_process(process_type, working_dir, build_system, package=None, - vcs=None, ensure_latest=True, skip_repo_errors=False, - ignore_existing_tag=False, verbose=False, quiet=False): +def create_build_process(process_type: str, + working_dir: str, + build_system: BuildSystem, + package=None, + vcs: ReleaseVCS | None = None, + ensure_latest: bool = True, + skip_repo_errors: bool = False, + ignore_existing_tag: bool = False, + verbose: bool = False, + quiet: bool = False) -> BuildProcess: """Create a :class:`BuildProcess` instance. .. warning:: @@ -44,7 +60,7 @@ def create_build_process(process_type, working_dir, build_system, package=None, if process_type not in process_types: raise BuildProcessError("Unknown build process: %r" % process_type) - cls = plugin_manager.get_plugin_class('build_process', process_type) + cls = plugin_manager.get_plugin_class('build_process', process_type, BuildProcess) return cls(working_dir, # ignored (deprecated) build_system, @@ -77,9 +93,10 @@ class BuildProcess(object): def name(cls): raise NotImplementedError - def __init__(self, working_dir, build_system, package=None, vcs=None, - ensure_latest=True, skip_repo_errors=False, - ignore_existing_tag=False, verbose=False, quiet=False): + def __init__(self, working_dir: str, build_system: BuildSystem, package=None, + vcs: ReleaseVCS | None = None, + ensure_latest: bool = True, skip_repo_errors: bool = False, + ignore_existing_tag: bool = False, verbose: bool = False, quiet: bool = False) -> None: """Create a BuildProcess. Args: @@ -119,14 +136,15 @@ def __init__(self, working_dir, build_system, package=None, vcs=None, self.package.config.build_directory) @property - def package(self): + def package(self) -> DeveloperPackage: return self.build_system.package @property - def working_dir(self): + def working_dir(self) -> str: return self.build_system.working_dir - def build(self, install_path=None, clean=False, install=False, variants=None): + def build(self, install_path: str | None = None, clean: bool = False, + install: bool = False, variants: list[int] | None = None) -> int: """Perform the build process. Iterates over the package's variants, resolves the environment for @@ -149,7 +167,8 @@ def build(self, install_path=None, clean=False, install=False, variants=None): """ raise NotImplementedError - def release(self, release_message=None, variants=None): + def release(self, release_message: str | None = None, + variants: list[int] | None = None) -> int: """Perform the release process. Iterates over the package's variants, building and installing each into @@ -167,7 +186,7 @@ def release(self, release_message=None, variants=None): """ raise NotImplementedError - def get_changelog(self): + def get_changelog(self) -> str | None: """Get the changelog since last package release. Returns: @@ -187,7 +206,8 @@ def repo_operation(self): except exc_type as e: print_warning("THE FOLLOWING ERROR WAS SKIPPED:\n%s" % str(e)) - def visit_variants(self, func, variants=None, **kwargs): + def visit_variants(self, func, variants: list[int] | None = None, + **kwargs) -> tuple[int, list[str | None]]: """Iterate over variants and call a function on each.""" if variants: present_variants = range(self.package.num_variants) @@ -215,7 +235,7 @@ def visit_variants(self, func, variants=None, **kwargs): return num_visited, results - def get_package_install_path(self, path): + def get_package_install_path(self, path: str) -> str: """Return the installation path for a package (where its payload goes). Args: @@ -230,7 +250,8 @@ def get_package_install_path(self, path): package_version=self.package.version ) - def create_build_context(self, variant, build_type, build_path): + def create_build_context(self, variant: Variant, build_type: BuildType, + build_path: str) -> tuple[ResolvedContext, str]: """Create a context to build the variant within.""" request = variant.get_requires(build_requires=True, private_build_requires=True) @@ -274,7 +295,7 @@ def create_build_context(self, variant, build_type, build_path): raise BuildContextResolveError(context) return context, rxt_filepath - def pre_release(self): + def pre_release(self) -> None: release_settings = self.package.config.plugins.release_vcs # test that the release path exists @@ -322,7 +343,7 @@ def pre_release(self): else: break - def post_release(self, release_message=None): + def post_release(self, release_message=None) -> None: tag_name = self.get_current_tag_name() if self.vcs is None: @@ -332,7 +353,7 @@ def post_release(self, release_message=None): with self.repo_operation(): self.vcs.create_release_tag(tag_name=tag_name, message=release_message) - def get_current_tag_name(self): + def get_current_tag_name(self) -> str: release_settings = self.package.config.plugins.release_vcs try: tag_name = self.package.format(release_settings.tag_name) @@ -342,7 +363,7 @@ def get_current_tag_name(self): tag_name = "unversioned" return tag_name - def run_hooks(self, hook_event, **kwargs): + def run_hooks(self, hook_event, **kwargs) -> None: hook_names = self.package.config.release_hooks or [] hooks = create_release_hooks(hook_names, self.working_dir) @@ -357,12 +378,12 @@ def run_hooks(self, hook_event, **kwargs): "%s cancelled by %s hook '%s': %s:\n%s" % (hook_event.noun, hook_event.label, hook.name(), e.__class__.__name__, str(e))) - except RezError: + except RezError as e: debug_print("Error in %s hook '%s': %s:\n%s" % (hook_event.label, hook.name(), e.__class__.__name__, str(e))) - def get_previous_release(self): + def get_previous_release(self) -> Package | None: release_path = self.package.config.release_packages_path it = iter_packages(self.package.name, paths=[release_path]) packages = sorted(it, key=lambda x: x.version, reverse=True) @@ -372,7 +393,7 @@ def get_previous_release(self): return package return None - def get_changelog(self): + def get_changelog(self) -> str | None: previous_package = self.get_previous_release() if previous_package: previous_revision = previous_package.revision @@ -423,13 +444,13 @@ def get_release_data(self): previous_version=previous_version, previous_revision=previous_revision) - def _print(self, txt, *nargs): + def _print(self, txt, *nargs) -> None: if self.verbose: if nargs: txt = txt % nargs print(txt) - def _print_header(self, txt, n=1): + def _print_header(self, txt, n: int=1) -> None: if self.quiet: return @@ -443,7 +464,7 @@ def _print_header(self, txt, n=1): pr = Printer(sys.stdout) pr(title, heading) - def _n_of_m(self, variant): + def _n_of_m(self, variant) -> str: num_variants = max(self.package.num_variants, 1) index = (variant.index or 0) + 1 return "%d/%d" % (index, num_variants) diff --git a/src/rez/build_system.py b/src/rez/build_system.py index 9f27b8e0b..eba04c4d3 100644 --- a/src/rez/build_system.py +++ b/src/rez/build_system.py @@ -2,21 +2,43 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + +import argparse import os.path +from typing import Sequence, TYPE_CHECKING + from rez.build_process import BuildType from rez.exceptions import BuildSystemError from rez.packages import get_developer_package from rez.rex_bindings import VariantBinding +if TYPE_CHECKING: + from typing import TypedDict # not available until python 3.8 + from rez.developer_package import DeveloperPackage + from rez.resolved_context import ResolvedContext + from rez.packages import Package, Variant + from rez.rex import RexExecutor + + # FIXME: move this out of TYPE_CHECKING block when python 3.7 support is dropped + class BuildResult(TypedDict, total=False): + success: bool + extra_files: list[str] + build_env_script: str + +else: + BuildResult = dict + -def get_buildsys_types(): +def get_buildsys_types() -> list[str]: """Returns the available build system implementations - cmake, make etc.""" from rez.plugin_managers import plugin_manager return plugin_manager.get_plugins('build_system') -def get_valid_build_systems(working_dir, package=None): +def get_valid_build_systems(working_dir: str, + package: DeveloperPackage | None = None) -> list[type[BuildSystem]]: """Returns the build system classes that could build the source in given dir. Args: @@ -41,19 +63,19 @@ def get_valid_build_systems(working_dir, package=None): if package: if getattr(package, "build_command", None) is not None: - buildsys_name = "custom" + buildsys_name: str | None = "custom" else: buildsys_name = getattr(package, "build_system", None) # package explicitly specifies build system if buildsys_name: - cls = plugin_manager.get_plugin_class('build_system', buildsys_name) + cls = plugin_manager.get_plugin_class('build_system', buildsys_name, BuildSystem) return [cls] # detect valid build systems clss = [] - for buildsys_name in get_buildsys_types(): - cls = plugin_manager.get_plugin_class('build_system', buildsys_name) + for buildsys_name_ in get_buildsys_types(): + cls = plugin_manager.get_plugin_class('build_system', buildsys_name_, BuildSystem) if cls.is_valid_root(working_dir, package=package): clss.append(cls) @@ -67,9 +89,13 @@ def get_valid_build_systems(working_dir, package=None): return clss -def create_build_system(working_dir, buildsys_type=None, package=None, opts=None, - write_build_scripts=False, verbose=False, - build_args=[], child_build_args=[]): +def create_build_system(working_dir: str, + buildsys_type: str | None = None, + package: DeveloperPackage | None = None, + opts: argparse.Namespace | None = None, + write_build_scripts: bool = False, + verbose: bool = False, + build_args=[], child_build_args=[]) -> BuildSystem: """Return a new build system that can build the source in working_dir.""" from rez.plugin_managers import plugin_manager @@ -89,7 +115,7 @@ def create_build_system(working_dir, buildsys_type=None, package=None, opts=None buildsys_type = next(iter(clss)).name() # create instance of build system - cls_ = plugin_manager.get_plugin_class('build_system', buildsys_type) + cls_ = plugin_manager.get_plugin_class('build_system', buildsys_type, BuildSystem) return cls_(working_dir, opts=opts, @@ -104,13 +130,18 @@ class BuildSystem(object): """A build system, such as cmake, make, Scons etc. """ @classmethod - def name(cls): + def name(cls) -> str: """Return the name of the build system, eg 'make'.""" raise NotImplementedError - def __init__(self, working_dir, opts=None, package=None, - write_build_scripts=False, verbose=False, build_args=[], - child_build_args=[]): + def __init__(self, + working_dir: str, + opts: argparse.Namespace | None = None, + package: DeveloperPackage | None = None, + write_build_scripts: bool = False, + verbose: bool = False, + build_args: Sequence[str] = [], + child_build_args: list[str] = []) -> None: """Create a build system instance. Args: @@ -143,12 +174,12 @@ def __init__(self, working_dir, opts=None, package=None, self.opts = opts @classmethod - def is_valid_root(cls, path): + def is_valid_root(cls, path: str, package=None) -> bool: """Return True if this build system can build the source in path.""" raise NotImplementedError @classmethod - def child_build_system(cls): + def child_build_system(cls) -> str | None: """Returns the child build system. Some build systems, such as cmake, don't build the source directly. @@ -163,19 +194,24 @@ def child_build_system(cls): return None @classmethod - def bind_cli(cls, parser, group): + def bind_cli(cls, parser: argparse.ArgumentParser, group: argparse._ArgumentGroup) -> None: """Expose parameters to an argparse.ArgumentParser that are specific to this build system. Args: parser (`ArgumentParser`): Arg parser. - group (`ArgumentGroup`): Arg parser group - you should add args to + group (`_ArgumentGroup`): Arg parser group - you should add args to this, NOT to `parser`. """ pass - def build(self, context, variant, build_path, install_path, install=False, - build_type=BuildType.local): + def build(self, + context: ResolvedContext, + variant: Variant, + build_path: str, + install_path: str, + install: bool = False, + build_type=BuildType.local) -> BuildResult: """Implement this method to perform the actual build. Args: @@ -206,8 +242,9 @@ def build(self, context, variant, build_path, install_path, install=False, raise NotImplementedError @classmethod - def set_standard_vars(cls, executor, context, variant, build_type, install, - build_path, install_path=None): + def set_standard_vars(cls, executor: RexExecutor, context: ResolvedContext, + variant: Variant, build_type: BuildType, install: bool, build_path: str, + install_path: str | None = None) -> None: """Set some standard env vars that all build systems can rely on. """ from rez.config import config @@ -258,7 +295,7 @@ def set_standard_vars(cls, executor, context, variant, build_type, install, @classmethod def add_pre_build_commands(cls, executor, variant, build_type, install, - build_path, install_path=None): + build_path, install_path=None) -> None: """Execute pre_build_commands function if present.""" from rez.utils.data_utils import RO_AttrDictWrapper as ROA @@ -294,8 +331,9 @@ def add_pre_build_commands(cls, executor, variant, build_type, install, executor.execute_code(pre_build_commands) @classmethod - def add_standard_build_actions(cls, executor, context, variant, build_type, - install, build_path, install_path=None): + def add_standard_build_actions(cls, executor: RexExecutor, context: ResolvedContext, variant: Variant, + build_type: BuildType, install: bool, build_path: str, + install_path: str | None = None) -> None: """Perform build actions common to every build system. """ diff --git a/src/rez/bundle_context.py b/src/rez/bundle_context.py index a12c39025..43e9bef3a 100644 --- a/src/rez/bundle_context.py +++ b/src/rez/bundle_context.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + import os import os.path import stat @@ -15,8 +17,8 @@ from rez.util import which -def bundle_context(context, dest_dir, force=False, skip_non_relocatable=False, - quiet=False, patch_libs=False, verbose=False): +def bundle_context(context, dest_dir, force: bool = False, skip_non_relocatable: bool = False, + quiet: bool = False, patch_libs: bool = False, verbose: bool = False) -> None: """Bundle a context and its variants into a relocatable dir. This creates a copy of a context with its variants retargeted to a local @@ -62,8 +64,8 @@ def bundle_context(context, dest_dir, force=False, skip_non_relocatable=False, class _ContextBundler(object): """Performs context bundling. """ - def __init__(self, context, dest_dir, force=False, skip_non_relocatable=False, - quiet=False, patch_libs=False, verbose=False): + def __init__(self, context, dest_dir, force: bool = False, skip_non_relocatable: bool = False, + quiet: bool = False, patch_libs: bool = False, verbose: bool = False) -> None: if quiet: verbose = False if force: @@ -112,18 +114,18 @@ def bundle(self): def _repo_path(self): return os.path.join(self.dest_dir, "packages") - def _info(self, msg, *nargs): + def _info(self, msg, *nargs) -> None: self.logs.append("INFO: %s" % (msg % nargs)) - def _verbose_info(self, msg, *nargs): + def _verbose_info(self, msg, *nargs) -> None: if self.verbose: print_info(msg, *nargs) - def _warning(self, msg, *nargs): + def _warning(self, msg, *nargs) -> None: print_warning(msg, *nargs) self.logs.append("WARNING: %s" % (msg % nargs)) - def _init_bundle(self): + def _init_bundle(self) -> None: os.mkdir(self.dest_dir) os.mkdir(self._repo_path) @@ -143,7 +145,7 @@ def _init_bundle(self): settings_filepath = os.path.join(self._repo_path, "settings.yaml") save_yaml(settings_filepath, disable_memcached=True) - def _finalize_bundle(self): + def _finalize_bundle(self) -> None: # write metafile bundle_metafile = os.path.join(self.dest_dir, "bundle.yaml") save_yaml(bundle_metafile, logs=self.logs) @@ -181,7 +183,7 @@ def _copy_variants(self): return relocated_package_names - def _write_retargeted_context(self, relocated_package_names): + def _write_retargeted_context(self, relocated_package_names) -> None: rxt_filepath = os.path.join(self.dest_dir, "context.rxt") bundled_context = self.context.retargeted( @@ -193,14 +195,14 @@ def _write_retargeted_context(self, relocated_package_names): bundled_context.save(rxt_filepath) self._verbose_info("Bundled context written to to %s", rxt_filepath) - def _patch_libs(self): + def _patch_libs(self) -> None: # TODO if platform_.name in ("osx", "windows"): return self._patch_libs_linux() - def _patch_libs_linux(self): + def _patch_libs_linux(self) -> None: """Fix elfs that reference elfs outside of the bundle. Finds elf files, inspects their runpath/rpath, then looks to see if @@ -313,7 +315,7 @@ def _patch_libs_linux(self): elf, ':'.join(rpaths), ':'.join(new_rpaths) ) - def _find_files(self, executable=False, filename_substrs=None): + def _find_files(self, executable: bool = False, filename_substrs=None): found_files = [] # iterate over payload of each package diff --git a/src/rez/cli/_complete_util.py b/src/rez/cli/_complete_util.py index 3b34ca95a..1224f9216 100644 --- a/src/rez/cli/_complete_util.py +++ b/src/rez/cli/_complete_util.py @@ -10,7 +10,7 @@ class RezCompletionFinder(CompletionFinder): - def __init__(self, parser, comp_line, comp_point): + def __init__(self, parser, comp_line, comp_point) -> None: self._parser = parser self.always_complete_options = False self.exclude = None @@ -72,7 +72,7 @@ def ExecutablesCompleter(prefix, **kwargs): class FilesCompleter(object): - def __init__(self, files=True, dirs=True, file_patterns=None): + def __init__(self, files: bool = True, dirs: bool = True, file_patterns=None) -> None: self.files = files self.dirs = dirs self.file_patterns = file_patterns @@ -115,7 +115,7 @@ def __call__(self, prefix, **kwargs): class CombinedCompleter(object): - def __init__(self, completer, *completers): + def __init__(self, completer, *completers) -> None: self.completers = [completer] self.completers += list(completers) @@ -130,7 +130,7 @@ def __call__(self, prefix, **kwargs): class SequencedCompleter(CombinedCompleter): - def __init__(self, arg, completer, *completers): + def __init__(self, arg, completer, *completers) -> None: super(SequencedCompleter, self).__init__(completer, *completers) self.arg = arg diff --git a/src/rez/cli/_entry_points.py b/src/rez/cli/_entry_points.py index 8590e9d72..f3f0d9239 100644 --- a/src/rez/cli/_entry_points.py +++ b/src/rez/cli/_entry_points.py @@ -47,7 +47,7 @@ def decorator(fn): return decorator -def check_production_install(): +def check_production_install() -> None: path = os.path.dirname(sys.argv[0]) filepath = os.path.join(path, ".rez_production_install") diff --git a/src/rez/cli/_main.py b/src/rez/cli/_main.py index a5a9744f0..c5733a1ce 100644 --- a/src/rez/cli/_main.py +++ b/src/rez/cli/_main.py @@ -5,6 +5,8 @@ """ The main command-line entry point. """ +from __future__ import annotations + import sys import importlib from argparse import _StoreTrueAction, SUPPRESS @@ -25,7 +27,7 @@ def is_hyphened_command(): class SetupRezSubParser(object): """Callback class for lazily setting up rez sub-parsers. """ - def __init__(self, module_name): + def __init__(self, module_name) -> None: self.module_name = module_name def __call__(self, parser_name, parser): @@ -61,7 +63,7 @@ def get_module(self): return sys.modules[self.module_name] -def _add_common_args(parser): +def _add_common_args(parser) -> None: parser.add_argument("-v", "--verbose", action="count", default=0, help="verbose mode, repeat for more verbosity") parser.add_argument("--debug", dest="debug", action="store_true", @@ -71,7 +73,7 @@ def _add_common_args(parser): class InfoAction(_StoreTrueAction): - def __call__(self, parser, args, values, option_string=None): + def __call__(self, parser, args, values, option_string=None) -> None: from rez.system import system txt = system.get_summary_string() print() @@ -165,7 +167,7 @@ def run(command=None): extra_arg_groups = [] if opts.debug or _env_var_true("REZ_DEBUG"): - exc_type = _NeverError + exc_type: type[RezError] = _NeverError else: exc_type = RezError diff --git a/src/rez/cli/_util.py b/src/rez/cli/_util.py index 917c27777..16c57e5ca 100644 --- a/src/rez/cli/_util.py +++ b/src/rez/cli/_util.py @@ -2,11 +2,14 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + import os import sys import signal from argparse import _SubParsersAction, ArgumentParser, SUPPRESS, \ ArgumentError +from typing import Any # Subcommands and their behaviors. @@ -18,7 +21,7 @@ # The '--' arg is not treated as a special case. # * missing: Native python argparse behavior. # -subcommands = { +subcommands: dict[str, dict[str, Any]] = { "bind": {}, "build": { "arg_mode": "grouped" @@ -140,7 +143,7 @@ def __call__(self, parser, namespace, values, option_string=None): caller = super(LazySubParsersAction, self).__call__ return caller(parser2, namespace, values, option_string) - def _setup_subparser(self, parser_name, parser): + def _setup_subparser(self, parser_name, parser) -> None: if hasattr(parser, 'setup_subparser'): help_ = parser.setup_subparser(parser_name, parser) if help_ is not None: @@ -167,7 +170,7 @@ class LazyArgumentParser(ArgumentParser): `setup_subparser` is passed 'parser_name', 'parser', and can return a help string. """ - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: self.setup_subparser = kwargs.pop('setup_subparser', None) super(LazyArgumentParser, self).__init__(*args, **kwargs) self.register('action', 'parsers', LazySubParsersAction) @@ -177,7 +180,7 @@ def format_help(self): self._setup_all_subparsers() return super(LazyArgumentParser, self).format_help() - def _setup_all_subparsers(self): + def _setup_all_subparsers(self) -> None: """Sets up all sub-parsers on demand.""" if self._subparsers: for action in self._subparsers._actions: @@ -190,11 +193,11 @@ def _setup_all_subparsers(self): _handled_term = False -def _env_var_true(name): +def _env_var_true(name) -> bool: return (os.getenv(name, "").lower() in ("1", "true", "on", "yes")) -def print_items(items, stream=sys.stdout): +def print_items(items, stream=sys.stdout) -> None: try: item_per_line = (not stream.isatty()) except: @@ -207,7 +210,7 @@ def print_items(items, stream=sys.stdout): print(' '.join(map(str, items))) -def sigbase_handler(signum, frame): +def sigbase_handler(signum, frame) -> None: # show cursor - progress lib may have hidden it SHOW_CURSOR = '\x1b[?25h' sys.stdout.write(SHOW_CURSOR) @@ -223,7 +226,7 @@ def sigbase_handler(signum, frame): sys.exit(1) -def sigint_handler(signum, frame): +def sigint_handler(signum, frame) -> None: """Exit gracefully on ctrl-C.""" global _handled_int if not _handled_int: @@ -233,7 +236,7 @@ def sigint_handler(signum, frame): sigbase_handler(signum, frame) -def sigterm_handler(signum, frame): +def sigterm_handler(signum, frame) -> None: """Exit gracefully on terminate.""" global _handled_term if not _handled_term: diff --git a/src/rez/cli/benchmark.py b/src/rez/cli/benchmark.py index a3a568dd6..266e70361 100644 --- a/src/rez/cli/benchmark.py +++ b/src/rez/cli/benchmark.py @@ -5,6 +5,8 @@ ''' Run a benchmarking suite for runtime resolves. ''' +from __future__ import annotations + import json import os import os.path @@ -17,11 +19,11 @@ # globals opts = None -out_dir = None -pkg_repo_dir = None +out_dir: str | None = None +pkg_repo_dir: str | None = None -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "--out", metavar="RESULTS_DIR", default="out", help="Output dir (default: %(default)s)" @@ -42,7 +44,7 @@ def setup_parser(parser, completions=False): ) -def load_packages(): +def load_packages() -> None: """Load all packages so loading time doesn't impact solve times """ from rez.packages import iter_package_families @@ -111,14 +113,14 @@ def get_system_info(): return info -def do_resolves(): +def do_resolves() -> None: from rez import module_root_path from rez.resolved_context import ResolvedContext from rez.solver import SolverCallbackReturn filepath = os.path.join(module_root_path, "data", "benchmarking", "requests.json") - with open(filepath) as f: - requests = json.loads(f.read()) + with open(filepath) as fr: + requests = json.loads(fr.read()) print("Performing %d resolves..." % len(requests)) @@ -232,7 +234,7 @@ def callback(solver_state): f.write(stats_str) -def run_benchmark(): +def run_benchmark() -> None: from rez import module_root_path from rez.utils.execution import Popen @@ -258,7 +260,7 @@ def run_benchmark(): do_resolves() -def print_histogram(): +def print_histogram() -> None: n_rows = 40 n_columns = 40 resolve_times = [] @@ -307,7 +309,7 @@ def print_histogram(): start_t = end_t -def compare(): +def compare() -> None: out_dir2 = _opts.compare with open(os.path.join(out_dir, "resolves.json")) as f: @@ -356,7 +358,7 @@ def compare(): print(json.dumps(delta_summary, indent=2)) -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: global _opts global out_dir global pkg_repo_dir diff --git a/src/rez/cli/bind.py b/src/rez/cli/bind.py index a8fc5d13d..7a9b64123 100644 --- a/src/rez/cli/bind.py +++ b/src/rez/cli/bind.py @@ -5,10 +5,12 @@ ''' Create a Rez package for existing software. ''' +from __future__ import annotations + import argparse -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "--quickstart", action="store_true", help="bind a set of standard packages to get started") @@ -37,7 +39,7 @@ def setup_parser(parser, completions=False): "for the module") -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.config import config from rez.package_bind import bind_package, find_bind_module, \ get_bind_modules, _print_package_list diff --git a/src/rez/cli/build.py b/src/rez/cli/build.py index f39057934..8ddbfe452 100644 --- a/src/rez/cli/build.py +++ b/src/rez/cli/build.py @@ -5,17 +5,23 @@ ''' Build a package from source. ''' +from __future__ import annotations + import os +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from rez.developer_package import DeveloperPackage # Cache the developer package loaded from cwd. This is so the package is only # loaded once, even though it's required once at arg parsing time (to determine # valid build system types), and once at command run time. # -_package = None +_package: DeveloperPackage | None = None -def get_current_developer_package(): +def get_current_developer_package() -> DeveloperPackage: from rez.packages import get_developer_package global _package @@ -26,7 +32,7 @@ def get_current_developer_package(): return _package -def setup_parser_common(parser): +def setup_parser_common(parser) -> None: """Parser setup common to both rez-build and rez-release.""" from rez.build_process import get_build_process_types from rez.build_system import get_valid_build_systems @@ -77,7 +83,7 @@ def setup_parser_common(parser): "Alternatively, list these after a second '--'.") -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "-c", "--clean", action="store_true", help="clear the current build before rebuilding.") @@ -121,7 +127,7 @@ def get_build_args(opts, parser, extra_arg_groups): return result_groups[0], result_groups[1] -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.exceptions import BuildContextResolveError from rez.build_process import create_build_process from rez.build_system import create_build_system diff --git a/src/rez/cli/bundle.py b/src/rez/cli/bundle.py index 962c3ab01..9c96baffb 100644 --- a/src/rez/cli/bundle.py +++ b/src/rez/cli/bundle.py @@ -5,12 +5,14 @@ ''' Bundle a context and its packages into a relocatable dir. ''' +from __future__ import annotations + import os import os.path import sys -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: group = parser.add_mutually_exclusive_group() group.add_argument( "-s", "--skip-non-relocatable", action="store_true", @@ -29,7 +31,7 @@ def setup_parser(parser, completions=False): help="directory to create bundle in; must not exist") -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.utils.logging_ import print_error from rez.bundle_context import bundle_context from rez.resolved_context import ResolvedContext diff --git a/src/rez/cli/complete.py b/src/rez/cli/complete.py index 8a10d4b15..e73769099 100644 --- a/src/rez/cli/complete.py +++ b/src/rez/cli/complete.py @@ -5,26 +5,28 @@ """ Prints package completion strings. """ +from __future__ import annotations + import argparse __doc__ = argparse.SUPPRESS -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: pass -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.cli._util import subcommands import os import re # get comp info from environment variables comp_line = os.getenv("COMP_LINE", "") - comp_point = os.getenv("COMP_POINT", "") + comp_point_str = os.getenv("COMP_POINT", "") try: - comp_point = int(comp_point) + comp_point = int(comp_point_str) except: comp_point = len(comp_line) @@ -60,7 +62,7 @@ def _pop_arg(l, p): cmds = [k for k, v in subcommands.items() if not v.get("hidden")] if prefix: - cmds = (x for x in cmds if x.startswith(prefix)) + cmds = [x for x in cmds if x.startswith(prefix)] print(" ".join(cmds)) if subcommand not in subcommands: diff --git a/src/rez/cli/config.py b/src/rez/cli/config.py index c04ce1008..3a86b7326 100644 --- a/src/rez/cli/config.py +++ b/src/rez/cli/config.py @@ -5,10 +5,12 @@ ''' Print current rez settings. ''' +from __future__ import annotations + import json -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "--json", dest="json", action="store_true", help="Output dict/list field values as JSON. Useful for setting " diff --git a/src/rez/cli/context.py b/src/rez/cli/context.py index b549c7a9a..3d0ac0611 100644 --- a/src/rez/cli/context.py +++ b/src/rez/cli/context.py @@ -13,6 +13,8 @@ # Since features such as context tracking are related to context use only, we # disable them in this tool. # +from __future__ import annotations + import os import json import sys @@ -25,7 +27,7 @@ from rez.rex import OutputStyle # noqa -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: from rez.system import system from rez.shells import get_shell_types @@ -107,7 +109,7 @@ def setup_parser(parser, completions=False): diff_action.completer = rxt_completer -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.cli._util import print_items from rez.status import status from rez.utils.formatting import columnise, PackageRequest diff --git a/src/rez/cli/cp.py b/src/rez/cli/cp.py index 7b9c14231..d50d7d6b2 100644 --- a/src/rez/cli/cp.py +++ b/src/rez/cli/cp.py @@ -5,9 +5,10 @@ ''' Copy a package from one repository to another. ''' +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "--dest-path", metavar="PATH", help="package repository destination path. Defaults to the same " @@ -64,7 +65,7 @@ def setup_parser(parser, completions=False): pkg_action.completer = PackageCompleter -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: import os import sys diff --git a/src/rez/cli/depends.py b/src/rez/cli/depends.py index 40eb00263..3db392690 100644 --- a/src/rez/cli/depends.py +++ b/src/rez/cli/depends.py @@ -5,9 +5,10 @@ """ Perform a reverse package dependency lookup. """ +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "-d", "--depth", type=int, help="dependency tree depth limit") @@ -41,7 +42,7 @@ def setup_parser(parser, completions=False): PKG_action.completer = PackageFamilyCompleter -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> int | None: from rez.package_search import get_reverse_dependency_tree from rez.utils.graph_utils import save_graph, view_graph from rez.config import config @@ -81,3 +82,5 @@ def command(opts, parser, extra_arg_groups=None): else: toks = ["#%d:" % i] + pkgs print(' '.join(toks)) + + return None diff --git a/src/rez/cli/diff.py b/src/rez/cli/diff.py index 86fe9dbd5..15cc4c2b9 100644 --- a/src/rez/cli/diff.py +++ b/src/rez/cli/diff.py @@ -5,9 +5,10 @@ """ Compare the source code of two packages. """ +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: PKG1_action = parser.add_argument( "PKG1", type=str, help='package to diff') @@ -22,7 +23,7 @@ def setup_parser(parser, completions=False): PKG2_action.completer = PackageCompleter -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.packages import get_package_from_string from rez.utils.diff_packages import diff_packages diff --git a/src/rez/cli/env.py b/src/rez/cli/env.py index 9d8eb8aef..b431ab1bb 100644 --- a/src/rez/cli/env.py +++ b/src/rez/cli/env.py @@ -5,9 +5,10 @@ ''' Open a rez-configured shell, possibly interactive. ''' +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: from argparse import SUPPRESS from rez.config import config from rez.system import system @@ -143,7 +144,7 @@ def setup_parser(parser, completions=False): "extra_0", ExecutablesCompleter, FilesCompleter()) -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.resolved_context import ResolvedContext from rez.resolver import ResolverStatus from rez.package_filter import PackageFilterList, Rule diff --git a/src/rez/cli/forward.py b/src/rez/cli/forward.py index 97b9962ae..89a9f41de 100644 --- a/src/rez/cli/forward.py +++ b/src/rez/cli/forward.py @@ -3,12 +3,14 @@ """See util.create_forwarding_script().""" +from __future__ import annotations + import argparse __doc__ = argparse.SUPPRESS -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument("YAML", type=str) parser.add_argument("ARG", type=str, nargs=argparse.REMAINDER) diff --git a/src/rez/cli/gui.py b/src/rez/cli/gui.py index fa3ac3a79..e7c39c2c7 100644 --- a/src/rez/cli/gui.py +++ b/src/rez/cli/gui.py @@ -5,9 +5,10 @@ """ Run the Rez GUI application. """ +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "--diff", nargs=2, metavar=("RXT1", "RXT2"), help="open in diff mode with the given contexts") @@ -20,6 +21,6 @@ def setup_parser(parser, completions=False): FILE_action.completer = FilesCompleter() -def command(opts, parser=None, extra_arg_groups=None): +def command(opts, parser=None, extra_arg_groups=None) -> None: from rezgui.app import run run(opts, parser) diff --git a/src/rez/cli/help.py b/src/rez/cli/help.py index cc1c9771e..ab53edd44 100644 --- a/src/rez/cli/help.py +++ b/src/rez/cli/help.py @@ -7,7 +7,7 @@ """ -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument("-m", "--manual", dest="manual", action="store_true", default=False, help="Load the rez technical user manual") diff --git a/src/rez/cli/interpret.py b/src/rez/cli/interpret.py index 6c3f4a4d1..bd7148b71 100644 --- a/src/rez/cli/interpret.py +++ b/src/rez/cli/interpret.py @@ -5,9 +5,10 @@ ''' Execute some Rex code and print the interpreted result. ''' +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: from rez.shells import get_shell_types from rez.system import system @@ -38,7 +39,7 @@ def setup_parser(parser, completions=False): file_patterns=["*.py", "*.rex"]) -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.shells import create_shell from rez.utils.formatting import columnise from rez.rex import RexExecutor, Python diff --git a/src/rez/cli/memcache.py b/src/rez/cli/memcache.py index 961e2fe6e..c48714929 100644 --- a/src/rez/cli/memcache.py +++ b/src/rez/cli/memcache.py @@ -5,9 +5,10 @@ """ Manage and query memcache server(s). """ +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "--flush", action="store_true", help="flush all cache entries") @@ -28,7 +29,7 @@ def setup_parser(parser, completions=False): help="warm the cache server with visible packages") -def poll(client, interval): +def poll(client, interval) -> None: from rez.utils.memcached import Client import time @@ -74,7 +75,7 @@ def poll(client, interval): time.sleep(interval) -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.config import config from rez.packages import iter_package_families, iter_packages from rez.utils.yaml import dump_yaml @@ -124,7 +125,7 @@ def command(opts, parser, extra_arg_groups=None): print("memcached servers are stat reset.") return - def _fail(): + def _fail() -> None: print("memcached servers are not responding.", file=sys.stderr) sys.exit(1) diff --git a/src/rez/cli/mv.py b/src/rez/cli/mv.py index 803c6f280..d70f03dcd 100644 --- a/src/rez/cli/mv.py +++ b/src/rez/cli/mv.py @@ -5,9 +5,10 @@ ''' Move a package from one repository to another. ''' +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "-d", "--dest-path", metavar="PATH", required=True, help="package repository to move PKG to.") @@ -30,7 +31,7 @@ def setup_parser(parser, completions=False): pkg_action.completer = PackageCompleter -def list_repos_containing_pkg(pkg_name, pkg_version): +def list_repos_containing_pkg(pkg_name, pkg_version) -> None: from rez.config import config from rez.package_repository import package_repository_manager import sys @@ -52,7 +53,7 @@ def list_repos_containing_pkg(pkg_name, pkg_version): sys.exit(1) -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.version import VersionedObject from rez.packages import get_package_from_repository from rez.package_move import move_package diff --git a/src/rez/cli/pip.py b/src/rez/cli/pip.py index f7f916b97..66959f822 100644 --- a/src/rez/cli/pip.py +++ b/src/rez/cli/pip.py @@ -5,11 +5,13 @@ """ Install a pip-compatible python package, and its dependencies, as rez packages. """ +from __future__ import annotations + from argparse import REMAINDER import logging -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "--python-version", dest="py_ver", metavar="VERSION", help="python version (rez package) to use, default is latest. Note " @@ -34,7 +36,7 @@ def setup_parser(parser, completions=False): ) -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.config import config # debug_package_release is used by rez.pip._verbose diff --git a/src/rez/cli/pkg-cache.py b/src/rez/cli/pkg-cache.py index c35f27ecc..c9af12c64 100644 --- a/src/rez/cli/pkg-cache.py +++ b/src/rez/cli/pkg-cache.py @@ -5,12 +5,14 @@ ''' Manipulate a package cache. ''' +from __future__ import annotations + from argparse import SUPPRESS import os.path import sys -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: column_choices = ( "status", "package", @@ -66,7 +68,7 @@ def setup_parser(parser, completions=False): ) -def add_variant(pkgcache, uri, opts): +def add_variant(pkgcache, uri, opts) -> None: from rez.config import config from rez.packages import get_variant_from_uri from rez.utils.logging_ import print_info, print_warning @@ -99,7 +101,7 @@ def add_variant(pkgcache, uri, opts): print_info("Successfully cached to: %s", destpath) -def remove_variant(pkgcache, uri, opts): +def remove_variant(pkgcache, uri, opts) -> None: from rez.packages import get_variant_from_uri from rez.utils.logging_ import print_info, print_warning, print_error from rez.package_cache import PackageCache @@ -121,7 +123,7 @@ def remove_variant(pkgcache, uri, opts): print_info("Variant successfully removed") -def view_logs(pkgcache, opts): +def view_logs(pkgcache, opts) -> None: from rez.utils.logging_ import view_file_logs view_file_logs( @@ -130,7 +132,7 @@ def view_logs(pkgcache, opts): ) -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.config import config from rez.package_cache import PackageCache from rez.utils.formatting import print_colored_columns diff --git a/src/rez/cli/pkg-ignore.py b/src/rez/cli/pkg-ignore.py index 0b3dd8261..b67bfb229 100644 --- a/src/rez/cli/pkg-ignore.py +++ b/src/rez/cli/pkg-ignore.py @@ -5,9 +5,10 @@ ''' Disable a package so it is hidden from resolves. ''' +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "-u", "--unignore", action="store_true", help="Unignore a package.") @@ -27,7 +28,7 @@ def setup_parser(parser, completions=False): PKG_action.completer = PackageCompleter -def list_repos(): +def list_repos() -> None: from rez.config import config from rez.package_repository import package_repository_manager @@ -38,7 +39,7 @@ def list_repos(): print(str(repo)) -def list_repos_containing_pkg(pkg_name, pkg_version): +def list_repos_containing_pkg(pkg_name, pkg_version) -> None: from rez.config import config from rez.package_repository import package_repository_manager import sys @@ -60,7 +61,7 @@ def list_repos_containing_pkg(pkg_name, pkg_version): sys.exit(1) -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.package_repository import package_repository_manager from rez.version import VersionedObject import sys diff --git a/src/rez/cli/plugins.py b/src/rez/cli/plugins.py index 4e26bbd7b..cce1b995e 100644 --- a/src/rez/cli/plugins.py +++ b/src/rez/cli/plugins.py @@ -5,9 +5,10 @@ """ Get a list of a package's plugins. """ +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "--paths", type=str, default=None, help="set package search path") @@ -20,7 +21,7 @@ def setup_parser(parser, completions=False): PKG_action.completer = PackageFamilyCompleter -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.package_search import get_plugins from rez.config import config import os diff --git a/src/rez/cli/python.py b/src/rez/cli/python.py index 01e69944e..26b0b3199 100644 --- a/src/rez/cli/python.py +++ b/src/rez/cli/python.py @@ -7,9 +7,10 @@ Unrecognised args are passed directly to the underlying python interpreter. """ +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: file_action = parser.add_argument( "FILE", type=str, nargs='?', help='python script to execute') @@ -20,7 +21,7 @@ def setup_parser(parser, completions=False): file_patterns=["*.py"]) -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.cli._main import is_hyphened_command from rez.utils.execution import Popen import sys diff --git a/src/rez/cli/release.py b/src/rez/cli/release.py index 275f597ac..3b708db96 100644 --- a/src/rez/cli/release.py +++ b/src/rez/cli/release.py @@ -5,12 +5,14 @@ ''' Build a package from source and deploy it. ''' +from __future__ import annotations + import os import sys from subprocess import call -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: from rez.cli.build import setup_parser_common from rez.release_vcs import get_release_vcs_types @@ -40,7 +42,7 @@ def setup_parser(parser, completions=False): setup_parser_common(parser) -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.build_process import create_build_process from rez.build_system import create_build_system from rez.release_vcs import create_release_vcs diff --git a/src/rez/cli/rm.py b/src/rez/cli/rm.py index 6433f0d57..cdf5afda4 100644 --- a/src/rez/cli/rm.py +++ b/src/rez/cli/rm.py @@ -5,10 +5,12 @@ ''' Remove package(s) from a repository. ''' +from __future__ import annotations + import sys -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: group = parser.add_mutually_exclusive_group() group.add_argument( "-p", "--package", @@ -34,7 +36,7 @@ def setup_parser(parser, completions=False): " to remove.") -def remove_package(opts, parser): +def remove_package(opts, parser) -> None: from rez.version import VersionedObject from rez.package_remove import remove_package @@ -53,7 +55,7 @@ def remove_package(opts, parser): sys.exit(1) -def remove_package_family(opts, parser, force=False): +def remove_package_family(opts, parser, force: bool = False) -> None: from rez.version import VersionedObject from rez.package_remove import remove_package_family from rez.exceptions import PackageRepositoryError @@ -82,7 +84,7 @@ def remove_package_family(opts, parser, force=False): sys.exit(1) -def remove_ignored_since(opts, parser): +def remove_ignored_since(opts, parser) -> None: from rez.package_remove import remove_packages_ignored_since if opts.PATH: @@ -106,7 +108,7 @@ def remove_ignored_since(opts, parser): print("No packages were removed.") -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: if opts.package: remove_package(opts, parser) elif opts.family: diff --git a/src/rez/cli/search.py b/src/rez/cli/search.py index a0a180d3d..03b7689df 100644 --- a/src/rez/cli/search.py +++ b/src/rez/cli/search.py @@ -5,11 +5,13 @@ """ Search for packages """ +from __future__ import annotations + import os import sys -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: from rez.package_search import ResourceSearchResultFormatter type_choices = ("package", "family", "variant", "auto") @@ -64,7 +66,7 @@ def setup_parser(parser, completions=False): PKG_action.completer = PackageCompleter -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.package_search import ResourceSearcher, ResourceSearchResultFormatter from rez.utils.formatting import get_epoch_time_from_str from rez.config import config diff --git a/src/rez/cli/selftest.py b/src/rez/cli/selftest.py index ec4d0e39d..128d9bec0 100644 --- a/src/rez/cli/selftest.py +++ b/src/rez/cli/selftest.py @@ -5,6 +5,7 @@ ''' Run unit tests. Use pytest if available. ''' +from __future__ import annotations import os import sys @@ -27,7 +28,7 @@ all_module_tests = [] -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False): parser.add_argument( "tests", metavar="NAMED_TEST", default=[], nargs="*", help="a specific test module/class/method to run; may be repeated " @@ -45,7 +46,7 @@ def setup_parser(parser, completions=False): # make an Action that will append the appropriate test to the "--test" arg class AddTestModuleAction(argparse.Action): - def __call__(self, parser, namespace, values, option_string=None): + def __call__(self, parser, namespace, values, option_string=None) -> None: name = option_string.lstrip('-') if getattr(namespace, "module_tests", None) is None: namespace.module_tests = [] @@ -72,7 +73,7 @@ def __call__(self, parser, namespace, values, option_string=None): help=module.__doc__.strip().rstrip('.')) -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: os.environ["__REZ_SELFTEST_RUNNING"] = "1" if opts.only_shell: @@ -101,7 +102,7 @@ def command(opts, parser, extra_arg_groups=None): shutil.rmtree(repo) -def run_unittest(module_tests, tests, verbosity): +def run_unittest(module_tests, tests, verbosity) -> None: from unittest.main import main module_tests = [("rez.tests.test_%s" % x) for x in sorted(module_tests)] @@ -111,7 +112,7 @@ def run_unittest(module_tests, tests, verbosity): main(module=None, argv=argv, verbosity=verbosity) -def run_pytest(module_tests, tests, verbosity, extra_arg_groups): +def run_pytest(module_tests, tests, verbosity, extra_arg_groups) -> None: from pytest import main tests_dir = os.path.abspath(os.path.join(__file__, "..", "..", "tests")) @@ -146,17 +147,17 @@ def run_pytest(module_tests, tests, verbosity, extra_arg_groups): sys.exit(exitcode) -def create_python_package(repo): +def create_python_package(repo) -> None: from rez.package_maker import make_package from rez.utils.lint_helper import env, system import venv print("Creating python package in {0!r}".format(repo)) - def make_root(variant, root): + def make_root(variant, root) -> None: venv.create(root) - def commands(): + def commands() -> None: if system.platform == "windows": env.PATH.prepend("{this.root}/Scripts") else: diff --git a/src/rez/cli/status.py b/src/rez/cli/status.py index 1e702483d..faf6f9f63 100644 --- a/src/rez/cli/status.py +++ b/src/rez/cli/status.py @@ -5,9 +5,10 @@ ''' Report current status of the environment, or a tool or package etc. ''' +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: tools_action = parser.add_argument( "-t", "--tools", action="store_true", help="List visible tools. In this mode, OBJECT can be a glob pattern " @@ -22,7 +23,7 @@ def setup_parser(parser, completions=False): tools_action.completer = ExecutablesCompleter -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.status import status import sys diff --git a/src/rez/cli/suite.py b/src/rez/cli/suite.py index 02f34106b..8b6739777 100644 --- a/src/rez/cli/suite.py +++ b/src/rez/cli/suite.py @@ -5,9 +5,10 @@ ''' Manage a suite or print information about an existing suite. ''' +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "-l", "--list", action="store_true", help="list visible suites") @@ -85,7 +86,7 @@ def setup_parser(parser, completions=False): find_resolve_action.completer = PackageCompleter -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.suite import Suite from rez.status import status from rez.exceptions import SuiteError @@ -97,7 +98,7 @@ def command(opts, parser, extra_arg_groups=None): save_needed = set(("add", "remove", "bump", "prefix", "suffix", "hide", "unhide", "alias", "unalias")) - def _pr(s): + def _pr(s) -> None: if opts.verbose: print(s) diff --git a/src/rez/cli/test.py b/src/rez/cli/test.py index 0a8552d03..bb2deeea9 100644 --- a/src/rez/cli/test.py +++ b/src/rez/cli/test.py @@ -5,9 +5,10 @@ ''' Run tests listed in a package's definition file. ''' +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "-l", "--list", action="store_true", help="list package's tests and exit") @@ -43,7 +44,7 @@ def setup_parser(parser, completions=False): PKG_action.completer = PackageCompleter -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.package_test import PackageTestRunner from rez.config import config import os.path diff --git a/src/rez/cli/view.py b/src/rez/cli/view.py index 6e3cd5804..849f74c7f 100644 --- a/src/rez/cli/view.py +++ b/src/rez/cli/view.py @@ -5,9 +5,10 @@ """ View the contents of a package. """ +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: formats = ("py", "yaml") parser.add_argument( "-f", "--format", default="yaml", choices=formats, @@ -30,7 +31,7 @@ def setup_parser(parser, completions=False): PKG_action.completer = PackageCompleter -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.utils.formatting import PackageRequest from rez.serialise import FileFormat from rez.packages import iter_packages diff --git a/src/rez/cli/yaml2py.py b/src/rez/cli/yaml2py.py index 66ee206ee..aae32eac8 100644 --- a/src/rez/cli/yaml2py.py +++ b/src/rez/cli/yaml2py.py @@ -5,16 +5,17 @@ """ Print a package.yaml file in package.py format. """ +from __future__ import annotations -def setup_parser(parser, completions=False): +def setup_parser(parser, completions: bool = False) -> None: parser.add_argument( "PATH", type=str, nargs='?', help="path to yaml to convert, or directory to search for package.yaml;" " cwd if not provided") -def command(opts, parser, extra_arg_groups=None): +def command(opts, parser, extra_arg_groups=None) -> None: from rez.packages import get_developer_package from rez.serialise import FileFormat from rez.exceptions import PackageMetadataError diff --git a/src/rez/command.py b/src/rez/command.py index 3cc0696a0..0adef8ba6 100644 --- a/src/rez/command.py +++ b/src/rez/command.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.config import config @@ -48,11 +50,11 @@ def register_plugin(): return CommandFoo """ - def __init__(self): + def __init__(self) -> None: self.type_settings = config.plugins.extension self.settings = self.type_settings.get(self.name()) @classmethod - def name(cls): + def name(cls) -> str: """Return the name of the Command and rez-subcommand.""" raise NotImplementedError diff --git a/src/rez/config.py b/src/rez/config.py index 56b4ca3f2..85456b4a3 100644 --- a/src/rez/config.py +++ b/src/rez/config.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez import __version__ from rez.utils.data_utils import AttrDictWrapper, RO_AttrDictWrapper, \ convert_dicts, cached_property, cached_class_property, LazyAttributeMeta, \ @@ -15,6 +17,7 @@ from rez.vendor.schema.schema import Schema, SchemaError, And, Or, Use from rez.vendor import yaml from rez.vendor.yaml.error import YAMLError +from rez.utils.typing import Protocol import rez.deprecations from contextlib import contextmanager from functools import lru_cache @@ -22,14 +25,23 @@ import os import re import copy +from typing import Any, TypeVar, TYPE_CHECKING + + +T = TypeVar("T") + + +class Validatable(Protocol): + def validate(self, data: T) -> T: + pass class _Deprecation(object): - def __init__(self, removed_in, extra=None): + def __init__(self, removed_in, extra=None) -> None: self.__removed_in = removed_in self.__extra = extra or "" - def get_message(self, name, env_var=False): + def get_message(self, name: str, env_var: bool | str = False): if self.__removed_in: return ( "config setting named {0!r} {1}is " @@ -54,20 +66,20 @@ class Setting(object): Note that lazy setting validation only happens on main configuration settings - plugin settings are validated on load only. """ - schema = Schema(object) + schema: Validatable = Schema(object) - def __init__(self, config, key): + def __init__(self, config, key) -> None: self.config = config self.key = key @property - def _env_var_name(self): + def _env_var_name(self) -> str: return "REZ_%s" % self.key.upper() def _parse_env_var(self, value): raise NotImplementedError - def validate(self, data): + def validate(self, data: Any) -> Any: try: data = self._validate(data) data = self.schema.validate(data) @@ -135,7 +147,7 @@ def _validate(self, data): class Str(Setting): - schema = Schema(str) + schema: Validatable = Schema(str) def _parse_env_var(self, value): return value @@ -153,7 +165,7 @@ class OptionalStr(Str): class StrList(Setting): - schema = Schema([str]) + schema: Validatable = Schema([str]) sep = ',' def _parse_env_var(self, value): @@ -170,7 +182,7 @@ class PipInstallRemaps(Setting): schema = Schema([{key: And(str, len) for key in KEYS}]) - def validate(self, data): + def validate(self, data: list) -> list: """Extended to substitute regex-escaped path tokens.""" return [ { @@ -184,8 +196,7 @@ def validate(self, data): class OptionalStrList(StrList): - schema = Or(And(None, Use(lambda x: [])), - [str]) + schema = Or(And(None, Use(lambda x: [])), [str]) class PathList(StrList): @@ -219,12 +230,12 @@ def _parse_env_var(self, value): class Bool(Setting): - schema = Schema(bool) + schema: Validatable = Schema(bool) true_words = frozenset(["1", "true", "t", "yes", "y", "on"]) false_words = frozenset(["0", "false", "f", "no", "n", "off"]) all_words = true_words | false_words - def _parse_env_var(self, value): + def _parse_env_var(self, value) -> bool: value = value.lower() if value in self.true_words: return True @@ -255,7 +266,7 @@ def _parse_env_var(self, value): class Dict(Setting): - schema = Schema(dict) + schema: Validatable = Schema(dict) def _parse_env_var(self, value): items = value.split(",") @@ -549,7 +560,14 @@ class Config(object, metaclass=LazyAttributeMeta): schema = config_schema schema_error = ConfigurationError - def __init__(self, filepaths, overrides=None, locked=False): + if TYPE_CHECKING: + # mypy: The use of LazyAttributeMeta means that this class generates hundreds + # of spurious attribute errors. Adding this for the type analysis will silence + # them until the use of LazyAttributeMeta can be addressed. + def __getattr__(self, item: str) -> Any: + pass + + def __init__(self, filepaths: list[str], overrides=None, locked: bool = False) -> None: """Create a config. Args: @@ -560,7 +578,7 @@ def __init__(self, filepaths, overrides=None, locked=False): ignored. """ self.filepaths = filepaths - self._sourced_filepaths = None + self._sourced_filepaths: list[str] | None = None self.overrides = overrides or {} self.locked = locked @@ -568,7 +586,7 @@ def get(self, key, default=None): """Get the value of a setting.""" return getattr(self, key, default) - def copy(self, overrides=None, locked=False): + def copy(self, overrides=None, locked: bool = False) -> Config: """Create a separate copy of this config.""" other = copy.copy(self) @@ -580,7 +598,7 @@ def copy(self, overrides=None, locked=False): other._uncache() return other - def override(self, key, value): + def override(self, key: str, value): """Set a setting to the given value. Note that `key` can be in dotted form, eg @@ -595,10 +613,10 @@ def override(self, key, value): self.overrides[key] = value self._uncache(key) - def is_overridden(self, key): + def is_overridden(self, key: str) -> bool: return (key in self.overrides) - def remove_override(self, key): + def remove_override(self, key: str): """Remove a setting override, if one exists.""" keys = key.split('.') if len(keys) > 1: @@ -607,27 +625,27 @@ def remove_override(self, key): del self.overrides[key] self._uncache(key) - def warn(self, key): + def warn(self, key: str): """Returns True if the warning setting is enabled.""" return ( not self.quiet and not self.warn_none and (self.warn_all or getattr(self, "warn_%s" % key)) ) - def debug(self, key): + def debug(self, key: str): """Returns True if the debug setting is enabled.""" return ( not self.quiet and not self.debug_none and (self.debug_all or getattr(self, "debug_%s" % key)) ) - def debug_printer(self, key): + def debug_printer(self, key: str): """Returns a printer object suitably enabled based on the given key.""" enabled = self.debug(key) return get_debug_printer(enabled) @cached_property - def sourced_filepaths(self): + def sourced_filepaths(self) -> list[str]: """Get the list of files actually sourced to create the config. Note: @@ -643,7 +661,7 @@ def sourced_filepaths(self): return self._sourced_filepaths @cached_property - def plugins(self): + def plugins(self) -> _PluginConfigs: """Plugin settings are loaded lazily, to avoid loading the plugins until necessary.""" plugin_data = self._data.get("plugins", {}) @@ -699,7 +717,7 @@ def _get_plugin_completions(prefix_): keys += _get_plugin_completions('') return keys - def _uncache(self, key=None): + def _uncache(self, key=None) -> None: # deleting the attribute falls up back to the class attribute, which is # the cached_property descriptor if key and hasattr(self, key): @@ -713,7 +731,7 @@ def _uncache(self, key=None): if hasattr(self, "plugins"): delattr(self, "plugins") - def _swap(self, other): + def _swap(self, other) -> None: """Swap this config with another. This is used by the unit tests to swap the config to one that is @@ -749,7 +767,7 @@ def _data(self): return data @classmethod - def _create_main_config(cls, overrides=None): + def _create_main_config(cls, overrides=None) -> Config: """See comment block at top of 'rezconfig' describing how the main config is assembled.""" filepaths = [] @@ -764,11 +782,11 @@ def _create_main_config(cls, overrides=None): return Config(filepaths, overrides) - def __str__(self): + def __str__(self) -> str: keys = (x for x in self.schema._schema if isinstance(x, str)) return "%r" % sorted(list(keys) + ["plugins"]) - def __repr__(self): + def __repr__(self) -> str: return "%s(%s)" % (self.__class__.__name__, str(self)) # -- dynamic defaults @@ -804,14 +822,14 @@ def _get_new_session_popen_args(self): class _PluginConfigs(object): """Lazy config loading for plugins.""" - def __init__(self, plugin_data): + def __init__(self, plugin_data) -> None: self.__dict__['_data'] = plugin_data - def __setattr__(self, attr, value): + def __setattr__(self, attr, value) -> None: raise AttributeError("'%s' object attribute '%s' is read-only" % (self.__class__.__name__, attr)) - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> RO_AttrDictWrapper: if attr in self.__dict__: return self.__dict__[attr] @@ -845,7 +863,7 @@ def __iter__(self): from rez.plugin_managers import plugin_manager return iter(plugin_manager.get_plugin_types()) - def override(self, key, value): + def override(self, key, value) -> None: def _nosuch(): raise AttributeError("no such setting: %r" % '.'.join(key)) if len(key) < 2: @@ -880,21 +898,21 @@ def data(self): d = convert_dicts(d, dict, (dict, AttrDictWrapper)) return d - def __str__(self): + def __str__(self) -> str: from rez.plugin_managers import plugin_manager return "%r" % sorted(plugin_manager.get_plugin_types()) - def __repr__(self): + def __repr__(self) -> str: return "%s(%s)" % (self.__class__.__name__, str(self)) -def expand_system_vars(data): +def expand_system_vars(data: T) -> T: """Expands any strings within `data` such as '{system.user}'.""" def _expanded(value): if isinstance(value, str): - value = expandvars(value) - value = expanduser(value) - return scoped_format(value, system=system) + str_value = expandvars(value) + str_value = expanduser(str_value) + return scoped_format(str_value, system=system) elif isinstance(value, (list, tuple, set)): return [_expanded(x) for x in value] elif isinstance(value, dict): @@ -904,7 +922,7 @@ def _expanded(value): return _expanded(data) -def create_config(overrides=None): +def create_config(overrides=None) -> Config: """Create a configuration based on the global config. """ if not overrides: @@ -940,7 +958,7 @@ def _replace_config(other): @lru_cache() -def _load_config_py(filepath): +def _load_config_py(filepath: str) -> dict[str, Any]: reserved = dict( # Standard Python module variables # Made available from within the module, @@ -974,7 +992,7 @@ def _load_config_py(filepath): @lru_cache() -def _load_config_yaml(filepath): +def _load_config_yaml(filepath: str) -> dict[str, Any]: with open(filepath) as f: content = f.read() try: @@ -989,7 +1007,7 @@ def _load_config_yaml(filepath): return doc -def _load_config_from_filepaths(filepaths): +def _load_config_from_filepaths(filepaths: list[str]) -> tuple[dict[str, Any], list[str]]: data = {} sourced_filepaths = [] loaders = ((".py", _load_config_py), @@ -1029,7 +1047,7 @@ def _load_config_from_filepaths(filepaths): return data, sourced_filepaths -def get_module_root_config(): +def get_module_root_config() -> str: return os.path.join(module_root_path, "rezconfig.py") diff --git a/src/rez/data/tests/builds/packages/anti/1.0.0/package.py b/src/rez/data/tests/builds/packages/anti/1.0.0/package.py index 40330ffa9..e982b43f7 100644 --- a/src/rez/data/tests/builds/packages/anti/1.0.0/package.py +++ b/src/rez/data/tests/builds/packages/anti/1.0.0/package.py @@ -8,7 +8,7 @@ private_build_requires = ["build_util", "python"] requires = ["floob", "!loco"] -def commands(): +def commands() -> None: env.PYTHONPATH.append('{root}/python') build_command = 'python {root}/build.py {install}' diff --git a/src/rez/data/tests/builds/packages/bah/2.1/build.py b/src/rez/data/tests/builds/packages/bah/2.1/build.py index 56cabebc4..f29bd12cd 100644 --- a/src/rez/data/tests/builds/packages/bah/2.1/build.py +++ b/src/rez/data/tests/builds/packages/bah/2.1/build.py @@ -4,7 +4,7 @@ -def build(source_path, build_path, install_path, targets): +def build(source_path, build_path, install_path, targets) -> None: # normal requirement 'foo' should be visible check_visible("bah", "foo") diff --git a/src/rez/data/tests/builds/packages/build_util/1/build.py b/src/rez/data/tests/builds/packages/build_util/1/build.py index 0d51f61eb..e5029f35e 100644 --- a/src/rez/data/tests/builds/packages/build_util/1/build.py +++ b/src/rez/data/tests/builds/packages/build_util/1/build.py @@ -2,9 +2,9 @@ import os.path -def build(source_path, build_path, install_path, targets): +def build(source_path, build_path, install_path, targets) -> None: - def _copy(src, dest): + def _copy(src, dest) -> None: print("copying %s to %s..." % (src, dest)) if os.path.exists(dest): shutil.rmtree(dest) diff --git a/src/rez/data/tests/builds/packages/build_util/1/build_util/__init__.py b/src/rez/data/tests/builds/packages/build_util/1/build_util/__init__.py index a516190d6..cf5c5af4f 100644 --- a/src/rez/data/tests/builds/packages/build_util/1/build_util/__init__.py +++ b/src/rez/data/tests/builds/packages/build_util/1/build_util/__init__.py @@ -4,9 +4,9 @@ def build_directory_recurse(src_dir, dest_dir, source_path, build_path, - install_path=None): + install_path=None) -> None: - def _copy(src, dest): + def _copy(src, dest) -> None: print("copying %s to %s..." % (src, dest)) if os.path.exists(dest): shutil.rmtree(dest) diff --git a/src/rez/data/tests/builds/packages/build_util/1/package.py b/src/rez/data/tests/builds/packages/build_util/1/package.py index a44d1a7b9..758ce92e3 100644 --- a/src/rez/data/tests/builds/packages/build_util/1/package.py +++ b/src/rez/data/tests/builds/packages/build_util/1/package.py @@ -6,7 +6,7 @@ private_build_requires = ["python"] -def commands(): +def commands() -> None: env.PYTHONPATH.append('{root}/python') build_command = 'python {root}/build.py {install}' diff --git a/src/rez/data/tests/builds/packages/floob/build.py b/src/rez/data/tests/builds/packages/floob/build.py index 1bf65031c..89b7ac54a 100644 --- a/src/rez/data/tests/builds/packages/floob/build.py +++ b/src/rez/data/tests/builds/packages/floob/build.py @@ -2,7 +2,7 @@ import os.path -def build(source_path, build_path, install_path, targets): +def build(source_path, build_path, install_path, targets) -> None: if "install" not in (targets or []): install_path = None diff --git a/src/rez/data/tests/builds/packages/floob/floob/__init__.py b/src/rez/data/tests/builds/packages/floob/floob/__init__.py index 035c723a1..e00de0551 100644 --- a/src/rez/data/tests/builds/packages/floob/floob/__init__.py +++ b/src/rez/data/tests/builds/packages/floob/floob/__init__.py @@ -1,2 +1,2 @@ -def hello(): +def hello() -> str: return "yes this is floob" diff --git a/src/rez/data/tests/builds/packages/floob/package.py b/src/rez/data/tests/builds/packages/floob/package.py index 5495dfe2b..4568dcaed 100644 --- a/src/rez/data/tests/builds/packages/floob/package.py +++ b/src/rez/data/tests/builds/packages/floob/package.py @@ -9,7 +9,7 @@ private_build_requires = ["build_util", "python"] -def commands(): +def commands() -> None: env.PYTHONPATH.append('{root}/python') build_command = 'python {root}/build.py {install}' diff --git a/src/rez/data/tests/builds/packages/foo/1.0.0/foo/__init__.py b/src/rez/data/tests/builds/packages/foo/1.0.0/foo/__init__.py index 12039d6c6..f9ea7c026 100644 --- a/src/rez/data/tests/builds/packages/foo/1.0.0/foo/__init__.py +++ b/src/rez/data/tests/builds/packages/foo/1.0.0/foo/__init__.py @@ -2,5 +2,5 @@ __version__ = os.getenv("REZ_FOO_VERSION") -def report(): +def report() -> str: return "hello from foo-%s" % __version__ diff --git a/src/rez/data/tests/builds/packages/foo/1.0.0/package.py b/src/rez/data/tests/builds/packages/foo/1.0.0/package.py index 9ced5fa48..ef552fbf4 100644 --- a/src/rez/data/tests/builds/packages/foo/1.0.0/package.py +++ b/src/rez/data/tests/builds/packages/foo/1.0.0/package.py @@ -8,10 +8,10 @@ private_build_requires = ["build_util", "python"] -def pre_build_commands(): +def pre_build_commands() -> None: env.FOO_TEST_VAR = "hello" -def commands(): +def commands() -> None: env.PYTHONPATH.append('{root}/python') build_command = 'python {root}/build.py {install}' diff --git a/src/rez/data/tests/builds/packages/foo/1.1.0/build.py b/src/rez/data/tests/builds/packages/foo/1.1.0/build.py index 93981be70..46708f9fe 100644 --- a/src/rez/data/tests/builds/packages/foo/1.1.0/build.py +++ b/src/rez/data/tests/builds/packages/foo/1.1.0/build.py @@ -2,7 +2,7 @@ import os.path -def build(source_path, build_path, install_path, targets): +def build(source_path, build_path, install_path, targets) -> None: # build requirement 'floob' should be visible check_visible("foo", "floob") diff --git a/src/rez/data/tests/builds/packages/foo/1.1.0/foo/__init__.py b/src/rez/data/tests/builds/packages/foo/1.1.0/foo/__init__.py index 12039d6c6..f9ea7c026 100644 --- a/src/rez/data/tests/builds/packages/foo/1.1.0/foo/__init__.py +++ b/src/rez/data/tests/builds/packages/foo/1.1.0/foo/__init__.py @@ -2,5 +2,5 @@ __version__ = os.getenv("REZ_FOO_VERSION") -def report(): +def report() -> str: return "hello from foo-%s" % __version__ diff --git a/src/rez/data/tests/builds/packages/foo/1.1.0/package.py b/src/rez/data/tests/builds/packages/foo/1.1.0/package.py index 33be656fa..dc9d9073e 100644 --- a/src/rez/data/tests/builds/packages/foo/1.1.0/package.py +++ b/src/rez/data/tests/builds/packages/foo/1.1.0/package.py @@ -9,7 +9,7 @@ private_build_requires = ["build_util", "python"] @include("late_utils") -def commands(): +def commands() -> None: env.PYTHONPATH.append('{root}/python') env.FOO_IN_DA_HOUSE = "1" diff --git a/src/rez/data/tests/builds/packages/hello/1.0/package.py b/src/rez/data/tests/builds/packages/hello/1.0/package.py index cbbd0403c..f7da72e1e 100644 --- a/src/rez/data/tests/builds/packages/hello/1.0/package.py +++ b/src/rez/data/tests/builds/packages/hello/1.0/package.py @@ -8,5 +8,5 @@ build_command = "make -f {root}/Makefile {install}" -def commands(): +def commands() -> None: env.PATH.append('{root}/bin') diff --git a/src/rez/data/tests/builds/packages/sup_world/3.8/package.py b/src/rez/data/tests/builds/packages/sup_world/3.8/package.py index 93b8c7017..db0de3aea 100644 --- a/src/rez/data/tests/builds/packages/sup_world/3.8/package.py +++ b/src/rez/data/tests/builds/packages/sup_world/3.8/package.py @@ -9,5 +9,5 @@ tools = ['greeter'] -def commands(): +def commands() -> None: env.PATH.append('{root}/bin') diff --git a/src/rez/data/tests/builds/packages/testing_obj/1.0.0/build.py b/src/rez/data/tests/builds/packages/testing_obj/1.0.0/build.py index 21e8b9337..95bbf5b35 100644 --- a/src/rez/data/tests/builds/packages/testing_obj/1.0.0/build.py +++ b/src/rez/data/tests/builds/packages/testing_obj/1.0.0/build.py @@ -2,7 +2,7 @@ import os.path -def build(source_path, build_path, install_path, targets): +def build(source_path, build_path, install_path, targets) -> None: if "install" not in (targets or []): install_path = None diff --git a/src/rez/data/tests/builds/packages/testing_obj/1.0.0/package.py b/src/rez/data/tests/builds/packages/testing_obj/1.0.0/package.py index 162d7742a..550edbc17 100644 --- a/src/rez/data/tests/builds/packages/testing_obj/1.0.0/package.py +++ b/src/rez/data/tests/builds/packages/testing_obj/1.0.0/package.py @@ -12,7 +12,7 @@ def requires(): private_build_requires = ["build_util", "python"] -def commands(): +def commands() -> None: env.PYTHONPATH.append('{root}/python') if testing: env.CAR_IDEA = "STURDY STEERING WHEEL" diff --git a/src/rez/data/tests/builds/packages/testing_obj/1.0.0/testing_obj/__init__.py b/src/rez/data/tests/builds/packages/testing_obj/1.0.0/testing_obj/__init__.py index fcab6ab00..e11fc0e04 100644 --- a/src/rez/data/tests/builds/packages/testing_obj/1.0.0/testing_obj/__init__.py +++ b/src/rez/data/tests/builds/packages/testing_obj/1.0.0/testing_obj/__init__.py @@ -1,2 +1,2 @@ -def hello(): +def hello() -> str: return "This shirt was $150 out the door and the pattern's not that complicated" diff --git a/src/rez/data/tests/builds/packages/translate_lib/2.2.0/package.py b/src/rez/data/tests/builds/packages/translate_lib/2.2.0/package.py index 05e89d831..c09fd2b63 100644 --- a/src/rez/data/tests/builds/packages/translate_lib/2.2.0/package.py +++ b/src/rez/data/tests/builds/packages/translate_lib/2.2.0/package.py @@ -6,7 +6,7 @@ description = "A simple C++ library with minimal dependencies." -def commands(): +def commands() -> None: import platform env.CMAKE_MODULE_PATH.append("{root}/cmake") diff --git a/src/rez/data/tests/commands/packages/rextest/1.3/package.py b/src/rez/data/tests/commands/packages/rextest/1.3/package.py index cc062fe1f..c7f4f9f28 100644 --- a/src/rez/data/tests/commands/packages/rextest/1.3/package.py +++ b/src/rez/data/tests/commands/packages/rextest/1.3/package.py @@ -1,7 +1,7 @@ name = 'rextest' version = '1.3' -def commands(): +def commands() -> None: env.REXTEST_ROOT = '{root}' env.REXTEST_VERSION = this.version env.REXTEST_MAJOR_VERSION = this.version.major diff --git a/src/rez/data/tests/commands/packages/rextest2/2/package.py b/src/rez/data/tests/commands/packages/rextest2/2/package.py index 8f4a9032c..416f30685 100644 --- a/src/rez/data/tests/commands/packages/rextest2/2/package.py +++ b/src/rez/data/tests/commands/packages/rextest2/2/package.py @@ -3,7 +3,7 @@ requires = ["rextest-1.3"] -def commands(): +def commands() -> None: # prepend to existing var env.REXTEST_DIRS.append('{root}/data2') setenv("REXTEST2_REXTEST_VER", '{resolve.rextest.version}') diff --git a/src/rez/data/tests/extensions/bar/rezplugins/package_repository/memory.py b/src/rez/data/tests/extensions/bar/rezplugins/package_repository/memory.py index db9805e2a..6dcf2bc90 100644 --- a/src/rez/data/tests/extensions/bar/rezplugins/package_repository/memory.py +++ b/src/rez/data/tests/extensions/bar/rezplugins/package_repository/memory.py @@ -3,7 +3,7 @@ class MemoryPackageRepository(PackageRepository): @classmethod - def name(cls): + def name(cls) -> str: return "memory" on_test = "bar" diff --git a/src/rez/data/tests/extensions/foo/rezplugins/package_repository/cloud.py b/src/rez/data/tests/extensions/foo/rezplugins/package_repository/cloud.py index 4e7fea636..38597e5c3 100644 --- a/src/rez/data/tests/extensions/foo/rezplugins/package_repository/cloud.py +++ b/src/rez/data/tests/extensions/foo/rezplugins/package_repository/cloud.py @@ -3,7 +3,7 @@ class CloudPackageRepository(PackageRepository): @classmethod - def name(cls): + def name(cls) -> str: return "cloud" diff --git a/src/rez/data/tests/extensions/non-mod/rezplugins/package_repository/memory.py b/src/rez/data/tests/extensions/non-mod/rezplugins/package_repository/memory.py index ccc3079a5..633087a09 100644 --- a/src/rez/data/tests/extensions/non-mod/rezplugins/package_repository/memory.py +++ b/src/rez/data/tests/extensions/non-mod/rezplugins/package_repository/memory.py @@ -3,7 +3,7 @@ class MemoryPackageRepository(PackageRepository): @classmethod - def name(cls): + def name(cls) -> str: return "memory" on_test = "non-mod" diff --git a/src/rez/data/tests/packages/developer_dynamic_global_preprocess/package.py b/src/rez/data/tests/packages/developer_dynamic_global_preprocess/package.py index f36c4b9ee..91f0f605e 100644 --- a/src/rez/data/tests/packages/developer_dynamic_global_preprocess/package.py +++ b/src/rez/data/tests/packages/developer_dynamic_global_preprocess/package.py @@ -1,7 +1,7 @@ name = "developer_dynamic_global_preprocess" @early() -def description(): +def description() -> str: return "This." # make sure imported modules don't break developer packages diff --git a/src/rez/data/tests/packages/developer_dynamic_local_preprocess/package.py b/src/rez/data/tests/packages/developer_dynamic_local_preprocess/package.py index bb35f2a5c..4b9315efa 100644 --- a/src/rez/data/tests/packages/developer_dynamic_local_preprocess/package.py +++ b/src/rez/data/tests/packages/developer_dynamic_local_preprocess/package.py @@ -1,14 +1,14 @@ name = "developer_dynamic_local_preprocess" @early() -def description(): +def description() -> str: return "This." requires = [ "versioned-*" ] -def preprocess(this, data): +def preprocess(this, data) -> None: from early_utils import get_authors data["authors"] = get_authors() data["added_by_local_preprocess"] = True diff --git a/src/rez/data/tests/packages/developer_dynamic_local_preprocess_additive/package.py b/src/rez/data/tests/packages/developer_dynamic_local_preprocess_additive/package.py index 97f6b6574..b0d09406a 100644 --- a/src/rez/data/tests/packages/developer_dynamic_local_preprocess_additive/package.py +++ b/src/rez/data/tests/packages/developer_dynamic_local_preprocess_additive/package.py @@ -1,14 +1,14 @@ name = "developer_dynamic_local_preprocess_additive" @early() -def description(): +def description() -> str: return "This." requires = [ "versioned-*" ] -def preprocess(this, data): +def preprocess(this, data) -> None: from early_utils import get_authors data["authors"] = get_authors() data["dynamic_attribute_added"] = {"value_set_by": "local"} diff --git a/src/rez/data/tests/packages/py_packages/late_binding/1.0/package.py b/src/rez/data/tests/packages/py_packages/late_binding/1.0/package.py index 4919d68e9..92b724769 100644 --- a/src/rez/data/tests/packages/py_packages/late_binding/1.0/package.py +++ b/src/rez/data/tests/packages/py_packages/late_binding/1.0/package.py @@ -6,5 +6,5 @@ def tools(): return ["util"] -def commands(): +def commands() -> None: env.PATH.append("{root}/bin") diff --git a/src/rez/data/tests/packages/py_packages/variants_py/2.0/package.py b/src/rez/data/tests/packages/py_packages/variants_py/2.0/package.py index 930d32a9b..cc887809a 100644 --- a/src/rez/data/tests/packages/py_packages/variants_py/2.0/package.py +++ b/src/rez/data/tests/packages/py_packages/variants_py/2.0/package.py @@ -11,5 +11,5 @@ ["platform-osx"] ] -def commands(): +def commands() -> None: env.PATH.append("{root}/bin") diff --git a/src/rez/data/tests/packages/py_packages/versioned/3.0/package.py b/src/rez/data/tests/packages/py_packages/versioned/3.0/package.py index fb7e11406..d89e9cf3c 100644 --- a/src/rez/data/tests/packages/py_packages/versioned/3.0/package.py +++ b/src/rez/data/tests/packages/py_packages/versioned/3.0/package.py @@ -1,5 +1,5 @@ name = 'versioned' version = '3.0' -def commands(): +def commands() -> None: env.PATH.append("{root}/bin") diff --git a/src/rez/data/tests/python/late_bind/late_utils.py b/src/rez/data/tests/python/late_bind/late_utils.py index 63378de7e..6819c1480 100644 --- a/src/rez/data/tests/python/late_bind/late_utils.py +++ b/src/rez/data/tests/python/late_bind/late_utils.py @@ -1,2 +1,2 @@ -def add_eek_var(env): +def add_eek_var(env) -> None: env.EEK = "2" diff --git a/src/rez/data/tests/python/preprocess/global_preprocess.py b/src/rez/data/tests/python/preprocess/global_preprocess.py index bdab125c0..0f628481e 100644 --- a/src/rez/data/tests/python/preprocess/global_preprocess.py +++ b/src/rez/data/tests/python/preprocess/global_preprocess.py @@ -1,2 +1,2 @@ -def inject_data(this, data): +def inject_data(this, data) -> None: data['added_by_global_preprocess'] = True diff --git a/src/rez/data/tests/release/build.py b/src/rez/data/tests/release/build.py index 966915c98..10c4ce7c1 100644 --- a/src/rez/data/tests/release/build.py +++ b/src/rez/data/tests/release/build.py @@ -4,9 +4,9 @@ import sys -def build(source_path, build_path, install_path, targets): +def build(source_path, build_path, install_path, targets) -> None: - def _copy(src, dest): + def _copy(src, dest) -> None: print("copying %s to %s..." % (src, dest)) if os.path.exists(dest): shutil.rmtree(dest) diff --git a/src/rez/data/tests/solver/packages/missing_variant_requires/1/package.py b/src/rez/data/tests/solver/packages/missing_variant_requires/1/package.py index 6cf364e53..8cb36ed25 100644 --- a/src/rez/data/tests/solver/packages/missing_variant_requires/1/package.py +++ b/src/rez/data/tests/solver/packages/missing_variant_requires/1/package.py @@ -1,7 +1,7 @@ name = "missing_variant_requires" version = "1" -def commands(): +def commands() -> None: pass variants = [ diff --git a/src/rez/deprecations.py b/src/rez/deprecations.py index 40b3f5e31..c82bd3fea 100644 --- a/src/rez/deprecations.py +++ b/src/rez/deprecations.py @@ -2,10 +2,12 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + import warnings -def warn(message, category, pre_formatted=False, stacklevel=1, filename=None, **kwargs): +def warn(message, category, pre_formatted: bool = False, stacklevel: int=1, filename=None, **kwargs) -> None: """ Wrapper around warnings.warn that allows to pass a pre-formatter warning message. This allows to warn about things that aren't coming @@ -20,7 +22,7 @@ def warn(message, category, pre_formatted=False, stacklevel=1, filename=None, ** original_formatwarning = warnings.formatwarning if pre_formatted: - def formatwarning(_, category, *args, **kwargs): + def formatwarning(_, category, *args, **kwargs) -> str: return "{0}{1}: {2}\n".format( "{0}: ".format(filename) if filename else "", category.__name__, message ) diff --git a/src/rez/developer_package.py b/src/rez/developer_package.py index 820576f6d..7015f03ed 100644 --- a/src/rez/developer_package.py +++ b/src/rez/developer_package.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.config import config from rez.packages import Package, create_package from rez.serialise import load_from_file, FileFormat, set_objects @@ -29,22 +31,22 @@ class DeveloperPackage(Package): This is a package in a source directory that is subsequently built or released. """ - def __init__(self, resource): + def __init__(self, resource) -> None: super(DeveloperPackage, self).__init__(resource) - self.filepath = None + self.filepath: str | None = None # include modules, derived from any present @include decorators - self.includes = None + self.includes: set[str] | None = None @property - def root(self): + def root(self) -> str | None: if self.filepath: return os.path.dirname(self.filepath) else: return None @classmethod - def from_path(cls, path, format=None): + def from_path(cls, path: str, format: FileFormat | None = None) -> DeveloperPackage: """Load a developer package. A developer package may for example be a package.yaml or package.py in a @@ -62,9 +64,9 @@ def from_path(cls, path, format=None): data = None if format is None: - formats = (FileFormat.py, FileFormat.yaml) + formats = [FileFormat.py, FileFormat.yaml] else: - formats = (format,) + formats = [format] try: mode = os.stat(path).st_mode @@ -135,7 +137,7 @@ def visit(d): return package - def get_reevaluated(self, objects): + def get_reevaluated(self, objects) -> DeveloperPackage: """Get a newly loaded and re-evaluated package. Values in `objects` are made available to early-bound package @@ -152,7 +154,7 @@ def get_reevaluated(self, objects): with set_objects(objects): return self.from_path(self.root) - def _validate_includes(self): + def _validate_includes(self) -> None: if not self.includes: return @@ -173,7 +175,7 @@ def _validate_includes(self): "@include decorator requests module '%s', but the file " "%s does not exist." % (name, filepath)) - def _get_preprocessed(self, data): + def _get_preprocessed(self, data: dict) -> tuple[DeveloperPackage, dict] | None: """ Returns: (DeveloperPackage, new_data) 2-tuple IF the preprocess function diff --git a/src/rez/exceptions.py b/src/rez/exceptions.py index 659e39e6b..ebc24b791 100644 --- a/src/rez/exceptions.py +++ b/src/rez/exceptions.py @@ -5,15 +5,17 @@ """ Exceptions. """ +from __future__ import annotations + from contextlib import contextmanager class RezError(Exception): """Base-class Rez error.""" - def __init__(self, value=None): + def __init__(self, value=None) -> None: self.value = value - def __str__(self): + def __str__(self) -> str: return str(self.value) @@ -66,7 +68,7 @@ class ResourceContentError(ResourceError): """A resource contains incorrect data.""" type_name = "resource file" - def __init__(self, value=None, path=None, resource_key=None): + def __init__(self, value=None, path=None, resource_key=None) -> None: msg = [] if resource_key is not None: msg.append("resource type: %r" % resource_key) @@ -150,7 +152,7 @@ class BuildSystemError(BuildError): class BuildContextResolveError(BuildError): """Raised if unable to resolve the required context when creating the environment for a build process.""" - def __init__(self, context): + def __init__(self, context) -> None: self.context = context assert context.status != "solved" msg = ("The build environment could not be resolved:\n%s" diff --git a/src/rez/package_bind.py b/src/rez/package_bind.py index be51c1a52..a60063730 100644 --- a/src/rez/package_bind.py +++ b/src/rez/package_bind.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.exceptions import RezBindError, _NeverError from rez import module_root_path from rez.util import get_close_pkgs @@ -12,8 +14,13 @@ import os.path import os +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from rez.packages import Variant + -def get_bind_modules(verbose=False): +def get_bind_modules(verbose: bool = False) -> dict[str, str]: """Get available bind modules. Returns: @@ -39,7 +46,7 @@ def get_bind_modules(verbose=False): return bindnames -def find_bind_module(name, verbose=False): +def find_bind_module(name: str, verbose: bool = False) -> str | None: """Find the bind module matching the given name. Args: @@ -71,8 +78,9 @@ def find_bind_module(name, verbose=False): return None -def bind_package(name, path=None, version_range=None, no_deps=False, - bind_args=None, quiet=False): +def bind_package(name: str, path: str | None = None, version_range=None, + no_deps: bool = False, bind_args: list[str] | None = None, + quiet: bool = False) -> list[Variant]: """Bind software available on the current system, as a rez package. Note: @@ -101,7 +109,7 @@ def bind_package(name, path=None, version_range=None, no_deps=False, while pending: pending_ = pending pending = set() - exc_type = _NeverError + exc_type: type[Exception] = _NeverError for name_ in pending_: # turn error on binding of dependencies into a warning - we don't @@ -142,8 +150,8 @@ def bind_package(name, path=None, version_range=None, no_deps=False, return installed_variants -def _bind_package(name, path=None, version_range=None, bind_args=None, - quiet=False): +def _bind_package(name: str, path: str | None = None, version_range=None, bind_args: list[str] | None = None, + quiet: bool = False) -> list[Variant]: bindfile = find_bind_module(name, verbose=(not quiet)) if not bindfile: raise RezBindError("Bind module not found for '%s'" % name) @@ -179,7 +187,7 @@ def _bind_package(name, path=None, version_range=None, bind_args=None, return variants -def _print_package_list(variants): +def _print_package_list(variants) -> None: packages = set([x.parent for x in variants]) packages = sorted(packages, key=lambda x: x.name) diff --git a/src/rez/package_cache.py b/src/rez/package_cache.py index 18e406029..d5c9a8c90 100644 --- a/src/rez/package_cache.py +++ b/src/rez/package_cache.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + import json import os import os.path @@ -27,10 +29,12 @@ from rez.utils.filesystem import forceful_rmtree, safe_listdir, safe_remove from rez.utils.colorize import ColorizedStreamHandler from rez.utils.logging_ import print_warning -from rez.packages import get_variant +from rez.packages import get_variant, Variant from rez.system import system from rez.utils.filesystem import rename +from typing import Iterable, Iterator + class PackageCache(object): """Package cache. @@ -87,7 +91,7 @@ class PackageCache(object): _COPYING_TIME_INC = 0.2 _COPYING_TIME_MAX = 5.0 - def __init__(self, path): + def __init__(self, path: str) -> None: """Create a package cache. Args: @@ -103,7 +107,7 @@ def __init__(self, path): os.makedirs(self._pending_dir, exist_ok=True) os.makedirs(self._remove_dir, exist_ok=True) - def get_cached_root(self, variant): + def get_cached_root(self, variant: Variant) -> str | None: """Get location of variant payload copy. Args: @@ -130,7 +134,8 @@ def get_cached_root(self, variant): return rootpath - def add_variant(self, variant, force=False, wait_for_copying=False, logger=None): + def add_variant(self, variant: Variant, force: bool = False, wait_for_copying: bool = False, + logger: logging.Logger | None = None) -> tuple[str, int]: """Copy a variant's payload into the cache. The following steps are taken to ensure muti-thread/proc safety, and to @@ -310,7 +315,7 @@ def add_variant(self, variant, force=False, wait_for_copying=False, logger=None) # still_copying = True - def _while_copying(): + def _while_copying() -> None: while still_copying: time.sleep(self._COPYING_TIME_INC) try: @@ -334,7 +339,7 @@ def _while_copying(): return (rootpath, self.VARIANT_CREATED) - def remove_variant(self, variant): + def remove_variant(self, variant: Variant) -> int: """Remove a variant from the cache. Since this removes the associated cached variant payload, there is no @@ -400,7 +405,7 @@ def remove_variant(self, variant): return self.VARIANT_REMOVED - def add_variants_async(self, variants): + def add_variants_async(self, variants: Iterable[Variant]) -> None: """Update the package cache by adding some or all of the given variants. This method is called when a context is created or sourced. Variants @@ -411,7 +416,7 @@ def add_variants_async(self, variants): """ return self.add_variants(variants, package_cache_async=True) - def add_variants(self, variants, package_cache_async=True): + def add_variants(self, variants: Iterable[Variant], package_cache_async: bool = True) -> None: """Add the given variants to the package payload cache. """ @@ -493,7 +498,7 @@ def add_variants(self, variants, package_cache_async=True): self._run_caching_operation(wait_for_copying=True) @staticmethod - def _subprocess_package_caching_daemon(path): + def _subprocess_package_caching_daemon(path: str) -> subprocess.Popen | None: """ Run the package cache in a daemon process @@ -539,8 +544,9 @@ def _subprocess_package_caching_daemon(path): "Failed to start package caching daemon (command: %s): %s", ' '.join(args), e ) + return None - def get_variants(self): + def get_variants(self) -> list[tuple[Variant, str, int]]: """Get variants and their current statuses from the cache. Returns: @@ -610,7 +616,7 @@ def get_variants(self): return results - def run_daemon(self): + def run_daemon(self) -> None: """Run as daemon and copy pending variants. Called via `rez-pkg-cache --daemon`. @@ -633,7 +639,7 @@ def run_daemon(self): self._run_caching_operation(wait_for_copying=False) - def _run_caching_operation(self, wait_for_copying=True): + def _run_caching_operation(self, wait_for_copying: bool = True): """Copy pending variants. Args: @@ -664,7 +670,7 @@ def _run_caching_operation(self, wait_for_copying=True): except Exception: logger.exception("An error occurred while cleaning the cache") - def clean(self, time_limit=None): + def clean(self, time_limit: float | None = None) -> None: """Delete unused package cache files. This should be run periodically via 'rez-pkg-cache --clean'. @@ -751,7 +757,7 @@ def should_exit(): return @contextmanager - def _lock(self): + def _lock(self) -> Iterator[None]: lock_filepath = os.path.join(self._sys_dir, ".lock") lock = LockFile(lock_filepath) @@ -764,7 +770,7 @@ def _lock(self): except NotLocked: pass - def _run_caching_step(self, state, wait_for_copying=False): + def _run_caching_step(self, state, wait_for_copying: bool = False) -> bool: logger = state["logger"] # pick a random pending variant to copy @@ -837,7 +843,7 @@ def _run_caching_step(self, state, wait_for_copying=False): return True - def _init_logging(self): + def _init_logging(self) -> logging.Logger: """ Creates logger that logs to file and stdout. Used for: - adding variants in daemonized proc; @@ -880,22 +886,22 @@ def _init_logging(self): return logger @property - def _sys_dir(self): + def _sys_dir(self) -> str: return os.path.join(self.path, ".sys") @property - def _log_dir(self): + def _log_dir(self) -> str: return os.path.join(self.path, ".sys", "log") @property - def _pending_dir(self): + def _pending_dir(self) -> str: return os.path.join(self.path, ".sys", "pending") @property - def _remove_dir(self): + def _remove_dir(self) -> str: return os.path.join(self.path, ".sys", "to_delete") - def _get_cached_root(self, variant): + def _get_cached_root(self, variant: Variant) -> tuple[int, str]: path = self._get_hash_path(variant) if not os.path.exists(path): return (self.VARIANT_NOT_FOUND, '') @@ -936,7 +942,7 @@ def _get_cached_root(self, variant): return (self.VARIANT_NOT_FOUND, '') - def _get_hash_path(self, variant): + def _get_hash_path(self, variant: Variant) -> str: dirs = [self.path, variant.name] if variant.version: diff --git a/src/rez/package_copy.py b/src/rez/package_copy.py index 271d74da6..6ec3793f7 100644 --- a/src/rez/package_copy.py +++ b/src/rez/package_copy.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from functools import partial import os.path import shutil @@ -9,8 +11,8 @@ from rez.config import config from rez.exceptions import PackageCopyError -from rez.package_repository import package_repository_manager -from rez.packages import Variant +from rez.package_repository import package_repository_manager, PackageRepository +from rez.packages import Package, Variant from rez.serialise import FileFormat from rez.utils import with_noop from rez.utils.base26 import create_unique_base26_symlink @@ -19,11 +21,26 @@ from rez.utils.filesystem import replacing_symlink, replacing_copy, \ additive_copytree, make_path_writable, get_existing_path - -def copy_package(package, dest_repository, variants=None, shallow=False, - dest_name=None, dest_version=None, overwrite=False, force=False, - follow_symlinks=False, dry_run=False, keep_timestamp=False, - skip_payload=False, overrides=None, verbose=False): +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from rez.version import Version + + +def copy_package(package: Package, + dest_repository: PackageRepository, + variants: list[int] | None = None, + shallow: bool = False, + dest_name: str | None = None, + dest_version: str | Version | None = None, + overwrite: bool = False, + force: bool = False, + follow_symlinks: bool = False, + dry_run: bool = False, + keep_timestamp: bool = False, + skip_payload: bool = False, + overrides=None, + verbose: bool = False) -> dict[str, list[tuple[Variant, Variant]]]: """Copy a package from one package repository to another. This copies the package definition and payload. The package can also be @@ -227,8 +244,12 @@ def finalize(): return finalize() -def _copy_variant_payload(src_variant, dest_pkg_repo, shallow=False, - follow_symlinks=False, overrides=None, verbose=False): +def _copy_variant_payload(src_variant: Variant, + dest_pkg_repo: PackageRepository, + shallow: bool = False, + follow_symlinks: bool = False, + overrides=None, + verbose: bool = False) -> None: # Get payload path of source variant. For some types (eg from a "memory" # type repo) there may not be a root. # @@ -243,7 +264,7 @@ def _copy_variant_payload(src_variant, dest_pkg_repo, shallow=False, if not os.path.isdir(variant_root): raise PackageCopyError( "Cannot copy source variant %s - its root does not appear to " - "be present on disk (%s)." % src_variant.uri, variant_root + "be present on disk (%s)." % (src_variant.uri, variant_root) ) dest_variant_name = overrides.get("name") or src_variant.name @@ -381,7 +402,7 @@ def _copy_variant_payload(src_variant, dest_pkg_repo, shallow=False, ) -def _get_overlapped_variant_dirs(src_variant): +def _get_overlapped_variant_dirs(src_variant: Variant) -> list[str]: package = src_variant.parent dirs = set() @@ -398,7 +419,8 @@ def _get_overlapped_variant_dirs(src_variant): return list(dirs) -def _copy_package_include_modules(src_package, dest_pkg_repo, overrides=None): +def _copy_package_include_modules(src_package: Package, dest_pkg_repo: PackageRepository, + overrides=None) -> None: src_include_modules_path = \ os.path.join(src_package.base, IncludeModuleManager.include_modules_subpath) diff --git a/src/rez/package_filter.py b/src/rez/package_filter.py index 70014e400..8e50d07ab 100644 --- a/src/rez/package_filter.py +++ b/src/rez/package_filter.py @@ -2,20 +2,26 @@ # Copyright Contributors to the Rez Project -from rez.packages import iter_packages +from __future__ import annotations + +from rez.packages import iter_packages, Package from rez.exceptions import ConfigurationError from rez.config import config from rez.utils.data_utils import cached_property, cached_class_property -from rez.version import VersionedObject, Requirement +from rez.version import VersionedObject, VersionRange, Requirement from hashlib import sha1 +from typing import Any, Iterator, Pattern, TYPE_CHECKING, ClassVar import fnmatch import re +if TYPE_CHECKING: + from typing import Self + class PackageFilterBase(object): """Base class for package filters.""" - def excludes(self, package): + def excludes(self, package: Package) -> Rule | None: """Determine if the filter excludes the given package. Args: @@ -27,7 +33,7 @@ def excludes(self, package): """ raise NotImplementedError - def add_exclusion(self, rule): + def add_exclusion(self, rule: Rule) -> None: """Add an exclusion rule. Args: @@ -35,7 +41,7 @@ def add_exclusion(self, rule): """ raise NotImplementedError - def add_inclusion(self, rule): + def add_inclusion(self, rule: Rule) -> None: """Add an inclusion rule. Args: @@ -44,19 +50,20 @@ def add_inclusion(self, rule): raise NotImplementedError @classmethod - def from_pod(cls, data): + def from_pod(cls, data: Any) -> Self: """Convert from POD types to equivalent package filter.""" raise NotImplementedError - def to_pod(self): + def to_pod(self) -> Any: """Convert to POD type, suitable for storing in an rxt file. - Returns: + Return type depends on subclass implementation dict[str, list[str]]: """ raise NotImplementedError - def iter_packages(self, name, range_=None, paths=None): + def iter_packages(self, name: str, range_: VersionRange | str | None = None, + paths: list[str] | None = None) -> Iterator[Package]: """Same as :func:`~rez.packages.iter_packages`, but also applies this filter. Args: @@ -74,7 +81,7 @@ def iter_packages(self, name, range_=None, paths=None): yield package @property - def sha1(self): + def sha1(self) -> str: """ SHA1 representation @@ -83,7 +90,7 @@ def sha1(self): """ return sha1(str(self).encode("utf-8")).hexdigest() - def __repr__(self): + def __repr__(self) -> str: return "%s(%s)" % (self.__class__.__name__, str(self)) @@ -99,15 +106,15 @@ class PackageFilter(PackageFilterBase): excluded if it matches one or more exclusion rules, and does not match any inclusion rules. """ - def __init__(self): - self._excludes = {} - self._includes = {} + def __init__(self) -> None: + self._excludes: dict[str | None, list[Rule]] = {} + self._includes: dict[str | None, list[Rule]] = {} - def excludes(self, package): + def excludes(self, package: Package) -> Rule | None: if not self._excludes: return None # quick out - def _match(rules): + def _match(rules: list[Rule] | None) -> Rule | None: if rules: for rule in rules: if rule.match(package): @@ -132,13 +139,13 @@ def _match(rules): return excl - def add_exclusion(self, rule): + def add_exclusion(self, rule: Rule) -> None: self._add_rule(self._excludes, rule) - def add_inclusion(self, rule): + def add_inclusion(self, rule: Rule) -> None: self._add_rule(self._includes, rule) - def copy(self): + def copy(self) -> PackageFilter: """Return a shallow copy of the filter. Adding rules to the copy will not alter the source. @@ -148,7 +155,7 @@ def copy(self): other._includes = self._includes.copy() return other - def __and__(self, other): + def __and__(self, other: PackageFilter) -> PackageFilter: """Combine two filters.""" result = self.copy() for rule in other._excludes.values(): @@ -157,11 +164,11 @@ def __and__(self, other): result.add_inclusion(rule) return result - def __bool__(self): + def __bool__(self) -> bool: return bool(self._excludes) @cached_property - def cost(self): + def cost(self) -> float: """Get the approximate cost of this filter. Cost is the total cost of the exclusion rules in this filter. The cost @@ -172,14 +179,14 @@ def cost(self): """ total = 0.0 for family, rules in self._excludes.items(): - cost = sum(x.cost() for x in rules) + cost: float = sum(x.cost() for x in rules) if family: cost = cost / float(10) total += cost return total @classmethod - def from_pod(cls, data): + def from_pod(cls, data: dict) -> PackageFilter: """Convert from POD types to equivalent package filter. Returns: @@ -196,7 +203,7 @@ def from_pod(cls, data): func(rule) return f - def to_pod(self): + def to_pod(self) -> dict[str, list[str]]: data = {} for namespace, dict_ in (("excludes", self._excludes), ("includes", self._includes)): @@ -207,14 +214,14 @@ def to_pod(self): data[namespace] = rules return data - def _add_rule(self, rules_dict, rule): + def _add_rule(self, rules_dict: dict[str | None, list[Rule]], rule: Rule) -> None: family = rule.family() rules_ = rules_dict.get(family, []) rules_dict[family] = sorted(rules_ + [rule], key=lambda x: x.cost()) - cached_property.uncache(self, "cost") + cached_property.uncache(self, "cost") # type: ignore[attr-defined] - def __str__(self): - def sortkey(rule_items): + def __str__(self) -> str: + def sortkey(rule_items: tuple[str | None, list[Rule]]) -> tuple[str, list[Rule]]: family, rules = rule_items if family is None: return ("", rules) @@ -230,10 +237,10 @@ class PackageFilterList(PackageFilterBase): A package is excluded by a filter list iff any filter within the list excludes it. """ - def __init__(self): - self.filters = [] + def __init__(self) -> None: + self.filters: list[PackageFilter] = [] - def add_filter(self, package_filter): + def add_filter(self, package_filter: PackageFilter) -> None: """Add a filter to the list. Args: @@ -242,7 +249,7 @@ def add_filter(self, package_filter): filters = self.filters + [package_filter] self.filters = sorted(filters, key=lambda x: x.cost) - def add_exclusion(self, rule): + def add_exclusion(self, rule: Rule) -> None: if self.filters: f = self.filters[-1] f.add_exclusion(rule) @@ -251,7 +258,7 @@ def add_exclusion(self, rule): f.add_exclusion(rule) self.add_filter(f) - def add_inclusion(self, rule): + def add_inclusion(self, rule: Rule) -> None: """ See also: :meth:`PackageFilterBase.add_inclusion` @@ -262,8 +269,8 @@ def add_inclusion(self, rule): for f in self.filters: f.add_inclusion(rule) - def excludes(self, package): - """Returns the first rule that exlcudes ``package``, if any. + def excludes(self, package: Package) -> Rule | None: + """Returns the first rule that excludes ``package``, if any. Returns: Rule: @@ -274,7 +281,7 @@ def excludes(self, package): return rule return None - def copy(self): + def copy(self) -> PackageFilterList: """Return a copy of the filter list. Adding rules to the copy will not alter the source. @@ -284,7 +291,7 @@ def copy(self): return other @classmethod - def from_pod(cls, data): + def from_pod(cls, data: list[dict]) -> PackageFilterList: """Convert from POD types to equivalent package filter. Returns: @@ -296,21 +303,21 @@ def from_pod(cls, data): flist.add_filter(f) return flist - def to_pod(self): + def to_pod(self) -> list[dict]: data = [] for f in self.filters: data.append(f.to_pod()) return data - def __bool__(self): + def __bool__(self) -> bool: return any(self.filters) - def __str__(self): + def __str__(self) -> str: filters = sorted(self.filters, key=lambda x: (x.cost, str(x))) return str(tuple(filters)) @cached_class_property - def singleton(cls): + def singleton(cls) -> PackageFilterList: """Filter list as configured by :data:`package_filter`. Returns: @@ -329,7 +336,7 @@ class Rule(object): #: Rule name name = None - def match(self, package): + def match(self, package: Package) -> bool: """Apply the rule to the package. Args: @@ -340,7 +347,7 @@ def match(self, package): """ raise NotImplementedError - def family(self): + def family(self) -> str | None: """Returns a package family string if this rule only applies to a given package family, otherwise None. @@ -349,12 +356,12 @@ def family(self): """ return self._family - def cost(self): + def cost(self) -> int: """Relative cost of filter. Cheaper filters are applied first.""" raise NotImplementedError @classmethod - def parse_rule(cls, txt): + def parse_rule(cls, txt: str) -> Rule: """Parse a rule from a string. See :data:`package_filter` for an overview of valid strings. @@ -365,11 +372,12 @@ def parse_rule(cls, txt): Returns: Rule: """ - types = {"glob": GlobRule, - "regex": RegexRule, - "range": RangeRule, - "before": TimestampRule, - "after": TimestampRule} + types: dict[str, type[Rule]] = { + "glob": GlobRule, + "regex": RegexRule, + "range": RangeRule, + "before": TimestampRule, + "after": TimestampRule} # parse form 'x(y)' into x, y label, txt = Rule._parse_label(txt) @@ -393,7 +401,7 @@ def parse_rule(cls, txt): return rule @classmethod - def _parse(cls, txt): + def _parse(cls, txt: str) -> Rule: """Create a rule from a string. Returns: @@ -403,7 +411,7 @@ def _parse(cls, txt): raise NotImplementedError @classmethod - def _parse_label(cls, txt): + def _parse_label(cls, txt: str) -> tuple[str | None, str]: m = cls.label_re.match(txt) if m: label, txt = m.groups() @@ -412,32 +420,35 @@ def _parse_label(cls, txt): return None, txt @classmethod - def _extract_family(cls, txt): + def _extract_family(cls, txt: str) -> str | None: m = cls.family_re.match(txt) if m: return m.group()[:-1] return None - def __repr__(self): + def __repr__(self) -> str: return str(self) - family_re = re.compile("[^*?]+" + VersionedObject.sep_regex_str) - label_re = re.compile("^([^(]+)\\(([^\\(\\)]+)\\)$") + family_re: ClassVar[re.Pattern[str]] = re.compile("[^*?]+" + VersionedObject.sep_regex.pattern) + label_re: ClassVar[re.Pattern[str]] = re.compile("^([^(]+)\\(([^\\(\\)]+)\\)$") class RegexRuleBase(Rule): - def match(self, package): + regex: Pattern[str] + txt: str + + def match(self, package: Package) -> bool: return bool(self.regex.match(package.qualified_name)) - def cost(self): + def cost(self) -> int: return 10 @classmethod - def _parse(cls, txt): + def _parse(cls, txt: str) -> Self: _, txt = Rule._parse_label(txt) return cls(txt) - def __str__(self): + def __str__(self) -> str: return "%s(%s)" % (self.name, self.txt) @@ -448,7 +459,7 @@ class RegexRule(RegexRuleBase): """ name = "regex" - def __init__(self, s): + def __init__(self, s: str) -> None: """Create a regex rule. Args: @@ -466,7 +477,7 @@ class GlobRule(RegexRuleBase): """ name = "glob" - def __init__(self, s): + def __init__(self, s: str) -> None: """Create a glob rule. Args: @@ -485,23 +496,23 @@ class RangeRule(Rule): """ name = "range" - def __init__(self, requirement): + def __init__(self, requirement: Requirement) -> None: self._requirement = requirement self._family = requirement.name - def match(self, package): + def match(self, package: Package) -> bool: o = VersionedObject.construct(package.name, package.version) return not self._requirement.conflicts_with(o) - def cost(self): + def cost(self) -> int: return 10 @classmethod - def _parse(cls, txt): + def _parse(cls, txt: str) -> Self: _, txt = Rule._parse_label(txt) return cls(Requirement(txt)) - def __str__(self): + def __str__(self) -> str: return "%s(%s)" % (self.name, str(self._requirement)) @@ -529,8 +540,8 @@ class TimestampRule(Rule): """ name = "timestamp" - def __init__(self, timestamp, family=None, reverse=False, - match_untimestamped=False): + def __init__(self, timestamp: int, family: str | None = None, reverse: bool = False, + match_untimestamped: bool = False) -> None: """Create a timestamp rule. Args: @@ -546,7 +557,7 @@ def __init__(self, timestamp, family=None, reverse=False, self.match_untimestamped = match_untimestamped self._family = family - def match(self, package): + def match(self, package: Package) -> bool: if not package.timestamp: return self.match_untimestamped elif self.reverse: @@ -554,20 +565,20 @@ def match(self, package): else: return (package.timestamp <= self.timestamp) - def cost(self): + def cost(self) -> int: # This is expensive because it causes a package load return 1000 @classmethod - def after(cls, timestamp, family=None): + def after(cls, timestamp: int, family: str | None = None) -> Self: return cls(timestamp, family=family, reverse=True) @classmethod - def before(cls, timestamp, family=None): + def before(cls, timestamp: int, family: str | None = None) -> Self: return cls(timestamp, family=family) @classmethod - def _parse(cls, txt): + def _parse(cls, txt: str) -> Self: label, txt = Rule._parse_label(txt) if ':' in txt: family, txt = txt.split(':', 1) @@ -578,7 +589,7 @@ def _parse(cls, txt): reverse = (label == "after") return cls(timestamp, family=family, reverse=reverse) - def __str__(self): + def __str__(self) -> str: label = "after" if self.reverse else "before" parts = [] if self._family: diff --git a/src/rez/package_help.py b/src/rez/package_help.py index b036dd1ca..5702d9cc8 100644 --- a/src/rez/package_help.py +++ b/src/rez/package_help.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.packages import iter_packages from rez.config import config from rez.rex_bindings import VersionBinding @@ -11,6 +13,10 @@ from rez.system import system import webbrowser import sys +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from rez.version import VersionRange class PackageHelp(object): @@ -19,7 +25,8 @@ class PackageHelp(object): Given a package and version range, help will be extracted from the latest package in the version range that provides it. """ - def __init__(self, package_name, version_range=None, paths=None, verbose=False): + def __init__(self, package_name: str, version_range: VersionRange | None = None, paths=None, + verbose: bool = False) -> None: """Create a PackageHelp object. Args: @@ -78,16 +85,16 @@ def __init__(self, package_name, version_range=None, paths=None, verbose=False): self._sections = sections @property - def success(self): + def success(self) -> bool: """Return True if help was found, False otherwise.""" return bool(self._sections) @property - def sections(self): + def sections(self) -> list[list[str]]: """Returns a list of (name, uri) 2-tuples.""" return self._sections - def open(self, section_index=0): + def open(self, section_index: int=0) -> None: """Launch a help section.""" uri = self._sections[section_index][1] if len(uri.split()) == 1: @@ -99,7 +106,7 @@ def open(self, section_index=0): with Popen(uri, shell=True) as p: p.wait() - def print_info(self, buf=None): + def print_info(self, buf=None) -> None: """Print help sections.""" buf = buf or sys.stdout print("Sections:", file=buf) @@ -107,12 +114,12 @@ def print_info(self, buf=None): print(" %s:\t%s (%s)" % (i + 1, section[0], section[1]), file=buf) @classmethod - def open_rez_manual(cls): + def open_rez_manual(cls) -> None: """Open the Rez user manual.""" cls._open_url(config.documentation_url) @classmethod - def _open_url(cls, url): + def _open_url(cls, url: str) -> None: if config.browser: cmd = [config.browser, url] if not config.quiet: diff --git a/src/rez/package_maker.py b/src/rez/package_maker.py index 409fb7365..d8f7f8b4c 100644 --- a/src/rez/package_maker.py +++ b/src/rez/package_maker.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.utils._version import _rez_version from rez.utils.schema import Required, extensible_schema_dict from rez.utils.filesystem import retain_cwd @@ -12,12 +14,13 @@ from rez.package_resources import help_schema, _commands_schema, \ _function_schema, late_bound from rez.package_repository import create_memory_package_repository -from rez.packages import Package +from rez.packages import Package, Variant from rez.package_py_utils import expand_requirement from rez.vendor.schema.schema import Schema, Optional, Or, Use, And from rez.version import Version from contextlib import contextmanager import os +from typing import Any, Callable, Iterable, Iterator # this schema will automatically harden request strings like 'python-*'; see @@ -92,7 +95,7 @@ class PackageMaker(AttrDictWrapper): """Utility class for creating packages.""" - def __init__(self, name, data=None, package_cls=None): + def __init__(self, name: str, data: dict | None = None, package_cls: type[Package] | None = None) -> None: """Create a package maker. Args: @@ -106,7 +109,7 @@ def __init__(self, name, data=None, package_cls=None): self.installed_variants = [] self.skipped_variants = [] - def get_package(self): + def get_package(self) -> Package: """Create the analogous package. Returns: @@ -142,7 +145,7 @@ def get_package(self): package.validate_data() return package - def _get_data(self): + def _get_data(self) -> dict: data = self._data.copy() data.pop("installed_variants", None) @@ -154,8 +157,11 @@ def _get_data(self): @contextmanager -def make_package(name, path, make_base=None, make_root=None, skip_existing=True, - warn_on_skip=True): +def make_package(name: str, path: str, + make_base: Callable[[Variant, str], Any] | None = None, + make_root: Callable[[Variant, str], Any] | None= None, + skip_existing: bool = True, + warn_on_skip: bool = True) -> Iterator[PackageMaker]: """Make and install a package. Example: diff --git a/src/rez/package_move.py b/src/rez/package_move.py index 0cd4c6cd7..443eaf03a 100644 --- a/src/rez/package_move.py +++ b/src/rez/package_move.py @@ -2,14 +2,16 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.exceptions import PackageMoveError from rez.package_copy import copy_package from rez.package_repository import package_repository_manager from rez.utils.logging_ import print_info -def move_package(package, dest_repository, keep_timestamp=False, force=False, - verbose=False): +def move_package(package, dest_repository, keep_timestamp: bool = False, force: bool = False, + verbose: bool = False): """Move a package. Moving a package means copying the package to a destination repo, and @@ -32,7 +34,7 @@ def move_package(package, dest_repository, keep_timestamp=False, force=False, Returns: `Package`: The newly created package in the destination repo. """ - def _info(msg, *nargs): + def _info(msg, *nargs) -> None: if verbose: print_info(msg, *nargs) diff --git a/src/rez/package_order.py b/src/rez/package_order.py index 706c5f0ca..d8c755575 100644 --- a/src/rez/package_order.py +++ b/src/rez/package_order.py @@ -2,15 +2,23 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from inspect import isclass from hashlib import sha1 -from typing import Dict, Iterable, List, Optional, Union +from typing import Any, Callable, Iterable, List, TYPE_CHECKING from rez.config import config from rez.utils.data_utils import cached_class_property from rez.version import Version, VersionRange from rez.version._version import _Comparable, _ReversedComparable, _LowerBound, _UpperBound, _Bound -from rez.packages import iter_packages +from rez.packages import iter_packages, Package +from rez.utils.typing import SupportsLessThan + +if TYPE_CHECKING: + # this is not available in typing until 3.11, but due to __future__.annotations + # we can use it without really importing it + from typing import Self ALL_PACKAGES = "*" @@ -20,23 +28,25 @@ class FallbackComparable(_Comparable): fails, compares using the fallback_comparable object. """ - def __init__(self, main_comparable, fallback_comparable): + def __init__(self, + main_comparable: SupportsLessThan, + fallback_comparable: SupportsLessThan) -> None: self.main_comparable = main_comparable self.fallback_comparable = fallback_comparable - def __eq__(self, other): + def __eq__(self, other: object) -> bool: try: return self.main_comparable == other.main_comparable except Exception: return self.fallback_comparable == other.fallback_comparable - def __lt__(self, other): + def __lt__(self, other: object) -> bool: try: return self.main_comparable < other.main_comparable except Exception: return self.fallback_comparable < other.fallback_comparable - def __repr__(self): + def __repr__(self) -> str: return '%s(%r, %r)' % (type(self).__name__, self.main_comparable, self.fallback_comparable) @@ -46,15 +56,16 @@ class PackageOrder(object): #: Orderer name name = None - def __init__(self, packages: Optional[Iterable[str]] = None): + def __init__(self, packages: list[str] | None = None) -> None: """ Args: packages: If not provided, PackageOrder applies to all packages. """ - self.packages = packages + # TYPING: odd behavior where mypy disregards the property setter + self.packages = packages # type: ignore[assignment] @property - def packages(self) -> List[str]: + def packages(self) -> list[str]: """Returns an iterable over the list of package family names that this order applies to @@ -64,7 +75,7 @@ def packages(self) -> List[str]: return self._packages @packages.setter - def packages(self, packages: Union[str, Iterable[str]]): + def packages(self, packages: str | Iterable[str] | None) -> None: if packages is None: # Apply to all packages self._packages = [ALL_PACKAGES] @@ -73,7 +84,8 @@ def packages(self, packages: Union[str, Iterable[str]]): else: self._packages = sorted(packages) - def reorder(self, iterable, key=None): + def reorder(self, iterable: Iterable[Package], + key: Callable[[Any], Package] | None = None) -> list[Package] | None: """Put packages into some order for consumption. You can safely assume that the packages referred to by `iterable` are @@ -101,7 +113,9 @@ def reorder(self, iterable, key=None): reverse=True) @staticmethod - def _get_package_name_from_iterable(iterable, key=None): + def _get_package_name_from_iterable(iterable: Iterable[Package], + key: Callable[[Any], Package] | None = None + ) -> str | None: """Utility method for getting a package from an iterable""" try: item = next(iter(iterable)) @@ -111,7 +125,9 @@ def _get_package_name_from_iterable(iterable, key=None): key = key or (lambda x: x) return key(item).name - def sort_key(self, package_name, version_like): + def sort_key(self, package_name: str, + version_like: Version | _LowerBound | _UpperBound | _Bound | VersionRange | None + ) -> SupportsLessThan: """Returns a sort key usable for sorting packages within the same family Args: @@ -148,7 +164,7 @@ def sort_key(self, package_name, version_like): return 0 raise TypeError(version_like) - def sort_key_implementation(self, package_name, version): + def sort_key_implementation(self, package_name: str, version: Version) -> SupportsLessThan: """Returns a sort key usable for sorting these packages within the same family Args: @@ -162,27 +178,27 @@ def sort_key_implementation(self, package_name, version): """ raise NotImplementedError - def to_pod(self): + def to_pod(self) -> dict[str, Any]: raise NotImplementedError @classmethod - def from_pod(cls, data): + def from_pod(cls, data: dict[str, Any]) -> PackageOrder: raise NotImplementedError @property - def sha1(self): + def sha1(self) -> str: return sha1(repr(self).encode('utf-8')).hexdigest() - def __str__(self): + def __str__(self) -> str: raise NotImplementedError def __eq__(self, other): return type(self) == type(other) and str(self) == str(other) # noqa: E721 - def __ne__(self, other): + def __ne__(self, other) -> bool: return not self == other - def __repr__(self): + def __repr__(self) -> str: return "%s(%s)" % (self.__class__.__name__, str(self)) @@ -195,18 +211,18 @@ class NullPackageOrder(PackageOrder): """ name = "no_order" - def sort_key_implementation(self, package_name, version): + def sort_key_implementation(self, package_name: str, version: Version) -> SupportsLessThan: # python's sort will preserve the order of items that compare equal, so # to not change anything, we just return the same object for all... return 0 - def __str__(self): + def __str__(self) -> str: return "{}" def __eq__(self, other): return type(self) == type(other) # noqa: E721 - def to_pod(self): + def to_pod(self) -> dict[str, Any]: """ Example (in yaml): @@ -220,7 +236,7 @@ def to_pod(self): } @classmethod - def from_pod(cls, data): + def from_pod(cls, data: dict[str, Any]) -> Self: return cls(packages=data.get("packages")) @@ -229,11 +245,11 @@ class SortedOrder(PackageOrder): """ name = "sorted" - def __init__(self, descending, packages=None): + def __init__(self, descending: bool, packages: list[str] | None = None) -> None: super().__init__(packages) self.descending = descending - def sort_key_implementation(self, package_name, version): + def sort_key_implementation(self, package_name: str, version: Version) -> SupportsLessThan: # Note that the name "descending" can be slightly confusing - it # indicates that the final ordering this Order gives should be # version descending (ie, the default) - however, the sort_key itself @@ -246,7 +262,7 @@ def sort_key_implementation(self, package_name, version): else: return _ReversedComparable(version) - def __str__(self): + def __str__(self) -> str: return str(self.descending) def __eq__(self, other): @@ -255,7 +271,7 @@ def __eq__(self, other): and self.descending == other.descending ) - def to_pod(self): + def to_pod(self) -> dict[str, Any]: """ Example (in yaml): @@ -271,7 +287,7 @@ def to_pod(self): } @classmethod - def from_pod(cls, data): + def from_pod(cls, data: dict[str, Any]) -> Self: return cls( data["descending"], packages=data.get("packages"), @@ -283,7 +299,8 @@ class PerFamilyOrder(PackageOrder): """ name = "per_family" - def __init__(self, order_dict, default_order=None): + def __init__(self, order_dict: dict[str, PackageOrder], + default_order: PackageOrder | None = None) -> None: """Create a reorderer. Args: @@ -296,7 +313,8 @@ def __init__(self, order_dict, default_order=None): self.order_dict = order_dict.copy() self.default_order = default_order - def reorder(self, iterable, key=None): + def reorder(self, iterable: Iterable[Package], + key: Callable[[Any], Package] | None = None) -> list[Package] | None: package_name = self._get_package_name_from_iterable(iterable, key) if package_name is None: return None @@ -309,7 +327,7 @@ def reorder(self, iterable, key=None): return orderer.reorder(iterable, key) - def sort_key_implementation(self, package_name, version): + def sort_key_implementation(self, package_name: str, version: Version) -> SupportsLessThan: orderer = self.order_dict.get(package_name) if orderer is None: if self.default_order is None: @@ -322,7 +340,7 @@ def sort_key_implementation(self, package_name, version): return orderer.sort_key_implementation(package_name, version) - def __str__(self): + def __str__(self) -> str: items = sorted((x[0], str(x[1])) for x in self.order_dict.items()) return str((items, str(self.default_order))) @@ -333,7 +351,7 @@ def __eq__(self, other): and self.default_order == other.default_order ) - def to_pod(self): + def to_pod(self) -> dict[str, Any]: """ Example (in yaml): @@ -367,7 +385,7 @@ def to_pod(self): data["packages"] = sorted(fams) orderlist.append(data) - result = {"orderers": orderlist} + result: dict[str, Any] = {"orderers": orderlist} if self.default_order is not None: result["default_order"] = to_pod(self.default_order) @@ -375,7 +393,7 @@ def to_pod(self): return result @classmethod - def from_pod(cls, data): + def from_pod(cls, data: dict[str, Any]) -> Self: order_dict = {} default_order = None @@ -402,7 +420,7 @@ class VersionSplitPackageOrder(PackageOrder): """ name = "version_split" - def __init__(self, first_version, packages=None): + def __init__(self, first_version: Version, packages: list[str] | None = None) -> None: """Create a reorderer. Args: @@ -411,11 +429,11 @@ def __init__(self, first_version, packages=None): super().__init__(packages) self.first_version = first_version - def sort_key_implementation(self, package_name, version): + def sort_key_implementation(self, package_name: str, version: Version) -> SupportsLessThan: priority_key = 1 if version <= self.first_version else 0 return priority_key, version - def __str__(self): + def __str__(self) -> str: return str(self.first_version) def __eq__(self, other): @@ -424,7 +442,7 @@ def __eq__(self, other): and self.first_version == other.first_version ) - def to_pod(self): + def to_pod(self) -> dict[str, Any]: """ Example (in yaml): @@ -440,7 +458,7 @@ def to_pod(self): ) @classmethod - def from_pod(cls, data): + def from_pod(cls, data: dict[str, Any]) -> Self: return cls( Version(data["first_version"]), packages=data.get("packages"), @@ -490,7 +508,7 @@ class TimestampPackageOrder(PackageOrder): """ name = "soft_timestamp" - def __init__(self, timestamp, rank=0, packages=None): + def __init__(self, timestamp: int, rank: int = 0, packages: list[str] | None = None) -> None: """Create a reorderer. Args: @@ -508,7 +526,7 @@ def __init__(self, timestamp, rank=0, packages=None): self._cached_first_after = {} self._cached_sort_key = {} - def _get_first_after(self, package_family): + def _get_first_after(self, package_family: str) -> Version | None: """Get the first package version that is after the timestamp""" try: first_after = self._cached_first_after[package_family] @@ -517,7 +535,7 @@ def _get_first_after(self, package_family): self._cached_first_after[package_family] = first_after return first_after - def _calc_first_after(self, package_family): + def _calc_first_after(self, package_family: str) -> Version | None: descending = sorted(iter_packages(package_family), key=lambda p: p.version, reverse=True) @@ -551,11 +569,11 @@ def _calc_first_after(self, package_family): return first_after - def _calc_sort_key(self, package_name, version): + def _calc_sort_key(self, package_name: str, version: Version) -> SupportsLessThan: first_after = self._get_first_after(package_name) if first_after is None: # all packages are before T - is_before = True + is_before: bool | int = True else: is_before = int(version < first_after) @@ -569,7 +587,7 @@ def _calc_sort_key(self, package_name, version): return is_before, _ReversedComparable(version) - def sort_key_implementation(self, package_name, version): + def sort_key_implementation(self, package_name: str, version: Version) -> SupportsLessThan: cache_key = (package_name, str(version)) result = self._cached_sort_key.get(cache_key) if result is None: @@ -578,7 +596,7 @@ def sort_key_implementation(self, package_name, version): return result - def __str__(self): + def __str__(self) -> str: return str((self.timestamp, self.rank)) def __eq__(self, other): @@ -588,7 +606,7 @@ def __eq__(self, other): and self.rank == other.rank ) - def to_pod(self): + def to_pod(self) -> dict[str, Any]: """ Example (in yaml): @@ -606,7 +624,7 @@ def to_pod(self): ) @classmethod - def from_pod(cls, data): + def from_pod(cls, data: dict[str, Any]) -> Self: return cls( data["timestamp"], rank=data.get("rank", 0), @@ -614,20 +632,20 @@ def from_pod(cls, data): ) -class PackageOrderList(list): +class PackageOrderList(List[PackageOrder]): """A list of package orderer. """ - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self.by_package: Dict[str, PackageOrder] = {} + self.by_package: dict[str, PackageOrder] = {} self.dirty = True - def to_pod(self): + def to_pod(self) -> list[dict[str, Any]]: return [to_pod(f) for f in self] @classmethod - def from_pod(cls, data): + def from_pod(cls, data: list[dict[str, Any]]) -> PackageOrderList: flist = PackageOrderList() for dict_ in data: f = from_pod(dict_) @@ -635,12 +653,12 @@ def from_pod(cls, data): return flist @cached_class_property - def singleton(cls): + def singleton(cls) -> PackageOrderList: """Filter list as configured by rezconfig.package_filter.""" return cls.from_pod(config.package_orderers) @staticmethod - def _to_orderer(orderer: Union[dict, PackageOrder]) -> PackageOrder: + def _to_orderer(orderer: dict | PackageOrder) -> PackageOrder: if isinstance(orderer, dict): orderer = from_pod(orderer) return orderer @@ -657,31 +675,36 @@ def refresh(self) -> None: continue self.by_package[package] = orderer - def append(self, *args, **kwargs): - self.dirty = True - return super().append(*args, **kwargs) + if not TYPE_CHECKING: + # Since this class inherits from list it's easier to rely on the type hints coming from + # that base class than to redefine them here, so we hide them by placing them behind + # not TYPE_CHECKING. - def extend(self, *args, **kwargs): - self.dirty = True - return super().extend(*args, **kwargs) + def append(self, *args, **kwargs): + self.dirty = True + return super().append(*args, **kwargs) - def pop(self, *args, **kwargs): - self.dirty = True - return super().pop(*args, **kwargs) + def extend(self, *args, **kwargs): + self.dirty = True + return super().extend(*args, **kwargs) - def remove(self, *args, **kwargs): - self.dirty = True - return super().remove(*args, **kwargs) + def pop(self, *args, **kwargs): + self.dirty = True + return super().pop(*args, **kwargs) - def clear(self, *args, **kwargs): - self.dirty = True - return super().clear(*args, **kwargs) + def remove(self, *args, **kwargs): + self.dirty = True + return super().remove(*args, **kwargs) - def insert(self, *args, **kwargs): - self.dirty = True - return super().insert(*args, **kwargs) + def clear(self, *args, **kwargs): + self.dirty = True + return super().clear(*args, **kwargs) + + def insert(self, *args, **kwargs): + self.dirty = True + return super().insert(*args, **kwargs) - def get(self, key: str, default: Optional[PackageOrder] = None) -> PackageOrder: + def get(self, key: str, default: PackageOrder | None = None) -> PackageOrder | None: """ Get an orderer that sorts a package by name. """ @@ -692,13 +715,13 @@ def get(self, key: str, default: Optional[PackageOrder] = None) -> PackageOrder: return result -def to_pod(orderer): +def to_pod(orderer: PackageOrder) -> dict: data = {"type": orderer.name} data.update(orderer.to_pod()) return data -def from_pod(data): +def from_pod(data: dict[str, Any]) -> PackageOrder: if isinstance(data, dict): cls_name = data["type"] data = data.copy() @@ -713,7 +736,7 @@ def from_pod(data): return cls.from_pod(data_) -def get_orderer(package_name, orderers=None): +def get_orderer(package_name: str, orderers: PackageOrderList | dict[str, PackageOrder] | None = None) -> PackageOrder: if orderers is None: orderers = PackageOrderList.singleton orderer = orderers.get(package_name) @@ -725,7 +748,7 @@ def get_orderer(package_name, orderers=None): return orderer -def register_orderer(cls): +def register_orderer(cls: type[PackageOrder]) -> bool: """Register an orderer Args: diff --git a/src/rez/package_py_utils.py b/src/rez/package_py_utils.py index 816fbb969..0767d240e 100644 --- a/src/rez/package_py_utils.py +++ b/src/rez/package_py_utils.py @@ -10,12 +10,18 @@ - early bound functions that use the @early decorator. """ +from __future__ import annotations + # these imports just forward the symbols into this module's namespace from rez.utils.execution import Popen from rez.exceptions import InvalidPackageError +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from rez.packages import Package -def expand_requirement(request, paths=None): +def expand_requirement(request: str, paths=None) -> str: """Expands a requirement string like ``python-2.*``, ``foo-2.*+<*``, etc. Wildcards are expanded to the latest version that matches. There is also a @@ -144,7 +150,7 @@ def visit_version(version): return str(expanded_req) -def expand_requires(*requests): +def expand_requires(*requests: str) -> list[str]: """Create an expanded requirements list. Example: @@ -164,7 +170,7 @@ def expand_requires(*requests): return [expand_requirement(x) for x in requests] -def exec_command(attr, cmd): +def exec_command(attr: str, cmd: list[str]) -> tuple[str, str]: """Runs a subprocess to calculate a package attribute. Args: @@ -187,7 +193,7 @@ def exec_command(attr, cmd): return out.strip(), err.strip() -def exec_python(attr, src, executable="python"): +def exec_python(attr: str, src: list[str], executable="python") -> str: """Runs a python subproc to calculate a package attribute. Args: @@ -215,7 +221,7 @@ def exec_python(attr, src, executable="python"): return out.strip() -def find_site_python(module_name, paths=None): +def find_site_python(module_name: str, paths: list[str] | None = None) -> Package: """Find the rez native python package that contains the given module. This function is used by python 'native' rez installers to find the native diff --git a/src/rez/package_remove.py b/src/rez/package_remove.py index 3aa83dfbe..1adcddb94 100644 --- a/src/rez/package_remove.py +++ b/src/rez/package_remove.py @@ -2,13 +2,15 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.package_repository import package_repository_manager from rez.version import Version from rez.utils.logging_ import print_info from rez.config import config -def remove_package_family(name, path, force=False): +def remove_package_family(name: str, path: str, force: bool = False) -> bool: """Remove a package family from its repository. A family can only be deleted if it contains no packages, hidden or @@ -26,7 +28,7 @@ def remove_package_family(name, path, force=False): return repo.remove_package_family(name, force=force) -def remove_package(name, version, path): +def remove_package(name: str, version: Version | str, path: str) -> bool: """Remove a package from its repository. Note that you are able to remove a package that is hidden (ie ignored). @@ -48,7 +50,8 @@ def remove_package(name, version, path): return repo.remove_package(name, version) -def remove_packages_ignored_since(days, paths=None, dry_run=False, verbose=False): +def remove_packages_ignored_since(days: int, paths: list[str] | None = None, + dry_run: bool = False, verbose: bool = False) -> int: """Remove packages ignored for >= specified number of days. Args: diff --git a/src/rez/package_repository.py b/src/rez/package_repository.py index 2c7be5ab2..b0962bd30 100644 --- a/src/rez/package_repository.py +++ b/src/rez/package_repository.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.utils.resources import ResourcePool, ResourceHandle from rez.utils.data_utils import cached_property from rez.plugin_managers import plugin_manager @@ -11,14 +13,22 @@ import threading import os.path import time +from typing import Any, Hashable, Iterator, TYPE_CHECKING + +if TYPE_CHECKING: + from rez.package_resources import (PackageFamilyResource, PackageResource, PackageResourceHelper, + VariantResource, PackageRepositoryResource) + from rez.utils.resources import Resource + from rez.version import Version + from rezplugins.package_repository.memory import MemoryPackageRepository -def get_package_repository_types(): +def get_package_repository_types() -> list[str]: """Returns the available package repository implementations.""" return plugin_manager.get_plugins('package_repository') -def create_memory_package_repository(repository_data): +def create_memory_package_repository(repository_data: dict) -> MemoryPackageRepository: """Create a standalone in-memory package repository from the data given. See rezplugins/package_repository/memory.py for more details. @@ -29,20 +39,21 @@ def create_memory_package_repository(repository_data): Returns: `PackageRepository` object. """ - cls_ = plugin_manager.get_plugin_class("package_repository", "memory") + from rezplugins.package_repository.memory import MemoryPackageRepository # noqa + cls_ = plugin_manager.get_plugin_class("package_repository", "memory", MemoryPackageRepository) return cls_.create_repository(repository_data) class PackageRepositoryGlobalStats(threading.local): """Gathers stats across package repositories. """ - def __init__(self): + def __init__(self) -> None: # the amount of time that has been spent loading package from , # repositories, since process start self.package_load_time = 0.0 @contextmanager - def package_loading(self): + def package_loading(self) -> Iterator[None]: """Use this around code in your package repository that is loading a package, for example from file or cache. """ @@ -69,11 +80,11 @@ class PackageRepository(object): remove = object() @classmethod - def name(cls): + def name(cls) -> str: """Return the name of the package repository type.""" raise NotImplementedError - def __init__(self, location, resource_pool): + def __init__(self, location: str, resource_pool: ResourcePool) -> None: """Create a package repository. Args: @@ -85,10 +96,10 @@ def __init__(self, location, resource_pool): self.location = location self.pool = resource_pool - def __str__(self): + def __str__(self) -> str: return "%s@%s" % (self.name(), self.location) - def register_resource(self, resource_class): + def register_resource(self, resource_class: type[Resource]) -> None: """Register a resource with the repository. Your derived repository class should call this method in its __init__ to @@ -96,12 +107,12 @@ def register_resource(self, resource_class): """ self.pool.register_resource(resource_class) - def clear_caches(self): + def clear_caches(self) -> None: """Clear any cached resources in the pool.""" self.pool.clear_caches() @cached_property - def uid(self): + def uid(self) -> tuple: """Returns a unique identifier for this repository. This must be a persistent identifier, for example a filepath, or @@ -112,14 +123,14 @@ def uid(self): """ return self._uid() - def __eq__(self, other): + def __eq__(self, other) -> bool: return ( isinstance(other, PackageRepository) and other.name() == self.name() and other.uid == self.uid ) - def is_empty(self): + def is_empty(self) -> bool: """Determine if the repository contains any packages. Returns: @@ -131,7 +142,7 @@ def is_empty(self): return True - def get_package_family(self, name): + def get_package_family(self, name: str) -> PackageFamilyResource | None: """Get a package family. Args: @@ -142,7 +153,7 @@ def get_package_family(self, name): """ raise NotImplementedError - def iter_package_families(self): + def iter_package_families(self) -> Iterator[PackageFamilyResource]: """Iterate over the package families in the repository, in no particular order. @@ -151,7 +162,7 @@ def iter_package_families(self): """ raise NotImplementedError - def iter_packages(self, package_family_resource): + def iter_packages(self, package_family_resource: PackageFamilyResource) -> Iterator[PackageResource]: """Iterate over the packages within the given family, in no particular order. @@ -163,7 +174,7 @@ def iter_packages(self, package_family_resource): """ raise NotImplementedError - def iter_variants(self, package_resource): + def iter_variants(self, package_resource: PackageResource) -> Iterator[VariantResource]: """Iterate over the variants within the given package. Args: @@ -174,7 +185,7 @@ def iter_variants(self, package_resource): """ raise NotImplementedError - def get_package(self, name, version): + def get_package(self, name: str, version: Version) -> PackageResourceHelper | None: """Get a package. Args: @@ -182,7 +193,7 @@ def get_package(self, name, version): version (`Version`): Package version. Returns: - `PackageResource` or None: Matching package, or None if not found. + `PackageResourceHelper` or None: Matching package, or None if not found. """ fam = self.get_package_family(name) if fam is None: @@ -194,7 +205,7 @@ def get_package(self, name, version): return None - def get_package_from_uri(self, uri): + def get_package_from_uri(self, uri: str) -> PackageResource | None: """Get a package given its URI. Args: @@ -206,7 +217,7 @@ def get_package_from_uri(self, uri): """ return None - def get_variant_from_uri(self, uri): + def get_variant_from_uri(self, uri: str) -> VariantResource | None: """Get a variant given its URI. Args: @@ -218,7 +229,7 @@ def get_variant_from_uri(self, uri): """ return None - def ignore_package(self, pkg_name, pkg_version, allow_missing=False): + def ignore_package(self, pkg_name: str, pkg_version: Version, allow_missing: bool = False) -> int: """Ignore the given package. Ignoring a package makes it invisible to further resolves. @@ -239,7 +250,7 @@ def ignore_package(self, pkg_name, pkg_version, allow_missing=False): """ raise NotImplementedError - def unignore_package(self, pkg_name, pkg_version): + def unignore_package(self, pkg_name: str, pkg_version: Version) -> int: """Unignore the given package. Args: @@ -254,7 +265,7 @@ def unignore_package(self, pkg_name, pkg_version): """ raise NotImplementedError - def remove_package(self, pkg_name, pkg_version): + def remove_package(self, pkg_name: str, pkg_version: Version) -> bool: """Remove a package. Note that this should work even if the specified package is currently @@ -269,7 +280,7 @@ def remove_package(self, pkg_name, pkg_version): """ raise NotImplementedError - def remove_package_family(self, pkg_name, force=False): + def remove_package_family(self, pkg_name: str, force: bool = False) -> bool: """Remove an empty package family. Args: @@ -281,7 +292,8 @@ def remove_package_family(self, pkg_name, force=False): """ raise NotImplementedError - def remove_ignored_since(self, days, dry_run=False, verbose=False): + def remove_ignored_since(self, days: int, dry_run: bool = False, + verbose: bool = False) -> int: """Remove packages ignored for >= specified number of days. Args: @@ -295,7 +307,7 @@ def remove_ignored_since(self, days, dry_run=False, verbose=False): """ raise NotImplementedError - def pre_variant_install(self, variant_resource): + def pre_variant_install(self, variant_resource: VariantResource) -> None: """Called before a variant is installed. If any directories are created on disk for the variant to install into, @@ -306,7 +318,7 @@ def pre_variant_install(self, variant_resource): """ pass - def on_variant_install_cancelled(self, variant_resource): + def on_variant_install_cancelled(self, variant_resource: VariantResource) -> None: """Called when a variant installation is cancelled. This is called after `pre_variant_install`, but before `install_variant`, @@ -321,7 +333,10 @@ def on_variant_install_cancelled(self, variant_resource): """ pass - def install_variant(self, variant_resource, dry_run=False, overrides=None): + def install_variant(self, + variant_resource: VariantResource, + dry_run: bool = False, + overrides: dict[str, Any] | None = None) -> VariantResource: """Install a variant into this repository. Use this function to install a variant from some other package repository @@ -343,7 +358,7 @@ def install_variant(self, variant_resource, dry_run=False, overrides=None): """ raise NotImplementedError - def get_equivalent_variant(self, variant_resource): + def get_equivalent_variant(self, variant_resource: VariantResource) -> VariantResource: """Find a variant in this repository that is equivalent to that given. A variant is equivalent to another if it belongs to a package of the @@ -362,7 +377,7 @@ def get_equivalent_variant(self, variant_resource): """ return self.install_variant(variant_resource, dry_run=True) - def get_parent_package_family(self, package_resource): + def get_parent_package_family(self, package_resource: PackageResourceHelper) -> PackageFamilyResource: """Get the parent package family of the given package. Args: @@ -373,7 +388,7 @@ def get_parent_package_family(self, package_resource): """ raise NotImplementedError - def get_parent_package(self, variant_resource): + def get_parent_package(self, variant_resource: VariantResource) -> PackageRepositoryResource: """Get the parent package of the given variant. Args: @@ -384,7 +399,8 @@ def get_parent_package(self, variant_resource): """ raise NotImplementedError - def get_variant_state_handle(self, variant_resource): + def get_variant_state_handle(self, variant_resource: PackageResource + ) -> Hashable | None: """Get a value that indicates the state of the variant. This is used for resolve caching. For example, in the 'filesystem' @@ -400,7 +416,8 @@ def get_variant_state_handle(self, variant_resource): """ return None - def get_last_release_time(self, package_family_resource): + def get_last_release_time(self, package_family_resource: PackageFamilyResource + ) -> int: """Get the last time a package was added to the given family. This information is used to cache resolves via memcached. It can be left @@ -414,7 +431,7 @@ def get_last_release_time(self, package_family_resource): """ return 0 - def make_resource_handle(self, resource_key, **variables): + def make_resource_handle(self, resource_key: str, **variables: Any) -> ResourceHandle: """Create a `ResourceHandle` Nearly all `ResourceHandle` creation should go through here, because it @@ -438,7 +455,7 @@ def make_resource_handle(self, resource_key, **variables): variables = resource_cls.normalize_variables(variables) return ResourceHandle(resource_key, variables) - def get_resource(self, resource_key, **variables): + def get_resource(self, resource_key: str | type[Resource], **variables: Any) -> Resource: """Get a resource. Attempts to get and return a cached version of the resource if @@ -454,7 +471,8 @@ def get_resource(self, resource_key, **variables): handle = self.make_resource_handle(resource_key, **variables) return self.get_resource_from_handle(handle, verify_repo=False) - def get_resource_from_handle(self, resource_handle, verify_repo=True): + def get_resource_from_handle(self, resource_handle: ResourceHandle, + verify_repo: bool = True) -> Resource: """Get a resource. Args: @@ -484,11 +502,11 @@ def get_resource_from_handle(self, resource_handle, verify_repo=True): resource._repository = self return resource - def get_package_payload_path(self, package_name, package_version=None): + def get_package_payload_path(self, package_name: str, package_version: str | Version | None = None) -> str: """Defines where a package's payload should be installed to. Args: - package_name (str): Nmae of package. + package_name (str): Name of package. package_version (str or `Version`): Package version. Returns: @@ -496,7 +514,7 @@ def get_package_payload_path(self, package_name, package_version=None): """ raise NotImplementedError - def _uid(self): + def _uid(self) -> tuple: """Unique identifier implementation. You may need to provide your own implementation. For example, consider @@ -517,7 +535,7 @@ class PackageRepositoryManager(object): Manages retrieval of resources (packages and variants) from `PackageRepository` instances, and caches these resources in a resource pool. """ - def __init__(self, resource_pool=None): + def __init__(self, resource_pool: ResourcePool | None = None) -> None: """Create a package repo manager. Args: @@ -532,9 +550,9 @@ def __init__(self, resource_pool=None): resource_pool = ResourcePool(cache_size=cache_size) self.pool = resource_pool - self.repositories = {} + self.repositories: dict[str, PackageRepository] = {} - def get_repository(self, path): + def get_repository(self, path: str) -> PackageRepository: """Get a package repository. Args: @@ -573,7 +591,7 @@ def get_repository(self, path): return repository - def are_same(self, path_1, path_2): + def are_same(self, path_1: str, path_2: str) -> bool: """Test that `path_1` and `path_2` refer to the same repository. This is more reliable than testing that the strings match, since slightly @@ -590,8 +608,8 @@ def are_same(self, path_1, path_2): repo_2 = self.get_repository(path_2) return (repo_1.uid == repo_2.uid) - def get_resource(self, resource_key, repository_type, location, - **variables): + def get_resource(self, resource_key: str, repository_type: str, + location: str, **variables: Any) -> Resource: """Get a resource. Attempts to get and return a cached version of the resource if @@ -612,7 +630,8 @@ def get_resource(self, resource_key, repository_type, location, resource = repo.get_resource(**variables) return resource - def get_resource_from_handle(self, resource_handle): + def get_resource_from_handle(self, resource_handle: ResourceHandle + ) -> Resource: """Get a resource. Args: @@ -632,14 +651,14 @@ def get_resource_from_handle(self, resource_handle): resource = repo.get_resource_from_handle(resource_handle) return resource - def clear_caches(self): + def clear_caches(self) -> None: """Clear all cached data.""" self.repositories.clear() self.pool.clear_caches() - def _get_repository(self, path, **repo_args): + def _get_repository(self, path: str, **repo_args: Any) -> PackageRepository: repo_type, location = path.split('@', 1) - cls = plugin_manager.get_plugin_class('package_repository', repo_type) + cls = plugin_manager.get_plugin_class('package_repository', repo_type, PackageRepository) repo = cls(location, self.pool, **repo_args) return repo diff --git a/src/rez/package_resources.py b/src/rez/package_resources.py index f33c4270a..268b8c180 100644 --- a/src/rez/package_resources.py +++ b/src/rez/package_resources.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.utils.resources import Resource from rez.utils.schema import Required, schema_keys, extensible_schema_dict from rez.utils.logging_ import print_warning @@ -12,12 +14,18 @@ from rez.utils.formatting import PackageRequest from rez.exceptions import PackageMetadataError, ResourceError from rez.config import config, Config, create_config -from rez.version import Version +from rez.version import Requirement, Version from rez.vendor.schema.schema import Schema, SchemaError, Optional, Or, And, Use from textwrap import dedent import os.path +from abc import abstractmethod from hashlib import sha1 +from typing import Any, Iterable, Iterator, TYPE_CHECKING +from types import FunctionType, MethodType + +if TYPE_CHECKING: + from rez.packages import Variant # package attributes created at release time @@ -76,7 +84,7 @@ def late_bound(schema): # requirements of all package-related resources # -base_resource_schema_dict = { +base_resource_schema_dict: dict[Schema, Any] = { Required("name"): str } @@ -269,7 +277,7 @@ class PackageRepositoryResource(Resource): """ schema_error = PackageMetadataError #: Type of package repository associated with this resource type. - repository_type = None + repository_type: str @classmethod def normalize_variables(cls, variables): @@ -280,22 +288,22 @@ def normalize_variables(cls, variables): return super(PackageRepositoryResource, cls).normalize_variables( variables) - def __init__(self, variables=None): + def __init__(self, variables=None) -> None: super(PackageRepositoryResource, self).__init__(variables) @cached_property - def uri(self): + def uri(self) -> str: return self._uri() @property - def location(self): + def location(self) -> str | None: return self.get("location") @property - def name(self): + def name(self) -> str | None: return self.get("name") - def _uri(self): + def _uri(self) -> str: """Return a URI. Implement this function to return a short, readable string that @@ -330,7 +338,7 @@ def normalize_variables(cls, variables): return super(PackageResource, cls).normalize_variables(variables) @cached_property - def version(self): + def version(self) -> Version: ver_str = self.get("version", "") return Version(ver_str) @@ -345,17 +353,23 @@ class VariantResource(PackageResource): this case it is the 'None' variant (the value of `index` is None). This provides some internal consistency and simplifies the implementation. """ + + @property + @abstractmethod + def parent(self) -> PackageRepositoryResource: + raise NotImplementedError + @property - def index(self): + def index(self) -> int | None: return self.get("index", None) @cached_property - def root(self): + def root(self) -> str: """Return the 'root' path of the variant.""" return self._root() @cached_property - def subpath(self): + def subpath(self) -> str: """Return the variant's 'subpath' The subpath is the relative path the variant's payload should be stored @@ -364,10 +378,12 @@ def subpath(self): """ return self._subpath() - def _root(self, ignore_shortlinks=False): + @abstractmethod + def _root(self, ignore_shortlinks: bool = False): raise NotImplementedError - def _subpath(self, ignore_shortlinks=False): + @abstractmethod + def _subpath(self, ignore_shortlinks: bool = False): raise NotImplementedError @@ -382,24 +398,40 @@ class PackageResourceHelper(PackageResource): """PackageResource with some common functionality included. """ variant_key = None + if TYPE_CHECKING: + # I think these attributes are provided dynamically be LazyAttributeMeta + _commands: list[str] | str | FunctionType | MethodType | SourceCode + _pre_commands: list[str] | str | FunctionType | MethodType | SourceCode + _post_commands: list[str] | str | FunctionType | MethodType | SourceCode + variants: list[Variant] + + @property + @abstractmethod + def base(self) -> str | None: + raise NotImplementedError + + @property + @abstractmethod + def parent(self) -> PackageRepositoryResource: + raise NotImplementedError @cached_property - def commands(self): + def commands(self) -> SourceCode: return self._convert_to_rex(self._commands) @cached_property - def pre_commands(self): + def pre_commands(self) -> SourceCode: return self._convert_to_rex(self._pre_commands) @cached_property - def post_commands(self): + def post_commands(self) -> SourceCode: return self._convert_to_rex(self._post_commands) - def iter_variants(self): + def iter_variants(self) -> Iterator[VariantResourceHelper]: num_variants = len(self.variants or []) if num_variants == 0: - indexes = [None] + indexes: Iterable[int | None] = [None] else: indexes = range(num_variants) @@ -412,7 +444,7 @@ def iter_variants(self): index=index) yield variant - def _convert_to_rex(self, commands): + def _convert_to_rex(self, commands: list[str] | str | FunctionType | MethodType | SourceCode) -> SourceCode: if isinstance(commands, list): from rez.utils.backcompat import convert_old_commands @@ -453,12 +485,12 @@ class VariantResourceHelper(VariantResource, metaclass=_Metas): # forward Package attributes onto ourself keys = schema_keys(package_schema) - set(["variants"]) - def _uri(self): + def _uri(self) -> str: index = self.index idxstr = '' if index is None else str(index) return "%s[%s]" % (self.parent.uri, idxstr) - def _subpath(self, ignore_shortlinks=False): + def _subpath(self, ignore_shortlinks: bool = False) -> str | None: if self.index is None: return None @@ -488,7 +520,7 @@ def _subpath(self, ignore_shortlinks=False): subpath = os.path.join(*dirs) return subpath - def _root(self, ignore_shortlinks=False): + def _root(self, ignore_shortlinks: bool = False) -> str | None: if self.base is None: return None elif self.index is None: @@ -499,7 +531,7 @@ def _root(self, ignore_shortlinks=False): return root @cached_property - def variant_requires(self): + def variant_requires(self) -> list[Requirement]: index = self.index if index is None: return [] diff --git a/src/rez/package_search.py b/src/rez/package_search.py index 26b7f60dc..831da0370 100644 --- a/src/rez/package_search.py +++ b/src/rez/package_search.py @@ -9,6 +9,7 @@ repository. The algorithms here serve as backup for those package repositories that do not provide an implementation. """ +from __future__ import annotations import fnmatch from collections import defaultdict @@ -26,9 +27,12 @@ from rez.version import Requirement -def get_reverse_dependency_tree(package_name, depth=None, paths=None, - build_requires=False, - private_build_requires=False): +def get_reverse_dependency_tree(package_name: str, + depth: int | None = None, + paths: list[str] | None = None, + build_requires: bool = False, + private_build_requires: bool = False + ) -> tuple[list[list[str]], digraph]: """Find packages that depend on the given package. This is a reverse dependency lookup. A tree is constructed, showing what @@ -126,7 +130,7 @@ def get_reverse_dependency_tree(package_name, depth=None, paths=None, return pkgs_list, g -def get_plugins(package_name, paths=None): +def get_plugins(package_name: str, paths: list[str] | None = None) -> list[str]: """Find packages that are plugins of the given package. Args: @@ -167,7 +171,7 @@ class ResourceSearchResult(object): Will contain either a package, variant, or name of a package family (str). """ - def __init__(self, resource, resource_type, validation_error=None): + def __init__(self, resource, resource_type, validation_error=None) -> None: self.resource = resource self.resource_type = resource_type self.validation_error = validation_error @@ -176,8 +180,11 @@ def __init__(self, resource, resource_type, validation_error=None): class ResourceSearcher(object): """Search for resources (packages, variants or package families). """ - def __init__(self, package_paths=None, resource_type=None, no_local=False, - latest=False, after_time=None, before_time=None, validate=False): + def __init__(self, package_paths: list[str] | None = None, + resource_type: str | None = None, no_local: bool = False, + latest: bool = False, + after_time: int | None = None, before_time: int | None = None, + validate: bool = False) -> None: """Create resource search. Args: @@ -206,13 +213,13 @@ def __init__(self, package_paths=None, resource_type=None, no_local=False, self.validate = validate if package_paths: - self.package_paths = package_paths + self.package_paths: list[str] | None = package_paths elif no_local: self.package_paths = config.nonlocal_packages_path else: self.package_paths = None - def iter_resources(self, resources_request=None): + def iter_resources(self, resources_request: str | None = None) -> None: """Iterate over matching resources. Args: @@ -228,7 +235,7 @@ def iter_resources(self, resources_request=None): packages or variants. """ - def search(self, resources_request=None): + def search(self, resources_request: str | None = None) -> tuple[str, list[ResourceSearchResult]]: """Search for resources. Args: @@ -353,7 +360,7 @@ class ResourceSearchResultFormatter(object): 'qualified_name' ) - def __init__(self, output_format=None, suppress_newlines=False): + def __init__(self, output_format: str | None = None, suppress_newlines: bool = False) -> None: """ Args: output_format (str): String that can contain keywords such as @@ -366,7 +373,7 @@ def __init__(self, output_format=None, suppress_newlines=False): self.output_format = output_format self.suppress_newlines = suppress_newlines - def print_search_results(self, search_results, buf=sys.stdout): + def print_search_results(self, search_results: list[ResourceSearchResult], buf=sys.stdout) -> None: """Print formatted search results. Args: @@ -378,7 +385,7 @@ def print_search_results(self, search_results, buf=sys.stdout): for txt, style in formatted_lines: pr(txt, style) - def format_search_results(self, search_results): + def format_search_results(self, search_results: list[ResourceSearchResult]): """Format search results. Args: @@ -395,7 +402,7 @@ def format_search_results(self, search_results): return formatted_lines - def _format_search_result(self, resource_search_result): + def _format_search_result(self, resource_search_result: ResourceSearchResult): formatted_lines = [] # just ignore formatting if family diff --git a/src/rez/package_serialise.py b/src/rez/package_serialise.py index c17b0c0ee..759e74e04 100644 --- a/src/rez/package_serialise.py +++ b/src/rez/package_serialise.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.serialise import FileFormat from rez.package_resources import help_schema, late_bound from rez.vendor.schema.schema import Schema, Optional, And, Or, Use @@ -13,6 +15,10 @@ from rez.utils.schema import Required from rez.utils.yaml import dump_yaml from pprint import pformat +from typing import Any, TYPE_CHECKING + +if TYPE_CHECKING: + from rez.utils.typing import SupportsWrite # preferred order of keys in a package definition file @@ -110,7 +116,8 @@ }) -def dump_package_data(data, buf, format_=FileFormat.py, skip_attributes=None): +def dump_package_data(data: dict, buf: SupportsWrite, format_: FileFormat = FileFormat.py, + skip_attributes: list[str] | None = None) -> None: """Write package data to `buf`. Args: @@ -150,7 +157,7 @@ def dump_package_data(data, buf, format_=FileFormat.py, skip_attributes=None): # the package file to see what the original commands were, but they don't get # processed by rex. # -def _commented_old_command_annotations(sourcecode): +def _commented_old_command_annotations(sourcecode: SourceCode) -> SourceCode: lines = sourcecode.source.split('\n') for i, line in enumerate(lines): if line.startswith("comment('OLD COMMAND:"): @@ -162,7 +169,7 @@ def _commented_old_command_annotations(sourcecode): return other -def _dump_package_data_yaml(items, buf): +def _dump_package_data_yaml(items: list[tuple[str, Any]], buf: SupportsWrite) -> None: for i, (key, value) in enumerate(items): if isinstance(value, SourceCode) \ and key in ("commands", "pre_commands", "post_commands"): @@ -175,7 +182,7 @@ def _dump_package_data_yaml(items, buf): print('', file=buf) -def _dump_package_data_py(items, buf): +def _dump_package_data_py(items: list[tuple[str, Any]], buf: SupportsWrite) -> None: print("# -*- coding: utf-8 -*-\n", file=buf) for i, (key, value) in enumerate(items): diff --git a/src/rez/package_test.py b/src/rez/package_test.py index 19eecf3f9..3d62574fc 100644 --- a/src/rez/package_test.py +++ b/src/rez/package_test.py @@ -2,9 +2,11 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.config import config from rez.resolved_context import ResolvedContext -from rez.packages import get_latest_package_from_string +from rez.packages import get_latest_package_from_string, Package from rez.exceptions import RezError, PackageNotFoundError, PackageTestError from rez.utils.data_utils import RO_AttrDictWrapper from rez.utils.colorize import heading, Printer @@ -47,10 +49,10 @@ class PackageTestRunner(object): Commands can also be a list - in this case, the test process is launched directly, rather than interpreted via a shell. """ - def __init__(self, package_request, use_current_env=False, + def __init__(self, package_request, use_current_env: bool = False, extra_package_requests=None, package_paths=None, stdout=None, - stderr=None, verbose=0, dry_run=False, stop_on_fail=False, - cumulative_test_results=None, **context_kwargs): + stderr=None, verbose: int=0, dry_run: bool = False, stop_on_fail: bool = False, + cumulative_test_results=None, **context_kwargs) -> None: """Create a package tester. Args: @@ -92,7 +94,7 @@ def __init__(self, package_request, use_current_env=False, else package_paths) self.test_results = PackageTestResults() - self.package = None + self.package: Package | None = None self.contexts = {} self.stopped_on_fail = False @@ -172,7 +174,7 @@ def _select(value): ) if ran_once: - def _select(key, value): + def _select_kv(key, value) -> bool: if isinstance(value, dict): value = value.get("on_variants") else: @@ -185,7 +187,7 @@ def _select(key, value): tests_dict = dict( (k, v) for k, v in tests_dict.items() - if _select(k, v) + if _select_kv(k, v) ) return sorted(tests_dict.keys()) @@ -443,7 +445,7 @@ def run_test(self, test_name, extra_test_args=None): ) continue - def _pre_test_commands(executor): + def _pre_test_commands(executor) -> None: # run package.py:pre_test_commands() if present pre_test_commands = getattr(variant, "pre_test_commands") if not pre_test_commands: @@ -499,17 +501,17 @@ def _pre_test_commands(executor): return exitcode - def print_summary(self): + def print_summary(self) -> None: self.test_results.print_summary() - def _add_test_result(self, *nargs, **kwargs): + def _add_test_result(self, *nargs, **kwargs) -> None: self.test_results.add_test_result(*nargs, **kwargs) if self.cumulative_test_results: self.cumulative_test_results.add_test_result(*nargs, **kwargs) @classmethod - def _print_header(cls, txt, *nargs): + def _print_header(cls, txt, *nargs) -> None: pr = Printer(sys.stdout) pr(txt % nargs, heading) @@ -533,11 +535,11 @@ def _on_variant_requires(self, variant, params): # If the combined requirements, minus conflict requests, is equal to the # variant's requirements, then this variant is selected. # - reqs1 = RequirementList(x for x in reqlist if not x.conflict) - reqs2 = RequirementList(x for x in variant.variant_requires if not x.conflict) + reqs1 = RequirementList([x for x in reqlist if not x.conflict]) + reqs2 = RequirementList([x for x in variant.variant_requires if not x.conflict]) return (reqs1 == reqs2) - def _get_test_info(self, test_name, variant): + def _get_test_info(self, test_name: str, variant) -> dict | None: tests_dict = variant.tests or {} test_entry = tests_dict.get(test_name) @@ -581,7 +583,7 @@ def _get_test_info(self, test_name, variant): "on_variants": test_entry.get("on_variants", False) } - def _get_context(self, requires, quiet=False): + def _get_context(self, requires, quiet: bool = False): # if using current env, only return current context if it meets # requirements, otherwise return None @@ -590,7 +592,7 @@ def _get_context(self, requires, quiet=False): if current_context is None: return None - reqs = map(Requirement, requires) + reqs = [Requirement(x) for x in requires] current_reqs = current_context.get_resolve_as_exact_requests() meets_requirements = ( @@ -681,29 +683,29 @@ class PackageTestResults(object): """ valid_statuses = ("success", "failed", "skipped") - def __init__(self): + def __init__(self) -> None: self.test_results = [] @property - def num_tests(self): + def num_tests(self) -> int: """Get the number of tests, regardless of stats. """ return len(self.test_results) @property - def num_success(self): + def num_success(self) -> int: """Get the number of successful test runs. """ return len([x for x in self.test_results if x["status"] == "success"]) @property - def num_failed(self): + def num_failed(self) -> int: """Get the number of failed test runs. """ return len([x for x in self.test_results if x["status"] == "failed"]) @property - def num_skipped(self): + def num_skipped(self) -> int: """Get the number of skipped test runs. """ return len([x for x in self.test_results if x["status"] == "skipped"]) @@ -719,7 +721,7 @@ def add_test_result(self, test_name, variant, status, description): "description": description }) - def print_summary(self): + def print_summary(self) -> None: from rez.utils.formatting import columnise pr = Printer(sys.stdout) diff --git a/src/rez/packages.py b/src/rez/packages.py index dc816303b..3ce68fefe 100644 --- a/src/rez/packages.py +++ b/src/rez/packages.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.package_repository import package_repository_manager from rez.package_resources import PackageFamilyResource, PackageResource, \ VariantResource, package_family_schema, package_schema, variant_schema, \ @@ -14,6 +16,7 @@ from rez.utils.schema import schema_keys from rez.utils.resources import ResourceHandle, ResourceWrapper from rez.exceptions import PackageFamilyNotFoundError, ResourceError +from rez.utils.typing import SupportsWrite from rez.version import Version, VersionRange from rez.version import VersionedObject from rez.serialise import FileFormat @@ -21,6 +24,19 @@ import os import sys +from typing import overload, Any, Iterator, TypeVar, TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Literal # not available in typing module until 3.8 + from rez.config import Config + from rez.developer_package import DeveloperPackage + from rez.version import Requirement + from rez.package_repository import PackageRepository + from rez.resolved_context import ResolvedContext + from rez.utils.resources import Resource + +T = TypeVar("T") +PackageT = TypeVar("PackageT", bound="Package") # ------------------------------------------------------------------------------ # package-related classes @@ -30,13 +46,13 @@ class PackageRepositoryResourceWrapper(ResourceWrapper, StringFormatMixin): format_expand = StringFormatType.unchanged - def validated_data(self): + def validated_data(self) -> dict: data = ResourceWrapper.validated_data(self) data = dict((k, v) for k, v in data.items() if v is not None) return data @property - def repository(self): + def repository(self) -> PackageRepository: """The package repository this resource comes from. Returns: @@ -54,11 +70,11 @@ class PackageFamily(PackageRepositoryResourceWrapper): """ keys = schema_keys(package_family_schema) - def __init__(self, resource): + def __init__(self, resource: PackageFamilyResource) -> None: _check_class(resource, PackageFamilyResource) super(PackageFamily, self).__init__(resource) - def iter_packages(self): + def iter_packages(self) -> Iterator[Package]: """Iterate over the packages within this family, in no particular order. Returns: @@ -75,25 +91,25 @@ class PackageBaseResourceWrapper(PackageRepositoryResourceWrapper): "requires": late_requires_schema } - def __init__(self, resource, context=None): + def __init__(self, resource: PackageResource | VariantResource, context: ResolvedContext | None = None) -> None: super(PackageBaseResourceWrapper, self).__init__(resource) self.context = context # cached results of late-bound funcs self._late_binding_returnvalues = {} - def set_context(self, context): + def set_context(self, context: ResolvedContext | None) -> None: self.context = context - def arbitrary_keys(self): + def arbitrary_keys(self) -> set[str]: raise NotImplementedError @property - def uri(self): + def uri(self) -> str: return self.resource.uri @property - def config(self): + def config(self) -> Config: """Returns the config for this package. Defaults to global config if this package did not provide a 'config' @@ -102,14 +118,14 @@ def config(self): return self.resource.config or config @cached_property - def is_local(self): + def is_local(self) -> bool: """Returns True if the package is in the local package repository""" local_repo = package_repository_manager.get_repository( self.config.local_packages_path) return (self.resource._repository.uid == local_repo.uid) - def print_info(self, buf=None, format_=FileFormat.yaml, - skip_attributes=None, include_release=False): + def print_info(self, buf: SupportsWrite | None = None, format_: FileFormat = FileFormat.yaml, + skip_attributes: list[str] | None = None, include_release: bool = False) -> None: """Print the contents of the package. Args: @@ -140,7 +156,7 @@ def print_info(self, buf=None, format_=FileFormat.yaml, dump_package_data(data, buf=buf, format_=format_, skip_attributes=skip_attributes) - def _wrap_forwarded(self, key, value): + def _wrap_forwarded(self, key: str, value: Any) -> Any: if isinstance(value, SourceCode) and value.late_binding: # get cached return value if present value_ = self._late_binding_returnvalues.get(key, KeyError) @@ -160,8 +176,8 @@ def _wrap_forwarded(self, key, value): else: return value - def _eval_late_binding(self, sourcecode): - g = {} + def _eval_late_binding(self, sourcecode: SourceCode[T]) -> T: + g: dict[str, Any] = {} if self.context is None: g["in_context"] = lambda: False @@ -200,19 +216,19 @@ class Package(PackageBaseResourceWrapper): #: funcs, where ``this`` may be a package or variant. is_variant = False - def __init__(self, resource, context=None): + def __init__(self, resource: PackageResource, context: ResolvedContext | None = None) -> None: _check_class(resource, PackageResource) super(Package, self).__init__(resource, context) # arbitrary keys - def __getattr__(self, name): + def __getattr__(self, name: str) -> Any: if name in self.data: value = self.data[name] return self._wrap_forwarded(name, value) else: raise AttributeError("Package instance has no attribute '%s'" % name) - def arbitrary_keys(self): + def arbitrary_keys(self) -> set[str]: """Get the arbitrary keys present in this package. These are any keys not in the standard list ('name', 'version' etc). @@ -223,7 +239,7 @@ def arbitrary_keys(self): return set(self.data.keys()) - set(self.keys) @cached_property - def qualified_name(self): + def qualified_name(self) -> str: """Get the qualified name of the package. Returns: @@ -232,7 +248,7 @@ def qualified_name(self): o = VersionedObject.construct(self.name, self.version) return str(o) - def as_exact_requirement(self): + def as_exact_requirement(self) -> str: """Get the package, as an exact requirement string. Returns: @@ -242,7 +258,7 @@ def as_exact_requirement(self): return o.as_exact_requirement() @cached_property - def parent(self): + def parent(self) -> PackageFamily | None: """Get the parent package family. Returns: @@ -252,11 +268,11 @@ def parent(self): return PackageFamily(family) if family else None @cached_property - def num_variants(self): + def num_variants(self) -> int: return len(self.data.get("variants", [])) @property - def is_relocatable(self): + def is_relocatable(self) -> bool: """True if the package and its payload is safe to copy. """ if self.relocatable is not None: @@ -276,7 +292,7 @@ def is_relocatable(self): return config.default_relocatable @property - def is_cachable(self): + def is_cachable(self) -> bool: """True if the package and its payload is safe to cache locally. """ if self.cachable is not None: @@ -301,7 +317,7 @@ def is_cachable(self): return self.is_relocatable - def iter_variants(self): + def iter_variants(self) -> Iterator[Variant]: """Iterate over the variants within this package, in index order. Returns: @@ -310,7 +326,7 @@ def iter_variants(self): for variant in self.repository.iter_variants(self.resource): yield Variant(variant, context=self.context, parent=self) - def get_variant(self, index=None): + def get_variant(self, index: int | None = None) -> Variant | None: """Get the variant with the associated index. Returns: @@ -319,6 +335,7 @@ def get_variant(self, index=None): for variant in self.iter_variants(): if variant.index == index: return variant + return None class Variant(PackageBaseResourceWrapper): @@ -337,28 +354,29 @@ class Variant(PackageBaseResourceWrapper): #: See :attr:`Package.is_variant`. is_variant = True - def __init__(self, resource, context=None, parent=None): + def __init__(self, resource: VariantResource, context: ResolvedContext | None = None, + parent: Package | None = None) -> None: _check_class(resource, VariantResource) super(Variant, self).__init__(resource, context) self._parent = parent # arbitrary keys - def __getattr__(self, name): + def __getattr__(self, name: str) -> Any: try: return self.parent.__getattr__(name) except AttributeError: raise AttributeError("Variant instance has no attribute '%s'" % name) - def arbitrary_keys(self): + def arbitrary_keys(self) -> set[str]: return self.parent.arbitrary_keys() @cached_property - def qualified_package_name(self): + def qualified_package_name(self) -> str: o = VersionedObject.construct(self.name, self.version) return str(o) @cached_property - def qualified_name(self): + def qualified_name(self) -> str: """Get the qualified name of the variant. Returns: @@ -368,7 +386,7 @@ def qualified_name(self): return "%s[%s]" % (self.qualified_package_name, idxstr) @cached_property - def parent(self): + def parent(self) -> Package: """Get the parent package. Returns: @@ -386,7 +404,7 @@ def parent(self): return self._parent @property - def variant_requires(self): + def variant_requires(self) -> list[Requirement]: """Get the subset of requirements specific to this variant. Returns: @@ -398,7 +416,7 @@ def variant_requires(self): return self.parent.variants[self.index] or [] @property - def requires(self): + def requires(self) -> list[Requirement]: """Get variant requirements. This is a concatenation of the package requirements and those of this @@ -411,7 +429,8 @@ def requires(self): (self.parent.requires or []) + self.variant_requires ) - def get_requires(self, build_requires=False, private_build_requires=False): + def get_requires(self, build_requires: bool = False, private_build_requires: bool = False + ) -> list[Requirement]: """Get the requirements of the variant. Args: @@ -431,7 +450,8 @@ def get_requires(self, build_requires=False, private_build_requires=False): return requires - def install(self, path, dry_run=False, overrides=None): + def install(self, path: str, dry_run: bool = False, + overrides: dict[str, Any] | None = None) -> Variant | None: """Install this variant into another package repository. If the package already exists, this variant will be correctly merged @@ -463,7 +483,7 @@ def install(self, path, dry_run=False, overrides=None): return Variant(resource) @property - def _non_shortlinked_subpath(self): + def _non_shortlinked_subpath(self) -> str: return self.resource._subpath(ignore_shortlinks=True) @@ -472,7 +492,7 @@ class PackageSearchPath(object): For example, $REZ_PACKAGES_PATH refers to a list of repositories. """ - def __init__(self, packages_path): + def __init__(self, packages_path: list[str]) -> None: """Create a package repository list. Args: @@ -480,7 +500,7 @@ def __init__(self, packages_path): """ self.paths = packages_path - def iter_packages(self, name, range_=None): + def iter_packages(self, name: str, range_: VersionRange | str | None = None) -> Iterator[Package]: """See `iter_packages`. Returns: @@ -489,7 +509,7 @@ def iter_packages(self, name, range_=None): for package in iter_packages(name=name, range_=range_, paths=self.paths): yield package - def __contains__(self, package): + def __contains__(self, package: Package | Variant) -> bool: """See if a package is in this list of repositories. Note: @@ -506,7 +526,7 @@ def __contains__(self, package): return (package.resource._repository.uid in self._repository_uids) @cached_property - def _repository_uids(self): + def _repository_uids(self) -> set[tuple[str, str]]: uids = set() for path in self.paths: repo = package_repository_manager.get_repository(path) @@ -518,7 +538,7 @@ def _repository_uids(self): # resource acquisition functions # ------------------------------------------------------------------------------ -def iter_package_families(paths=None): +def iter_package_families(paths: list[str] | None = None) -> Iterator[PackageFamily]: """Iterate over package families, in no particular order. Note that multiple package families with the same name can be returned. @@ -538,7 +558,8 @@ def iter_package_families(paths=None): yield PackageFamily(resource) -def iter_packages(name, range_=None, paths=None): +def iter_packages(name: str, range_: VersionRange | str | None = None, + paths: list[str] | None = None) -> Iterator[Package]: """Iterate over `Package` instances, in no particular order. Packages of the same name and version earlier in the search path take @@ -574,7 +595,7 @@ def iter_packages(name, range_=None, paths=None): yield Package(package_resource) -def get_package(name, version, paths=None): +def get_package(name: str, version: Version | str, paths: list[str] | None = None) -> Package | None: """Get a package by searching a list of repositories. Args: @@ -598,7 +619,7 @@ def get_package(name, version, paths=None): return None -def get_package_family_from_repository(name, path): +def get_package_family_from_repository(name: str, path: str) -> PackageFamily | None: """Get a package family from a repository. Args: @@ -616,7 +637,7 @@ def get_package_family_from_repository(name, path): return PackageFamily(family_resource) -def get_package_from_repository(name, version, path): +def get_package_from_repository(name: str, version: Version | str, path: str) -> Package | None: """Get a package from a repository. Args: @@ -638,7 +659,7 @@ def get_package_from_repository(name, version, path): return Package(package_resource) -def get_package_from_handle(package_handle): +def get_package_from_handle(package_handle: ResourceHandle | dict) -> Package: """Create a package given its handle (or serialized dict equivalent) Args: @@ -656,7 +677,7 @@ def get_package_from_handle(package_handle): return package -def get_package_from_string(txt, paths=None): +def get_package_from_string(txt: str, paths: list[str] | None = None) -> Package | None: """Get a package given a string. Args: @@ -671,12 +692,12 @@ def get_package_from_string(txt, paths=None): return get_package(o.name, o.version, paths=paths) -def get_developer_package(path, format=None): +def get_developer_package(path: str, format: FileFormat | None = None) -> DeveloperPackage: """Create a developer package. Args: path (str): Path to dir containing package definition file. - format (str): Package definition file format, detected if None. + format (FileFormat): Package definition file format, detected if None. Returns: `DeveloperPackage`. @@ -685,7 +706,15 @@ def get_developer_package(path, format=None): return DeveloperPackage.from_path(path, format=format) -def create_package(name, data, package_cls=None): +@overload +def create_package(name: str, data: dict, package_cls: type[PackageT]) -> PackageT: + pass + +@overload +def create_package(name: str, data: dict) -> Package: + pass + +def create_package(name: str, data: dict, package_cls: type[Package] | None = None) -> Package: """Create a package given package data. Args: @@ -700,7 +729,8 @@ def create_package(name, data, package_cls=None): return maker.get_package() -def get_variant(variant_handle, context=None): +def get_variant(variant_handle: ResourceHandle | dict, + context: ResolvedContext | None = None) -> Variant: """Create a variant given its handle (or serialized dict equivalent) Args: @@ -721,7 +751,7 @@ def get_variant(variant_handle, context=None): return variant -def get_package_from_uri(uri, paths=None): +def get_package_from_uri(uri: str, paths: list[str] | None = None) -> Package | None: """Get a package given its URI. Args: @@ -733,7 +763,7 @@ def get_package_from_uri(uri, paths=None): Returns: `Package`, or None if the package could not be found. """ - def _find_in_path(path): + def _find_in_path(path: str) -> Package | None: repo = package_repository_manager.get_repository(path) pkg_resource = repo.get_package_from_uri(uri) if pkg_resource is not None: @@ -768,7 +798,7 @@ def _find_in_path(path): return _find_in_path(path) -def get_variant_from_uri(uri, paths=None): +def get_variant_from_uri(uri: str, paths: list[str] | None = None) -> Variant | None: """Get a variant given its URI. Args: @@ -780,7 +810,7 @@ def get_variant_from_uri(uri, paths=None): Returns: `Variant`, or None if the variant could not be found. """ - def _find_in_path(path): + def _find_in_path(path: str) -> Variant | None: repo = package_repository_manager.get_repository(path) variant_resource = repo.get_variant_from_uri(uri) if variant_resource is not None: @@ -822,7 +852,7 @@ def _find_in_path(path): return _find_in_path(path) -def get_last_release_time(name, paths=None): +def get_last_release_time(name: str, paths: list[str] | None = None) -> int: """Returns the most recent time this package was released. Note that releasing a variant into an already-released package is also @@ -848,7 +878,7 @@ def get_last_release_time(name, paths=None): return max_time -def get_completions(prefix, paths=None, family_only=False): +def get_completions(prefix: str, paths: list[str] | None = None, family_only: bool = False) -> set[str]: """Get autocompletion options given a prefix string. Example: @@ -928,7 +958,8 @@ def get_latest_package(name, range_=None, paths=None, error=False): return None -def get_latest_package_from_string(txt, paths=None, error=False): +def get_latest_package_from_string(txt: str, paths: list[str] | None = None, + error: bool = False) -> Package | None: """Get the latest package found within the given request string. Args: @@ -949,7 +980,8 @@ def get_latest_package_from_string(txt, paths=None, error=False): error=error) -def _get_families(name, paths=None): +def _get_families(name: str, paths: list[str] | None = None + ) -> list[tuple[PackageRepository, PackageFamilyResource]]: entries = [] for path in (paths or config.packages_path): repo = package_repository_manager.get_repository(path) @@ -960,7 +992,7 @@ def _get_families(name, paths=None): return entries -def _check_class(resource, cls): +def _check_class(resource: Resource, cls: type[Resource]) -> None: if not isinstance(resource, cls): raise ResourceError("Expected %s, got %s" % (cls.__name__, resource.__class__.__name__)) diff --git a/src/rez/pip.py b/src/rez/pip.py index 1f7e60185..7c93e5d8c 100644 --- a/src/rez/pip.py +++ b/src/rez/pip.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.packages import get_latest_package from rez.version import Version from rez.vendor.distlib.database import DistributionPath @@ -43,7 +45,7 @@ class InstallMode(Enum): min_deps = 1 -def run_pip_command(command_args, pip_version=None, python_version=None): +def run_pip_command(command_args, pip_version=None, python_version=None) -> Popen: """Run a pip command. Args: command_args (list of str): Args to pip. @@ -234,7 +236,7 @@ def find_pip_from_context(python_version, pip_version=None): def pip_install_package(source_name, pip_version=None, python_version=None, - mode=InstallMode.min_deps, release=False, prefix=None, + mode=InstallMode.min_deps, release: bool = False, prefix=None, extra_args=None): """Install a pip-compatible python package as a rez package. Args: @@ -319,7 +321,7 @@ def pip_install_package(source_name, pip_version=None, python_version=None, distributions = list(distribution_path.get_distributions()) dist_names = [x.name for x in distributions] - def log_append_pkg_variants(pkg_maker): + def log_append_pkg_variants(pkg_maker) -> None: template = '{action} [{package.qualified_name}] {package.uri}{suffix}' actions_variants = [ ( @@ -377,7 +379,7 @@ def log_append_pkg_variants(pkg_maker): message += '\nTry again with rez-pip --verbose ...' print_warning(message.format(distribution.name_and_version)) - def make_root(variant, path): + def make_root(variant, path) -> None: """Using distlib to iterate over all installed files of the current distribution to copy files to the target directory of the rez package variant @@ -574,7 +576,7 @@ def get_mapping(rel_src): return result -def _option_present(opts, *args): +def _option_present(opts, *args) -> bool: for opt in opts: for arg in args: if opt == arg or opt.startswith(arg + '='): @@ -598,7 +600,7 @@ def _cmd(context, command): raise BuildError("Failed to download source with pip: %s" % cmd_str) -def _check_found(py_exe, version_text, log_invalid=True): +def _check_found(py_exe, version_text, log_invalid: bool = True): """Check the Python and pip version text found. Args: @@ -628,6 +630,6 @@ def _check_found(py_exe, version_text, log_invalid=True): _verbose = config.debug("package_release") -def _log(msg): +def _log(msg) -> None: if _verbose: print_debug(msg) diff --git a/src/rez/plugin_managers.py b/src/rez/plugin_managers.py index 64b1d8f09..64e7ec6bd 100644 --- a/src/rez/plugin_managers.py +++ b/src/rez/plugin_managers.py @@ -5,6 +5,8 @@ """ Manages loading of all types of Rez plugins. """ +from __future__ import annotations + from rez.config import config, expand_system_vars, _load_config_from_filepaths from rez.utils.formatting import columnise from rez.utils.schema import dict_to_schema @@ -12,9 +14,13 @@ from rez.utils.logging_ import print_debug, print_warning from rez.exceptions import RezPluginError from zipimport import zipimporter +from typing import overload, Any, TypeVar import pkgutil import os.path import sys +import types + +T = TypeVar("T") # modified from pkgutil standard library: @@ -54,7 +60,7 @@ def extend_path(path, name): init_py = "__init__" + os.extsep + "py" path = path[:] - def append_if_valid(dir_): + def append_if_valid(dir_) -> None: if os.path.isdir(dir_): subdir = os.path.normcase(os.path.join(dir_, pname)) initfile = os.path.join(subdir, init_py) @@ -74,9 +80,9 @@ def append_if_valid(dir_): return path -def uncache_rezplugins_module_paths(instance=None): +def uncache_rezplugins_module_paths(instance=None) -> None: instance = instance or plugin_manager - cached_property.uncache(instance, "rezplugins_module_paths") + cached_property.uncache(instance, "rezplugins_module_paths") # type: ignore[attr-defined] class RezPluginType(object): @@ -85,30 +91,30 @@ class RezPluginType(object): 'type_name' must correspond with one of the source directories found under the 'plugins' directory. """ - type_name = None + type_name: str - def __init__(self): + def __init__(self) -> None: if self.type_name is None: raise TypeError("Subclasses of RezPluginType must provide a " "'type_name' attribute") self.pretty_type_name = self.type_name.replace('_', ' ') - self.plugin_classes = {} - self.failed_plugins = {} - self.plugin_modules = {} + self.plugin_classes: dict[str, type] = {} + self.failed_plugins: dict[str, str] = {} + self.plugin_modules: dict[str, types.ModuleType] = {} self.config_data = {} self.load_plugins() - def __repr__(self): + def __repr__(self) -> str: return '%s(%s)' % (self.__class__.__name__, self.plugin_classes.keys()) - def register_plugin(self, plugin_name, plugin_class, plugin_module): + def register_plugin(self, plugin_name: str, plugin_class: type, plugin_module: types.ModuleType) -> None: # TODO: check plugin_class to ensure it is a sub-class of expected base-class? # TODO: perhaps have a Plugin base class. This introduces multiple # inheritance in Shell class though :/ self.plugin_classes[plugin_name] = plugin_class self.plugin_modules[plugin_name] = plugin_module - def load_plugins(self): + def load_plugins(self) -> None: import pkgutil from importlib import import_module type_module_name = 'rezplugins.' + self.type_name @@ -205,7 +211,7 @@ def load_plugins(self): data, _ = _load_config_from_filepaths([os.path.join(path, "rezconfig")]) deep_update(self.config_data, data) - def get_plugin_class(self, plugin_name): + def get_plugin_class(self, plugin_name: str) -> type: """Returns the class registered under the given plugin name.""" try: return self.plugin_classes[plugin_name] @@ -213,7 +219,7 @@ def get_plugin_class(self, plugin_name): raise RezPluginError("Unrecognised %s plugin: '%s'" % (self.pretty_type_name, plugin_name)) - def get_plugin_module(self, plugin_name): + def get_plugin_module(self, plugin_name: str) -> types.ModuleType: """Returns the module containing the plugin of the given name.""" try: return self.plugin_modules[plugin_name] @@ -235,7 +241,7 @@ def config_schema(self): deep_update(d, d_) return dict_to_schema(d, required=True, modifier=expand_system_vars) - def create_instance(self, plugin, **instance_kwargs): + def create_instance(self, plugin: str, **instance_kwargs) -> Any: """Create and return an instance of the given plugin.""" return self.get_plugin_class(plugin)(**instance_kwargs) @@ -293,8 +299,8 @@ def register_plugin(): This is important because it ensures that rez's copy of 'rezplugins' is always found first. """ - def __init__(self): - self._plugin_types = {} + def __init__(self) -> None: + self._plugin_types: dict[str, LazySingleton[RezPluginType]] = {} @cached_property def rezplugins_module_paths(self): @@ -329,14 +335,14 @@ def rezplugins_module_paths(self): # -- plugin types - def _get_plugin_type(self, plugin_type): + def _get_plugin_type(self, plugin_type: str) -> RezPluginType: try: return self._plugin_types[plugin_type]() except KeyError: raise RezPluginError("Unrecognised plugin type: '%s'" % plugin_type) - def register_plugin_type(self, type_class): + def register_plugin_type(self, type_class: type[RezPluginType]) -> None: if not issubclass(type_class, RezPluginType): raise TypeError("'type_class' must be a RezPluginType sub class") if type_class.type_name is None: @@ -344,38 +350,50 @@ def register_plugin_type(self, type_class): "'type_name' attribute") self._plugin_types[type_class.type_name] = LazySingleton(type_class) - def get_plugin_types(self): + def get_plugin_types(self) -> list[str]: """Return a list of the registered plugin types.""" - return self._plugin_types.keys() + return list(self._plugin_types.keys()) # -- plugins - def get_plugins(self, plugin_type): + def get_plugins(self, plugin_type: str) -> list[str]: """Return a list of the registered names available for the given plugin type.""" - return self._get_plugin_type(plugin_type).plugin_classes.keys() + return list(self._get_plugin_type(plugin_type).plugin_classes.keys()) + + @overload + def get_plugin_class(self, plugin_type: str, plugin_name: str) -> type: + pass + + @overload + def get_plugin_class(self, plugin_type: str, plugin_name: str, expected_type: type[T]) -> type[T]: + pass - def get_plugin_class(self, plugin_type, plugin_name): + def get_plugin_class(self, plugin_type: str, plugin_name: str, expected_type: type | None = None) -> type: """Return the class registered under the given plugin name.""" plugin = self._get_plugin_type(plugin_type) - return plugin.get_plugin_class(plugin_name) + cls = plugin.get_plugin_class(plugin_name) + if expected_type is not None and not isinstance(cls, type) and issubclass(cls, expected_type): + raise RezPluginError("%s: Plugin class for %s was not the expected type: %s != %s" + % (plugin.pretty_type_name, plugin_name, cls, expected_type)) + return cls - def get_plugin_module(self, plugin_type, plugin_name): + def get_plugin_module(self, plugin_type: str, plugin_name: str) -> types.ModuleType: """Return the module defining the class registered under the given plugin name.""" plugin = self._get_plugin_type(plugin_type) return plugin.get_plugin_module(plugin_name) - def get_plugin_config_data(self, plugin_type): + def get_plugin_config_data(self, plugin_type: str): """Return the merged configuration data for the plugin type.""" plugin = self._get_plugin_type(plugin_type) return plugin.config_data - def get_plugin_config_schema(self, plugin_type): + def get_plugin_config_schema(self, plugin_type: str): plugin = self._get_plugin_type(plugin_type) return plugin.config_schema - def get_failed_plugins(self, plugin_type): + def get_failed_plugins(self, plugin_type: str) -> list[tuple[str, str]]: """Return a list of plugins for the given type that failed to load. Returns: @@ -383,14 +401,14 @@ def get_failed_plugins(self, plugin_type): name (str): Name of the plugin. reason (str): Error message. """ - return self._get_plugin_type(plugin_type).failed_plugins.items() + return list(self._get_plugin_type(plugin_type).failed_plugins.items()) - def create_instance(self, plugin_type, plugin_name, **instance_kwargs): + def create_instance(self, plugin_type: str, plugin_name, **instance_kwargs: Any) -> Any: """Create and return an instance of the given plugin.""" plugin_type = self._get_plugin_type(plugin_type) return plugin_type.create_instance(plugin_name, **instance_kwargs) - def get_summary_string(self): + def get_summary_string(self) -> str: """Get a formatted string summarising the plugins that were loaded.""" rows = [["PLUGIN TYPE", "NAME", "DESCRIPTION", "STATUS"], ["-----------", "----", "-----------", "------"]] diff --git a/src/rez/release_hook.py b/src/rez/release_hook.py index 61c3a86d6..e9e788135 100644 --- a/src/rez/release_hook.py +++ b/src/rez/release_hook.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.utils.logging_ import print_warning, print_debug from rez.packages import get_developer_package from enum import Enum @@ -46,7 +48,7 @@ def name(cls): """ Return name of source retriever, eg 'git'""" raise NotImplementedError - def __init__(self, source_path): + def __init__(self, source_path) -> None: """Create a release hook. Args: @@ -59,7 +61,7 @@ def __init__(self, source_path): def pre_build(self, user, install_path, variants=None, release_message=None, changelog=None, previous_version=None, - previous_revision=None, **kwargs): + previous_revision=None, **kwargs) -> None: """Pre-build hook. Args: @@ -83,7 +85,7 @@ def pre_build(self, user, install_path, variants=None, release_message=None, def pre_release(self, user, install_path, variants=None, release_message=None, changelog=None, previous_version=None, - previous_revision=None, **kwargs): + previous_revision=None, **kwargs) -> None: """Pre-release hook. This is called before any package variants are released. @@ -109,7 +111,7 @@ def pre_release(self, user, install_path, variants=None, def post_release(self, user, install_path, variants, release_message=None, changelog=None, previous_version=None, - previous_revision=None, **kwargs): + previous_revision=None, **kwargs) -> None: """Post-release hook. This is called after all package variants have been released. @@ -135,7 +137,7 @@ class ReleaseHookEvent(Enum): pre_release = ("pre-release", "release", "pre_release") post_release = ("post-release", "release", "post_release") - def __init__(self, label, noun, func_name): + def __init__(self, label, noun, func_name) -> None: self.label = label self.noun = noun self.__name__ = func_name diff --git a/src/rez/release_vcs.py b/src/rez/release_vcs.py index dc232c402..c9d861131 100644 --- a/src/rez/release_vcs.py +++ b/src/rez/release_vcs.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.exceptions import ReleaseVCSError from rez.packages import get_developer_package from rez.util import which @@ -12,25 +14,25 @@ import subprocess -def get_release_vcs_types(): +def get_release_vcs_types() -> list[str]: """Returns the available VCS implementations - git, hg etc.""" from rez.plugin_managers import plugin_manager return plugin_manager.get_plugins('release_vcs') -def create_release_vcs(path, vcs_name=None): +def create_release_vcs(path: str, vcs_name: str | None = None) -> ReleaseVCS: """Return a new release VCS that can release from this source path.""" from rez.plugin_managers import plugin_manager vcs_types = get_release_vcs_types() if vcs_name: if vcs_name not in vcs_types: raise ReleaseVCSError("Unknown version control system: %r" % vcs_name) - cls = plugin_manager.get_plugin_class('release_vcs', vcs_name) + cls = plugin_manager.get_plugin_class('release_vcs', vcs_name, expected_type=ReleaseVCS) return cls(path) classes_by_level = {} for vcs_name in vcs_types: - cls = plugin_manager.get_plugin_class('release_vcs', vcs_name) + cls = plugin_manager.get_plugin_class('release_vcs', vcs_name, expected_type=ReleaseVCS) result = cls.find_vcs_root(path) if not result: continue @@ -70,7 +72,7 @@ def create_release_vcs(path, vcs_name=None): class ReleaseVCS(object): """A version control system (VCS) used to release Rez packages. """ - def __init__(self, pkg_root, vcs_root=None): + def __init__(self, pkg_root: str, vcs_root: str | None = None) -> None: if vcs_root is None: result = self.find_vcs_root(pkg_root) if not result: @@ -87,12 +89,12 @@ def __init__(self, pkg_root, vcs_root=None): self.settings = self.type_settings.get(self.name()) @classmethod - def name(cls): + def name(cls) -> str: """Return the name of the VCS type, eg 'git'.""" raise NotImplementedError @classmethod - def find_executable(cls, name): + def find_executable(cls, name: str) -> str: exe = which(name) if not exe: raise ReleaseVCSError("Couldn't find executable '%s' for VCS '%s'" @@ -100,7 +102,7 @@ def find_executable(cls, name): return exe @classmethod - def is_valid_root(cls, path): + def is_valid_root(cls, path: str) -> bool: """Return True if the given path is a valid root directory for this version control system. @@ -111,14 +113,14 @@ def is_valid_root(cls, path): raise NotImplementedError @classmethod - def search_parents_for_root(cls): + def search_parents_for_root(cls) -> bool: """Return True if this vcs type should check parent directories to find the root directory """ raise NotImplementedError @classmethod - def find_vcs_root(cls, path): + def find_vcs_root(cls, path: str) -> tuple[str, int] | None: """Try to find a version control root directory of this type for the given path. @@ -137,11 +139,11 @@ def find_vcs_root(cls, path): return current_path, i return None - def validate_repostate(self): + def validate_repostate(self) -> None: """Ensure that the VCS working copy is up-to-date.""" raise NotImplementedError - def get_current_revision(self): + def get_current_revision(self) -> object: """Get the current revision, this can be any type (str, dict etc) appropriate to your VCS implementation. @@ -152,7 +154,7 @@ def get_current_revision(self): """ raise NotImplementedError - def get_changelog(self, previous_revision=None, max_revisions=None): + def get_changelog(self, previous_revision=None, max_revisions=None) -> str: """Get the changelog text since the given revision. If previous_revision is not an ancestor (for example, the last release @@ -169,7 +171,7 @@ def get_changelog(self, previous_revision=None, max_revisions=None): """ raise NotImplementedError - def tag_exists(self, tag_name): + def tag_exists(self, tag_name: str) -> bool: """Test if a tag exists in the repo. Args: @@ -180,7 +182,7 @@ def tag_exists(self, tag_name): """ raise NotImplementedError - def create_release_tag(self, tag_name, message=None): + def create_release_tag(self, tag_name: str, message: str | None = None) -> None: """Create a tag in the repo. Create a tag in the repository representing the release of the @@ -193,7 +195,7 @@ def create_release_tag(self, tag_name, message=None): raise NotImplementedError @classmethod - def export(cls, revision, path): + def export(cls, revision: object, path: str) -> None: """Export the repository to the given path at the given revision. Note: diff --git a/src/rez/resolved_context.py b/src/rez/resolved_context.py index 8ad89fe22..e4861e696 100644 --- a/src/rez/resolved_context.py +++ b/src/rez/resolved_context.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez import __version__, module_root_path from rez.package_repository import package_repository_manager from rez.solver import SolverCallbackReturn @@ -19,13 +21,13 @@ from rez.utils.memcached import pool_memcached_connections from rez.utils.logging_ import print_error, print_warning from rez.utils.which import which -from rez.rex import RexExecutor, Python, OutputStyle, literal +from rez.rex import Action, ActionInterpreter, RexExecutor, Python, OutputStyle, literal from rez.rex_bindings import VersionBinding, VariantBinding, \ VariantsBinding, RequirementsBinding, EphemeralsBinding, intersects from rez import package_order -from rez.packages import get_variant, iter_packages +from rez.packages import get_variant, iter_packages, Package, Variant from rez.package_filter import PackageFilterList -from rez.package_order import PackageOrderList +from rez.package_order import PackageOrder, PackageOrderList from rez.package_cache import PackageCache from rez.shells import create_shell from rez.exceptions import ResolvedContextError, PackageCommandError, \ @@ -33,7 +35,7 @@ from rez.utils.graph_utils import write_dot, write_compacted, \ read_graph_from_string from rez.utils.resolve_graph import failure_detail_from_graph -from rez.version import VersionRange +from rez.version import Version, VersionRange from rez.version import Requirement from rez.vendor import yaml from rez.utils.yaml import dump_yaml @@ -42,6 +44,8 @@ from contextlib import contextmanager from functools import wraps from enum import Enum +from typing import cast, Any, Callable, Iterable, Iterator, Mapping, NoReturn, Sequence, TypeVar, \ + TYPE_CHECKING, overload import getpass import json import socket @@ -51,6 +55,17 @@ import os import os.path +if TYPE_CHECKING: + from typing import Literal # not available in typing module until 3.8 + from rez.utils.typing import SupportsWrite, SupportsRead + from rez.solver import SolverState + from rez.package_resources import VariantResource + from rez.vendor.pygraph.classes.digraph import digraph + from subprocess import Popen + +T = TypeVar("T") +CallableT = TypeVar("CallableT", bound=Callable) + class RezToolsVisibility(Enum): """Determines if/how rez cli tools are added back to PATH within a @@ -89,12 +104,14 @@ class PatchLock(Enum): __order__ = "no_lock,lock_2,lock_3,lock_4,lock" - def __init__(self, description, rank): + def __init__(self, description: str, rank: int) -> None: self.description = description self.rank = rank -def get_lock_request(name, version, patch_lock, weak=True): +def get_lock_request(name: str, + version: Version, patch_lock: PatchLock, + weak: bool = True) -> PackageRequest | None: """Given a package and patch lock, return the equivalent request. For example, for object 'foo-1.2.1' and lock type 'lock_3', the equivalent @@ -123,6 +140,17 @@ def get_lock_request(name, version, patch_lock, weak=True): return PackageRequest(s) +def _on_success(fn: CallableT) -> CallableT: + @wraps(fn) + def _check(self: ResolvedContext, *nargs: Any, **kwargs: Any) -> Any: + if self.status_ == ResolverStatus.solved: + return fn(self, *nargs, **kwargs) + else: + raise ResolvedContextError( + "Cannot perform operation in a failed context") + return _check # type: ignore[return-value] + + class ResolvedContext(object): """A class that resolves, stores and spawns Rez environments. @@ -136,20 +164,22 @@ class ResolvedContext(object): """ serialize_version = (4, 9) tmpdir_manager = TempDirs(config.context_tmpdir, prefix="rez_context_") - context_tracking_payload = None + context_tracking_payload: dict[str, Any] | None = None context_tracking_lock = threading.Lock() package_cache_present = True local = threading.local() class Callback(object): - def __init__(self, max_fails, time_limit, callback, buf=None): + def __init__(self, max_fails: int, time_limit: int, + callback: Callable[[SolverState], tuple[SolverCallbackReturn, str]] | None, + buf: SupportsWrite | None = None) -> None: self.max_fails = max_fails self.time_limit = time_limit self.callback = callback self.start_time = time.time() self.buf = buf or sys.stdout - def __call__(self, state): + def __call__(self, state: SolverState) -> tuple[SolverCallbackReturn, str]: if self.max_fails != -1 and state.num_fails >= self.max_fails: reason = ("fail limit reached: aborted after %d failures" % state.num_fails) @@ -162,16 +192,30 @@ def __call__(self, state): return self.callback(state) return SolverCallbackReturn.keep_going, '' - def __init__(self, package_requests, verbosity=0, timestamp=None, - building=False, testing=False, caching=None, package_paths=None, - package_filter=None, package_orderers=None, max_fails=-1, - add_implicit_packages=True, time_limit=-1, callback=None, - package_load_callback=None, buf=None, suppress_passive=False, - print_stats=False, package_caching=None, package_cache_async=None): + def __init__(self, + package_requests: Iterable[str | Requirement], + verbosity: int = 0, + timestamp: float | None = None, + building: bool = False, + testing: bool = False, + caching: bool | None = None, + package_paths: list[str] | None = None, + package_filter: PackageFilterList | None = None, + package_orderers: list[PackageOrder] | None = None, + max_fails: int = -1, + add_implicit_packages: bool = True, + time_limit: int = -1, + callback: Callable[[SolverState], tuple[SolverCallbackReturn, str]] | None = None, + package_load_callback: Callable[[Package], Any] | None = None, + buf: SupportsWrite | None = None, + suppress_passive: bool = False, + print_stats: bool = False, + package_caching: bool | None = None, + package_cache_async: bool | None = None) -> None: """Perform a package resolve, and store the result. Args: - package_requests (list[typing.Union[str, PackageRequest]]): request + package_requests (list[typing.Union[str, Requirement]]): request verbosity (int): Verbosity level. One of [0,1,2]. timestamp (float): Ignore packages released after this epoch time. Packages released at exactly this time will not be ignored. @@ -209,20 +253,22 @@ def __init__(self, package_requests, verbosity=0, timestamp=None, package_cache_async (bool|None): If True, cache packages asynchronously. If None, use the config setting :data:`package_cache_async` """ - self.load_path = None + self.load_path: str | None = None # resolving settings self.requested_timestamp = timestamp self.timestamp = self.requested_timestamp or int(time.time()) self.building = building self.testing = testing - self.implicit_packages = [] + self.implicit_packages: list[Requirement] = [] self.caching = config.resolve_caching if caching is None else caching self.verbosity = verbosity - self._package_requests = [] + self._package_requests: list[Requirement] = [] for req in package_requests: if isinstance(req, str): + # FIXME: Requirement seems like it would work fine here. the only difference + # appears to be that PackageRequest does some additional validation req = PackageRequest(req) self._package_requests.append(req) @@ -237,7 +283,7 @@ def __init__(self, package_requests, verbosity=0, timestamp=None, self.package_filter = (PackageFilterList.singleton if package_filter is None else package_filter) - self.package_orderers = PackageOrderList( + self.package_orderers: PackageOrderList | None = PackageOrderList( PackageOrderList.singleton if package_orderers is None else package_orderers ) @@ -272,12 +318,12 @@ def __init__(self, package_requests, verbosity=0, timestamp=None, # resolve results self.status_ = ResolverStatus.pending - self._resolved_packages = None + self._resolved_packages: list[Variant] | None = None self._resolved_ephemerals = None - self.failure_description = None - self.graph_string = None - self.graph_ = None - self.from_cache = None + self.failure_description: str | None = None + self.graph_string: str | None = None + self.graph_: digraph | None = None + self.from_cache: bool | None = None # stats self.solve_time = 0.0 # total solve time, inclusive of load time @@ -286,11 +332,11 @@ def __init__(self, package_requests, verbosity=0, timestamp=None, # the pre-resolve bindings. We store these because @late package.py # functions need them, and we cache them to avoid cost - self.pre_resolve_bindings = None + self.pre_resolve_bindings: dict[str, Any] | None = None # suite information - self.parent_suite_path = None - self.suite_context_name = None + self.parent_suite_path: str | None = None + self.suite_context_name: str | None = None # perform the solve callback_ = self.Callback(buf=buf, @@ -298,7 +344,7 @@ def __init__(self, package_requests, verbosity=0, timestamp=None, time_limit=time_limit, callback=callback) - def _package_load_callback(package): + def _package_load_callback(package: Package) -> None: if package_load_callback: package_load_callback(package) self.num_loaded_packages += 1 @@ -346,7 +392,7 @@ def _package_load_callback(package): # update package cache self._update_package_cache() - def __str__(self): + def __str__(self) -> str: request = self.requested_packages(include_implicit=True) req_str = " ".join(str(x) for x in request) if self.status == ResolverStatus.solved: @@ -357,12 +403,12 @@ def __str__(self): self.status.name, req_str) @property - def success(self): + def success(self) -> bool: """True if the context has been solved, False otherwise.""" return (self.status_ == ResolverStatus.solved) @property - def status(self): + def status(self) -> ResolverStatus: """Return the current status of the context. Returns: @@ -370,7 +416,7 @@ def status(self): """ return self.status_ - def requested_packages(self, include_implicit=False): + def requested_packages(self, include_implicit: bool = False) -> list[Requirement]: """Get packages in the request. Args: @@ -378,7 +424,7 @@ def requested_packages(self, include_implicit=False): to the result. Returns: - list[PackageRequest]: + list[Requirement]: """ if include_implicit: return self._package_requests + self.implicit_packages @@ -386,7 +432,7 @@ def requested_packages(self, include_implicit=False): return self._package_requests @property - def resolved_packages(self): + def resolved_packages(self) -> list[Variant] | None: """Get packages in the resolve. Returns: @@ -395,7 +441,7 @@ def resolved_packages(self): return self._resolved_packages @property - def resolved_ephemerals(self): + def resolved_ephemerals(self) -> list[Requirement] | None: """Get non-conflict ephemerals in the resolve. Returns: @@ -403,7 +449,7 @@ def resolved_ephemerals(self): """ return self._resolved_ephemerals - def set_load_path(self, path): + def set_load_path(self, path: str) -> None: """Set the path that this context was reportedly loaded from. You may want to use this method in cases where a context is saved to @@ -425,8 +471,8 @@ def __eq__(self, other): and other.resolved_packages == self.resolved_packages ) - def __hash__(self): - list_ = [] + def __hash__(self) -> int: + list_: list[Any] = [] req = self.requested_packages(True) list_.append(tuple(req)) res = self.resolved_packages @@ -439,23 +485,24 @@ def __hash__(self): return hash(value) @property - def has_graph(self): + def has_graph(self) -> bool: """Return True if the resolve has a graph.""" return bool((self.graph_ is not None) or self.graph_string) - def get_resolved_package(self, name): + def get_resolved_package(self, name: str) -> Variant | None: """Returns a `Variant` object or None if the package is not in the resolve. """ pkgs = [x for x in (self._resolved_packages or []) if x.name == name] return pkgs[0] if pkgs else None - def copy(self): + def copy(self) -> ResolvedContext: """Returns a shallow copy of the context.""" import copy return copy.copy(self) - def retargeted(self, package_paths, package_names=None, skip_missing=False): + def retargeted(self, package_paths: list[str], package_names: list[str] | None = None, + skip_missing: bool = False) -> ResolvedContext: """Create a retargeted copy of this context. Retargeting a context means replacing its variant references with @@ -472,7 +519,7 @@ def retargeted(self, package_paths, package_names=None, skip_missing=False): Returns: ResolvedContext: The retargeted context. """ - retargeted_variants = [] + retargeted_variants: list[Variant | VariantResource] = [] pkg_repos = [ package_repository_manager.get_repository(x) @@ -518,8 +565,10 @@ def retargeted(self, package_paths, package_names=None, skip_missing=False): return self.from_dict(d) # TODO: deprecate in favor of patch() method - def get_patched_request(self, package_requests=None, - package_subtractions=None, strict=False, rank=0): + def get_patched_request(self, package_requests: list[PackageRequest] | None = None, + package_subtractions: list[str] | None = None, + strict: bool = False, rank: int = 0 + ) -> list[Requirement | PackageRequest | str]: """Get a 'patched' request. A patched request is a copy of this context's request, but with some @@ -558,10 +607,9 @@ def get_patched_request(self, package_requests=None, """ # assemble source request if strict: - request = [] + request: list[Requirement | PackageRequest] = [] for variant in self.resolved_packages: req = PackageRequest(variant.qualified_package_name) - request.append(req) else: request = self.requested_packages()[:] @@ -618,7 +666,15 @@ def get_patched_request(self, package_requests=None, return request - def graph(self, as_dot=False): + @overload + def graph(self, as_dot: Literal[True]) -> str | None: + pass + + @overload + def graph(self, as_dot: Literal[False] = False) -> digraph | None: + pass + + def graph(self, as_dot: bool = False) -> str | digraph | None: """Get the resolve graph. Args: @@ -649,13 +705,13 @@ def graph(self, as_dot=False): return write_dot(self.graph_) - def save(self, path): + def save(self, path: str) -> None: """Save the resolved context to file.""" with self._detect_bundle(path): with open(path, 'w') as f: self.write_to_buffer(f) - def write_to_buffer(self, buf): + def write_to_buffer(self, buf: SupportsWrite) -> None: """Save the context to a buffer.""" doc = self.to_dict() @@ -664,7 +720,7 @@ def write_to_buffer(self, buf): buf.write(content) @classmethod - def get_current(cls): + def get_current(cls) -> ResolvedContext | None: """Get the context for the current env, if there is one. Returns: @@ -676,7 +732,7 @@ def get_current(cls): return cls.load(filepath) - def is_current(self): + def is_current(self) -> bool | None: """ Returns: bool: True if this is the currently sourced context, False otherwise. @@ -691,7 +747,7 @@ def is_current(self): return (self.load_path == filepath) @classmethod - def load(cls, path): + def load(cls, path: str) -> ResolvedContext: """Load a resolved context from file.""" with cls._detect_bundle(path): with open(path) as f: @@ -701,14 +757,14 @@ def load(cls, path): return context @classmethod - def read_from_buffer(cls, buf, identifier_str=None): + def read_from_buffer(cls, buf: SupportsRead, identifier_str: str | None = None) -> ResolvedContext: """Load the context from a buffer.""" try: return cls._read_from_buffer(buf, identifier_str) except Exception as e: cls._load_error(e, identifier_str) - def get_resolve_diff(self, other): + def get_resolve_diff(self, other: ResolvedContext) -> dict: """Get the difference between the resolve in this context and another. The difference is described from the point of view of the current context @@ -747,7 +803,8 @@ def get_resolve_diff(self, other): raise ResolvedContextError("Cannot diff resolves, package search " "paths differ:\n%s" % '\n'.join(diff)) - d = {} + # FIXME: make this a TypedDict + d: dict[str, Any] = {} self_pkgs_ = set(x.parent for x in self._resolved_packages) other_pkgs_ = set(x.parent for x in other._resolved_packages) self_pkgs = self_pkgs_ - other_pkgs_ @@ -796,8 +853,8 @@ def get_resolve_diff(self, other): return d @pool_memcached_connections - def print_info(self, buf=sys.stdout, verbosity=0, source_order=False, - show_resolved_uris=False): + def print_info(self, buf: SupportsWrite = sys.stdout, verbosity: int=0, + source_order: bool = False, show_resolved_uris: bool = False) -> None: """Prints a message summarising the contents of the resolved context. Args: @@ -811,7 +868,7 @@ def print_info(self, buf=sys.stdout, verbosity=0, source_order=False, """ _pr = Printer(buf) - def _rt(t): + def _rt(t: float) -> str: if verbosity: s = time.strftime("%a %b %d %H:%M:%S %Z %Y", time.localtime(t)) return s + " (%d)" % int(t) @@ -833,7 +890,7 @@ def _rt(t): if verbosity: _pr("search paths:", heading) - rows = [] + rows: list[tuple[str, str]] = [] colors = [] for path in self.package_paths: if package_repository_manager.are_same(path, config.local_packages_path): @@ -891,7 +948,7 @@ def _rt(t): return _pr("resolved packages:", heading) - rows = [] + rows3: list[tuple[str, str, str]] = [] colors = [] resolved_packages = self.resolved_packages or [] @@ -906,7 +963,7 @@ def _rt(t): location = None # check for retargeted variant root (ie package caching) - pkg_root = pkg.root + pkg_root: str = pkg.root if is_current: uname = pkg.name.upper().replace('.', '_') @@ -931,18 +988,18 @@ def _rt(t): t.append('local') col = local - t = '(%s)' % ', '.join(t) if t else '' - rows.append((pkg.qualified_package_name, location, t)) + t_str = '(%s)' % ', '.join(t) if t else '' + rows3.append((pkg.qualified_package_name, location, t_str)) colors.append(col) # add ephemerals to end of resolved packages list ephemerals = self.resolved_ephemerals or [] ephemerals = sorted(ephemerals, key=lambda x: x.name) for req in ephemerals: - rows.append((str(req), '', "(ephemeral)")) + rows3.append((str(req), '', "(ephemeral)")) colors.append(ephemeral_color) - for col, line in zip(colors, columnise(rows)): + for col, line in zip(colors, columnise(rows3)): _pr(line, col) if verbosity: @@ -961,7 +1018,7 @@ def _rt(t): _pr("tools:", heading) self.print_tools(buf=buf) - def print_tools(self, buf=sys.stdout): + def print_tools(self, buf: SupportsWrite = sys.stdout) -> None: data = self.get_tools() if not data: return @@ -970,7 +1027,7 @@ def print_tools(self, buf=sys.stdout): conflicts = set(self.get_conflicting_tools().keys()) rows = [["TOOL", "PACKAGE", ""], ["----", "-------", ""]] - colors = [None, None] + colors: list[Callable[[str], str] | None] = [None, None] for _, (variant, tools) in sorted(data.items()): pkg_str = variant.qualified_package_name @@ -983,10 +1040,11 @@ def print_tools(self, buf=sys.stdout): rows.append(row) colors.append(col) - for col, line in zip(colors, columnise(rows)): - _pr(line, col) + for colorizer, line in zip(colors, columnise(rows)): + _pr(line, colorizer) - def print_resolve_diff(self, other, heading=None): + def print_resolve_diff(self, other: ResolvedContext, + heading: Literal[True] | None | tuple[str, str] = None) -> None: """Print the difference between the resolve of two contexts. Args: @@ -1009,7 +1067,7 @@ def print_resolve_diff(self, other, heading=None): b = os.path.basename(other.load_path) heading = (a, b) if isinstance(heading, tuple): - rows.append(list(heading) + [""]) + rows.append(heading + ("",)) rows.append(('-' * len(heading[0]), '-' * len(heading[1]), "")) newer_packages = d.get("newer_packages", {}) @@ -1045,18 +1103,16 @@ def print_resolve_diff(self, other, heading=None): print('\n'.join(columnise(rows))) - def _on_success(fn): - @wraps(fn) - def _check(self, *nargs, **kwargs): - if self.status_ == ResolverStatus.solved: - return fn(self, *nargs, **kwargs) - else: - raise ResolvedContextError( - "Cannot perform operation in a failed context") - return _check + @overload + def get_dependency_graph(self, as_dot: Literal[False]) -> digraph: + pass + + @overload + def get_dependency_graph(self, as_dot: Literal[True]) -> str: + pass @_on_success - def get_dependency_graph(self, as_dot=False): + def get_dependency_graph(self, as_dot: bool = False) -> digraph | str: """Generate the dependency graph. The dependency graph is a simpler subset of the resolve graph. It @@ -1102,7 +1158,7 @@ def get_dependency_graph(self, as_dot=False): return g @_on_success - def validate(self): + def validate(self) -> None: """Validate the context.""" try: for pkg in self.resolved_packages: @@ -1111,7 +1167,7 @@ def validate(self): raise ResolvedContextError("%s: %s" % (e.__class__.__name__, str(e))) @_on_success - def get_environ(self, parent_environ=None): + def get_environ(self, parent_environ: Mapping[str, str] | None = None) -> dict[str, str]: """Get the environ dict resulting from interpreting this context. Args: @@ -1128,7 +1184,7 @@ def get_environ(self, parent_environ=None): return executor.get_output() @_on_success - def get_key(self, key, request_only=False): + def get_key(self, key: str, request_only: bool = False) -> dict[str, tuple[Variant, Any]]: """Get a data key value for each resolved package. Args: @@ -1152,7 +1208,7 @@ def get_key(self, key, request_only=False): return values @_on_success - def get_tools(self, request_only=False): + def get_tools(self, request_only: bool = False) -> dict[str, tuple[Variant, list[str]]]: """Returns the commandline tools available in the context. Args: @@ -1165,7 +1221,7 @@ def get_tools(self, request_only=False): return self.get_key("tools", request_only=request_only) @_on_success - def get_tool_variants(self, tool_name): + def get_tool_variants(self, tool_name: str) -> set[Variant]: """Get the variant(s) that provide the named tool. If there are more than one variants, the tool is in conflict, and Rez @@ -1186,7 +1242,7 @@ def get_tool_variants(self, tool_name): return variants @_on_success - def get_conflicting_tools(self, request_only=False): + def get_conflicting_tools(self, request_only: bool = False) -> dict[str, set[Variant]]: """Returns tools of the same name provided by more than one package. Args: @@ -1208,7 +1264,9 @@ def get_conflicting_tools(self, request_only=False): return conflicts @_on_success - def get_shell_code(self, shell=None, parent_environ=None, style=OutputStyle.file): + def get_shell_code(self, shell: str | None = None, + parent_environ: Mapping[str, str] | None = None, + style: OutputStyle = OutputStyle.file) -> str: """Get the shell code resulting from intepreting this context. Args: @@ -1228,7 +1286,7 @@ def get_shell_code(self, shell=None, parent_environ=None, style=OutputStyle.file return executor.get_output(style) @_on_success - def get_actions(self, parent_environ=None): + def get_actions(self, parent_environ: Mapping[str, str] | None = None) -> list[Action]: """Get the list of rex.Action objects resulting from interpreting this context. This is provided mainly for testing purposes. @@ -1245,7 +1303,7 @@ def get_actions(self, parent_environ=None): return executor.actions @_on_success - def apply(self, parent_environ=None): + def apply(self, parent_environ: Mapping[str, str] | None = None) -> None: """Apply the context to the current python session. Note that this updates os.environ and possibly sys.path, if @@ -1261,7 +1319,8 @@ def apply(self, parent_environ=None): interpreter.apply_environ() @_on_success - def which(self, cmd, parent_environ=None, fallback=False): + def which(self, cmd: str, parent_environ: Mapping[str, str] | None = None, + fallback: bool = False) -> str | None: """Find a program in the resolved environment. Args: @@ -1281,7 +1340,9 @@ def which(self, cmd, parent_environ=None, fallback=False): return path @_on_success - def execute_command(self, args, parent_environ=None, **Popen_args): + def execute_command(self, args: str | Iterable[str], + parent_environ: dict[str, str] | None = None, + **Popen_args: Any) -> Popen: """Run a command within a resolved context. This applies the context to a python environ dict, then runs a @@ -1306,7 +1367,7 @@ def execute_command(self, args, parent_environ=None, **Popen_args): Note: This does not alter the current python session. """ - if parent_environ in (None, os.environ): + if parent_environ is None or parent_environ is os.environ: target_environ = {} else: target_environ = parent_environ.copy() @@ -1318,8 +1379,11 @@ def execute_command(self, args, parent_environ=None, **Popen_args): return interpreter.subprocess(args, **Popen_args) @_on_success - def execute_rex_code(self, code, filename=None, shell=None, - parent_environ=None, **Popen_args): + def execute_rex_code(self, code: str, + filename: str | None = None, + shell: str | None = None, + parent_environ: Mapping[str, str] | None = None, + **Popen_args: Any) -> Popen: """Run some rex code in the context. Note: @@ -1337,7 +1401,7 @@ def execute_rex_code(self, code, filename=None, shell=None, Returns: subprocess.Popen: Subprocess object for the shell process. """ - def _actions_callback(executor): + def _actions_callback(executor: RexExecutor) -> None: executor.execute_code(code, filename=filename) return self.execute_shell(shell=shell, @@ -1348,11 +1412,21 @@ def _actions_callback(executor): **Popen_args) @_on_success - def execute_shell(self, shell=None, parent_environ=None, rcfile=None, - norc=False, stdin=False, command=None, quiet=False, - block=None, actions_callback=None, post_actions_callback=None, - context_filepath=None, start_new_session=False, detached=False, - pre_command=None, **Popen_args): + def execute_shell(self, + shell: str | None = None, + parent_environ: Mapping[str, str] | None = None, + rcfile: str | None = None, + norc: bool = False, + stdin: bool = False, + command: str | Sequence[str] | None = None, + quiet: bool = False, + block: bool | None = None, + actions_callback: Callable[[RexExecutor], Any] | None = None, + post_actions_callback: Callable[[RexExecutor], Any] | None = None, + context_filepath: str | None = None, + start_new_session: bool = False, detached: bool = False, + pre_command: str | list[str] | None = None, + **Popen_args: Any) -> Popen: """Spawn a possibly-interactive shell. Args: @@ -1473,12 +1547,13 @@ def execute_shell(self, shell=None, parent_environ=None, rcfile=None, **Popen_args) if block: stdout, stderr = p.communicate() - return p.returncode, stdout, stderr + # FIXME: make overload for this + return p.returncode, stdout, stderr # type: ignore[return-value] else: return p @_on_success - def get_resolve_as_exact_requests(self): + def get_resolve_as_exact_requests(self) -> list[PackageRequest]: """Convert to a package request list of exact resolved package versions. >>> r = ResolvedContext(['foo'] @@ -1489,12 +1564,12 @@ def get_resolve_as_exact_requests(self): List of `PackageRequest`: Context as a list of exact version requests. """ - def to_req(variant): + def to_req(variant: Variant) -> PackageRequest: return PackageRequest(variant.parent.as_exact_requirement()) - return map(to_req, self.resolved_packages) + return [to_req(r) for r in self.resolved_packages] - def to_dict(self, fields=None): + def to_dict(self, fields: list[str] | None = None) -> dict: """Convert context to dict containing only builtin types. Args: @@ -1505,9 +1580,9 @@ def to_dict(self, fields=None): Returns: dict: Dictified context. """ - data = {} + data: dict[str, Any] = {} - def _add(field): + def _add(field: str) -> bool: return (fields is None or field in fields) if _add("resolved_packages"): @@ -1594,7 +1669,7 @@ def _add(field): return data @classmethod - def from_dict(cls, d, identifier_str=None): + def from_dict(cls, d: dict, identifier_str: str | None = None) -> ResolvedContext: """Load a `ResolvedContext` from a dict. Args: @@ -1607,7 +1682,7 @@ def from_dict(cls, d, identifier_str=None): `ResolvedContext` object. """ # check serialization version - def _print_version(value): + def _print_version(value: Iterable[int]) -> str: return '.'.join(str(x) for x in value) toks = str(d["serialize_version"]).split('.') @@ -1741,7 +1816,7 @@ def _print_version(value): return r - def _execute_bundle_post_actions_callback(self, executor): + def _execute_bundle_post_actions_callback(self, executor: RexExecutor) -> None: """ In bundles, you can drop a 'post_commands.py' file (rex) alongside the 'bundle.yaml' file, and it will be sourced after all package commands. @@ -1768,7 +1843,7 @@ def _execute_bundle_post_actions_callback(self, executor): @classmethod @contextmanager - def _detect_bundle(cls, path): + def _detect_bundle(cls, path: str) -> Iterator[None]: bundle_path = None base_dir = os.path.dirname(os.path.abspath(path)) bundle_filepath = os.path.join(base_dir, "bundle.yaml") @@ -1791,11 +1866,11 @@ def _detect_bundle(cls, path): pass @classmethod - def _get_bundle_path(cls): + def _get_bundle_path(cls) -> str | None: return getattr(cls.local, "bundle_path", None) @classmethod - def _adjust_variant_for_bundling(cls, handle, out): + def _adjust_variant_for_bundling(cls, handle: dict, out: bool) -> None: """ Deals with making variant pkg repo ref relative/nonrelative to take bundling into account. @@ -1835,7 +1910,7 @@ def _adjust_variant_for_bundling(cls, handle, out): vars_["location"] = location @classmethod - def _get_package_cache(cls): + def _get_package_cache(cls) -> PackageCache | None: if not cls.package_cache_present: return None @@ -1847,8 +1922,9 @@ def _get_package_cache(cls): config.cache_packages_path ) cls.package_cache_present = False + return None - def _update_package_cache(self): + def _update_package_cache(self) -> None: if not self.package_caching or \ not config.cache_packages_path or \ not config.write_package_cache or \ @@ -1861,13 +1937,14 @@ def _update_package_cache(self): pkgcache = self._get_package_cache() if pkgcache: + assert self.resolved_packages is not None pkgcache.add_variants( self.resolved_packages, self.package_cache_async, ) @classmethod - def _init_context_tracking_payload_base(cls): + def _init_context_tracking_payload_base(cls) -> None: if cls.context_tracking_payload is not None: return @@ -1884,8 +1961,8 @@ def _init_context_tracking_payload_base(cls): data.update(config.context_tracking_extra_fields or {}) # remove fields with unexpanded env-vars, or empty string - def _del(value): - return ( + def _del(value: object) -> bool: + return bool( isinstance(value, str) and (not value or ENV_VAR_REGEX.search(value)) ) @@ -1896,7 +1973,7 @@ def _del(value): if cls.context_tracking_payload is None: cls.context_tracking_payload = data - def _track_context(self, context_data, action): + def _track_context(self, context_data, action: str) -> None: # create message payload data = { "action": action, @@ -1929,7 +2006,7 @@ def _track_context(self, context_data, action): ) @classmethod - def _read_from_buffer(cls, buf, identifier_str=None): + def _read_from_buffer(cls, buf: SupportsRead, identifier_str: str | None = None) -> ResolvedContext: content = buf.read() if content.startswith('{'): # assume json content @@ -1941,18 +2018,19 @@ def _read_from_buffer(cls, buf, identifier_str=None): return context @classmethod - def _load_error(cls, e, path=None): + def _load_error(cls, e: Exception, path: str | None = None) -> NoReturn: exc_name = e.__class__.__name__ msg = "Failed to load context" if path: msg += " from %s" % path raise ResolvedContextError("%s: %s: %s" % (msg, exc_name, str(e))) - def _set_parent_suite(self, suite_path, context_name): + def _set_parent_suite(self, suite_path: str, context_name: str) -> None: self.parent_suite_path = suite_path self.suite_context_name = context_name - def _create_executor(self, interpreter, parent_environ): + def _create_executor(self, interpreter: ActionInterpreter, + parent_environ: Mapping[str, str] | None) -> RexExecutor: parent_vars = True if config.all_parent_variables \ else config.parent_variables @@ -1960,7 +2038,7 @@ def _create_executor(self, interpreter, parent_environ): parent_environ=parent_environ, parent_variables=parent_vars) - def _get_pre_resolve_bindings(self): + def _get_pre_resolve_bindings(self) -> dict: if self.pre_resolve_bindings is None: self.pre_resolve_bindings = { "system": system, @@ -1974,10 +2052,10 @@ def _get_pre_resolve_bindings(self): return self.pre_resolve_bindings @pool_memcached_connections - def _execute(self, executor): + def _execute(self, executor: RexExecutor) -> None: """Bind various info to the execution context """ - def normalized(path): + def normalized(path: str) -> str: return executor.normalize_path(path) resolved_pkgs = self.resolved_packages or [] @@ -2160,7 +2238,7 @@ def normalized(path): elif mode == RezToolsVisibility.prepend: executor.prepend_rez_path() - def _append_suite_paths(self, executor): + def _append_suite_paths(self, executor: RexExecutor) -> None: from rez.suite import Suite mode = SuiteVisibility[config.suite_visibility] diff --git a/src/rez/resolver.py b/src/rez/resolver.py index 91580b4bb..ed68374a3 100644 --- a/src/rez/resolver.py +++ b/src/rez/resolver.py @@ -2,17 +2,39 @@ # Copyright Contributors to the Rez Project -from rez.solver import Solver, SolverStatus +from __future__ import annotations + +from rez.solver import Solver, SolverCallbackReturn, SolverState, SolverStatus from rez.package_repository import package_repository_manager -from rez.packages import get_variant, get_last_release_time +from rez.packages import get_variant, get_last_release_time, Package, Variant from rez.package_filter import PackageFilterList, TimestampRule -from rez.utils.memcached import memcached_client, pool_memcached_connections +from rez.utils.memcached import memcached_client, pool_memcached_connections, Client from rez.utils.logging_ import log_duration from rez.config import config from rez.version import Requirement from contextlib import contextmanager from enum import Enum from hashlib import sha1 +from typing import Any, Callable, Iterator, TYPE_CHECKING + +if TYPE_CHECKING: + from rez.package_order import PackageOrder, PackageOrderList + from rez.resolved_context import ResolvedContext + from rez.utils.typing import SupportsWrite + + from typing import TypedDict + + # FIXME: move this out of TYPE_CHECKING block when python 3.7 support is dropped + class SolverDict(TypedDict): + status: ResolverStatus + graph: Any # digraph + solve_time: float | None + load_time: float | None + failure_description: str | None + variant_handles: list[dict[str, Any]] + ephemerals: list[str] +else: + SolverDict = dict class ResolverStatus(Enum): @@ -25,7 +47,7 @@ class ResolverStatus(Enum): failed = ("The resolve is not possible.", ) aborted = ("The resolve was stopped by the user (via callback).", ) - def __init__(self, description): + def __init__(self, description) -> None: self.description = description @@ -35,10 +57,22 @@ class Resolver(object): The Resolver uses a combination of Solver(s) and cache(s) to resolve a package request as quickly as possible. """ - def __init__(self, context, package_requests, package_paths, package_filter=None, - package_orderers=None, timestamp=0, callback=None, building=False, - testing=False, verbosity=False, buf=None, package_load_callback=None, - caching=True, suppress_passive=False, print_stats=False): + def __init__(self, + context: ResolvedContext, + package_requests: list[Requirement], + package_paths: list[str], + package_filter: PackageFilterList | None = None, + package_orderers: PackageOrderList | None = None, + timestamp: int | None = 0, + callback: Callable[[SolverState], tuple[SolverCallbackReturn, str]] | None = None, + building: bool = False, + testing: bool = False, + verbosity: int = 0, + buf: SupportsWrite | None = None, + package_load_callback: Callable[[Package], Any] | None = None, + caching: bool = True, + suppress_passive: bool = False, + print_stats: bool = False) -> None: """Create a Resolver. Args: @@ -89,7 +123,7 @@ def __init__(self, context, package_requests, package_paths, package_filter=None # combine timestamp and package filter into single filter if self.timestamp: if package_filter: - self.package_filter = package_filter.copy() + self.package_filter: PackageFilterList | None = package_filter.copy() else: self.package_filter = PackageFilterList() rule = TimestampRule.after(self.timestamp) @@ -98,20 +132,20 @@ def __init__(self, context, package_requests, package_paths, package_filter=None self.package_filter = package_filter self.status_ = ResolverStatus.pending - self.resolved_packages_ = None - self.resolved_ephemerals_ = None - self.failure_description = None - self.graph_ = None + self.resolved_packages_: list[Variant] | None = None + self.resolved_ephemerals_: list[Requirement] | None = None + self.failure_description: str | None = None + self.graph_: digraph | None = None self.from_cache = False self.memcached_servers = config.memcached_uri if config.resolve_caching else None - self.solve_time = 0.0 # time spent solving - self.load_time = 0.0 # time spent loading package resources + self.solve_time: float | None = 0.0 # time spent solving + self.load_time: float | None = 0.0 # time spent loading package resources self._print = config.debug_printer("resolve_memcache") @pool_memcached_connections - def solve(self): + def solve(self) -> None: """Perform the solve. """ with log_duration(self._print, "memcache get (resolve) took %s"): @@ -130,7 +164,7 @@ def solve(self): self._set_cached_solve(solver_dict) @property - def status(self): + def status(self) -> ResolverStatus: """Return the current status of the resolve. Returns: @@ -139,17 +173,17 @@ def status(self): return self.status_ @property - def resolved_packages(self): + def resolved_packages(self) -> list[Variant] | None: """Get the list of resolved packages. Returns: - List of `PackageVariant` objects, or None if the resolve has not + List of `Variant` objects, or None if the resolve has not completed. """ return self.resolved_packages_ @property - def resolved_ephemerals(self): + def resolved_ephemerals(self) -> list[Requirement] | None: """Get the list of resolved ewphemerals. Returns: @@ -159,7 +193,7 @@ def resolved_ephemerals(self): return self.resolved_ephemerals_ @property - def graph(self): + def graph(self) -> digraph | None: """Return the resolve graph. The resolve graph shows unsuccessful as well as successful resolves. @@ -169,10 +203,10 @@ def graph(self): """ return self.graph_ - def _get_variant(self, variant_handle): + def _get_variant(self, variant_handle: ResourceHandle | dict) -> Variant: return get_variant(variant_handle, context=self.context) - def _get_cached_solve(self): + def _get_cached_solve(self) -> SolverDict | None: """Find a memcached resolve. If there is NOT a resolve timestamp: @@ -220,27 +254,27 @@ def _get_cached_solve(self): variant_states = {} last_release_times = {} - def _hit(data): + def _hit(data: tuple[SolverDict, dict, dict]) -> SolverDict: solver_dict, _, _ = data return solver_dict - def _miss(): + def _miss() -> None: self._print("No cache key retrieved") return None - def _delete_cache_entry(key): + def _delete_cache_entry(key: str) -> None: with self._memcached_client() as client: client.delete(key) self._print("Discarded entry: %r", key) - def _retrieve(timestamped): + def _retrieve(timestamped: bool) -> tuple[str, tuple[SolverDict, dict, dict]]: key = self._memcache_key(timestamped=timestamped) self._print("Retrieving memcache key: %r", key) with self._memcached_client() as client: data = client.get(key) return key, data - def _packages_changed(key, data): + def _packages_changed(key: str, data: tuple[SolverDict, dict, dict]) -> bool: solver_dict, _, variant_states_dict = data for variant_handle in solver_dict.get("variant_handles", []): variant = self._get_variant(variant_handle) @@ -266,7 +300,7 @@ def _packages_changed(key, data): return True return False - def _releases_since_solve(key, data): + def _releases_since_solve(key: str, data: tuple[SolverDict, dict, dict]) -> bool: _, release_times_dict, _ = data for package_name, release_time in release_times_dict.items(): time_ = last_release_times.get(package_name) @@ -282,7 +316,7 @@ def _releases_since_solve(key, data): return True return False - def _timestamp_is_earlier(key, data): + def _timestamp_is_earlier(key: str, data: tuple[SolverDict, dict, dict]) -> bool: _, release_times_dict, _ = data for package_name, release_time in release_times_dict.items(): if self.timestamp < release_time: @@ -303,28 +337,28 @@ def _timestamp_is_earlier(key, data): key, data = _retrieve(True) if not data: - return _miss() + return _miss() # type: ignore[func-returns-value] if _packages_changed(key, data): _delete_cache_entry(key) - return _miss() + return _miss() # type: ignore[func-returns-value] else: return _hit(data) else: if not data: - return _miss() + return _miss() # type: ignore[func-returns-value] if _packages_changed(key, data) or _releases_since_solve(key, data): _delete_cache_entry(key) - return _miss() + return _miss() # type: ignore[func-returns-value] else: return _hit(data) @contextmanager - def _memcached_client(self): + def _memcached_client(self) -> Iterator[Client]: with memcached_client(self.memcached_servers, debug=config.debug_memcache) as client: yield client - def _set_cached_solve(self, solver_dict): + def _set_cached_solve(self, solver_dict: SolverDict) -> None: """Store a solve to memcached. If there is NOT a resolve timestamp: @@ -365,14 +399,14 @@ def _set_cached_solve(self, solver_dict): variant_states_dict[variant.name] = \ repo.get_variant_state_handle(variant.resource) - timestamped = (self.timestamp and releases_since_solve) + timestamped = bool(self.timestamp and releases_since_solve) key = self._memcache_key(timestamped=timestamped) data = (solver_dict, release_times_dict, variant_states_dict) with self._memcached_client() as client: client.set(key, data) self._print("Sent memcache key: %r", key) - def _memcache_key(self, timestamped=False): + def _memcache_key(self, timestamped: bool = False) -> str: """Makes a key suitable as a memcache entry.""" request = tuple(map(str, self.package_requests)) repo_ids = [] @@ -394,7 +428,7 @@ def _memcache_key(self, timestamped=False): return str(tuple(t)) - def _solve(self): + def _solve(self) -> Solver: solver = Solver(package_requests=self.package_requests, package_paths=self.package_paths, context=self.context, @@ -412,7 +446,7 @@ def _solve(self): return solver - def _set_result(self, solver_dict): + def _set_result(self, solver_dict: SolverDict) -> None: self.status_ = solver_dict.get("status") self.graph_ = solver_dict.get("graph") self.solve_time = solver_dict.get("solve_time") @@ -435,7 +469,7 @@ def _set_result(self, solver_dict): self.resolved_ephemerals_.append(req) @classmethod - def _solver_to_dict(cls, solver): + def _solver_to_dict(cls, solver: Solver) -> SolverDict: graph_ = solver.get_graph() solve_time = solver.solve_time load_time = solver.load_time @@ -463,7 +497,7 @@ def _solver_to_dict(cls, solver): for ephemeral in solver.resolved_ephemerals: ephemerals.append(str(ephemeral)) - return dict( + return SolverDict( status=status_, graph=graph_, solve_time=solve_time, diff --git a/src/rez/rex.py b/src/rez/rex.py index 1606a12d1..ccf7a4eed 100644 --- a/src/rez/rex.py +++ b/src/rez/rex.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + import os import sys import re @@ -11,6 +13,7 @@ from contextlib import contextmanager from string import Formatter from collections.abc import MutableMapping +from typing import Iterable, Mapping from rez.system import system from rez.config import config @@ -30,12 +33,13 @@ #=============================================================================== class Action(object): + name: str _registry = [] - def __init__(self, *args): + def __init__(self, *args) -> None: self.args = args - def __repr__(self): + def __repr__(self) -> str: return "%s(%s)" % (self.__class__.__name__, ', '.join(repr(x) for x in self.args)) @@ -43,11 +47,11 @@ def __eq__(self, other): return (self.name == other.name) and (self.args == other.args) @classmethod - def register_command_type(cls, name, klass): + def register_command_type(cls, name, klass) -> None: cls._registry.append((name, klass)) @classmethod - def register(cls): + def register(cls) -> None: cls.register_command_type(cls.name, cls) @classmethod @@ -57,13 +61,14 @@ def get_command_types(cls): class EnvAction(Action): @property - def key(self): + def key(self) -> str: return self.args[0] @property - def value(self): + def value(self) -> str | None: if len(self.args) == 2: return self.args[1] + return None class Unsetenv(EnvAction): @@ -73,7 +78,7 @@ class Unsetenv(EnvAction): class Setenv(EnvAction): name = 'setenv' - def pre_exec(self, interpreter): + def pre_exec(self, interpreter) -> None: key, value = self.args if isinstance(value, (list, tuple)): value = interpreter._env_sep(key).join(value) @@ -92,7 +97,7 @@ def friends(self): if len(self.args) == 3: return self.args[2] - def pre_exec(self, interpreter): + def pre_exec(self, interpreter) -> None: key, value, friends = self.args if isinstance(value, (list, tuple)): value = interpreter._env_sep(key).join(value) @@ -173,8 +178,11 @@ class ActionManager(object): """Handles the execution book-keeping. Tracks env variable values, and triggers the callbacks of the `ActionInterpreter`. """ - def __init__(self, interpreter, parent_environ=None, parent_variables=None, - formatter=None, verbose=False, env_sep_map=None): + def __init__(self, interpreter: ActionInterpreter, + parent_environ: Mapping[str, str] | None = None, + parent_variables: Iterable[str] | None = None, + formatter=None, + verbose: bool = False, env_sep_map=None) -> None: ''' interpreter: string or `ActionInterpreter` the interpreter to use when executing rex actions @@ -245,7 +253,7 @@ def _format(self, value): except (KeyError, ValueError): return value - def _expand(self, value): + def _expand(self, value: str) : def _fn(str_): str_ = expandvars(str_, self.environ) str_ = expandvars(str_, self.parent_environ) @@ -270,17 +278,17 @@ def get_output(self, style=OutputStyle.file): # -- Commands - def undefined(self, key): + def undefined(self, key) -> bool: _, expanded_key = self._key(key) return ( expanded_key not in self.environ and expanded_key not in self.parent_environ ) - def defined(self, key): + def defined(self, key) -> bool: return not self.undefined(key) - def expandvars(self, value, format=True): + def expandvars(self, value, format: bool = True) -> str: if format: value = str(self._format(value)) return str(self._expand(value)) @@ -294,7 +302,7 @@ def getenv(self, key): raise RexUndefinedVariableError( "Referenced undefined environment variable: %s" % expanded_key) - def setenv(self, key, value): + def setenv(self, key, value) -> None: unexpanded_key, expanded_key = self._key(key) unexpanded_value, expanded_value = self._value(value) @@ -308,7 +316,7 @@ def setenv(self, key, value): key, value = unexpanded_key, unexpanded_value self.interpreter.setenv(key, value) - def unsetenv(self, key): + def unsetenv(self, key) -> None: unexpanded_key, expanded_key = self._key(key) self.actions.append(Unsetenv(unexpanded_key)) @@ -320,7 +328,7 @@ def unsetenv(self, key): key = unexpanded_key self.interpreter.unsetenv(key) - def resetenv(self, key, value, friends=None): + def resetenv(self, key, value, friends=None) -> None: unexpanded_key, expanded_key = self._key(key) unexpanded_value, expanded_value = self._value(value) @@ -334,7 +342,7 @@ def resetenv(self, key, value, friends=None): key, value = unexpanded_key, unexpanded_value self.interpreter.resetenv(key, value) - def _pendenv(self, key, value, action, interpfunc, addfunc): + def _pendenv(self, key, value, action, interpfunc, addfunc) -> None: unexpanded_key, expanded_key = self._key(key) unexpanded_value, expanded_value = self._value(value) @@ -388,26 +396,26 @@ def _pendenv(self, key, value, action, interpfunc, addfunc): key, value = unexpanded_key, unexpanded_values self.interpreter.setenv(key, value) - def prependenv(self, key, value): + def prependenv(self, key, value) -> None: self._pendenv(key, value, Prependenv, self.interpreter.prependenv, lambda x, y: [x] + y) - def appendenv(self, key, value): + def appendenv(self, key, value) -> None: self._pendenv(key, value, Appendenv, self.interpreter.appendenv, lambda x, y: y + [x]) - def alias(self, key, value): + def alias(self, key, value) -> None: key = str(self._format(key)) value = str(self._format(value)) self.actions.append(Alias(key, value)) self.interpreter.alias(key, value) - def info(self, value=''): + def info(self, value='') -> None: value = self._format(value) self.actions.append(Info(value)) self.interpreter.info(value) - def error(self, value): + def error(self, value) -> None: value = self._format(value) self.actions.append(Error(value)) self.interpreter.error(value) @@ -416,22 +424,22 @@ def stop(self, msg, *nargs): from rez.exceptions import RexStopError raise RexStopError(msg % nargs) - def command(self, value): + def command(self, value) -> None: # Note: Value is deliberately not formatted in commands self.actions.append(Command(value)) self.interpreter.command(value) - def comment(self, value): + def comment(self, value) -> None: value = str(self._format(value)) self.actions.append(Comment(value)) self.interpreter.comment(value) - def source(self, value): + def source(self, value) -> None: value = str(self._format(value)) self.actions.append(Source(value)) self.interpreter.source(value) - def shebang(self): + def shebang(self) -> None: self.actions.append(Shebang()) self.interpreter.shebang() @@ -512,10 +520,10 @@ def appendenv(self, key, value): def alias(self, key, value): raise NotImplementedError - def info(self, value): + def info(self, value: str): raise NotImplementedError - def error(self, value): + def error(self, value: str): raise NotImplementedError def command(self, value): @@ -530,9 +538,12 @@ def source(self, value): def shebang(self): raise NotImplementedError + def get_key_token(self, key) -> str: + raise NotImplementedError + # --- other - def escape_string(self, value, is_path=False): + def escape_string(self, value, is_path: bool = False): """Escape a string. Escape the given string so that special characters (such as quotes and @@ -615,7 +626,7 @@ class Python(ActionInterpreter): '''Execute commands in the current python session''' expand_env_vars = True - def __init__(self, target_environ=None, passive=False): + def __init__(self, target_environ=None, passive: bool = False) -> None: ''' target_environ: dict If target_environ is None or os.environ, interpreted actions are @@ -628,7 +639,7 @@ def __init__(self, target_environ=None, passive=False): are skipped. ''' self.passive = passive - self.manager = None + self.manager: ActionManager | None = None if (target_environ is None) or (target_environ is os.environ): self.target_environ = os.environ self.update_session = True @@ -636,10 +647,10 @@ def __init__(self, target_environ=None, passive=False): self.target_environ = target_environ self.update_session = False - def set_manager(self, manager): + def set_manager(self, manager: ActionManager) -> None: self.manager = manager - def apply_environ(self): + def apply_environ(self) -> None: """Apply changes to target environ. """ if self.manager is None: @@ -649,45 +660,45 @@ def apply_environ(self): self.target_environ.update(self.manager.environ) self.adjust_env_for_platform(self.target_environ) - def get_output(self, style=OutputStyle.file): + def get_output(self, style=OutputStyle.file) -> dict[str, str]: self.apply_environ() return self.manager.environ - def setenv(self, key, value): + def setenv(self, key, value) -> None: if self.update_session: if key == 'PYTHONPATH': value = self.escape_string(value) sys.path = value.split(self.pathsep) - def unsetenv(self, key): + def unsetenv(self, key) -> None: pass - def resetenv(self, key, value, friends=None): + def resetenv(self, key, value, friends=None) -> None: pass - def prependenv(self, key, value): + def prependenv(self, key, value) -> None: if self.update_session: if key == 'PYTHONPATH': value = self.escape_string(value) sys.path.insert(0, value) - def appendenv(self, key, value): + def appendenv(self, key, value) -> None: if self.update_session: if key == 'PYTHONPATH': value = self.escape_string(value) sys.path.append(value) - def info(self, value): + def info(self, value) -> None: if not self.passive: value = self.escape_string(value) print(value) - def error(self, value): + def error(self, value) -> None: if not self.passive: value = self.escape_string(value) print(value, file=sys.stderr) - def subprocess(self, args, **subproc_kwargs): + def subprocess(self, args: str | Iterable[str], **subproc_kwargs) -> Popen: if self.manager: self.target_environ.update(self.manager.environ) self.adjust_env_for_platform(self.target_environ) @@ -717,38 +728,38 @@ def command(self, value): cmd = shlex_join(value) raise RexError('Error executing command: %s\n%s' % (cmd, str(e))) - def comment(self, value): + def comment(self, value) -> None: pass - def source(self, value): + def source(self, value) -> None: pass - def alias(self, key, value): + def alias(self, key, value) -> None: pass - def _bind_interactive_rez(self): + def _bind_interactive_rez(self) -> None: pass - def _saferefenv(self, key): + def _saferefenv(self, key) -> None: pass - def shebang(self): + def shebang(self) -> None: pass - def get_key_token(self, key): + def get_key_token(self, key) -> str: # Not sure if this actually needs to be returned here. Prior to the # Windows refactor this is the value this interpretter was receiving, # but the concept doesn't really feel applicable to Python. It's just # here because the API requires it. return "${%s}" % key - def adjust_env_for_platform(self, env): + def adjust_env_for_platform(self, env) -> None: """ Make required platform-specific adjustments to env. """ if platform_.name == "windows": self._add_systemroot_to_env_win32(env) - def _add_systemroot_to_env_win32(self, env): + def _add_systemroot_to_env_win32(self, env) -> None: r""" Sets ``%SYSTEMROOT%`` environment variable, if not present in :py:attr:`target_environ` . @@ -822,19 +833,19 @@ class EscapedString(object): you can use the `literal` and `expandable` free functions, rather than constructing a class instance directly. """ - def __init__(self, value, is_literal=False): + def __init__(self, value: str, is_literal: bool = False) -> None: self.strings = [(is_literal, value)] - def copy(self): + def copy(self) -> EscapedString: other = EscapedString.__new__(EscapedString) other.strings = self.strings[:] return other - def literal(self, value): + def literal(self, value) -> EscapedString: self._add(value, True) return self - def expandable(self, value): + def expandable(self, value) -> EscapedString: self._add(value, False) return self @@ -844,18 +855,18 @@ def l(self, value): # noqa def e(self, value): return self.expandable(value) - def _add(self, value, is_literal): + def _add(self, value, is_literal) -> None: last = self.strings[-1] if last[0] == is_literal: self.strings[-1] = (last[0], last[1] + value) else: self.strings.append((is_literal, value)) - def __str__(self): + def __str__(self) -> str: """Return the string unescaped.""" return ''.join(x[1] for x in self.strings) - def __repr__(self): + def __repr__(self) -> str: return "%s(%r)" % (self.__class__.__name__, self.strings) def __eq__(self, other): @@ -867,10 +878,10 @@ def __eq__(self, other): and other.strings == self.strings ) - def __ne__(self, other): + def __ne__(self, other) -> bool: return not (self == other) - def __add__(self, other): + def __add__(self, other) -> EscapedString: """Join two escaped strings together. Returns: @@ -883,7 +894,7 @@ def __add__(self, other): result._add(value, is_literal) return result - def expanduser(self): + def expanduser(self) -> EscapedString: """Analogous to os.path.expanduser. Returns: @@ -891,7 +902,7 @@ def expanduser(self): """ return self.formatted(os.path.expanduser) - def formatted(self, func): + def formatted(self, func) -> EscapedString: """Return the string with non-literal parts formatted. Args: @@ -946,7 +957,7 @@ def split(self, delimiter=None): return result @classmethod - def join(cls, sep, values): + def join(cls, sep: str, values) -> EscapedString: if not values: return EscapedString('') @@ -960,15 +971,15 @@ def join(cls, sep, values): return result @classmethod - def promote(cls, value): - if isinstance(value, cls): + def promote(cls, value: str | EscapedString) -> EscapedString: + if isinstance(value, EscapedString): return value else: return cls(value) @classmethod - def demote(cls, value): - if isinstance(value, cls): + def demote(cls, value: str | EscapedString) -> str: + if isinstance(value, EscapedString): return str(value) else: return value @@ -980,12 +991,12 @@ def disallow(cls, value): return value -def literal(value): +def literal(value) -> EscapedString: """Creates a literal string.""" return EscapedString(value, True) -def expandable(value): +def expandable(value) -> EscapedString: """Creates an expandable string.""" return EscapedString(value, False) @@ -1028,13 +1039,13 @@ class NamespaceFormatter(Formatter): some situations. """ - def __init__(self, namespace): + def __init__(self, namespace) -> None: Formatter.__init__(self) self.initial_namespace = namespace self.namespace = self.initial_namespace def format(self, format_string, *args, **kwargs): - def escape_envvar(matchobj): + def escape_envvar(matchobj) -> str: value = next((x for x in matchobj.groups() if x is not None)) return "${{%s}}" % value @@ -1090,7 +1101,7 @@ class EnvironmentDict(MutableMapping): `__getitem__` is always guaranteed to return an `EnvironmentVariable` instance: it will not raise a KeyError. """ - def __init__(self, manager): + def __init__(self, manager) -> None: """Creates an `EnvironmentDict`. Args: @@ -1106,7 +1117,7 @@ def __init__(self, manager): def keys(self): return self._var_cache.keys() - def __repr__(self): + def __repr__(self) -> str: return '%s(%s)' % (self.__class__.__name__, str(self._var_cache)) def __getitem__(self, key): @@ -1114,20 +1125,20 @@ def __getitem__(self, key): self._var_cache[key] = EnvironmentVariable(key, self) return self._var_cache[key] - def __setitem__(self, key, value): + def __setitem__(self, key, value) -> None: self[key].set(value) - def __contains__(self, key): + def __contains__(self, key) -> bool: return (key in self._var_cache) - def __delitem__(self, key): + def __delitem__(self, key) -> None: del self._var_cache[key] def __iter__(self): for key in self._var_cache.keys(): yield key - def __len__(self): + def __len__(self) -> int: return len(self._var_cache) @@ -1137,7 +1148,7 @@ class representing an environment variable combined with EnvironmentDict class, records changes to the environment ''' - def __init__(self, name, environ_map): + def __init__(self, name, environ_map) -> None: self._name = name self._environ_map = environ_map @@ -1145,19 +1156,19 @@ def __init__(self, name, environ_map): def name(self): return self._name - def prepend(self, value): + def prepend(self, value) -> None: self._environ_map.manager.prependenv(self.name, value) - def append(self, value): + def append(self, value) -> None: self._environ_map.manager.appendenv(self.name, value) - def reset(self, value, friends=None): + def reset(self, value, friends=None) -> None: self._environ_map.manager.resetenv(self.name, value, friends=friends) - def set(self, value): + def set(self, value) -> None: self._environ_map.manager.setenv(self.name, value) - def unset(self): + def unset(self) -> None: self._environ_map.manager.unsetenv(self.name) # --- the following methods all require knowledge of the current environment @@ -1168,19 +1179,19 @@ def get(self): def value(self): return self.get() - def setdefault(self, value): + def setdefault(self, value) -> None: '''set value if the variable does not yet exist''' if not self: self.set(value) - def __str__(self): + def __str__(self) -> str: return self.value() - def __repr__(self): + def __repr__(self) -> str: return '%s(%r, %r)' % (self.__class__.__name__, self._name, self.value()) - def __bool__(self): + def __bool__(self) -> bool: try: return bool(self.value()) except RexUndefinedVariableError: @@ -1191,7 +1202,7 @@ def __eq__(self, value): value = value.value() return self.value() == value - def __ne__(self, value): + def __ne__(self, value) -> bool: return not self == value @@ -1209,8 +1220,9 @@ class RexExecutor(object): ex.env.FOO_SET = 1 ex.alias('foo','foo -l') """ - def __init__(self, interpreter=None, globals_map=None, parent_environ=None, - parent_variables=None, shebang=True, add_default_namespaces=True): + def __init__(self, interpreter: ActionInterpreter | None = None, + globals_map=None, parent_environ: Mapping[str, str] | None = None, + parent_variables=None, shebang: bool = True, add_default_namespaces: bool = True) -> None: """ interpreter: `ActionInterpreter` or None the interpreter to use when executing rex actions. If None, creates @@ -1274,7 +1286,7 @@ def __getattr__(self, attr): return self.globals[attr] if attr in self.globals \ else getattr(super(RexExecutor, self), attr) - def bind(self, name, obj): + def bind(self, name, obj) -> None: """Binds an object to the execution context. Args: @@ -1283,7 +1295,7 @@ def bind(self, name, obj): """ self.globals[name] = obj - def unbind(self, name): + def unbind(self, name) -> None: """Unbind an object from the execution context. Has no effect if the binding does not exist. @@ -1316,7 +1328,7 @@ def reset_globals(self): self.globals.clear() self.globals.update(saved_globals) - def append_system_paths(self): + def append_system_paths(self) -> None: """Append system paths to $PATH.""" from rez.shells import Shell, create_shell @@ -1328,12 +1340,12 @@ def append_system_paths(self): for path in sh.get_syspaths(): self.env.PATH.append(path) - def prepend_rez_path(self): + def prepend_rez_path(self) -> None: """Prepend rez path to $PATH.""" if system.rez_bin_path: self.env.PATH.prepend(system.rez_bin_path) - def append_rez_path(self): + def append_rez_path(self) -> None: """Append rez path to $PATH.""" if system.rez_bin_path: self.env.PATH.append(system.rez_bin_path) @@ -1399,7 +1411,7 @@ def compile_code(cls, code, filename=None, exec_namespace=None): return pyc - def execute_code(self, code, filename=None): + def execute_code(self, code, filename=None) -> None: """Execute code within the execution context. Args: diff --git a/src/rez/rex_bindings.py b/src/rez/rex_bindings.py index 34322184b..4b47e093e 100644 --- a/src/rez/rex_bindings.py +++ b/src/rez/rex_bindings.py @@ -10,14 +10,17 @@ The classes in this file are intended to have simple interfaces that hide unnecessary data from Rex, and provide APIs that will not change. """ -from rez.version import VersionRange +from __future__ import annotations + +from rez.rex import ActionInterpreter +from rez.version import VersionRange, Version, VersionToken from rez.version import Requirement class Binding(object): """Abstract base class. """ - def __init__(self, data=None): + def __init__(self, data=None) -> None: self._data = data or {} def _attr_error(self, attr): @@ -54,20 +57,20 @@ class VersionBinding(Binding): >>> v.as_tuple(): (1, 2, '3alpha') """ - def __init__(self, version): + def __init__(self, version: Version) -> None: super(VersionBinding, self).__init__() self.__version = version @property - def major(self): + def major(self) -> int: return self[0] @property - def minor(self): + def minor(self) -> int: return self[1] @property - def patch(self): + def patch(self) -> int | str: return self[2] def as_tuple(self): @@ -83,8 +86,8 @@ def __getitem__(self, i): except IndexError: return None - def __getitem(self, i): - def _convert(t): + def __getitem(self, i: int | slice): + def _convert(t: VersionToken) -> str | int: s = str(t) if s.isdigit() and (s[0] != '0' or s == '0'): return int(s) @@ -97,10 +100,10 @@ def _convert(t): else: return _convert(tokens) - def __len__(self): + def __len__(self) -> int: return len(self.__version) - def __str__(self): + def __str__(self) -> str: return str(self.__version) def __iter__(self): @@ -112,7 +115,8 @@ def __iter__(self): class VariantBinding(Binding): """Binds a packages.Variant object. """ - def __init__(self, variant, cached_root=None, interpreter=None): + def __init__(self, variant, cached_root: str | None = None, + interpreter: ActionInterpreter | None = None) -> None: doc = dict(version=VersionBinding(variant.version)) super(VariantBinding, self).__init__(doc) @@ -144,21 +148,21 @@ def __getattr__(self, attr): return value - def _is_in_package_cache(self): + def _is_in_package_cache(self) -> bool: return (self.__cached_root is not None) - def _attr_error(self, attr): + def _attr_error(self, attr: str): raise AttributeError("package %s has no attribute '%s'" % (str(self), attr)) - def __str__(self): + def __str__(self) -> str: return self.__variant.qualified_package_name class RO_MappingBinding(Binding): """A read-only, dict-like object. """ - def __init__(self, data): + def __init__(self, data) -> None: super(RO_MappingBinding, self).__init__(data) def get(self, name, default=None): @@ -170,17 +174,17 @@ def __getitem__(self, name): else: self._attr_error(name) - def __contains__(self, name): + def __contains__(self, name) -> bool: return (name in self._data) - def __str__(self): + def __str__(self) -> str: return str(self._data.values()) class VariantsBinding(RO_MappingBinding): """Binds a list of packages.VariantBinding objects, under the package name of each variant.""" - def __init__(self, variant_bindings): + def __init__(self, variant_bindings) -> None: super(VariantsBinding, self).__init__(variant_bindings) def _attr_error(self, attr): @@ -189,14 +193,14 @@ def _attr_error(self, attr): class RequirementsBinding(RO_MappingBinding): """Binds a list of version.Requirement objects.""" - def __init__(self, requirements): + def __init__(self, requirements) -> None: doc = dict((x.name, str(x)) for x in requirements) super(RequirementsBinding, self).__init__(doc) - def _attr_error(self, attr): + def _attr_error(self, attr: str): raise AttributeError("request does not exist: '%s'" % attr) - def get_range(self, name, default=None): + def get_range(self, name: str, default=None) -> Requirement | VersionRange | None: """Returns requirement version range object""" req_str = self._data.get(name) if req_str: @@ -219,17 +223,17 @@ class EphemeralsBinding(RO_MappingBinding): def commands(): if "foo.cli" in ephemerals: # will match '.foo.cli-*' request """ - def __init__(self, ephemerals): + def __init__(self, ephemerals) -> None: doc = dict( (x.name[1:], str(x)) # note: stripped leading '.' for x in ephemerals ) super(EphemeralsBinding, self).__init__(doc) - def _attr_error(self, attr): + def _attr_error(self, attr: str): raise AttributeError("ephemeral does not exist: '%s'" % attr) - def get_range(self, name, default=None): + def get_range(self, name: str, default=None) -> Requirement | VersionRange | None: """Returns ephemeral version range object""" req_str = self._data.get(name) if req_str: diff --git a/src/rez/rezconfig.py b/src/rez/rezconfig.py index f4eb2c062..05567dab5 100644 --- a/src/rez/rezconfig.py +++ b/src/rez/rezconfig.py @@ -2,7 +2,7 @@ # Copyright Contributors to the Rez Project -""" +r""" Rez configuration settings. Do not change this file. Settings are determined in the following way (higher number means higher @@ -45,6 +45,7 @@ """ # flake8: noqa +from __future__ import annotations import os diff --git a/src/rez/serialise.py b/src/rez/serialise.py index dac33a38d..7cb61113e 100644 --- a/src/rez/serialise.py +++ b/src/rez/serialise.py @@ -5,6 +5,8 @@ """ Read and write data from file. File caching via a memcached server is supported. """ +from __future__ import annotations + from contextlib import contextmanager from enum import Enum from inspect import isfunction, ismodule @@ -41,7 +43,7 @@ class FileFormat(Enum): __order__ = "py,yaml,txt" - def __init__(self, extension): + def __init__(self, extension) -> None: self.extension = extension @@ -104,8 +106,8 @@ def open_file_for_write(filepath, mode=None): file_cache[filepath] = cache_filepath -def load_from_file(filepath, format_=FileFormat.py, update_data_callback=None, - disable_memcache=False): +def load_from_file(filepath: str, format_=FileFormat.py, update_data_callback=None, + disable_memcache: bool = False): """Load data from a file. Note: @@ -156,11 +158,11 @@ def _load_from_file__key(filepath, format_, update_data_callback): min_compress_len=config.memcached_package_file_min_compress_len, key=_load_from_file__key, debug=config.debug_memcache) -def _load_from_file(filepath, format_, update_data_callback): +def _load_from_file(filepath: str, format_, update_data_callback): return _load_file(filepath, format_, update_data_callback) -def _load_file(filepath, format_, update_data_callback, original_filepath=None): +def _load_file(filepath: str, format_, update_data_callback, original_filepath=None): load_func = load_functions[format_] if debug_print: @@ -219,7 +221,7 @@ def set_objects(objects): _set_objects.variables = {} -def load_py(stream, filepath=None): +def load_py(stream, filepath: str = None): """Load python-formatted data from a stream. Args: @@ -232,7 +234,7 @@ def load_py(stream, filepath=None): return _load_py(stream, filepath=filepath) -def _load_py(stream, filepath=None): +def _load_py(stream, filepath: str = None): scopes = ScopeContext() g = dict(scope=scopes, @@ -276,7 +278,7 @@ class EarlyThis(object): Just exposes raw package data as object attributes. """ - def __init__(self, data): + def __init__(self, data) -> None: self._data = data def __getattr__(self, attr): @@ -293,7 +295,7 @@ def __getattr__(self, attr): return value -def process_python_objects(data, filepath=None): +def process_python_objects(data: dict, filepath: str | None = None) -> dict: """Replace certain values in the given package data dict. Does things like: @@ -397,7 +399,7 @@ def _trim(value): return data -def load_yaml(stream, **kwargs): +def load_yaml(stream, filepath: str = None): """Load yaml-formatted data from a stream. Args: @@ -426,7 +428,7 @@ def load_yaml(stream, **kwargs): raise e -def load_txt(stream, **kwargs): +def load_txt(stream, filepath: str = None): """Load text data from a stream. Args: @@ -439,7 +441,7 @@ def load_txt(stream, **kwargs): return content -def clear_file_caches(): +def clear_file_caches() -> None: """Clear any cached files.""" _load_from_file.forget() diff --git a/src/rez/shells.py b/src/rez/shells.py index 6dfe6d676..ff8608589 100644 --- a/src/rez/shells.py +++ b/src/rez/shells.py @@ -5,6 +5,9 @@ """ Pluggable API for creating subshells using different programs, such as bash. """ + +from __future__ import annotations + from rez.rex import RexExecutor, ActionInterpreter, OutputStyle from rez.util import shlex_join, is_non_string_iterable from rez.utils.which import which @@ -17,9 +20,16 @@ import os import os.path from shlex import quote +from typing import Any, Iterable, TYPE_CHECKING + +if TYPE_CHECKING: + import subprocess + # this is not available in typing until 3.11, but due to __future__.annotations + # we can use it without really importing it + from typing import Self -def get_shell_types(): +def get_shell_types() -> list[str]: """Returns the available shell types: bash, tcsh etc. Returns: @@ -29,7 +39,7 @@ def get_shell_types(): return list(plugin_manager.get_plugins('shell')) -def get_shell_class(shell=None): +def get_shell_class(shell: str | None = None) -> type[Shell]: """Get the plugin class associated with the given or current shell. Returns: @@ -42,10 +52,10 @@ def get_shell_class(shell=None): shell = system.shell from rez.plugin_managers import plugin_manager - return plugin_manager.get_plugin_class("shell", shell) + return plugin_manager.get_plugin_class("shell", shell, Shell) -def create_shell(shell=None, **kwargs): +def create_shell(shell: str | None = None, **kwargs: Any) -> Shell: """Returns a Shell of the given or current type. Returns: @@ -67,29 +77,29 @@ class Shell(ActionInterpreter): schema_dict = {"prompt": str} @classmethod - def name(cls): + def name(cls) -> str: """Plugin name. """ raise NotImplementedError @classmethod - def executable_name(cls): + def executable_name(cls) -> str: """Name of executable to create shell instance. """ return cls.name() @classmethod - def executable_filepath(cls): + def executable_filepath(cls) -> str: """Get full filepath to executable, or raise if not found. """ return cls.find_executable(cls.executable_name()) @property - def executable(self): + def executable(self) -> str: return self.__class__.executable_filepath() @classmethod - def is_available(cls): + def is_available(cls) -> bool: """Determine if the shell is available to instantiate. Returns: @@ -101,7 +111,7 @@ def is_available(cls): return False @classmethod - def file_extension(cls): + def file_extension(cls) -> str: """Get the file extension associated with the shell. Returns: @@ -110,8 +120,8 @@ def file_extension(cls): raise NotImplementedError @classmethod - def startup_capabilities(cls, rcfile=False, norc=False, stdin=False, - command=False): + def startup_capabilities(cls, rcfile: bool = False, norc: bool = False, + stdin: bool = False, command: bool = False): """ Given a set of options related to shell startup, return the actual options that will be applied. @@ -125,14 +135,14 @@ def startup_capabilities(cls, rcfile=False, norc=False, stdin=False, def get_syspaths(cls): raise NotImplementedError - def __init__(self): + def __init__(self) -> None: self._lines = [] self.settings = config.plugins.shell[self.name()] - def _addline(self, line): + def _addline(self, line: str) -> None: self._lines.append(line) - def get_output(self, style=OutputStyle.file): + def get_output(self, style: OutputStyle = OutputStyle.file) -> str: if style == OutputStyle.file: script = '\n'.join(self._lines) + '\n' else: # eval style @@ -145,24 +155,24 @@ def get_output(self, style=OutputStyle.file): return script - def new_shell(self): + def new_shell(self) -> Self: """Returns A new, reset shell of the same type.""" return self.__class__() @classmethod - def _unsupported_option(cls, option, val): + def _unsupported_option(cls, option, val) -> None: if val and config.warn("shell_startup"): print_warning("%s ignored, not supported by %s shell" % (option, cls.name())) @classmethod - def _overruled_option(cls, option, overruling_option, val): + def _overruled_option(cls, option, overruling_option, val) -> None: if val and config.warn("shell_startup"): print_warning("%s ignored by %s shell - overruled by %s option" % (option, cls.name(), overruling_option)) @classmethod - def find_executable(cls, name, check_syspaths=False): + def find_executable(cls, name: str, check_syspaths: bool = False) -> str: """Find an executable. Args: @@ -195,10 +205,10 @@ def find_executable(cls, name, check_syspaths=False): raise RuntimeError("Couldn't find executable '%s'." % name) return exe - def spawn_shell(self, context_file, tmpdir, rcfile=None, norc=False, - stdin=False, command=None, env=None, quiet=False, - pre_command=None, add_rez=True, - package_commands_sourced_first=None, **Popen_args): + def spawn_shell(self, context_file: str, tmpdir, rcfile=None, norc: bool = False, + stdin: bool = False, command=None, env=None, quiet: bool = False, + pre_command: str | list[str] | None = None, add_rez: bool = True, + package_commands_sourced_first=None, **Popen_args) -> subprocess.Popen: """Spawn a possibly interactive subshell. Args: @@ -233,7 +243,7 @@ def spawn_shell(self, context_file, tmpdir, rcfile=None, norc=False, raise NotImplementedError @classmethod - def convert_tokens(cls, value): + def convert_tokens(cls, value) -> str: """ Converts any token like ${VAR} and $VAR to shell specific form. Uses the ENV_VAR_REGEX to correctly parse tokens. @@ -250,7 +260,7 @@ def convert_tokens(cls, value): ) @classmethod - def get_key_token(cls, key): + def get_key_token(cls, key) -> str: """ Encodes the environment variable into the shell specific form. Shells might implement multiple forms, but the most common/safest @@ -265,7 +275,7 @@ def get_key_token(cls, key): return cls.get_all_key_tokens(key)[0] @classmethod - def get_all_key_tokens(cls, key): + def get_all_key_tokens(cls, key: str) -> list[str]: """ Encodes the environment variable into the shell specific forms. Shells might implement multiple forms, but the most common/safest @@ -280,7 +290,7 @@ def get_all_key_tokens(cls, key): raise NotImplementedError @classmethod - def line_terminator(cls): + def line_terminator(cls) -> str: """ Returns: str: default line terminator @@ -288,7 +298,7 @@ def line_terminator(cls): raise NotImplementedError @classmethod - def join(cls, command): + def join(cls, command: Iterable[str]) -> str: """ Note: Default to unix sh/bash- friendly behaviour. @@ -321,33 +331,33 @@ class UnixShell(Shell): r""" A base class for common \*nix shells, such as bash and tcsh. """ - rcfile_arg = None - norc_arg = None - histfile = None - histvar = None + rcfile_arg: str = None + norc_arg: str = None + histfile: str = None + histvar: str = None command_arg = '-c' stdin_arg = '-s' last_command_status = '$?' - syspaths = None + syspaths: list[str] = None # # startup rules # @classmethod - def supports_norc(cls): + def supports_norc(cls) -> bool: return True @classmethod - def supports_command(cls): + def supports_command(cls) -> bool: return True @classmethod - def supports_stdin(cls): + def supports_stdin(cls) -> bool: return True @classmethod - def get_startup_sequence(cls, rcfile, norc, stdin, command): + def get_startup_sequence(cls, rcfile: str, norc, stdin, command): """ Return a dict containing: @@ -364,9 +374,9 @@ def get_startup_sequence(cls, rcfile, norc, stdin, command): """ raise NotImplementedError - def spawn_shell(self, context_file, tmpdir, rcfile=None, norc=False, - stdin=False, command=None, env=None, quiet=False, - pre_command=None, add_rez=True, + def spawn_shell(self, context_file, tmpdir, rcfile=None, norc: bool = False, + stdin: bool = False, command=None, env=None, quiet: bool = False, + pre_command: str | list[str] | None = None, add_rez: bool = True, package_commands_sourced_first=None, **Popen_args): d = self.get_startup_sequence(rcfile, norc, bool(stdin), command) @@ -379,7 +389,7 @@ def spawn_shell(self, context_file, tmpdir, rcfile=None, norc=False, if package_commands_sourced_first is None: package_commands_sourced_first = config.package_commands_sourced_first - def _record_shell(ex, files, bind_rez=True, print_msg=False): + def _record_shell(ex, files, bind_rez: bool = True, print_msg: bool = False) -> None: if bind_rez and package_commands_sourced_first: ex.source(context_file) @@ -511,21 +521,21 @@ def _create_ex(): % (cmd_str, str(e))) return p - def resetenv(self, key, value, friends=None): + def resetenv(self, key, value, friends=None) -> None: self._addline(self.setenv(key, value)) - def info(self, value): + def info(self, value: str) -> None: for line in value.split('\n'): line = self.escape_string(line) self._addline('echo %s' % line) - def error(self, value): + def error(self, value: str) -> None: for line in value.split('\n'): line = self.escape_string(line) self._addline('echo %s 1>&2' % line) # escaping is allowed in args, but not in program string - def command(self, value): + def command(self, value) -> None: if is_non_string_iterable(value): it = iter(value) cmd = EscapedString.disallow(next(it)) @@ -535,12 +545,12 @@ def command(self, value): value = EscapedString.disallow(value) self._addline(value) - def comment(self, value): + def comment(self, value) -> None: value = EscapedString.demote(value) for line in value.split('\n'): self._addline('# %s' % line) - def shebang(self): + def shebang(self) -> None: self._addline("#!%s" % self.executable) @classmethod @@ -548,5 +558,5 @@ def get_all_key_tokens(cls, key): return ["${%s}" % key, "$%s" % key] @classmethod - def line_terminator(cls): + def line_terminator(cls) -> str: return "\n" diff --git a/src/rez/solver.py b/src/rez/solver.py index b128dbc88..697dddcf6 100644 --- a/src/rez/solver.py +++ b/src/rez/solver.py @@ -11,8 +11,10 @@ See SOLVER.md for an in-depth description of how this module works. """ +from __future__ import annotations + from rez.config import config -from rez.packages import iter_packages +from rez.packages import iter_packages, Package, Variant from rez.package_repository import package_repo_stats from rez.utils.logging_ import print_debug from rez.utils.data_utils import cached_property @@ -21,16 +23,26 @@ from rez.vendor.pygraph.algorithms.accessibility import accessibility from rez.exceptions import PackageNotFoundError, ResolveError, \ PackageFamilyNotFoundError, RezSystemError -from rez.version import VersionRange +from rez.version import Version, VersionRange from rez.version import VersionedObject, Requirement, RequirementList +from rez.utils.typing import SupportsLessThan, SupportsWrite from contextlib import contextmanager from enum import Enum from itertools import product, chain +from typing import cast, Any, Callable, Generator, Iterator, TypeVar, TYPE_CHECKING import copy import time import sys import os +if TYPE_CHECKING: + from rez.resolved_context import ResolvedContext + from rez.package_filter import PackageFilterBase + from rez.package_order import PackageOrderList + + +T = TypeVar("T") + # a hidden control for forcing to non-optimized solving mode. This is here as # first port of call for narrowing down the cause of a solver bug if we see one @@ -87,15 +99,15 @@ class SolverCallbackReturn(Enum): class _Printer(object): - def __init__(self, verbosity, buf=None, suppress_passive=False): + def __init__(self, verbosity: int, buf: SupportsWrite | None = None, suppress_passive: bool = False) -> None: self.verbosity = verbosity self.buf = buf or sys.stdout self.suppress_passive = suppress_passive - self.pending_sub = None + self.pending_sub: str | None = None self.pending_br = False self.last_pr = True - def header(self, txt, *args): + def header(self, txt: str, *args: Any) -> None: if self.verbosity: if self.verbosity > 2: self.pr() @@ -104,11 +116,11 @@ def header(self, txt, *args): if self.verbosity > 2: self.pr('-' * 80) - def subheader(self, txt): + def subheader(self, txt: str) -> None: if self.verbosity > 2: self.pending_sub = txt - def __call__(self, txt, *args): + def __call__(self, txt: str, *args: Any) -> None: if self.verbosity > 2: if self.pending_sub: if self.last_pr: @@ -122,19 +134,19 @@ def __call__(self, txt, *args): self.last_pr = True self.pending_br = False - def passive(self, txt, *args): + def passive(self, txt: str, *args: Any) -> None: if self.suppress_passive: return self(txt, *args) - def br(self): + def br(self) -> None: self.pending_br = True - def pr(self, txt='', *args): + def pr(self, txt: str = '', *args: Any) -> None: print(txt % args, file=self.buf) - def __bool__(self): + def __bool__(self) -> bool: return self.verbosity > 0 @@ -142,51 +154,51 @@ class SolverState(object): """Represent the current state of the solver instance for use with a callback. """ - def __init__(self, num_solves, num_fails, phase): + def __init__(self, num_solves: int, num_fails: int, phase: _ResolvePhase) -> None: self.num_solves = num_solves self.num_fails = num_fails self.phase = phase - def __str__(self): + def __str__(self) -> str: return ("solve #%d (%d fails so far): %s" % (self.num_solves, self.num_fails, str(self.phase))) class _Common(object): - def __repr__(self): + def __repr__(self) -> str: return "%s(%s)" % (self.__class__.__name__, str(self)) class Reduction(_Common): """A variant was removed because its dependencies conflicted with another scope in the current phase.""" - def __init__(self, name, version, variant_index, dependency, - conflicting_request): + def __init__(self, name: str, version: Version, variant_index: int | None, dependency: Requirement, + conflicting_request: Requirement) -> None: self.name = name self.version = version self.variant_index = variant_index self.dependency = dependency self.conflicting_request = conflicting_request - def reducee_str(self): + def reducee_str(self) -> str: stmt = VersionedObject.construct(self.name, self.version) idx_str = "[]" if self.variant_index is None \ else "[%d]" % self.variant_index return str(stmt) + idx_str - def involved_requirements(self): + def involved_requirements(self) -> list[Requirement]: range_ = VersionRange.from_version(self.version) req = Requirement.construct(self.name, range_) return [req, self.dependency, self.conflicting_request] - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return (self.name == other.name and self.version == other.version and self.variant_index == other.variant_index and self.dependency == other.dependency and self.conflicting_request == other.conflicting_request) - def __str__(self): + def __str__(self) -> str: return "%s (dep(%s) <--!--> %s)" \ % (self.reducee_str(), self.dependency, self.conflicting_request) @@ -194,7 +206,7 @@ def __str__(self): class DependencyConflict(_Common): """A common dependency shared by all variants in a scope, conflicted with another scope in the current phase.""" - def __init__(self, dependency, conflicting_request): + def __init__(self, dependency: Requirement, conflicting_request: Requirement) -> None: """ Args: dependency (`Requirement`): Merged requirement from a set of variants. @@ -203,73 +215,73 @@ def __init__(self, dependency, conflicting_request): self.dependency = dependency self.conflicting_request = conflicting_request - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return (self.dependency == other.dependency) \ and (self.conflicting_request == other.conflicting_request) - def __str__(self): + def __str__(self) -> str: return "%s <--!--> %s" % (str(self.dependency), str(self.conflicting_request)) class FailureReason(_Common): - def involved_requirements(self): + def involved_requirements(self) -> list[Requirement]: return [] - def description(self): + def description(self) -> str: return "" class TotalReduction(FailureReason): """All of a scope's variants were reduced away.""" - def __init__(self, reductions): + def __init__(self, reductions: list[Reduction]) -> None: self.reductions = reductions - def involved_requirements(self): + def involved_requirements(self) -> list[Requirement]: pkgs = [] for red in self.reductions: pkgs.extend(red.involved_requirements()) return pkgs - def description(self): + def description(self) -> str: return "A package was completely reduced: %s" % str(self) - def __eq__(self, other): + def __eq__(self, other: TotalReduction) -> bool: return (self.reductions == other.reductions) - def __str__(self): + def __str__(self) -> str: return ' '.join(("(%s)" % str(x)) for x in self.reductions) class DependencyConflicts(FailureReason): """A common dependency in a scope conflicted with another scope in the current phase.""" - def __init__(self, conflicts): + def __init__(self, conflicts: list[DependencyConflict]) -> None: self.conflicts = conflicts - def involved_requirements(self): + def involved_requirements(self) -> list[Requirement]: pkgs = [] for conflict in self.conflicts: pkgs.append(conflict.dependency) pkgs.append(conflict.conflicting_request) return pkgs - def description(self): + def description(self) -> str: return "The following package conflicts occurred: %s" % str(self) - def __eq__(self, other): + def __eq__(self, other: DependencyConflicts) -> bool: return (self.conflicts == other.conflicts) - def __str__(self): + def __str__(self) -> str: return ' '.join(("(%s)" % str(x)) for x in self.conflicts) class Cycle(FailureReason): """The solve contains a cyclic dependency.""" - def __init__(self, packages): + def __init__(self, packages: list[VersionedObject]) -> None: self.packages = packages - def involved_requirements(self): + def involved_requirements(self) -> list[Requirement]: pkgs = [] for pkg in self.packages: range_ = VersionRange.from_version(pkg.version) @@ -277,13 +289,13 @@ def involved_requirements(self): pkgs.append(stmt) return pkgs - def description(self): + def description(self) -> str: return "A cyclic dependency was detected: %s" % str(self) - def __eq__(self, other): + def __eq__(self, other: Cycle) -> bool: return (self.packages == other.packages) - def __str__(self): + def __str__(self) -> str: stmts = self.packages + self.packages[:1] return " --> ".join(map(str, stmts)) @@ -291,7 +303,7 @@ def __str__(self): class PackageVariant(_Common): """A variant of a package. """ - def __init__(self, variant, building): + def __init__(self, variant: Variant, building: bool) -> None: """Create a package variant. Args: @@ -302,23 +314,23 @@ def __init__(self, variant, building): self.building = building @property - def name(self): + def name(self) -> str: return self.variant.name @property - def version(self): + def version(self) -> Version: return self.variant.version @property - def index(self): + def index(self) -> int | None: return self.variant.index @property - def handle(self): + def handle(self) -> dict[str, Any]: return self.variant.handle.to_dict() @cached_property - def requires_list(self): + def requires_list(self) -> RequirementList: """ It is important that this property is calculated lazily. Getting the 'requires' attribute may trigger a package load, which may be avoided if @@ -335,31 +347,31 @@ def requires_list(self): return reqlist @property - def request_fams(self): + def request_fams(self) -> set[str]: return self.requires_list.names @property - def conflict_request_fams(self): + def conflict_request_fams(self) -> set[str]: return self.requires_list.conflict_names - def get(self, pkg_name): + def get(self, pkg_name: str) -> Requirement | None: return self.requires_list.get(pkg_name) - def __eq__(self, other): + def __eq__(self, other: PackageVariant) -> bool: return ( self.name == other.name and self.version == other.version and self.index == other.index ) - def __lt__(self, other): + def __lt__(self, other: PackageVariant) -> bool: return ( self.name < other.name and self.version < other.version and self.index < other.index ) - def __str__(self): + def __str__(self) -> str: stmt = VersionedObject.construct(self.name, self.version) idxstr = '' if self.index is None else str(self.index) return "%s[%s]" % (str(stmt), idxstr) @@ -370,20 +382,20 @@ class _PackageEntry(object): Holds some extra state data, such as whether the variants are sorted. """ - def __init__(self, package, variants, solver): + def __init__(self, package: Package, variants: list[PackageVariant], solver: Solver) -> None: self.package = package self.variants = variants self.solver = solver self.sorted = False @property - def version(self): + def version(self) -> Version: return self.package.version - def __len__(self): + def __len__(self) -> int: return len(self.variants) - def split(self, nvariants): + def split(self, nvariants: int) -> tuple[_PackageEntry, _PackageEntry] | None: if nvariants >= len(self.variants): return None @@ -393,7 +405,7 @@ def split(self, nvariants): entry.sorted = next_entry.sorted = True return entry, next_entry - def sort(self): + def sort(self) -> None: """Sort variants from most correct to consume, to least. Sort rules: @@ -420,7 +432,7 @@ def sort(self): if self.sorted: return - def key(variant): + def key(variant: PackageVariant) -> tuple[SupportsLessThan, ...]: requested_key = [] names = set() @@ -461,7 +473,7 @@ def key(variant): class _PackageVariantList(_Common): """A list of package variants, loaded lazily. """ - def __init__(self, package_name, solver): + def __init__(self, package_name: str, solver: Solver) -> None: self.package_name = package_name self.solver = solver @@ -469,7 +481,7 @@ def __init__(self, package_name, solver): # cause package loads (eg, timestamp rules). We only apply filters # during an intersection, which minimises the amount of filtering. # - self.entries = [] + self.entries: list[list[Any]] = [] for package in iter_packages(self.package_name, paths=self.solver.package_paths): @@ -481,7 +493,7 @@ def __init__(self, package_name, solver): "package family not found: %s (searched: %s)" % (package_name, "; ".join(self.solver.package_paths))) - def get_intersection(self, range_): + def get_intersection(self, range_: VersionRange) -> list[_PackageEntry] | None: """Get a list of variants that intersect with the given range. Args: @@ -532,7 +544,7 @@ def get_intersection(self, range_): return result or None - def dump(self): + def dump(self) -> None: print(self.package_name) for package, value in self.entries: @@ -546,7 +558,7 @@ def dump(self): else: print(" %s" % str(package)) - def __str__(self): + def __str__(self) -> str: strs = [] for package, value in self.entries: @@ -565,7 +577,7 @@ def __str__(self): class _PackageVariantSlice(_Common): """A subset of a variant list, but with more dependency-related info.""" - def __init__(self, package_name, entries, solver): + def __init__(self, package_name: str, entries: list[_PackageEntry], solver: Solver) -> None: """ Args: entries (list of `_PackageEntry`): result of @@ -580,24 +592,24 @@ def __init__(self, package_name, entries, solver): self.sorted = False # calculated on demand - self._len = None - self._range = None - self._fam_requires = None - self._common_fams = None + self._len: int | None = None + self._range: VersionRange | None = None + self._fam_requires: set[str] | None = None + self._common_fams: set[str] | None = None @property - def pr(self): + def pr(self) -> _Printer: return self.solver.pr @property - def range_(self): + def range_(self) -> VersionRange: if self._range is None: versions = (x.version for x in self.entries) self._range = VersionRange.from_versions(versions) return self._range @property - def fam_requires(self): + def fam_requires(self) -> set[str]: self._update_fam_info() return self._fam_requires @@ -607,22 +619,22 @@ def common_fams(self): return self._common_fams @property - def extractable(self): + def extractable(self) -> bool: """True if there are possible remaining extractions.""" return not self.extracted_fams.issuperset(self.common_fams) @property - def first_variant(self): + def first_variant(self) -> PackageVariant: entry = self.entries[0] entry.sort() return entry.variants[0] - def iter_variants(self): + def iter_variants(self) -> Iterator[PackageVariant]: for entry in self.entries: for variant in entry.variants: yield variant - def intersect(self, range_): + def intersect(self, range_: VersionRange) -> _PackageVariantSlice | None: self.solver.intersection_broad_tests_count += 1 """Remove variants whose version fall outside of the given range.""" @@ -652,7 +664,7 @@ def intersect(self, range_): self.been_intersected_with.add(range_) return self - def reduce_by(self, package_request): + def reduce_by(self, package_request: Requirement) -> tuple[_PackageVariantSlice | None, list[Reduction]]: """Remove variants whos dependencies conflict with the given package request. @@ -675,14 +687,14 @@ def reduce_by(self, package_request): with self.solver.timed(self.solver.reduction_time): return self._reduce_by(package_request) - def _reduce_by(self, package_request): + def _reduce_by(self, package_request: Requirement) -> tuple[_PackageVariantSlice | None, list[Reduction]]: self.solver.reduction_tests_count += 1 entries = [] reductions = [] conflict_tests = {} - def _conflicts(req_): + def _conflicts(req_: Requirement) -> bool: # cache conflict tests, since variants often share similar requirements req_s = str(req) result = conflict_tests.get(req_s) @@ -727,7 +739,7 @@ def _conflicts(req_): self.been_reduced_by.add(package_request) return (self, []) - def extract(self): + def extract(self) -> tuple[_PackageVariantSlice, Requirement | None]: """Extract a common dependency. Note that conflict dependencies are never extracted, they are always @@ -741,7 +753,7 @@ def extract(self): # the sort is necessary to ensure solves are deterministic fam = sorted(extractable)[0] - last_range = None + last_range: VersionRange | None = None ranges = set() for variant in self.iter_variants(): @@ -758,7 +770,7 @@ def extract(self): common_req = Requirement.construct(fam, range_) return slice_, common_req - def split(self): + def split(self) -> tuple[_PackageVariantSlice, _PackageVariantSlice]: """Split the slice. Returns: @@ -772,7 +784,7 @@ def split(self): # self.sort_versions() - def _split(i_entry, n_variants, common_fams=None): + def _split(i_entry: int, n_variants: int, common_fams: set[str] | None = None) -> tuple[_PackageVariantSlice, _PackageVariantSlice]: # perform a split at a specific point result = self.entries[i_entry].split(n_variants) @@ -812,7 +824,7 @@ def _split(i_entry, n_variants, common_fams=None): return _split(0, 1) # find split point - first variant with no dependency shared with previous - prev = None + prev: tuple[int, int, set[str]] | None = None for i, entry in enumerate(self.entries): # sort the variants. This is done here in order to do the sort as # late as possible, simply to avoid the cost. @@ -832,7 +844,7 @@ def _split(i_entry, n_variants, common_fams=None): "Unexpected solver error: common family(s) still in slice being " "split: slice: %s, family(s): %s" % (self, str(fams))) - def sort_versions(self): + def sort_versions(self) -> None: """Sort entries by version. The order is typically descending, but package order functions can @@ -845,7 +857,7 @@ def sort_versions(self): orderer = get_orderer(self.package_name, orderers=self.solver.package_orderers or {}) - def sort_key(entry): + def sort_key(entry: _PackageEntry) -> SupportsLessThan: return orderer.sort_key(entry.package.name, entry.version) self.entries = sorted(self.entries, key=sort_key, reverse=True) @@ -854,11 +866,11 @@ def sort_key(entry): if self.pr: self.pr("sorted: %s packages: %s", self.package_name, repr(orderer)) - def dump(self): + def dump(self) -> None: print(self.package_name) print('\n'.join(map(str, self.iter_variants()))) - def _copy(self, new_entries): + def _copy(self, new_entries: list[_PackageEntry]) -> _PackageVariantSlice: slice_ = _PackageVariantSlice(package_name=self.package_name, entries=new_entries, solver=self.solver) @@ -868,7 +880,7 @@ def _copy(self, new_entries): slice_.been_intersected_with = self.been_intersected_with.copy() return slice_ - def _update_fam_info(self): + def _update_fam_info(self) -> None: if self._common_fams is not None: return @@ -880,7 +892,7 @@ def _update_fam_info(self): self._fam_requires |= (variant.request_fams | variant.conflict_request_fams) - def __len__(self): + def __len__(self) -> int: if self._len is None: self._len = 0 for entry in self.entries: @@ -888,7 +900,7 @@ def __len__(self): return self._len - def __str__(self): + def __str__(self) -> str: """ foo[2..6(3:4)]* means, 3 versions, 4 variants in 2..6, and at least one family can still be extracted. @@ -907,7 +919,7 @@ def __str__(self): s = "[%s==%s%s]" % (self.package_name, str(variant.version), s_idx) elif nversions == 1: entry = self.entries[0] - indexes = sorted([x.index for x in entry.variants]) + indexes = cast("list[int]", sorted([x.index for x in entry.variants])) s_idx = ','.join(str(x) for x in indexes) verstr = str(entry.version) s = "[%s==%s[%s]]" % (self.package_name, verstr, s_idx) @@ -923,11 +935,11 @@ def __str__(self): class PackageVariantCache(object): - def __init__(self, solver): + def __init__(self, solver: Solver) -> None: self.solver = solver - self.variant_lists = {} # {package-name: _PackageVariantList} + self.variant_lists: dict[str, _PackageVariantList] = {} # {package-name: _PackageVariantList} - def get_variant_slice(self, package_name, range_): + def get_variant_slice(self, package_name: str, range_: VersionRange) -> _PackageVariantSlice | None: """Get a list of variants from the cache. Args: @@ -958,10 +970,9 @@ class _PackageScope(_Common): or a conflict range. As the resolve progresses, package scopes are narrowed down. """ - def __init__(self, package_request, solver): + def __init__(self, package_request: Requirement, solver: Solver) -> None: self.package_name = package_request.name self.solver = solver - self.package_request = None self.variant_slice = None self.pr = solver.pr self.is_ephemeral = (package_request.name.startswith('.')) @@ -978,13 +989,14 @@ def __init__(self, package_request, solver): package_request.range) raise PackageNotFoundError("Package could not be found: %s" % str(req)) + # This call to _update() will set self.package_request self._update() @property - def is_conflict(self): - return self.package_request and self.package_request.conflict + def is_conflict(self) -> bool: + return bool(self.package_request and self.package_request.conflict) - def intersect(self, range_): + def intersect(self, range_: VersionRange) -> _PackageScope | None: """Intersect this scope with a package range. Returns: @@ -1056,7 +1068,7 @@ def intersect(self, range_): # intersection did not change the scope return self - def reduce_by(self, package_request): + def reduce_by(self, package_request: Requirement) -> tuple[_PackageScope | None, list[Reduction]]: """Reduce this scope wrt a package request. Returns: @@ -1071,6 +1083,9 @@ def reduce_by(self, package_request): if self.is_conflict or self.is_ephemeral: return (self, []) + assert self.variant_slice is not None, \ + "variant_slice should always exist for non-conflicted non-ephemeral requests" + # perform the reduction new_slice, reductions = self.variant_slice.reduce_by(package_request) @@ -1099,7 +1114,7 @@ def reduce_by(self, package_request): # there was no reduction return (self, []) - def extract(self): + def extract(self) -> tuple[_PackageScope, Requirement | None]: """Extract a common dependency. Returns: @@ -1123,7 +1138,7 @@ def extract(self): self.pr("extracted %s from %s", package_request, self) return (scope, package_request) - def split(self): + def split(self) -> tuple[_PackageScope, _PackageScope] | None: """Split the scope. Returns: @@ -1147,13 +1162,13 @@ def split(self): next_scope = self._copy(next_slice) return (scope, next_scope) - def _copy(self, new_slice): + def _copy(self, new_slice: _PackageVariantSlice) -> _PackageScope: scope = copy.copy(self) scope.variant_slice = new_slice scope._update() return scope - def _is_solved(self): + def _is_solved(self) -> bool: return ( self.is_conflict or self.is_ephemeral @@ -1163,7 +1178,7 @@ def _is_solved(self): ) ) - def _get_solved_variant(self): + def _get_solved_variant(self) -> PackageVariant | None: if ( self.variant_slice is not None and len(self.variant_slice) == 1 @@ -1173,25 +1188,25 @@ def _get_solved_variant(self): else: return None - def _get_solved_ephemeral(self): + def _get_solved_ephemeral(self) -> Requirement | None: if self.is_ephemeral and not self.is_conflict: return self.package_request else: return None - def _update(self): + def _update(self) -> None: if self.variant_slice is not None: self.package_request = Requirement.construct( self.package_name, self.variant_slice.range_) - def __str__(self): + def __str__(self) -> str: if self.variant_slice is None: return str(self.package_request) else: return str(self.variant_slice) -def _get_dependency_order(g, node_list): +def _get_dependency_order(g: digraph, node_list: list[T]) -> list[T]: """Return list of nodes as close as possible to the ordering in node_list, but with child nodes earlier in the list than parents.""" access_ = accessibility(g) @@ -1230,10 +1245,10 @@ class _ResolvePhase(_Common): If the resolve phase gets to a point where every package scope is solved, then the entire resolve is considered to be solved. """ - def __init__(self, solver): + def __init__(self, solver: Solver) -> None: self.solver = solver - self.failure_reason = None - self.extractions = {} + self.failure_reason: FailureReason | None = None + self.extractions: dict[tuple[str, str], Requirement] = {} self.status = SolverStatus.pending self.scopes = [] @@ -1245,21 +1260,21 @@ def __init__(self, solver): self.changed_scopes_i = set(range(len(self.scopes))) @property - def pr(self): + def pr(self) -> _Printer: return self.solver.pr - def solve(self): + def solve(self) -> _ResolvePhase: """Attempt to solve the phase.""" if self.status != SolverStatus.pending: return self scopes = self.scopes[:] - failure_reason = None - extractions = {} + failure_reason: FailureReason | None = None + extractions: dict[tuple[str, str], Requirement] = {} changed_scopes_i = self.changed_scopes_i.copy() - def _create_phase(status=None): + def _create_phase(status: SolverStatus | None = None) -> _ResolvePhase: phase = copy.copy(self) phase.scopes = scopes phase.failure_reason = failure_reason @@ -1281,7 +1296,7 @@ def _create_phase(status=None): # iteratively extract until no more extractions possible while True: self.pr.subheader("EXTRACTING:") - extracted_requests = [] + extracted_requests_ = [] # perform all possible extractions with self.solver.timed(self.solver.extraction_time): @@ -1290,7 +1305,7 @@ def _create_phase(status=None): scope_, extracted_request = scopes[i].extract() if extracted_request: - extracted_requests.append(extracted_request) + extracted_requests_.append(extracted_request) k = (scopes[i].package_name, extracted_request.name) extractions[k] = extracted_request self.solver.extractions_count += 1 @@ -1298,12 +1313,12 @@ def _create_phase(status=None): else: break - if not extracted_requests: + if not extracted_requests_: break # simplify extractions (there may be overlaps) self.pr.subheader("MERGE-EXTRACTIONS:") - extracted_requests = RequirementList(extracted_requests) + extracted_requests = RequirementList(extracted_requests_) if extracted_requests.conflict: # extractions are in conflict req1, req2 = extracted_requests.conflict @@ -1446,10 +1461,10 @@ def _create_phase(status=None): # A different order here wouldn't cause an invalid solve, however # rez solves must be deterministic, so this is why we sort. # - pending_reducts = sorted(pending_reducts) + pending_reducts_ = sorted(pending_reducts) - while pending_reducts: - x, y = pending_reducts.pop() + while pending_reducts_: + x, y = pending_reducts_.pop() if x == y: continue @@ -1466,13 +1481,13 @@ def _create_phase(status=None): # other scopes need to reduce against x again for j in all_scopes_i: if j != x: - pending_reducts.append((j, x)) + pending_reducts_.append((j, x)) changed_scopes_i = set() return _create_phase() - def finalise(self): + def finalise(self) -> _ResolvePhase: """Remove conflict requests, detect cyclic dependencies, and reorder packages wrt dependency and then request order. @@ -1516,7 +1531,7 @@ def finalise(self): phase.scopes = scopes_ return phase - def split(self): + def split(self) -> tuple[_ResolvePhase, _ResolvePhase]: """Split the phase. When a phase is exhausted, it gets split into a pair of phases to be @@ -1540,7 +1555,7 @@ def split(self): scopes = [] next_scopes = [] - split_i = None + split_i: int | None = None for i, scope in enumerate(self.scopes): if split_i is None: @@ -1572,7 +1587,7 @@ def split(self): next_phase.scopes = next_scopes return (phase, next_phase) - def get_graph(self): + def get_graph(self) -> digraph: """Get the resolve graph. The resolve graph shows what packages were resolved, and the @@ -1597,12 +1612,12 @@ def get_graph(self): node_fontsize = 10 counter = [1] - def _uid(): + def _uid() -> str: id_ = counter[0] counter[0] += 1 return "_%d" % id_ - def _add_edge(id1, id2, arrowsize=0.5): + def _add_edge(id1: str, id2: str, arrowsize: float=0.5) -> tuple[str, str]: e = (id1, id2) if g.has_edge(e): g.del_edge(e) @@ -1610,30 +1625,30 @@ def _add_edge(id1, id2, arrowsize=0.5): g.add_edge_attribute(e, ("arrowsize", str(arrowsize))) return e - def _add_extraction_merge_edge(id1, id2): + def _add_extraction_merge_edge(id1: str, id2: str) -> None: e = _add_edge(id1, id2, 1) g.add_edge_attribute(e, ("arrowhead", "odot")) - def _add_conflict_edge(id1, id2): + def _add_conflict_edge(id1: str, id2: str) -> None: e = _add_edge(id1, id2, 1) g.set_edge_label(e, "CONFLICT") g.add_edge_attribute(e, ("style", "bold")) g.add_edge_attribute(e, ("color", "red")) g.add_edge_attribute(e, ("fontcolor", "red")) - def _add_cycle_edge(id1, id2): + def _add_cycle_edge(id1: str, id2: str) -> None: e = _add_edge(id1, id2, 1) g.set_edge_label(e, "CYCLE") g.add_edge_attribute(e, ("style", "bold")) g.add_edge_attribute(e, ("color", "red")) g.add_edge_attribute(e, ("fontcolor", "red")) - def _add_reduct_edge(id1, id2, label): + def _add_reduct_edge(id1: str, id2: str, label: str) -> None: e = _add_edge(id1, id2, 1) g.set_edge_label(e, label) g.add_edge_attribute(e, ("fontsize", node_fontsize)) - def _add_node(label, color, style): + def _add_node(label: str, color: str, style: str) -> str: attrs = [("label", label), ("fontsize", node_fontsize), ("fillcolor", color), @@ -1642,7 +1657,7 @@ def _add_node(label, color, style): g.add_node(id_, attrs=attrs) return id_ - def _add_request_node(request, initial_request=False): + def _add_request_node(request: Requirement, initial_request: bool = False) -> str: id_ = request_nodes.get(request) if id_ is not None: return id_ @@ -1657,7 +1672,7 @@ def _add_request_node(request, initial_request=False): request_nodes[request] = id_ return id_ - def _add_scope_node(scope): + def _add_scope_node(scope: _PackageScope) -> str: id_ = scope_nodes.get(scope.package_name) if id_ is not None: return id_ @@ -1681,7 +1696,7 @@ def _add_scope_node(scope): scope_requests[id_] = scope.package_request return id_ - def _add_reduct_node(request): + def _add_reduct_node(request: Requirement) -> str: return _add_node(str(request), node_color, "filled,dashed") # -- generate the graph @@ -1749,7 +1764,7 @@ def _add_reduct_node(request): for conflict in fr.conflicts: conflicting_request = conflict.conflicting_request scope_n = scope_nodes.get(conflicting_request.name) - scope_r = scope_requests.get(scope_n) + scope_r = scope_requests.get(scope_n) if scope_n is not None else None if scope_n is not None \ and scope_r is not None \ @@ -1807,7 +1822,7 @@ def _add_reduct_node(request): if not g.neighbors(id1): # leaf node id2 = scope_nodes.get(request.name) if id2 is not None: - scope = scopes.get(request.name) + scope = scopes[request.name] if not request.conflicts_with(scope.package_request): _add_edge(id1, id2) @@ -1825,7 +1840,7 @@ def _add_reduct_node(request): return g - def _get_minimal_graph(self): + def _get_minimal_graph(self) -> digraph | None: if not self._is_solved(): return None @@ -1852,13 +1867,13 @@ def _get_minimal_graph(self): return g - def _is_solved(self): + def _is_solved(self) -> bool: for scope in self.scopes: if not scope._is_solved(): return False return True - def _get_solved_variants(self): + def _get_solved_variants(self) -> list[PackageVariant]: variants = [] for scope in self.scopes: variant = scope._get_solved_variant() @@ -1867,7 +1882,7 @@ def _get_solved_variants(self): return variants - def _get_solved_ephemerals(self): + def _get_solved_ephemerals(self) -> list[Requirement]: ephemerals = [] for scope in self.scopes: ephemeral = scope._get_solved_ephemeral() @@ -1876,7 +1891,7 @@ def _get_solved_ephemerals(self): return ephemerals - def __str__(self): + def __str__(self) -> str: return ' '.join(str(x) for x in self.scopes) @@ -1889,11 +1904,21 @@ class Solver(_Common): """ max_verbosity = 3 - def __init__(self, package_requests, package_paths, context=None, - package_filter=None, package_orderers=None, callback=None, - building=False, optimised=True, verbosity=0, buf=None, - package_load_callback=None, prune_unfailed=True, - suppress_passive=False, print_stats=False): + def __init__(self, + package_requests: list[Requirement], + package_paths: list[str], + context: ResolvedContext | None = None, + package_filter: PackageFilterBase | None = None, + package_orderers: PackageOrderList | None = None, + callback: Callable[[SolverState], tuple[SolverCallbackReturn, str]] | None = None, + building: bool = False, + optimised: bool = True, + verbosity: int = 0, + buf: SupportsWrite | None = None, + package_load_callback: Callable[[Package], Any] | None = None, + prune_unfailed: bool = True, + suppress_passive: bool = False, + print_stats: bool = False) -> None: """Create a Solver. Args: @@ -1934,7 +1959,6 @@ def __init__(self, package_requests, package_paths, context=None, self.prune_unfailed = prune_unfailed self.package_load_callback = package_load_callback self.building = building - self.request_list = None self.context = context self.pr = _Printer(verbosity, buf=buf, suppress_passive=suppress_passive) @@ -1946,14 +1970,16 @@ def __init__(self, package_requests, package_paths, context=None, else: self.optimised = optimised - self.phase_stack = None - self.failed_phase_list = None - self.abort_reason = None - self.callback_return = None - self.depth_counts = None - self.solve_begun = None - self.solve_time = None - self.load_time = None + # these values are all set in _init() + self.phase_stack: list[_ResolvePhase] + self.failed_phase_list: list[_ResolvePhase] + self.depth_counts: dict + self.solve_begun: bool + self.solve_time: float + self.load_time: float + + self.abort_reason: str | None = None + self.callback_return: SolverCallbackReturn | None = None # advanced solve metrics self.solve_count = 0 @@ -2000,14 +2026,14 @@ def __init__(self, package_requests, package_paths, context=None, self._push_phase(phase) @contextmanager - def timed(self, target): + def timed(self, target: list[float]) -> Generator: t = time.time() yield secs = time.time() - t target[0] += secs @property - def status(self): + def status(self) -> SolverStatus: """Return the current status of the solve. Returns: @@ -2035,12 +2061,12 @@ def status(self): return st @property - def num_solves(self): + def num_solves(self) -> int: """Return the number of solve steps that have been executed.""" return self.solve_count @property - def num_fails(self): + def num_fails(self) -> int: """Return the number of failed solve steps that have been executed. Note that num_solves is inclusive of failures.""" n = len(self.failed_phase_list) @@ -2049,12 +2075,12 @@ def num_fails(self): return n @property - def cyclic_fail(self): + def cyclic_fail(self) -> bool: """Return True if the solve failed due to a cycle, False otherwise.""" return (self.phase_stack[-1].status == SolverStatus.cyclic) @property - def resolved_packages(self): + def resolved_packages(self) -> list[PackageVariant] | None: """Return a list of resolved variants. Returns: @@ -2068,7 +2094,7 @@ def resolved_packages(self): return final_phase._get_solved_variants() @property - def resolved_ephemerals(self): + def resolved_ephemerals(self) -> list[Requirement] | None: """Return the list of final ephemeral package ranges. Note that conflict ephemerals are not included. @@ -2083,15 +2109,15 @@ def resolved_ephemerals(self): final_phase = self.phase_stack[-1] return final_phase._get_solved_ephemerals() - def reset(self): + def reset(self) -> None: """Reset the solver, removing any current solve.""" if not self.request_list.conflict: - phase = _ResolvePhase(self.request_list.requirements, solver=self) + phase = _ResolvePhase(solver=self) self.pr("resetting...") self._init() self._push_phase(phase) - def solve(self): + def solve(self) -> None: """Attempt to solve the request. """ if self.solve_begun: @@ -2122,7 +2148,7 @@ def solve(self): print(pformat(data), file=(self.buf or sys.stdout)) @property - def solve_stats(self): + def solve_stats(self) -> dict[str, dict[str, Any]]: extraction_stats = { "extraction_time": self.extraction_time[0], "num_extractions": self.extractions_count @@ -2158,7 +2184,7 @@ def solve_stats(self): "reductions": reduction_stats } - def solve_step(self): + def solve_step(self) -> None: """Perform a single solve step. """ self.solve_begun = True @@ -2209,7 +2235,7 @@ def solve_step(self): assert new_phase.status == SolverStatus.exhausted self._push_phase(new_phase) - def failure_reason(self, failure_index=None): + def failure_reason(self, failure_index: int | None = None) -> FailureReason | None: """Get the reason for a failure. Args: @@ -2228,7 +2254,7 @@ def failure_reason(self, failure_index=None): phase, _ = self._get_failed_phase(failure_index) return phase.failure_reason - def failure_description(self, failure_index=None): + def failure_description(self, failure_index: int | None = None) -> str: """Get a description of the failure. This differs from `failure_reason` - in some cases, such as when a @@ -2238,7 +2264,7 @@ def failure_description(self, failure_index=None): _, description = self._get_failed_phase(failure_index) return description - def failure_packages(self, failure_index=None): + def failure_packages(self, failure_index: int | None = None) -> list[Requirement] | None: """Get packages involved in a failure. Args: @@ -2251,7 +2277,7 @@ def failure_packages(self, failure_index=None): fr = phase.failure_reason return fr.involved_requirements() if fr else None - def get_graph(self): + def get_graph(self) -> digraph: """Returns the most recent solve graph. This gives a graph showing the latest state of the solve. The specific @@ -2271,7 +2297,7 @@ def get_graph(self): else: return self.get_fail_graph() - def get_fail_graph(self, failure_index=None): + def get_fail_graph(self, failure_index: int | None = None) -> digraph: """Returns a graph showing a solve failure. Args: @@ -2283,7 +2309,7 @@ def get_fail_graph(self, failure_index=None): phase, _ = self._get_failed_phase(failure_index) return phase.get_graph() - def dump(self): + def dump(self) -> None: """Print a formatted summary of the current solve state.""" from rez.utils.formatting import columnise @@ -2291,7 +2317,7 @@ def dump(self): for i, phase in enumerate(self.phase_stack): rows.append((self._depth_label(i), phase.status, str(phase))) - print("status: %s (%s)" % (self.status.name, self.status.description)) + print("status: %s (%s)" % (self.status.name, self.status.value[0])) print("initial request: %s" % str(self.request_list)) print() print("solve stack:") @@ -2305,7 +2331,7 @@ def dump(self): print("previous failures:") print('\n'.join(columnise(rows))) - def _init(self): + def _init(self) -> None: self.phase_stack = [] self.failed_phase_list = [] self.depth_counts = {} @@ -2329,7 +2355,7 @@ def _init(self): self.reduction_time = [0.0] self.reduction_test_time = [0.0] - def _latest_nonfailed_phase(self): + def _latest_nonfailed_phase(self) -> _ResolvePhase | None: if self.status == SolverStatus.failed: return None @@ -2338,7 +2364,7 @@ def _latest_nonfailed_phase(self): return phase assert False # should never get here - def _do_callback(self): + def _do_callback(self) -> bool: keep_going = True if self.callback: phase = self._latest_nonfailed_phase() @@ -2358,13 +2384,13 @@ def _do_callback(self): return keep_going - def _get_variant_slice(self, package_name, range_): + def _get_variant_slice(self, package_name: str, range_: VersionRange) -> _PackageVariantSlice | None: slice_ = self.package_cache.get_variant_slice( package_name=package_name, range_=range_) return slice_ - def _push_phase(self, phase): + def _push_phase(self, phase: _ResolvePhase) -> None: depth = len(self.phase_stack) count = self.depth_counts.get(depth, -1) + 1 self.depth_counts[depth] = count @@ -2374,14 +2400,14 @@ def _push_phase(self, phase): dlabel = self._depth_label() self.pr("pushed %s: %s", dlabel, phase) - def _pop_phase(self): + def _pop_phase(self) -> _ResolvePhase: dlabel = self._depth_label() phase = self.phase_stack.pop() if self.pr: self.pr("popped %s: %s", dlabel, phase) return phase - def _get_failed_phase(self, index=None): + def _get_failed_phase(self, index: int | None = None) -> tuple[_ResolvePhase, str]: # returns (phase, fail_description) prepend_abort_reason = False fails = self.failed_phase_list @@ -2412,19 +2438,19 @@ def _get_failed_phase(self, index=None): return phase, fail_description - def _depth_label(self, depth=None): + def _depth_label(self, depth: int | None = None) -> str: if depth is None: depth = len(self.phase_stack) - 1 count = self.depth_counts[depth] return "{%d,%d}" % (depth, count) - def __str__(self): + def __str__(self) -> str: return "%s %s %s" % (self.status, self._depth_label(), str(self.phase_stack[-1])) -def _short_req_str(package_request): +def _short_req_str(package_request: Requirement) -> str: """print shortened version of '==X|==Y|==Z' ranged requests.""" if not package_request.conflict: versions = package_request.range.to_versions() diff --git a/src/rez/status.py b/src/rez/status.py index 5be0b9d76..5acfe4c55 100644 --- a/src/rez/status.py +++ b/src/rez/status.py @@ -2,11 +2,14 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + import sys import os import os.path from fnmatch import fnmatch from rez import __version__ +from rez.utils.typing import SupportsWrite from rez.utils.data_utils import cached_property from rez.resolved_context import ResolvedContext from rez.packages import iter_packages, Package @@ -23,11 +26,11 @@ class Status(object): The current status tells you things such as if you are within a context, or if suite(s) are visible on $PATH. """ - def __init__(self): + def __init__(self) -> None: pass @cached_property - def context_file(self): + def context_file(self) -> str | None: """Get path to the current context file. Returns: @@ -36,7 +39,7 @@ def context_file(self): return os.getenv("REZ_RXT_FILE") @cached_property - def context(self): + def context(self) -> ResolvedContext | None: """Get the current context. Returns: @@ -46,7 +49,7 @@ def context(self): return ResolvedContext.load(path) if path else None @cached_property - def suites(self): + def suites(self) -> list[Suite]: """Get currently visible suites. Visible suites are those whos bin path appea on $PATH. @@ -57,7 +60,7 @@ def suites(self): return Suite.load_visible_suites() @cached_property - def parent_suite(self): + def parent_suite(self) -> Suite | None: """Get the current parent suite. A parent suite exists when a context within a suite is active. That is, @@ -74,7 +77,7 @@ def parent_suite(self): # TODO: store this info in env-var instead, remove suite info from context. @cached_property - def active_suite_context_name(self): + def active_suite_context_name(self) -> str | None: """Get the name of the currently active context in a parent suite. If a parent suite exists, then an active context exists - this is the @@ -88,7 +91,7 @@ def active_suite_context_name(self): return self.context.suite_context_name return None - def print_info(self, obj=None, buf=sys.stdout): + def print_info(self, obj=None, buf: SupportsWrite = sys.stdout) -> bool: """Print a status message about the given object. If an object is not provided, status info is shown about the current @@ -121,7 +124,7 @@ def print_info(self, obj=None, buf=sys.stdout): print("Rez does not know what '%s' is" % obj, file=buf) return b - def print_tools(self, pattern=None, buf=sys.stdout): + def print_tools(self, pattern: str | None = None, buf: SupportsWrite = sys.stdout) -> bool: """Print a list of visible tools. Args: @@ -147,7 +150,7 @@ def print_tools(self, pattern=None, buf=sys.stdout): label = '' color = None - rows.append([tool, '-', pkg_str, "active context", label, color]) + rows.append((tool, '-', pkg_str, "active context", label, color)) seen.add(tool) for suite in self.suites: @@ -261,7 +264,7 @@ def _load_wrapper(filepath): return False - def _print_package_info(self, value, buf=sys.stdout, b=False): + def _print_package_info(self, value, buf=sys.stdout, b: bool = False) -> bool: word = "is also" if b else "is" _pr = Printer(buf) @@ -269,7 +272,7 @@ def _print_package_info(self, value, buf=sys.stdout, b=False): if request_str != value: return False - def _print_package(package): + def _print_package(package) -> None: if isinstance(package, Package): name = package.qualified_name else: @@ -314,7 +317,7 @@ def _print_package(package): return False - def _print_suite_info(self, value, buf=sys.stdout, b=False): + def _print_suite_info(self, value, buf=sys.stdout, b: bool = False) -> bool: word = "is also" if b else "is" _pr = Printer(buf) @@ -330,7 +333,7 @@ def _print_suite_info(self, value, buf=sys.stdout, b=False): _pr("'%s' %s a suite. Use 'rez-suite' for more information." % (path, word)) return True - def _print_context_info(self, value, buf=sys.stdout, b=False): + def _print_context_info(self, value, buf=sys.stdout, b: bool = False) -> bool: word = "is also" if b else "is" _pr = Printer(buf) @@ -346,7 +349,7 @@ def _print_context_info(self, value, buf=sys.stdout, b=False): _pr("'%s' %s a context. Use 'rez-context' for more information." % (path, word)) return True - def _print_info(self, buf=sys.stdout): + def _print_info(self, buf=sys.stdout) -> None: lines = ["Using Rez v%s" % __version__] if self.context: if self.context.load_path: diff --git a/src/rez/suite.py b/src/rez/suite.py index bf9415070..4b5056797 100644 --- a/src/rez/suite.py +++ b/src/rez/suite.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.utils.execution import create_forwarding_script from rez.exceptions import SuiteError, ResolvedContextError from rez.resolved_context import ResolvedContext @@ -12,12 +14,40 @@ from rez.vendor.yaml.error import YAMLError from rez.utils.yaml import dump_yaml from collections import defaultdict +from typing import cast, Callable, TYPE_CHECKING, Any, NoReturn import os import os.path import shutil import sys +if TYPE_CHECKING: + from rez.packages import Variant + from typing import TypedDict + + # FIXME: move this out of TYPE_CHECKING block when python 3.7 support is dropped + class Tool(TypedDict): + tool_name: str + tool_alias: str + context_name: str + variant: Variant | set[Variant] + + class Context(TypedDict, total=False): + name: str + context: ResolvedContext + tool_aliases: dict[str, str] + hidden_tools: set[str] + priority: int + prefix_char: str | None + loaded: bool + prefix: str + suffix: str + +else: + Tool = dict + Context = dict + + class Suite(object): """A collection of contexts. @@ -40,18 +70,18 @@ class Suite(object): - Explicitly alias a tool using the `alias_tool` method. This takes precedence over context prefix/suffixing. """ - def __init__(self): + def __init__(self) -> None: """Create a suite.""" - self.load_path = None - self.contexts = {} + self.load_path: str | None = None + self.contexts: dict[str, Context] = {} self.next_priority = 1 - self.tools = None - self.tool_conflicts = None - self.hidden_tools = None + self.tools: dict[str, Tool] | None = None + self.tool_conflicts: defaultdict[str, list[Tool]] | None = None + self.hidden_tools: list[Tool] | None = None @property - def context_names(self): + def context_names(self) -> list[str]: """Get the names of the contexts in the suite. Reurns: @@ -81,7 +111,7 @@ def activation_shell_code(self, shell=None): executor.env.PATH.append(self.tools_path) return executor.get_output().strip() - def __str__(self): + def __str__(self) -> str: return "%s(%s)" % (self.__class__.__name__, " ".join(self.context_names)) def context(self, name): @@ -105,7 +135,7 @@ def context(self, name): data["loaded"] = True return context - def add_context(self, name, context, prefix_char=None): + def add_context(self, name: str, context: ResolvedContext, prefix_char=None): """Add a context to the suite. Args: @@ -117,12 +147,12 @@ def add_context(self, name, context, prefix_char=None): if not context.success: raise SuiteError("Context is not resolved: %r" % name) - self.contexts[name] = dict(name=name, - context=context.copy(), - tool_aliases={}, - hidden_tools=set(), - priority=self._next_priority, - prefix_char=prefix_char) + self.contexts[name] = Context(name=name, + context=context.copy(), + tool_aliases={}, + hidden_tools=set(), + priority=self._next_priority, + prefix_char=prefix_char) self._flush_tools() def find_contexts(self, in_request=None, in_resolve=None): @@ -141,7 +171,7 @@ def find_contexts(self, in_request=None, in_resolve=None): """ names = self.context_names if in_request: - def _in_request(name): + def _in_request(name) -> bool: context = self.context(name) packages = set(x.name for x in context.requested_packages(True)) return (in_request in packages) @@ -167,7 +197,7 @@ def _in_resolve(name): names = [x for x in names if _in_resolve(x)] return names - def remove_context(self, name): + def remove_context(self, name: str) -> None: """Remove a context from the suite. Args: @@ -177,7 +207,7 @@ def remove_context(self, name): del self.contexts[name] self._flush_tools() - def set_context_prefix(self, name, prefix): + def set_context_prefix(self, name, prefix) -> None: """Set a context's prefix. This will be applied to all wrappers for the tools in this context. For @@ -192,7 +222,7 @@ def set_context_prefix(self, name, prefix): data["prefix"] = prefix self._flush_tools() - def remove_context_prefix(self, name): + def remove_context_prefix(self, name) -> None: """Remove a context's prefix. Args: @@ -200,7 +230,7 @@ def remove_context_prefix(self, name): """ self.set_context_prefix(name, "") - def set_context_suffix(self, name, suffix): + def set_context_suffix(self, name, suffix) -> None: """Set a context's suffix. This will be applied to all wrappers for the tools in this context. For @@ -215,7 +245,7 @@ def set_context_suffix(self, name, suffix): data["suffix"] = suffix self._flush_tools() - def remove_context_suffix(self, name): + def remove_context_suffix(self, name) -> None: """Remove a context's suffix. Args: @@ -223,13 +253,13 @@ def remove_context_suffix(self, name): """ self.set_context_suffix(name, "") - def bump_context(self, name): + def bump_context(self, name) -> None: """Causes the context's tools to take priority over all others.""" data = self._context(name) data["priority"] = self._next_priority self._flush_tools() - def hide_tool(self, context_name, tool_name): + def hide_tool(self, context_name, tool_name) -> None: """Hide a tool so that it is not exposed in the suite. Args: @@ -243,7 +273,7 @@ def hide_tool(self, context_name, tool_name): hidden_tools.add(tool_name) self._flush_tools() - def unhide_tool(self, context_name, tool_name): + def unhide_tool(self, context_name, tool_name) -> None: """Unhide a tool so that it may be exposed in a suite. Note that unhiding a tool doesn't guarantee it can be seen - a tool of @@ -278,7 +308,7 @@ def alias_tool(self, context_name, tool_name, tool_alias): aliases[tool_name] = tool_alias self._flush_tools() - def unalias_tool(self, context_name, tool_name): + def unalias_tool(self, context_name, tool_name) -> None: """Deregister an alias for a specific tool. Args: @@ -327,7 +357,7 @@ def get_tool_filepath(self, tool_alias): else: return None - def get_tool_context(self, tool_alias): + def get_tool_context(self, tool_alias: str) -> str | None: """Given a visible tool alias, return the name of the context it belongs to. @@ -344,7 +374,7 @@ def get_tool_context(self, tool_alias): return data["context_name"] return None - def get_hidden_tools(self): + def get_hidden_tools(self) -> list[Tool]: """Get the tools hidden in this suite. Hidden tools are those that have been explicitly hidden via `hide_tool`. @@ -358,18 +388,20 @@ def get_hidden_tools(self): - variant (`Variant`): Variant providing the tool. """ self._update_tools() + assert self.hidden_tools is not None return self.hidden_tools - def get_conflicting_aliases(self): + def get_conflicting_aliases(self) -> list[str]: """Get a list of tool aliases that have one or more conflicts. Returns: List of strings. """ self._update_tools() + assert self.tool_conflicts is not None return list(self.tool_conflicts.keys()) - def get_alias_conflicts(self, tool_alias): + def get_alias_conflicts(self, tool_alias: str) -> list[Tool] | None: """Get a list of conflicts on the given tool alias. Args: @@ -385,7 +417,7 @@ def get_alias_conflicts(self, tool_alias): self._update_tools() return self.tool_conflicts.get(tool_alias) - def validate(self): + def validate(self) -> None: """Validate the suite.""" for context_name in self.context_names: context = self.context(context_name) @@ -398,7 +430,7 @@ def validate(self): def to_dict(self): contexts_ = {} for k, data in self.contexts.items(): - data_ = data.copy() + data_ = cast(dict[str, Any], data.copy()) if "context" in data_: del data_["context"] if "loaded" in data_: @@ -408,7 +440,7 @@ def to_dict(self): return dict(contexts=contexts_) @classmethod - def from_dict(cls, d): + def from_dict(cls, d) -> Suite: s = Suite.__new__(Suite) s.load_path = None s.tools = None @@ -421,7 +453,7 @@ def from_dict(cls, d): s.next_priority = 1 return s - def save(self, path, verbose=False): + def save(self, path, verbose: bool = False): """Save the suite to disk. Args: @@ -485,7 +517,7 @@ def save(self, path, verbose=False): prefix_char=prefix_char) @classmethod - def load(cls, path): + def load(cls, path: str) -> Suite: if not os.path.exists(path): open(path) # raise IOError filepath = os.path.join(path, "suite.yaml") @@ -503,7 +535,7 @@ def load(cls, path): return s @classmethod - def visible_suite_paths(cls, paths=None): + def visible_suite_paths(cls, paths: list[str] | None = None): """Get a list of paths to suites that are visible on $PATH. Returns: @@ -521,7 +553,7 @@ def visible_suite_paths(cls, paths=None): return suite_paths @classmethod - def load_visible_suites(cls, paths=None): + def load_visible_suites(cls, paths: list[str] | None = None) -> list[Suite]: """Get a list of suites whos bin paths are visible on $PATH. Returns: @@ -531,7 +563,7 @@ def load_visible_suites(cls, paths=None): suites = [cls.load(x) for x in suite_paths] return suites - def print_info(self, buf=sys.stdout, verbose=False): + def print_info(self, buf=sys.stdout, verbose: bool = False) -> None: """Prints a message summarising the contents of the suite.""" _pr = Printer(buf) @@ -555,8 +587,8 @@ def print_info(self, buf=sys.stdout, verbose=False): context_variants[context_name].add(str(entry["variant"])) _pr() - rows = [["NAME", "VISIBLE TOOLS", "PATH"], - ["----", "-------------", "----"]] + rows = [("NAME", "VISIBLE TOOLS", "PATH"), + ("----", "-------------", "----")] for context_name in context_names: context_path = self._context_path(context_name) or '-' @@ -570,7 +602,7 @@ def print_info(self, buf=sys.stdout, verbose=False): _pr("\n".join(columnise(rows))) - def print_tools(self, buf=sys.stdout, verbose=False, context_name=None): + def print_tools(self, buf=sys.stdout, verbose: bool = False, context_name=None) -> None: """Print table of tools available in the suite. Args: @@ -666,34 +698,34 @@ def _get_row(entry): else: _pr("No tools available.") - def _context(self, name): + def _context(self, name: str) -> Context: data = self.contexts.get(name) if not data: raise SuiteError("No such context: %r" % name) return data - def _context_path(self, name, suite_path=None): + def _context_path(self, name: str, suite_path=None): suite_path = suite_path or self.load_path if not suite_path: return None filepath = os.path.join(suite_path, "contexts", "%s.rxt" % name) return filepath - def _sorted_contexts(self): + def _sorted_contexts(self) -> list[Context]: return sorted(self.contexts.values(), key=lambda x: x["priority"]) @property - def _next_priority(self): + def _next_priority(self) -> int: p = self.next_priority self.next_priority += 1 return p - def _flush_tools(self): + def _flush_tools(self) -> None: self.tools = None self.tool_conflicts = None self.hidden_tools = None - def _validate_tool(self, context_name, tool_name): + def _validate_tool(self, context_name: str, tool_name: str) -> None: context = self.context(context_name) context_tools = context.get_tools(request_only=True) for _, tool_names in context_tools.values(): @@ -702,7 +734,7 @@ def _validate_tool(self, context_name, tool_name): raise SuiteError("No such tool %r in context %r" % (tool_name, context_name)) - def _update_tools(self): + def _update_tools(self) -> None: if self.tools is not None: return self.tools = {} @@ -725,7 +757,7 @@ def _update_tools(self): if alias is None: alias = "%s%s%s" % (prefix, tool_name, suffix) - entry = dict(tool_name=tool_name, + entry = Tool(tool_name=tool_name, tool_alias=alias, context_name=context_name, variant=variant) @@ -751,8 +783,8 @@ def _update_tools(self): self.tools[alias] = entry -def _FWD__invoke_suite_tool_alias(context_name, tool_name, prefix_char=None, - _script=None, _cli_args=None): +def _FWD__invoke_suite_tool_alias(context_name: str, tool_name: str, prefix_char=None, + _script=None, _cli_args=None) -> NoReturn: suite_path = os.path.dirname(os.path.dirname(_script)) path = os.path.join(suite_path, "contexts", "%s.rxt" % context_name) context = ResolvedContext.load(path) diff --git a/src/rez/system.py b/src/rez/system.py index 2fac207d7..310f4b803 100644 --- a/src/rez/system.py +++ b/src/rez/system.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + import os import os.path import re @@ -61,7 +63,7 @@ def variant(self): # TODO: move shell detection into shell plugins @cached_property - def shell(self): + def shell(self) -> str: """Get the current shell. Returns: @@ -263,7 +265,7 @@ def get_summary_string(self): txt += "\n\n%s" % plugin_manager.get_summary_string() return txt - def clear_caches(self, hard=False): + def clear_caches(self, hard: bool = False) -> None: """Clear all caches in Rez. Rez caches package contents and iteration during a python session. Thus diff --git a/src/rez/tests/test_bind.py b/src/rez/tests/test_bind.py index b457412da..696a3f94b 100644 --- a/src/rez/tests/test_bind.py +++ b/src/rez/tests/test_bind.py @@ -12,7 +12,7 @@ class TestPackageBind(TestBase): - def test_get_bind_modules(self): + def test_get_bind_modules(self) -> None: """Test get_bind_modules returns the expected modules""" self.assertEqual( sorted(package_bind.get_bind_modules().keys()), @@ -34,7 +34,7 @@ def test_get_bind_modules(self): ] ) - def test_os_module_override(self): + def test_os_module_override(self) -> None: """Test that bind_module_path can override built-in bind modules""" self.update_settings({ "bind_module_path": [self.data_path("bind")] diff --git a/src/rez/tests/test_build.py b/src/rez/tests/test_build.py index 957c3b22e..c6f4891a4 100644 --- a/src/rez/tests/test_build.py +++ b/src/rez/tests/test_build.py @@ -21,7 +21,7 @@ class TestBuild(TestBase, TempdirMixin): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: TempdirMixin.setUpClass() packages_path = cls.data_path("builds", "packages") @@ -42,7 +42,7 @@ def setUpClass(cls): implicit_packages=[]) @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: TempdirMixin.tearDownClass() @classmethod @@ -56,7 +56,7 @@ def _create_builder(cls, working_dir): def _create_context(cls, *pkgs): return ResolvedContext(pkgs) - def _test_build(self, name, version=None): + def _test_build(self, name, version=None) -> None: # create the builder working_dir = os.path.join(self.src_root, name) if version: @@ -71,17 +71,17 @@ def _test_build(self, name, version=None): builder.build(install_path=self.install_root, install=True, clean=True) builder.build(install_path=self.install_root, install=True) - def _test_build_build_util(self): + def _test_build_build_util(self) -> None: """Build, install, test the build_util package.""" self._test_build("build_util", "1") self._create_context("build_util==1") - def _test_build_floob(self): + def _test_build_floob(self) -> None: """Build, install, test the floob package.""" self._test_build("floob") self._create_context("floob==1.2.0") - def _test_build_foo(self): + def _test_build_foo(self) -> None: """Build, install, test the foo package.""" self._test_build("foo", "1.0.0") self._create_context("foo==1.0.0") @@ -96,25 +96,25 @@ def _test_build_foo(self): # test that include modules are working self.assertEqual(environ.get("EEK"), "2") - def _test_build_loco(self): + def _test_build_loco(self) -> None: """Test that a package with conflicting requirements fails correctly. """ working_dir = os.path.join(self.src_root, "loco", "3") builder = self._create_builder(working_dir) self.assertRaises(BuildContextResolveError, builder.build, clean=True) - def _test_build_bah(self): + def _test_build_bah(self) -> None: """Build, install, test the bah package.""" self._test_build("bah", "2.1") self._create_context("bah==2.1", "foo==1.0.0") self._create_context("bah==2.1", "foo==1.1.0") - def _test_build_anti(self): + def _test_build_anti(self) -> None: """Build, install, test the anti package.""" self._test_build("anti", "1.0.0") self._create_context("anti==1.0.0") - def _test_build_translate_lib(self): + def _test_build_translate_lib(self) -> None: """Build, install, test the translate_lib package.""" self._test_build("translate_lib", "2.2.0") context = self._create_context("translate_lib==2.2.0") @@ -126,7 +126,7 @@ def _test_build_translate_lib(self): # is testing spaces in symlinks per issue #553 self.assertTrue(find_file_in_path('a spaced document', os.path.join(root, 'docs'))) - def _test_build_sup_world(self): + def _test_build_sup_world(self) -> None: """Build, install, test the sup_world package.""" from subprocess import PIPE self._test_build("sup_world", "3.8") @@ -137,7 +137,7 @@ def _test_build_sup_world(self): @per_available_shell() @install_dependent() - def test_build_whack(self, shell): + def test_build_whack(self, shell) -> None: """Test that a broken build fails correctly. """ config.override("default_shell", shell) @@ -149,7 +149,7 @@ def test_build_whack(self, shell): @per_available_shell() @install_dependent() - def test_builds(self, shell): + def test_builds(self, shell) -> None: """Test an interdependent set of builds. """ config.override("default_shell", shell) @@ -163,7 +163,7 @@ def test_builds(self, shell): @per_available_shell() @install_dependent() - def test_builds_anti(self, shell): + def test_builds_anti(self, shell) -> None: """Test we can build packages that contain anti packages """ config.override("default_shell", shell) @@ -175,7 +175,7 @@ def test_builds_anti(self, shell): @program_dependent("cmake") @install_dependent() - def test_build_cmake(self): + def test_build_cmake(self) -> None: """Test a cmake-based package.""" if platform_.name == "windows": self.skipTest("This test does not run on Windows due to temporary" @@ -189,7 +189,7 @@ def test_build_cmake(self): @unittest.skipIf(platform_.name == "windows", "Skipping because make and GCC are not common on Windows") @program_dependent("make", "g++") - def test_build_custom(self): + def test_build_custom(self) -> None: """Test a make-based package that uses the custom_build attribute.""" from subprocess import PIPE diff --git a/src/rez/tests/test_cli.py b/src/rez/tests/test_cli.py index 2533808e8..d57a4b060 100644 --- a/src/rez/tests/test_cli.py +++ b/src/rez/tests/test_cli.py @@ -15,7 +15,7 @@ class TestImports(TestBase): - def test_1(self): + def test_1(self) -> None: """run -h option on every cli tool""" # skip if cli not available diff --git a/src/rez/tests/test_commands.py b/src/rez/tests/test_commands.py index c1f790fc5..c7522ae05 100644 --- a/src/rez/tests/test_commands.py +++ b/src/rez/tests/test_commands.py @@ -27,7 +27,7 @@ def get_packages_path(cls): return canonical_path(cls.data_path("commands", "packages")) @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: cls.settings = dict( packages_path=[cls.get_packages_path()], package_filter=None, @@ -37,11 +37,11 @@ def setUpClass(cls): implicit_packages=[], rez_1_environment_variables=False) - def __init__(self, fn): + def __init__(self, fn) -> None: TestBase.__init__(self, fn) self.packages_path = self.get_packages_path() - def _test_package(self, pkg, env, expected_commands): + def _test_package(self, pkg, env, expected_commands) -> None: orig_environ = os.environ.copy() r = ResolvedContext([str(pkg)], caching=False) @@ -91,7 +91,7 @@ def _get_rextest_commands(self, pkg): Alias('rextest', 'foobar')] return cmds - def _test_rextest_package(self, version): + def _test_rextest_package(self, version) -> None: pkg = VersionedObject("rextest-%s" % version) cmds = [Setenv('REZ_USED_REQUEST', str(pkg)), @@ -102,13 +102,13 @@ def _test_rextest_package(self, version): # first prepend should still override self._test_package(pkg, {"REXTEST_DIRS": "TEST"}, cmds) - def test_old_yaml_raises(self): + def test_old_yaml_raises(self) -> None: """Resolve a yaml-based package with old-style bash commands.""" self.update_settings({"disable_rez_1_compatibility": True, "warn_old_commands": False}) with self.assertRaises(SchemaError): self._test_rextest_package("1.1") - def test_old_yaml_compatibility_enabled(self): + def test_old_yaml_compatibility_enabled(self) -> None: """Resolve a yaml-based package with old-style bash commands.""" self.update_settings({"disable_rez_1_compatibility": False, "warn_old_commands": True}) with self.assertLogs(logger=logging.getLogger("rez.utils.logging_"), level=logging.WARNING): @@ -117,15 +117,15 @@ def test_old_yaml_compatibility_enabled(self): self.update_settings({"disable_rez_1_compatibility": False, "warn_old_commands": False}) self._test_rextest_package("1.1") - def test_new_yaml(self): + def test_new_yaml(self) -> None: """Resolve a yaml-based package with new rex commands.""" self._test_rextest_package("1.2") - def test_py(self): + def test_py(self) -> None: """Resolve a new py-based package with rex commands.""" self._test_rextest_package("1.3") - def test_2(self): + def test_2(self) -> None: """Resolve a package with a dependency, see that their commands are concatenated as expected.""" pkg = VersionedObject("rextest2-2") diff --git a/src/rez/tests/test_completion.py b/src/rez/tests/test_completion.py index f87629903..ad984d8b3 100644 --- a/src/rez/tests/test_completion.py +++ b/src/rez/tests/test_completion.py @@ -13,7 +13,7 @@ class TestCompletion(TestBase): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: packages_path = cls.data_path("solver", "packages") cls.settings = dict( packages_path=[packages_path], @@ -21,9 +21,9 @@ def setUpClass(cls): cls.config = Config([get_module_root_config()], locked=True) - def test_config(self): + def test_config(self) -> None: """Test config completion.""" - def _eq(prefix, expected_completions): + def _eq(prefix, expected_completions) -> None: completions = self.config.get_completions(prefix) self.assertEqual(set(completions), set(expected_completions)) @@ -42,9 +42,9 @@ def _eq(prefix, expected_completions): _eq("plugins.release_vcs.releasable_", ["plugins.release_vcs.releasable_branches"]) - def test_packages(self): + def test_packages(self) -> None: """Test packages completion.""" - def _eq(prefix, expected_completions): + def _eq(prefix, expected_completions) -> None: completions = get_completions(prefix) self.assertEqual(set(completions), set(expected_completions)) diff --git a/src/rez/tests/test_config.py b/src/rez/tests/test_config.py index a8bf2064b..084e1f161 100644 --- a/src/rez/tests/test_config.py +++ b/src/rez/tests/test_config.py @@ -23,12 +23,12 @@ class TestConfig(TestBase): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: cls.settings = {} cls.root_config_file = get_module_root_config() cls.config_path = cls.data_path("config") - def _test_basic(self, c): + def _test_basic(self, c) -> None: self.assertEqual(type(c.warn_all), bool) self.assertEqual(type(c.build_directory), str) @@ -41,7 +41,7 @@ def _test_basic(self, c): # plugin settings common to a plugin type self.assertEqual(type(p.release_vcs.tag_name), str) - def _test_overrides(self, c): + def _test_overrides(self, c) -> None: c.override("debug_none", True) c.override("build_directory", "floober") c.override("plugins.release_vcs.tag_name", "bah") @@ -67,7 +67,7 @@ def _test_overrides(self, c): self._test_basic(c) - def test_1(self): + def test_1(self) -> None: """Test just the root config file.""" # do a full validation of a config @@ -100,7 +100,7 @@ def test_1(self): self._test_overrides(c) - def test_2(self): + def test_2(self) -> None: """Test a config with an overriding file.""" conf = os.path.join(self.config_path, "test1.yaml") c = Config([self.root_config_file, conf], locked=True) @@ -114,7 +114,7 @@ def test_2(self): self._test_overrides(c) - def test_3(self): + def test_3(self) -> None: """Test environment variable config overrides.""" c = Config([self.root_config_file], locked=False) @@ -139,7 +139,7 @@ def test_3(self): os.environ["BUILD_DIRECTORY"] = "flaabs" self._test_overrides(c) - def test_4(self): + def test_4(self) -> None: """Test package config overrides.""" conf = os.path.join(self.config_path, "test2.py") config2 = Config([self.root_config_file, conf]) @@ -172,7 +172,7 @@ def test_4(self): self._test_overrides(c) - def test_5(self): + def test_5(self) -> None: """Test misconfigurations.""" # overrides set to bad types @@ -199,7 +199,7 @@ def test_5(self): with self.assertRaises(ConfigurationError): _ = c.debug_all # noqa - def test_6(self): + def test_6(self) -> None: """Test setting of dict values from environ""" from rez.config import Dict from rez.vendor.schema.schema import Schema @@ -232,7 +232,7 @@ def _data(self): finally: os.environ = old_environ - def test_7(self): + def test_7(self) -> None: """Test path list environment variable with whitespace.""" c = Config([self.root_config_file], locked=False) @@ -299,15 +299,15 @@ def test_8(self): class TestDeprecations(TestBase, TempdirMixin): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: cls.settings = {} TempdirMixin.setUpClass() @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: TempdirMixin.tearDownClass() - def test_deprecation_from_user_config(self): + def test_deprecation_from_user_config(self) -> None: user_home = os.path.join(self.root, "user_home") self.addCleanup(functools.partial(shutil.rmtree, user_home)) @@ -342,7 +342,7 @@ def test_deprecation_from_user_config(self): "config setting named 'packages_path' is deprecated and will be removed in 0.0.0.", ) - def test_deprecation_from_env_var(self): + def test_deprecation_from_env_var(self) -> None: fake_deprecated_settings = { "packages_path": _Deprecation("0.0.0"), } @@ -385,7 +385,7 @@ def test_deprecation_from_env_var(self): "be removed in 0.0.0.", ) - def test_non_preformatted_warning(self): + def test_non_preformatted_warning(self) -> None: with self.assertWarns(DeprecationWarning) as warning: warn('Warning Message', DeprecationWarning, pre_formatted=False) self.assertEqual(str(warning.warning), 'Warning Message') diff --git a/src/rez/tests/test_context.py b/src/rez/tests/test_context.py index 82786f049..c2ccc380c 100644 --- a/src/rez/tests/test_context.py +++ b/src/rez/tests/test_context.py @@ -22,7 +22,7 @@ class TestContext(TestBase, TempdirMixin): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: TempdirMixin.setUpClass() cls.packages_path = os.path.join(cls.root, "packages") @@ -37,10 +37,10 @@ def setUpClass(cls): resolve_caching=False) @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: TempdirMixin.tearDownClass() - def test_create_context(self): + def test_create_context(self) -> None: """Test creation of context.""" r = ResolvedContext([]) r.print_info() @@ -48,7 +48,7 @@ def test_create_context(self): r = ResolvedContext(["hello_world"]) r.print_info() - def test_apply(self): + def test_apply(self) -> None: """Test apply() function.""" # Isolate our changes to os.environ and sys.path and return to the # original state to not mess with our test environment. @@ -58,7 +58,7 @@ def test_apply(self): self.assertEqual(os.environ.get("OH_HAI_WORLD"), "hello") # TODO make shell-dependent (wait until port to pytest) - def test_execute_command(self): + def test_execute_command(self) -> None: """Test command execution in context.""" if platform_.name == "windows": self.skipTest("This test does not run on Windows due to problems" @@ -71,7 +71,7 @@ def test_execute_command(self): stdout = stdout.strip() self.assertEqual(stdout, "Hello Rez World!") - def test_resolved_packages_testing_environ(self): + def test_resolved_packages_testing_environ(self) -> None: """Test resolving packages within a testing environment behaves correctly""" packages_path = self.data_path("builds", "packages") @@ -80,7 +80,7 @@ def test_resolved_packages_testing_environ(self): resolvedPackages = [x.qualified_package_name for x in r.resolved_packages] self.assertEqual(resolvedPackages, ["floob", "testing_obj-1.0.0"]) - def test_execute_command_testing_environ(self): + def test_execute_command_testing_environ(self) -> None: """Test that execute_command properly sets test specific environ dict""" self.inject_python_repo() packages_path = self.data_path("builds", "packages") @@ -91,13 +91,13 @@ def test_execute_command_testing_environ(self): ) self.assertEqual(r.get_environ().get("CAR_IDEA"), "STURDY STEERING WHEEL") - def test_execute_command_environ(self): + def test_execute_command_environ(self) -> None: """Test that execute_command properly sets environ dict.""" self.inject_python_repo() r = ResolvedContext(["hello_world", "python"]) self._test_execute_command_environ(r) - def _test_execute_command_environ(self, r): + def _test_execute_command_environ(self, r) -> None: pycode = ("import os; " "print(os.getenv(\"BIGLY\")); " "print(os.getenv(\"OH_HAI_WORLD\"))") @@ -113,7 +113,7 @@ def _test_execute_command_environ(self, r): self.assertEqual(parts, ["covfefe", "hello"]) - def test_serialize(self): + def test_serialize(self) -> None: """Test context serialization.""" # save @@ -129,7 +129,7 @@ def test_serialize(self): env = r2.get_environ() self.assertEqual(env.get("OH_HAI_WORLD"), "hello") - def test_deserialize_older_versions(self): + def test_deserialize_older_versions(self) -> None: """Test deserialization of older contexts.""" baked_contexts_path = self.data_path("contexts") @@ -137,7 +137,7 @@ def test_deserialize_older_versions(self): # load _ = ResolvedContext.load(os.path.join(baked_contexts_path, context_file)) - def test_retarget(self): + def test_retarget(self) -> None: """Test that a retargeted context behaves identically.""" self.inject_python_repo() @@ -155,12 +155,12 @@ def test_retarget(self): self._test_execute_command_environ(r2) - def test_bundled(self): + def test_bundled(self) -> None: """Test that a bundled context behaves identically.""" self.inject_python_repo() - def _test_bundle(path): + def _test_bundle(path) -> None: # load the bundled context r2 = ResolvedContext.load(os.path.join(path, "context.rxt")) @@ -214,7 +214,7 @@ def _test_bundle(path): _test_bundle(bundle_path3) - def test_orderer_variants(self): + def test_orderer_variants(self) -> None: """Test that package orderers apply to the variants of packages, not just requires.""" from rez.package_order import PerFamilyOrder, VersionSplitPackageOrder from rez.version import Version @@ -241,7 +241,7 @@ def test_orderer_variants(self): resolved = [x.qualified_package_name for x in r.resolved_packages] self.assertEqual(resolved, ['python-2.6.8', 'pyvariants-2']) - def test_orderer_package_argument(self): + def test_orderer_package_argument(self) -> None: """Test that the packages argument to an orderer gives expected results.""" from rez.package_order import VersionSplitPackageOrder from rez.version import Version diff --git a/src/rez/tests/test_copy_package.py b/src/rez/tests/test_copy_package.py index 9f33db8d8..dfb35f4a4 100644 --- a/src/rez/tests/test_copy_package.py +++ b/src/rez/tests/test_copy_package.py @@ -22,7 +22,7 @@ class TestCopyPackage(TestBase, TempdirMixin): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: TempdirMixin.setUpClass() packages_path = cls.data_path("builds", "packages") @@ -46,10 +46,10 @@ def setUpClass(cls): implicit_packages=[]) @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: TempdirMixin.tearDownClass() - def setup_once(self): + def setup_once(self) -> None: # build packages used by this test self.inject_python_repo() self._build_package("build_util", "1") @@ -66,7 +66,7 @@ def _create_builder(cls, working_dir): build_system=buildsys) @classmethod - def _build_package(cls, name, version=None): + def _build_package(cls, name, version=None) -> None: # create the builder working_dir = os.path.join(cls.src_root, name) if version: @@ -75,7 +75,7 @@ def _build_package(cls, name, version=None): builder.build(install_path=cls.install_root, install=True, clean=True) - def _reset_dest_repository(self): + def _reset_dest_repository(self) -> None: system.clear_caches() if os.path.exists(self.dest_install_root): shutil.rmtree(self.dest_install_root) @@ -98,11 +98,11 @@ def _get_dest_pkg(self, name, version): error=True ) - def _assert_copied(self, result, copied, skipped): + def _assert_copied(self, result, copied, skipped) -> None: self.assertEqual(len(result["copied"]), copied) self.assertEqual(len(result["skipped"]), skipped) - def test_1(self): + def test_1(self) -> None: """Simple package copy, no variants, no overwrite.""" self._reset_dest_repository() @@ -135,7 +135,7 @@ def test_1(self): # check that package payload wasn't overwritten self.assertEqual(os.stat(pyfile).st_ctime, ctime) - def test_2(self): + def test_2(self) -> None: """Package copy, no variants, overwrite.""" self._reset_dest_repository() @@ -163,7 +163,7 @@ def test_2(self): # check that package payload was overwritten self.assertNotEqual(os.stat(pyfile).st_ctime, ctime) - def test_3(self): + def test_3(self) -> None: """Package copy, variants, overwrite and non-overwrite.""" self._reset_dest_repository() @@ -223,7 +223,7 @@ def test_3(self): pyfile = os.path.join(skipped_variant.root, "python", "bah", "__init__.py") self.assertEqual(os.stat(pyfile).st_ctime, ctimes[0]) - def test_4(self): + def test_4(self) -> None: """Package copy with rename, reversion.""" self._reset_dest_repository() @@ -244,7 +244,7 @@ def test_4(self): dest_variant = next(dest_pkg.iter_variants()) self.assertEqual(dest_variant.handle, result_variant.handle) - def test_5(self): + def test_5(self) -> None: """Package copy with standard, new timestamp.""" self._reset_dest_repository() @@ -262,7 +262,7 @@ def test_5(self): dest_pkg = self._get_dest_pkg("floob", "1.2.0") self.assertTrue(dest_pkg.timestamp > src_pkg.timestamp) - def test_6(self): + def test_6(self) -> None: """Package copy with keep_timestamp.""" self._reset_dest_repository() @@ -281,7 +281,7 @@ def test_6(self): dest_pkg = self._get_dest_pkg("floob", "1.2.0") self.assertEqual(dest_pkg.timestamp, src_pkg.timestamp) - def test_7(self): + def test_7(self) -> None: """Package copy with overrides.""" self._reset_dest_repository() @@ -305,7 +305,7 @@ def test_7(self): for k, v in list(overrides.items()): self.assertEqual(getattr(dest_pkg, k), v) - def test_8(self): + def test_8(self) -> None: """Ensure that include modules are copied.""" self._reset_dest_repository() diff --git a/src/rez/tests/test_formatter.py b/src/rez/tests/test_formatter.py index 9548a29e5..8d69dc7f4 100644 --- a/src/rez/tests/test_formatter.py +++ b/src/rez/tests/test_formatter.py @@ -11,23 +11,23 @@ class TestFormatter(TestBase): - def setUp(self): + def setUp(self) -> None: TestBase.setUp(self) self.formatter = NamespaceFormatter({}) - def assert_formatter_equal(self, format, expected, *args, **kwargs): + def assert_formatter_equal(self, format, expected, *args, **kwargs) -> None: self.assertEqual(self.formatter.format(format, *args, **kwargs), expected) - def assert_formatter_raises(self, format, error, *args, **kwargs): + def assert_formatter_raises(self, format, error, *args, **kwargs) -> None: self.assertRaises(error, self.formatter.format, format, *args, **kwargs) - def test_formatter_rex(self): + def test_formatter_rex(self) -> None: self.assert_formatter_equal('Hello, ${world}!', 'Hello, ${world}!') self.assert_formatter_equal('Hello, $WORLD!', 'Hello, ${WORLD}!') self.assert_formatter_equal('Hello, ${{world}}!', 'Hello, ${world}!', world="Earth") self.assert_formatter_equal('Hello, {world}!', 'Hello, Earth!', world="Earth") - def test_formatter_stdlib(self): + def test_formatter_stdlib(self) -> None: """ string.Formatter.format tests from the Python standard library used to ensure we haven't broken functionality preset in the standard @@ -53,59 +53,59 @@ def test_formatter_stdlib(self): # classes we'll use for testing class C(object): - def __init__(self, x=100): + def __init__(self, x: int=100) -> None: self._x = x - def __format__(self, spec): + def __format__(self, spec) -> str: return spec class D(object): - def __init__(self, x): + def __init__(self, x) -> None: self.x = x - def __format__(self, spec): + def __format__(self, spec) -> str: return str(self.x) # class with __str__, but no __format__ class E(object): - def __init__(self, x): + def __init__(self, x) -> None: self.x = x - def __str__(self): + def __str__(self) -> str: return 'E(' + self.x + ')' # class with __repr__, but no __format__ or __str__ class F(object): - def __init__(self, x): + def __init__(self, x) -> None: self.x = x - def __repr__(self): + def __repr__(self) -> str: return 'F(' + self.x + ')' # class with __format__ that forwards to string, for some format_spec's class G(object): - def __init__(self, x): + def __init__(self, x) -> None: self.x = x - def __str__(self): + def __str__(self) -> str: return "string is " + self.x - def __format__(self, format_spec): + def __format__(self, format_spec) -> str: if format_spec == 'd': return 'G(' + self.x + ')' return format(str(self), format_spec) # class that returns a bad type from __format__ class H(object): - def __format__(self, format_spec): + def __format__(self, format_spec) -> str: return 1.0 class I(datetime.date): - def __format__(self, format_spec): + def __format__(self, format_spec) -> str: return self.strftime(format_spec) class J(int): - def __format__(self, format_spec): + def __format__(self, format_spec) -> str: return int.__format__(self * 2, format_spec) self.assert_formatter_equal('', '') @@ -259,7 +259,7 @@ def __format__(self, format_spec): self.assert_formatter_raises("{0:-s}", ValueError, '') self.assert_formatter_raises("{0:=s}", ValueError, '') - def test_formatter_recurse(self): + def test_formatter_recurse(self) -> None: self.assert_formatter_equal('Hello {0}!', 'Hello Earth!', '{world}', world='Earth') diff --git a/src/rez/tests/test_imports.py b/src/rez/tests/test_imports.py index bf77cf59a..d92b5d3d8 100644 --- a/src/rez/tests/test_imports.py +++ b/src/rez/tests/test_imports.py @@ -10,7 +10,7 @@ class TestImports(TestBase): - def test_1(self): + def test_1(self) -> None: """import every file in rez.""" import rez # noqa import rez.build_process # noqa diff --git a/src/rez/tests/test_package_cache.py b/src/rez/tests/test_package_cache.py index a4ad7dbf4..0868d7a4f 100644 --- a/src/rez/tests/test_package_cache.py +++ b/src/rez/tests/test_package_cache.py @@ -22,7 +22,7 @@ class TestPackageCache(TestBase, TempdirMixin): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: TempdirMixin.setUpClass() cls.py_packages_path = canonical_path(cls.data_path("packages", "py_packages")) @@ -49,13 +49,13 @@ def setUpClass(cls): ) @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: TempdirMixin.tearDownClass() def _pkgcache(self): return PackageCache(self.package_cache_path) - def test_cache_variant(self): + def test_cache_variant(self) -> None: """Test direct caching of a cachable variant.""" pkgcache = self._pkgcache() @@ -69,7 +69,7 @@ def test_cache_variant(self): _, status = pkgcache.add_variant(variant) self.assertEqual(status, PackageCache.VARIANT_FOUND) - def test_delete_cached_variant(self): + def test_delete_cached_variant(self) -> None: """Test variant deletion from cache.""" pkgcache = self._pkgcache() @@ -85,7 +85,7 @@ def test_delete_cached_variant(self): result = pkgcache.remove_variant(variant) self.assertEqual(result, PackageCache.VARIANT_NOT_FOUND) - def test_cache_fail_uncachable_variant(self): + def test_cache_fail_uncachable_variant(self) -> None: """Test that caching of an uncachable variant fails.""" pkgcache = self._pkgcache() @@ -95,7 +95,7 @@ def test_cache_fail_uncachable_variant(self): with self.assertRaises(PackageCacheError): pkgcache.add_variant(variant) - def test_cache_fail_no_variant_payload(self): + def test_cache_fail_no_variant_payload(self) -> None: """Test that adding a variant with no disk payload, fails.""" pkgcache = self._pkgcache() @@ -105,7 +105,7 @@ def test_cache_fail_no_variant_payload(self): with self.assertRaises(PackageCacheError): pkgcache.add_variant(variant) - def test_cache_fail_per_repo(self): + def test_cache_fail_per_repo(self) -> None: """Test that caching fails on a package from a repo set to non-cachable.""" pkgcache = self._pkgcache() @@ -115,7 +115,7 @@ def test_cache_fail_per_repo(self): with self.assertRaises(PackageCacheError): pkgcache.add_variant(variant) - def test_cache_fail_per_package(self): + def test_cache_fail_per_package(self) -> None: """Test that caching fails on a package with a blacklisted name.""" pkgcache = self._pkgcache() @@ -138,7 +138,7 @@ def test_external_logging_config(self): self.assertEqual(logger.handlers[0].level, logging.DEBUG) @install_dependent() - def test_caching_on_resolve(self): + def test_caching_on_resolve(self) -> None: """Test that cache is updated as expected on resolved env.""" pkgcache = self._pkgcache() @@ -200,7 +200,7 @@ def test_caching_on_resolve(self): ) @install_dependent() - def test_caching_on_resolve_synchronous(self): + def test_caching_on_resolve_synchronous(self) -> None: """Test that cache is updated as expected on resolved env using syncrhonous package caching.""" pkgcache = self._pkgcache() diff --git a/src/rez/tests/test_package_filter.py b/src/rez/tests/test_package_filter.py index 66d7481b4..9dd84eb70 100644 --- a/src/rez/tests/test_package_filter.py +++ b/src/rez/tests/test_package_filter.py @@ -16,7 +16,7 @@ class TestPackageFilter(TestBase): """Tests package filtering. """ @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: cls.py_packages_path = cls.data_path("packages", "py_packages") cls.solver_packages_path = cls.data_path("solver", "packages") @@ -27,7 +27,7 @@ def setUpClass(cls): ], package_filter=None) - def _test(self, fltr, pkg_family, expected_result): + def _test(self, fltr, pkg_family, expected_result) -> None: # convert from json if required if isinstance(fltr, dict): @@ -35,7 +35,7 @@ def _test(self, fltr, pkg_family, expected_result): elif isinstance(fltr, list): fltr = PackageFilterList.from_pod(fltr) - def filter_versions(fltr_): + def filter_versions(fltr_) -> None: matching_versions = set() for pkg in iter_packages(pkg_family): @@ -52,7 +52,7 @@ def filter_versions(fltr_): fltr2 = fltr.from_pod(data) filter_versions(fltr2) - def test_empty_filter(self): + def test_empty_filter(self) -> None: """Test that empty filter has no effect """ fltr = PackageFilter() @@ -62,7 +62,7 @@ def test_empty_filter(self): ["1", "2", "3"] ) - def test_empty_filter_list(self): + def test_empty_filter_list(self) -> None: """Test that empty filter list has no effect """ fltr = PackageFilterList() @@ -72,7 +72,7 @@ def test_empty_filter_list(self): ["1", "2", "3"] ) - def test_glob_filter(self): + def test_glob_filter(self) -> None: """Test the glob filter. """ fltr = PackageFilter() @@ -91,7 +91,7 @@ def test_glob_filter(self): ] ) - def test_regex_filter(self): + def test_regex_filter(self) -> None: """Test the regex filter. """ fltr = PackageFilter() @@ -109,7 +109,7 @@ def test_regex_filter(self): ] ) - def test_range_filter(self): + def test_range_filter(self) -> None: """Test the range filter. """ fltr = PackageFilter() @@ -124,7 +124,7 @@ def test_range_filter(self): ] ) - def test_timestamp_filter(self): + def test_timestamp_filter(self) -> None: """Test the timestamp filter. """ fltr = PackageFilter() @@ -139,7 +139,7 @@ def test_timestamp_filter(self): ] ) - def test_otherfam_filter(self): + def test_otherfam_filter(self) -> None: """Test that a filter on a different fam has no effect """ fltr = PackageFilter() @@ -151,7 +151,7 @@ def test_otherfam_filter(self): ["1", "2", "3"] ) - def test_excl_and_incl(self): + def test_excl_and_incl(self) -> None: """Test that combo of exclusion and inclusion works as expected """ self._test( @@ -172,7 +172,7 @@ def test_excl_and_incl(self): ] ) - def test_filter_list(self): + def test_filter_list(self) -> None: """Test that logic wrt list of filters works as expected """ diff --git a/src/rez/tests/test_packages.py b/src/rez/tests/test_packages.py index cce3deaf2..fb6a1bc71 100644 --- a/src/rez/tests/test_packages.py +++ b/src/rez/tests/test_packages.py @@ -80,7 +80,7 @@ def _to_qnames(it): class TestPackages(TestBase, TempdirMixin): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: TempdirMixin.setUpClass() cls.solver_packages_path = cls.data_path("solver", "packages") @@ -101,15 +101,15 @@ def setUpClass(cls): package_filter=None) @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: TempdirMixin.tearDownClass() - def test_fam_iteration(self): + def test_fam_iteration(self) -> None: """package family iteration.""" all_fams = _to_names(iter_package_families()) self.assertEqual(all_fams, ALL_FAMILIES) - def test_pkg_iteration(self): + def test_pkg_iteration(self) -> None: """package iteration.""" all_packages = set() all_fams = iter_package_families() @@ -135,7 +135,7 @@ def test_pkg_iteration(self): it = family.iter_packages() self.assertTrue(package in it) - def test_pkg_data(self): + def test_pkg_data(self) -> None: """check package contents.""" # a py-based package package = get_package("versioned", "3.0") @@ -185,7 +185,7 @@ def test_pkg_data(self): expected_uri = canonical_path(os.path.join(self.py_packages_path, "multi.py<2.0>")) self.assertEqual(package.uri, expected_uri) - def test_pkg_create(self): + def test_pkg_create(self) -> None: """test package creation.""" package_data = { "name": "foo", @@ -205,7 +205,7 @@ def test_pkg_create(self): self.assertEqual(len(packages), 1) self.assertEqual(package, packages[0]) - def test_developer_pkg(self): + def test_developer_pkg(self) -> None: """test developer package.""" path = os.path.join(self.packages_base_path, "developer") package = get_developer_package(path) @@ -221,7 +221,7 @@ def test_developer_pkg(self): data = package.validated_data() self.assertDictEqual(data, expected_data) - def test_developer_dynamic_local_preprocess(self): + def test_developer_dynamic_local_preprocess(self) -> None: """test developer package with a local preprocess function""" # a developer package with features such as expanding requirements, # early-binding attribute functions, and preprocessing @@ -243,7 +243,7 @@ def test_developer_dynamic_local_preprocess(self): self.assertFalse(hasattr(package, "added_by_global_preprocess")) self.assertEqual(package.added_by_local_preprocess, True) - def test_developer_dynamic_global_preprocess_string(self): + def test_developer_dynamic_global_preprocess_string(self) -> None: """test developer package with a global preprocess function as string""" # a developer package with features such as expanding requirements, # global preprocessing @@ -259,11 +259,11 @@ def test_developer_dynamic_global_preprocess_string(self): self.assertEqual(package.description, "This.") self.assertEqual(package.added_by_global_preprocess, True) - def test_developer_dynamic_global_preprocess_func(self): + def test_developer_dynamic_global_preprocess_func(self) -> None: """test developer package with a global preprocess function as function""" # a developer package with features such as expanding requirements, # global preprocessing - def preprocess(this, data): + def preprocess(this, data) -> None: data["dynamic_attribute_added"] = {'test': True} self.update_settings( @@ -278,11 +278,11 @@ def preprocess(this, data): self.assertEqual(package.description, "This.") self.assertEqual(package.dynamic_attribute_added, {'test': True}) - def test_developer_dynamic_before(self): + def test_developer_dynamic_before(self) -> None: """test developer package with both global and local preprocess in before mode""" # a developer package with features such as expanding requirements, # global preprocessing - def preprocess(this, data): + def preprocess(this, data) -> None: data["dynamic_attribute_added"] = {'value_set_by': 'global'} data["added_by_global_preprocess"] = True @@ -302,11 +302,11 @@ def preprocess(this, data): self.assertEqual(package.added_by_global_preprocess, True) self.assertEqual(package.added_by_local_preprocess, True) - def test_developer_dynamic_after(self): + def test_developer_dynamic_after(self) -> None: """test developer package with both global and local preprocess in after mode""" # a developer package with features such as expanding requirements, # global preprocessing - def preprocess(this, data): + def preprocess(this, data) -> None: data["dynamic_attribute_added"] = {'value_set_by': 'global'} data["added_by_global_preprocess"] = True @@ -326,7 +326,7 @@ def preprocess(this, data): self.assertEqual(package.added_by_global_preprocess, True) self.assertEqual(package.added_by_local_preprocess, True) - def test_variant_iteration(self): + def test_variant_iteration(self) -> None: """test variant iteration.""" base = canonical_path(os.path.join(self.py_packages_path, "variants_py", "2.0")) expected_data = dict( @@ -344,7 +344,7 @@ def test_variant_iteration(self): self.assertEqual(variant.index, i) self.assertEqual(variant.parent, package) - def test_variant_install(self): + def test_variant_install(self) -> None: """test variant installation.""" repo_path = os.path.join(self.root, "packages") os.makedirs(repo_path, exist_ok=True) @@ -400,7 +400,7 @@ def _data(obj): data_ = _data(installed_package) self.assertDictEqual(data, data_) - def test_expand_requirement(self): + def test_expand_requirement(self) -> None: """test expand_requirement function.""" tests = ( ("pyfoo", "pyfoo"), @@ -438,20 +438,20 @@ def test_expand_requirement(self): for req in bad_tests: self.assertRaises(VersionError, expand_requirement, req) - def test_package_from_uri(self): + def test_package_from_uri(self) -> None: """Test getting a package from its uri.""" package = get_package("variants_py", "2.0") package2 = get_package_from_uri(package.uri) self.assertEqual(package, package2) - def test_variant_from_uri(self): + def test_variant_from_uri(self) -> None: """Test getting a variant from its uri.""" package = get_package("variants_py", "2.0") for variant in package.iter_variants(): variant2 = get_variant_from_uri(variant.uri) self.assertEqual(variant, variant2) - def test_package_ignore(self): + def test_package_ignore(self) -> None: """Test package ignore/unignore.""" pkg_name = "pydad" pkg_version = Version("2") @@ -502,7 +502,7 @@ def test_package_ignore(self): pkg = get_package_from_repository(pkg_name, pkg_version, repo_path) self.assertNotEqual(pkg, None) - def test_package_move(self): + def test_package_move(self) -> None: """Test package move.""" pkg_name = "pydad" pkg_version = Version("2") @@ -530,7 +530,7 @@ def test_package_move(self): src_pkg = get_package_from_repository(pkg_name, pkg_version, repo_path) self.assertEqual(src_pkg, None) - def test_package_remove(self): + def test_package_remove(self) -> None: """Test package remove.""" pkg_name = "pydad" pkg_version = Version("2") @@ -552,7 +552,7 @@ def test_package_remove(self): i = repo.unignore_package(pkg_name, pkg_version) self.assertEqual(i, -1) - def test_package_family_remove(self): + def test_package_family_remove(self) -> None: """Test package family remove.""" pkg_name = "pydad" @@ -594,7 +594,7 @@ def test_package_family_remove(self): None ) - def test_remove_packages_ignored_since(self): + def test_remove_packages_ignored_since(self) -> None: pkg_name = "pydad" pkg_version = Version("2") @@ -621,7 +621,7 @@ def test_remove_packages_ignored_since(self): class TestMemoryPackages(TestBase): - def test_1_memory_variant_parent(self): + def test_1_memory_variant_parent(self) -> None: """Test that a package's variant's parent is the original package """ desc = 'the foo package' diff --git a/src/rez/tests/test_packages_order.py b/src/rez/tests/test_packages_order.py index 1138b86fe..12c0ebe7c 100644 --- a/src/rez/tests/test_packages_order.py +++ b/src/rez/tests/test_packages_order.py @@ -18,7 +18,7 @@ class _BaseTestPackagesOrder(TestBase, TempdirMixin): """Base class for a package ordering test case""" @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: TempdirMixin.setUpClass() cls.py_packages_path = cls.data_path("packages", "py_packages") @@ -32,10 +32,10 @@ def setUpClass(cls): package_filter=None) @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: TempdirMixin.tearDownClass() - def _test_reorder(self, orderer, package_name, expected_order): + def _test_reorder(self, orderer, package_name, expected_order) -> None: """Ensure ordered order package version as expected.""" it = iter_packages(package_name) descending = sorted(it, key=lambda x: x.version, reverse=True) @@ -43,7 +43,7 @@ def _test_reorder(self, orderer, package_name, expected_order): result = [str(x.version) for x in ordered] self.assertEqual(expected_order, result) - def _test_pod(self, orderer): + def _test_pod(self, orderer) -> None: """Ensure an orderer integrity when serialized to pod.""" pod = json.loads(json.dumps(orderer.to_pod())) # roundtrip to JSON actual = orderer.__class__.from_pod(pod) @@ -53,16 +53,16 @@ def _test_pod(self, orderer): class TestAbstractPackageOrder(TestBase): """Test case for the abstract PackageOrder class""" - def test_to_pod(self): + def test_to_pod(self) -> None: """Validate to_pod is not implemented""" self.assertRaises(NotImplementedError, PackageOrder().to_pod) - def test_str(self): + def test_str(self) -> None: """Validate __str__ is not implemented""" with self.assertRaises(NotImplementedError): str(PackageOrder()) - def test_eq(self): + def test_eq(self) -> None: """Validate __eq__ is not implemented""" with self.assertRaises(NotImplementedError): PackageOrder() == PackageOrder() @@ -71,11 +71,11 @@ def test_eq(self): class TestNullPackageOrder(_BaseTestPackagesOrder): """Test case for the NullPackageOrder class""" - def test_repr(self): + def test_repr(self) -> None: """Validate we can represent a VersionSplitPackageOrder as a string.""" self.assertEqual("NullPackageOrder({})", repr(NullPackageOrder())) - def test_comparison(self): + def test_comparison(self) -> None: """Validate we can compare VersionSplitPackageOrder together.""" inst1 = NullPackageOrder() inst2 = NullPackageOrder() @@ -84,11 +84,11 @@ def test_comparison(self): self.assertTrue(inst1 != "wrong_type") # __ne__ positive (wrong type) self.assertFalse(inst1 != inst2) # __ne__ negative - def test_pod(self): + def test_pod(self) -> None: """Validate we can save and load a VersionSplitPackageOrder to it's pod representation.""" self._test_pod(NullPackageOrder()) - def test_sha1(self): + def test_sha1(self) -> None: """Validate we can get a sha1 hash. """ self.assertEqual( @@ -99,15 +99,15 @@ def test_sha1(self): class TestSortedOrder(_BaseTestPackagesOrder): """Test case for the SortedOrder class""" - def test_reorder_ascending(self): + def test_reorder_ascending(self) -> None: """Validate we can sort packages in ascending order.""" self._test_reorder(SortedOrder(descending=False), "pymum", ["1", "2", "3"]) - def test_reorder_descending(self): + def test_reorder_descending(self) -> None: """Validate we can sort packages in descending order.""" self._test_reorder(SortedOrder(descending=True), "pymum", ["3", "2", "1"]) - def test_comparison(self): + def test_comparison(self) -> None: """Validate we can compare SortedOrder together.""" inst1 = SortedOrder(descending=False) inst2 = SortedOrder(descending=False) @@ -119,11 +119,11 @@ def test_comparison(self): self.assertFalse(inst1 == "wrong_type") # __eq__ negative (wrong type) self.assertTrue(inst1 != "wrong_type") # __eq__ negative (wrong type) - def test_repr(self): + def test_repr(self) -> None: """Validate we can represent a SortedOrder as a string.""" self.assertEqual("SortedOrder(True)", repr(SortedOrder(descending=True))) - def test_pod(self): + def test_pod(self) -> None: """Validate we can save and load a SortedOrder to it's pod representation.""" self._test_pod(SortedOrder(descending=True)) @@ -131,7 +131,7 @@ def test_pod(self): class TestPerFamilyOrder(_BaseTestPackagesOrder): """Test case for the PerFamilyOrder class""" - def test_reorder(self): + def test_reorder(self) -> None: """Test ordering.""" expected_null_result = ["7", "6", "5"] expected_split_result = ["2.6.0", "2.5.2", "2.7.0", "2.6.8"] @@ -151,17 +151,17 @@ def test_reorder(self): self._test_reorder(orderer, "timestamped", expected_timestamp_result) self._test_reorder(orderer, "pymum", ["1", "2", "3"]) - def test_reorder_no_packages(self): + def test_reorder_no_packages(self) -> None: """Validate ordering for a family with no packages.""" orderer = PerFamilyOrder(order_dict=dict(missing_package=NullPackageOrder())) self._test_reorder(orderer, "missing_package", []) - def test_reorder_no_default_order(self): + def test_reorder_no_default_order(self) -> None: """Test behavior when there's no secondary default_order.""" fam_orderer = PerFamilyOrder(order_dict={}) self._test_reorder(fam_orderer, "pymum", ["3", "2", "1"]) - def test_comparison(self): + def test_comparison(self) -> None: """Validate we can compare PerFamilyOrder.""" inst1 = PerFamilyOrder(order_dict={'foo': NullPackageOrder()}, default_order=NullPackageOrder()) inst2 = PerFamilyOrder(order_dict={'foo': NullPackageOrder()}, default_order=NullPackageOrder()) @@ -174,12 +174,12 @@ def test_comparison(self): self.assertTrue(inst1 != inst4) # __ne__ positive (different default order) self.assertFalse(inst1 != inst2) # __ne__ negative - def test_repr(self): + def test_repr(self) -> None: """Validate we can represent a PerFamilyOrder as a string.""" inst = PerFamilyOrder(order_dict={"family1": VersionSplitPackageOrder(Version("2.6.0"))}) self.assertEqual("PerFamilyOrder(([('family1', '2.6.0')], 'None'))", repr(inst)) - def test_pod(self): + def test_pod(self) -> None: """Validate we can save and load a PerFamilyOrder to it's pod representation.""" self._test_pod( PerFamilyOrder(order_dict={'foo': NullPackageOrder()}, default_order=NullPackageOrder()) @@ -194,13 +194,13 @@ def test_pod(self): class TestVersionSplitPackageOrder(_BaseTestPackagesOrder): """Test case for the VersionSplitPackageOrder class""" - def test_reordere(self): + def test_reordere(self) -> None: """Validate package ordering with a VersionSplitPackageOrder""" orderer = VersionSplitPackageOrder(Version("2.6.0")) expected = ["2.6.0", "2.5.2", "2.7.0", "2.6.8"] self._test_reorder(orderer, "python", expected) - def test_comparison(self): + def test_comparison(self) -> None: """Validate we can compare VersionSplitPackageOrder together.""" inst1 = VersionSplitPackageOrder(first_version=Version("1.2.3")) inst2 = VersionSplitPackageOrder(first_version=Version("1.2.3")) @@ -212,12 +212,12 @@ def test_comparison(self): self.assertFalse(inst1 == "wrong_type") # __eq__ negative (wrong type) self.assertTrue(inst1 != "wrong_type") # __eq__ negative (wrong type) - def test_repr(self): + def test_repr(self) -> None: """Validate we can represent a VersionSplitPackageOrder as a string.""" inst = VersionSplitPackageOrder(first_version=Version("1,2,3")) self.assertEqual("VersionSplitPackageOrder(1,2,3)", repr(inst)) - def test_pod(self): + def test_pod(self) -> None: """Validate we can save and load a VersionSplitPackageOrder to it's pod representation.""" self._test_pod(VersionSplitPackageOrder(first_version=Version("1.2.3"))) @@ -225,13 +225,13 @@ def test_pod(self): class TestTimestampPackageOrder(_BaseTestPackagesOrder): """Test cases for the TimestampPackageOrder class""" - def test_reorder_no_rank(self): + def test_reorder_no_rank(self) -> None: """Validate reordering with a rank of 0.""" orderer = TimestampPackageOrder(timestamp=3001) expected = ['1.1.0', '1.0.6', '1.0.5', '1.1.1', '1.2.0', '2.0.0', '2.1.0', '2.1.5'] self._test_reorder(orderer, "timestamped", expected) - def test_reorder_rank_3(self): + def test_reorder_rank_3(self) -> None: """Validate reordering with a rank of 3.""" # after v1.1.0 and before v1.1.1 orderer1 = TimestampPackageOrder(timestamp=3001, rank=3) @@ -243,30 +243,30 @@ def test_reorder_rank_3(self): expected2 = ["2.1.5", "2.1.0", "2.0.0", "1.2.0", "1.1.1", "1.1.0", "1.0.6", "1.0.5"] self._test_reorder(orderer2, "timestamped", expected2) - def test_reorder_rank_2(self): + def test_reorder_rank_2(self) -> None: """Add coverage for a corner case where there's only one candidate without the rank.""" orderer = TimestampPackageOrder(timestamp=4001, rank=3) # 1.1.1 expected = ['1.1.1', '1.1.0', '1.0.6', '1.0.5', '1.2.0', '2.0.0', '2.1.5', '2.1.0'] self._test_reorder(orderer, "timestamped", expected) - def test_reorder_packages_without_timestamps(self): + def test_reorder_packages_without_timestamps(self) -> None: """Validate reordering of packages that have no timestamp data.""" orderer = TimestampPackageOrder(timestamp=3001) self._test_reorder(orderer, "pymum", ["3", "2", "1"]) - def test_reorder_all_packages_before_timestamp(self): + def test_reorder_all_packages_before_timestamp(self) -> None: """Test behavior when all packages are before the timestamp.""" timestamp_orderer = TimestampPackageOrder(timestamp=9999999999, rank=3) expected = ['2.1.5', '2.1.0', '2.0.0', '1.2.0', '1.1.1', '1.1.0', '1.0.6', '1.0.5'] self._test_reorder(timestamp_orderer, "timestamped", expected) - def test_reorder_all_packages_after_timestamp(self): + def test_reorder_all_packages_after_timestamp(self) -> None: """Test behavior when all packages are after the timestamp.""" timestamp_orderer = TimestampPackageOrder(timestamp=0, rank=3) expected = ['1.0.6', '1.0.5', '1.1.1', '1.1.0', '1.2.0', '2.0.0', '2.1.5', '2.1.0'] self._test_reorder(timestamp_orderer, "timestamped", expected) - def test_comparison(self): + def test_comparison(self) -> None: """Validate we can compare TimestampPackageOrder.""" inst1 = TimestampPackageOrder(timestamp=1, rank=1) inst2 = TimestampPackageOrder(timestamp=1, rank=1) @@ -279,12 +279,12 @@ def test_comparison(self): self.assertTrue(inst1 != inst4) # __ne__ positive (different rank) self.assertFalse(inst1 != inst2) # __ne__ negative - def test_repr(self): + def test_repr(self) -> None: """Validate we can represent a TimestampPackageOrder as a string.""" inst = TimestampPackageOrder(timestamp=1, rank=2) self.assertEqual(repr(inst), "TimestampPackageOrder((1, 2))") - def test_pod(self): + def test_pod(self) -> None: """Validate we can save and load a TimestampPackageOrder to pod representation.""" self._test_pod(TimestampPackageOrder(timestamp=3001, rank=3)) @@ -292,7 +292,7 @@ def test_pod(self): class TestPackageOrdererList(_BaseTestPackagesOrder): """Test cases for the PackageOrderList class.""" - def test_singleton(self): + def test_singleton(self) -> None: """Validate we can build a PackageOrderList object from configuration values.""" config.override("package_orderers", [ { @@ -318,7 +318,7 @@ def test_singleton(self): pass self.assertEqual(expected, PackageOrderList.singleton) - def test_singleton_novalue(self): + def test_singleton_novalue(self) -> None: """Validate we can build a PackageOrderList object from empty configuration values.""" config.override("package_orderers", None) @@ -330,7 +330,7 @@ def test_singleton_novalue(self): self.assertEqual(PackageOrderList(), PackageOrderList.singleton) - def test_pod(self): + def test_pod(self) -> None: """Validate we can save and load a PackageOrdererList to pod representation.""" inst = PackageOrderList(( VersionSplitPackageOrder(Version("2.6.0")), @@ -342,7 +342,7 @@ def test_pod(self): class TestPackageOrderPublic(TestBase): """Additional tests for public symbols in package_order.py""" - def test_from_pod_old_style(self): + def test_from_pod_old_style(self) -> None: """Validate from_pod is still compatible with the older pod style.""" self.assertEqual( VersionSplitPackageOrder(first_version=Version("1.2.3")), diff --git a/src/rez/tests/test_pip_utils.py b/src/rez/tests/test_pip_utils.py index c42bf6766..19318301a 100644 --- a/src/rez/tests/test_pip_utils.py +++ b/src/rez/tests/test_pip_utils.py @@ -23,17 +23,17 @@ class TestPipUtils(TestBase): """ """ @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: cls.settings = {} cls.dist_path = cls.data_path("pip", "installed_distributions") - def test_pip_to_rez_package_name(self): + def test_pip_to_rez_package_name(self) -> None: """ """ self.assertEqual(rez.utils.pip.pip_to_rez_package_name("asd"), "asd") self.assertEqual(rez.utils.pip.pip_to_rez_package_name("package-name"), "package_name") - def test_pip_to_rez_version(self): + def test_pip_to_rez_version(self) -> None: """ """ self.assertEqual(rez.utils.pip.pip_to_rez_version("1.0.0"), "1.0.0") @@ -45,17 +45,17 @@ def test_pip_to_rez_version(self): self.assertEqual(rez.utils.pip.pip_to_rez_version("1!2.3.4"), "2.3.4") self.assertEqual(rez.utils.pip.pip_to_rez_version("2.0b1pl0"), "2.0b1pl0") - def test_pip_to_rez_version_raises(self): + def test_pip_to_rez_version_raises(self) -> None: with self.assertRaises(rez.vendor.packaging.version.InvalidVersion): self.assertEqual( rez.utils.pip.pip_to_rez_version("2.0b1pl0", allow_legacy=False), "2.0b1pl0" ) - def test_pip_specifier_to_rez_requirement(self): + def test_pip_specifier_to_rez_requirement(self) -> None: """ """ - def assertPipRezEquivalent(pip_spec_str, rez_req_str): + def assertPipRezEquivalent(pip_spec_str, rez_req_str) -> None: pip_spec = SpecifierSet(pip_spec_str) self.assertEqual( rez.utils.pip.pip_specifier_to_rez_requirement(pip_spec), @@ -119,13 +119,13 @@ def assertPipRezEquivalent(pip_spec_str, rez_req_str): "2.6+<3.0|3.3+<4" ) - def test_pip_specifier_to_rez_requirement_raises(self): + def test_pip_specifier_to_rez_requirement_raises(self) -> None: """ """ with self.assertRaises(PackageRequestError): rez.utils.pip.pip_specifier_to_rez_requirement(SpecifierSet("<2,>3")) - def test_packaging_req_to_rez_req(self): + def test_packaging_req_to_rez_req(self) -> None: """ """ self.assertEqual( @@ -141,7 +141,7 @@ def test_packaging_req_to_rez_req(self): Requirement("package") ) - def test_is_pure_python_package(self): + def test_is_pure_python_package(self) -> None: """ """ dpath = rez.vendor.distlib.database.DistributionPath([self.dist_path]) @@ -149,14 +149,14 @@ def test_is_pure_python_package(self): self.assertTrue(rez.utils.pip.is_pure_python_package(dist)) - def test_is_entry_points_scripts_package(self): + def test_is_entry_points_scripts_package(self) -> None: """ """ dpath = rez.vendor.distlib.database.DistributionPath([self.dist_path]) dist = list(dpath.get_distributions())[0] self.assertFalse(rez.utils.pip.is_entry_points_scripts_package(dist)) - def test_convert_distlib_to_setuptools_wrong(self): + def test_convert_distlib_to_setuptools_wrong(self) -> None: """ """ dpath = rez.vendor.distlib.database.DistributionPath([self.dist_path]) @@ -165,10 +165,10 @@ def test_convert_distlib_to_setuptools_wrong(self): self.assertEqual(rez.utils.pip.convert_distlib_to_setuptools(dist), None) - def test_get_marker_sys_requirements(self): + def test_get_marker_sys_requirements(self) -> None: """ """ - def assertSysRequirements(req_str, sys_reqs): + def assertSysRequirements(req_str, sys_reqs) -> None: self.assertEqual( rez.utils.pip.get_marker_sys_requirements(req_str), sys_reqs @@ -243,10 +243,10 @@ def assertSysRequirements(req_str, sys_reqs): ["python"] ) - def test_normalize_requirement(self): + def test_normalize_requirement(self) -> None: """ """ - def assertRequirements(requirement, expected, conditional_extras): + def assertRequirements(requirement, expected, conditional_extras) -> None: """ """ result = rez.utils.pip.normalize_requirement(requirement) diff --git a/src/rez/tests/test_plugin_manager.py b/src/rez/tests/test_plugin_manager.py index 670441fba..84175f536 100644 --- a/src/rez/tests/test_plugin_manager.py +++ b/src/rez/tests/test_plugin_manager.py @@ -13,12 +13,12 @@ class TestPluginManagers(TestBase, TempdirMixin): - def __init__(self, *nargs, **kwargs): + def __init__(self, *nargs, **kwargs) -> None: TestBase.__init__(self, *nargs, **kwargs) self._reset_plugin_manager() @classmethod - def _reset_plugin_manager(cls): + def _reset_plugin_manager(cls) -> None: # for resetting package_repository type plugins package_repository_manager.clear_caches() package_repository_manager.pool.resource_classes.clear() @@ -38,18 +38,18 @@ def _reset_plugin_manager(cls): del sys.modules[key] @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: cls.settings = {"debug_plugins": True} @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: cls._reset_plugin_manager() - def setUp(self): + def setUp(self) -> None: TestBase.setUp(self) self._reset_plugin_manager() - def test_old_loading_style(self): + def test_old_loading_style(self) -> None: """Test loading rez plugin from plugin_path""" self.update_settings(dict( plugin_path=[self.data_path("extensions", "foo")] @@ -59,7 +59,7 @@ def test_old_loading_style(self): "package_repository", "cloud") self.assertEqual(cloud_cls.name(), "cloud") - def test_new_loading_style(self): + def test_new_loading_style(self) -> None: """Test loading rez plugin from python modules""" with restore_sys_path(): sys.path.append(self.data_path("extensions")) @@ -68,7 +68,7 @@ def test_new_loading_style(self): "package_repository", "cloud") self.assertEqual(cloud_cls.name(), "cloud") - def test_plugin_override_1(self): + def test_plugin_override_1(self) -> None: """Test plugin from plugin_path can override the default""" self.update_settings(dict( plugin_path=[self.data_path("extensions", "non-mod")] @@ -78,7 +78,7 @@ def test_plugin_override_1(self): "package_repository", "memory") self.assertEqual("non-mod", mem_cls.on_test) - def test_plugin_override_2(self): + def test_plugin_override_2(self) -> None: """Test plugin from python modules can override the default""" with restore_sys_path(): sys.path.append(self.data_path("extensions")) @@ -87,7 +87,7 @@ def test_plugin_override_2(self): "package_repository", "memory") self.assertEqual("bar", mem_cls.on_test) - def test_plugin_override_3(self): + def test_plugin_override_3(self) -> None: """Test plugin from python modules can override plugin_path""" with restore_sys_path(): # setup new diff --git a/src/rez/tests/test_release.py b/src/rez/tests/test_release.py index cfb37ff5a..5811621fd 100644 --- a/src/rez/tests/test_release.py +++ b/src/rez/tests/test_release.py @@ -25,7 +25,7 @@ class TestRelease(TestBase, TempdirMixin): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: TempdirMixin.setUpClass() cls.src_path = cls.data_path("release") @@ -41,14 +41,14 @@ def setUpClass(cls): implicit_packages=[]) @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: TempdirMixin.tearDownClass() @classmethod def _create_context(cls, *pkgs): return ResolvedContext(pkgs) - def _setup_release(self): + def _setup_release(self) -> None: # start fresh system.clear_caches() if os.path.exists(self.install_root): @@ -78,11 +78,11 @@ def _setup_release(self): self.vcs = create_release_vcs(self.src_root) self.assertEqual(self.vcs.name(), "stub") - def _write_package(self): + def _write_package(self) -> None: with open(self.packagefile, 'w') as f: dump_package_data(self.package_data, f, format_=FileFormat.yaml) - def _create_builder(self, ensure_latest=True): + def _create_builder(self, ensure_latest: bool = True): buildsys = create_build_system(self.src_root, verbose=True) return create_build_process(process_type="local", @@ -93,7 +93,7 @@ def _create_builder(self, ensure_latest=True): ignore_existing_tag=True, verbose=True) - def assertVariantsEqual(self, vars1, vars2): + def assertVariantsEqual(self, vars1, vars2) -> None: """Utility function to compare string-variants with formal lists of "PackageRequest" objects """ @@ -105,7 +105,7 @@ def _standardize_variants(variants): @per_available_shell() @install_dependent() - def test_1(self, shell): + def test_1(self, shell) -> None: """Basic release.""" self.inject_python_repo() config.override("default_shell", shell) @@ -169,7 +169,7 @@ def test_1(self, shell): @per_available_shell() @install_dependent() - def test_2_variant_add(self, shell): + def test_2_variant_add(self, shell) -> None: """Test variant installation on release """ self.inject_python_repo() diff --git a/src/rez/tests/test_resources_.py b/src/rez/tests/test_resources_.py index 216510c5e..26327481a 100644 --- a/src/rez/tests/test_resources_.py +++ b/src/rez/tests/test_resources_.py @@ -55,7 +55,7 @@ class ResourceBad(Resource): class BasePetResource(Resource): schema_error = PetResourceError - def __init__(self, variables=None): + def __init__(self, variables=None) -> None: super(BasePetResource, self).__init__(variables) self.validations = {} @@ -68,7 +68,7 @@ def _validate_key(self, key, attr, key_schema): class PetResource(BasePetResource): schema = pet_schema - def __init__(self, variables): + def __init__(self, variables) -> None: super(PetResource, self).__init__(variables) self.is_loaded = False @@ -101,14 +101,14 @@ def get_resource(self, resource_key, variables=None): class PetRepository(PackageRepository): - def __init__(self, pool): + def __init__(self, pool) -> None: self.pool = pool self.pool.register_resource(KittenResource) self.pool.register_resource(PuppyResource) self.location = 'Pets R Us' @classmethod - def name(cls): + def name(cls) -> str: return "pet_repository" def get_kitten(self, name): @@ -150,7 +150,7 @@ class Puppy(Pet): class PetStore(object): - def __init__(self): + def __init__(self) -> None: self.pool = PetPool(cache_size=None) self.repo = PetRepository(self.pool) @@ -171,7 +171,7 @@ def _get_pet(self, species, cls_, name): # -- test suite class TestResources_(TestBase): - def test_1(self): + def test_1(self) -> None: """resource registration test.""" pool = PetPool(cache_size=None) @@ -189,7 +189,7 @@ def test_1(self): self.assertTrue(isinstance(resource_a, ResourceA)) self.assertTrue(isinstance(resource_b, ResourceB)) - def test_2(self): + def test_2(self) -> None: """basic resource loading test.""" repo = PetRepository(PetPool(cache_size=None)) repo.pool.register_resource(ResourceA) @@ -216,7 +216,7 @@ def test_2(self): self.assertEqual(resource_.variables, variables) self.assertTrue(resource_ is not resource) - def test_3(self): + def test_3(self) -> None: """real world(ish) example of a resource system. In this example, `pets` is a resource repository - in a real resource @@ -233,7 +233,7 @@ class to hide potentially multiple resource classes, they also implement the `name` attribute, which means we can query a resource for its name, without causing the resource data to be loaded. """ - def _validate(resource, expected_data): + def _validate(resource, expected_data) -> None: self.assertEqual(resource.validated_data(), expected_data) # after full validation, each attrib should validate exactly once. diff --git a/src/rez/tests/test_rex.py b/src/rez/tests/test_rex.py index 32a37d495..ac3a8966d 100644 --- a/src/rez/tests/test_rex.py +++ b/src/rez/tests/test_rex.py @@ -34,7 +34,7 @@ def _create_executor(self, env, **kwargs): **kwargs) def _test(self, func, env, expected_actions=None, expected_output=None, - expected_exception=None, **ex_kwargs): + expected_exception=None, **ex_kwargs) -> None: """Tests rex code as a function object, and code string.""" loc = inspect.getsourcelines(func)[0][1:] code = textwrap.dedent('\n'.join(loc)) @@ -56,9 +56,9 @@ def _test(self, func, env, expected_actions=None, expected_output=None, self.assertEqual(ex.actions, expected_actions) self.assertEqual(ex.get_output(), expected_output) - def test_1(self): + def test_1(self) -> None: """Test simple use of every available action.""" - def _rex(): + def _rex() -> None: shebang() setenv("FOO", "foo") setenv("BAH", "bah") @@ -101,9 +101,9 @@ def _rex(): 'A': os.pathsep.join(["/data", "/tmp"]), 'B': os.pathsep.join(["/tmp", "/data"])}) - def test_2(self): + def test_2(self) -> None: """Test simple setenvs and assignments.""" - def _rex(): + def _rex() -> None: env.FOO = "foo" setenv("BAH", "bah") env.EEK = env.FOO @@ -119,9 +119,9 @@ def _rex(): 'EEK': 'foo', 'BAH': 'bah'}) - def test_3(self): + def test_3(self) -> None: """Test appending/prepending.""" - def _rex(): + def _rex() -> None: appendenv("FOO", "test1") env.FOO.append("test2") env.FOO.append("test3") @@ -170,9 +170,9 @@ def _rex(): 'BAH': os.pathsep.join(["B", "A", "Z", "C"])}, parent_variables=["FOO", "BAH"]) - def test_4(self): + def test_4(self) -> None: """Test control flow using internally-set env vars.""" - def _rex(): + def _rex() -> None: env.FOO = "foo" setenv("BAH", "bah") env.EEK = "foo" @@ -199,9 +199,9 @@ def _rex(): 'EEK': 'foo', 'FOO_VALID': '1'}) - def test_5(self): + def test_5(self) -> None: """Test control flow using externally-set env vars.""" - def _rex(): + def _rex() -> None: if defined("EXT") and env.EXT == "alpha": env.EXT_FOUND = 1 env.EXT.append("beta") # will still overwrite @@ -228,9 +228,9 @@ def _rex(): 'EXT_FOUND': '1', 'EXT': 'beta'}) - def test_6(self): + def test_6(self) -> None: """Test variable expansion.""" - def _rex(): + def _rex() -> None: env.FOO = "foo" env.DOG = "$FOO" # this will convert to '${FOO}' env.BAH = "${FOO}" @@ -273,9 +273,9 @@ def _rex(): 'EEK': 'foo', 'FEE': 'alpha'}) - def test_7(self): + def test_7(self) -> None: """Test exceptions.""" - def _rex1(): + def _rex1() -> None: # reference to undefined var getenv("NOTEXIST") @@ -283,7 +283,7 @@ def _rex1(): env={}, expected_exception=RexUndefinedVariableError) - def _rex2(): + def _rex2() -> None: # reference to undefined var info(env.NOTEXIST) @@ -299,12 +299,12 @@ def _rex3(): env={}, expected_exception=RexError) - def test_8(self): + def test_8(self) -> None: """Custom environment variable separators.""" config.override("env_var_separators", {"FOO": ",", "BAH": " "}) - def _rex(): + def _rex() -> None: appendenv("FOO", "test1") env.FOO.append("test2") env.FOO.append("test3") @@ -326,15 +326,15 @@ def _rex(): 'FOO': ",".join(["test1", "test2", "test3"]), 'BAH': " ".join(["B", "A", "C"])}) - def test_9(self): + def test_9(self) -> None: """Test literal and expandable strings.""" - def _rex(): + def _rex() -> None: env.A = "hello" env.FOO = expandable("$A") # will convert to '${A}' env.BAH = expandable("${A}") env.EEK = literal("$A") - def _rex2(): + def _rex2() -> None: env.BAH = "omg" env.FOO.append("$BAH") env.FOO.append(literal("${BAH}")) @@ -364,10 +364,10 @@ def _rex2(): 'BAH': 'omg', 'FOO': os.pathsep.join(['omg', '${BAH}', 'like']) + ', $SHE said, omg'}) - def test_10(self): + def test_10(self) -> None: """Test env __contains__ and __bool__""" - def _test(func, env, expected): + def _test(func, env, expected) -> None: ex = self._create_executor(env=env) self.assertEqual(expected, ex.execute_function(func)) @@ -390,7 +390,7 @@ def _rex_3(): _test(_rex_2, env={"A": "foo"}, expected={"A": True, "B": False}) _test(_rex_3, env={}, expected="not b") - def test_version_binding(self): + def test_version_binding(self) -> None: """Test the Rex binding of the Version class.""" v = VersionBinding(Version("1.2.3alpha")) self.assertEqual(v.major, 1) @@ -403,7 +403,7 @@ def test_version_binding(self): self.assertEqual(v[5], None) self.assertEqual(v.as_tuple(), (1, 2, "3alpha")) - def test_old_style_commands(self): + def test_old_style_commands(self) -> None: """Convert old style commands to rex""" expected = "" rez_commands = convert_old_commands([], annotate=False) @@ -440,7 +440,7 @@ def test_old_style_commands(self): annotate=False) self.assertEqual(rez_commands, expected) - def test_intersects_resolve(self): + def test_intersects_resolve(self) -> None: """Test intersects with resolve object""" resolved_pkg_data = { "foo": {"1": {"name": "foo", "version": "1"}}, @@ -467,7 +467,7 @@ def test_intersects_resolve(self): self.assertTrue(intersects(resolve.maya, "2019+")) self.assertFalse(intersects(resolve.maya, "<=2019")) - def test_intersects_request(self): + def test_intersects_request(self) -> None: """Test intersects with request object""" # request.get request = RequirementsBinding([Requirement("foo.bar-1")]) @@ -503,7 +503,7 @@ def test_intersects_request(self): foo = intersects(request.get_range("foo", "==1.2.3"), "1.4") self.assertTrue(foo) - def test_intersects_ephemerals(self): + def test_intersects_ephemerals(self) -> None: """Test intersects with ephemerals object""" # ephemerals.get ephemerals = EphemeralsBinding([Requirement(".foo.bar-1")]) diff --git a/src/rez/tests/test_shells.py b/src/rez/tests/test_shells.py index 504615f6d..44eae5248 100644 --- a/src/rez/tests/test_shells.py +++ b/src/rez/tests/test_shells.py @@ -35,7 +35,7 @@ def _stdout(proc): class TestShells(TestBase, TempdirMixin): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: TempdirMixin.setUpClass() packages_path = os.path.join(cls.root, "packages") @@ -49,7 +49,7 @@ def setUpClass(cls): warn_untimestamped=False) @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: TempdirMixin.tearDownClass() @classmethod @@ -100,7 +100,7 @@ def test_aaa_shell_presence(self): ) @per_available_shell() - def test_no_output(self, shell): + def test_no_output(self, shell) -> None: sh = create_shell(shell) _, _, _, command = sh.startup_capabilities(command=True) if command: @@ -114,7 +114,7 @@ def test_no_output(self, shell): "startup scripts are printing to stdout. Please remove the " "printout and try again.") - def test_create_executable_script(self): + def test_create_executable_script(self) -> None: script_file = os.path.join(self.root, "script") py_script_file = os.path.join(self.root, "script.py") @@ -164,7 +164,7 @@ def test_create_executable_script(self): self.assertListEqual(files, [py_script_file]) @per_available_shell() - def test_command(self, shell): + def test_command(self, shell) -> None: sh = create_shell(shell) _, _, _, command = sh.startup_capabilities(command=True) @@ -175,7 +175,7 @@ def test_command(self, shell): self.assertEqual(_stdout(p), "Hello Rez World!") @per_available_shell() - def test_per_available_shell_decorator(self, shell): + def test_per_available_shell_decorator(self, shell) -> None: """ Test that the "per_available_shell" decorator correctly sets the default shell and that ResolvedContext.execute_shell will use the default shell as expected. @@ -240,7 +240,7 @@ def test_per_available_shell_decorator(self, shell): data[shell]["assert"](_stdout(p).strip()) @per_available_shell() - def test_command_returncode(self, shell): + def test_command_returncode(self, shell) -> None: sh = create_shell(shell) _, _, _, command = sh.startup_capabilities(command=True) @@ -287,7 +287,7 @@ def actions_callback(ex): self.assertEqual(p.returncode, 0) @per_available_shell() - def test_norc(self, shell): + def test_norc(self, shell) -> None: sh = create_shell(shell) _, norc, _, command = sh.startup_capabilities(norc=True, command=True) @@ -299,7 +299,7 @@ def test_norc(self, shell): self.assertEqual(_stdout(p), "Hello Rez World!") @per_available_shell() - def test_stdin(self, shell): + def test_stdin(self, shell) -> None: sh = create_shell(shell) _, _, stdin, _ = sh.startup_capabilities(stdin=True) @@ -313,7 +313,7 @@ def test_stdin(self, shell): self.assertEqual(stdout, "Hello Rez World!") @per_available_shell() - def test_rcfile(self, shell): + def test_rcfile(self, shell) -> None: sh = create_shell(shell) rcfile, _, _, command = sh.startup_capabilities(rcfile=True, command=True) @@ -336,7 +336,7 @@ def test_rcfile(self, shell): # @per_available_shell(exclude=["cmd"]) @install_dependent() - def test_rez_env_output(self, shell): + def test_rez_env_output(self, shell) -> None: def _test(txt): # Assumes that the shell has an echo command, built-in or alias @@ -374,7 +374,7 @@ def _test(txt): @per_available_shell() @install_dependent() - def test_rez_command(self, shell): + def test_rez_command(self, shell) -> None: sh = create_shell(shell) _, _, _, command = sh.startup_capabilities(command=True) @@ -389,13 +389,13 @@ def test_rez_command(self, shell): self.assertEqual(p.returncode, 0) @per_available_shell() - def test_rex_code(self, shell): + def test_rex_code(self, shell) -> None: """Test that Rex code run in the shell creates the environment variable values that we expect. """ config.override("default_shell", shell) - def _execute_code(func, expected_output): + def _execute_code(func, expected_output) -> None: loc = inspect.getsourcelines(func)[0][1:] code = textwrap.dedent('\n'.join(loc)) r = self._create_context([]) @@ -407,11 +407,11 @@ def _execute_code(func, expected_output): output = out.strip().split("\n") self.assertEqual(output, expected_output) - def _rex_assigning(): + def _rex_assigning() -> None: from rez.shells import create_shell sh = create_shell() - def _print(value): + def _print(value) -> None: env.FOO = value # Wrap the output in quotes to prevent the shell from # interpreting parts of our output as commands. This can happen @@ -515,7 +515,7 @@ def _print(value): _execute_code(_rex_assigning, expected_output) - def _rex_appending(): + def _rex_appending() -> None: from rez.shells import create_shell sh = create_shell() @@ -535,7 +535,7 @@ def _rex_appending(): _execute_code(_rex_appending, expected_output) - def _rex_prepending(): + def _rex_prepending() -> None: from rez.shells import create_shell sh = create_shell() @@ -556,7 +556,7 @@ def _rex_prepending(): _execute_code(_rex_prepending, expected_output) @per_available_shell() - def test_rex_code_alias(self, shell): + def test_rex_code_alias(self, shell) -> None: """Ensure PATH changes do not influence the alias command. This is important for Windows because the doskey.exe might not be on @@ -566,7 +566,7 @@ def test_rex_code_alias(self, shell): """ config.override("default_shell", shell) - def _execute_code(func): + def _execute_code(func) -> None: loc = inspect.getsourcelines(func)[0][1:] code = textwrap.dedent('\n'.join(loc)) r = self._create_context([]) @@ -575,7 +575,7 @@ def _execute_code(func): out, _ = p.communicate() self.assertEqual(p.returncode, 0) - def _alias_after_path_manipulation(): + def _alias_after_path_manipulation() -> None: # Appending something to the PATH and creating an alias afterwards # did fail before we implemented a doskey specific fix. env.PATH.append("hey") @@ -586,7 +586,7 @@ def _alias_after_path_manipulation(): _execute_code(_alias_after_path_manipulation) @per_available_shell() - def test_alias_command(self, shell): + def test_alias_command(self, shell) -> None: """Testing alias can be passed in as command This is important for Windows CMD shell because the doskey.exe isn't @@ -594,7 +594,7 @@ def test_alias_command(self, shell): """ config.override("default_shell", shell) - def _make_alias(ex): + def _make_alias(ex) -> None: ex.alias('hi', 'echo "hi"') r = self._create_context([]) @@ -606,7 +606,7 @@ def _make_alias(ex): self.assertEqual(0, p.returncode) @per_available_shell() - def test_alias_command_with_args(self, shell): + def test_alias_command_with_args(self, shell) -> None: """Testing alias can be passed in as command with args This is important for Windows CMD shell because the doskey.exe isn't @@ -614,7 +614,7 @@ def test_alias_command_with_args(self, shell): """ config.override("default_shell", shell) - def _make_alias(ex): + def _make_alias(ex) -> None: ex.alias('tell', 'echo') r = self._create_context([]) @@ -626,11 +626,11 @@ def _make_alias(ex): self.assertEqual(0, p.returncode) @per_available_shell() - def test_alias_return_code(self, shell): + def test_alias_return_code(self, shell) -> None: """Ensure return codes are correct while using aliases.""" config.override("default_shell", shell) - def _make_alias(ex): + def _make_alias(ex) -> None: ex.alias('my_alias', 'hello_world -r 1') r = self._create_context(["hello_world"]) diff --git a/src/rez/tests/test_solver.py b/src/rez/tests/test_solver.py index 11c3c4ce9..f076067eb 100644 --- a/src/rez/tests/test_solver.py +++ b/src/rez/tests/test_solver.py @@ -19,7 +19,7 @@ class TestSolver(TestBase): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: packages_path = cls.data_path("solver", "packages") cls.packages_path = [packages_path] cls.settings = dict( @@ -107,7 +107,7 @@ def _fail(self, *packages): return s1 - def test_01(self): + def test_01(self) -> None: """Extremely basic solves involving a single package.""" self._solve([], []) @@ -134,7 +134,7 @@ def test_01(self): self._solve(["!python"], []) - def test_02(self): + def test_02(self) -> None: """Basic solves involving a single package.""" self._solve(["nada", "~nada"], ["nada[]"]) @@ -155,7 +155,7 @@ def test_02(self): self._solve(["!python-2.6+", "python"], ["python-2.5.2[]"]) - def test_03(self): + def test_03(self) -> None: """Failures in the initial request.""" self._fail("nada", "!nada") self._fail("python-2.6", "~python-2.7") @@ -163,19 +163,19 @@ def test_03(self): self._fail(".foo-1", ".foo-2") self._fail(".foo-2.5", "!.foo-2") - def test_04(self): + def test_04(self) -> None: """Basic failures.""" self._fail("pybah", "!python") self._fail("pyfoo-3.1", "python-2.7+") self._fail("pyodd<2", "python-2.7") self._fail("nopy", "python-2.5.2") - def test_05(self): + def test_05(self) -> None: """More complex failures.""" self._fail("bahish", "pybah<5") self._fail("pybah-4", "pyfoo-3.0") - def test_06(self): + def test_06(self) -> None: """Basic solves involving multiple packages.""" self._solve(["nada", "nopy"], ["nada[]", "nopy-2.1[]"]) @@ -196,7 +196,7 @@ def test_06(self): self._solve(["python", "pybah"], ["python-2.6.8[]", "pybah-4[]", ".eek-1"]) - def test_07(self): + def test_07(self) -> None: """More complex solves.""" self._solve(["python", "pyodd"], ["python-2.6.8[]", "pybah-4[]", "pyodd-2[]", ".eek-1"]) @@ -211,10 +211,10 @@ def test_07(self): self._solve([".foo-2.5+", ".foo-2"], [".foo-2.5+<2_"]) - def test_08(self): + def test_08(self) -> None: """Cyclic failures.""" - def _test(*pkgs): + def _test(*pkgs) -> None: s = self._fail(*pkgs) self.assertTrue(isinstance(s.failure_reason(), Cycle)) @@ -228,27 +228,27 @@ def _test(*pkgs): # variant tests - def test_09_version_priority_mode(self): + def test_09_version_priority_mode(self) -> None: config.override("variant_select_mode", "version_priority") self._solve(["pyvariants", "python"], ["python-2.7.0[]", "pyvariants-2[0]"]) self._solve(["pyvariants", "python", "nada"], ["python-2.7.0[]", "pyvariants-2[0]", "nada[]"]) - def test_10_intersection_priority_mode(self): + def test_10_intersection_priority_mode(self) -> None: config.override("variant_select_mode", "intersection_priority") self._solve(["pyvariants", "python"], ["python-2.7.0[]", "pyvariants-2[0]"]) self._solve(["pyvariants", "python", "nada"], ["python-2.6.8[]", "nada[]", "pyvariants-2[1]"]) - def test_11_variant_splitting(self): + def test_11_variant_splitting(self) -> None: self._solve(["test_variant_split_start"], ["test_variant_split_end-1.0[1]", "test_variant_split_mid2-2.0[0]", "test_variant_split_start-1.0[1]"]) - def test_12_missing_variant_requires(self): + def test_12_missing_variant_requires(self) -> None: config.override("error_on_missing_variant_requires", True) with self.assertRaises(rez.exceptions.PackageFamilyNotFoundError): self._solve(["missing_variant_requires"], []) @@ -256,14 +256,14 @@ def test_12_missing_variant_requires(self): config.override("error_on_missing_variant_requires", False) self._solve(["missing_variant_requires"], ["nada[]", "missing_variant_requires-1[1]"]) - def test_13_resolve_weakly_reference_requires(self): + def test_13_resolve_weakly_reference_requires(self) -> None: """Test resolving a package with a weakly referenced requirement.""" self._solve(["test_weakly_reference_requires", "test_variant_split_mid2-2"], ['test_weakly_reference_requires-2.0[]', 'test_variant_split_end-3.0[0]', 'test_variant_split_mid2-2.0[1]']) - def test_14_resolve_weakly_reference_variant(self): + def test_14_resolve_weakly_reference_variant(self) -> None: """Test resolving a package with a weakly referenced variant.""" self._solve(["test_weakly_reference_variant-2.0", "test_variant_split_mid2-2", "pyfoo"], ['test_variant_split_end-1.0[1]', diff --git a/src/rez/tests/test_suites.py b/src/rez/tests/test_suites.py index e2ada77a5..8b4b20351 100644 --- a/src/rez/tests/test_suites.py +++ b/src/rez/tests/test_suites.py @@ -19,7 +19,7 @@ class TestRezSuites(TestBase, TempdirMixin): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: TempdirMixin.setUpClass() packages_path = cls.data_path("suites", "packages") @@ -31,10 +31,10 @@ def setUpClass(cls): resolve_caching=False) @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: TempdirMixin.tearDownClass() - def _test_serialization(self, suite): + def _test_serialization(self, suite) -> None: name = uuid.uuid4().hex path = os.path.join(self.root, name) suite.save(path) @@ -42,14 +42,14 @@ def _test_serialization(self, suite): self.assertEqual(suite.get_tools(), suite2.get_tools()) self.assertEqual(set(suite.context_names), set(suite2.context_names)) - def test_1(self): + def test_1(self) -> None: """Test empty suite.""" s = Suite() tools = s.get_tools() self.assertEqual(tools, {}) self._test_serialization(s) - def test_2(self): + def test_2(self) -> None: """Test basic suite.""" c_foo = ResolvedContext(["foo"]) c_bah = ResolvedContext(["bah"]) @@ -101,7 +101,7 @@ def test_2(self): self._test_serialization(s) - def test_3(self): + def test_3(self) -> None: """Test tool clashes in a suite.""" c_foo = ResolvedContext(["foo"]) c_bah = ResolvedContext(["bah"]) @@ -143,7 +143,7 @@ def test_3(self): @per_available_shell() @install_dependent() - def test_executable(self, shell): + def test_executable(self, shell) -> None: """Test suite tool can be executed Testing suite tool can be found and executed in multiple platforms. diff --git a/src/rez/tests/test_test.py b/src/rez/tests/test_test.py index 1a9ccc674..8d104ca74 100644 --- a/src/rez/tests/test_test.py +++ b/src/rez/tests/test_test.py @@ -12,7 +12,7 @@ class TestTest(TestBase, TempdirMixin): @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: TempdirMixin.setUpClass() packages_path = cls.data_path("builds", "packages") @@ -25,23 +25,23 @@ def setUpClass(cls): ) @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: TempdirMixin.tearDownClass() - def test_1(self): + def test_1(self) -> None: """package.py unit tests are correctly run in a testing environment""" self.inject_python_repo() context = ResolvedContext(["testing_obj", "python"]) self._run_tests(context) - def test_2(self): + def test_2(self) -> None: """package.py unit tests are correctly run in a testing environment when no verbosity is set""" self.inject_python_repo() context = ResolvedContext(["testing_obj", "python"]) # This will get us more code coverage :) self._run_tests(context, verbose=0) - def _run_tests(self, r, verbose=2): + def _run_tests(self, r, verbose: int=2) -> None: """Run unit tests in package.py""" self.inject_python_repo() runner = PackageTestRunner( diff --git a/src/rez/tests/test_util.py b/src/rez/tests/test_util.py index 3caeeecc1..1f780149c 100644 --- a/src/rez/tests/test_util.py +++ b/src/rez/tests/test_util.py @@ -22,7 +22,7 @@ def setUpClass(cls): def tearDownClass(cls): TempdirMixin.tearDownClass() - def test_load_module(self): + def test_load_module(self) -> None: """Ensure that the imported module does not show up in sys.modules""" # Random chars are used in the module name to ensure that the module name is unique # and the test won't fail because some other module with the same name diff --git a/src/rez/tests/test_utils.py b/src/rez/tests/test_utils.py index 7be41d903..42be6bbb6 100644 --- a/src/rez/tests/test_utils.py +++ b/src/rez/tests/test_utils.py @@ -14,15 +14,15 @@ class TestCanonicalPath(TestBase): class CaseSensitivePlatform(Platform): @property - def has_case_sensitive_filesystem(self): + def has_case_sensitive_filesystem(self) -> bool: return True class CaseInsensitivePlatform(Platform): @property - def has_case_sensitive_filesystem(self): + def has_case_sensitive_filesystem(self) -> bool: return False - def test_win32_case_insensitive(self): + def test_win32_case_insensitive(self) -> None: if platform_.name != 'windows': self.skipTest('on linux/macos, `os.path.realpath()` treats windows ' 'abspaths as relpaths, and prepends `os.getcwd()`') @@ -31,7 +31,7 @@ def test_win32_case_insensitive(self): expects = 'c:\\dir\\file.txt'.replace('\\', os.sep) self.assertEqual(path, expects) - def test_unix_case_sensistive_platform(self): + def test_unix_case_sensistive_platform(self) -> None: if platform_.name == 'windows': self.skipTest('on windows, `os.path.realpath()` treats unix abspaths ' 'as relpaths, and prepends `os.getcwd()`') @@ -40,7 +40,7 @@ def test_unix_case_sensistive_platform(self): expects = '/a/b/File.txt'.replace('\\', os.sep) self.assertEqual(path, expects) - def test_unix_case_insensistive_platform(self): + def test_unix_case_insensistive_platform(self) -> None: if platform_.name == 'windows': self.skipTest('on windows, `os.path.realpath()` treats unix abspaths ' 'as relpaths, and prepends `os.getcwd()`') diff --git a/src/rez/tests/test_utils_elf.py b/src/rez/tests/test_utils_elf.py index b04a906d5..f3117f80b 100644 --- a/src/rez/tests/test_utils_elf.py +++ b/src/rez/tests/test_utils_elf.py @@ -14,20 +14,20 @@ class TestElfUtils(TestBase): - def __init__(self, *nargs, **kwargs): + def __init__(self, *nargs, **kwargs) -> None: super().__init__(*nargs, **kwargs) @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: super().setUpClass() @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: super().tearDownClass() @unittest.skipUnless(platform.system() == "Linux", "Linux only") @program_dependent("readelf") - def test_get_rpaths_raises_runtime_exception(self): + def test_get_rpaths_raises_runtime_exception(self) -> None: """Tests that no TypeError from elf functions are raised.""" with self.assertRaises(RuntimeError) as exc: get_rpaths("/path/to/elfpath") diff --git a/src/rez/tests/test_utils_filesystem.py b/src/rez/tests/test_utils_filesystem.py index cccbfe499..99cbe6087 100644 --- a/src/rez/tests/test_utils_filesystem.py +++ b/src/rez/tests/test_utils_filesystem.py @@ -17,20 +17,20 @@ class TestFileSystem(TestBase, TempdirMixin): - def __init__(self, *nargs, **kwargs): + def __init__(self, *nargs, **kwargs) -> None: super().__init__(*nargs, **kwargs) @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: super().setUpClass() TempdirMixin.setUpClass() @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: super().tearDownClass() TempdirMixin.tearDownClass() - def test_windows_rename_fallback_to_robocopy(self): + def test_windows_rename_fallback_to_robocopy(self) -> None: if platform_.name != 'windows': self.skipTest('Robocopy is only available on windows.') src = tempfile.mkdtemp(dir=self.root) @@ -41,7 +41,7 @@ def test_windows_rename_fallback_to_robocopy(self): self.assertTrue(os.path.exists(dst)) self.assertFalse(os.path.exists(src)) - def test_windows_robocopy_failed(self): + def test_windows_robocopy_failed(self) -> None: if platform_.name != 'windows': self.skipTest('Robocopy is only available on windows.') src = tempfile.mkdtemp(dir=self.root) @@ -54,7 +54,7 @@ def test_windows_robocopy_failed(self): filesystem.rename(src, dst) self.assertEqual(str(err.exception), "Rename {} to {} failed.".format(src, dst)) - def test_rename_folder_with_permission_error_and_no_robocopy(self): + def test_rename_folder_with_permission_error_and_no_robocopy(self) -> None: src = tempfile.mkdtemp(dir=self.root) dst = tempfile.mkdtemp(dir=self.root) with unittest.mock.patch("os.rename") as mock_rename: @@ -65,7 +65,7 @@ def test_rename_folder_with_permission_error_and_no_robocopy(self): filesystem.rename(src, dst) self.assertEqual(str(err.exception), "Permission denied") - def test_rename_folder_with_permission_error_and_src_is_file(self): + def test_rename_folder_with_permission_error_and_src_is_file(self) -> None: src = tempfile.mktemp(dir=self.root) dst = tempfile.mktemp(dir=self.root) with open(src, "w") as file_: @@ -78,7 +78,7 @@ def test_rename_folder_with_permission_error_and_src_is_file(self): self.assertFalse(os.path.exists(dst)) self.assertTrue(os.path.exists(src)) - def test_rename_file(self): + def test_rename_file(self) -> None: src = tempfile.mktemp(dir=self.root) dst = tempfile.mktemp(dir=self.root) with open(src, "w") as file_: diff --git a/src/rez/tests/test_utils_formatting.py b/src/rez/tests/test_utils_formatting.py index a63388f3c..e5a2cb07d 100644 --- a/src/rez/tests/test_utils_formatting.py +++ b/src/rez/tests/test_utils_formatting.py @@ -10,7 +10,7 @@ class TestFormatting(TestBase): - def test_readable_units(self): + def test_readable_units(self) -> None: readable_units = formatting._readable_units( 0, formatting.memory_divs diff --git a/src/rez/tests/test_utils_resolve_graph.py b/src/rez/tests/test_utils_resolve_graph.py index 83f726787..bc0a25e8d 100644 --- a/src/rez/tests/test_utils_resolve_graph.py +++ b/src/rez/tests/test_utils_resolve_graph.py @@ -12,7 +12,7 @@ class TestResolveGraph(TestBase): - def test_conflict_graph_with_cycle(self): + def test_conflict_graph_with_cycle(self) -> None: """ Tests creating a test digraph which contains a cycle foo-1.0.0 => bar-0.0.1 => !foo-1.0.0 Note that the solver doesn't detect this as a cycle. See #1568 """ diff --git a/src/rez/tests/test_version.py b/src/rez/tests/test_version.py index 72c26b612..9e461213b 100644 --- a/src/rez/tests/test_version.py +++ b/src/rez/tests/test_version.py @@ -16,7 +16,7 @@ from rez.version import VersionError -def _print(txt=''): +def _print(txt='') -> None: # uncomment for verbose output #print txt pass @@ -25,10 +25,10 @@ def _print(txt=''): class TestVersionSchema(unittest.TestCase): make_token = AlphanumericVersionToken - def __init__(self, fn): + def __init__(self, fn) -> None: unittest.TestCase.__init__(self, fn) - def _test_strict_weak_ordering(self, a, b): + def _test_strict_weak_ordering(self, a, b) -> None: self.assertTrue(a == a) self.assertTrue(b == b) @@ -66,8 +66,8 @@ def _test_strict_weak_ordering(self, a, b): self._test_strict_weak_ordering(reverse_sort_key(a), reverse_sort_key(b)) - def _test_ordered(self, items): - def _test(fn, items_, op_str): + def _test_ordered(self, items) -> None: + def _test(fn, items_, op_str) -> None: for i, a in enumerate(items_): for b in items_[i + 1:]: _print("'%s' %s '%s'" % (a, op_str, b)) @@ -89,10 +89,10 @@ def _create_random_version(self): for i in range(random.randint(0, 6))) return Version(ver_str, make_token=self.make_token) - def test_misc(self): + def test_misc(self) -> None: self.assertEqual(Version("1.2.12").as_tuple(), ("1", "2", "12")) - def test_token_strict_weak_ordering(self): + def test_token_strict_weak_ordering(self) -> None: # test equal tokens tok = self._create_random_token() self._test_strict_weak_ordering(tok, tok) @@ -103,7 +103,7 @@ def test_token_strict_weak_ordering(self): tok2 = self._create_random_token() self._test_strict_weak_ordering(tok1, tok2) - def test_version_strict_weak_ordering(self): + def test_version_strict_weak_ordering(self) -> None: # test equal versions ver = self._create_random_version() self._test_strict_weak_ordering(ver, ver) @@ -114,8 +114,8 @@ def test_version_strict_weak_ordering(self): ver2 = self._create_random_version() self._test_strict_weak_ordering(ver1, ver2) - def test_token_comparisons(self): - def _lt(a, b): + def test_token_comparisons(self) -> None: + def _lt(a, b) -> None: _print("'%s' < '%s'" % (a, b)) self.assertTrue(self.make_token(a) < self.make_token(b)) self.assertTrue(Version(a) < Version(b)) @@ -128,8 +128,8 @@ def _lt(a, b): _lt("alpha", "alpha3") _lt("gamma33", "33gamma") - def test_version_comparisons(self): - def _eq(a, b): + def test_version_comparisons(self) -> None: + def _eq(a, b) -> None: _print("'%s' == '%s'" % (a, b)) self.assertTrue(Version(a) == Version(b)) @@ -152,7 +152,7 @@ def _eq(a, b): "2.1.0"] self._test_ordered([Version(x) for x in ascending]) - def _eq2(a, b): + def _eq2(a, b) -> None: _print("'%s' == '%s'" % (a, b)) self.assertTrue(a == b) @@ -169,8 +169,8 @@ def _eq2(a, b): _eq2(set([b, c]) | set([c, d]), set([b, c, d])) _eq2(set([b, c]) & set([c, d]), set([c])) - def test_version_range(self): - def _eq(a, b): + def test_version_range(self) -> None: + def _eq(a, b) -> None: _print("'%s' == '%s'" % (a, b)) a_range = VersionRange(a) b_range = VersionRange(b) @@ -211,7 +211,7 @@ def _eq(a, b): a_range_ = a_ranges[0].union(a_ranges[1:]) self.assertTrue(a_range_ == b_range) - def _and(a, b, c): + def _and(a, b, c) -> None: _print("'%s' & '%s' == '%s'" % (a, b, c)) a_range = VersionRange(a) b_range = VersionRange(b) @@ -227,7 +227,7 @@ def _and(a, b, c): ranges = [x for x in ranges if x] self.assertTrue(ranges[0].union(ranges[1:]) == a_or_b) - def _inv(a, b): + def _inv(a, b) -> None: a_range = VersionRange(a) b_range = VersionRange(b) self.assertTrue(~a_range == b_range) @@ -362,7 +362,7 @@ def _inv(a, b): == VersionRange("==3|==4|==6.0")) # test behaviour in sets - def _eq2(a, b): + def _eq2(a, b) -> None: _print("'%s' == '%s'" % (a, b)) self.assertTrue(a == b) @@ -379,7 +379,7 @@ def _eq2(a, b): _eq2(set([b, c, e]) | set([c, d]), set([b, c, d, e])) _eq2(set([b, c]) & set([c, d]), set([c])) - def test_containment(self): + def test_containment(self) -> None: # basic containment self.assertTrue(Version("3") in VersionRange("3+")) self.assertTrue(Version("5") in VersionRange("3..5")) @@ -426,7 +426,7 @@ def test_containment(self): self.assertEqual(len(matches), count) # more optimal containment tests - def _test_it(it): + def _test_it(it) -> None: matches_ = set(version for contains, version in it if contains) self.assertEqual(matches_, matches) @@ -444,8 +444,8 @@ def _test_it(it): if count: self.assertTrue(composite_range.issuperset(int_range)) - def test_requirement_list(self): - def _eq(reqs, expected_reqs): + def test_requirement_list(self) -> None: + def _eq(reqs, expected_reqs) -> None: _print("requirements(%s) == requirements(%s)" % (' '.join(reqs), ' '.join(expected_reqs))) reqs_ = [Requirement(x) for x in reqs] @@ -461,7 +461,7 @@ def _eq(reqs, expected_reqs): exp_confl_names = set(x.name for x in exp_reqs_ if x.conflict) self.assertTrue(reqlist.conflict_names == exp_confl_names) - def _confl(reqs, a, b): + def _confl(reqs, a, b) -> None: _print("requirements(%s) == %s <--!--> %s" % (' '.join(reqs), a, b)) reqs_ = [Requirement(x) for x in reqs] reqlist = RequirementList(reqs_) diff --git a/src/rez/tests/util.py b/src/rez/tests/util.py index cd66fd967..25a5b0067 100644 --- a/src/rez/tests/util.py +++ b/src/rez/tests/util.py @@ -29,15 +29,15 @@ class TestBase(unittest.TestCase): """Unit test base class.""" - def __init__(self, *nargs, **kwargs): + def __init__(self, *nargs, **kwargs) -> None: super(TestBase, self).__init__(*nargs, **kwargs) self.setup_once_called = False @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: cls.settings = {} - def setUp(self): + def setUp(self) -> None: # We have some tests that unfortunately don't clean themselves up # after they are done. Store the origianl environment to be # restored in tearDown @@ -55,10 +55,10 @@ def setUp(self): self.setup_once() self.setup_once_called = True - def setup_once(self): + def setup_once(self) -> None: pass - def tearDown(self): + def tearDown(self) -> None: self.teardown_config() os.environ = self.__environ # Try to clear as much caches as possible to avoid tests @@ -75,20 +75,20 @@ def data_path(cls, *dirs): # These are moved into their own functions so update_settings can call # them without having to call setUp / tearDown, and without worrying # about future or subclass modifications to those methods... - def setup_config(self): + def setup_config(self) -> None: # to make sure config changes from one test don't affect another, copy # the overrides dict... self._config = _create_locked_config(dict(self.settings)) config._swap(self._config) - def teardown_config(self): + def teardown_config(self) -> None: # moved to it's own section because it's called in update_settings... # so if in the future, tearDown does more than call this, # update_settings is still valid config._swap(self._config) self._config = None - def update_settings(self, new_settings, override=False): + def update_settings(self, new_settings, override: bool = False) -> None: """Can be called within test methods to modify settings on a per-test basis (as opposed cls.settings, which modifies it for all tests on the class) @@ -132,7 +132,7 @@ def get_settings_env(self): for k, v in self.settings.items() ) - def inject_python_repo(self): + def inject_python_repo(self) -> None: self.update_settings( { "packages_path": config.packages_path + [os.environ["__REZ_SELFTEST_PYTHON_REPO"]], @@ -143,11 +143,11 @@ def inject_python_repo(self): class TempdirMixin(object): """Mixin that adds tmpdir create/delete.""" @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: cls.root = tempfile.mkdtemp(prefix="rez_selftest_") @classmethod - def tearDownClass(cls): + def tearDownClass(cls) -> None: if os.getenv("REZ_KEEP_TMPDIRS"): print("Tempdir kept due to $REZ_KEEP_TMPDIRS: %s" % cls.root) return @@ -169,7 +169,7 @@ def tearDownClass(cls): time.sleep(0.2) -def find_file_in_path(to_find, path_str, pathsep=None, reverse=True): +def find_file_in_path(to_find, path_str, pathsep=None, reverse: bool = True): """Attempts to find the given relative path to_find in the given path """ if pathsep is None: diff --git a/src/rez/util.py b/src/rez/util.py index cfdbe6174..a7e8e570b 100644 --- a/src/rez/util.py +++ b/src/rez/util.py @@ -6,6 +6,8 @@ Misc useful stuff. TODO: Move this into rez.utils.? """ +from __future__ import annotations + import collections.abc import atexit import os @@ -16,9 +18,19 @@ from rez.exceptions import RezError from rez.vendor.progress.bar import Bar +from types import ModuleType +from typing import Iterable, TypeVar, TYPE_CHECKING + +if TYPE_CHECKING: + # this is not available in typing until 3.10, but due to __future__.annotations + # we can use it without really importing it + from typing import TypeGuard + +T = TypeVar("T") + class ProgressBar(Bar): - def __init__(self, label, max): + def __init__(self, label, max) -> None: from rez.config import config if config.quiet or not config.show_progress: @@ -30,7 +42,7 @@ def __init__(self, label, max): super(Bar, self).__init__(label, max=max, bar_prefix=' [', bar_suffix='] ') - def __del__(self): + def __del__(self) -> None: if self.close_file: self.file.close() if hasattr(Bar, '__del__'): @@ -49,8 +61,9 @@ def dedup(seq): _find_unsafe = re.compile(r'[^\w@%+=`:,./-]').search -def shlex_join(value, unsafe_regex=None, replacements=None, - enclose_with='"'): +def shlex_join(value: Iterable[str], unsafe_regex=None, + replacements: Iterable[tuple[str | re.Pattern[str], str]] | None = None, + enclose_with: str = '"') -> str: """Join args into a valid shell command. """ @@ -78,7 +91,7 @@ def escape_word(s): # returns path to first program in the list to be successfully found -def which(*programs, **shutilwhich_kwargs): +def which(*programs, **shutilwhich_kwargs) -> str | None: from rez.utils.which import which as which_ for prog in programs: @@ -89,7 +102,7 @@ def which(*programs, **shutilwhich_kwargs): # case-insensitive fuzzy string match -def get_close_matches(term, fields, fuzziness=0.4, key=None): +def get_close_matches(term: str, fields, fuzziness: float=0.4, key=None): import math import difflib @@ -117,7 +130,7 @@ def _ratio(a, b): # fuzzy string matching on package names, such as 'boost', 'numpy-3.4' -def get_close_pkgs(pkg, pkgs, fuzziness=0.4): +def get_close_pkgs(pkg, pkgs, fuzziness: float=0.4): matches = get_close_matches(pkg, pkgs, fuzziness=fuzziness) fam_matches = get_close_matches(pkg.split('-')[0], pkgs, fuzziness=fuzziness, @@ -144,7 +157,7 @@ def find_last_sublist(list_, sublist): @atexit.register -def _atexit(): +def _atexit() -> None: try: from rez.resolved_context import ResolvedContext ResolvedContext.tmpdir_manager.clear() @@ -152,7 +165,7 @@ def _atexit(): pass -def is_non_string_iterable(arg): +def is_non_string_iterable(arg: str | Iterable[str] | None) -> TypeGuard[Iterable[str]]: """Python 2 and 3 compatible non-string iterable identifier""" return ( isinstance(arg, collections.abc.Iterable) @@ -169,7 +182,7 @@ def get_function_arg_names(func): return spec.args + spec.kwonlyargs -def load_module_from_file(name, filepath): +def load_module_from_file(name: str, filepath: str) -> ModuleType: """Load a python module from a sourcefile. Args: diff --git a/src/rez/utils/__init__.py b/src/rez/utils/__init__.py index 58a0da7ec..fbf44b704 100644 --- a/src/rez/utils/__init__.py +++ b/src/rez/utils/__init__.py @@ -4,6 +4,7 @@ import sys from contextlib import contextmanager +from typing import NoReturn @contextmanager @@ -11,11 +12,11 @@ def with_noop(): yield -def reraise(exc, new_exc_cls): +def reraise(exc, new_exc_cls) -> NoReturn: traceback = sys.exc_info()[2] # TODO test this. - def reraise_(tp, value, tb=None): + def reraise_(tp, value, tb=None) -> NoReturn: try: if value is None: value = tp() diff --git a/src/rez/utils/amqp.py b/src/rez/utils/amqp.py index c26e875f7..980dc5e8b 100644 --- a/src/rez/utils/amqp.py +++ b/src/rez/utils/amqp.py @@ -25,7 +25,7 @@ _num_pending = 0 -def publish_message(host, amqp_settings, routing_key, data, block=True): +def publish_message(host, amqp_settings, routing_key, data, block: bool = True): """Publish an AMQP message. Returns: @@ -58,7 +58,7 @@ def publish_message(host, amqp_settings, routing_key, data, block=True): return True -def _publish_message(host, amqp_settings, routing_key, data): +def _publish_message(host, amqp_settings, routing_key, data) -> bool: """Publish an AMQP message. Returns: @@ -123,7 +123,7 @@ def _publish_message(host, amqp_settings, routing_key, data): return True -def _publish_messages_async(): +def _publish_messages_async() -> None: global _num_pending while True: @@ -137,7 +137,7 @@ def _publish_messages_async(): @atexit.register -def on_exit(): +def on_exit() -> None: # Give pending messages a chance to publish, otherwise a command like # 'rez-env --output ...' could exit before the publish. # @@ -159,7 +159,7 @@ def parse_host_and_port(url): return host, port -def set_pika_log_level(): +def set_pika_log_level() -> None: mod_name = "rez.vendor.pika" if config.debug("context_tracking"): diff --git a/src/rez/utils/backcompat.py b/src/rez/utils/backcompat.py index 3afd42e3b..636cc1e5d 100644 --- a/src/rez/utils/backcompat.py +++ b/src/rez/utils/backcompat.py @@ -5,6 +5,8 @@ """ Utility code for supporting earlier Rez data in later Rez releases. """ +from __future__ import annotations + import re import os import os.path @@ -52,7 +54,7 @@ def convert_old_command_expansions(command): within_unescaped_quotes_regex = re.compile('(? str: """Converts old-style package commands into equivalent Rex code.""" from rez.config import config from rez.utils.logging_ import print_debug diff --git a/src/rez/utils/base26.py b/src/rez/utils/base26.py index 3972d26ab..ba286f1d9 100644 --- a/src/rez/utils/base26.py +++ b/src/rez/utils/base26.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + import errno import os import os.path @@ -10,7 +12,7 @@ from rez.utils.filesystem import find_matching_symlink -def get_next_base26(prev=None): +def get_next_base26(prev: str | None = None) -> str: """Increment letter-based IDs. Generates IDs like ['a', 'b', ..., 'z', 'aa', ab', ..., 'az', 'ba', ...] @@ -31,7 +33,7 @@ def get_next_base26(prev=None): return get_next_base26(prev[:-1]) + 'a' -def create_unique_base26_symlink(path, source): +def create_unique_base26_symlink(path: str, source: str) -> str: """Create a base-26 symlink in `path` pointing to `source`. If such a symlink already exists, it is returned. Note that there is a small diff --git a/src/rez/utils/colorize.py b/src/rez/utils/colorize.py index df0469662..7b8b5072c 100644 --- a/src/rez/utils/colorize.py +++ b/src/rez/utils/colorize.py @@ -2,10 +2,16 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + import sys import logging + +from rez.utils.typing import SupportsWrite from rez.vendor import colorama +from typing import Callable + # Important - we don't want to init Colorama at startup, # because colorama prints a RESET_ALL character at exit. This in turn adds # unexpected output when capturing the output of a command run in a @@ -29,7 +35,7 @@ def stream_is_tty(stream): return isatty and isatty() -def critical(str_): +def critical(str_: str) -> str: """ Return the string wrapped with the appropriate styling of a critical message. The styling will be determined based on the rez configuration. @@ -184,7 +190,7 @@ def notset(str_): return _color(str_) -def _color_level(str_, level): +def _color_level(str_, level) -> str: """ Return the string wrapped with the appropriate styling for the message level. The styling will be determined based on the rez configuration. @@ -200,7 +206,7 @@ def _color_level(str_, level): return _color(str_, fore_color, back_color, styles) -def _color(str_, fore_color=None, back_color=None, styles=None): +def _color(str_, fore_color=None, back_color=None, styles=None) -> str: """ Return the string wrapped with the appropriate styling escape sequences. Args: @@ -264,7 +270,7 @@ class ColorizedStreamHandler(logging.StreamHandler): 0: notset, } - def __init__(self, stream=None): + def __init__(self, stream=None) -> None: super(ColorizedStreamHandler, self).__init__(stream) self.stream = colorama_wrap(self.stream) @@ -312,7 +318,7 @@ def emit(self, record): class Printer(object): - def __init__(self, buf=sys.stdout): + def __init__(self, buf: SupportsWrite = sys.stdout) -> None: from rez.config import config # Avoid circular import self.colorize = ( config.get("color_enabled", False) == "force" @@ -322,12 +328,12 @@ def __init__(self, buf=sys.stdout): buf = colorama_wrap(buf) self.buf = buf - def __call__(self, msg='', style=None): + def __call__(self, msg='', style=None) -> None: print(self.get(msg, style), file=self.buf) if hasattr(self.buf, 'flush'): self.buf.flush() - def get(self, msg, style=None): + def get(self, msg: str, style: Callable[[str], str] | None = None) -> str: if style and self.colorize: msg = style(msg) return msg diff --git a/src/rez/utils/data_utils.py b/src/rez/utils/data_utils.py index e5f4c009e..6735950e1 100644 --- a/src/rez/utils/data_utils.py +++ b/src/rez/utils/data_utils.py @@ -5,6 +5,8 @@ """ Utilities related to managing data types. """ +from __future__ import annotations + import os.path import json import functools @@ -12,6 +14,9 @@ from rez.vendor.schema.schema import Schema, Optional from threading import Lock +from typing import Any, Callable, Generic, TypeVar, TYPE_CHECKING + +T = TypeVar("T") class ModifyList(object): @@ -20,7 +25,7 @@ class ModifyList(object): This can be used in configs to add to list-based settings, rather than overwriting them. """ - def __init__(self, append=None, prepend=None): + def __init__(self, append=None, prepend=None) -> None: for v in (prepend, append): if v is not None and not isinstance(v, list): raise ValueError("Expected list in ModifyList, not %r" % v) @@ -45,10 +50,10 @@ class DelayLoad(object): - yaml (``*.yaml``, ``*.yml``) - json (``*.json``) """ - def __init__(self, filepath): + def __init__(self, filepath) -> None: self.filepath = os.path.expanduser(filepath) - def __str__(self): + def __str__(self) -> str: return "%s(%s)" % (self.__class__.__name__, self.filepath) def get_value(self): @@ -95,7 +100,7 @@ def remove_nones(**kwargs): return dict((k, v) for k, v in kwargs.items() if v is not None) -def deep_update(dict1, dict2): +def deep_update(dict1, dict2) -> None: """Perform a deep merge of `dict2` into `dict1`. Note that `dict2` and any nested dicts are unchanged. @@ -213,53 +218,56 @@ def get_dict_diff_str(d1, d2, title): return '\n'.join(lines) -class cached_property(object): - """Simple property caching descriptor. - - Example: - - >>> class Foo(object): - >>> @cached_property - >>> def bah(self): - >>> print('bah') - >>> return 1 - >>> - >>> f = Foo() - >>> f.bah - bah - 1 - >>> f.bah - 1 - """ - def __init__(self, func, name=None): - self.func = func - # Make sure that Sphinx autodoc can follow and get the docstring from our wrapped function. - functools.update_wrapper(self, func) - self.name = name or func.__name__ - - def __get__(self, instance, owner=None): - if instance is None: - return self - - result = self.func(instance) - try: - setattr(instance, self.name, result) - except AttributeError: - raise AttributeError("can't set attribute %r on %r" - % (self.name, instance)) - return result +if TYPE_CHECKING: + cached_property = property +else: + class cached_property(object): + """Simple property caching descriptor. + + Example: + + >>> class Foo(object): + >>> @cached_property + >>> def bah(self): + >>> print('bah') + >>> return 1 + >>> + >>> f = Foo() + >>> f.bah + bah + 1 + >>> f.bah + 1 + """ + def __init__(self, func, name=None) -> None: + self.func = func + # Make sure that Sphinx autodoc can follow and get the docstring from our wrapped function. + functools.update_wrapper(self, func) + self.name = name or func.__name__ + + def __get__(self, instance, owner=None): + if instance is None: + return self + + result = self.func(instance) + try: + setattr(instance, self.name, result) + except AttributeError: + raise AttributeError("can't set attribute %r on %r" + % (self.name, instance)) + return result - # This is to silence Sphinx that complains that cached_property is not a callable. - def __call__(self): - raise RuntimeError("@cached_property should not be called.") + # This is to silence Sphinx that complains that cached_property is not a callable. + def __call__(self): + raise RuntimeError("@cached_property should not be called.") - @classmethod - def uncache(cls, instance, name): - if hasattr(instance, name): - delattr(instance, name) + @classmethod + def uncache(cls, instance, name) -> None: + if hasattr(instance, name): + delattr(instance, name) -class cached_class_property(object): +class cached_class_property(Generic[T]): """Simple class property caching descriptor. Example: @@ -276,13 +284,13 @@ class cached_class_property(object): >>> Foo.bah 1 """ - def __init__(self, func, name=None): + def __init__(self, func: Callable[[Any], T], name=None) -> None: self.func = func # Make sure that Sphinx autodoc can follow and get the docstring from our wrapped function. # TODO: Doesn't work... - functools.update_wrapper(self, func) + functools.update_wrapper(self, func) # type: ignore[arg-type] - def __get__(self, instance, owner=None): + def __get__(self, instance, owner=None) -> T: assert owner name = "_class_property_" + self.func.__name__ result = getattr(owner, name, KeyError) @@ -290,19 +298,19 @@ def __get__(self, instance, owner=None): if result is KeyError: result = self.func(owner) setattr(owner, name, result) - return result + return result # type: ignore[return-value] -class LazySingleton(object): +class LazySingleton(Generic[T]): """A threadsafe singleton that initialises when first referenced.""" - def __init__(self, instance_class, *nargs, **kwargs): + def __init__(self, instance_class: type[T], *nargs, **kwargs) -> None: self.instance_class = instance_class self.nargs = nargs self.kwargs = kwargs self.lock = Lock() - self.instance = None + self.instance: T | None = None - def __call__(self): + def __call__(self) -> T: if self.instance is None: try: self.lock.acquire() @@ -315,7 +323,7 @@ def __call__(self): return self.instance -class AttrDictWrapper(MutableMapping): +class AttrDictWrapper(MutableMapping[str, Any]): """Wrap a custom dictionary with attribute-based lookup:: >>> d = {'one': 1} @@ -327,14 +335,14 @@ class AttrDictWrapper(MutableMapping): >>> assert dd.one == 1 >>> assert d['one'] == 1 """ - def __init__(self, data=None): + def __init__(self, data=None) -> None: self.__dict__['_data'] = {} if data is None else data @property - def _data(self): + def _data(self) -> dict: return self.__dict__['_data'] - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> Any: if attr.startswith('__') and attr.endswith('__'): d = self.__dict__ else: @@ -345,7 +353,7 @@ def __getattr__(self, attr): raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, attr)) - def __setattr__(self, attr, value): + def __setattr__(self, attr, value) -> None: # For things like '__class__', for instance if attr.startswith('__') and attr.endswith('__'): super(AttrDictWrapper, self).__setattr__(attr, value) @@ -354,25 +362,25 @@ def __setattr__(self, attr, value): def __getitem__(self, key): return self._data[key] - def __setitem__(self, key, value): + def __setitem__(self, key, value) -> None: self._data[key] = value - def __delitem__(self, key): + def __delitem__(self, key) -> None: del self._data[key] - def __contains__(self, key): + def __contains__(self, key) -> bool: return key in self._data def __iter__(self): return iter(self._data) - def __len__(self): + def __len__(self) -> int: return len(self._data) - def __str__(self): + def __str__(self) -> str: return str(self._data) - def __repr__(self): + def __repr__(self) -> str: return "%s(%r)" % (self.__class__.__name__, self._data) def copy(self): @@ -381,7 +389,7 @@ def copy(self): class RO_AttrDictWrapper(AttrDictWrapper): """Read-only version of AttrDictWrapper.""" - def __setattr__(self, attr, value): + def __setattr__(self, attr, value) -> None: self[attr] # may raise 'no attribute' error raise AttributeError("'%s' object attribute '%s' is read-only" % (self.__class__.__name__, attr)) @@ -623,7 +631,7 @@ def _defined(x): @classmethod def _make_validate_data(cls): - def func(self): + def func(self) -> None: self.validated_data() return func diff --git a/src/rez/utils/diff_packages.py b/src/rez/utils/diff_packages.py index 6e949d00a..d4018e616 100644 --- a/src/rez/utils/diff_packages.py +++ b/src/rez/utils/diff_packages.py @@ -2,7 +2,9 @@ # Copyright Contributors to the Rez Project -from rez.packages import iter_packages +from __future__ import annotations + +from rez.packages import iter_packages, Package from rez.config import config from rez.plugin_managers import plugin_manager from rez.exceptions import RezError @@ -11,7 +13,7 @@ import os.path -def diff_packages(pkg1, pkg2=None): +def diff_packages(pkg1: Package, pkg2: Package | None = None) -> None: """Invoke a diff editor to show the difference between the source of two packages. diff --git a/src/rez/utils/elf.py b/src/rez/utils/elf.py index 0916f32b6..69be02f6f 100644 --- a/src/rez/utils/elf.py +++ b/src/rez/utils/elf.py @@ -5,6 +5,8 @@ """ Functions that wrap readelf/patchelf utils on linux. """ +from __future__ import annotations + import os from shlex import quote import subprocess @@ -13,7 +15,7 @@ from rez.utils.execution import Popen -def get_rpaths(elfpath): +def get_rpaths(elfpath: str) -> list[str]: """Get rpaths/runpaths from header. """ @@ -35,7 +37,7 @@ def get_rpaths(elfpath): return [] -def patch_rpaths(elfpath, rpaths): +def patch_rpaths(elfpath, rpaths) -> None: """Replace an elf's rpath header with those provided. """ diff --git a/src/rez/utils/execution.py b/src/rez/utils/execution.py index 2ae5e8052..d1a2d83c6 100644 --- a/src/rez/utils/execution.py +++ b/src/rez/utils/execution.py @@ -6,6 +6,10 @@ Utilities related to process/script execution. """ +from __future__ import annotations + +from collections.abc import Callable + from rez.utils.yaml import dump_yaml from contextlib import contextmanager from enum import Enum @@ -15,9 +19,11 @@ import os import io +from typing import Any, Callable, Iterable, Iterator + @contextmanager -def add_sys_paths(paths): +def add_sys_paths(paths: Iterable[str]) -> Iterator[None]: """Add to sys.path, and revert on scope exit. """ original_syspath = sys.path[:] @@ -36,7 +42,7 @@ class Popen(subprocess.Popen): and also forces the encoding to be utf-8 if text=True or universal_newlines=True is set without specifying the encoding. """ - def __init__(self, args, **kwargs): + def __init__(self, args, **kwargs) -> None: # Avoids python bug described here: https://bugs.python.org/issue3905. # This can arise when apps (maya) install a non-standard stdin handler. # @@ -94,7 +100,9 @@ class ExecutableScriptMode(Enum): # TODO: Maybe also allow distlib.ScriptMaker instead of the .py + PATHEXT. -def create_executable_script(filepath, body, program=None, py_script_mode=None): +def create_executable_script(filepath: str, body: str | Callable, + program: str | None = None, + py_script_mode: ExecutableScriptMode | None = None) -> list[str]: """ Create an executable script. In case a py_script_mode has been set to create a .py script the shell is expected to have the PATHEXT environment @@ -176,7 +184,7 @@ def create_executable_script(filepath, body, program=None, py_script_mode=None): return script_filepaths -def _get_python_script_files(filepath, py_script_mode, platform): +def _get_python_script_files(filepath: str, py_script_mode, platform: str) -> list[str]: """ Evaluates the py_script_mode for the requested filepath on the given platform. @@ -217,7 +225,7 @@ def _get_python_script_files(filepath, py_script_mode, platform): return script_filepaths -def create_forwarding_script(filepath, module, func_name, *nargs, **kwargs): +def create_forwarding_script(filepath: str, module: str | tuple[str, str], func_name: str, *nargs, **kwargs) -> None: """Create a 'forwarding' script. A forwarding script is one that executes some arbitrary Rez function. This @@ -230,7 +238,7 @@ def create_forwarding_script(filepath, module, func_name, *nargs, **kwargs): os.path.splitext(filepath)[-1].lower() != ".cmd": filepath += ".cmd" - doc = dict( + doc: dict[str, Any] = dict( module=module, func_name=func_name) diff --git a/src/rez/utils/filesystem.py b/src/rez/utils/filesystem.py index 0daf57949..6e614142a 100644 --- a/src/rez/utils/filesystem.py +++ b/src/rez/utils/filesystem.py @@ -5,6 +5,8 @@ """ Filesystem-related utilities. """ +from __future__ import annotations + from threading import Lock from tempfile import mkdtemp from contextlib import contextmanager @@ -37,7 +39,7 @@ class TempDirs(object): instances_lock = Lock() instances = [] - def __init__(self, tmpdir, prefix="rez_"): + def __init__(self, tmpdir, prefix="rez_") -> None: self.tmpdir = tmpdir self.prefix = prefix self.dirs = set() @@ -46,7 +48,7 @@ def __init__(self, tmpdir, prefix="rez_"): with TempDirs.instances_lock: TempDirs.instances.append(weakref.ref(self)) - def mkdtemp(self, cleanup=True): + def mkdtemp(self, cleanup: bool = True): path = mkdtemp(dir=self.tmpdir, prefix=self.prefix) if not cleanup: return path @@ -56,10 +58,10 @@ def mkdtemp(self, cleanup=True): return path - def __del__(self): + def __del__(self) -> None: self.clear() - def clear(self): + def clear(self) -> None: with self.lock: if not self.dirs: return @@ -72,7 +74,7 @@ def clear(self): shutil.rmtree(path) @classmethod - def clear_all(cls): + def clear_all(cls) -> None: with TempDirs.instances_lock: instances = cls.instances[:] @@ -191,7 +193,7 @@ def safe_remove(path): raise -def forceful_rmtree(path): +def forceful_rmtree(path) -> None: """Like shutil.rmtree, but may change permissions. Specifically, non-writable dirs within `path` can cause rmtree to fail. This @@ -202,7 +204,7 @@ def forceful_rmtree(path): * unicode path """ - def _on_error(func, path, exc_info): + def _on_error(func, path, exc_info) -> None: try: if is_windows: path = windows_long_path(path) @@ -225,7 +227,7 @@ def _on_error(func, path, exc_info): shutil.rmtree(path, onerror=_on_error) -def replacing_symlink(source, link_name): +def replacing_symlink(source, link_name) -> None: """Create symlink that overwrites any existing target. """ with make_tmp_name(link_name) as tmp_link_name: @@ -233,7 +235,7 @@ def replacing_symlink(source, link_name): replace_file_or_dir(link_name, tmp_link_name) -def replacing_copy(src, dest, follow_symlinks=False): +def replacing_copy(src, dest, follow_symlinks: bool = False) -> None: """Perform copy that overwrites any existing target. Will copy/copytree `src` to `dest`, and will remove `dest` if it exists, @@ -287,7 +289,7 @@ def replace_file_or_dir(dest, source): rename(source, dest) -def additive_copytree(src, dst, symlinks=False, ignore=None): +def additive_copytree(src, dst, symlinks: bool = False, ignore=None) -> None: """Version of `copytree` that merges into an existing directory. """ os.makedirs(dst, exist_ok=True) @@ -324,7 +326,7 @@ def make_tmp_name(name): safe_remove(tmp_name) -def is_subdirectory(path_a, path_b): +def is_subdirectory(path_a, path_b) -> bool: """Returns True if `path_a` is a subdirectory of `path_b`.""" path_a = os.path.realpath(path_a) path_b = os.path.realpath(path_b) @@ -339,7 +341,7 @@ def is_subdirectory(path_a, path_b): return not relative.startswith(os.pardir + os.sep) -def find_matching_symlink(path, source): +def find_matching_symlink(path: str, source: str) -> str | None: """Find a symlink under `path` that points at `source`. If source is relative, it is considered relative to `path`. @@ -365,7 +367,7 @@ def to_abs(target): return None -def copy_or_replace(src, dst): +def copy_or_replace(src: str, dst: str): '''try to copy with mode, and if it fails, try replacing ''' try: @@ -404,7 +406,7 @@ def copy_or_replace(src, dst): shutil.move(dst_temp, dst) -def copytree(src, dst, symlinks=False, ignore=None, hardlinks=False): +def copytree(src: str, dst: str, symlinks: bool = False, ignore=None, hardlinks: bool = False): '''copytree that supports hard-linking ''' names = os.listdir(src) @@ -414,14 +416,14 @@ def copytree(src, dst, symlinks=False, ignore=None, hardlinks=False): ignored_names = set() if hardlinks: - def copy(srcname, dstname): + def copy(srcname, dstname) -> None: try: # try hard-linking first os.link(srcname, dstname) except OSError: shutil.copy2(srcname, dstname) else: - copy = shutil.copy2 + copy = shutil.copy2 # type: ignore[assignment] os.makedirs(dst, exist_ok=True) @@ -452,12 +454,12 @@ def copy(srcname, dstname): # can't copy file access times on Windows pass except OSError as why: - errors.extend((src, dst, str(why))) + errors.append((src, dst, str(why))) if errors: raise shutil.Error(errors) -def movetree(src, dst): +def movetree(src: str, dst: str) -> None: """Attempts a move, and falls back to a copy+delete if this fails """ try: @@ -467,27 +469,27 @@ def movetree(src, dst): shutil.rmtree(src) -def safe_chmod(path, mode): +def safe_chmod(path: str, mode) -> None: """Set the permissions mode on path, but only if it differs from the current mode. """ if stat.S_IMODE(os.stat(path).st_mode) != mode: os.chmod(path, mode) -def to_nativepath(path): +def to_nativepath(path: str): path = path.replace('\\', '/') return os.path.join(*path.split('/')) -def to_ntpath(path): +def to_ntpath(path: str): return ntpath.sep.join(path.split(posixpath.sep)) -def to_posixpath(path): +def to_posixpath(path: str): return posixpath.sep.join(path.split(ntpath.sep)) -def canonical_path(path, platform=None): +def canonical_path(path: str, platform=None): r""" Resolves symlinks, and formats filepath. Resolves symlinks, lowercases if filesystem is case-insensitive, @@ -512,7 +514,7 @@ def canonical_path(path, platform=None): return path -def encode_filesystem_name(input_str): +def encode_filesystem_name(input_str: str): """Encodes an arbitrary unicode string to a generic filesystem-compatible non-unicode filename. @@ -584,7 +586,7 @@ def encode_filesystem_name(input_str): _HEX_RE = re.compile('[0-9a-f]+$') -def decode_filesystem_name(filename): +def decode_filesystem_name(filename: str): """Decodes a filename encoded using the rules given in encode_filesystem_name to a unicode string. """ @@ -636,8 +638,8 @@ def decode_filesystem_name(filename): return u''.join(result) -def test_encode_decode(): - def do_test(orig, expected_encoded): +def test_encode_decode() -> None: + def do_test(orig, expected_encoded) -> None: print('=' * 80) print(orig) encoded = encode_filesystem_name(orig) @@ -653,7 +655,7 @@ def do_test(orig, expected_encoded): do_test(u"\u20ac3 ~= $4.06", '_3e282ac3_020_07e_03d_020_0244.06') -def walk_up_dirs(path): +def walk_up_dirs(path: str): """Yields absolute directories starting with the given path, and iterating up through all it's parents, until it reaches a root directory""" prev_path = None @@ -664,7 +666,7 @@ def walk_up_dirs(path): current_path = os.path.dirname(prev_path) -def windows_long_path(dos_path): +def windows_long_path(dos_path: str): """Prefix '\\?\' for path longer than 259 char (Win32API limitation) """ path = os.path.abspath(dos_path) @@ -679,7 +681,7 @@ def windows_long_path(dos_path): return path -def rename(src, dst): +def rename(src: str, dst: str): """Utility function to rename a file or folder src to dst with retrying. This function uses the built-in `os.rename()` function and falls back to `robocopy` tool diff --git a/src/rez/utils/formatting.py b/src/rez/utils/formatting.py index aa8aad41c..64bc847c2 100644 --- a/src/rez/utils/formatting.py +++ b/src/rez/utils/formatting.py @@ -5,17 +5,24 @@ """ Utilities related to formatting output or translating input. """ +from __future__ import annotations + from string import Formatter from rez.version import Requirement from rez.exceptions import PackageRequestError from pprint import pformat from enum import Enum +from typing import Any, Sequence, Mapping, TYPE_CHECKING import math import os import os.path import re import time +if TYPE_CHECKING: + from rez.rex import RexExecutor + from rez.utils import colorize + PACKAGE_NAME_REGSTR = r"[a-zA-Z_0-9](\.?[a-zA-Z0-9_]+)*" PACKAGE_NAME_REGEX = re.compile(r"^%s\Z" % PACKAGE_NAME_REGSTR) @@ -34,7 +41,7 @@ ) -def is_valid_package_name(name, raise_error=False): +def is_valid_package_name(name: str, raise_error: bool = False) -> bool: """Test the validity of a package name string. Args: @@ -44,7 +51,7 @@ def is_valid_package_name(name, raise_error=False): Returns: bool. """ - is_valid = ( + is_valid = bool( PACKAGE_NAME_REGEX.match(name) and name not in invalid_package_names ) @@ -68,7 +75,7 @@ class PackageRequest(Requirement): >>> print(pr.name, pr.range) foo 1.3+ """ - def __init__(self, s): + def __init__(self, s: str) -> None: super(PackageRequest, self).__init__(s) # detect ephemeral package @@ -96,7 +103,8 @@ class ObjectStringFormatter(Formatter): empty = StringFormatType.empty unchanged = StringFormatType.unchanged - def __init__(self, instance, pretty=False, expand=StringFormatType.error): + def __init__(self, instance: Any, pretty: bool = False, + expand: StringFormatType = StringFormatType.error) -> None: """Create a formatter. Args: @@ -110,7 +118,7 @@ def __init__(self, instance, pretty=False, expand=StringFormatType.error): self.pretty = pretty self.expand = expand - def convert_field(self, value, conversion): + def convert_field(self, value: Any, conversion: str | None) -> Any: if self.pretty: if value is None: return '' @@ -119,7 +127,7 @@ def convert_field(self, value, conversion): return Formatter.convert_field(self, value, conversion) - def get_field(self, field_name, args, kwargs): + def get_field(self, field_name: str, args: Sequence[Any], kwargs: Mapping[str, Any]) -> Any: if self.expand == StringFormatType.error: return Formatter.get_field(self, field_name, args, kwargs) try: @@ -127,7 +135,7 @@ def get_field(self, field_name, args, kwargs): except (AttributeError, KeyError, TypeError): reg = re.compile(r"[^\.\[]+") try: - key = reg.match(field_name).group() + key = reg.match(field_name).group() # type: ignore[union-attr] except: key = field_name if self.expand == StringFormatType.empty: @@ -135,7 +143,7 @@ def get_field(self, field_name, args, kwargs): else: # StringFormatType.unchanged return ("{%s}" % field_name, key) - def get_value(self, key, args, kwds): + def get_value(self, key: int | str, args: Sequence[Any], kwds: Mapping[str, Any]) -> Any: if isinstance(key, str): if key: try: @@ -167,7 +175,8 @@ class StringFormatMixin(object): format_expand = StringFormatType.error format_pretty = True - def format(self, s, pretty=None, expand=None): + def format(self, s: str, pretty: bool | None = None, + expand: StringFormatType | None = None) -> str: """Format a string. Args: @@ -191,7 +200,7 @@ def format(self, s, pretty=None, expand=None): return formatter.format(s) -def expand_abbreviations(txt, fields): +def expand_abbreviations(txt: str, fields: list[str]) -> str: """Expand abbreviations in a format string. If an abbreviation does not match a field, or matches multiple fields, it @@ -210,7 +219,7 @@ def expand_abbreviations(txt, fields): Returns: Expanded string. """ - def _expand(matchobj): + def _expand(matchobj: re.Match[str]) -> str: s = matchobj.group("var") if s not in fields: matches = [x for x in fields if x.startswith(s)] @@ -220,7 +229,7 @@ def _expand(matchobj): return re.sub(FORMAT_VAR_REGEX, _expand, txt) -def expandvars(text, environ=None): +def expandvars(text: str, environ: Mapping[str, str] | None = None) -> str: """Expand shell variables of form $var and ${var}. Unknown variables are left unchanged. @@ -258,13 +267,13 @@ def expandvars(text, environ=None): return text -def indent(txt): +def indent(txt: str) -> str: """Indent the given text by 4 spaces.""" lines = ((" " + x) for x in txt.split('\n')) return '\n'.join(lines) -def dict_to_attributes_code(dict_): +def dict_to_attributes_code(dict_: dict) -> str: """Given a nested dict, generate a python code equivalent. Example: @@ -299,7 +308,7 @@ def dict_to_attributes_code(dict_): return '\n'.join(lines) -def columnise(rows, padding=2): +def columnise(rows: Sequence[Sequence[Any]], padding: int=2) -> list[str]: """Print rows of entries in aligned columns.""" strs = [] maxwidths = {} @@ -324,7 +333,7 @@ def columnise(rows, padding=2): return strs -def print_colored_columns(printer, rows, padding=2): +def print_colored_columns(printer: colorize.Printer, rows: Sequence[tuple], padding: int=2) -> None: """Like `columnise`, but with colored rows. Args: @@ -349,7 +358,7 @@ def print_colored_columns(printer, rows, padding=2): (1, "seconds", 60)) -def readable_time_duration(secs): +def readable_time_duration(secs: int) -> str: """Convert number of seconds into human readable form, eg '3.2 hours'. """ return _readable_units(secs, time_divs, True) @@ -363,7 +372,7 @@ def readable_time_duration(secs): (1, "bytes", 1024)) -def readable_memory_size(bytes_): +def readable_memory_size(bytes_: int) -> str: """Convert number of bytes into human-readable form. This method rounds to 1 decimal place eg '1.2 Kb'. @@ -371,7 +380,8 @@ def readable_memory_size(bytes_): return _readable_units(bytes_, memory_divs) -def _readable_units(value, divs, plural_aware=False): +def _readable_units(value: int, divs: tuple[tuple[int, str, int], ...], + plural_aware: bool = False) -> str: if value == 0: unit = divs[-1][1] return "0 %s" % unit @@ -396,7 +406,7 @@ def _readable_units(value, divs, plural_aware=False): return txt -def get_epoch_time_from_str(s): +def get_epoch_time_from_str(s: str) -> int: """Convert a string into epoch time. Examples of valid strings: 1418350671 # already epoch time @@ -429,7 +439,7 @@ def get_epoch_time_from_str(s): positional_suffix = ("th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th") -def positional_number_string(n): +def positional_number_string(n: int) -> str: """Print the position string equivalent of a positive integer. Examples: 0: zeroeth @@ -461,7 +471,7 @@ def positional_number_string(n): ) -def expanduser(path): +def expanduser(path: str) -> str: """Expand '~' to home directory in the given string. Note that this function deliberately differs from the builtin @@ -486,7 +496,7 @@ def expanduser(path): else: userhome = os.path.expanduser('~') - def _expanduser(path): + def _expanduser(path: str) -> str: return EXPANDUSER_RE.sub( lambda m: m.groups()[0] + userhome + m.groups()[1], path) @@ -498,7 +508,7 @@ def _expanduser(path): return os.path.normpath(_expanduser(path)) -def as_block_string(txt): +def as_block_string(txt: str) -> str: """Return a string formatted as a python block comment string, like the one you're currently reading. Special characters are escaped if necessary. """ @@ -517,7 +527,7 @@ def as_block_string(txt): _header_br_minor = '-' * 80 -def header_comment(executor, txt): +def header_comment(executor: RexExecutor, txt: str) -> None: """Convenience for creating header-like comment in a rex executor. Args: @@ -531,7 +541,7 @@ def header_comment(executor, txt): executor.comment(_header_br) -def minor_header_comment(executor, txt): +def minor_header_comment(executor: RexExecutor, txt: str) -> None: executor.comment("") executor.comment(txt) executor.comment(_header_br_minor) diff --git a/src/rez/utils/graph_utils.py b/src/rez/utils/graph_utils.py index 0e040b8a1..d25d12050 100644 --- a/src/rez/utils/graph_utils.py +++ b/src/rez/utils/graph_utils.py @@ -5,6 +5,8 @@ """ Functions for manipulating dot-based resolve graphs. """ +from __future__ import annotations + import os.path import sys import tempfile @@ -17,9 +19,10 @@ from rez.vendor.pygraph.readwrite.dot import read as read_dot from rez.vendor.pygraph.algorithms.accessibility import accessibility from rez.vendor.pygraph.classes.digraph import digraph +from typing import cast -def read_graph_from_string(txt): +def read_graph_from_string(txt: str) -> digraph: """Read a graph from a string, either in dot format, or our own compressed format. @@ -27,7 +30,7 @@ def read_graph_from_string(txt): `pygraph.digraph`: Graph object. """ if not txt.startswith('{'): - return read_dot(txt) # standard dot format + return cast(digraph, read_dot(txt)) # standard dot format def conv(value): if isinstance(value, str): @@ -108,7 +111,7 @@ def conv(value): return contents -def write_dot(g): +def write_dot(g: digraph) -> str: """Replacement for pygraph.readwrite.dot.write, which is dog slow. Note: @@ -261,7 +264,7 @@ def save_graph_object(g, dest_file, fmt=None, image_ratio=None): return fmt -def view_graph(graph_str, dest_file=None): +def view_graph(graph_str, dest_file=None) -> None: """View a dot graph in an image viewer.""" from rez.system import system from rez.config import config diff --git a/src/rez/utils/installer.py b/src/rez/utils/installer.py index c09503c12..dbb9a2c65 100644 --- a/src/rez/utils/installer.py +++ b/src/rez/utils/installer.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + import rez from rez.package_maker import make_package from rez.system import system @@ -10,7 +12,7 @@ import shutil -def install_as_rez_package(repo_path): +def install_as_rez_package(repo_path) -> None: """Install the current rez installation as a rez package. Note: This is very similar to 'rez-bind rez', however rez-bind is intended @@ -19,10 +21,10 @@ def install_as_rez_package(repo_path): Args: repo_path (str): Repository to install the rez package into. """ - def commands(): + def commands() -> None: env.PYTHONPATH.append('{this.root}') # noqa - def make_root(variant, root): + def make_root(variant, root) -> None: # copy source rez_path = rez.__path__[0] site_path = os.path.dirname(rez_path) diff --git a/src/rez/utils/lint_helper.py b/src/rez/utils/lint_helper.py index 914709a89..cbc2ce6e4 100644 --- a/src/rez/utils/lint_helper.py +++ b/src/rez/utils/lint_helper.py @@ -7,6 +7,8 @@ None. It is only here to keep linters such as PyFlakes happy. It is used in cases where code looks like it references an uninitialised variable, but does not. """ +from __future__ import annotations + from types import ModuleType import sys @@ -15,7 +17,7 @@ class NoneModule(ModuleType): def __getattr__(self, name): return None - def used(self, object_): + def used(self, object_) -> None: """Use this to stop 'variable/module not used' linting errors.""" pass diff --git a/src/rez/utils/logging_.py b/src/rez/utils/logging_.py index 76b71601b..a62769d5b 100644 --- a/src/rez/utils/logging_.py +++ b/src/rez/utils/logging_.py @@ -12,57 +12,57 @@ logger = logging.getLogger(__name__) -def print_debug(msg, *nargs): +def print_debug(msg, *nargs) -> None: logger.debug(msg, *nargs) -def print_info(msg, *nargs): +def print_info(msg, *nargs) -> None: logger.info(msg, *nargs) -def print_warning(msg, *nargs): +def print_warning(msg, *nargs) -> None: logger.warning(msg, *nargs) -def print_error(msg, *nargs): +def print_error(msg, *nargs) -> None: logger.error(msg, *nargs) -def print_critical(msg, *nargs): +def print_critical(msg, *nargs) -> None: logger.critical(msg, *nargs) -def get_debug_printer(enabled=True): +def get_debug_printer(enabled: bool = True): return _Printer(enabled, logger.debug) -def get_info_printer(enabled=True): +def get_info_printer(enabled: bool = True): return _Printer(enabled, logger.info) -def get_warning_printer(enabled=True): +def get_warning_printer(enabled: bool = True): return _Printer(enabled, logger.warning) -def get_error_printer(enabled=True): +def get_error_printer(enabled: bool = True): return _Printer(enabled, logger.error) -def get_critical_printer(enabled=True): +def get_critical_printer(enabled: bool = True): return _Printer(enabled, logger.critical) class _Printer(object): - def __init__(self, enabled=True, printer_function=None): + def __init__(self, enabled: bool = True, printer_function=None) -> None: self.printer_function = printer_function if enabled else None - def __call__(self, msg, *nargs): + def __call__(self, msg, *nargs) -> None: if self.printer_function: if nargs: msg = msg % nargs self.printer_function(msg) - def __bool__(self): + def __bool__(self) -> bool: return bool(self.printer_function) @@ -76,7 +76,7 @@ def log_duration(printer, msg): printer(msg, str(secs)) -def view_file_logs(globbed_path, loglevel_index=None): +def view_file_logs(globbed_path, loglevel_index=None) -> None: """View logs from one or more logfiles. Prints to stdout. diff --git a/src/rez/utils/memcached.py b/src/rez/utils/memcached.py index 000d4d262..d613ccd5f 100644 --- a/src/rez/utils/memcached.py +++ b/src/rez/utils/memcached.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.config import config from rez.vendor.memcache.memcache import Client as Client_, \ SERVER_MAX_KEY_LENGTH, __version__ as memcache_client_version @@ -12,7 +14,10 @@ from inspect import isgeneratorfunction from hashlib import md5 from uuid import uuid4 +from typing import Callable, Iterator, TypeVar + +CallableT = TypeVar("CallableT", bound=Callable) # this version should be changed if and when the caching interface changes cache_interface_version = 2 @@ -27,14 +32,14 @@ class Client(object): - ability to cache None. """ class _Miss(object): - def __bool__(self): + def __bool__(self) -> bool: return False miss = _Miss() logger = config.debug_printer("memcache") - def __init__(self, servers, debug=False): + def __init__(self, servers: str | list[str], debug: bool = False) -> None: """Create a memcached client. Args: @@ -45,15 +50,15 @@ def __init__(self, servers, debug=False): """ self.servers = [servers] if isinstance(servers, str) else servers self.key_hasher = self._debug_key_hash if debug else self._key_hash - self._client = None + self._client: Client_ | None = None self.debug = debug self.current = '' - def __bool__(self): + def __bool__(self) -> bool: return bool(self.servers) @property - def client(self): + def client(self) -> Client_: """Get the native memcache client. Returns: @@ -63,7 +68,7 @@ def client(self): self._client = Client_(self.servers) return self._client - def test_servers(self): + def test_servers(self) -> set[str]: """Test that memcached servers are servicing requests. Returns: @@ -78,7 +83,7 @@ def test_servers(self): responders.add(server) return responders - def set(self, key, val, time=0, min_compress_len=0): + def set(self, key: str, val: Any, time: int=0, min_compress_len: int=0) -> None: """See memcache.Client.""" if not self.servers: return @@ -93,7 +98,7 @@ def set(self, key, val, time=0, min_compress_len=0): min_compress_len=min_compress_len) self.logger("SET: %s", key) - def get(self, key): + def get(self, key: str) -> Any | Client._Miss: """See memcache.Client. Returns: @@ -117,14 +122,14 @@ def get(self, key): self.logger("MISS: %s", key) return self.miss - def delete(self, key): + def delete(self, key: str) -> None: """See memcache.Client.""" if self.servers: key = self._qualified_key(key) hashed_key = self.key_hasher(key) self.client.delete(hashed_key) - def flush(self, hard=False): + def flush(self, hard: bool = False) -> None: """Drop existing entries from the cache. Args: @@ -144,7 +149,7 @@ def flush(self, hard=False): tag = "flushed" + tag self.current = tag - def get_stats(self): + def get_stats(self) -> list[tuple]: """Get server statistics. Returns: @@ -152,17 +157,17 @@ def get_stats(self): """ return self._get_stats() - def reset_stats(self): + def reset_stats(self) -> None: """Reset the server stats.""" self._get_stats("reset") - def disconnect(self): + def disconnect(self) -> None: """Disconnect from server(s). Behaviour is undefined after this call.""" if self.servers and self._client: self._client.disconnect_all() # print("Disconnected memcached client %s" % str(self)) - def _qualified_key(self, key): + def _qualified_key(self, key: str) -> str: """ Qualify cache key so that: * changes to schemas don't break compatibility (cache_interface_version) @@ -176,15 +181,15 @@ def _qualified_key(self, key): key ) - def _get_stats(self, stat_args=None): + def _get_stats(self, stat_args=None) -> list[tuple]: return self.client.get_stats(stat_args=stat_args) @classmethod - def _key_hash(cls, key): + def _key_hash(cls, key: str) -> str: return md5(key.encode("utf-8")).hexdigest() @classmethod - def _debug_key_hash(cls, key): + def _debug_key_hash(cls, key: str) -> str: import re h = cls._key_hash(key)[:16] value = "%s:%s" % (h, key) @@ -194,10 +199,10 @@ def _debug_key_hash(cls, key): class _ScopedInstanceManager(local): - def __init__(self): - self.clients = {} + def __init__(self) -> None: + self.clients: dict[tuple[tuple, bool], list] = {} - def acquire(self, servers, debug=False): + def acquire(self, servers, debug: bool = False) -> tuple[Client, tuple[tuple, bool]]: key = (tuple(servers or []), debug) entry = self.clients.get(key) if entry: @@ -208,7 +213,7 @@ def acquire(self, servers, debug=False): self.clients[key] = [client, 1] return client, key - def release(self, key): + def release(self, key: tuple[tuple, bool]) -> None: entry = self.clients.get(key) assert entry @@ -223,7 +228,7 @@ def release(self, key): @contextmanager -def memcached_client(servers=config.memcached_uri, debug=config.debug_memcache): +def memcached_client(servers=config.memcached_uri, debug=config.debug_memcache) -> Iterator[Client]: """Get a shared memcached instance. This function shares the same memcached instance across nested invocations. @@ -264,8 +269,8 @@ def wrapper(*nargs, **kwargs): return update_wrapper(wrapper, func) -def memcached(servers, key=None, from_cache=None, to_cache=None, time=0, - min_compress_len=0, debug=False): +def memcached(servers, key=None, from_cache=None, to_cache=None, time: int=0, + min_compress_len: int=0, debug: bool = False) -> Callable[[CallableT], CallableT]: """memcached memoization function decorator. The wrapped function is expected to return a value that is stored to a @@ -382,7 +387,7 @@ def wrapper(*nargs, **kwargs): return result.result return result - def forget(): + def forget() -> None: """Forget entries in the cache. Note that this does not delete entries from a memcached server - that @@ -400,5 +405,5 @@ def forget(): class DoNotCache(object): - def __init__(self, result): + def __init__(self, result: Any) -> None: self.result = result diff --git a/src/rez/utils/patching.py b/src/rez/utils/patching.py index 1252867bb..79037ae68 100644 --- a/src/rez/utils/patching.py +++ b/src/rez/utils/patching.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.version import Requirement @@ -42,8 +44,8 @@ def get_patched_request(requires, patchlist): '^': (True, True, True) } - requires = [Requirement(x) if not isinstance(x, Requirement) else x - for x in requires] + requires: list[Requirement | None] = [ + Requirement(x) if not isinstance(x, Requirement) else x for x in requires] appended = [] for patch in patchlist: diff --git a/src/rez/utils/pip.py b/src/rez/utils/pip.py index a4c70e08c..2ec532bf4 100644 --- a/src/rez/utils/pip.py +++ b/src/rez/utils/pip.py @@ -42,7 +42,7 @@ def pip_to_rez_package_name(dist_name): return dist_name.replace("-", "_") -def pip_to_rez_version(dist_version, allow_legacy=True): +def pip_to_rez_version(dist_version, allow_legacy: bool = True): """Convert a distribution version to a rez compatible version. TODO [AJ] needs a table of example conversions. @@ -187,7 +187,7 @@ def pip_specifier_to_rez_requirement(specifier): Returns: `VersionRange`: Equivalent rez version range. """ - def is_release(rez_ver): + def is_release(rez_ver) -> bool: parts = rez_ver.split('.') try: _ = int(parts[-1]) # noqa diff --git a/src/rez/utils/platform_.py b/src/rez/utils/platform_.py index f0901a1ca..7582c18ab 100644 --- a/src/rez/utils/platform_.py +++ b/src/rez/utils/platform_.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + import platform import os import os.path @@ -18,9 +20,9 @@ class Platform(object): """Abstraction of a platform. """ - name = None + name: str - def __init__(self): + def __init__(self) -> None: pass @cached_property @@ -109,7 +111,7 @@ def logical_cores(self): return 1 @property - def has_case_sensitive_filesystem(self): + def has_case_sensitive_filesystem(self) -> bool: return True # -- implementation @@ -138,7 +140,7 @@ def _difftool(self): def _tmpdir(self): return gettempdir() - def symlink(self, source, link_name): + def symlink(self, source, link_name) -> None: """Create a symbolic link pointing to source named link_name.""" os.symlink(source, link_name) @@ -411,7 +413,7 @@ def _physical_cores(self): class OSXPlatform(_UnixPlatform): name = "osx" - def _os(self): + def _os(self) -> str: release = platform.mac_ver()[0] return "osx-%s" % release @@ -426,10 +428,10 @@ def _terminal_emulator_command(self): else: return "%s -hold -e" % term - def _image_viewer(self): + def _image_viewer(self) -> str: return "open" - def _editor(self): + def _editor(self) -> str: return "open" def _physical_cores_from_osx_sysctl(self): @@ -464,7 +466,7 @@ def _difftool(self): class WindowsPlatform(Platform): name = "windows" - def _os(self): + def _os(self) -> str: release, version, csd, ptype = platform.win32_ver() toks = [] for item in (version, csd): @@ -474,14 +476,14 @@ def _os(self): return "windows-%s" % final_version @property - def has_case_sensitive_filesystem(self): + def has_case_sensitive_filesystem(self) -> bool: return False - def _image_viewer(self): + def _image_viewer(self) -> str: # os.system("file.jpg") will open default viewer on windows return '' - def _editor(self): + def _editor(self) -> str: # os.system("file.txt") will open default editor on windows return '' @@ -489,7 +491,7 @@ def _new_session_popen_args(self): # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863%28v=vs.85%29.aspx return dict(creationflags=0x00000010) - def symlink(self, source, link_name): + def symlink(self, source: str, link_name: str): # If we are already in a version of python that supports symlinks then # just use the os module, otherwise fall back on ctypes. It requires # administrator privileges to run or the correct group policy to be set. @@ -511,10 +513,10 @@ def symlink(self, source, link_name): if csl(link_name, source, flags) == 0: raise ctypes.WinError() - def _terminal_emulator_command(self): + def _terminal_emulator_command(self) -> str: return "START" - def _physical_cores_from_wmic(self): + def _physical_cores_from_wmic(self) -> int | None: # windows import subprocess try: @@ -545,7 +547,7 @@ def _physical_cores_from_wmic(self): return sum(map(int, result)) - def _physical_cores(self): + def _physical_cores(self) -> int | None: return self._physical_cores_from_wmic() def _difftool(self): @@ -555,7 +557,8 @@ def _difftool(self): # singleton -platform_ = None +# FIXME: is is valid for platform_ to be None? +platform_: Platform = None name = platform.system().lower() if name == "linux": platform_ = LinuxPlatform() diff --git a/src/rez/utils/py_dist.py b/src/rez/utils/py_dist.py index 6999f5fc7..abf474cf3 100644 --- a/src/rez/utils/py_dist.py +++ b/src/rez/utils/py_dist.py @@ -84,7 +84,7 @@ def convert_requirement(req): return req_strs -def get_dist_dependencies(name, recurse=True): +def get_dist_dependencies(name, recurse: bool = True): """ Get the dependencies of the given, already installed distribution. @param recurse If True, recursively find all dependencies. @@ -119,7 +119,7 @@ def get_dist_dependencies(name, recurse=True): # TODO: doesn't deal with executable scripts yet -def convert_dist(name, dest_path, make_variant=True, ignore_dirs=None, +def convert_dist(name, dest_path, make_variant: bool = True, ignore_dirs=None, python_requirement="major_minor"): """Convert an already installed python distribution into a rez package. diff --git a/src/rez/utils/resolve_graph.py b/src/rez/utils/resolve_graph.py index 5108a7ca6..acf870f30 100644 --- a/src/rez/utils/resolve_graph.py +++ b/src/rez/utils/resolve_graph.py @@ -118,12 +118,12 @@ def _get_node_label(graph, node): return _request_from_label(label_) -def _is_request_node(graph, node): +def _is_request_node(graph, node) -> bool: style = next(at[1] for at in graph.node_attr[node] if at[0] == "style") return "dashed" in style -def _print_each_graph_edges(graph): +def _print_each_graph_edges(graph) -> None: """for debug""" for (from_, to_), properties in graph.edge_properties.items(): edge_status = properties["label"] diff --git a/src/rez/utils/resources.py b/src/rez/utils/resources.py index 372d1455f..fdf610f1e 100644 --- a/src/rez/utils/resources.py +++ b/src/rez/utils/resources.py @@ -33,6 +33,8 @@ See the 'pets' unit test in tests/test_resources.py for a complete example. """ +from __future__ import annotations + from functools import lru_cache from rez.utils.data_utils import cached_property, AttributeForwardMeta, \ @@ -41,6 +43,15 @@ from rez.exceptions import ResourceError from rez.utils.logging_ import print_debug +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + # this is not available in typing until 3.11, but due to __future__.annotations + # we can use it without really importing it + from typing import Self + from rez.vendor.schema.schema import Schema + from rez.package_repository import PackageRepository + class Resource(object, metaclass=LazyAttributeMeta): """Abstract base class for a data resource. @@ -69,30 +80,35 @@ class Resource(object, metaclass=LazyAttributeMeta): `validated_data` function, and test full validation using `validate_data`. """ #: Unique identifier of the resource type. - key = None + key: str = None #: Schema for the resource data. #: Must validate a dict. Can be None, in which case the resource does #: not load any data. - schema = None + schema: Schema | None = None #: The exception type to raise on key validation failure. schema_error = Exception + if TYPE_CHECKING: + # all Resources that are acquired using PackageRepository.get_resource + # have this attribute added to them + _repository: PackageRepository + @classmethod - def normalize_variables(cls, variables): + def normalize_variables(cls, variables: dict[str, Any]) -> dict[str, Any]: """Give subclasses a chance to standardize values for certain variables """ return variables - def __init__(self, variables=None): + def __init__(self, variables: dict[str, Any] | None = None) -> None: self.variables = self.normalize_variables(variables or {}) @cached_property - def handle(self): + def handle(self) -> ResourceHandle: """Get the resource handle.""" return ResourceHandle(self.key, self.variables) @cached_property - def _data(self): + def _data(self) -> dict[str, Any] | None: if not self.schema: return None @@ -101,23 +117,23 @@ def _data(self): print_debug("Loaded resource: %s" % str(self)) return data - def get(self, key, default=None): + def get(self, key: str, default: Any | None = None) -> Any | None: """Get the value of a resource variable.""" return self.variables.get(key, default) - def __str__(self): + def __str__(self) -> str: return "%s%r" % (self.key, self.variables) - def __repr__(self): + def __repr__(self) -> str: return "%s(%r)" % (self.__class__.__name__, self.variables) - def __hash__(self): + def __hash__(self) -> int: return hash((self.__class__, self.handle)) def __eq__(self, other): return (self.handle == other.handle) - def _load(self): + def _load(self) -> dict[str, Any]: """Load the data associated with the resource. You are not expected to cache this data - the resource system does this @@ -139,22 +155,22 @@ class ResourceHandle(object): A handle uniquely identifies a resource. A handle can be stored and used with a `ResourcePool` to retrieve the same resource at a later date. """ - def __init__(self, key, variables=None): + def __init__(self, key: str, variables: dict[str, Any] | None = None) -> None: self.key = key self.variables = variables or {} - def get(self, key, default=None): + def get(self, key: str, default: Any | None = None) -> Any: """Get the value of a resource variable.""" return self.variables.get(key, default) - def to_dict(self): + def to_dict(self) -> dict[str, Any]: """Serialize the contents of this resource handle to a dictionary representation. """ return dict(key=self.key, variables=self.variables) @classmethod - def from_dict(cls, d): + def from_dict(cls, d: dict[str, Any]) -> Self: """Return a `ResourceHandle` instance from a serialized dict This should ONLY be used with dicts created with ResourceHandle.to_dict; @@ -163,25 +179,25 @@ def from_dict(cls, d): """ return cls(**d) - def _hashable_repr(self): + def _hashable_repr(self) -> tuple[str, tuple]: return ( self.key, tuple(sorted(self.variables.items())) ) - def __str__(self): + def __str__(self) -> str: return str(self.to_dict()) - def __repr__(self): + def __repr__(self) -> str: return "%s(%r, %r)" % (self.__class__.__name__, self.key, self.variables) def __eq__(self, other): return (self.key == other.key) and (self.variables == other.variables) - def __ne__(self, other): + def __ne__(self, other: object) -> bool: return not self.__eq__(other) - def __hash__(self): + def __hash__(self) -> int: return hash(self._hashable_repr()) @@ -193,12 +209,12 @@ class ResourcePool(object): resources are created via some factory class, which first checks for the existence of the resource before creating one from a pool. """ - def __init__(self, cache_size=None): - self.resource_classes = {} + def __init__(self, cache_size: int | None = None) -> None: + self.resource_classes: dict[str, type[Resource]] = {} cache = lru_cache(maxsize=cache_size) self.cached_get_resource = cache(self._get_resource) - def register_resource(self, resource_class): + def register_resource(self, resource_class: type[Resource]) -> None: resource_key = resource_class.key assert issubclass(resource_class, Resource) assert resource_key is not None @@ -216,20 +232,20 @@ def register_resource(self, resource_class): self.resource_classes[resource_key] = resource_class - def get_resource_from_handle(self, resource_handle): + def get_resource_from_handle(self, resource_handle: ResourceHandle) -> Resource: return self.cached_get_resource(resource_handle) - def clear_caches(self): + def clear_caches(self) -> None: self.cached_get_resource.cache_clear() - def get_resource_class(self, resource_key): + def get_resource_class(self, resource_key) -> type[Resource]: resource_class = self.resource_classes.get(resource_key) if resource_class is None: raise ResourceError("Error getting resource from pool: Unknown " "resource type %r" % resource_key) return resource_class - def _get_resource(self, resource_handle): + def _get_resource(self, resource_handle: ResourceHandle) -> Resource: resource_class = self.get_resource_class(resource_handle.key) return resource_class(resource_handle.variables) @@ -254,26 +270,28 @@ class ResourceWrapper(object, metaclass=AttributeForwardMeta): """ keys = None - def __init__(self, resource): + def __init__(self, resource: Resource) -> None: self.wrapped = resource @property - def resource(self): + def resource(self) -> Resource: return self.wrapped @property - def handle(self): + def handle(self) -> ResourceHandle: return self.resource.handle @property - def data(self): + def data(self) -> dict[str, Any] | None: return self.resource._data - def validated_data(self): - return self.resource.validated_data() + def validated_data(self) -> dict[str, Any] | None: + # provided by LazyAttributeMeta metaclass + return self.resource.validated_data() # type: ignore[attr-defined] - def validate_data(self): - self.resource.validate_data() + def validate_data(self) -> None: + # provided by LazyAttributeMeta metaclass + self.resource.validate_data() # type: ignore[attr-defined] def __eq__(self, other): return ( @@ -281,11 +299,11 @@ def __eq__(self, other): and self.resource == other.resource ) - def __str__(self): + def __str__(self) -> str: return "%s(%s)" % (self.__class__.__name__, str(self.resource)) - def __repr__(self): + def __repr__(self) -> str: return "%s(%r)" % (self.__class__.__name__, self.resource) - def __hash__(self): + def __hash__(self) -> int: return hash((self.__class__, self.resource)) diff --git a/src/rez/utils/schema.py b/src/rez/utils/schema.py index 36c22380e..45cddb811 100644 --- a/src/rez/utils/schema.py +++ b/src/rez/utils/schema.py @@ -5,7 +5,13 @@ """ Utilities for working with dict-based schemas. """ +from __future__ import annotations + from rez.vendor.schema.schema import Schema, Optional, Use, And +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from rez.config import Validatable # an alias which just so happens to be the same number of characters as @@ -13,7 +19,7 @@ Required = Schema -def schema_keys(schema): +def schema_keys(schema) -> set[str]: """Get the string values of keys in a dict-based schema. Non-string keys are ignored. @@ -43,7 +49,7 @@ def _get_leaf(value): return keys -def dict_to_schema(schema_dict, required, allow_custom_keys=True, modifier=None): +def dict_to_schema(schema_dict, required, allow_custom_keys: bool = True, modifier=None) -> Validatable: """Convert a dict of Schemas into a Schema. Args: @@ -68,7 +74,7 @@ def _to(value): d[k] = _to(v) if allow_custom_keys: d[Optional(str)] = modifier or object - schema = Schema(d) + schema: Validatable = Schema(d) elif modifier: schema = And(value, modifier) else: diff --git a/src/rez/utils/scope.py b/src/rez/utils/scope.py index fa694d198..4c3546d7b 100644 --- a/src/rez/utils/scope.py +++ b/src/rez/utils/scope.py @@ -2,10 +2,17 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.utils.formatting import StringFormatMixin, StringFormatType from collections import UserDict import sys +from typing import cast, Any, TYPE_CHECKING, ClassVar, NoReturn + +if TYPE_CHECKING: + from typing import Self + class RecursiveAttribute(UserDict, StringFormatMixin): """An object that can have new attributes added recursively:: @@ -31,7 +38,7 @@ class RecursiveAttribute(UserDict, StringFormatMixin): """ format_expand = StringFormatType.unchanged - def __init__(self, data=None, read_only=False): + def __init__(self, data=None, read_only: bool = False) -> None: self.__dict__.update(dict(data={}, read_only=read_only)) self._update(data or {}) @@ -58,7 +65,7 @@ def _noattrib(): attr_.__dict__["pending"] = (attr, self) return attr_ - def __setattr__(self, attr, value): + def __setattr__(self, attr: str, value: Any) -> None: d = self.__dict__ if d["read_only"]: if attr in d["data"]: @@ -73,16 +80,16 @@ def __setattr__(self, attr, value): d["data"][attr] = value self._reparent() - def __getitem__(self, attr): + def __getitem__(self, attr: str) -> Any: return getattr(self, attr) - def __str__(self): + def __str__(self) -> str: return str(self.to_dict()) - def __repr__(self): + def __repr__(self) -> str: return "%s(%r)" % (self.__class__.__name__, self.to_dict()) - def _create_child_attribute(self, attr): + def _create_child_attribute(self, attr: str) -> RecursiveAttribute: """Override this method to create new child attributes. Returns: @@ -90,7 +97,7 @@ def _create_child_attribute(self, attr): """ return self.__class__() - def to_dict(self): + def to_dict(self) -> dict[str, Any]: """Get an equivalent dict representation.""" d = {} for k, v in self.__dict__["data"].items(): @@ -100,22 +107,22 @@ def to_dict(self): d[k] = v return d - def copy(self): + def copy(self) -> Self: return self.__class__(self.__dict__['data'].copy()) - def update(self, data): + def update(self, data: dict[str, Any]) -> None: # type: ignore[override] """Dict-like update operation.""" if self.__dict__["read_only"]: raise AttributeError("read-only, cannot be updated") self._update(data) - def _update(self, data): + def _update(self, data: dict[str, Any]) -> None: for k, v in data.items(): if isinstance(v, dict): v = RecursiveAttribute(v) self.__dict__["data"][k] = v - def _reparent(self): + def _reparent(self) -> None: d = self.__dict__ if "pending" in d: attr_, parent = d["pending"] @@ -125,18 +132,18 @@ def _reparent(self): class _Scope(RecursiveAttribute): - def __init__(self, name=None, context=None): + def __init__(self, name: str | None = None, context: ScopeContext | None = None) -> None: RecursiveAttribute.__init__(self) self.__dict__.update(dict(name=name, context=context, locals=None)) - def __enter__(self): + def __enter__(self) -> _Scope: locals_ = sys._getframe(1).f_locals self.__dict__["locals"] = locals_.copy() return self - def __exit__(self, *args): + def __exit__(self, *args: Any) -> None: # find what's changed updates = {} d = self.__dict__ @@ -159,7 +166,7 @@ def __exit__(self, *args): if self_context: self_context._scope_exit(d["name"]) - def _create_child_attribute(self, attr): + def _create_child_attribute(self, attr: str) -> RecursiveAttribute: return RecursiveAttribute() @@ -200,11 +207,11 @@ class ScopeContext(object): and the assigned properties will be merged. If the same property is set multiple times, it will be overwritten. """ - def __init__(self): + def __init__(self) -> None: self.scopes = {} self.scope_stack = [_Scope()] - def __call__(self, name): + def __call__(self, name: str) -> _Scope: path = tuple([x.name for x in self.scope_stack[1:]] + [name]) if path in self.scopes: scope = self.scopes[path] @@ -215,23 +222,23 @@ def __call__(self, name): self.scope_stack.append(scope) return scope - def _scope_exit(self, name): + def _scope_exit(self, name: str) -> None: scope = self.scope_stack.pop() assert self.scope_stack assert name == scope.name - data = {scope.name: scope.to_dict()} + data = {cast(str, scope.name): scope.to_dict()} self.scope_stack[-1].update(data) - def to_dict(self): + def to_dict(self) -> dict[str, Any]: """Get an equivalent dict representation.""" return self.scope_stack[-1].to_dict() - def __str__(self): + def __str__(self) -> str: names = ('.'.join(y for y in x) for x in self.scopes.keys()) return "%r" % (tuple(names),) -def scoped_formatter(**objects): +def scoped_formatter(**objects: Any) -> RecursiveAttribute: """Format a string with respect to a set of objects' attributes. Use this rather than `scoped_format` when you need to reuse the formatter. @@ -239,7 +246,7 @@ def scoped_formatter(**objects): return RecursiveAttribute(objects, read_only=True) -def scoped_format(txt, **objects): +def scoped_format(txt: str, **objects: Any) -> str: """Format a string with respect to a set of objects' attributes. Example: diff --git a/src/rez/utils/sourcecode.py b/src/rez/utils/sourcecode.py index ff835e6ac..e036eabe4 100644 --- a/src/rez/utils/sourcecode.py +++ b/src/rez/utils/sourcecode.py @@ -2,18 +2,28 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.utils.formatting import indent from rez.utils.data_utils import cached_property from rez.utils.logging_ import print_debug from rez.util import load_module_from_file from inspect import getsourcelines from textwrap import dedent +from types import CodeType, ModuleType +from typing import Callable, Generic, TypeVar, TYPE_CHECKING from glob import glob import traceback import os.path +if TYPE_CHECKING: + from rez.packages import PackageBaseResourceWrapper + +T = TypeVar("T") +CallabeT = TypeVar("CallabeT", bound=Callable) -def early(): + +def early() -> Callable[[CallabeT], CallabeT]: """Used by functions in package.py to harden to the return value at build time. The term 'early' refers to the fact these package attribute are evaluated @@ -26,7 +36,7 @@ def decorated(fn): return decorated -def late(): +def late() -> Callable[[CallabeT], CallabeT]: """Used by functions in package.py that are evaluated lazily. The term 'late' refers to the fact these package attributes are evaluated @@ -53,7 +63,7 @@ def decorated(fn): return decorated -def include(module_name, *module_names): +def include(module_name: str, *module_names: str) -> Callable[[CallabeT], CallabeT]: """Used by functions in package.py to have access to named modules. See the 'package_definition_python_path' config setting for more info. @@ -65,7 +75,7 @@ def decorated(fn): return decorated -def _add_decorator(fn, name, **kwargs): +def _add_decorator(fn, name, **kwargs) -> None: if not hasattr(fn, "_decorators"): setattr(fn, "_decorators", []) @@ -74,7 +84,7 @@ def _add_decorator(fn, name, **kwargs): class SourceCodeError(Exception): - def __init__(self, msg, short_msg): + def __init__(self, msg: str, short_msg: str) -> None: super(SourceCodeError, self).__init__(msg) self.short_msg = short_msg @@ -87,27 +97,27 @@ class SourceCodeExecError(SourceCodeError): pass -class SourceCode(object): +class SourceCode(Generic[T]): """Wrapper for python source code. This object is aware of the decorators defined in this sourcefile (such as 'include') and deals with them appropriately. """ - def __init__(self, source=None, func=None, filepath=None, - eval_as_function=True): + def __init__(self, source: str | None = None, func: Callable[[], T] | None = None, + filepath: str | None = None, eval_as_function: bool = True) -> None: self.source = (source or '').rstrip() self.func = func self.filepath = filepath self.eval_as_function = eval_as_function - self.package = None + self.package: PackageBaseResourceWrapper | None = None - self.funcname = None - self.decorators = [] + self.funcname: str | None = None + self.decorators: list[dict] = [] if self.func is not None: self._init_from_func() - def copy(self): + def copy(self) -> SourceCode[T]: other = SourceCode.__new__(SourceCode) other.source = self.source other.func = self.func @@ -119,7 +129,7 @@ def copy(self): return other - def _init_from_func(self): + def _init_from_func(self) -> None: self.funcname = self.func.__name__ self.decorators = getattr(self.func, "_decorators", []) @@ -151,7 +161,7 @@ def _init_from_func(self): self.source = code @cached_property - def includes(self): + def includes(self) -> set | None: info = self._get_decorator_info("include") if not info: return None @@ -159,12 +169,12 @@ def includes(self): return set(info.get("nargs", [])) @cached_property - def late_binding(self): + def late_binding(self) -> bool: info = self._get_decorator_info("late") return bool(info) @cached_property - def evaluated_code(self): + def evaluated_code(self) -> str: if self.eval_as_function: funcname = self.funcname or "_unnamed" @@ -180,7 +190,7 @@ def evaluated_code(self): return code @property - def sourcename(self): + def sourcename(self) -> str: if self.filepath: filename = self.filepath else: @@ -192,7 +202,7 @@ def sourcename(self): return "<%s>" % filename @cached_property - def compiled(self): + def compiled(self) -> CodeType: try: pyc = compile(self.evaluated_code, self.sourcename, 'exec') except Exception as e: @@ -203,11 +213,11 @@ def compiled(self): return pyc - def set_package(self, package): + def set_package(self, package: PackageBaseResourceWrapper) -> None: # this is needed to load @included modules self.package = package - def exec_(self, globals_={}): + def exec_(self, globals_={}) -> T: # bind import modules if self.package is not None and self.includes: for name in self.includes: @@ -227,7 +237,7 @@ def exec_(self, globals_={}): return globals_.get("_result") - def to_text(self, funcname): + def to_text(self, funcname: str) -> str: # don't indent code if already indented if self.source[0] in (' ', '\t'): source = self.source @@ -245,7 +255,7 @@ def to_text(self, funcname): return txt - def _get_decorator_info(self, name): + def _get_decorator_info(self, name: str) -> dict | None: matches = [x for x in self.decorators if x.get("name") == name] if not matches: return None @@ -261,7 +271,7 @@ def __getstate__(self): "decorators": self.decorators } - def __setstate__(self, state): + def __setstate__(self, state) -> None: self.source = state["source"] self.filepath = state["filepath"] self.funcname = state["funcname"] @@ -277,13 +287,13 @@ def __eq__(self, other): and other.source == self.source ) - def __ne__(self, other): + def __ne__(self, other) -> bool: return not (other == self) - def __str__(self): + def __str__(self) -> str: return self.source - def __repr__(self): + def __repr__(self) -> str: return "%s(%r)" % (self.__class__.__name__, self.source) @@ -296,10 +306,10 @@ class IncludeModuleManager(object): # include_modules_subpath = ".rez/include" - def __init__(self): + def __init__(self) -> None: self.modules = {} - def load_module(self, name, package): + def load_module(self, name: str, package: PackageBaseResourceWrapper) -> ModuleType | None: from hashlib import sha1 from rez.config import config # avoiding circular import from rez.developer_package import DeveloperPackage diff --git a/src/rez/utils/typing.py b/src/rez/utils/typing.py new file mode 100644 index 000000000..4b47e0ea7 --- /dev/null +++ b/src/rez/utils/typing.py @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright Contributors to the Rez Project + + +from __future__ import absolute_import, print_function, annotations + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + # FIXME: use typing.Protocol instead of this workaround when python 3.7 support is dropped + from typing import Protocol + +else: + class Protocol(object): + pass + + +class SupportsLessThan(Protocol): + def __lt__(self, __other: Any) -> bool: + pass + + +class SupportsWrite(Protocol): + def write(self, __s: str) -> object: + pass + +class SupportsRead(Protocol): + def read(self) -> str: + pass diff --git a/src/rez/utils/which.py b/src/rez/utils/which.py index 8dd55e841..14c01412b 100644 --- a/src/rez/utils/which.py +++ b/src/rez/utils/which.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + import os import sys @@ -9,7 +11,7 @@ _default_pathext = '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC' -def which(cmd, mode=os.F_OK | os.X_OK, path=None, env=None): +def which(cmd: str, mode=os.F_OK | os.X_OK, path: str | None = None, env=None) -> str | None: """A replacement for shutil.which. Things we do that shutil.which does not: diff --git a/src/rez/utils/yaml.py b/src/rez/utils/yaml.py index 5cef870f9..26a2ca5b4 100644 --- a/src/rez/utils/yaml.py +++ b/src/rez/utils/yaml.py @@ -41,7 +41,7 @@ def represent_sourcecode(self, data): _Dumper.add_representer(SourceCode, _Dumper.represent_sourcecode) -def dump_yaml(data, Dumper=_Dumper, default_flow_style=False): +def dump_yaml(data, Dumper=_Dumper, default_flow_style: bool = False): """Returns data as yaml-formatted string.""" content = yaml.dump(data, default_flow_style=default_flow_style, @@ -56,7 +56,7 @@ def load_yaml(filepath): return yaml.load(txt, Loader=yaml.FullLoader) -def save_yaml(filepath, **fields): +def save_yaml(filepath, **fields) -> None: """Convenience function for writing yaml-encoded data to disk.""" content = dump_yaml(fields) with open(filepath, 'w') as f: diff --git a/src/rez/version/_requirement.py b/src/rez/version/_requirement.py index 9e72a1133..8c4bce760 100644 --- a/src/rez/version/_requirement.py +++ b/src/rez/version/_requirement.py @@ -1,10 +1,11 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the Rez Project - +from __future__ import annotations from rez.version._version import Version, VersionRange from rez.version._util import _Common import re +from typing import Iterator, Iterable class VersionedObject(_Common): @@ -20,7 +21,7 @@ class VersionedObject(_Common): sep_regex_str = r'[-@#]' sep_regex = re.compile(sep_regex_str) - def __init__(self, s): + def __init__(self, s: str) -> None: """ Args: s (str): @@ -43,20 +44,20 @@ def __init__(self, s): self.version_ = Version() @classmethod - def construct(cls, name, version=None): + def construct(cls, name: str, version: Version | None = None) -> VersionedObject: """Create a VersionedObject directly from an object name and version. Args: name (str): Object name string. version (typing.Optional[Version]): Version object. """ - other = VersionedObject(None) + other = VersionedObject(None) # type: ignore[arg-type] # special case other.name_ = name other.version_ = Version() if version is None else version return other @property - def name(self): + def name(self) -> str: """Name of the object. Returns: @@ -65,7 +66,7 @@ def name(self): return self.name_ @property - def version(self): + def version(self) -> Version: """Version of the object. Returns: @@ -73,7 +74,7 @@ def version(self): """ return self.version_ - def as_exact_requirement(self): + def as_exact_requirement(self) -> str: """Get the versioned object, as an exact requirement string. Returns: @@ -86,15 +87,15 @@ def as_exact_requirement(self): ver_str = str(self.version_) return self.name_ + sep_str + ver_str - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return (isinstance(other, VersionedObject) and (self.name_ == other.name_) and (self.version_ == other.version_)) - def __hash__(self): + def __hash__(self) -> int: return hash((self.name_, self.version_)) - def __str__(self): + def __str__(self) -> str: sep_str = '' ver_str = '' if self.version_: @@ -137,7 +138,7 @@ class Requirement(_Common): """ sep_regex = re.compile(r'[-@#=<>]') - def __init__(self, s, invalid_bound_error=True): + def __init__(self, s: str | None, invalid_bound_error: bool = True) -> None: """ Args: s (str): Requirement string @@ -183,7 +184,7 @@ def __init__(self, s, invalid_bound_error=True): self.range_ = VersionRange() @classmethod - def construct(cls, name, range=None): + def construct(cls, name: str, range: VersionRange | None = None) -> Requirement: """Create a requirement directly from an object name and VersionRange. Args: @@ -197,7 +198,7 @@ def construct(cls, name, range=None): return other @property - def name(self): + def name(self) -> str: """Name of the required object. Returns: @@ -206,7 +207,7 @@ def name(self): return self.name_ @property - def range(self): + def range(self) -> VersionRange: """Version range of the requirement. Returns: @@ -215,7 +216,7 @@ def range(self): return self.range_ @property - def conflict(self): + def conflict(self) -> bool: """True if the requirement is a conflict requirement, eg "!foo", "~foo-1". Returns: @@ -224,7 +225,7 @@ def conflict(self): return self.conflict_ @property - def weak(self): + def weak(self) -> bool: """True if the requirement is weak, eg "~foo". .. note:: @@ -236,7 +237,7 @@ def weak(self): """ return self.negate_ - def safe_str(self): + def safe_str(self) -> str: """Return a string representation that is safe for the current filesystem, and guarantees that no two different Requirement objects will encode to the same value. @@ -246,7 +247,7 @@ def safe_str(self): """ return str(self) - def conflicts_with(self, other): + def conflicts_with(self, other: Requirement | VersionedObject) -> bool: """Returns True if this requirement conflicts with another :class:`Requirement` or :class:`VersionedObject`. @@ -272,7 +273,7 @@ def conflicts_with(self, other): else: return (other.version_ not in self.range_) - def merged(self, other): + def merged(self, other: Requirement) -> Requirement | None: """Merge two requirements. Two requirements can be in conflict and if so, this function returns @@ -291,7 +292,7 @@ def merged(self, other): if self.name_ != other.name_: return None # cannot merge across object names - def _r(r_): + def _r(r_: Requirement) -> Requirement: r = Requirement(None) r.name_ = r_.name_ r.negate_ = r_.negate_ @@ -335,23 +336,24 @@ def _r(r_): r.range_ = range_ return r - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return (isinstance(other, Requirement) and (self.name_ == other.name_) and (self.range_ == other.range_) and (self.conflict_ == other.conflict_)) - def __hash__(self): + def __hash__(self) -> int: return hash(str(self)) - def __str__(self): + def __str__(self) -> str: if self._str is None: pre_str = '~' if self.negate_ else ('!' if self.conflict_ else '') range_str = '' sep_str = '' range_ = self.range_ - if self.negate_: + # Note: the only time that range_ is None is if self.negate_ is True + if self.negate_ or range_ is None: range_ = ~range_ if range_ else VersionRange() if not range_.is_any(): @@ -370,16 +372,16 @@ class RequirementList(_Common): optimal form, merging any requirements for common objects. Order of objects is retained. """ - def __init__(self, requirements): + def __init__(self, requirements: Iterable[Requirement]) -> None: """ Args: - requirements (list[Requirement]): List of requirements. + requirements (Iterable[Requirement]): List of requirements. """ - self.requirements_ = [] - self.conflict_ = None - self.requirements_dict = {} - self.names_ = set() - self.conflict_names_ = set() + self.requirements_: list[Requirement] = [] + self.conflict_: tuple[Requirement, Requirement] | None = None + self.requirements_dict: dict[str, Requirement] = {} + self.names_: set[str] = set() + self.conflict_names_: set[str] = set() for req in requirements: existing_req = self.requirements_dict.get(req.name) @@ -410,7 +412,7 @@ def __init__(self, requirements): self.names_.add(req.name) @property - def requirements(self): + def requirements(self) -> list[Requirement]: """Returns optimised list of requirements, or None if there are conflicts. @@ -420,7 +422,7 @@ def requirements(self): return self.requirements_ @property - def conflict(self): + def conflict(self) -> tuple[Requirement, Requirement] | None: """Get the requirement conflict, if any. Returns: @@ -430,7 +432,7 @@ def conflict(self): return self.conflict_ @property - def names(self): + def names(self) -> set[str]: """Set of names of requirements, not including conflict requirements. Returns: @@ -439,7 +441,7 @@ def names(self): return self.names_ @property - def conflict_names(self): + def conflict_names(self) -> set[str]: """Set of conflict requirement names. Returns: @@ -447,11 +449,11 @@ def conflict_names(self): """ return self.conflict_names_ - def __iter__(self): + def __iter__(self) -> Iterator[Requirement]: for requirement in (self.requirements_ or []): yield requirement - def get(self, name): + def get(self, name: str) -> Requirement | None: """Returns the requirement for the given object, or None. Args: @@ -462,12 +464,12 @@ def get(self, name): """ return self.requirements_dict.get(name) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return (isinstance(other, RequirementList) and (self.requirements_ == other.requirements_) and (self.conflict_ == other.conflict_)) - def __str__(self): + def __str__(self) -> str: if self.conflict_: s1 = str(self.conflict_[0]) s2 = str(self.conflict_[1]) diff --git a/src/rez/version/_util.py b/src/rez/version/_util.py index 47da793b6..26d1101d4 100644 --- a/src/rez/version/_util.py +++ b/src/rez/version/_util.py @@ -3,6 +3,9 @@ from itertools import groupby +from typing import Iterable, Iterator, TypeVar + +T = TypeVar("T") class VersionError(Exception): @@ -14,17 +17,17 @@ class ParseException(Exception): class _Common(object): - def __str__(self): + def __str__(self) -> str: raise NotImplementedError - def __ne__(self, other): + def __ne__(self, other: object) -> bool: return not (self == other) - def __repr__(self): + def __repr__(self) -> str: return "%s(%r)" % (self.__class__.__name__, str(self)) -def dedup(iterable): +def dedup(iterable: Iterable[T]) -> Iterator[T]: """Removes duplicates from a sorted sequence.""" for e in groupby(iterable): yield e[0] diff --git a/src/rez/version/_version.py b/src/rez/version/_version.py index 42c2caa4a..7444b4731 100644 --- a/src/rez/version/_version.py +++ b/src/rez/version/_version.py @@ -2,51 +2,59 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.version._util import VersionError, ParseException, _Common, \ dedup from bisect import bisect_left import copy import string import re +from typing import cast, Any, Callable, Iterable, TypeVar, TYPE_CHECKING, overload + +if TYPE_CHECKING: + from typing_extensions import Self + +CallableT = TypeVar("CallableT", bound=Callable) re_token = re.compile(r"[a-zA-Z0-9_]+") class _Comparable(_Common): - def __gt__(self, other): + def __gt__(self, other: _Comparable) -> bool: return not (self < other or self == other) - def __le__(self, other): + def __le__(self, other: _Comparable) -> bool: return self < other or self == other - def __ge__(self, other): + def __ge__(self, other: _Comparable) -> bool: return not self < other class _ReversedComparable(_Common): - def __init__(self, value): + def __init__(self, value: _Comparable) -> None: self.value = value - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return self.value == other.value - def __lt__(self, other): + def __lt__(self, other: object) -> bool: return self.value > other.value - def __gt__(self, other): + def __gt__(self, other: object) -> bool: return not (self < other or self == other) - def __le__(self, other): + def __le__(self, other: object) -> bool: return self < other or self == other - def __ge__(self, other): + def __ge__(self, other: object) -> bool: return not self < other - def __str__(self): + def __str__(self) -> str: return f"reverse({self.value!r})" - def __repr__(self): + def __repr__(self) -> str: return "reverse(%r)" % self.value @@ -60,7 +68,7 @@ class VersionToken(_Comparable): Version tokens are only allowed to contain alphanumerics (any case) and underscores. """ - def __init__(self, token): + def __init__(self, token: str) -> None: """ Args: token (str): Token string, eg "rc02" @@ -68,14 +76,14 @@ def __init__(self, token): raise NotImplementedError @classmethod - def create_random_token_string(cls): + def create_random_token_string(cls) -> str: """Create a random token string. For testing purposes only. :meta private: """ raise NotImplementedError - def less_than(self, other): + def less_than(self, other: Any) -> bool: """Compare to another :class:`VersionToken`. Args: @@ -86,17 +94,17 @@ def less_than(self, other): """ raise NotImplementedError - def next(self): + def next(self) -> Self: """Returns the next largest token.""" raise NotImplementedError - def __str__(self): + def __str__(self) -> str: raise NotImplementedError - def __lt__(self, other): + def __lt__(self, other: object) -> bool: return self.less_than(other) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return (not self < other) and (not other < self) @@ -105,54 +113,54 @@ class NumericToken(VersionToken): Version token supporting numbers only. Padding is ignored. """ - def __init__(self, token): + def __init__(self, token: str) -> None: if not token.isdigit(): raise VersionError("Invalid version token: '%s'" % token) else: self.n = int(token) @classmethod - def create_random_token_string(cls): + def create_random_token_string(cls) -> str: import random chars = string.digits return ''.join([chars[random.randint(0, len(chars) - 1)] for _ in range(8)]) - def __str__(self): + def __str__(self) -> str: return str(self.n) - def __eq__(self, other): + def __eq__(self, other: NumericToken) -> bool: return (self.n == other.n) - def less_than(self, other): + def less_than(self, other: NumericToken) -> bool: return (self.n < other.n) - def __next__(self): + def __next__(self) -> NumericToken: other = copy.copy(self) other.n = self.n = 1 return other - def next(self): + def next(self) -> NumericToken: return self.__next__() class _SubToken(_Comparable): """Used internally by AlphanumericVersionToken.""" - def __init__(self, s): + def __init__(self, s: str) -> None: self.s = s self.n = int(s) if s.isdigit() else None - def __lt__(self, other): + def __lt__(self, other: _SubToken) -> bool: if self.n is None: return (self.s < other.s) if other.n is None else True else: return False if other.n is None \ else ((self.n, self.s) < (other.n, other.s)) - def __eq__(self, other): + def __eq__(self, other: _SubToken) -> bool: return (self.s == other.s) and (self.n == other.n) - def __str__(self): + def __str__(self) -> str: return self.s @@ -184,7 +192,7 @@ class AlphanumericVersionToken(VersionToken): numeric_regex = re.compile("[0-9]+") regex = re.compile(r"[a-zA-Z0-9_]+\Z") - def __init__(self, token): + def __init__(self, token: str | None) -> None: if token is None: self.subtokens = None elif not self.regex.match(token): @@ -193,22 +201,22 @@ def __init__(self, token): self.subtokens = self._parse(token) @classmethod - def create_random_token_string(cls): + def create_random_token_string(cls) -> str: import random chars = string.digits + string.ascii_letters return ''.join([chars[random.randint(0, len(chars) - 1)] for _ in range(8)]) - def __str__(self): + def __str__(self) -> str: return ''.join(map(str, self.subtokens)) - def __eq__(self, other): + def __eq__(self, other: AlphanumericVersionToken) -> bool: return (self.subtokens == other.subtokens) - def less_than(self, other): + def less_than(self, other: AlphanumericVersionToken) -> bool: return (self.subtokens < other.subtokens) - def __next__(self): + def __next__(self) -> AlphanumericVersionToken: other = AlphanumericVersionToken(None) other.subtokens = self.subtokens[:] subtok = other.subtokens[-1] @@ -218,11 +226,11 @@ def __next__(self): other.subtokens.append(_SubToken('_')) return other - def next(self): + def next(self) -> AlphanumericVersionToken: return self.__next__() @classmethod - def _parse(cls, s): + def _parse(cls, s: str) -> list[_SubToken]: subtokens = [] alphas = cls.numeric_regex.split(s) numerics = cls.numeric_regex.findall(s) @@ -243,7 +251,7 @@ def _parse(cls, s): return subtokens -def reverse_sort_key(comparable): +def reverse_sort_key(comparable: _Comparable) -> _ReversedComparable: """Key that gives reverse sort order on versions and version ranges. Example: @@ -274,17 +282,17 @@ class Version(_Comparable): """ inf = None - def __init__(self, ver_str='', make_token=AlphanumericVersionToken): + def __init__(self, ver_str: str | None = '', make_token: Callable[[str], VersionToken] = AlphanumericVersionToken) -> None: """ Args: ver_str (str): Version string. make_token (typing.Callable[[str], None]): Callable that creates a VersionToken subclass from a string. """ - self.tokens = [] + self.tokens: list[VersionToken] | None = [] self.seps = [] - self._str = None - self._hash = None + self._str: str | None = None + self._hash: int | None = None if ver_str: toks = re_token.findall(ver_str) @@ -304,7 +312,7 @@ def __init__(self, ver_str='', make_token=AlphanumericVersionToken): self.seps = seps[1:-1] - def copy(self): + def copy(self) -> Version: """ Returns a copy of the version. @@ -312,11 +320,13 @@ def copy(self): Version: """ other = Version(None) + if self.tokens is None: + raise RuntimeError("Version.inf cannot be copied") other.tokens = self.tokens[:] other.seps = self.seps[:] return other - def trim(self, len_): + def trim(self, len_: int) -> Version: """Return a copy of the version, possibly with less tokens. Args: @@ -327,25 +337,29 @@ def trim(self, len_): Version: """ other = Version(None) + if self.tokens is None: + raise RuntimeError("Version.inf cannot be trimmed") other.tokens = self.tokens[:len_] other.seps = self.seps[:len_ - 1] return other - def __next__(self): + def __next__(self) -> Version: """Return :meth:`next` version. Eg, ``next(1.2)`` is ``1.2_``""" if self.tokens: other = self.copy() + assert other.tokens is not None, \ + "Value should not be None because self.tokens is not None" tok = other.tokens.pop() other.tokens.append(tok.next()) return other else: return Version.inf - def next(self): + def next(self) -> Version: return self.__next__() @property - def major(self): + def major(self) -> VersionToken: """Semantic versioning major version. Returns: @@ -354,7 +368,7 @@ def major(self): return self[0] @property - def minor(self): + def minor(self) -> VersionToken: """Semantic versioning minor version. Returns: @@ -363,7 +377,7 @@ def minor(self): return self[1] @property - def patch(self): + def patch(self) -> VersionToken: """Semantic versioning patch version. Returns: @@ -371,7 +385,7 @@ def patch(self): """ return self[2] - def as_tuple(self): + def as_tuple(self) -> tuple[str, ...]: """Convert to a tuple of strings. Example: @@ -384,23 +398,31 @@ def as_tuple(self): """ return tuple(map(str, self.tokens)) - def __len__(self): + def __len__(self) -> int: return len(self.tokens or []) - def __getitem__(self, index): + @overload + def __getitem__(self, index: int) -> VersionToken: + pass + + @overload + def __getitem__(self, index: slice) -> list[VersionToken]: + pass + + def __getitem__(self, index: int | slice) -> VersionToken | list[VersionToken]: try: return (self.tokens or [])[index] except IndexError: raise IndexError("version token index out of range") - def __bool__(self): + def __bool__(self) -> bool: """The empty version equates to False.""" return bool(self.tokens) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return isinstance(other, Version) and self.tokens == other.tokens - def __lt__(self, other): + def __lt__(self, other: object) -> bool: if self.tokens is None: return False elif other.tokens is None: @@ -408,13 +430,13 @@ def __lt__(self, other): else: return (self.tokens < other.tokens) - def __hash__(self): + def __hash__(self) -> int: if self._hash is None: self._hash = hash(None) if self.tokens is None \ else hash(tuple(map(str, self.tokens))) return self._hash - def __str__(self): + def __str__(self) -> str: if self._str is None: self._str = "[INF]" if self.tokens is None \ else ''.join(str(x) + y for x, y in zip(self.tokens, self.seps + [''])) @@ -429,30 +451,30 @@ def __str__(self): class _LowerBound(_Comparable): min = None - def __init__(self, version, inclusive): + def __init__(self, version: Version, inclusive: bool) -> None: self.version = version self.inclusive = inclusive - def __str__(self): + def __str__(self) -> str: if self.version: s = "%s+" if self.inclusive else ">%s" return s % self.version else: return '' if self.inclusive else ">" - def __eq__(self, other): + def __eq__(self, other: _LowerBound | _UpperBound) -> bool: return (self.version == other.version) \ and (self.inclusive == other.inclusive) - def __lt__(self, other): + def __lt__(self, other: _LowerBound | _UpperBound) -> bool: return (self.version < other.version) \ or ((self.version == other.version) and (self.inclusive and not other.inclusive)) - def __hash__(self): + def __hash__(self) -> int: return hash((self.version, self.inclusive)) - def contains_version(self, version): + def contains_version(self, version: Version) -> bool: return (version > self.version) \ or (self.inclusive and (version == self.version)) @@ -463,29 +485,29 @@ def contains_version(self, version): class _UpperBound(_Comparable): inf = None - def __init__(self, version, inclusive): + def __init__(self, version: Version, inclusive: bool) -> None: self.version = version self.inclusive = inclusive if not version and not inclusive: raise VersionError("Invalid upper bound: '%s'" % str(self)) - def __str__(self): + def __str__(self) -> str: s = "<=%s" if self.inclusive else "<%s" return s % self.version - def __eq__(self, other): + def __eq__(self, other: _LowerBound | _UpperBound) -> bool: return (self.version == other.version) \ and (self.inclusive == other.inclusive) - def __lt__(self, other): + def __lt__(self, other: _LowerBound | _UpperBound) -> bool: return (self.version < other.version) \ or ((self.version == other.version) and (not self.inclusive and other.inclusive)) - def __hash__(self): + def __hash__(self) -> int: return hash((self.version, self.inclusive)) - def contains_version(self, version): + def contains_version(self, version: Version) -> bool: return (version < self.version) \ or (self.inclusive and (version == self.version)) @@ -496,7 +518,9 @@ def contains_version(self, version): class _Bound(_Comparable): any = None - def __init__(self, lower=None, upper=None, invalid_bound_error=True): + def __init__(self, lower: _LowerBound | None = None, + upper: _UpperBound | None = None, + invalid_bound_error: bool = True) -> None: self.lower = lower or _LowerBound.min self.upper = upper or _UpperBound.inf @@ -509,7 +533,7 @@ def __init__(self, lower=None, upper=None, invalid_bound_error=True): ): raise VersionError("Invalid bound") - def __str__(self): + def __str__(self) -> str: if self.upper.version == Version.inf: return str(self.lower) elif self.lower.version == self.upper.version: @@ -525,35 +549,35 @@ def __str__(self): else: return "%s%s" % (self.lower, self.upper) - def __eq__(self, other): + def __eq__(self, other: _Bound) -> bool: return (self.lower == other.lower) and (self.upper == other.upper) - def __lt__(self, other): + def __lt__(self, other: _Bound) -> bool: return (self.lower, self.upper) < (other.lower, other.upper) - def __hash__(self): + def __hash__(self) -> int: return hash((self.lower, self.upper)) - def lower_bounded(self): + def lower_bounded(self) -> bool: return (self.lower != _LowerBound.min) - def upper_bounded(self): + def upper_bounded(self) -> bool: return (self.upper != _UpperBound.inf) - def contains_version(self, version): + def contains_version(self, version: Version) -> bool: return (self.version_containment(version) == 0) - def version_containment(self, version): + def version_containment(self, version: Version) -> int: if not self.lower.contains_version(version): return -1 if not self.upper.contains_version(version): return 1 return 0 - def contains_bound(self, bound): + def contains_bound(self, bound: _Bound) -> bool: return (self.lower <= bound.lower) and (self.upper >= bound.upper) - def intersects(self, other): + def intersects(self, other: _Bound) -> bool: lower = max(self.lower, other.lower) upper = min(self.upper, other.upper) @@ -561,7 +585,7 @@ def intersects(self, other): (lower.version == upper.version) and (lower.inclusive and upper.inclusive) ) - def intersection(self, other): + def intersection(self, other: _Bound) -> _Bound | None: lower = max(self.lower, other.lower) upper = min(self.upper, other.upper) @@ -576,6 +600,20 @@ def intersection(self, other): _Bound.any = _Bound() +def action(fn: CallableT) -> CallableT: + def fn_(self: Any) -> Any: + result = fn(self) + if self.debug: + label = fn.__name__.replace("_act_", "") + print("%-21s: %s" % (label, self._input_string)) + for key, value in self._groups.items(): + print(" %-17s= %s" % (key, value)) + print(" %-17s= %s" % ("bounds", self.bounds)) + return result + + return fn_ # type: ignore[return-value] + + class _VersionRangeParser(object): debug = False # set to True to enable parser debugging @@ -659,9 +697,9 @@ class _VersionRangeParser(object): regex = re.compile(version_range_regex, re_flags) - def __init__(self, input_string, make_token, invalid_bound_error=True): + def __init__(self, input_string: str, make_token: Callable[[str], VersionToken], invalid_bound_error: bool = True) -> None: self.make_token = make_token - self._groups = {} + self._groups: dict[str, Any | None] = {} self._input_string = input_string self.bounds = [] self.invalid_bound_error = invalid_bound_error @@ -712,29 +750,17 @@ def __init__(self, input_string, make_token, invalid_bound_error=True): elif self._groups['upper_bound']: self._act_upper_bound() - def _is_lower_bound_exclusive(self, token): + def _is_lower_bound_exclusive(self, token: str | None) -> bool: return (token == ">") - def _is_upper_bound_exclusive(self, token): + def _is_upper_bound_exclusive(self, token: str | None) -> bool: return (token == "<") - def _create_version_from_token(self, token): + def _create_version_from_token(self, token: str | None) -> Version: return Version(token, make_token=self.make_token) - def action(fn): - def fn_(self): - result = fn(self) - if self.debug: - label = fn.__name__.replace("_act_", "") - print("%-21s: %s" % (label, self._input_string)) - for key, value in self._groups.items(): - print(" %-17s= %s" % (key, value)) - print(" %-17s= %s" % ("bounds", self.bounds)) - return result - return fn_ - @action - def _act_version(self): + def _act_version(self) -> None: version = self._create_version_from_token(self._groups['version']) lower_bound = _LowerBound(version, True) upper_bound = _UpperBound(version.next(), False) if version else None @@ -742,7 +768,7 @@ def _act_version(self): self.bounds.append(_Bound(lower_bound, upper_bound)) @action - def _act_exact_version(self): + def _act_exact_version(self) -> None: version = self._create_version_from_token(self._groups['exact_version_group']) lower_bound = _LowerBound(version, True) upper_bound = _UpperBound(version, True) @@ -750,7 +776,7 @@ def _act_exact_version(self): self.bounds.append(_Bound(lower_bound, upper_bound)) @action - def _act_bound(self): + def _act_bound(self) -> None: lower_version = self._create_version_from_token(self._groups['inclusive_lower_version']) lower_bound = _LowerBound(lower_version, True) @@ -760,7 +786,7 @@ def _act_bound(self): self.bounds.append(_Bound(lower_bound, upper_bound, self.invalid_bound_error)) @action - def _act_lower_bound(self): + def _act_lower_bound(self) -> None: version = self._create_version_from_token(self._groups['lower_version']) exclusive = self._is_lower_bound_exclusive(self._groups['lower_bound_prefix']) lower_bound = _LowerBound(version, not exclusive) @@ -768,7 +794,7 @@ def _act_lower_bound(self): self.bounds.append(_Bound(lower_bound, None)) @action - def _act_upper_bound(self): + def _act_upper_bound(self) -> None: version = self._create_version_from_token(self._groups['upper_version']) exclusive = self._is_upper_bound_exclusive(self._groups['upper_bound_prefix']) upper_bound = _UpperBound(version, not exclusive) @@ -776,7 +802,7 @@ def _act_upper_bound(self): self.bounds.append(_Bound(None, upper_bound)) @action - def _act_lower_and_upper_bound_asc(self): + def _act_lower_and_upper_bound_asc(self) -> None: lower_bound = None upper_bound = None @@ -793,7 +819,7 @@ def _act_lower_and_upper_bound_asc(self): self.bounds.append(_Bound(lower_bound, upper_bound, self.invalid_bound_error)) @action - def _act_lower_and_upper_bound_desc(self): + def _act_lower_and_upper_bound_desc(self) -> None: lower_bound = None upper_bound = None @@ -867,8 +893,9 @@ class VersionRange(_Comparable): valid version range syntax. For example, ``>`` is a valid range - read like ``>''``, it means ``any version greater than the empty version``. """ - def __init__(self, range_str='', make_token=AlphanumericVersionToken, - invalid_bound_error=True): + def __init__(self, range_str: str | None = '', + make_token: Callable[[str], VersionToken] = AlphanumericVersionToken, + invalid_bound_error: bool = True) -> None: """ Args: range_str (str): Range string, such as "3", "3+<4.5", "2|6+". The range @@ -878,7 +905,7 @@ def __init__(self, range_str='', make_token=AlphanumericVersionToken, invalid_bound_error (bool): If True, raise an exception if an impossible range is given, such as '3+<2'. """ - self._str = None + self._str: str | None = None self.bounds = [] # note: kept in ascending order if range_str is None: return @@ -899,7 +926,7 @@ def __init__(self, range_str='', make_token=AlphanumericVersionToken, else: self.bounds.append(_Bound.any) - def is_any(self): + def is_any(self) -> bool: """ Returns: bool: True if this is the "any" range, ie the empty string range @@ -907,7 +934,7 @@ def is_any(self): """ return (len(self.bounds) == 1) and (self.bounds[0] == _Bound.any) - def lower_bounded(self): + def lower_bounded(self) -> bool: """ Returns: bool: True if the range has a lower bound (that is not the empty @@ -915,35 +942,35 @@ def lower_bounded(self): """ return self.bounds[0].lower_bounded() - def upper_bounded(self): + def upper_bounded(self) -> bool: """ Returns: bool: True if the range has an upper bound. """ return self.bounds[-1].upper_bounded() - def bounded(self): + def bounded(self) -> bool: """ Returns: bool: True if the range has a lower and upper bound. """ return (self.lower_bounded() and self.upper_bounded()) - def issuperset(self, range): + def issuperset(self, range: VersionRange) -> bool: """ Returns: bool: True if the VersionRange is contained within this range. """ return self._issuperset(self.bounds, range.bounds) - def issubset(self, range): + def issubset(self, range: VersionRange) -> bool: """ Returns: bool: True if we are contained within the version range. """ return range.issuperset(self) - def union(self, other): + def union(self, other: VersionRange | Iterable[VersionRange]) -> VersionRange: """OR together version ranges. Calculates the union of this range with one or more other ranges. @@ -965,7 +992,7 @@ def union(self, other): range.bounds = bounds return range - def intersection(self, other): + def intersection(self, other: VersionRange | Iterable[VersionRange]) -> VersionRange | None: """AND together version ranges. Calculates the intersection of this range with one or more other ranges. @@ -990,7 +1017,7 @@ def intersection(self, other): range.bounds = bounds return range - def inverse(self): + def inverse(self) -> VersionRange | None: """Calculate the inverse of the range. Returns: @@ -1005,7 +1032,7 @@ def inverse(self): range.bounds = bounds return range - def intersects(self, other): + def intersects(self, other: VersionRange) -> bool: """Determine if we intersect with another range. Args: @@ -1016,7 +1043,7 @@ def intersects(self, other): """ return self._intersects(self.bounds, other.bounds) - def split(self): + def split(self) -> list[VersionRange]: """Split into separate contiguous ranges. Returns: @@ -1031,8 +1058,11 @@ def split(self): return ranges @classmethod - def as_span(cls, lower_version=None, upper_version=None, - lower_inclusive=True, upper_inclusive=True): + def as_span(cls, + lower_version: Version | None = None, + upper_version: Version | None = None, + lower_inclusive: bool = True, + upper_inclusive: bool = True) -> Self: """Create a range from lower_version..upper_version. Args: @@ -1054,7 +1084,7 @@ def as_span(cls, lower_version=None, upper_version=None, return range @classmethod - def from_version(cls, version, op=None): + def from_version(cls, version: Version, op: str | None = None) -> Self: """Create a range from a version. Args: @@ -1093,7 +1123,7 @@ def from_version(cls, version, op=None): return range @classmethod - def from_versions(cls, versions): + def from_versions(cls, versions: Iterable[Version]) -> VersionRange: """Create a range from a list of versions. This method creates a range that contains only the given versions and @@ -1114,7 +1144,7 @@ def from_versions(cls, versions): range.bounds.append(bound) return range - def to_versions(self): + def to_versions(self) -> list[Version] | None: """Returns exact version ranges as Version objects, or None if there are no exact version ranges present. @@ -1129,7 +1159,7 @@ def to_versions(self): return versions or None - def contains_version(self, version): + def contains_version(self, version: Version) -> bool: """Returns True if version is contained in this range. Returns: @@ -1149,7 +1179,9 @@ def contains_version(self, version): return False - def iter_intersect_test(self, iterable, key=None, descending=False): + def iter_intersect_test(self, iterable: Iterable[Any], + key: Callable[[Any], Version] | None = None, + descending: bool = False) -> _ContainsVersionIterator[Any]: """Performs containment tests on a sorted list of versions. This is more optimal than performing separate containment tests on a @@ -1170,7 +1202,9 @@ def iter_intersect_test(self, iterable, key=None, descending=False): """ return _ContainsVersionIterator(self, iterable, key, descending) - def iter_intersecting(self, iterable, key=None, descending=False): + def iter_intersecting(self, iterable: Iterable[Any], + key: Callable[[Any], Version] | None = None, + descending: bool = False) -> _ContainsVersionIterator[Any]: """Like :meth:iter_intersect_test`, but returns intersections only. Returns: @@ -1180,7 +1214,9 @@ def iter_intersecting(self, iterable, key=None, descending=False): self, iterable, key, descending, mode=_ContainsVersionIterator.MODE_INTERSECTING ) - def iter_non_intersecting(self, iterable, key=None, descending=False): + def iter_non_intersecting(self, iterable: Iterable[Any], + key: Callable[[Any], Version] | None = None, + descending: bool = False) -> _ContainsVersionIterator[Any]: """Like :meth:`iter_intersect_test`, but returns non-intersections only. Returns: @@ -1190,7 +1226,7 @@ def iter_non_intersecting(self, iterable, key=None, descending=False): self, iterable, key, descending, mode=_ContainsVersionIterator.MODE_NON_INTERSECTING ) - def span(self): + def span(self) -> VersionRange: """Return a contiguous range that is a superset of this range. Returns: @@ -1204,7 +1240,7 @@ def span(self): # TODO have this return a new VersionRange instead - this currently breaks # VersionRange immutability, and could invalidate __str__. - def visit_versions(self, func): + def visit_versions(self, func: Callable[[Version], Version | None]) -> None: """Visit each version in the range, and apply a function to each. This is for advanced usage only. @@ -1236,46 +1272,46 @@ def visit_versions(self, func): if isinstance(result, Version): bound.upper.version = result - def __contains__(self, version_or_range): + def __contains__(self, version_or_range: Version | VersionRange) -> bool: if isinstance(version_or_range, Version): return self.contains_version(version_or_range) else: return self.issuperset(version_or_range) - def __len__(self): + def __len__(self) -> int: return len(self.bounds) - def __invert__(self): + def __invert__(self) -> VersionRange | None: return self.inverse() - def __and__(self, other): + def __and__(self, other: VersionRange | Iterable[VersionRange]) -> VersionRange | None: return self.intersection(other) - def __or__(self, other): + def __or__(self, other: VersionRange | Iterable[VersionRange]) -> VersionRange: return self.union(other) - def __add__(self, other): + def __add__(self, other: VersionRange | Iterable[VersionRange]) -> VersionRange: return self.union(other) - def __sub__(self, other): + def __sub__(self, other: VersionRange) -> VersionRange | None: inv = other.inverse() return None if inv is None else self.intersection(inv) - def __str__(self): + def __str__(self) -> str: if self._str is None: self._str = '|'.join(map(str, self.bounds)) return self._str - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return isinstance(other, VersionRange) and self.bounds == other.bounds - def __lt__(self, other): + def __lt__(self, other: VersionRange) -> bool: return (self.bounds < other.bounds) - def __hash__(self): + def __hash__(self) -> int: return hash(tuple(self.bounds)) - def _contains_version(self, version): + def _contains_version(self, version: Version) -> tuple[int, bool]: vbound = _Bound(_LowerBound(version, True)) i = bisect_left(self.bounds, vbound) if i and self.bounds[i - 1].contains_version(version): @@ -1285,14 +1321,14 @@ def _contains_version(self, version): return i, False @classmethod - def _union(cls, bounds): + def _union(cls, bounds: list[_Bound]) -> list[_Bound]: if len(bounds) < 2: return bounds bounds_ = list(sorted(bounds)) new_bounds = [] - prev_bound = None - upper = None + prev_bound: _Bound | None = None + upper: _UpperBound | None = None start = 0 for i, bound in enumerate(bounds_): @@ -1312,7 +1348,7 @@ def _union(cls, bounds): return new_bounds @classmethod - def _intersection(cls, bounds1, bounds2): + def _intersection(cls, bounds1: list[_Bound], bounds2: list[_Bound]) -> list[_Bound]: new_bounds = [] for bound1 in bounds1: for bound2 in bounds2: @@ -1322,22 +1358,22 @@ def _intersection(cls, bounds1, bounds2): return new_bounds @classmethod - def _inverse(cls, bounds): - lbounds = [None] - ubounds = [] + def _inverse(cls, bounds: list[_Bound]) -> list[_Bound]: + lbounds: list[_LowerBound | None] = [None] + ubounds: list[_UpperBound | None] = [] for bound in bounds: if not bound.lower.version and bound.lower.inclusive: ubounds.append(None) else: - b = _UpperBound(bound.lower.version, not bound.lower.inclusive) - ubounds.append(b) + ub = _UpperBound(bound.lower.version, not bound.lower.inclusive) + ubounds.append(ub) if bound.upper.version == Version.inf: lbounds.append(None) else: - b = _LowerBound(bound.upper.version, not bound.upper.inclusive) - lbounds.append(b) + lb = _LowerBound(bound.upper.version, not bound.upper.inclusive) + lbounds.append(lb) ubounds.append(None) new_bounds = [] @@ -1349,7 +1385,7 @@ def _inverse(cls, bounds): return new_bounds @classmethod - def _issuperset(cls, bounds1, bounds2): + def _issuperset(cls, bounds1: list[_Bound], bounds2: list[_Bound]) -> bool: lo = 0 for bound2 in bounds2: i = bisect_left(bounds1, bound2, lo=lo) @@ -1364,7 +1400,7 @@ def _issuperset(cls, bounds1, bounds2): return True @classmethod - def _intersects(cls, bounds1, bounds2): + def _intersects(cls, bounds1: list[_Bound], bounds2: list[_Bound]) -> bool: # sort so bounds1 is the shorter list bounds1, bounds2 = sorted((bounds1, bounds2), key=lambda x: len(x)) @@ -1393,18 +1429,22 @@ class _ContainsVersionIterator(object): MODE_NON_INTERSECTING = 2 MODE_ALL = 3 - def __init__(self, range_, iterable, key=None, descending=False, mode=MODE_ALL): + def __init__(self, range_: VersionRange, iterable: Iterable[Any], + key: Callable[[Any], Version] | None = None, + descending: bool = False, mode: int = MODE_ALL) -> None: self.mode = mode self.range_ = range_ - self.index = None + self.index: int | None = None self.nbounds = len(self.range_.bounds) self._constant = True if range_.is_any() else None self.fn = self._descending if descending else self._ascending self.it = iter(iterable) if key is None: - key = lambda x: x # noqa: E731 + # FIXME: this case seems to assume that iterable is Iterable[Version] + key = cast(Callable[[Any], Version], lambda x: x) # noqa: E731 self.keyfunc = key + self.next_fn: Callable[[], tuple[bool, Any]] | Callable[[], Any] if mode == self.MODE_ALL: self.next_fn = self._next elif mode == self.MODE_INTERSECTING: @@ -1412,16 +1452,16 @@ def __init__(self, range_, iterable, key=None, descending=False, mode=MODE_ALL): else: self.next_fn = self._next_non_intersecting - def __iter__(self): + def __iter__(self) -> _ContainsVersionIterator[Any]: return self - def __next__(self): + def __next__(self) -> Any | tuple[bool, Any]: return self.next_fn() - def next(self): + def next(self) -> Any | tuple[bool, Any]: return self.next_fn() - def _next(self): + def _next(self) -> tuple[bool, Any]: value = next(self.it) if self._constant is not None: return self._constant, value @@ -1430,7 +1470,7 @@ def _next(self): intersects = self.fn(version) return intersects, value - def _next_intersecting(self): + def _next_intersecting(self) -> Any: while True: value = next(self.it) @@ -1444,7 +1484,7 @@ def _next_intersecting(self): if intersects: return value - def _next_non_intersecting(self): + def _next_non_intersecting(self) -> Any: while True: value = next(self.it) @@ -1459,13 +1499,13 @@ def _next_non_intersecting(self): return value @property - def _bound(self): + def _bound(self) -> _Bound | None: if self.index < self.nbounds: return self.range_.bounds[self.index] else: return None - def _ascending(self, version): + def _ascending(self, version: Version) -> bool: if self.index is None: self.index, contains = self.range_._contains_version(version) bound = self._bound @@ -1501,7 +1541,7 @@ def _ascending(self, version): elif j == -1: return False - def _descending(self, version): + def _descending(self, version: Version) -> bool: if self.index is None: self.index, contains = self.range_._contains_version(version) bound = self._bound diff --git a/src/rez/wrapper.py b/src/rez/wrapper.py index 8cf0b1f6b..75e401e63 100644 --- a/src/rez/wrapper.py +++ b/src/rez/wrapper.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + from rez.resolved_context import ResolvedContext from rez.utils.colorize import heading, local, critical, Printer from rez.utils.data_utils import cached_property @@ -24,7 +26,7 @@ class Wrapper(object): When a wrapper is executed, it runs the associated tool within the matching context in the suite. """ - def __init__(self, filepath): + def __init__(self, filepath) -> None: """Create a wrapper given its executable file.""" from rez.suite import Suite @@ -55,7 +57,7 @@ def _err(msg): context = ResolvedContext.load(path) self._init(suite_path, context_name, context, tool_name, prefix_char) - def _init(self, suite_path, context_name, context, tool_name, prefix_char=None): + def _init(self, suite_path, context_name, context, tool_name, prefix_char=None) -> None: self.suite_path = suite_path self.context_name = context_name self.context = context @@ -95,7 +97,7 @@ def _run(self, prefix_char, args): parser = argparse.ArgumentParser(prog=self.tool_name, prefix_chars=prefix_char) - def _add_argument(*nargs, **kwargs): + def _add_argument(*nargs, **kwargs) -> None: nargs_ = [] for narg in nargs: nargs_.append(narg.replace('=', prefix_char)) @@ -203,7 +205,7 @@ def _add_argument(*nargs, **kwargs): block=True) return retcode - def print_about(self): + def print_about(self) -> int: """Print an info message about the tool.""" filepath = os.path.join(self.suite_path, "bin", self.tool_name) print("Tool: %s" % self.tool_name) @@ -222,7 +224,7 @@ def print_about(self): print("Package: %s" % variant.qualified_package_name) return 0 - def print_package_versions(self): + def print_package_versions(self) -> int: """Print a list of versions of the package this tool comes from, and indicate which version this tool is from.""" variants = self.context.get_tool_variants(self.tool_name) @@ -254,7 +256,7 @@ def print_package_versions(self): _pr(line, col) return 0 - def peek(self): + def peek(self) -> int: config.remove_override("quiet") new_context = ResolvedContext(self.context.requested_packages(), package_paths=self.context.package_paths) @@ -267,7 +269,7 @@ def peek(self): return 0 @classmethod - def _print_conflicting(cls, variants): + def _print_conflicting(cls, variants) -> None: vars_str = " ".join(x.qualified_package_name for x in variants) msg = "Packages (in conflict): %s" % vars_str Printer()(msg, critical) diff --git a/src/rezgui/app.py b/src/rezgui/app.py index 593a722ed..48dc82750 100644 --- a/src/rezgui/app.py +++ b/src/rezgui/app.py @@ -23,7 +23,7 @@ def get_context_files(filepaths): return context_files -def run(opts=None, parser=None): +def run(opts=None, parser=None) -> None: main_window = MainWindow() app.set_main_window(main_window) main_window.show() diff --git a/src/rezgui/dialogs/AboutDialog.py b/src/rezgui/dialogs/AboutDialog.py index 2e90c9d72..a1df6ba24 100644 --- a/src/rezgui/dialogs/AboutDialog.py +++ b/src/rezgui/dialogs/AboutDialog.py @@ -9,7 +9,7 @@ class AboutDialog(QtWidgets.QDialog): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(AboutDialog, self).__init__(parent) self.setWindowTitle("About Rez") @@ -36,6 +36,6 @@ def __init__(self, parent=None): def sizeHint(self): return QtCore.QSize(300, 150) - def _goto_github(self): + def _goto_github(self) -> None: import webbrowser webbrowser.open_new("https://github.com/AcademySoftwareFoundation/rez") diff --git a/src/rezgui/dialogs/BrowsePackageDialog.py b/src/rezgui/dialogs/BrowsePackageDialog.py index 9a4b84f72..37041951f 100644 --- a/src/rezgui/dialogs/BrowsePackageDialog.py +++ b/src/rezgui/dialogs/BrowsePackageDialog.py @@ -11,8 +11,8 @@ class BrowsePackageDialog(QtWidgets.QDialog, StoreSizeMixin): def __init__(self, context_model, package_text=None, parent=None, - close_only=False, lock_package=False, - package_selectable_callback=None): + close_only: bool = False, lock_package: bool = False, + package_selectable_callback=None) -> None: config_key = "layout/window/browse_package" super(BrowsePackageDialog, self).__init__(parent) StoreSizeMixin.__init__(self, app.config, config_key) @@ -49,7 +49,7 @@ def __init__(self, context_model, package_text=None, parent=None, self.widget.packageSelected.connect(self._set_package) self.widget.set_package_text(package_text) - def _set_package(self): + def _set_package(self) -> None: package = self.widget.current_package() if package is None: self.setWindowTitle("Find Package") @@ -58,6 +58,6 @@ def _set_package(self): self.setWindowTitle("Find Package - %s" % package.qualified_name) self.ok_btn.setEnabled(True) - def _ok(self): + def _ok(self) -> None: self.package = self.widget.current_package() self.close() diff --git a/src/rezgui/dialogs/ImageViewerDialog.py b/src/rezgui/dialogs/ImageViewerDialog.py index 3aabd3d90..545ac07b7 100644 --- a/src/rezgui/dialogs/ImageViewerDialog.py +++ b/src/rezgui/dialogs/ImageViewerDialog.py @@ -10,7 +10,7 @@ class ImageViewerDialog(QtWidgets.QDialog, StoreSizeMixin): - def __init__(self, image_file, parent=None): + def __init__(self, image_file, parent=None) -> None: config_key = "layout/window/resolve_graph" super(ImageViewerDialog, self).__init__(parent) StoreSizeMixin.__init__(self, app.config, config_key) diff --git a/src/rezgui/dialogs/ProcessDialog.py b/src/rezgui/dialogs/ProcessDialog.py index b97ef5177..594aa32d9 100644 --- a/src/rezgui/dialogs/ProcessDialog.py +++ b/src/rezgui/dialogs/ProcessDialog.py @@ -16,7 +16,7 @@ class ProcessDialog(QtWidgets.QDialog, StoreSizeMixin): Note that in order to capture the process's output, you need to have piped its stdout and stderr to subprocess.PIPE. """ - def __init__(self, process, command_string, parent=None): + def __init__(self, process, command_string, parent=None) -> None: config_key = "layout/window/process" super(ProcessDialog, self).__init__(parent) StoreSizeMixin.__init__(self, app.config, config_key) @@ -49,10 +49,10 @@ def __init__(self, process, command_string, parent=None): close_btn.clicked.connect(self.close) - def closeEvent(self, event): + def closeEvent(self, event) -> None: self.capture_output = False - def _read_output(self, buf): + def _read_output(self, buf) -> None: for line in buf: try: self.lock.acquire() @@ -62,7 +62,7 @@ def _read_output(self, buf): if not self.capture_output: break - def _update(self): + def _update(self) -> None: if not self.output_ended \ and not self.stdout_thread.is_alive() \ and not self.stderr_thread.is_alive() \ diff --git a/src/rezgui/dialogs/ResolveDialog.py b/src/rezgui/dialogs/ResolveDialog.py index b33a852f2..060a0c821 100644 --- a/src/rezgui/dialogs/ResolveDialog.py +++ b/src/rezgui/dialogs/ResolveDialog.py @@ -16,7 +16,7 @@ class ResolveDialog(QtWidgets.QDialog, StoreSizeMixin): - def __init__(self, context_model, parent=None, advanced=False): + def __init__(self, context_model, parent=None, advanced: bool = False) -> None: config_key = ("layout/window/advanced_resolve" if advanced else "layout/window/resolve") super(ResolveDialog, self).__init__(parent) @@ -163,13 +163,13 @@ def get_context(self): return self.resolver.context return None - def reject(self): + def reject(self) -> None: if self._finished or not self.started: super(ResolveDialog, self).reject() else: self._cancel_resolve() - def closeEvent(self, event): + def closeEvent(self, event) -> None: if self._finished or not self.started: super(ResolveDialog, self).closeEvent(event) StoreSizeMixin.closeEvent(self, event) @@ -177,11 +177,11 @@ def closeEvent(self, event): self._cancel_resolve() event.ignore() - def _on_dialog_open(self): + def _on_dialog_open(self) -> None: if not self.advanced: self._start_resolve() - def _reset(self): + def _reset(self) -> None: self.setWindowTitle("Resolve") self.cancel_btn.setText("Cancel") self.cancel_btn.hide() @@ -199,7 +199,7 @@ def _reset(self): request_str = " ".join(str(x) for x in self.context_model.request) self._log("Resolving: %s...\n" % request_str) - def _log(self, msg, color=None): + def _log(self, msg, color=None) -> None: if color: old_color = self.edit.textColor() self.edit.setTextColor(QtGui.QColor(color)) @@ -208,7 +208,7 @@ def _log(self, msg, color=None): if color: self.edit.setTextColor(old_color) - def _start_resolve(self): + def _start_resolve(self) -> None: max_fails = self._get_max_fails() if max_fails is None: return @@ -248,13 +248,13 @@ def _start_resolve(self): self.resolver.run() self._resolve_finished() - def _cancel_resolve(self): + def _cancel_resolve(self) -> None: if self.started: self.cancel_btn.setText("Cancelling...") self.cancel_btn.setEnabled(False) self.resolver.stop() - def _resolve_finished(self): + def _resolve_finished(self) -> None: self._finished = True self.cancel_btn.hide() self.ok_btn.show() @@ -310,18 +310,18 @@ def _get_max_fails(self): return None return i - def _save_context(self): + def _save_context(self) -> None: filepath, _ = QtWidgets.QFileDialog.getSaveFileName( self, "Save Context", filter="Context files (*.rxt)") if filepath: self.resolver.context.save(filepath) self._log("\nSaved context to: %s" % filepath) - def _view_graph(self): + def _view_graph(self) -> None: graph_str = self.resolver.context.graph(as_dot=True) view_graph(graph_str, self) - def _set_progress(self, done=True): + def _set_progress(self, done: bool = True) -> None: if done is True: self.bar.setMaximum(10) self.bar.setValue(10) diff --git a/src/rezgui/dialogs/VariantVersionsDialog.py b/src/rezgui/dialogs/VariantVersionsDialog.py index 6c7d33f50..663852a8c 100644 --- a/src/rezgui/dialogs/VariantVersionsDialog.py +++ b/src/rezgui/dialogs/VariantVersionsDialog.py @@ -9,7 +9,7 @@ class VariantVersionsDialog(QtWidgets.QDialog, StoreSizeMixin): - def __init__(self, context_model, variant, reference_variant=None, parent=None): + def __init__(self, context_model, variant, reference_variant=None, parent=None) -> None: config_key = "layout/window/package_versions" super(VariantVersionsDialog, self).__init__(parent) StoreSizeMixin.__init__(self, app.config, config_key) diff --git a/src/rezgui/dialogs/WriteGraphDialog.py b/src/rezgui/dialogs/WriteGraphDialog.py index 7a242fd8c..e3bd91cc7 100644 --- a/src/rezgui/dialogs/WriteGraphDialog.py +++ b/src/rezgui/dialogs/WriteGraphDialog.py @@ -14,18 +14,18 @@ class Writer(QtCore.QObject): graph_written = QtCore.Signal(str, str) - def __init__(self, graph_str, filepath, prune_to=None): + def __init__(self, graph_str, filepath, prune_to=None) -> None: super(Writer, self).__init__() self.graph_str = graph_str self.filepath = filepath self.prune_to = prune_to self.process = None - def cancel(self): + def cancel(self) -> None: if self.process: self.process.terminate() - def write_graph(self): + def write_graph(self) -> None: if self.prune_to: graph_str = prune_graph(self.graph_str, self.prune_to) else: @@ -41,7 +41,7 @@ def write_graph(self): class WriteGraphDialog(QtWidgets.QDialog): - def __init__(self, graph_str, filepath, parent=None, prune_to=None): + def __init__(self, graph_str, filepath, parent=None, prune_to=None) -> None: super(WriteGraphDialog, self).__init__(parent) self.setWindowTitle("Rendering graph...") self.writer = Writer(graph_str, filepath, prune_to) @@ -72,27 +72,27 @@ def write_graph(self): self.thread.join() return self.success - def reject(self): + def reject(self) -> None: if self._finished: super(WriteGraphDialog, self).reject() else: self._cancel() - def closeEvent(self, event): + def closeEvent(self, event) -> None: if self._finished: event.accept() else: self._cancel() event.ignore() - def _cancel(self): + def _cancel(self) -> None: self.bar.setMaximum(10) self.bar.setValue(10) self.cancel_btn.setText("Cancelling...") self.cancel_btn.setEnabled(False) self.writer.cancel() - def _graph_written(self, filepath, error_message): + def _graph_written(self, filepath, error_message) -> None: self._finished = True self.bar.setMaximum(10) self.bar.setValue(10) @@ -110,7 +110,7 @@ def _graph_written(self, filepath, error_message): graph_file_lookup = {} -def view_graph(graph_str, parent=None, prune_to=None): +def view_graph(graph_str, parent=None, prune_to=None) -> None: """View a graph.""" from rezgui.dialogs.ImageViewerDialog import ImageViewerDialog from rez.config import config diff --git a/src/rezgui/mixins/ContextViewMixin.py b/src/rezgui/mixins/ContextViewMixin.py index 67be42e72..ff13992bd 100644 --- a/src/rezgui/mixins/ContextViewMixin.py +++ b/src/rezgui/mixins/ContextViewMixin.py @@ -19,7 +19,7 @@ def __init__(self, context_model): def _contextChanged(self, flags=0): # handle the context update """ - def __init__(self, context_model=None): + def __init__(self, context_model=None) -> None: assert isinstance(self, QtCore.QObject) self.context_model = context_model or ContextModel() self._connect(True) @@ -27,12 +27,12 @@ def __init__(self, context_model=None): def context(self): return self.context_model.context() - def set_context_model(self, context_model=None): + def set_context_model(self, context_model=None) -> None: self._connect(False) self.context_model = context_model or ContextModel() self._connect(True) - def _connect(self, b): + def _connect(self, b) -> None: if hasattr(self, "_contextChanged"): sig = self.context_model.dataChanged fn = sig.connect if b else sig.disconnect diff --git a/src/rezgui/mixins/StoreSizeMixin.py b/src/rezgui/mixins/StoreSizeMixin.py index 7755ffeb0..ca8443ca9 100644 --- a/src/rezgui/mixins/StoreSizeMixin.py +++ b/src/rezgui/mixins/StoreSizeMixin.py @@ -8,7 +8,7 @@ class StoreSizeMixin(object): """A mixing for persisting a top-level widget's dimensions. """ - def __init__(self, config, config_key): + def __init__(self, config, config_key) -> None: assert isinstance(self, QtWidgets.QWidget) self.config = config self.config_key = config_key @@ -18,7 +18,7 @@ def sizeHint(self): height = self.config.get(self.config_key + "/height") return QtCore.QSize(width, height) - def closeEvent(self, event): + def closeEvent(self, event) -> None: size = self.size() self.config.setValue(self.config_key + "/width", size.width()) self.config.setValue(self.config_key + "/height", size.height()) diff --git a/src/rezgui/models/ContextModel.py b/src/rezgui/models/ContextModel.py index 5088fb13f..483e0d042 100644 --- a/src/rezgui/models/ContextModel.py +++ b/src/rezgui/models/ContextModel.py @@ -31,7 +31,7 @@ class ContextModel(QtCore.QObject): PACKAGE_FILTER_CHANGED = 32 CACHING_CHANGED = 64 - def __init__(self, context=None, parent=None): + def __init__(self, context=None, parent=None) -> None: super(ContextModel, self).__init__(parent) self._context = None @@ -137,19 +137,19 @@ def get_lock_requests(self): d[lock].append(request) return d - def set_request(self, request_strings): + def set_request(self, request_strings) -> None: self._attr_changed("request", request_strings, self.REQUEST_CHANGED) - def set_packages_path(self, packages_path): + def set_packages_path(self, packages_path) -> None: self._attr_changed("packages_path", packages_path, self.PACKAGES_PATH_CHANGED) - def set_package_filter(self, package_filter): + def set_package_filter(self, package_filter) -> None: self._attr_changed("package_filter", package_filter, self.PACKAGE_FILTER_CHANGED) - def set_caching(self, caching): + def set_caching(self, caching) -> None: self._attr_changed("caching", caching, self.CACHING_CHANGED) - def save(self, filepath): + def save(self, filepath) -> None: assert self._context assert not self._stale self._context.save(filepath) @@ -157,26 +157,26 @@ def save(self, filepath): self._modified = False self.dataChanged.emit(self.LOADPATH_CHANGED) - def set_default_patch_lock(self, lock): + def set_default_patch_lock(self, lock) -> None: self._attr_changed("default_patch_lock", lock, self.LOCKS_CHANGED) - def set_patch_lock(self, package_name, lock): + def set_patch_lock(self, package_name, lock) -> None: existing_lock = self.patch_locks.get(package_name) if lock != existing_lock: self.patch_locks[package_name] = lock self._changed(self.LOCKS_CHANGED) - def remove_patch_lock(self, package_name): + def remove_patch_lock(self, package_name) -> None: if package_name in self.patch_locks: del self.patch_locks[package_name] self._changed(self.LOCKS_CHANGED) - def remove_all_patch_locks(self): + def remove_all_patch_locks(self) -> None: if self.patch_locks: self.patch_locks = {} self._changed(self.LOCKS_CHANGED) - def resolve_context(self, verbosity=0, max_fails=-1, timestamp=None, + def resolve_context(self, verbosity: int=0, max_fails=-1, timestamp=None, callback=None, buf=None, package_load_callback=None): """Update the current context by performing a re-resolve. @@ -210,12 +210,12 @@ def can_revert(self): """Return True if the model is revertable, False otherwise.""" return bool(self._stale and self._context) - def revert(self): + def revert(self) -> None: """Discard any pending changes.""" if self.can_revert(): self._set_context(self._context) - def set_context(self, context): + def set_context(self, context) -> None: """Replace the current context with another.""" self._set_context(context, emit=False) self._modified = (not context.load_path) @@ -227,7 +227,7 @@ def set_context(self, context): self.PACKAGE_FILTER_CHANGED | self.CACHING_CHANGED) - def _set_context(self, context, emit=True): + def _set_context(self, context, emit: bool = True) -> None: self._context = context self._stale = False self._dependency_lookup = None @@ -245,12 +245,12 @@ def _set_context(self, context, emit=True): self.PACKAGES_PATH_CHANGED | self.LOCKS_CHANGED) - def _changed(self, flags): + def _changed(self, flags) -> None: self._stale = True self._modified = True self.dataChanged.emit(flags) - def _attr_changed(self, attr, value, flags): + def _attr_changed(self, attr, value, flags) -> None: if getattr(self, attr) == value: return setattr(self, attr, value) diff --git a/src/rezgui/objects/App.py b/src/rezgui/objects/App.py index ea3fc2535..418d662fe 100644 --- a/src/rezgui/objects/App.py +++ b/src/rezgui/objects/App.py @@ -17,7 +17,7 @@ class App(QtWidgets.QApplication): - def __init__(self, argv=None): + def __init__(self, argv=None) -> None: if argv is None: argv = sys.argv super(App, self).__init__(argv) @@ -49,7 +49,7 @@ def status(self, txt): with self.main_window.status(txt): yield - def set_main_window(self, window): + def set_main_window(self, window) -> None: self.main_window = window def load_context(self, filepath): @@ -72,7 +72,7 @@ def load_context(self, filepath): "max_most_recent_contexts") return context - def execute_shell(self, context, command=None, terminal=False, **Popen_args): + def execute_shell(self, context, command=None, terminal: bool = False, **Popen_args): # if the gui was called from a rez-env'd environ, then the new shell # here will have a prompt like '>>'. It's not incorrect, but it is a diff --git a/src/rezgui/objects/Config.py b/src/rezgui/objects/Config.py index e13b6f0d4..bd3b7b6aa 100644 --- a/src/rezgui/objects/Config.py +++ b/src/rezgui/objects/Config.py @@ -12,7 +12,7 @@ class Config(QtCore.QSettings): Methods are also provided for easily attaching widgets to settings. """ def __init__(self, default_settings, organization=None, application=None, - parent=None): + parent=None) -> None: super(Config, self).__init__(organization, application, parent) self.default_settings = default_settings @@ -50,7 +50,7 @@ def get_string_list(self, key): self.endArray() return strings - def prepend_string_list(self, key, value, max_length_key): + def prepend_string_list(self, key, value, max_length_key) -> None: """Prepend a fixed-length string list with a new string. The oldest string will be removed from the list. If the string is @@ -98,11 +98,11 @@ def _attach_checkbox(self, widget, key): widget.stateChanged.connect( partial(self._checkbox_stateChanged, widget, key)) - def _checkbox_stateChanged(self, widget, key): + def _checkbox_stateChanged(self, widget, key) -> None: value = widget.isChecked() self.setValue(key, value) - def _attach_combobox(self, widget, key): + def _attach_combobox(self, widget, key) -> None: value = str(self.value(key)) index = widget.findText(value) if index == -1: @@ -115,11 +115,11 @@ def _attach_combobox(self, widget, key): widget.editTextChanged.connect( partial(self._combobox_editTextChanged, widget, key)) - def _combobox_currentIndexChanged(self, widget, key, index): + def _combobox_currentIndexChanged(self, widget, key, index) -> None: value = widget.itemText(index) self.setValue(key, value) - def _combobox_editTextChanged(self, widget, key, txt): + def _combobox_editTextChanged(self, widget, key, txt) -> None: self.setValue(key, txt) def _default_value(self, key): diff --git a/src/rezgui/objects/LoadPackagesThread.py b/src/rezgui/objects/LoadPackagesThread.py index 7e58398c2..9063e1933 100644 --- a/src/rezgui/objects/LoadPackagesThread.py +++ b/src/rezgui/objects/LoadPackagesThread.py @@ -15,7 +15,7 @@ class LoadPackagesThread(QtCore.QObject): finished = QtCore.Signal(object) def __init__(self, package_paths, package_name, range_=None, - package_attributes=None, callback=None): + package_attributes=None, callback=None) -> None: super(LoadPackagesThread, self).__init__() self.stopped = False self.package_paths = package_paths @@ -24,10 +24,10 @@ def __init__(self, package_paths, package_name, range_=None, self.package_attributes = package_attributes self.callback = callback - def stop(self): + def stop(self) -> None: self.stopped = True - def run(self): + def run(self) -> None: it = iter_packages(name=self.package_name, paths=self.package_paths, range_=self.range_) packages = sorted(it, key=lambda x: x.version, reverse=True) num_packages = len(packages) diff --git a/src/rezgui/objects/ProcessTrackerThread.py b/src/rezgui/objects/ProcessTrackerThread.py index 8a58e91c4..b0108a392 100644 --- a/src/rezgui/objects/ProcessTrackerThread.py +++ b/src/rezgui/objects/ProcessTrackerThread.py @@ -11,7 +11,7 @@ class ProcessTrackerThread(QtCore.QThread): instanceCountChanged = QtCore.Signal(int, str, int) - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(ProcessTrackerThread, self).__init__(parent) self.processes = {} self.proc_list = [] @@ -21,7 +21,7 @@ def __init__(self, parent=None): self.timer = QtCore.QTimer() self.timer.timeout.connect(self._update) - def run(self): + def run(self) -> None: self.timer.start() self.exec_() @@ -41,7 +41,7 @@ def running_instances(self, context, process_name): entries = [x for x in it if x[0].poll() is None] return entries - def add_instance(self, context, process_name, process): + def add_instance(self, context, process_name, process) -> None: try: self.lock.acquire() entry = (id(context), process_name, process, int(time.time())) @@ -49,7 +49,7 @@ def add_instance(self, context, process_name, process): finally: self.lock.release() - def _update(self): + def _update(self) -> None: # add pending instances if self.pending_procs: try: diff --git a/src/rezgui/objects/ResolveThread.py b/src/rezgui/objects/ResolveThread.py index f600e4eb2..1ad4becfd 100644 --- a/src/rezgui/objects/ResolveThread.py +++ b/src/rezgui/objects/ResolveThread.py @@ -10,8 +10,8 @@ class ResolveThread(QtCore.QObject): finished = QtCore.Signal() - def __init__(self, context_model, verbosity=0, max_fails=-1, timestamp=None, - show_package_loads=True, buf=None): + def __init__(self, context_model, verbosity: int=0, max_fails=-1, timestamp=None, + show_package_loads: bool = True, buf=None) -> None: super(ResolveThread, self).__init__() self.context_model = context_model self.context = None @@ -25,7 +25,7 @@ def __init__(self, context_model, verbosity=0, max_fails=-1, timestamp=None, self.abort_reason = None self.error_message = None - def run(self): + def run(self) -> None: package_load_callback = (self._package_load_callback if self.show_package_loads else None) try: @@ -42,7 +42,7 @@ def run(self): if not self.stopped: self.finished.emit() - def stop(self): + def stop(self) -> None: self.stopped = True self.abort_reason = "Cancelled by user." @@ -54,6 +54,6 @@ def _callback(self, solver_state): print("solve step %d..." % solver_state.num_solves, file=self.buf) return (not self.stopped), self.abort_reason - def _package_load_callback(self, package): + def _package_load_callback(self, package) -> None: if self.buf: print("loading %s..." % str(package), file=self.buf) diff --git a/src/rezgui/util.py b/src/rezgui/util.py index a1b0a3575..d395735bb 100644 --- a/src/rezgui/util.py +++ b/src/rezgui/util.py @@ -8,8 +8,8 @@ import time -def create_pane(widgets, horizontal, parent_widget=None, compact=False, - compact_spacing=2): +def create_pane(widgets, horizontal, parent_widget=None, compact: bool = False, + compact_spacing: int=2): """Create a widget containing an aligned set of widgets. Args: @@ -51,7 +51,7 @@ def create_pane(widgets, horizontal, parent_widget=None, compact=False, icons = {} -def get_icon(name, as_qicon=False): +def get_icon(name, as_qicon: bool = False): """Returns a `QPixmap` containing the given image, or a QIcon if `as_qicon` is True""" filename = name + ".png" @@ -78,7 +78,7 @@ def get_icon_widget(filename, tooltip=None): return icon_label -def get_timestamp_str(timestamp): +def get_timestamp_str(timestamp) -> str: now = int(time.time()) release_time = time.localtime(timestamp) release_time_str = time.strftime('%d %b %Y %H:%M:%S', release_time) @@ -143,7 +143,7 @@ def create_toolbutton(entries, parent=None): return btn, actions -def update_font(widget, italic=None, bold=None, underline=None): +def update_font(widget, italic=None, bold=None, underline=None) -> None: font = widget.font() if italic is not None: font.setItalic(italic) diff --git a/src/rezgui/widgets/BrowsePackagePane.py b/src/rezgui/widgets/BrowsePackagePane.py index 23c0c4fb2..014298a17 100644 --- a/src/rezgui/widgets/BrowsePackagePane.py +++ b/src/rezgui/widgets/BrowsePackagePane.py @@ -17,7 +17,7 @@ class BrowsePackagePane(QtWidgets.QTabWidget, ContextViewMixin): because it is intended to allow browsing of packages within an existing context. """ - def __init__(self, context_model=None, parent=None): + def __init__(self, context_model=None, parent=None) -> None: super(BrowsePackagePane, self).__init__(parent) ContextViewMixin.__init__(self, context_model) diff --git a/src/rezgui/widgets/BrowsePackageWidget.py b/src/rezgui/widgets/BrowsePackageWidget.py index 39eff4dfe..c772ca4da 100644 --- a/src/rezgui/widgets/BrowsePackageWidget.py +++ b/src/rezgui/widgets/BrowsePackageWidget.py @@ -17,8 +17,8 @@ class BrowsePackageWidget(QtWidgets.QWidget, ContextViewMixin): """ packageSelected = QtCore.Signal() - def __init__(self, context_model=None, parent=None, lock_package=False, - package_selectable_callback=None): + def __init__(self, context_model=None, parent=None, lock_package: bool = False, + package_selectable_callback=None) -> None: super(BrowsePackageWidget, self).__init__(parent) ContextViewMixin.__init__(self, context_model) @@ -46,7 +46,7 @@ def __init__(self, context_model=None, parent=None, lock_package=False, self.edit.focusOutViaKeyPress.connect(self._set_package_name) self.versions_table.itemSelectionChanged.connect(self._set_package) - def set_package_text(self, txt): + def set_package_text(self, txt) -> None: try: req = Requirement(str(txt)) package_name = req.name @@ -64,11 +64,11 @@ def set_package_text(self, txt): def current_package(self): return self.versions_table.current_package() - def _set_package_name(self, package_name): + def _set_package_name(self, package_name) -> None: self.versions_table.set_package_name(package_name) self.versions_table.setFocus() - def _set_package(self): + def _set_package(self) -> None: package = self.versions_table.current_package() self.package_tab.set_package(package) self.packageSelected.emit() diff --git a/src/rezgui/widgets/ChangelogEdit.py b/src/rezgui/widgets/ChangelogEdit.py index ec71b2711..560de537a 100644 --- a/src/rezgui/widgets/ChangelogEdit.py +++ b/src/rezgui/widgets/ChangelogEdit.py @@ -14,12 +14,12 @@ def plaintext_to_html(txt): class ChangelogEdit(QtWidgets.QPlainTextEdit): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(ChangelogEdit, self).__init__(parent) self.setReadOnly(True) self.setUndoRedoEnabled(False) - def set_packages(self, packages): + def set_packages(self, packages) -> None: # note - I'm not just using appendHtml()/appendPlainText, because there's # a qt bug causing this to mess up the formatting. Hence the need to # convert plaintext to html manually. @@ -44,11 +44,11 @@ def set_packages(self, packages): class VariantChangelogEdit(ChangelogEdit): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(VariantChangelogEdit, self).__init__(parent) self.variant = None - def set_variant(self, variant): + def set_variant(self, variant) -> None: if variant is None: self.clear() else: diff --git a/src/rezgui/widgets/ConfiguredSplitter.py b/src/rezgui/widgets/ConfiguredSplitter.py index 940af7bd6..a81370d2e 100644 --- a/src/rezgui/widgets/ConfiguredSplitter.py +++ b/src/rezgui/widgets/ConfiguredSplitter.py @@ -8,14 +8,14 @@ class ConfiguredSplitter(QtWidgets.QSplitter): """A QSplitter that remembers its widget sizes. """ - def __init__(self, config, config_key, *nargs, **kwargs): + def __init__(self, config, config_key, *nargs, **kwargs) -> None: super(ConfiguredSplitter, self).__init__(*nargs, **kwargs) self.config = config self.config_key = config_key self.splitterMoved.connect(self._splitterMoved) - def apply_saved_layout(self): + def apply_saved_layout(self) -> bool: """Call this after adding your child widgets.""" num_widgets = self.config.get(self.config_key + "/num_widgets", int) if num_widgets: @@ -28,7 +28,7 @@ def apply_saved_layout(self): return True return False - def _splitterMoved(self, pos, index): + def _splitterMoved(self, pos, index) -> None: sizes = self.sizes() self.config.setValue(self.config_key + "/num_widgets", len(sizes)) for i, size in enumerate(sizes): diff --git a/src/rezgui/widgets/ContextDetailsWidget.py b/src/rezgui/widgets/ContextDetailsWidget.py index f8cbc4898..9e09541c0 100644 --- a/src/rezgui/widgets/ContextDetailsWidget.py +++ b/src/rezgui/widgets/ContextDetailsWidget.py @@ -16,7 +16,7 @@ class ContextDetailsWidget(QtWidgets.QTabWidget, ContextViewMixin): - def __init__(self, context_model=None, parent=None): + def __init__(self, context_model=None, parent=None) -> None: super(ContextDetailsWidget, self).__init__(parent) ContextViewMixin.__init__(self, context_model) self.code_pending = True @@ -54,7 +54,7 @@ def __init__(self, context_model=None, parent=None): self.refresh() - def refresh(self): + def refresh(self) -> None: self.overview_edit.clear() self.setCurrentIndex(0) @@ -68,23 +68,23 @@ def refresh(self): self.overview_edit.moveCursor(QtGui.QTextCursor.Start) self.environ_widget.set_context(context) - def search(self): + def search(self) -> None: tab_index = self.currentIndex() if tab_index == 0: self.overview_edit.search() elif tab_index == 1: self.code_edit.search() - def _contextChanged(self, flags=0): + def _contextChanged(self, flags: int=0) -> None: if not (flags & ContextModel.CONTEXT_CHANGED): return self.refresh() - def _currentTabChanged(self, index): + def _currentTabChanged(self, index) -> None: if index == 1 and self.code_pending: self._update_code() - def _update_code(self): + def _update_code(self) -> None: self.code_edit.clear() context = self.context() if not context: diff --git a/src/rezgui/widgets/ContextEnvironTable.py b/src/rezgui/widgets/ContextEnvironTable.py index 8b7968206..1b7fb07e1 100644 --- a/src/rezgui/widgets/ContextEnvironTable.py +++ b/src/rezgui/widgets/ContextEnvironTable.py @@ -6,7 +6,7 @@ class ContextEnvironTable(QtWidgets.QTableWidget): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(ContextEnvironTable, self).__init__(0, 2, parent) self.context = None self.split_char = None @@ -27,13 +27,13 @@ def __init__(self, parent=None): vh, QtWidgets.QHeaderView.ResizeToContents) self.setEnabled(False) - def clear(self): + def clear(self) -> None: super(ContextEnvironTable, self).clear() self.setEnabled(False) hh = self.horizontalHeader() hh.setVisible(False) - def set_split_character(self, ch=None): + def set_split_character(self, ch=None) -> None: """Set the 'split' character. If a 'split' character is set, values containing this character are @@ -44,12 +44,12 @@ def set_split_character(self, ch=None): self.split_char = ch self.refresh() - def refresh(self): + def refresh(self) -> None: context = self.context self.context = None self.set_context(context) - def set_context(self, context): + def set_context(self, context) -> None: self.clear() self.context = context if self.context is None: diff --git a/src/rezgui/widgets/ContextEnvironWidget.py b/src/rezgui/widgets/ContextEnvironWidget.py index 0b06a02a7..56570471b 100644 --- a/src/rezgui/widgets/ContextEnvironWidget.py +++ b/src/rezgui/widgets/ContextEnvironWidget.py @@ -16,7 +16,7 @@ class ContextEnvironWidget(QtWidgets.QWidget): ("Comma (,)", ','), ("Whitespace", ' ')] - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(ContextEnvironWidget, self).__init__(parent) self.table = ContextEnvironTable() @@ -35,10 +35,10 @@ def __init__(self, parent=None): self.split_combo.currentIndexChanged.connect(self._set_split_char) app.config.attach(self.split_combo, "split_char") - def set_context(self, context): + def set_context(self, context) -> None: self.table.set_context(context) - def _set_split_char(self): + def _set_split_char(self) -> None: index = self.split_combo.currentIndex() ch = self.split_entries[index][1] self.table.set_split_character(ch) diff --git a/src/rezgui/widgets/ContextManagerWidget.py b/src/rezgui/widgets/ContextManagerWidget.py index 557a90499..3b59f4b5c 100644 --- a/src/rezgui/widgets/ContextManagerWidget.py +++ b/src/rezgui/widgets/ContextManagerWidget.py @@ -26,7 +26,7 @@ class ContextManagerWidget(QtWidgets.QWidget, ContextViewMixin): diffModeChanged = QtCore.Signal() - def __init__(self, context_model=None, parent=None): + def __init__(self, context_model=None, parent=None) -> None: super(ContextManagerWidget, self).__init__(parent) ContextViewMixin.__init__(self, context_model) @@ -202,10 +202,10 @@ def get_title(self): """Returns a string suitable for titling a window containing this widget.""" return self.context_table.get_title() - def refresh(self): + def refresh(self) -> None: self._contextChanged(ContextModel.CONTEXT_CHANGED) - def _resolve(self, advanced=False): + def _resolve(self, advanced: bool = False) -> None: dlg = ResolveDialog(self.context_model, parent=self, advanced=advanced) dlg.resolve() # this updates the model on successful solve @@ -218,62 +218,62 @@ def _changes_prompt(self): QtWidgets.QMessageBox.Cancel) return (ret == QtWidgets.QMessageBox.Ok) - def _revert_to_last_resolve(self): + def _revert_to_last_resolve(self) -> None: assert self.context_model.can_revert() if self._changes_prompt(): self.context_model.revert() - def _revert_to_diff(self): + def _revert_to_diff(self) -> None: if self._changes_prompt(): self.context_table.revert_to_diff() - def _revert_to_disk(self): + def _revert_to_disk(self) -> None: if self._changes_prompt(): self.context_table.revert_to_disk() - def _open_shell(self): + def _open_shell(self) -> None: assert self.context() app.execute_shell(context=self.context(), terminal=True) - def _leave_diff_mode(self): + def _leave_diff_mode(self) -> None: self.context_table.leave_diff_mode() self._change_diff_mode(False) - def _diff_with_last_resolve(self): + def _diff_with_last_resolve(self) -> None: self.context_table.enter_diff_mode() self._change_diff_mode(True) - def _diff_with_disk(self): + def _diff_with_disk(self) -> None: filepath = self.context_model.filepath() self._diff_with_file(filepath) - def _diff_with_other(self): + def _diff_with_other(self) -> None: filepath, _ = QtWidgets.QFileDialog.getOpenFileName( self, "Open Context", filter="Context files (*.rxt)") if filepath: self._diff_with_file(str(filepath)) - def _diff_with_file(self, filepath): + def _diff_with_file(self, filepath) -> None: assert filepath disk_context = app.load_context(filepath) model = ContextModel(disk_context) self.context_table.enter_diff_mode(model) self._change_diff_mode(True) - def _change_diff_mode(self, enabled): + def _change_diff_mode(self, enabled) -> None: self.undiff_tbtn.setChecked(enabled) self.diff_tbtn_action.setVisible(not enabled) self.undiff_tbtn_action.setVisible(enabled) self.diffModeChanged.emit() - def _aboutToShowDiffMenu(self): + def _aboutToShowDiffMenu(self) -> None: stale = self.context_model.is_stale() self.diff_action.setEnabled(not stale) self.diff_to_other_action.setEnabled(not stale) self.diff_to_disk_action.setEnabled(bool(self.context_model.filepath()) and not stale) - def _aboutToShowRevertMenu(self): + def _aboutToShowRevertMenu(self) -> None: model = self.context_model self.revert_action.setEnabled(model.can_revert()) self.revert_disk_action.setEnabled(bool(model.filepath()) @@ -282,7 +282,7 @@ def _aboutToShowRevertMenu(self): and self.context_table.diff_from_source and not model.is_stale()) - def _contextChanged(self, flags=0): + def _contextChanged(self, flags: int=0) -> None: stale = self.context_model.is_stale() context = self.context() is_context = bool(context) @@ -321,13 +321,13 @@ def _contextChanged(self, flags=0): icon = get_icon(lock_type.name, as_qicon=True) self.lock_tbtn.setIcon(icon) - def _variantSelected(self, variant): + def _variantSelected(self, variant) -> None: self.package_tab.set_variant(variant) - def _effectiveRequestStateChanged(self, state): + def _effectiveRequestStateChanged(self, state) -> None: self.context_table.show_effective_request(state == QtCore.Qt.Checked) - def _timelockClicked(self): + def _timelockClicked(self) -> None: title = "The resolve is timelocked" body = str(self.time_lock_tbtn.toolTip()).capitalize() secs = int(time.time()) - self.context().requested_timestamp @@ -335,24 +335,24 @@ def _timelockClicked(self): body += "\n(%s ago)" % t_str QtWidgets.QMessageBox.information(self, title, body) - def _set_lock_type(self, lock_type): + def _set_lock_type(self, lock_type) -> None: self.context_model.set_default_patch_lock(lock_type) - def _updateToolsCount(self): + def _updateToolsCount(self) -> None: label = "tools (%d)" % self.tools_list.num_tools() self.tab.setTabText(2, label) - def _removeExplicitLocks(self): + def _removeExplicitLocks(self) -> None: self.context_model.remove_all_patch_locks() - def _search(self): + def _search(self) -> None: tab_index = self.tab.currentIndex() if tab_index == 0: self._search_variant() elif tab_index == 3: self.resolve_details.search() - def _search_variant(self): + def _search_variant(self) -> None: context = self.context() if not context: return diff --git a/src/rezgui/widgets/ContextResolveTimeLabel.py b/src/rezgui/widgets/ContextResolveTimeLabel.py index bbd783bf3..1874225a6 100644 --- a/src/rezgui/widgets/ContextResolveTimeLabel.py +++ b/src/rezgui/widgets/ContextResolveTimeLabel.py @@ -10,7 +10,7 @@ class ContextResolveTimeLabel(QtWidgets.QLabel, ContextViewMixin): - def __init__(self, context_model=None, parent=None): + def __init__(self, context_model=None, parent=None) -> None: super(ContextResolveTimeLabel, self).__init__(parent) ContextViewMixin.__init__(self, context_model) @@ -19,7 +19,7 @@ def __init__(self, context_model=None, parent=None): self.timer.timeout.connect(self.refresh) self.refresh() - def refresh(self): + def refresh(self) -> None: context = self.context() if not context: self.timer.stop() @@ -35,6 +35,6 @@ def refresh(self): self.setText("resolved %s ago" % time_txt) self.timer.start() - def _contextChanged(self, flags=0): + def _contextChanged(self, flags: int=0) -> None: if flags & ContextModel.CONTEXT_CHANGED: self.refresh() diff --git a/src/rezgui/widgets/ContextSettingsWidget.py b/src/rezgui/widgets/ContextSettingsWidget.py index 095f76a3c..5a5777306 100644 --- a/src/rezgui/widgets/ContextSettingsWidget.py +++ b/src/rezgui/widgets/ContextSettingsWidget.py @@ -31,7 +31,7 @@ class ContextSettingsWidget(QtWidgets.QWidget, ContextViewMixin): "caching": bool } - def __init__(self, context_model=None, attributes=None, parent=None): + def __init__(self, context_model=None, attributes=None, parent=None) -> None: """ Args: attributes (list of str): Select only certain settings to expose. If @@ -71,13 +71,13 @@ def __init__(self, context_model=None, attributes=None, parent=None): self._update_text() - def _contextChanged(self, flags=0): + def _contextChanged(self, flags: int=0) -> None: if not (flags & ContextModel.CONTEXT_CHANGED): return self._update_text() - def apply_changes(self): - def _content_error(title, text): + def apply_changes(self) -> None: + def _content_error(title, text) -> None: ret = QtWidgets.QMessageBox.warning(self, title, text, QtWidgets.QMessageBox.Discard, QtWidgets.QMessageBox.Cancel) @@ -106,7 +106,7 @@ def _content_error(title, text): self.context_model.set_caching(data["caching"]) self._update_text() - def discard_changes(self, prompt=False): + def discard_changes(self, prompt: bool = False) -> None: if prompt: ret = QtWidgets.QMessageBox.warning( self, @@ -119,7 +119,7 @@ def discard_changes(self, prompt=False): self._update_text() - def set_defaults(self): + def set_defaults(self) -> None: packages_path = config.packages_path caching = config.caching implicits = [str(x) for x in config.implicit_packages] @@ -136,7 +136,7 @@ def set_defaults(self): self.discard_btn.setEnabled(True) self.apply_btn.setEnabled(True) - def _update_text(self): + def _update_text(self) -> None: model = self.context_model implicits = [str(x) for x in model.implicit_packages] data = {"packages_path": model.packages_path, @@ -150,7 +150,7 @@ def _update_text(self): self.discard_btn.setEnabled(False) self.apply_btn.setEnabled(False) - def _set_text(self, data): + def _set_text(self, data) -> None: lines = [] for key, value in data.items(): lines.append('') @@ -164,6 +164,6 @@ def _set_text(self, data): txt = txt.lstrip() self.edit.setPlainText(txt) - def _settingsChanged(self): + def _settingsChanged(self) -> None: self.discard_btn.setEnabled(True) self.apply_btn.setEnabled(True) diff --git a/src/rezgui/widgets/ContextTableWidget.py b/src/rezgui/widgets/ContextTableWidget.py index c786af516..a4ee9cc9b 100644 --- a/src/rezgui/widgets/ContextTableWidget.py +++ b/src/rezgui/widgets/ContextTableWidget.py @@ -20,7 +20,7 @@ class CompareCell(QtWidgets.QWidget): def __init__(self, context_model, variant_left=None, variant_right=None, - parent=None): + parent=None) -> None: super(CompareCell, self).__init__(parent) self.context_model = context_model self.left_variant = variant_left @@ -96,13 +96,13 @@ def __init__(self, context_model, variant_left=None, variant_right=None, parent_widget=self) widget.clicked.connect(self._clicked) - def left(self): + def left(self) -> bool: return (self.side in ("left", "both")) - def right(self): + def right(self) -> bool: return (self.side in ("right", "both")) - def _clicked(self): + def _clicked(self) -> None: if self.comparable: from rezgui.dialogs.VariantVersionsDialog import VariantVersionsDialog dlg = VariantVersionsDialog(self.context_model, self.left_variant, @@ -147,7 +147,7 @@ def _clicked(self): "the package in the reference resolve (%s)" % (self.left_variant.uri, self.right_variant.uri)) - def _set_color(self, *c): + def _set_color(self, *c) -> None: f = 0.8 col = self.palette().color(QtGui.QPalette.Active, QtGui.QPalette.Base) bg_c = (col.redF(), col.greenF(), col.blueF()) @@ -158,7 +158,7 @@ def _set_color(self, *c): class CellDelegate(QtWidgets.QStyledItemDelegate): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(CellDelegate, self).__init__(parent) pal = self.parent().palette() col = pal.color(QtGui.QPalette.Active, QtGui.QPalette.Button) @@ -186,7 +186,7 @@ def __init__(self, parent=None): grad.setColorAt(1, c1) self.highlight_brush = QtGui.QBrush(grad) - def paint(self, painter, option, index): + def paint(self, painter, option, index) -> None: row = index.row() column = index.column() table = self.parent() @@ -197,7 +197,7 @@ def paint(self, painter, option, index): oldpen = painter.pen() pal = table.palette() - def _setpen(to_stale): + def _setpen(to_stale) -> None: pen = self.stale_pen if stale and to_stale else self.pen painter.setPen(pen) @@ -238,7 +238,7 @@ def _setpen(to_stale): # draw the curvy bits in the comparison column draw_right_edge = True - def _draw_path(): + def _draw_path() -> None: painter.setRenderHints(QtGui.QPainter.Antialiasing, True) painter.drawPath(self.path) painter.resetTransform() @@ -291,7 +291,7 @@ class ContextTableWidget(QtWidgets.QTableWidget, ContextViewMixin): short_double_arrow = u"\u21D4" variantSelected = QtCore.Signal(object) - def __init__(self, context_model=None, parent=None): + def __init__(self, context_model=None, parent=None) -> None: """Create a context table.""" super(ContextTableWidget, self).__init__(self.default_row_count, 2, parent) @@ -336,7 +336,7 @@ def current_variant(self): """Returns the currently selected variant, if any.""" return self._current_variant - def show_effective_request(self, b): + def show_effective_request(self, b) -> None: if b != self._show_effective_request: self._show_effective_request = b self._update_request_column(0, self.context_model) @@ -351,7 +351,7 @@ def get_request(self): """ return self._get_request(0) - def enter_diff_mode(self, context_model=None): + def enter_diff_mode(self, context_model=None) -> None: """Enter diff mode. Args: @@ -372,7 +372,7 @@ def enter_diff_mode(self, context_model=None): self.setColumnCount(5) self.refresh() - def leave_diff_mode(self): + def leave_diff_mode(self) -> None: """Leave diff mode.""" assert self.diff_mode self.diff_mode = False @@ -381,12 +381,12 @@ def leave_diff_mode(self): self.setColumnCount(2) self.refresh() - def revert_to_diff(self): + def revert_to_diff(self) -> None: assert self.diff_mode source_context = self.diff_context_model.context() self.context_model.set_context(source_context) - def revert_to_disk(self): + def revert_to_disk(self) -> None: filepath = self.context_model.filepath() assert filepath disk_context = app.load_context(filepath) @@ -415,20 +415,20 @@ def _title(context_model): # Stops focus loss when a widget inside the table is selected. In an MDI app # this can cause the current subwindow to lose focus. - def clear(self): + def clear(self) -> None: self.setFocus() super(ContextTableWidget, self).clear() - def select_variant(self, name): + def select_variant(self, name) -> None: for row, widget in self._iter_column_widgets(1, VariantCellWidget): if widget.variant.name == str(name): self.setCurrentIndex(self.model().index(row, 1)) return - def refresh(self): + def refresh(self) -> None: self._contextChanged(ContextModel.CONTEXT_CHANGED) - def _contextChanged(self, flags=0): + def _contextChanged(self, flags: int=0) -> None: update_request_columns = {} # apply request and variant widgets to columns @@ -486,7 +486,7 @@ def _contextChanged(self, flags=0): self.update() - def _update_request_column(self, column, context_model): + def _update_request_column(self, column, context_model) -> None: # remove effective request cells for row, widget in self._iter_column_widgets(column, EffectivePackageCellWidget): self.removeCellWidget(row, column) @@ -519,7 +519,7 @@ def _widget_is_selectable(self, widget): and not widget.read_only) def _currentCellChanged(self, currentRow, currentColumn, - previousRow, previousColumn): + previousRow, previousColumn) -> None: widget = self.cellWidget(currentRow, currentColumn) if self._widget_is_selectable(widget): self._current_variant = widget.variant @@ -543,7 +543,7 @@ def _currentCellChanged(self, currentRow, currentColumn, # this is only here to clear the current index, which leaves an annoying # visual cue even though the cell is not selected - def _itemSelectionChanged(self): + def _itemSelectionChanged(self) -> None: if not self.selectedIndexes(): self.setCurrentIndex(QtCore.QModelIndex()) @@ -562,7 +562,7 @@ def _get_request(self, column): request_strs.append(txt) return request_strs - def _apply_request(self, context_model, column): + def _apply_request(self, context_model, column) -> None: context = context_model.context() requests = context.requested_packages() num_requests = len(requests) @@ -571,8 +571,8 @@ def _apply_request(self, context_model, column): self._set_package_cell(num_requests, column) def _apply_resolve(self, context_model, column, reference_column, - hide_locks=False, read_only=False, - reference_column_is_variants=False): + hide_locks: bool = False, read_only: bool = False, + reference_column_is_variants: bool = False) -> None: context = context_model.context() resolved = context.resolved_packages[:] consumed_rows = set() @@ -611,7 +611,7 @@ def _apply_resolve(self, context_model, column, reference_column, hide_locks=hide_locks, read_only=read_only) row += 1 - def _update_comparison_column(self, column): + def _update_comparison_column(self, column) -> None: #no_color = self.palette().color(QtGui.QPalette.Active, QtGui.QPalette.Base) for row in range(self.rowCount()): @@ -645,15 +645,15 @@ def _set_package_cell(self, row, column, request=None): row, column)) return edit - def _set_effective_package_cell(self, row, column, request, lock_type): + def _set_effective_package_cell(self, row, column, request, lock_type) -> None: if row >= self.rowCount(): self.setRowCount(row + 1) cell = EffectivePackageCellWidget(request, lock_type) self.setCellWidget(row, column, cell) def _set_variant_cell(self, row, column, context_model, variant, - reference_variant=None, hide_locks=False, - read_only=False): + reference_variant=None, hide_locks: bool = False, + read_only: bool = False) -> None: if row >= self.rowCount(): self.setRowCount(row + 1) @@ -663,7 +663,7 @@ def _set_variant_cell(self, row, column, context_model, variant, self.setCellWidget(row, column, widget) widget._set_stale(column != 1) - def _set_cell_text(self, row, column, txt): + def _set_cell_text(self, row, column, txt) -> None: if row >= self.rowCount(): self.setRowCount(row + 1) @@ -672,12 +672,12 @@ def _set_cell_text(self, row, column, txt): item = QtWidgets.QTableWidgetItem(txt) self.setItem(row, column, item) - def _packageTextChanged(self, row, column, txt): + def _packageTextChanged(self, row, column, txt) -> None: if txt: if self._set_package_cell(row + 1, column): self._update_request_column(column, self.context_model) - def _packageFocusOutViaKeyPress(self, row, column, txt): + def _packageFocusOutViaKeyPress(self, row, column, txt) -> None: if txt: self._set_current_cell(row + 1, column) else: @@ -689,7 +689,7 @@ def _packageFocusOutViaKeyPress(self, row, column, txt): self.context_model.set_request(new_request) self._update_request_column(column, self.context_model) - def _packageFocusOut(self, row, column, txt): + def _packageFocusOut(self, row, column, txt) -> None: if txt: self._set_package_cell(row + 1, column) else: @@ -701,7 +701,7 @@ def _packageFocusOut(self, row, column, txt): self.context_model.set_request(new_request) self._update_request_column(column, self.context_model) - def _delete_cell(self, row, column): + def _delete_cell(self, row, column) -> None: for i in range(row, self.rowCount()): edit = self.cellWidget(i, column) if edit and isinstance(edit, PackageSelectWidget): @@ -711,7 +711,7 @@ def _delete_cell(self, row, column): else: self.removeCellWidget(i, column) - def _trim_trailing_rows(self): + def _trim_trailing_rows(self) -> None: n = 0 for i in reversed(range(self.default_row_count, self.rowCount())): row_clear = not any(self.cellWidget(i, x) @@ -725,7 +725,7 @@ def _trim_trailing_rows(self): self.setRowCount(self.rowCount() - n) self._set_current_cell(row, column) - def _set_current_cell(self, row, column): + def _set_current_cell(self, row, column) -> None: self.setCurrentCell(row, column) edit = self.cellWidget(row, column) if edit: diff --git a/src/rezgui/widgets/ContextToolsWidget.py b/src/rezgui/widgets/ContextToolsWidget.py index 3075c47ff..8d573dffe 100644 --- a/src/rezgui/widgets/ContextToolsWidget.py +++ b/src/rezgui/widgets/ContextToolsWidget.py @@ -14,12 +14,12 @@ class _TreeNode(QtWidgets.QLabel): clicked = QtCore.Signal() - def __init__(self, item, txt, parent=None): + def __init__(self, item, txt, parent=None) -> None: super(_TreeNode, self).__init__(txt, parent) self.item = item self.setCursor(QtCore.Qt.PointingHandCursor) - def mouseReleaseEvent(self, event): + def mouseReleaseEvent(self, event) -> None: super(_TreeNode, self).mouseReleaseEvent(event) self.clicked.emit() if event.button() == QtCore.Qt.LeftButton: @@ -30,7 +30,7 @@ class ContextToolsWidget(QtWidgets.QTreeWidget, ContextViewMixin): toolsChanged = QtCore.Signal() - def __init__(self, context_model=None, parent=None): + def __init__(self, context_model=None, parent=None) -> None: super(ContextToolsWidget, self).__init__(parent) ContextViewMixin.__init__(self, context_model) @@ -53,7 +53,7 @@ def __init__(self, context_model=None, parent=None): def num_tools(self): return len(self.tool_widgets) - def refresh(self): + def refresh(self) -> None: self.clear() self.tool_widgets = {} context = self.context() @@ -81,16 +81,16 @@ def refresh(self): self.resizeColumnToContents(0) self.toolsChanged.emit() - def _contextChanged(self, flags=0): + def _contextChanged(self, flags: int=0) -> None: if not flags & (ContextModel.CONTEXT_CHANGED): return self.refresh() - def _clear_selection(self): + def _clear_selection(self) -> None: self.setCurrentIndex(QtCore.QModelIndex()) self.clearSelection() - def _instanceCountChanged(self, context_id, tool_name, num_procs): + def _instanceCountChanged(self, context_id, tool_name, num_procs) -> None: if self.context() is None or context_id != id(self.context()): return diff --git a/src/rezgui/widgets/EffectivePackageCellWidget.py b/src/rezgui/widgets/EffectivePackageCellWidget.py index ecd4b9aa2..393fc2fd2 100644 --- a/src/rezgui/widgets/EffectivePackageCellWidget.py +++ b/src/rezgui/widgets/EffectivePackageCellWidget.py @@ -8,7 +8,7 @@ class EffectivePackageCellWidget(QtWidgets.QWidget): - def __init__(self, request, type_, parent=None): + def __init__(self, request, type_, parent=None) -> None: super(EffectivePackageCellWidget, self).__init__(parent) if type_ == "implicit": diff --git a/src/rezgui/widgets/FindPopup.py b/src/rezgui/widgets/FindPopup.py index aec6c0b0d..18a91450b 100644 --- a/src/rezgui/widgets/FindPopup.py +++ b/src/rezgui/widgets/FindPopup.py @@ -11,7 +11,7 @@ class FindPopup(QtWidgets.QFrame): find = QtCore.Signal(str) def __init__(self, pivot_widget, pivot_position=None, words=None, - initial_word=None, close_on_find=True, parent=None): + initial_word=None, close_on_find: bool = True, parent=None) -> None: super(FindPopup, self).__init__(parent) self.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Raised) self.setWindowFlags(QtCore.Qt.Popup) @@ -45,11 +45,11 @@ def __init__(self, pivot_widget, pivot_position=None, words=None, find_shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+F"), self) find_shortcut.activated.connect(self._find_again) - def _find(self): + def _find(self) -> None: word = self.edit.text() self.find.emit(word) if self.close_on_find: self.close() - def _find_again(self): + def _find_again(self) -> None: self.edit.selectAll() diff --git a/src/rezgui/widgets/IconButton.py b/src/rezgui/widgets/IconButton.py index 60c1fde6b..6a0093730 100644 --- a/src/rezgui/widgets/IconButton.py +++ b/src/rezgui/widgets/IconButton.py @@ -10,7 +10,7 @@ class IconButton(QtWidgets.QLabel): clicked = QtCore.Signal(int) - def __init__(self, icon_name, tooltip=None, parent=None): + def __init__(self, icon_name, tooltip=None, parent=None) -> None: super(IconButton, self).__init__(parent) icon = get_icon(icon_name) self.setPixmap(icon) @@ -18,5 +18,5 @@ def __init__(self, icon_name, tooltip=None, parent=None): if tooltip: self.setToolTip(tooltip) - def mousePressEvent(self, event): + def mousePressEvent(self, event) -> None: self.clicked.emit(event.button()) diff --git a/src/rezgui/widgets/ImageViewerWidget.py b/src/rezgui/widgets/ImageViewerWidget.py index 7fa4ed12c..5f06245f4 100644 --- a/src/rezgui/widgets/ImageViewerWidget.py +++ b/src/rezgui/widgets/ImageViewerWidget.py @@ -7,13 +7,13 @@ class GraphicsView(QtWidgets.QGraphicsView): - def __init__(self, parent=None, max_scale=None): + def __init__(self, parent=None, max_scale=None) -> None: super(GraphicsView, self).__init__(parent) self.interactive = True self.press_pos = None self.max_scale = max_scale - def mousePressEvent(self, event): + def mousePressEvent(self, event) -> None: if self.interactive: self.setCursor(QtCore.Qt.ClosedHandCursor) self.press_pos = QtGui.QCursor.pos() @@ -21,13 +21,13 @@ def mousePressEvent(self, event): else: event.ignore() - def mouseReleaseEvent(self, event): + def mouseReleaseEvent(self, event) -> None: if self.interactive: self.unsetCursor() else: event.ignore() - def mouseMoveEvent(self, event): + def mouseMoveEvent(self, event) -> None: if self.interactive: pos = QtGui.QCursor.pos() pos_delta = pos - self.press_pos @@ -36,7 +36,7 @@ def mouseMoveEvent(self, event): else: event.ignore() - def wheelEvent(self, event): + def wheelEvent(self, event) -> None: if self.interactive: scale = 1.0 + (event.delta() * 0.001) if scale < 1.0: @@ -60,7 +60,7 @@ def _scroll_pos(self): vs = self.verticalScrollBar() return QtCore.QPoint(hs.value(), vs.value()) - def _set_scroll_pos(self, pos): + def _set_scroll_pos(self, pos) -> None: hs = self.horizontalScrollBar() vs = self.verticalScrollBar() hs.setValue(pos.x()) @@ -68,7 +68,7 @@ def _set_scroll_pos(self, pos): class ImageViewerWidget(QtWidgets.QWidget): - def __init__(self, image_file, parent=None): + def __init__(self, image_file, parent=None) -> None: super(ImageViewerWidget, self).__init__(parent) self.fit = False self.prev_scale = 1.0 @@ -87,12 +87,12 @@ def __init__(self, image_file, parent=None): self.view.show() self._fit_in_view() - def resizeEvent(self, event): + def resizeEvent(self, event) -> None: if self.fit: self._fit_in_view() event.accept() - def fit_to_window(self, enabled): + def fit_to_window(self, enabled) -> None: if enabled != self.fit: self.fit = enabled self.view.interactive = not enabled @@ -105,5 +105,5 @@ def fit_to_window(self, enabled): factor = self.prev_scale / current_scale self.view.scale(factor, factor) - def _fit_in_view(self): + def _fit_in_view(self) -> None: self.view.fitInView(self.image_item, QtCore.Qt.KeepAspectRatio) diff --git a/src/rezgui/widgets/PackageLineEdit.py b/src/rezgui/widgets/PackageLineEdit.py index 82cf34841..eeb6a2b1f 100644 --- a/src/rezgui/widgets/PackageLineEdit.py +++ b/src/rezgui/widgets/PackageLineEdit.py @@ -15,8 +15,8 @@ class PackageLineEdit(QtWidgets.QLineEdit, ContextViewMixin): focusOut = QtCore.Signal(str) focusIn = QtCore.Signal() - def __init__(self, context_model=None, parent=None, family_only=False, - read_only=False): + def __init__(self, context_model=None, parent=None, family_only: bool = False, + read_only: bool = False) -> None: super(PackageLineEdit, self).__init__(parent) ContextViewMixin.__init__(self, context_model) self.read_only = read_only @@ -43,7 +43,7 @@ def __init__(self, context_model=None, parent=None, family_only=False, self.textEdited.connect(self._textEdited) self.textChanged.connect(self._textChanged) - def mouseReleaseEvent(self, event): + def mouseReleaseEvent(self, event) -> None: if not self.hasSelectedText(): self.completer.complete() @@ -69,7 +69,7 @@ def focusOutEvent(self, event): self.focusOut.emit(self.text()) return super(PackageLineEdit, self).focusOutEvent(event) - def clone_into(self, other): + def clone_into(self, other) -> None: other.family_only = self.family_only other.default_style = self.default_style other.setText(self.text()) @@ -78,10 +78,10 @@ def clone_into(self, other): other.completions.setStringList(completions) other.completer.setCompletionPrefix(self.text()) - def _textChanged(self, txt): + def _textChanged(self, txt) -> None: self._update_font() - def _update_font(self): + def _update_font(self) -> None: if self.read_only: return elif self.text(): @@ -97,7 +97,7 @@ def _update_font(self): pal.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Text, color) self.setPalette(pal) - def _contextChanged(self, flags=0): + def _contextChanged(self, flags: int=0) -> None: if flags & ContextModel.PACKAGES_PATH_CHANGED: self._update_status() @@ -105,13 +105,13 @@ def _contextChanged(self, flags=0): def _paths(self): return self.context_model.packages_path - def _textEdited(self, txt): + def _textEdited(self, txt) -> None: words = get_completions(str(txt), paths=self._paths, family_only=self.family_only) self.completions.setStringList(list(reversed(list(words)))) - def _set_style(self, style=None): + def _set_style(self, style=None) -> None: if style is None: if self.default_style is not None: self.setStyleSheet(self.default_style) @@ -120,12 +120,12 @@ def _set_style(self, style=None): self.default_style = self.styleSheet() self.setStyleSheet(style) - def _update_status(self): - def _ok(): + def _update_status(self) -> None: + def _ok() -> None: self._set_style() self.setToolTip("") - def _err(msg, color="red"): + def _err(msg, color="red") -> None: self._set_style("QLineEdit { border : 2px solid %s;}" % color) self.setToolTip(msg) diff --git a/src/rezgui/widgets/PackageLoadingWidget.py b/src/rezgui/widgets/PackageLoadingWidget.py index addbfd88a..c001a66e2 100644 --- a/src/rezgui/widgets/PackageLoadingWidget.py +++ b/src/rezgui/widgets/PackageLoadingWidget.py @@ -9,7 +9,7 @@ class PackageLoadingWidget(QtWidgets.QWidget): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(PackageLoadingWidget, self).__init__(parent) self.main_widget = None self.worker = None @@ -30,13 +30,13 @@ def set_packages(self, packages): are loaded, and the main widget will be bought into view afterwards.""" raise NotImplementedError - def set_loader_swap_delay(self, msecs): + def set_loader_swap_delay(self, msecs) -> None: """Set the delay before widget swaps to show the loading bar. A delay is useful because it avoids the annoying flicker that results from a fast packages load.""" self.swap_delay = msecs - def set_main_widget(self, widget): + def set_main_widget(self, widget) -> None: layout = self.layout() if self.main_widget is not None: layout.removeWidget(self.main_widget) @@ -46,13 +46,13 @@ def set_main_widget(self, widget): self.main_widget = widget self.loading_widget.hide() - def stop_loading_packages(self): + def stop_loading_packages(self) -> None: if self.worker: self.worker.stop() self.worker = None def load_packages(self, package_paths, package_name, range_=None, - package_attributes=None, callback=None): + package_attributes=None, callback=None) -> None: self.stop_loading_packages() self.bar.setRange(0, 0) @@ -89,14 +89,14 @@ def load_packages(self, package_paths, package_name, range_=None, thread.start() - def __del__(self): + def __del__(self) -> None: for _, worker in self.threads: worker.stop() for th, _ in self.threads: th.quit() th.wait() - def _swap_to_loader(self, id_): + def _swap_to_loader(self, id_) -> None: if self.worker is None or id(self.worker) != id_: return @@ -104,14 +104,14 @@ def _swap_to_loader(self, id_): if self.main_widget is not None: self.main_widget.hide() - def _progress(self, id_, value, total): + def _progress(self, id_, value, total) -> None: if self.worker is None or id(self.worker) != id_: return self.bar.setMaximum(total) self.bar.setValue(value) - def _packagesLoaded(self, id_, packages): + def _packagesLoaded(self, id_, packages) -> None: if self.worker is None or id(self.worker) != id_: return diff --git a/src/rezgui/widgets/PackageSelectWidget.py b/src/rezgui/widgets/PackageSelectWidget.py index ba492db1e..68216bdb1 100644 --- a/src/rezgui/widgets/PackageSelectWidget.py +++ b/src/rezgui/widgets/PackageSelectWidget.py @@ -16,7 +16,7 @@ class PackageSelectWidget(QtWidgets.QWidget, ContextViewMixin): focusOut = QtCore.Signal(str) textChanged = QtCore.Signal(str) - def __init__(self, context_model=None, read_only=False, parent=None): + def __init__(self, context_model=None, read_only: bool = False, parent=None) -> None: super(PackageSelectWidget, self).__init__(parent) ContextViewMixin.__init__(self, context_model) @@ -42,31 +42,31 @@ def __init__(self, context_model=None, read_only=False, parent=None): def text(self): return self.edit.text() - def setText(self, txt): + def setText(self, txt) -> None: self.edit.setText(txt) - def clone_into(self, other): + def clone_into(self, other) -> None: self.edit.clone_into(other.edit) - def setFocus(self): + def setFocus(self) -> None: self.edit.setFocus() self.btn.show() - def _focusIn(self): + def _focusIn(self) -> None: self.btn.show() - def _focusOut(self, txt): + def _focusOut(self, txt) -> None: self.btn.hide() self.focusOut.emit(txt) - def _focusOutViaKeyPress(self, txt): + def _focusOutViaKeyPress(self, txt) -> None: self.btn.hide() self.focusOutViaKeyPress.emit(txt) - def _textChanged(self, txt): + def _textChanged(self, txt) -> None: self.textChanged.emit(txt) - def _browse_package(self, button): + def _browse_package(self, button) -> None: self.btn.show() dlg = BrowsePackageDialog(context_model=self.context_model, package_text=self.text(), diff --git a/src/rezgui/widgets/PackageTabWidget.py b/src/rezgui/widgets/PackageTabWidget.py index ff3e0e60f..02cad05c3 100644 --- a/src/rezgui/widgets/PackageTabWidget.py +++ b/src/rezgui/widgets/PackageTabWidget.py @@ -17,7 +17,7 @@ class PackageTabWidget(QtWidgets.QTabWidget, ContextViewMixin): - def __init__(self, context_model=None, versions_tab=False, parent=None): + def __init__(self, context_model=None, versions_tab: bool = False, parent=None) -> None: super(PackageTabWidget, self).__init__(parent) ContextViewMixin.__init__(self, context_model) self.variant = None @@ -74,13 +74,13 @@ def __init__(self, context_model=None, versions_tab=False, parent=None): self.currentChanged.connect(self._tabChanged) self.setEnabled(False) - def set_package(self, package): + def set_package(self, package) -> None: self._set_packagebase(package) - def set_variant(self, variant): + def set_variant(self, variant) -> None: self._set_packagebase(variant) - def _set_packagebase(self, variant): + def _set_packagebase(self, variant) -> None: self.setEnabled(variant is not None) self.variant = variant is_package = isinstance(variant, Package) @@ -127,7 +127,7 @@ def _set_packagebase(self, variant): self.setCurrentIndex(0) # some widgets lazily load the variant when tab is selected - def _tabChanged(self, index): + def _tabChanged(self, index) -> None: widget = self.widget(index) if widget.variant != self.variant: widget.set_variant(self.variant) diff --git a/src/rezgui/widgets/PackageVersionsTable.py b/src/rezgui/widgets/PackageVersionsTable.py index 3e10a38ba..b1ac50b0e 100644 --- a/src/rezgui/widgets/PackageVersionsTable.py +++ b/src/rezgui/widgets/PackageVersionsTable.py @@ -11,7 +11,7 @@ class PackageVersionsTable(QtWidgets.QTableWidget, ContextViewMixin): - def __init__(self, context_model=None, parent=None, callback=None): + def __init__(self, context_model=None, parent=None, callback=None) -> None: """ Args: callback (callable): If supplied, this will be called and passed @@ -37,12 +37,12 @@ def __init__(self, context_model=None, parent=None, callback=None): vh, QtWidgets.QHeaderView.ResizeToContents) self.clear() - def clear(self): + def clear(self) -> None: super(PackageVersionsTable, self).clear() self.verticalHeader().setVisible(False) self.horizontalHeader().setVisible(False) - def refresh(self): + def refresh(self) -> None: self.set_package_name(self.package_name) def current_package(self): @@ -66,7 +66,7 @@ def select_version(self, version_range): self.selectRow(row) return version - def set_package_name(self, package_name): + def set_package_name(self, package_name) -> None: package_paths = self.context_model.packages_path self.packages = {} self.clear() @@ -131,6 +131,6 @@ def set_package_name(self, package_name): if first_selectable_row != -1: self.selectRow(first_selectable_row) - def _contextChanged(self, flags=0): + def _contextChanged(self, flags: int=0) -> None: if flags & ContextModel.PACKAGES_PATH_CHANGED: self.refresh() diff --git a/src/rezgui/widgets/SearchableTextEdit.py b/src/rezgui/widgets/SearchableTextEdit.py index ae5a713f6..5433ee9a3 100644 --- a/src/rezgui/widgets/SearchableTextEdit.py +++ b/src/rezgui/widgets/SearchableTextEdit.py @@ -9,15 +9,15 @@ class SearchableTextEdit(QtWidgets.QTextEdit): """A TextEdit that can be searched. """ - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(SearchableTextEdit, self).__init__(parent) self.searchable = True self.popup = None - def set_searchable(self, enable): + def set_searchable(self, enable) -> None: self.searchable = enable - def search(self): + def search(self) -> None: if not self.searchable: return @@ -32,7 +32,7 @@ def search(self): self.popup.find.connect(self._find_text) self.popup.show() - def _find_text(self, word): + def _find_text(self, word) -> None: if not self.find(word): # search from top self.moveCursor(QtGui.QTextCursor.Start) diff --git a/src/rezgui/widgets/StreamableTextEdit.py b/src/rezgui/widgets/StreamableTextEdit.py index 9e54e4a86..423a5bab6 100644 --- a/src/rezgui/widgets/StreamableTextEdit.py +++ b/src/rezgui/widgets/StreamableTextEdit.py @@ -14,7 +14,7 @@ class StreamableTextEdit(SearchableTextEdit): """ written = QtCore.Signal() - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(StreamableTextEdit, self).__init__(parent) self.setReadOnly(True) self.buffer = [] @@ -24,10 +24,10 @@ def __init__(self, parent=None): # -- file-like methods - def isatty(self): + def isatty(self) -> bool: return False - def write(self, txt): + def write(self, txt) -> None: emit = False try: self.lock.acquire() @@ -38,7 +38,7 @@ def write(self, txt): if emit: self.written.emit() - def _consume(self): + def _consume(self) -> None: try: self.lock.acquire() buffer_ = self.buffer @@ -50,7 +50,7 @@ def _consume(self): txt = ''.join(buffer_) self._write(txt) - def _write(self, txt): + def _write(self, txt) -> None: self.moveCursor(QtGui.QTextCursor.End) self.insertPlainText(txt) self.moveCursor(QtGui.QTextCursor.End) diff --git a/src/rezgui/widgets/TimeSelecterPopup.py b/src/rezgui/widgets/TimeSelecterPopup.py index f6f3b2ff0..2923f5ec9 100644 --- a/src/rezgui/widgets/TimeSelecterPopup.py +++ b/src/rezgui/widgets/TimeSelecterPopup.py @@ -13,14 +13,14 @@ class Canvas(QtWidgets.QWidget): secondsHover = QtCore.Signal(int) secondsClicked = QtCore.Signal(int) - def __init__(self, width, height, parent=None): + def __init__(self, width, height, parent=None) -> None: super(Canvas, self).__init__(parent) self.setCursor(QtCore.Qt.CrossCursor) self.setMouseTracking(True) self._width = width self._height = height - def paintEvent(self, event): + def paintEvent(self, event) -> None: rect = self.rect() w = rect.width() h = rect.height() @@ -45,14 +45,14 @@ def paintEvent(self, event): p.drawText(margin, j * 3 - margin, "minutes") p.drawText(margin, j * 4 - margin, "seconds") - def leaveEvent(self, event): + def leaveEvent(self, event) -> None: self.secondsHover.emit(-1) - def mousePressEvent(self, event): + def mousePressEvent(self, event) -> None: secs = self._get_seconds(event.pos()) self.secondsClicked.emit(secs) - def mouseMoveEvent(self, event): + def mouseMoveEvent(self, event) -> None: secs = self._get_seconds(event.pos()) self.secondsHover.emit(secs) @@ -92,7 +92,7 @@ class TimeSelecterPopup(QtWidgets.QFrame): secondsClicked = QtCore.Signal(int) - def __init__(self, pivot_widget, width=240, height=160, parent=None): + def __init__(self, pivot_widget, width: int=240, height: int=160, parent=None) -> None: super(TimeSelecterPopup, self).__init__(parent) self.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Raised) self.setWindowFlags(QtCore.Qt.Popup) @@ -120,13 +120,13 @@ def __init__(self, pivot_widget, width=240, height=160, parent=None): canvas.secondsHover.connect(self._secondsHover) canvas.secondsClicked.connect(self._secondsClicked) - def _secondsHover(self, seconds): + def _secondsHover(self, seconds) -> None: if seconds == -1: self.label.setText("") else: secs_txt = readable_time_duration(seconds) self.label.setText("%s ago" % secs_txt) - def _secondsClicked(self, seconds): + def _secondsClicked(self, seconds) -> None: self.secondsClicked.emit(seconds) self.close() diff --git a/src/rezgui/widgets/TimestampWidget.py b/src/rezgui/widgets/TimestampWidget.py index 1f976bc4a..698afe903 100644 --- a/src/rezgui/widgets/TimestampWidget.py +++ b/src/rezgui/widgets/TimestampWidget.py @@ -14,7 +14,7 @@ class TimestampWidget(QtWidgets.QFrame): timeChanged = QtCore.Signal(int) # epoch time - def __init__(self, context_model, parent=None): + def __init__(self, context_model, parent=None) -> None: super(TimestampWidget, self).__init__(parent) self.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Sunken) self.context_model = context_model @@ -46,23 +46,23 @@ def datetime(self): else: return None - def set_time(self, epoch): + def set_time(self, epoch) -> None: dt = QtCore.QDateTime() dt.setTime_t(epoch) self.edit.setDateTime(dt) self.checkbox.setChecked(True) self.timeChanged.emit(epoch) - def refresh(self): + def refresh(self) -> None: b = self.checkbox.isChecked() self.package_btn.setEnabled(b) self.clock_btn.setEnabled(b) self.edit.setEnabled(b) - def _stateChanged(self, state): + def _stateChanged(self, state) -> None: self.refresh() - def _selectPackage(self): + def _selectPackage(self) -> None: fn = lambda x: bool(x.timestamp) dlg = BrowsePackageDialog(context_model=self.context_model, parent=self.parentWidget(), @@ -71,11 +71,11 @@ def _selectPackage(self): if dlg.package: self.set_time(dlg.package.timestamp) - def _selectTime(self): + def _selectTime(self) -> None: self.popup = TimeSelecterPopup(self.clock_btn, parent=self) self.popup.secondsClicked.connect(self._secondsClicked) self.popup.show() - def _secondsClicked(self, seconds): + def _secondsClicked(self, seconds) -> None: now = int(time.time()) self.set_time(now - seconds) diff --git a/src/rezgui/widgets/ToolWidget.py b/src/rezgui/widgets/ToolWidget.py index 3809555e1..b6b8ca00f 100644 --- a/src/rezgui/widgets/ToolWidget.py +++ b/src/rezgui/widgets/ToolWidget.py @@ -16,7 +16,7 @@ class ToolWidget(QtWidgets.QWidget): clicked = QtCore.Signal() - def __init__(self, context, tool_name, process_tracker=None, parent=None): + def __init__(self, context, tool_name, process_tracker=None, parent=None) -> None: super(ToolWidget, self).__init__(parent) self.context = context self.tool_name = tool_name @@ -48,7 +48,7 @@ def get_processes(self): return self.process_tracker.running_instances(self.context, self.tool_name) - def mouseReleaseEvent(self, event): + def mouseReleaseEvent(self, event) -> None: super(ToolWidget, self).mouseReleaseEvent(event) if not self.context: return @@ -71,7 +71,7 @@ def mouseReleaseEvent(self, event): menu.exec_(self.mapToGlobal(event.pos())) self.clicked.emit() - def _launch_tool(self, terminal=False, moniter=False): + def _launch_tool(self, terminal: bool = False, moniter: bool = False) -> None: buf = subprocess.PIPE if moniter else None proc = app.execute_shell(context=self.context, command=self.tool_name, @@ -85,7 +85,7 @@ def _launch_tool(self, terminal=False, moniter=False): dlg = ProcessDialog(proc, self.tool_name) dlg.exec_() - def _list_processes(self): + def _list_processes(self) -> None: entries = self.get_processes() now = int(time.time()) items = [] @@ -106,7 +106,7 @@ def _list_processes(self): QtWidgets.QMessageBox.information(self, "Processes", txt) - def set_instance_count(self, nprocs): + def set_instance_count(self, nprocs) -> None: if nprocs: txt = "%d instances running..." % nprocs else: diff --git a/src/rezgui/widgets/VariantCellWidget.py b/src/rezgui/widgets/VariantCellWidget.py index 3447c1ea0..808b62340 100644 --- a/src/rezgui/widgets/VariantCellWidget.py +++ b/src/rezgui/widgets/VariantCellWidget.py @@ -17,7 +17,7 @@ # TODO deal with variant missing from disk class VariantCellWidget(QtWidgets.QWidget, ContextViewMixin): def __init__(self, context_model, variant, reference_variant=None, - hide_locks=False, read_only=False, parent=None): + hide_locks: bool = False, read_only: bool = False, parent=None) -> None: super(VariantCellWidget, self).__init__(parent) ContextViewMixin.__init__(self, context_model) @@ -45,7 +45,7 @@ def __init__(self, context_model, variant, reference_variant=None, def text(self): return self.variant.qualified_package_name - def contextMenuEvent(self, event): + def contextMenuEvent(self, event) -> None: if self.read_only or self.hide_locks: return @@ -80,10 +80,10 @@ def contextMenuEvent(self, event): menu.exec_(self.mapToGlobal(event.pos())) menu.setParent(None) - def refresh(self): + def refresh(self) -> None: self._contextChanged(ContextModel.CONTEXT_CHANGED) - def set_reference_sibling(self, variant=None): + def set_reference_sibling(self, variant=None) -> None: if variant is None or self.variant.name == variant.name: access = 0 else: @@ -100,7 +100,7 @@ def set_reference_sibling(self, variant=None): self.depends_icon.setToolTip(desc % (self.variant.name, variant.name)) self.depends_icon.setEnabled(enable) - def _contextChanged(self, flags=0): + def _contextChanged(self, flags: int=0) -> None: self._set_stale(self.context_model.is_stale()) if flags & (ContextModel.PACKAGES_PATH_CHANGED | @@ -276,18 +276,18 @@ def _get_lock_requirement(self, lock_type): else self.variant.version return get_lock_request(self.variant.name, version, lock_type, weak=False) - def _set_lock_type(self, lock_type): + def _set_lock_type(self, lock_type) -> None: self.context_model.set_patch_lock(self.variant.name, lock_type) - def _remove_lock(self): + def _remove_lock(self) -> None: self.context_model.remove_patch_lock(self.variant.name) - def _set_stale(self, b=True): + def _set_stale(self, b: bool = True) -> None: if b != self.stale: update_font(self.label, italic=b) self.stale = b - def _set_icons(self, icons): + def _set_icons(self, icons) -> None: current_icons = [tuple(x[1:]) for x in self.icons] if icons == current_icons: return @@ -304,7 +304,7 @@ def _set_icons(self, icons): layout.addWidget(widget) self.icons.append((widget, name, tooltip)) - def _set_lock_icon(self, name, tooltip): + def _set_lock_icon(self, name, tooltip) -> None: layout = self.layout() if self.lock_icon: widget_, name_, tooltip_ = self.lock_icon diff --git a/src/rezgui/widgets/VariantDetailsWidget.py b/src/rezgui/widgets/VariantDetailsWidget.py index 053881abd..d9d313a58 100644 --- a/src/rezgui/widgets/VariantDetailsWidget.py +++ b/src/rezgui/widgets/VariantDetailsWidget.py @@ -10,7 +10,7 @@ class VariantDetailsWidget(QtWidgets.QWidget, ContextViewMixin): - def __init__(self, context_model=None, parent=None): + def __init__(self, context_model=None, parent=None) -> None: super(VariantDetailsWidget, self).__init__(parent) ContextViewMixin.__init__(self, context_model) self.variant = None @@ -24,11 +24,11 @@ def __init__(self, context_model=None, parent=None): create_pane([self.edit, btn_pane], False, compact=True, parent_widget=self) self.clear() - def clear(self): + def clear(self) -> None: self.edit.clear() self.setEnabled(False) - def set_variant(self, variant): + def set_variant(self, variant) -> None: if variant == self.variant: return @@ -43,8 +43,8 @@ def set_variant(self, variant): self.variant = variant - def _update_graph_btn_visibility(self): + def _update_graph_btn_visibility(self) -> None: self.view_graph_btn.setVisible(bool(self.context())) - def _contextChanged(self, flags=0): + def _contextChanged(self, flags: int=0) -> None: self._update_graph_btn_visibility() diff --git a/src/rezgui/widgets/VariantHelpWidget.py b/src/rezgui/widgets/VariantHelpWidget.py index baa803cc9..a0bab1a3b 100644 --- a/src/rezgui/widgets/VariantHelpWidget.py +++ b/src/rezgui/widgets/VariantHelpWidget.py @@ -15,7 +15,7 @@ class HelpEntryWidget(QtWidgets.QWidget): clicked = QtCore.Signal() - def __init__(self, help_, index, parent=None): + def __init__(self, help_, index, parent=None) -> None: super(HelpEntryWidget, self).__init__(parent) self.help_ = help_ self.index = index @@ -28,7 +28,7 @@ def __init__(self, help_, index, parent=None): create_pane([icon, label_widget, None], True, compact=True, parent_widget=self) - def mouseReleaseEvent(self, event): + def mouseReleaseEvent(self, event) -> None: super(HelpEntryWidget, self).mouseReleaseEvent(event) self.clicked.emit() if event.button() == QtCore.Qt.LeftButton: @@ -36,7 +36,7 @@ def mouseReleaseEvent(self, event): class VariantHelpWidget(PackageLoadingWidget, ContextViewMixin): - def __init__(self, context_model=None, parent=None): + def __init__(self, context_model=None, parent=None) -> None: super(VariantHelpWidget, self).__init__(parent) ContextViewMixin.__init__(self, context_model) self.variant = None @@ -58,7 +58,7 @@ def __init__(self, context_model=None, parent=None): self.set_loader_swap_delay(300) self.clear() - def clear(self): + def clear(self) -> None: self.no_help_label.hide() self.tab.hide() self.table_1.setRowCount(0) @@ -68,7 +68,7 @@ def clear(self): self.tab.setTabEnabled(0, False) self.tab.setTabEnabled(1, False) - def set_variant(self, variant): + def set_variant(self, variant) -> None: self.clear() self.variant = variant if self.variant is None: @@ -82,7 +82,7 @@ def set_variant(self, variant): callback=self._load_packages_callback, package_attributes=("help",)) - def set_packages(self, packages): + def set_packages(self, packages) -> None: package_paths = self.context_model.packages_path self.help_1 = PackageHelp(self.variant.name, paths=package_paths) @@ -121,7 +121,7 @@ def _create_table(self): vh.setVisible(False) return table - def _apply_help(self, help_, tab_index): + def _apply_help(self, help_, tab_index) -> None: table = self.table_2 if tab_index else self.table_1 table.clear() sections = help_.sections @@ -132,10 +132,10 @@ def _apply_help(self, help_, tab_index): widget.clicked.connect(partial(self._helpClicked, tab_index)) table.setCellWidget(row, 0, widget) - def _helpClicked(self, tab_index): + def _helpClicked(self, tab_index) -> None: table = self.table_2 if tab_index else self.table_1 table.clearSelection() @classmethod - def _load_packages_callback(cls, package): + def _load_packages_callback(cls, package) -> bool: return (not package.help) diff --git a/src/rezgui/widgets/VariantSummaryWidget.py b/src/rezgui/widgets/VariantSummaryWidget.py index 6adce50d9..9593d174a 100644 --- a/src/rezgui/widgets/VariantSummaryWidget.py +++ b/src/rezgui/widgets/VariantSummaryWidget.py @@ -9,7 +9,7 @@ class VariantSummaryWidget(QtWidgets.QWidget): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(VariantSummaryWidget, self).__init__(parent) self.variant = None @@ -33,7 +33,7 @@ def __init__(self, parent=None): self.clear() - def clear(self): + def clear(self) -> None: self.label.setText("no package selected") self.table.clear() self.table.setRowCount(0) @@ -41,7 +41,7 @@ def clear(self): vh.setVisible(False) self.setEnabled(False) - def set_variant(self, variant): + def set_variant(self, variant) -> None: if variant == self.variant: return diff --git a/src/rezgui/widgets/VariantToolsList.py b/src/rezgui/widgets/VariantToolsList.py index f7553c94f..49c0d22f4 100644 --- a/src/rezgui/widgets/VariantToolsList.py +++ b/src/rezgui/widgets/VariantToolsList.py @@ -9,7 +9,7 @@ class VariantToolsList(QtWidgets.QTableWidget, ContextViewMixin): - def __init__(self, context_model=None, parent=None): + def __init__(self, context_model=None, parent=None) -> None: super(VariantToolsList, self).__init__(0, 1, parent) ContextViewMixin.__init__(self, context_model) @@ -29,12 +29,12 @@ def __init__(self, context_model=None, parent=None): #app.process_tracker.instanceCountChanged.connect(self._instanceCountChanged) - def clear(self): + def clear(self) -> None: self.tool_widgets = {} super(VariantToolsList, self).clear() self.setEnabled(False) - def set_variant(self, variant): + def set_variant(self, variant) -> None: if variant == self.variant: return @@ -58,11 +58,11 @@ def set_variant(self, variant): self.variant = variant - def _clear_selection(self): + def _clear_selection(self) -> None: self.clearSelection() self.setCurrentIndex(QtCore.QModelIndex()) - def _instanceCountChanged(self, context_id, tool_name, num_procs): + def _instanceCountChanged(self, context_id, tool_name, num_procs) -> None: if self.context() is None or context_id != id(self.context()): return diff --git a/src/rezgui/widgets/VariantVersionsTable.py b/src/rezgui/widgets/VariantVersionsTable.py index 2f323b7a5..b7e9291ef 100644 --- a/src/rezgui/widgets/VariantVersionsTable.py +++ b/src/rezgui/widgets/VariantVersionsTable.py @@ -11,7 +11,7 @@ class VariantVersionsTable(QtWidgets.QTableWidget, ContextViewMixin): - def __init__(self, context_model=None, reference_variant=None, parent=None): + def __init__(self, context_model=None, reference_variant=None, parent=None) -> None: super(VariantVersionsTable, self).__init__(0, 2, parent) ContextViewMixin.__init__(self, context_model) @@ -41,7 +41,7 @@ def selectionCommand(self, index, event=None): return QtCore.QItemSelectionModel.ClearAndSelect if self.allow_selection \ else QtCore.QItemSelectionModel.NoUpdate - def clear(self): + def clear(self) -> None: super(VariantVersionsTable, self).clear() self.version_index = -1 self.setRowCount(0) @@ -56,15 +56,15 @@ def get_reference_difference(self): return None return (self.reference_version_index - self.version_index) - def refresh(self): + def refresh(self) -> None: variant = self.variant self.variant = None self.set_variant(variant) - def set_variant(self, variant): + def set_variant(self, variant) -> None: self._set_variant(variant) - def _set_variant(self, variant, preloaded_packages=None): + def _set_variant(self, variant, preloaded_packages=None) -> None: self.clear() hh = self.horizontalHeader() diff --git a/src/rezgui/widgets/VariantVersionsWidget.py b/src/rezgui/widgets/VariantVersionsWidget.py index 2557f6f7b..6fe2d123a 100644 --- a/src/rezgui/widgets/VariantVersionsWidget.py +++ b/src/rezgui/widgets/VariantVersionsWidget.py @@ -17,7 +17,7 @@ class VariantVersionsWidget(PackageLoadingWidget, ContextViewMixin): closeWindow = QtCore.Signal() def __init__(self, context_model=None, reference_variant=None, - in_window=False, parent=None): + in_window: bool = False, parent=None) -> None: """ Args: reference_variant (`Variant`): Used to show the difference between @@ -64,18 +64,18 @@ def __init__(self, context_model=None, reference_variant=None, self.set_loader_swap_delay(300) self.clear() - def clear(self): + def clear(self) -> None: self.label.setText("no package selected") self.table.clear() self.pending_changelog_packages = None self.setEnabled(False) - def refresh(self): + def refresh(self) -> None: variant = self.variant self.variant = None self.set_variant(variant) - def set_variant(self, variant): + def set_variant(self, variant) -> None: self.tab.setCurrentIndex(0) self.stop_loading_packages() self.clear() @@ -102,13 +102,13 @@ def set_variant(self, variant): range_=range_, package_attributes=("timestamp",)) - def set_packages(self, packages): + def set_packages(self, packages) -> None: self.table._set_variant(self.variant, packages) self._update_label() self._update_changelogs(packages) self.setEnabled(True) - def _update_label(self): + def _update_label(self) -> None: diff_num = self.table.get_reference_difference() if diff_num is None: # normal mode @@ -132,38 +132,38 @@ def _update_label(self): self.label.setText(txt) - def _update_changelogs(self, packages): + def _update_changelogs(self, packages) -> None: # don't actually update until tab is selected - changelogs may get big, # we don't want to block up the gui thread unless necessary self.pending_changelog_packages = packages if self.tab.currentIndex() == 1: self._apply_changelogs() - def _tabIndexChanged(self, index): + def _tabIndexChanged(self, index) -> None: if index == 1: self._apply_changelogs() - def _apply_changelogs(self): + def _apply_changelogs(self) -> None: if self.pending_changelog_packages: self.changelog_edit.set_packages(self.pending_changelog_packages) self.pending_changelog_packages = None - def _changelogStateChanged(self, state): + def _changelogStateChanged(self, state) -> None: self._view_changelogs(state == QtCore.Qt.Checked) self.refresh() - def _view_or_hide_changelogs(self): + def _view_or_hide_changelogs(self) -> None: enable = (not self.table.view_changelog) self._view_changelogs(enable) self.refresh() - def _view_changelogs_window(self): + def _view_changelogs_window(self) -> None: from rezgui.dialogs.VariantVersionsDialog import VariantVersionsDialog dlg = VariantVersionsDialog(self.context_model, self.variant, parent=self) dlg.exec_() - def _browseVersions(self): + def _browseVersions(self) -> None: from rezgui.dialogs.BrowsePackageDialog import BrowsePackageDialog dlg = BrowsePackageDialog(context_model=self.context_model, package_text=self.variant.qualified_package_name, @@ -174,5 +174,5 @@ def _browseVersions(self): dlg.setWindowTitle("Versions - %s" % self.variant.name) dlg.exec_() - def _close_window(self): + def _close_window(self) -> None: self.closeWindow.emit() diff --git a/src/rezgui/widgets/VariantsList.py b/src/rezgui/widgets/VariantsList.py index 99e6958c6..8da0a6986 100644 --- a/src/rezgui/widgets/VariantsList.py +++ b/src/rezgui/widgets/VariantsList.py @@ -7,7 +7,7 @@ class VariantsList(QtWidgets.QTableWidget): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(VariantsList, self).__init__(0, 1, parent) self.variant = None @@ -28,7 +28,7 @@ def __init__(self, parent=None): vh, QtWidgets.QHeaderView.ResizeToContents) vh.setVisible(False) - def set_package(self, package): + def set_package(self, package) -> None: self.clear() if package is not None: self.setRowCount(package.num_variants) @@ -40,7 +40,7 @@ def set_package(self, package): self.package = package self.variant = None - def set_variant(self, variant): + def set_variant(self, variant) -> None: self.clear() if variant is not None: if isinstance(variant, Package): diff --git a/src/rezgui/widgets/ViewGraphButton.py b/src/rezgui/widgets/ViewGraphButton.py index 23e4450fd..b4d920943 100644 --- a/src/rezgui/widgets/ViewGraphButton.py +++ b/src/rezgui/widgets/ViewGraphButton.py @@ -10,7 +10,7 @@ class ViewGraphButton(QtWidgets.QToolButton, ContextViewMixin): - def __init__(self, context_model=None, parent=None): + def __init__(self, context_model=None, parent=None) -> None: super(ViewGraphButton, self).__init__(parent) ContextViewMixin.__init__(self, context_model) @@ -30,13 +30,13 @@ def __init__(self, context_model=None, parent=None): self.refresh() - def set_variant(self, variant=None): + def set_variant(self, variant=None) -> None: self.package_name = variant.name if variant else None - def refresh(self): + def refresh(self) -> None: self._contextChanged(ContextModel.CONTEXT_CHANGED) - def _contextChanged(self, flags=0): + def _contextChanged(self, flags: int=0) -> None: if not flags & ContextModel.CONTEXT_CHANGED: return @@ -51,11 +51,11 @@ def _contextChanged(self, flags=0): self.action_2.setEnabled(enable_dependency) self.setEnabled(enable_resolve or enable_dependency) - def _view_resolve_graph(self): + def _view_resolve_graph(self) -> None: graph_str = self.context().graph(as_dot=True) view_graph(graph_str, self.window(), prune_to=self.package_name) - def _view_dependency_graph(self): + def _view_dependency_graph(self) -> None: from rez.vendor.pygraph.readwrite.dot import write as write_dot graph = self.context().get_dependency_graph() graph_str = write_dot(graph) diff --git a/src/rezgui/windows/BrowsePackageSubWindow.py b/src/rezgui/windows/BrowsePackageSubWindow.py index 74a921fb1..507a443ca 100644 --- a/src/rezgui/windows/BrowsePackageSubWindow.py +++ b/src/rezgui/windows/BrowsePackageSubWindow.py @@ -9,7 +9,7 @@ class BrowsePackageSubWindow(QtWidgets.QMdiSubWindow, StoreSizeMixin): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(BrowsePackageSubWindow, self).__init__(parent) self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) config_key = "layout/window/package_browser" @@ -19,6 +19,6 @@ def __init__(self, parent=None): widget = BrowsePackagePane() self.setWidget(widget) - def closeEvent(self, event): + def closeEvent(self, event) -> None: super(BrowsePackageSubWindow, self).closeEvent(event) StoreSizeMixin.closeEvent(self, event) diff --git a/src/rezgui/windows/ContextSubWindow.py b/src/rezgui/windows/ContextSubWindow.py index 0aa7333d6..437fac05a 100644 --- a/src/rezgui/windows/ContextSubWindow.py +++ b/src/rezgui/windows/ContextSubWindow.py @@ -12,7 +12,7 @@ class ContextSubWindow(QtWidgets.QMdiSubWindow, ContextViewMixin, StoreSizeMixin): - def __init__(self, context=None, parent=None): + def __init__(self, context=None, parent=None) -> None: super(ContextSubWindow, self).__init__(parent) context_model = ContextModel(context) ContextViewMixin.__init__(self, context_model) @@ -28,14 +28,14 @@ def __init__(self, context=None, parent=None): def filepath(self): return self.context_model.filepath() - def closeEvent(self, event): + def closeEvent(self, event) -> None: if self.can_close(): super(ContextSubWindow, self).closeEvent(event) StoreSizeMixin.closeEvent(self, event) else: event.ignore() - def diff_with_file(self, filepath): + def diff_with_file(self, filepath) -> None: """Turn on diff mode and diff against given context. """ self.widget()._diff_with_file(filepath) @@ -89,30 +89,30 @@ def can_close(self): raise RuntimeError("Should never get here") # NOSONAR - def is_save_as_able(self): + def is_save_as_able(self) -> bool: return not self.context_model.is_stale() def is_saveable(self): return bool(self.is_save_as_able() and self.filepath()) - def save_context(self): + def save_context(self) -> None: if self.mdiArea().activeSubWindow() != self: return self._save_context() - def save_context_as(self): + def save_context_as(self) -> None: if self.mdiArea().activeSubWindow() != self: return self._save_context_as() - def copy_request_to_clipboard(self): + def copy_request_to_clipboard(self) -> None: txt = " ".join(self.context_model.request) clipboard = app.clipboard() clipboard.setText(txt) with app.status("Copied request to clipboard"): pass - def copy_resolve_to_clipboard(self): + def copy_resolve_to_clipboard(self) -> None: context = self.context() assert context strs = (x.qualified_package_name for x in context.resolved_packages) @@ -122,7 +122,7 @@ def copy_resolve_to_clipboard(self): with app.status("Copied resolve to clipboard"): pass - def _save_context(self): + def _save_context(self) -> bool: assert self.filepath() with app.status("Saving %s..." % self.filepath()): self.context_model.save(self.filepath()) @@ -140,12 +140,12 @@ def _save_context_as(self): return bool(filepath) - def _contextChanged(self, flags=0): + def _contextChanged(self, flags: int=0) -> None: self._update_window_title() - def _diffModeChanged(self): + def _diffModeChanged(self) -> None: self._update_window_title() - def _update_window_title(self): + def _update_window_title(self) -> None: title = self.widget().get_title() self.setWindowTitle(title) diff --git a/src/rezgui/windows/MainWindow.py b/src/rezgui/windows/MainWindow.py index 294b0bcd0..65ef106c2 100644 --- a/src/rezgui/windows/MainWindow.py +++ b/src/rezgui/windows/MainWindow.py @@ -14,7 +14,7 @@ class MainWindow(QtWidgets.QMainWindow): - def __init__(self, parent=None): + def __init__(self, parent=None) -> None: super(MainWindow, self).__init__(parent) self.setWindowTitle("Rez GUI") @@ -52,7 +52,7 @@ def __init__(self, parent=None): file_menu.aboutToShow.connect(self._update_file_menu) edit_menu.aboutToShow.connect(self._update_edit_menu) - def closeEvent(self, event): + def closeEvent(self, event) -> None: # attempt to close modified contexts first subwindows = [x for x in self.mdi.subWindowList() if x.isWindowModified()] subwindows += [x for x in self.mdi.subWindowList() if not x.isWindowModified()] @@ -65,19 +65,19 @@ def closeEvent(self, event): if self.mdi.subWindowList(): event.ignore() - def cascade(self): + def cascade(self) -> None: self.mdi.cascadeSubWindows() - def about(self): + def about(self) -> None: dlg = AboutDialog(self) dlg.exec_() - def _open_package_browser(self): + def _open_package_browser(self) -> None: subwindow = BrowsePackageSubWindow() self.mdi.addSubWindow(subwindow) subwindow.show() - def new_context(self): + def new_context(self) -> None: self._add_context_subwindow() def open_context(self, filepath): @@ -110,7 +110,7 @@ def status(self, txt): if milisecs < min_display_time: bar.showMessage(txt, min_display_time - milisecs) - def _open_context(self): + def _open_context(self) -> None: filepath, _ = QtWidgets.QFileDialog.getOpenFileName( self, "Open Context", filter="Context files (*.rxt)") if filepath: @@ -126,7 +126,7 @@ def _add_context_subwindow(self, context=None): subwindow.show() return subwindow - def _update_file_menu(self): + def _update_file_menu(self) -> None: context_save = False context_save_as = False @@ -149,7 +149,7 @@ def _update_file_menu(self): fn = partial(self.open_context, filepath) add_menu_action(menu, filepath, fn) - def _update_edit_menu(self): + def _update_edit_menu(self) -> None: copy_request = False copy_resolve = False diff --git a/src/rezplugins/build_process/local.py b/src/rezplugins/build_process/local.py index aa203bac5..2479ea899 100644 --- a/src/rezplugins/build_process/local.py +++ b/src/rezplugins/build_process/local.py @@ -5,6 +5,8 @@ """ Builds packages on local host """ +from __future__ import annotations + from rez.config import config from rez.package_repository import package_repository_manager from rez.build_process import BuildProcessHelper, BuildType @@ -21,11 +23,24 @@ from rez.package_test import PackageTestRunner, PackageTestResults from hashlib import sha1 +from typing import cast, TYPE_CHECKING import json import shutil import os import os.path +if TYPE_CHECKING: + from rez.packages import Variant + from rez.build_system import BuildResult + + # FIXME: move this out of TYPE_CHECKING block when python 3.7 support is dropped + class LocalBuildResult(BuildResult, total=False): + package_install_path: str + variant_install_path: str + +else: + LocalBuildResult = dict + class LocalBuildProcess(BuildProcessHelper): """The default build process. @@ -37,15 +52,19 @@ class LocalBuildProcess(BuildProcessHelper): tmpdir_manager = TempDirs(config.tmpdir, prefix="rez_testing_repo_") @classmethod - def name(cls): + def name(cls) -> str: return "local" - def __init__(self, *nargs, **kwargs): + def __init__(self, *nargs, **kwargs) -> None: super(LocalBuildProcess, self).__init__(*nargs, **kwargs) self.ran_test_names = set() self.all_test_results = PackageTestResults() - def build(self, install_path=None, clean=False, install=False, variants=None): + def build(self, + install_path: str | None = None, + clean: bool = False, + install: bool = False, + variants: list[int] | None = None) -> int: self._print_header("Building %s..." % self.package.qualified_name) # build variants @@ -71,7 +90,7 @@ def build(self, install_path=None, clean=False, install=False, variants=None): return num_visited - def release(self, release_message=None, variants=None): + def release(self, release_message=None, variants: list[int] | None = None): self._print_header("Releasing %s..." % self.package.qualified_name) # test that we're in a state to release @@ -130,8 +149,13 @@ def release(self, release_message=None, variants=None): return num_released - def _build_variant_base(self, variant, build_type, install_path=None, - clean=False, install=False, **kwargs): + def _build_variant_base(self, + variant: Variant, + build_type, + install_path: str | None = None, + clean: bool = False, + install: bool = False, + **kwargs) -> LocalBuildResult: # create build/install paths install_path = install_path or self.package.config.local_packages_path package_install_path = self.get_package_install_path(install_path) @@ -244,13 +268,13 @@ def _build_variant_base(self, variant, build_type, install_path=None, build_system_name = self.build_system.name() self._print("\nInvoking %s build system...", build_system_name) - build_result = self.build_system.build( + build_result = cast(LocalBuildResult, self.build_system.build( context=context, variant=variant, build_path=variant_build_path, install_path=variant_install_path, install=install, - build_type=build_type) + build_type=build_type)) if not build_result.get("success"): # delete the possibly partially installed variant payload @@ -283,7 +307,7 @@ def _build_variant_base(self, variant, build_type, install_path=None, return build_result - def _install_include_modules(self, install_path): + def _install_include_modules(self, install_path: str) -> None: # install 'include' sourcefiles, used by funcs decorated with @include if not self.package.includes: return @@ -310,14 +334,18 @@ def _install_include_modules(self, install_path): with open(sha1_filepath, "w") as f: # overwrite if exists f.write(uuid) - def _rmtree(self, path): + def _rmtree(self, path) -> None: try: forceful_rmtree(path) except Exception as e: print_warning("Failed to delete %s - %s", path, e) - def _build_variant(self, variant, install_path=None, clean=False, - install=False, **kwargs): + def _build_variant(self, + variant: Variant, + install_path: str | None = None, + clean: bool = False, + install: bool = False, + **kwargs) -> str | None: if variant.index is not None: self._print_header( "Building variant %s (%s)..." @@ -326,7 +354,7 @@ def _build_variant(self, variant, install_path=None, clean=False, # build and possibly install variant (ie the payload, not package.py) install_path = install_path or self.package.config.local_packages_path - def cancel_variant_install(): + def cancel_variant_install() -> None: if install: pkg_repo = package_repository_manager.get_repository(install_path) pkg_repo.on_variant_install_cancelled(variant.resource) @@ -366,7 +394,7 @@ def cancel_variant_install(): return build_result.get("build_env_script") - def _release_variant(self, variant, release_message=None, **kwargs): + def _release_variant(self, variant: Variant, release_message=None, **kwargs): release_path = self.package.config.release_packages_path # test if variant has already been released @@ -378,7 +406,7 @@ def _release_variant(self, variant, release_message=None, **kwargs): ) return None - def cancel_variant_install(): + def cancel_variant_install() -> None: pkg_repo = package_repository_manager.get_repository(release_path) pkg_repo.on_variant_install_cancelled(variant.resource) diff --git a/src/rezplugins/build_process/remote.py b/src/rezplugins/build_process/remote.py index a5cf7c070..c2bfd94ef 100644 --- a/src/rezplugins/build_process/remote.py +++ b/src/rezplugins/build_process/remote.py @@ -14,10 +14,10 @@ class RemoteBuildProcess(BuildProcessHelper): This process builds a package's variants sequentially, on remote hosts. """ @classmethod - def name(cls): + def name(cls) -> str: return "remote" - def build(self, install_path=None, clean=False, install=False, variants=None): + def build(self, install_path=None, clean: bool = False, install: bool = False, variants=None): raise NotImplementedError("coming soon...") def release(self, release_message=None, variants=None): diff --git a/src/rezplugins/build_system/cmake.py b/src/rezplugins/build_system/cmake.py index 6a76d92de..6fee115b2 100644 --- a/src/rezplugins/build_system/cmake.py +++ b/src/rezplugins/build_system/cmake.py @@ -5,17 +5,20 @@ """ CMake-based build system """ -from rez.build_system import BuildSystem +from __future__ import annotations + +from rez.build_system import BuildSystem, BuildResult from rez.build_process import BuildType from rez.resolved_context import ResolvedContext from rez.exceptions import BuildSystemError from rez.utils.execution import create_forwarding_script -from rez.packages import get_developer_package +from rez.packages import get_developer_package, Variant from rez.utils.platform_ import platform_ from rez.config import config from rez.utils.which import which from rez.vendor.schema.schema import Or from rez.shells import create_shell +import argparse import functools import os.path import sys @@ -62,11 +65,11 @@ class CMakeBuildSystem(BuildSystem): } @classmethod - def name(cls): + def name(cls) -> str: return "cmake" @classmethod - def child_build_system(cls): + def child_build_system(cls) -> str: return "make" @classmethod @@ -74,7 +77,7 @@ def is_valid_root(cls, path, package=None): return os.path.isfile(os.path.join(path, "CMakeLists.txt")) @classmethod - def bind_cli(cls, parser, group): + def bind_cli(cls, parser: argparse.ArgumentParser, group: argparse._ArgumentGroup) -> None: settings = config.plugins.build_system.cmake group.add_argument("--bt", "--build-target", dest="build_target", type=str, choices=cls.build_targets, @@ -86,8 +89,8 @@ def bind_cli(cls, parser, group): default=settings.build_system, help="set the cmake build system (default: %(default)s).") - def __init__(self, working_dir, opts=None, package=None, write_build_scripts=False, - verbose=False, build_args=[], child_build_args=[]): + def __init__(self, working_dir: str, opts=None, package=None, write_build_scripts: bool = False, + verbose: bool = False, build_args=[], child_build_args=[]) -> None: super(CMakeBuildSystem, self).__init__( working_dir, opts=opts, @@ -105,9 +108,14 @@ def __init__(self, working_dir, opts=None, package=None, write_build_scripts=Fal raise RezCMakeError("Generation of Xcode project only available " "on the OSX platform") - def build(self, context, variant, build_path, install_path, install=False, - build_type=BuildType.local): - def _pr(s): + def build(self, + context: ResolvedContext, + variant: Variant, + build_path: str, + install_path: str, + install: bool = False, + build_type=BuildType.local) -> BuildResult: + def _pr(s) -> None: if self.verbose: print(s) @@ -175,7 +183,7 @@ def _pr(s): post_actions_callback=post_actions_callback ) - ret = {} + ret = BuildResult() if retcode: ret["success"] = False return ret @@ -245,7 +253,7 @@ def _pr(s): @classmethod def _add_build_actions(cls, executor, context, package, variant, - build_type, install, build_path, install_path=None): + build_type, install, build_path, install_path=None) -> None: settings = package.config.plugins.build_system.cmake cmake_path = os.path.join(os.path.dirname(__file__), "cmake_files") template_path = os.path.join(os.path.dirname(__file__), "template_files") @@ -266,7 +274,7 @@ def _add_build_actions(cls, executor, context, package, variant, def _FWD__spawn_build_shell(working_dir, build_path, variant_index, install, - install_path=None): + install_path=None) -> None: # This spawns a shell that the user can run 'make' in directly context = ResolvedContext.load(os.path.join(build_path, "build.rxt")) package = get_developer_package(working_dir) diff --git a/src/rezplugins/build_system/custom.py b/src/rezplugins/build_system/custom.py index e1f29e765..682ddca62 100644 --- a/src/rezplugins/build_system/custom.py +++ b/src/rezplugins/build_system/custom.py @@ -5,16 +5,19 @@ """ Package-defined build command """ +from __future__ import annotations + from shlex import quote +from typing import TYPE_CHECKING import functools import os.path import sys import os -from rez.build_system import BuildSystem +from rez.build_system import BuildSystem, BuildResult from rez.build_process import BuildType from rez.utils.execution import create_forwarding_script -from rez.packages import get_developer_package +from rez.packages import get_developer_package, Variant from rez.resolved_context import ResolvedContext from rez.shells import create_shell from rez.exceptions import PackageMetadataError @@ -22,6 +25,10 @@ from rez.utils.logging_ import print_warning from rez.config import config +if TYPE_CHECKING: + import argparse + from rez.rex import RexExecutor + class CustomBuildSystem(BuildSystem): """This build system runs the 'build_command' defined in a package.py. @@ -48,11 +55,11 @@ class CustomBuildSystem(BuildSystem): """ @classmethod - def name(cls): + def name(cls) -> str: return "custom" @classmethod - def is_valid_root(cls, path, package=None): + def is_valid_root(cls, path, package=None) -> bool: if package is None: try: package = get_developer_package(path) @@ -61,8 +68,8 @@ def is_valid_root(cls, path, package=None): return (getattr(package, "build_command", None) is not None) - def __init__(self, working_dir, opts=None, package=None, write_build_scripts=False, - verbose=False, build_args=[], child_build_args=[]): + def __init__(self, working_dir, opts=None, package=None, write_build_scripts: bool = False, + verbose: bool = False, build_args=[], child_build_args=[]) -> None: super(CustomBuildSystem, self).__init__( working_dir, opts=opts, @@ -73,7 +80,7 @@ def __init__(self, working_dir, opts=None, package=None, write_build_scripts=Fal child_build_args=child_build_args) @classmethod - def bind_cli(cls, parser, group): + def bind_cli(cls, parser: argparse.ArgumentParser, group: argparse._ArgumentGroup) -> None: """ Uses a 'parse_build_args.py' file to add options, if found. """ @@ -97,15 +104,20 @@ def bind_cli(cls, parser, group): # store extra args onto parser so we can get to it in self.build() setattr(parser, "_rezbuild_extra_args", list(extra_args)) - def build(self, context, variant, build_path, install_path, install=False, - build_type=BuildType.local): + def build(self, + context: ResolvedContext, + variant: Variant, + build_path: str, + install_path: str, + install: bool = False, + build_type=BuildType.local) -> BuildResult: """Perform the build. Note that most of the func args aren't used here - that's because this info is already passed to the custom build command via environment variables. """ - ret = {} + ret = BuildResult() if self.write_build_scripts: # write out the script that places the user in a build env @@ -173,7 +185,7 @@ def expand(txt): install_path=install_path ) - def _actions_callback(executor): + def _actions_callback(executor) -> None: self._add_build_actions( executor, context=context, @@ -215,8 +227,8 @@ def _actions_callback(executor): return ret @classmethod - def _add_build_actions(cls, executor, context, package, variant, - build_type, install, build_path, install_path=None): + def _add_build_actions(cls, executor: RexExecutor, context: ResolvedContext, package, variant, + build_type, install, build_path, install_path=None) -> None: cls.add_standard_build_actions( executor=executor, context=context, @@ -229,7 +241,7 @@ def _add_build_actions(cls, executor, context, package, variant, def _FWD__spawn_build_shell(working_dir, build_path, variant_index, install, - install_path=None): + install_path=None) -> None: # This spawns a shell that the user can run the build command in directly context = ResolvedContext.load(os.path.join(build_path, "build.rxt")) package = get_developer_package(working_dir) diff --git a/src/rezplugins/build_system/make.py b/src/rezplugins/build_system/make.py index a6b7c82ba..34fe4d08f 100644 --- a/src/rezplugins/build_system/make.py +++ b/src/rezplugins/build_system/make.py @@ -11,15 +11,15 @@ class MakeBuildSystem(BuildSystem): @classmethod - def name(cls): + def name(cls) -> str: return "make" @classmethod def is_valid_root(cls, path, package=None): return os.path.isfile(os.path.join(path, "Makefile")) - def __init__(self, working_dir, opts=None, package=None, write_build_scripts=False, - verbose=False, build_args=[], child_build_args=[]): + def __init__(self, working_dir, opts=None, package=None, write_build_scripts: bool = False, + verbose: bool = False, build_args=[], child_build_args=[]) -> None: super(MakeBuildSystem, self).__init__(working_dir) raise NotImplementedError diff --git a/src/rezplugins/package_repository/filesystem.py b/src/rezplugins/package_repository/filesystem.py index 46fdce72c..82367fde9 100644 --- a/src/rezplugins/package_repository/filesystem.py +++ b/src/rezplugins/package_repository/filesystem.py @@ -5,6 +5,8 @@ """ Filesystem-based package repository """ +from __future__ import annotations + from contextlib import contextmanager from functools import lru_cache import os.path @@ -35,6 +37,12 @@ from rez.vendor.schema.schema import Schema, Optional, And, Use, Or from rez.version import Version, VersionRange +from typing import Any, Iterator, Iterable, TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Self + from rez.packages import Package, Variant, PackageRepositoryResourceWrapper + from rez.package_resources import PackageRepositoryResource, VariantResource debug_print = config.debug_printer("resources") @@ -50,7 +58,7 @@ format_version = 2 -def check_format_version(filename, data): +def check_format_version(filename: str, data: dict[str, Any]) -> None: format_version_ = data.pop("format_version", None) if format_version_ is not None: @@ -87,14 +95,14 @@ class FileSystemPackageFamilyResource(PackageFamilyResource): key = "filesystem.family" repository_type = "filesystem" - def _uri(self): + def _uri(self) -> str: return self.path @cached_property - def path(self): + def path(self) -> str: return os.path.join(self.location, self.name) - def get_last_release_time(self): + def get_last_release_time(self) -> float: # this repository makes sure to update path mtime every time a # variant is added to the repository try: @@ -102,7 +110,7 @@ def get_last_release_time(self): except OSError: return 0 - def iter_packages(self): + def iter_packages(self) -> Iterator[FileSystemPackageResource]: # check for unversioned package if config.allow_unversioned_packages: filepath, _ = self._repository._get_file(self.path) @@ -136,11 +144,11 @@ class FileSystemPackageResource(PackageResourceHelper): repository_type = "filesystem" schema = package_pod_schema - def _uri(self): + def _uri(self) -> str: return self.filepath @cached_property - def parent(self): + def parent(self) -> FileSystemPackageFamilyResource: family = self._repository.get_resource( FileSystemPackageFamilyResource.key, location=self.location, @@ -148,13 +156,13 @@ def parent(self): return family @cached_property - def state_handle(self): + def state_handle(self) -> float | None: if self.filepath: return os.path.getmtime(self.filepath) return None @property - def base(self): + def base(self) -> str: # Note: '_redirected_base' is a special attribute set by the build # process in order to perform pre-install/release package testing. See # `LocalBuildProcess._run_tests()` @@ -164,7 +172,7 @@ def base(self): return redirected_base or self.path @cached_property - def path(self): + def path(self) -> str: path = os.path.join(self.location, self.name) ver_str = self.get("version") if ver_str: @@ -172,18 +180,18 @@ def path(self): return path @cached_property - def filepath(self): + def filepath(self) -> str | None: return self._filepath_and_format[0] @cached_property - def file_format(self): + def file_format(self) -> FileFormat | None: return self._filepath_and_format[1] @cached_property - def _filepath_and_format(self): + def _filepath_and_format(self) -> tuple[str, FileFormat] | tuple[None, None]: return self._repository._get_file(self.path) - def _load(self): + def _load(self) -> dict[str, Any]: if self.filepath is None: raise PackageDefinitionFileMissing( "Missing package definition file: %r" % self) @@ -267,7 +275,7 @@ class FileSystemVariantResource(VariantResourceHelper): repository_type = "filesystem" @cached_property - def parent(self): + def parent(self) -> FileSystemPackageResource: package = self._repository.get_resource( FileSystemPackageResource.key, location=self.location, @@ -309,7 +317,7 @@ def get_last_release_time(self): except OSError: return 0 - def iter_packages(self): + def iter_packages(self) -> Iterator[FileSystemCombinedPackageResource]: # unversioned package if config.allow_unversioned_packages and not self.versions: package = self._repository.get_resource( @@ -349,12 +357,12 @@ class FileSystemCombinedPackageResource(PackageResourceHelper): repository_type = "filesystem" schema = package_pod_schema - def _uri(self): + def _uri(self) -> str: ver_str = self.get("version", "") return "%s<%s>" % (self.parent.filepath, ver_str) @cached_property - def parent(self): + def parent(self) -> FileSystemCombinedPackageFamilyResource: family = self._repository.get_resource( FileSystemCombinedPackageFamilyResource.key, location=self.location, @@ -363,17 +371,17 @@ def parent(self): return family @property - def base(self): + def base(self) -> str | None: return None # combined resource types do not have 'base' @cached_property - def state_handle(self): + def state_handle(self) -> float: return os.path.getmtime(self.parent.filepath) - def iter_variants(self): + def iter_variants(self) -> Iterator[FileSystemCombinedVariantResource]: num_variants = len(self._data.get("variants", [])) if num_variants == 0: - indexes = [None] + indexes: Iterable[int | None] = [None] else: indexes = range(num_variants) @@ -387,7 +395,7 @@ def iter_variants(self): index=index) yield variant - def _load(self): + def _load(self) -> dict[str, Any] | None: data = self.parent._data.copy() if "versions" in data: @@ -411,7 +419,7 @@ class FileSystemCombinedVariantResource(VariantResourceHelper): repository_type = "filesystem" @cached_property - def parent(self): + def parent(self) -> PackageRepositoryResource: package = self._repository.get_resource( FileSystemCombinedPackageResource.key, location=self.location, @@ -420,7 +428,7 @@ def parent(self): version=self.get("version")) return package - def _root(self): + def _root(self, ignore_shortlinks: bool = False) -> str | None: return None # combined resource types do not have 'root' @@ -481,11 +489,11 @@ class FileSystemPackageRepository(PackageRepository): ) @classmethod - def name(cls): + def name(cls) -> str: return "filesystem" - def __init__(self, location, resource_pool, disable_memcache=None, - disable_pkg_ignore=False): + def __init__(self, location: str, resource_pool: ResourcePool, disable_memcache: bool | None = None, + disable_pkg_ignore: bool = False) -> None: """Create a filesystem package repository. Args: @@ -549,44 +557,44 @@ def __init__(self, location, resource_pool, disable_memcache=None, ) self._get_version_dirs = decorator2(self._get_version_dirs) - def _uid(self): + def _uid(self) -> tuple: t = ["filesystem", self.location] if os.path.exists(self.location): st = os.stat(self.location) t.append(int(st.st_ino)) return tuple(t) - def get_package_family(self, name): + def get_package_family(self, name: str) -> PackageFamilyResource: return self.get_family(name) @pool_memcached_connections - def iter_package_families(self): + def iter_package_families(self) -> Iterator[PackageFamilyResource]: for family in self.get_families(): yield family @pool_memcached_connections - def iter_packages(self, package_family_resource): + def iter_packages(self, package_family_resource: PackageFamilyResource) -> Iterator[Package]: for package in self.get_packages(package_family_resource): yield package - def iter_variants(self, package_resource): + def iter_variants(self, package_resource: PackageResourceHelper) -> Iterator[VariantResource]: for variant in self.get_variants(package_resource): yield variant - def get_parent_package_family(self, package_resource): + def get_parent_package_family(self, package_resource: PackageResourceHelper) -> PackageRepositoryResource: return package_resource.parent - def get_parent_package(self, variant_resource): + def get_parent_package(self, variant_resource: VariantResource) -> PackageRepositoryResource: return variant_resource.parent - def get_variant_state_handle(self, variant_resource): + def get_variant_state_handle(self, variant_resource: VariantResource): package_resource = variant_resource.parent return package_resource.state_handle - def get_last_release_time(self, package_family_resource): + def get_last_release_time(self, package_family_resource: PackageFamilyResource): return package_family_resource.get_last_release_time() - def get_package_from_uri(self, uri): + def get_package_from_uri(self, uri: str) -> PackageResourceHelper | None: """ Example URIs: - /svr/packages/mypkg/1.0.0/package.py @@ -620,7 +628,7 @@ def get_package_from_uri(self, uri): pkg_ver = Version(pkg_ver_str) return self.get_package(pkg_name, pkg_ver) - def get_variant_from_uri(self, uri): + def get_variant_from_uri(self, uri: str) -> VariantResourceHelper | None: """ Example URIs: - /svr/packages/mypkg/1.0.0/package.py[1] @@ -656,7 +664,8 @@ def get_variant_from_uri(self, uri): return None - def ignore_package(self, pkg_name, pkg_version, allow_missing=False): + def ignore_package(self, pkg_name: str, pkg_version: Version, + allow_missing: bool = False) -> int: # find package, even if already ignored if not allow_missing: repo_copy = self._copy( @@ -684,7 +693,7 @@ def ignore_package(self, pkg_name, pkg_version, allow_missing=False): self._on_changed(pkg_name) return 1 - def unignore_package(self, pkg_name, pkg_version): + def unignore_package(self, pkg_name: str, pkg_version: Version) -> int: # find and remove .ignore{ver} file if it exists ignore_file_was_removed = False filename = self.ignore_prefix + str(pkg_version) @@ -703,7 +712,7 @@ def unignore_package(self, pkg_name, pkg_version): else: return -1 - def remove_package(self, pkg_name, pkg_version): + def remove_package(self, pkg_name: str, pkg_version: Version) -> bool: # ignore it first, so a partially deleted pkg is not visible i = self.ignore_package(pkg_name, pkg_version) if i == -1: @@ -731,7 +740,7 @@ def remove_package(self, pkg_name, pkg_version): return True - def remove_package_family(self, pkg_name, force=False): + def remove_package_family(self, pkg_name: str, force: bool = False) -> bool: # get a non-cached copy and see if fam exists repo_copy = self._copy( disable_pkg_ignore=True, @@ -761,11 +770,11 @@ def remove_package_family(self, pkg_name, force=False): self._on_changed(pkg_name) return True - def remove_ignored_since(self, days, dry_run=False, verbose=False): + def remove_ignored_since(self, days, dry_run: bool = False, verbose: bool = False) -> int: now = int(time.time()) num_removed = 0 - def _info(msg, *nargs): + def _info(msg, *nargs) -> None: if verbose: print_info(msg, *nargs) @@ -801,7 +810,7 @@ def _info(msg, *nargs): return num_removed - def get_resource_from_handle(self, resource_handle, verify_repo=True): + def get_resource_from_handle(self, resource_handle, verify_repo: bool = True): if verify_repo: repository_type = resource_handle.variables.get("repository_type") location = resource_handle.variables.get("location") @@ -830,7 +839,7 @@ def get_resource_from_handle(self, resource_handle, verify_repo=True): return resource @cached_property - def file_lock_dir(self): + def file_lock_dir(self) -> str | None: dirname = _settings.file_lock_dir if not dirname: return None @@ -848,7 +857,7 @@ def file_lock_dir(self): return dirname - def pre_variant_install(self, variant_resource): + def pre_variant_install(self, variant_resource: VariantResourceHelper) -> None: if not variant_resource.version: return @@ -865,7 +874,7 @@ def pre_variant_install(self, variant_resource): with open(filepath, 'w'): # create empty file pass - def on_variant_install_cancelled(self, variant_resource): + def on_variant_install_cancelled(self, variant_resource) -> None: """ TODO: Currently this will not delete a newly created package version @@ -888,7 +897,8 @@ def on_variant_install_cancelled(self, variant_resource): family_path = os.path.join(self.location, variant_resource.name) self._delete_stale_build_tagfiles(family_path) - def install_variant(self, variant_resource, dry_run=False, overrides=None): + def install_variant(self, variant_resource: VariantResource, + dry_run: bool = False, overrides: dict[str, Any] | None = None) -> VariantResource: overrides = overrides or {} # Name and version overrides are a special case - they change the @@ -934,11 +944,11 @@ def install_variant(self, variant_resource, dry_run=False, overrides=None): ) # install the variant - def _create_variant(): + def _create_variant() -> VariantResource: return self._create_variant( variant_resource, dry_run=dry_run, - overrides=overrides + overrides=overrides or {} ) if dry_run: @@ -949,7 +959,7 @@ def _create_variant(): return variant - def _copy(self, **kwargs): + def _copy(self, **kwargs) -> Self: """ Make a copy of the repo that does not share resources with this one. """ @@ -958,7 +968,7 @@ def _copy(self, **kwargs): return repo_copy @contextmanager - def _lock_package(self, package_name, package_version=None): + def _lock_package(self, package_name: str, package_version: str | Version | None = None) -> Iterator[None]: from rez.vendor.lockfile import NotLocked if _settings.file_lock_type == 'default': @@ -997,7 +1007,7 @@ def _lock_package(self, package_name, package_version=None): except NotLocked: pass - def clear_caches(self): + def clear_caches(self) -> None: super(FileSystemPackageRepository, self).clear_caches() self.get_families.cache_clear() self.get_family.cache_clear() @@ -1012,7 +1022,8 @@ def clear_caches(self): # unfortunately we need to clear file cache across the board clear_file_caches() - def get_package_payload_path(self, package_name, package_version=None): + def get_package_payload_path(self, package_name: str, + package_version: str | Version | None = None) -> str: path = os.path.join(self.location, package_name) if package_version: @@ -1022,15 +1033,15 @@ def get_package_payload_path(self, package_name, package_version=None): # -- internal - def _get_family_dirs__key(self): + def _get_family_dirs__key(self) -> str: if os.path.isdir(self.location): st = os.stat(self.location) return str(("listdir", self.location, int(st.st_ino), st.st_mtime)) else: return str(("listdir", self.location)) - def _get_family_dirs(self): - dirs = [] + def _get_family_dirs(self) -> list[tuple[str, str | None]]: + dirs: list[tuple[str, str | None]] = [] if not os.path.isdir(self.location): return dirs @@ -1050,13 +1061,13 @@ def _get_family_dirs(self): return dirs - def _get_version_dirs__key(self, root): + def _get_version_dirs__key(self, root: str) -> str: st = os.stat(root) return str(("listdir", root, int(st.st_ino), st.st_mtime)) - def _get_version_dirs(self, root): + def _get_version_dirs(self, root: str) -> list[str]: # Ignore a version if there is a .ignore file next to it - def ignore_dir(name): + def ignore_dir(name: str) -> bool: if self.disable_pkg_ignore: return False else: @@ -1084,7 +1095,7 @@ def ignore_dir(name): # tested regardless. Failed releases may cause 'building files' to be # left behind, so we need to clear these out also # - dirs = set() + dirs_set = set() building_dirs = set() # find dirs and dirs marked as 'building' @@ -1099,25 +1110,25 @@ def ignore_dir(name): path = os.path.join(root, name) if os.path.isdir(path) and not ignore_dir(name): - dirs.add(name) + dirs_set.add(name) # check 'building' dirs for validity for name in building_dirs: - if name not in dirs: + if name not in dirs_set: continue path = os.path.join(root, name) if not self._is_valid_package_directory(path): # package probably still being built - dirs.remove(name) + dirs_set.remove(name) - return list(dirs) + return list(dirs_set) # True if `path` contains package.py or similar - def _is_valid_package_directory(self, path): + def _is_valid_package_directory(self, path: str) -> bool: return bool(self._get_file(path, "package")[0]) - def _get_families(self): + def _get_families(self) -> list[PackageFamilyResource]: families = [] for name, ext in self._get_family_dirs(): if ext is None: # is a directory @@ -1135,7 +1146,7 @@ def _get_families(self): return families - def _get_family(self, name): + def _get_family(self, name: str) -> PackageFamilyResource | None: is_valid_package_name(name, raise_error=True) if os.path.isdir(os.path.join(self.location, name)): # force case-sensitive match on pkg family dir, on case-insensitive platforms @@ -1165,13 +1176,13 @@ def _get_family(self, name): ) return None - def _get_packages(self, package_family_resource): + def _get_packages(self, package_family_resource: PackageFamilyResource) -> list[Package]: return [x for x in package_family_resource.iter_packages()] - def _get_variants(self, package_resource): + def _get_variants(self, package_resource: PackageResourceHelper) -> list[VariantResource]: return [x for x in package_resource.iter_variants()] - def _get_file(self, path, package_filename=None): + def _get_file(self, path: str, package_filename=None) -> tuple[str, FileFormat] | tuple[None, None]: if package_filename: package_filenames = [package_filename] else: @@ -1186,14 +1197,16 @@ def _get_file(self, path, package_filename=None): return filepath, format_ return None, None - def _create_family(self, name): + def _create_family(self, name: str) -> PackageFamilyResource: path = os.path.join(self.location, name) os.makedirs(path, exist_ok=True) self._on_changed(name) return self.get_package_family(name) - def _create_variant(self, variant, dry_run=False, overrides=None): + # FIXME: overrides should not default to None, it must be provided + def _create_variant(self, variant: VariantResource, dry_run: bool = False, + overrides: dict[str, Any] = None) -> VariantResource | None: # special case overrides variant_name = overrides.get("name") or variant.name variant_version = overrides.get("version") or variant.version @@ -1218,7 +1231,7 @@ def _create_variant(self, variant, dry_run=False, overrides=None): % family.filepath) # find the package if it already exists - existing_package = None + existing_package: Package | None = None for package in self.iter_packages(family): if package.version == variant_version: @@ -1257,7 +1270,7 @@ def _create_variant(self, variant, dry_run=False, overrides=None): # converted to a Config object. We need it as the raw dict that you'd # see in a package.py. # - def _get_package_data(pkg): + def _get_package_data(pkg: PackageRepositoryResourceWrapper): data = pkg.validated_data() if hasattr(pkg, "_data"): raw_data = pkg._data @@ -1272,7 +1285,7 @@ def _get_package_data(pkg): return data - def _remove_build_keys(obj): + def _remove_build_keys(obj) -> None: for key in package_build_only_keys: obj.pop(key, None) @@ -1486,7 +1499,7 @@ def _remove_build_keys(obj): return new_variant - def _on_changed(self, pkg_name): + def _on_changed(self, pkg_name: str) -> None: """Called when a package is added/removed/changed. """ @@ -1501,7 +1514,7 @@ def _on_changed(self, pkg_name): # clear internal caches, otherwise change may not be visible self.clear_caches() - def _delete_stale_build_tagfiles(self, family_path): + def _delete_stale_build_tagfiles(self, family_path: str) -> None: now = time.time() for name in os.listdir(family_path): @@ -1528,5 +1541,5 @@ def _delete_stale_build_tagfiles(self, family_path): os.remove(tagfilepath) -def register_plugin(): +def register_plugin() -> type[FileSystemPackageRepository]: return FileSystemPackageRepository diff --git a/src/rezplugins/package_repository/memory.py b/src/rezplugins/package_repository/memory.py index 74d50731e..103529873 100644 --- a/src/rezplugins/package_repository/memory.py +++ b/src/rezplugins/package_repository/memory.py @@ -5,6 +5,8 @@ """ In-memory package repository """ +from __future__ import annotations + from rez.package_repository import PackageRepository from rez.package_resources import PackageFamilyResource, VariantResourceHelper, \ PackageResourceHelper, package_pod_schema @@ -12,6 +14,12 @@ from rez.utils.resources import ResourcePool, cached_property from rez.version import VersionedObject +from typing import Any, Iterator, TYPE_CHECKING + +if TYPE_CHECKING: + from rez.packages import VariantResource + from rez.package_resources import PackageRepositoryResource + # This repository type is used when loading 'developer' packages (a package.yaml # or package.py in a developer's working directory), and when programmatically @@ -26,10 +34,10 @@ class MemoryPackageFamilyResource(PackageFamilyResource): key = "memory.family" repository_type = "memory" - def _uri(self): + def _uri(self) -> str: return "%s:%s" % (self.location, self.name) - def iter_packages(self): + def iter_packages(self) -> Iterator[MemoryPackageResource]: data = self._repository.data.get(self.name, {}) # check for unversioned package @@ -57,23 +65,23 @@ class MemoryPackageResource(PackageResourceHelper): repository_type = "memory" schema = package_pod_schema - def _uri(self): + def _uri(self) -> str: obj = VersionedObject.construct(self.name, self.version) return "%s:%s" % (self.location, str(obj)) @property - def base(self): + def base(self) -> str | None: return None # memory types do not have 'base' @cached_property - def parent(self): + def parent(self) -> PackageRepositoryResource: family = self._repository.get_resource( MemoryPackageFamilyResource.key, location=self.location, name=self.name) return family - def _load(self): + def _load(self) -> dict[str, Any]: family_data = self._repository.data.get(self.name, {}) version_str = self.get("version") if not version_str: @@ -86,11 +94,11 @@ class MemoryVariantResource(VariantResourceHelper): key = "memory.variant" repository_type = "memory" - def _root(self): + def _root(self, ignore_shortlinks: bool = False) -> str | None: return None # memory types do not have 'root' @cached_property - def parent(self): + def parent(self) -> PackageRepositoryResource: package = self._repository.get_resource( MemoryPackageResource.key, location=self.location, @@ -131,11 +139,11 @@ class MemoryPackageRepository(PackageRepository): unversioned package 'bah'. """ @classmethod - def name(cls): + def name(cls) -> str: return "memory" @classmethod - def create_repository(cls, repository_data): + def create_repository(cls, repository_data) -> MemoryPackageRepository: """Create a standalone, in-memory repository. Using this function bypasses the `package_repository_manager` singleton. @@ -155,7 +163,7 @@ def create_repository(cls, repository_data): repo.data = repository_data return repo - def __init__(self, location, resource_pool): + def __init__(self, location: str, resource_pool: ResourcePool) -> None: """Create an in-memory package repository. Args: @@ -167,7 +175,7 @@ def __init__(self, location, resource_pool): self.register_resource(MemoryPackageResource) self.register_resource(MemoryVariantResource) - def get_package_family(self, name): + def get_package_family(self, name: str) -> MemoryPackageFamilyResource | None: is_valid_package_name(name, raise_error=True) if name in self.data: family = self.get_resource( @@ -177,23 +185,23 @@ def get_package_family(self, name): return family return None - def iter_package_families(self): + def iter_package_families(self) -> Iterator[MemoryPackageFamilyResource | None]: for name in self.data.keys(): family = self.get_package_family(name) yield family - def iter_packages(self, package_family_resource): + def iter_packages(self, package_family_resource: MemoryPackageFamilyResource) -> Iterator[MemoryPackageResource]: for package in package_family_resource.iter_packages(): yield package - def iter_variants(self, package_resource): + def iter_variants(self, package_resource: PackageResourceHelper) -> Iterator[VariantResource]: for variant in package_resource.iter_variants(): yield variant - def get_parent_package_family(self, package_resource): + def get_parent_package_family(self, package_resource: PackageResourceHelper) -> PackageFamilyResource: return package_resource.parent - def get_parent_package(self, variant_resource): + def get_parent_package(self, variant_resource: VariantResource): return variant_resource.parent diff --git a/src/rezplugins/release_hook/amqp.py b/src/rezplugins/release_hook/amqp.py index cb3cefc3f..43e571e7d 100644 --- a/src/rezplugins/release_hook/amqp.py +++ b/src/rezplugins/release_hook/amqp.py @@ -5,10 +5,13 @@ """ Publishes a message to the broker. """ +from __future__ import annotations + from rez.release_hook import ReleaseHook from rez.utils.logging_ import print_error, print_debug from rez.utils.amqp import publish_message from rez.config import config +from typing import Any class AmqpReleaseHook(ReleaseHook): @@ -42,20 +45,20 @@ class AmqpReleaseHook(ReleaseHook): "message_attributes": dict} @classmethod - def name(cls): + def name(cls) -> str: return "amqp" - def __init__(self, source_path): + def __init__(self, source_path) -> None: super(AmqpReleaseHook, self).__init__(source_path) - def post_release(self, user, install_path, variants, **kwargs): + def post_release(self, user, install_path, variants, **kwargs) -> None: if variants: package = variants[0].parent else: package = self.package # build the message dict - data = {} + data: dict[str, Any] = {} data["package"] = dict( name=package.name, version=str(package.version), @@ -77,7 +80,7 @@ def post_release(self, user, install_path, variants, **kwargs): self.publish_message(data) - def publish_message(self, data): + def publish_message(self, data) -> None: if not self.settings.host: print_error("Did not publish message, host is not specified") return diff --git a/src/rezplugins/release_hook/command.py b/src/rezplugins/release_hook/command.py index eca4c1c18..66571afa5 100644 --- a/src/rezplugins/release_hook/command.py +++ b/src/rezplugins/release_hook/command.py @@ -49,14 +49,14 @@ class CommandReleaseHook(ReleaseHook): } @classmethod - def name(cls): + def name(cls) -> str: return "command" - def __init__(self, source_path): + def __init__(self, source_path) -> None: super(CommandReleaseHook, self).__init__(source_path) def execute_command(self, cmd_name, cmd_arguments, user, errors, env=None): - def _err(msg): + def _err(msg) -> None: errors.append(msg) if self.settings.print_error: print(msg, file=sys.stderr) @@ -65,7 +65,7 @@ def _err(msg): if env: kwargs["env"] = env - def _execute(commands): + def _execute(commands) -> bool: process = Popen(commands, stdout=PIPE, stderr=STDOUT, **kwargs) stdout, _ = process.communicate() @@ -121,7 +121,7 @@ def pre_release(self, user, install_path, variants=None, **kwargs): "The following pre-release commands failed:\n%s" % '\n\n'.join(errors)) - def post_release(self, user, install_path, variants, **kwargs): + def post_release(self, user, install_path, variants, **kwargs) -> None: # note that the package we use here is the *installed* package, not the # developer package (self.package). Otherwise, attributes such as 'root' # will be None @@ -143,7 +143,7 @@ def post_release(self, user, install_path, variants, **kwargs): ) def _execute_commands(self, commands, install_path, package, errors=None, - variants=None): + variants=None) -> None: release_dict = dict(path=install_path) variant_infos = [] if variants: diff --git a/src/rezplugins/release_hook/emailer.py b/src/rezplugins/release_hook/emailer.py index 781151470..24541df8c 100644 --- a/src/rezplugins/release_hook/emailer.py +++ b/src/rezplugins/release_hook/emailer.py @@ -28,14 +28,14 @@ class EmailReleaseHook(ReleaseHook): } @classmethod - def name(cls): + def name(cls) -> str: return "emailer" - def __init__(self, source_path): + def __init__(self, source_path) -> None: super(EmailReleaseHook, self).__init__(source_path) def post_release(self, user, install_path, variants, release_message=None, - changelog=None, previous_version=None, **kwargs): + changelog=None, previous_version=None, **kwargs) -> None: if not variants: return # nothing was released @@ -62,7 +62,7 @@ def post_release(self, user, install_path, variants, release_message=None, subject = formatter.format(self.settings.subject) self.send_email(subject, body) - def send_email(self, subject, body): + def send_email(self, subject, body) -> None: if not self.settings.recipients: return # nothing to do, sending email to nobody diff --git a/src/rezplugins/release_vcs/git.py b/src/rezplugins/release_vcs/git.py index 5aa1a59f4..aad9bce6b 100644 --- a/src/rezplugins/release_vcs/git.py +++ b/src/rezplugins/release_vcs/git.py @@ -24,10 +24,10 @@ class GitReleaseVCS(ReleaseVCS): "allow_no_upstream": bool} @classmethod - def name(cls): + def name(cls) -> str: return 'git' - def __init__(self, pkg_root, vcs_root=None): + def __init__(self, pkg_root, vcs_root=None) -> None: super(GitReleaseVCS, self).__init__(pkg_root, vcs_root=vcs_root) self.executable = self.find_executable('git') @@ -42,7 +42,7 @@ def is_valid_root(cls, path): return os.path.isdir(os.path.join(path, '.git')) @classmethod - def search_parents_for_root(cls): + def search_parents_for_root(cls) -> bool: return True def git(self, *nargs): @@ -177,7 +177,7 @@ def _url(op): raise ReleaseVCSError("failed to parse %s url from:\n%s" % (op, '\n'.join(lines))) - def _get(key, fn): + def _get(key, fn) -> bool: try: value = fn() doc[key] = value @@ -199,11 +199,11 @@ def _tracking_branch(): _get("push_url", functools.partial(_url, "push")) return doc - def tag_exists(self, tag_name): + def tag_exists(self, tag_name) -> bool: tags = self.git("tag") return (tag_name in tags) - def create_release_tag(self, tag_name, message=None): + def create_release_tag(self, tag_name, message=None) -> None: if self.tag_exists(tag_name): return @@ -223,7 +223,7 @@ def create_release_tag(self, tag_name, message=None): self.git("push", remote, tag_name) @classmethod - def export(cls, revision, path): + def export(cls, revision, path) -> None: url = revision["fetch_url"] commit = revision["commit"] path_, dirname = os.path.split(path) diff --git a/src/rezplugins/release_vcs/hg.py b/src/rezplugins/release_vcs/hg.py index ecdcb73c2..a799aa13a 100644 --- a/src/rezplugins/release_vcs/hg.py +++ b/src/rezplugins/release_vcs/hg.py @@ -17,10 +17,10 @@ class HgReleaseVCSError(ReleaseVCSError): class HgReleaseVCS(ReleaseVCS): @classmethod - def name(cls): + def name(cls) -> str: return 'hg' - def __init__(self, pkg_root, vcs_root=None): + def __init__(self, pkg_root, vcs_root=None) -> None: super(HgReleaseVCS, self).__init__(pkg_root, vcs_root=vcs_root) self.executable = self.find_executable('hg') @@ -45,7 +45,7 @@ def is_valid_root(cls, path): return os.path.isdir(os.path.join(path, '.hg')) @classmethod - def search_parents_for_root(cls): + def search_parents_for_root(cls) -> bool: return True def hg(self, *nargs, **kwargs): @@ -87,8 +87,8 @@ def _create_tag_highlevel(self, tag_name, message=None): results.append({'type': 'tag', 'patch': False}) return results - def _create_tag_lowlevel(self, tag_name, message=None, force=True, - patch=False): + def _create_tag_lowlevel(self, tag_name, message=None, force: bool = True, + patch: bool = False) -> bool: """Create a tag on the toplevel or patch repo If the tag exists, and force is False, no tag is made. If force is True, @@ -142,7 +142,7 @@ def _create_tag_lowlevel(self, tag_name, message=None, force=True, self.hg(patch=patch, *tag_args) return True - def get_tags(self, patch=False): + def get_tags(self, patch: bool = False): lines = self.hg('tags', patch=patch) # results will look like: @@ -158,11 +158,11 @@ def get_tags(self, patch=False): tags[tag_name] = {'rev': rev, 'shortnode': shortnode} return tags - def tag_exists(self, tag_name): + def tag_exists(self, tag_name) -> bool: tags = self.get_tags() return (tag_name in tags.keys()) - def is_ancestor(self, commit1, commit2, patch=False): + def is_ancestor(self, commit1, commit2, patch: bool = False) -> bool: """Returns True if commit1 is a direct ancestor of commit2, or False otherwise. @@ -171,14 +171,14 @@ def is_ancestor(self, commit1, commit2, patch=False): "--template", "exists", patch=patch) return "exists" in result - def get_paths(self, patch=False): + def get_paths(self, patch: bool = False): paths = self.hg("paths", patch=patch) return dict(line.split(' = ', 1) for line in paths if line) - def get_default_url(self, patch=False): + def get_default_url(self, patch: bool = False): return self.get_paths(patch=patch).get('default') - def validate_repostate(self): + def validate_repostate(self) -> None: def _check(modified, path): if modified: modified = [line.split()[-1] for line in modified] @@ -196,7 +196,7 @@ def get_current_revision(self): 'branch': self.hg("branch")[0], } - def _get(key, fn): + def _get(key, fn) -> bool: try: value = fn() doc[key] = value diff --git a/src/rezplugins/release_vcs/stub.py b/src/rezplugins/release_vcs/stub.py index 41029e527..446992500 100644 --- a/src/rezplugins/release_vcs/stub.py +++ b/src/rezplugins/release_vcs/stub.py @@ -19,12 +19,12 @@ class StubReleaseVCS(ReleaseVCS): A writable '.stub' file must be present in the project root. Any created tags are written to this yaml file. """ - def __init__(self, pkg_root, vcs_root=None): + def __init__(self, pkg_root, vcs_root=None) -> None: super(StubReleaseVCS, self).__init__(pkg_root, vcs_root=vcs_root) self.time = int(time.time()) @classmethod - def name(cls): + def name(cls) -> str: return "stub" @classmethod @@ -32,16 +32,16 @@ def is_valid_root(cls, path): return os.path.exists(os.path.join(path, '.stub')) @classmethod - def search_parents_for_root(cls): + def search_parents_for_root(cls) -> bool: return False - def validate_repostate(self): + def validate_repostate(self) -> None: pass def get_current_revision(self): return self.time - def get_changelog(self, previous_revision=None, max_revisions=None): + def get_changelog(self, previous_revision=None, max_revisions=None) -> str: if previous_revision: if isinstance(previous_revision, int): seconds = self.time - previous_revision @@ -51,11 +51,11 @@ def get_changelog(self, previous_revision=None, max_revisions=None): else: return "This is the first commit" - def tag_exists(self, tag_name): + def tag_exists(self, tag_name) -> bool: data = self._read_stub() return tag_name in data.get("tags", []) - def create_release_tag(self, tag_name, message=None): + def create_release_tag(self, tag_name, message=None) -> None: data = self._read_stub() if "tags" not in data: data["tags"] = {} @@ -71,7 +71,7 @@ def _read_stub(self): with open(os.path.join(self.vcs_root, '.stub')) as f: return yaml.load(f.read(), Loader=yaml.FullLoader) or {} - def _write_stub(self, data): + def _write_stub(self, data) -> None: with open(os.path.join(self.vcs_root, '.stub'), 'w') as f: f.write(dump_yaml(data)) diff --git a/src/rezplugins/release_vcs/svn.py b/src/rezplugins/release_vcs/svn.py index df1ab7337..40136962b 100644 --- a/src/rezplugins/release_vcs/svn.py +++ b/src/rezplugins/release_vcs/svn.py @@ -59,10 +59,10 @@ def get_svn_login(realm, username, may_save): class SvnReleaseVCS(ReleaseVCS): @classmethod - def name(cls): + def name(cls) -> str: return 'svn' - def __init__(self, pkg_root, vcs_root=None): + def __init__(self, pkg_root, vcs_root=None) -> None: super(SvnReleaseVCS, self).__init__(pkg_root, vcs_root=vcs_root) self.svnc = svn_get_client() @@ -77,7 +77,7 @@ def is_valid_root(cls, path): return os.path.isdir(os.path.join(path, '.svn')) @classmethod - def search_parents_for_root(cls): + def search_parents_for_root(cls) -> bool: return True def validate_repostate(self): @@ -93,13 +93,13 @@ def validate_repostate(self): "'%s' is not in a state to release - you may need to svn-checkin " "and/or svn-update: %s" % (self.pkg_root, str(status_list_known))) - def _create_tag_impl(self, tag_name, message=None): + def _create_tag_impl(self, tag_name, message=None) -> None: tag_url = self.get_tag_url(tag_name) print("rez-release: creating project tag in: %s..." % tag_url) self.svnc.callback_get_log_message = lambda x: (True, x) self.svnc.copy2([(self.this_url,)], tag_url, make_parents=True) - def get_changelog(self, previous_revision=None, max_revisions=None): + def get_changelog(self, previous_revision=None, max_revisions=None) -> str: return "TODO" def get_tag_url(self, tag_name=None): @@ -126,7 +126,7 @@ def svn_url_exists(self, url): def get_current_revision(self): return self.svnc.info(self.pkg_root)['revision'].number - def create_release_tag(self, tag_name, message=None): + def create_release_tag(self, tag_name, message=None) -> None: # svn requires a message - if not provided, make it the same as the # tag name.. if not message: diff --git a/src/rezplugins/shell/_utils/powershell_base.py b/src/rezplugins/shell/_utils/powershell_base.py index 1e2481237..236471e65 100644 --- a/src/rezplugins/shell/_utils/powershell_base.py +++ b/src/rezplugins/shell/_utils/powershell_base.py @@ -43,10 +43,10 @@ def _escape_vars(s): @classmethod def startup_capabilities(cls, - rcfile=False, - norc=False, - stdin=False, - command=False): + rcfile: bool = False, + norc: bool = False, + stdin: bool = False, + command: bool = False): cls._unsupported_option('rcfile', rcfile) cls._unsupported_option('norc', norc) cls._unsupported_option('stdin', stdin) @@ -83,11 +83,11 @@ def get_syspaths(cls): return cls.syspaths - def _bind_interactive_rez(self): + def _bind_interactive_rez(self) -> None: if config.set_prompt and self.settings.prompt: self._addline('Function prompt {"%s"}' % self.settings.prompt) - def _additional_commands(self, executor): + def _additional_commands(self, executor) -> None: # Make .py launch within shell without extension. # For PowerShell this will also execute in the same window, so that # stdout can be captured. @@ -103,20 +103,20 @@ def spawn_shell(self, context_file, tmpdir, rcfile=None, - norc=False, - stdin=False, + norc: bool = False, + stdin: bool = False, command=None, env=None, - quiet=False, + quiet: bool = False, pre_command=None, - add_rez=True, + add_rez: bool = True, **Popen_args): startup_sequence = self.get_startup_sequence(rcfile, norc, bool(stdin), command) shell_command = None - def _record_shell(ex, files, bind_rez=True, print_msg=False): + def _record_shell(ex, files, bind_rez: bool = True, print_msg: bool = False) -> None: ex.source(context_file) if startup_sequence["envvar"]: ex.unsetenv(startup_sequence["envvar"]) @@ -218,7 +218,7 @@ def get_output(self, style=OutputStyle.file): script = '&& '.join(lines) return script - def escape_string(self, value, is_path=False): + def escape_string(self, value, is_path: bool = False): value = EscapedString.promote(value) value = value.expanduser() result = '' @@ -240,17 +240,17 @@ def normalize_path(self, path): else: return path - def _saferefenv(self, key): + def _saferefenv(self, key) -> None: pass - def shebang(self): + def shebang(self) -> None: pass - def setenv(self, key, value): + def setenv(self, key, value) -> None: value = self.escape_string(value, is_path=self._is_pathed_key(key)) self._addline('Set-Item -Path "Env:{0}" -Value "{1}"'.format(key, value)) - def prependenv(self, key, value): + def prependenv(self, key, value) -> None: value = self.escape_string(value, is_path=self._is_pathed_key(key)) # Be careful about ambiguous case in pwsh on Linux where pathsep is : @@ -260,7 +260,7 @@ def prependenv(self, key, value): .format(key, value, self.pathsep) ) - def appendenv(self, key, value): + def appendenv(self, key, value) -> None: value = self.escape_string(value, is_path=self._is_pathed_key(key)) # Be careful about ambiguous case in pwsh on Linux where pathsep is : @@ -271,15 +271,15 @@ def appendenv(self, key, value): 'Set-Item -Path "Env:{0}" -Value ((Get-ChildItem -ErrorAction SilentlyContinue "Env:{0}").Value + "{1}{2}")' .format(key, os.path.pathsep, value)) - def unsetenv(self, key): + def unsetenv(self, key) -> None: self._addline( 'Remove-Item -ErrorAction SilentlyContinue "Env:{0}"'.format(key) ) - def resetenv(self, key, value, friends=None): + def resetenv(self, key, value, friends=None) -> None: self._addline(self.setenv(key, value)) - def alias(self, key, value): + def alias(self, key, value) -> None: value = EscapedString.disallow(value) # TODO: Find a way to properly escape paths in alias() calls that also # contain args @@ -287,26 +287,26 @@ def alias(self, key, value): cmd = "function %s() { %s @args }" % (key, value) self._addline(cmd) - def comment(self, value): + def comment(self, value) -> None: for line in value.split('\n'): self._addline('# %s' % line) - def info(self, value): + def info(self, value) -> None: for line in value.split('\n'): line = self.escape_string(line) line = self.convert_tokens(line) self._addline('Write-Host %s' % line) - def error(self, value): + def error(self, value) -> None: for line in value.split('\n'): line = self.escape_string(line) line = self.convert_tokens(line) self._addline('Write-Error "%s"' % line) - def source(self, value): + def source(self, value) -> None: self._addline(". \"%s\"" % value) - def command(self, value): + def command(self, value) -> None: self._addline(value) @classmethod @@ -314,7 +314,7 @@ def get_all_key_tokens(cls, key): return ["${Env:%s}" % key, "$Env:%s" % key] @classmethod - def line_terminator(cls): + def line_terminator(cls) -> str: return "\n" @classmethod diff --git a/src/rezplugins/shell/_utils/windows.py b/src/rezplugins/shell/_utils/windows.py index 291b715a7..4f88994bb 100644 --- a/src/rezplugins/shell/_utils/windows.py +++ b/src/rezplugins/shell/_utils/windows.py @@ -2,6 +2,8 @@ # Copyright Contributors to the Rez Project +from __future__ import annotations + import os import re @@ -10,7 +12,7 @@ _env_var_regex = re.compile(r"%([^%]*)%") -def to_posix_path(path): +def to_posix_path(path: str) -> str: """Convert (eg) "C:\foo" to "/c/foo" TODO: doesn't take into account escaped bask slashes, which would be @@ -33,7 +35,7 @@ def _repl(m): return path -def to_windows_path(path): +def to_windows_path(path: str) -> str: """Convert (eg) "C:\foo/bin" to "C:\foo\bin" The mixed syntax results from strings in package commands such as @@ -45,26 +47,28 @@ def to_windows_path(path): return path.replace('/', '\\') -def get_syspaths_from_registry(): +def get_syspaths_from_registry() -> list[str]: # Local import to avoid import errors on non-windows systems. + import sys import winreg paths = [] - path_query_keys = ( - (winreg.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment'), - (winreg.HKEY_CURRENT_USER, 'Environment') - ) - - for root_key, sub_key in path_query_keys: - try: - with winreg.OpenKey(root_key, sub_key) as key: - reg_value, _ = winreg.QueryValueEx(key, 'Path') - except OSError: - # Key does not exist - pass - else: - expanded_value = winreg.ExpandEnvironmentStrings(reg_value) - paths.extend(expanded_value.split(os.pathsep)) + if sys.platform == "win32": + path_query_keys = ( + (winreg.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment'), + (winreg.HKEY_CURRENT_USER, 'Environment'), + ) + + for root_key, sub_key in path_query_keys: + try: + with winreg.OpenKey(root_key, sub_key) as key: + reg_value, _ = winreg.QueryValueEx(key, 'Path') + except OSError: + # Key does not exist + pass + else: + expanded_value = winreg.ExpandEnvironmentStrings(reg_value) + paths.extend(expanded_value.split(os.pathsep)) return [x for x in paths if x] diff --git a/src/rezplugins/shell/bash.py b/src/rezplugins/shell/bash.py index 7ce9437e1..2251c9e05 100644 --- a/src/rezplugins/shell/bash.py +++ b/src/rezplugins/shell/bash.py @@ -18,12 +18,12 @@ class Bash(SH): norc_arg = '--norc' @classmethod - def name(cls): + def name(cls) -> str: return 'bash' @classmethod - def startup_capabilities(cls, rcfile=False, norc=False, stdin=False, - command=False): + def startup_capabilities(cls, rcfile: bool = False, norc: bool = False, stdin: bool = False, + command: bool = False): if norc: cls._overruled_option('rcfile', 'norc', rcfile) rcfile = False @@ -79,12 +79,12 @@ def get_startup_sequence(cls, rcfile, norc, stdin, command): source_bind_files=True ) - def alias(self, key, value): + def alias(self, key, value) -> None: value = EscapedString.disallow(value) cmd = 'function {key}() {{ {value} "$@"; }};export -f {key};' self._addline(cmd.format(key=key, value=value)) - def _bind_interactive_rez(self): + def _bind_interactive_rez(self) -> None: super(Bash, self)._bind_interactive_rez() completion = os.path.join(module_root_path, "completion", "complete.sh") self.source(completion) diff --git a/src/rezplugins/shell/cmd.py b/src/rezplugins/shell/cmd.py index ddf6f4833..32632b8da 100644 --- a/src/rezplugins/shell/cmd.py +++ b/src/rezplugins/shell/cmd.py @@ -33,21 +33,21 @@ class CMD(Shell): _escape_re = re.compile(r'(?]|(?\^])|(\|)') _escaper = partial(_escape_re.sub, lambda m: '^' + m.group(0)) - def __init__(self): + def __init__(self) -> None: super(CMD, self).__init__() self._doskey_aliases = {} @classmethod - def name(cls): + def name(cls) -> str: return 'cmd' @classmethod - def file_extension(cls): + def file_extension(cls) -> str: return 'bat' @classmethod - def startup_capabilities(cls, rcfile=False, norc=False, stdin=False, - command=False): + def startup_capabilities(cls, rcfile: bool = False, norc: bool = False, stdin: bool = False, + command: bool = False): cls._unsupported_option('rcfile', rcfile) rcfile = False cls._unsupported_option('norc', norc) @@ -83,7 +83,7 @@ def get_syspaths(cls): cls.syspaths = get_syspaths_from_registry() return cls.syspaths - def _bind_interactive_rez(self): + def _bind_interactive_rez(self) -> None: if config.set_prompt and self.settings.prompt: stored_prompt = os.getenv("REZ_STORED_PROMPT_CMD") curr_prompt = stored_prompt or os.getenv("PROMPT", "") @@ -96,15 +96,15 @@ def _bind_interactive_rez(self): new_prompt = new_prompt % curr_prompt self._addline('set PROMPT=%s' % new_prompt) - def spawn_shell(self, context_file, tmpdir, rcfile=None, norc=False, - stdin=False, command=None, env=None, quiet=False, - pre_command=None, add_rez=True, **Popen_args): + def spawn_shell(self, context_file, tmpdir, rcfile=None, norc: bool = False, + stdin: bool = False, command=None, env=None, quiet: bool = False, + pre_command=None, add_rez: bool = True, **Popen_args): command = self._expand_alias(command) startup_sequence = self.get_startup_sequence(rcfile, norc, bool(stdin), command) shell_command = None - def _record_shell(ex, files, bind_rez=True, print_msg=False): + def _record_shell(ex, files, bind_rez: bool = True, print_msg: bool = False) -> None: ex.source(context_file) if startup_sequence["envvar"]: ex.unsetenv(startup_sequence["envvar"]) @@ -213,7 +213,7 @@ def get_output(self, style=OutputStyle.file): script = '&& '.join(lines) return script - def escape_string(self, value, is_path=False): + def escape_string(self, value: str, is_path: bool = False) -> str: """Escape the <, >, ^, and & special characters reserved by Windows. Args: @@ -243,23 +243,23 @@ def escape_string(self, value, is_path=False): def normalize_path(self, path): return to_windows_path(path) - def _saferefenv(self, key): + def _saferefenv(self, key) -> None: pass - def shebang(self): + def shebang(self) -> None: pass - def setenv(self, key, value): + def setenv(self, key, value) -> None: value = self.escape_string(value, is_path=self._is_pathed_key(key)) self._addline('set %s=%s' % (key, value)) - def unsetenv(self, key): + def unsetenv(self, key) -> None: self._addline("set %s=" % key) - def resetenv(self, key, value, friends=None): + def resetenv(self, key, value, friends=None) -> None: self._addline(self.setenv(key, value)) - def alias(self, key, value): + def alias(self, key, value) -> None: # find doskey, falling back to system paths if not in $PATH. Fall back # to unqualified 'doskey' if all else fails if self._doskey is None: @@ -273,11 +273,11 @@ def alias(self, key, value): self._addline("%s %s=%s $*" % (self._doskey, key, value)) - def comment(self, value): + def comment(self, value) -> None: for line in value.split('\n'): self._addline('REM %s' % line) - def info(self, value): + def info(self, value) -> None: for line in value.split('\n'): line = self.escape_string(line) line = self.convert_tokens(line) @@ -286,16 +286,16 @@ def info(self, value): else: self._addline('echo.') - def error(self, value): + def error(self, value) -> None: for line in value.split('\n'): line = self.escape_string(line) line = self.convert_tokens(line) self._addline('echo "%s" 1>&2' % line) - def source(self, value): + def source(self, value) -> None: self._addline("call %s" % value) - def command(self, value): + def command(self, value) -> None: self._addline(value) @classmethod @@ -312,7 +312,7 @@ def join(cls, command): return subprocess.list2cmdline(command) @classmethod - def line_terminator(cls): + def line_terminator(cls) -> str: return "\r\n" def _expand_alias(self, command): diff --git a/src/rezplugins/shell/csh.py b/src/rezplugins/shell/csh.py index 788460f94..c745648c4 100644 --- a/src/rezplugins/shell/csh.py +++ b/src/rezplugins/shell/csh.py @@ -5,6 +5,8 @@ """ CSH shell """ +from __future__ import annotations + import os.path import subprocess import re @@ -17,6 +19,8 @@ from rez.shells import UnixShell from rez.rex import EscapedString +from typing import Iterable + class CSH(UnixShell): norc_arg = '-f' @@ -25,11 +29,11 @@ class CSH(UnixShell): histvar = "histfile" @classmethod - def name(cls): + def name(cls) -> str: return 'csh' @classmethod - def file_extension(cls): + def file_extension(cls) -> str: return 'csh' @classmethod @@ -62,8 +66,8 @@ def get_syspaths(cls): return cls.syspaths @classmethod - def startup_capabilities(cls, rcfile=False, norc=False, stdin=False, - command=False): + def startup_capabilities(cls, rcfile: bool = False, norc: bool = False, stdin: bool = False, + command: bool = False): cls._unsupported_option('rcfile', rcfile) rcfile = False if command is not None: @@ -98,7 +102,7 @@ def get_startup_sequence(cls, rcfile, norc, stdin, command): source_bind_files=(not norc) ) - def escape_string(self, value, is_path=False): + def escape_string(self, value, is_path: bool = False): value = EscapedString.promote(value) value = value.expanduser() result = '' @@ -119,8 +123,8 @@ def escape_string(self, value, is_path=False): return result @classmethod - def join(cls, command): - replacements = [ + def join(cls, command: Iterable[str]): + replacements: list[tuple[str | re.Pattern[str], str]] = [ # escape ! as \! ('!', "\\!"), @@ -137,7 +141,7 @@ def join(cls, command): return shlex_join(command, replacements=replacements) - def _bind_interactive_rez(self): + def _bind_interactive_rez(self) -> None: if config.set_prompt and self.settings.prompt: # TODO: Do more like in sh.py, much less error prone stored_prompt = os.getenv("REZ_STORED_PROMPT_CSH") @@ -153,21 +157,21 @@ def _bind_interactive_rez(self): new_prompt = self.escape_string(new_prompt) self._addline('set prompt=%s' % new_prompt) - def _saferefenv(self, key): + def _saferefenv(self, key) -> None: self._addline("if (!($?%s)) setenv %s" % (key, key)) - def setenv(self, key, value): + def setenv(self, key, value) -> None: value = self.escape_string(value, is_path=self._is_pathed_key(key)) self._addline('setenv %s %s' % (key, value)) - def unsetenv(self, key): + def unsetenv(self, key) -> None: self._addline("unsetenv %s" % key) - def alias(self, key, value): + def alias(self, key, value) -> None: value = EscapedString.disallow(value) self._addline("alias %s '%s';" % (key, value)) - def source(self, value): + def source(self, value) -> None: value = self.escape_string(value) self._addline('source %s' % value) diff --git a/src/rezplugins/shell/gitbash.py b/src/rezplugins/shell/gitbash.py index 30dac3f00..eabe68ca3 100644 --- a/src/rezplugins/shell/gitbash.py +++ b/src/rezplugins/shell/gitbash.py @@ -28,15 +28,15 @@ class GitBash(Bash): _drive_regex = re.compile(r"([A-Za-z]):\\") @classmethod - def name(cls): + def name(cls) -> str: return "gitbash" @classmethod - def executable_name(cls): + def executable_name(cls) -> str: return "bash" @classmethod - def find_executable(cls, name, check_syspaths=False): + def find_executable(cls, name, check_syspaths: bool = False): exepath = super(GitBash, cls).find_executable(name, check_syspaths=check_syspaths) if exepath and "system32" in exepath.lower(): diff --git a/src/rezplugins/shell/powershell.py b/src/rezplugins/shell/powershell.py index b5082314c..8ef272f7c 100644 --- a/src/rezplugins/shell/powershell.py +++ b/src/rezplugins/shell/powershell.py @@ -11,11 +11,11 @@ class PowerShell(PowerShellBase): @classmethod - def name(cls): + def name(cls) -> str: return 'powershell' @classmethod - def file_extension(cls): + def file_extension(cls) -> str: return 'ps1' diff --git a/src/rezplugins/shell/pwsh.py b/src/rezplugins/shell/pwsh.py index febef478d..ed64e14dd 100644 --- a/src/rezplugins/shell/pwsh.py +++ b/src/rezplugins/shell/pwsh.py @@ -11,11 +11,11 @@ class PowerShellCore(PowerShellBase): @classmethod - def name(cls): + def name(cls) -> str: return 'pwsh' @classmethod - def file_extension(cls): + def file_extension(cls) -> str: return 'ps1' @classmethod diff --git a/src/rezplugins/shell/sh.py b/src/rezplugins/shell/sh.py index b9f05150b..9236abf5a 100644 --- a/src/rezplugins/shell/sh.py +++ b/src/rezplugins/shell/sh.py @@ -5,6 +5,8 @@ """ SH shell """ +from __future__ import annotations + import os import os.path import subprocess @@ -22,11 +24,11 @@ class SH(UnixShell): histvar = "HISTFILE" @classmethod - def name(cls): + def name(cls) -> str: return 'sh' @classmethod - def file_extension(cls): + def file_extension(cls) -> str: return 'sh' @classmethod @@ -58,8 +60,8 @@ def get_syspaths(cls): return cls.syspaths @classmethod - def startup_capabilities(cls, rcfile=False, norc=False, stdin=False, - command=False): + def startup_capabilities(cls, rcfile: bool = False, norc: bool = False, stdin: bool = False, + command: bool = False): cls._unsupported_option('rcfile', rcfile) rcfile = False if command is not None: @@ -94,7 +96,7 @@ def get_startup_sequence(cls, rcfile, norc, stdin, command): bind_files=[], source_bind_files=False) - def _bind_interactive_rez(self): + def _bind_interactive_rez(self) -> None: if config.set_prompt and self.settings.prompt: self._addline(r'if [ -z "$REZ_STORED_PROMPT_SH" ]; then export REZ_STORED_PROMPT_SH="$PS1"; fi') if config.prefix_prompt: @@ -103,23 +105,23 @@ def _bind_interactive_rez(self): cmd = 'export PS1="$REZ_STORED_PROMPT_SH %s"' self._addline(cmd % r"\[\e[1m\]$REZ_ENV_PROMPT\[\e[0m\]") - def setenv(self, key, value): + def setenv(self, key, value) -> None: value = self.escape_string(value, is_path=self._is_pathed_key(key)) self._addline('export %s=%s' % (key, value)) - def unsetenv(self, key): + def unsetenv(self, key) -> None: self._addline("unset %s" % key) - def alias(self, key, value): + def alias(self, key, value) -> None: value = EscapedString.disallow(value) cmd = '{key}() {{ {value} "$@"; }};' self._addline(cmd.format(key=key, value=value)) - def source(self, value): + def source(self, value) -> None: value = self.escape_string(value) self._addline('. %s' % value) - def escape_string(self, value, is_path=False): + def escape_string(self, value, is_path: bool = False): value = EscapedString.promote(value) value = value.expanduser() result = '' @@ -139,7 +141,7 @@ def escape_string(self, value, is_path=False): result += txt return result - def _saferefenv(self, key): + def _saferefenv(self, key) -> None: pass diff --git a/src/rezplugins/shell/tcsh.py b/src/rezplugins/shell/tcsh.py index d39d4d7c8..11fa1fa6f 100644 --- a/src/rezplugins/shell/tcsh.py +++ b/src/rezplugins/shell/tcsh.py @@ -16,10 +16,10 @@ class TCSH(CSH): @classmethod - def name(cls): + def name(cls) -> str: return 'tcsh' - def escape_string(self, value, is_path=False): + def escape_string(self, value, is_path: bool = False): value = EscapedString.promote(value) value = value.expanduser() result = '' @@ -39,7 +39,7 @@ def escape_string(self, value, is_path=False): result += txt return result - def _bind_interactive_rez(self): + def _bind_interactive_rez(self) -> None: super(TCSH, self)._bind_interactive_rez() completion = os.path.join(module_root_path, "completion", "complete.csh") self.source(completion) diff --git a/src/rezplugins/shell/zsh.py b/src/rezplugins/shell/zsh.py index 364426144..ecbad127e 100644 --- a/src/rezplugins/shell/zsh.py +++ b/src/rezplugins/shell/zsh.py @@ -21,12 +21,12 @@ class Zsh(SH): histfile = "~/.zsh_history" @classmethod - def name(cls): + def name(cls) -> str: return 'zsh' @classmethod - def startup_capabilities(cls, rcfile=False, norc=False, stdin=False, - command=False): + def startup_capabilities(cls, rcfile: bool = False, norc: bool = False, stdin: bool = False, + command: bool = False): if norc: cls._overruled_option('rcfile', 'norc', rcfile) rcfile = False @@ -73,7 +73,7 @@ def get_startup_sequence(cls, rcfile, norc, stdin, command): source_bind_files=not norc ) - def _bind_interactive_rez(self): + def _bind_interactive_rez(self) -> None: if config.set_prompt and self.settings.prompt: self._addline(r'if [ -z "$REZ_STORED_PROMPT_SH" ]; then export REZ_STORED_PROMPT_SH="$PS1"; fi') if config.prefix_prompt: diff --git a/src/support/package_utils/set_authors.py b/src/support/package_utils/set_authors.py index 481ba5b94..62852eb55 100644 --- a/src/support/package_utils/set_authors.py +++ b/src/support/package_utils/set_authors.py @@ -6,7 +6,7 @@ import subprocess -def set_authors(data): +def set_authors(data) -> None: """Add 'authors' attribute based on repo contributions """ if "authors" in data: From adc763020c79c449abf7b65d673307a77d5557a9 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Thu, 26 Jun 2025 23:49:29 -0400 Subject: [PATCH 02/10] Regenerate baseline --- .github/workflows/mypy.yaml | 2 +- mypy-baseline.txt | 663 +++++++++++++++++++----------------- pyproject.toml | 2 +- src/rez/utils/memcached.py | 2 +- 4 files changed, 345 insertions(+), 324 deletions(-) diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index d69506dbc..62a77250b 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -43,7 +43,7 @@ jobs: - name: Run mypy run: >- echo "To update the baseline run:" - echo "mypy | mypy-baseline sync --ignore-categories=note" + echo "mypy | mypy-baseline sync --ignore-categories note syntax --ignore ".* is not subscriptable.*"" echo "then commit mypy-baseline.txt" echo mypy | mypy-baseline filter --ignore-categories=note --allow-unsynced diff --git a/mypy-baseline.txt b/mypy-baseline.txt index 0793f129a..c3f27d95e 100644 --- a/mypy-baseline.txt +++ b/mypy-baseline.txt @@ -1,23 +1,21 @@ -src/rez/utils/which.py:0: error: Incompatible types in assignment (expression has type "list[str] | Any", variable has type "str | None") [assignment] -src/rez/utils/which.py:0: error: Item "str" of "str | Any" has no attribute "insert" [union-attr] -src/rez/deprecations.py:0: error: Incompatible types in assignment (expression has type "Callable[[Any, Any, VarArg(Any), KwArg(Any)], str]", variable has type "Callable[[Warning | str, type[Warning], str, int, str | None], str]") [assignment] -src/rez/version/_version.py:0: error: Unsupported left operand type for < ("_Comparable") [operator] -src/rez/version/_version.py:0: error: Unsupported left operand type for < ("_Comparable") [operator] -src/rez/version/_version.py:0: error: Unsupported left operand type for < ("_Comparable") [operator] +src/rez/utils/which.py:0: error: Incompatible types in assignment (expression has type "Union[List[str], Any]", variable has type "Optional[str]") [assignment] +src/rez/utils/which.py:0: error: Item "str" of "Union[str, Any]" has no attribute "insert" [union-attr] +src/rez/deprecations.py:0: error: Incompatible types in assignment (expression has type "Callable[[Any, Any, VarArg(Any), KwArg(Any)], str]", variable has type "Callable[[Union[Warning, str], Type[Warning], str, int, Optional[str]], str]") [assignment] src/rez/version/_version.py:0: error: "object" has no attribute "value" [attr-defined] src/rez/version/_version.py:0: error: "object" has no attribute "value" [attr-defined] +src/rez/version/_version.py:0: error: Unsupported operand types for > ("VersionToken" and "object") [operator] src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "VersionToken"; supertype defines the argument type as "object" [override] src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] -src/rez/version/_version.py:0: error: Argument 2 to "map" has incompatible type "list[_SubToken] | None"; expected "Iterable[_SubToken]" [arg-type] +src/rez/version/_version.py:0: error: Argument 2 to "map" has incompatible type "Optional[List[_SubToken]]"; expected "Iterable[_SubToken]" [arg-type] src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "VersionToken"; supertype defines the argument type as "object" [override] src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] -src/rez/version/_version.py:0: error: Unsupported operand types for < ("list[_SubToken]" and "None") [operator] -src/rez/version/_version.py:0: error: Unsupported operand types for > ("list[_SubToken]" and "None") [operator] +src/rez/version/_version.py:0: error: Unsupported operand types for < ("List[_SubToken]" and "None") [operator] +src/rez/version/_version.py:0: error: Unsupported operand types for > ("List[_SubToken]" and "None") [operator] src/rez/version/_version.py:0: error: Unsupported left operand type for < ("None") [operator] -src/rez/version/_version.py:0: error: Value of type "list[_SubToken] | None" is not indexable [index] +src/rez/version/_version.py:0: error: Value of type "Optional[List[_SubToken]]" is not indexable [index] src/rez/version/_version.py:0: error: Incompatible return value type (got "None", expected "Version") [return-value] -src/rez/version/_version.py:0: error: Argument 2 to "map" has incompatible type "list[VersionToken] | None"; expected "Iterable[VersionToken]" [arg-type] +src/rez/version/_version.py:0: error: Argument 2 to "map" has incompatible type "Optional[List[VersionToken]]"; expected "Iterable[VersionToken]" [arg-type] src/rez/version/_version.py:0: error: "object" has no attribute "tokens" [attr-defined] src/rez/version/_version.py:0: error: "object" has no attribute "tokens" [attr-defined] src/rez/version/_version.py:0: error: Incompatible types in assignment (expression has type "Version", variable has type "None") [assignment] @@ -27,150 +25,164 @@ src/rez/version/_version.py:0: error: Incompatible types in assignment (expressi src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] src/rez/version/_version.py:0: error: Incompatible types in assignment (expression has type "_UpperBound", variable has type "None") [assignment] src/rez/version/_version.py:0: error: Argument 1 to "_UpperBound" has incompatible type "None"; expected "Version" [arg-type] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] src/rez/version/_version.py:0: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" [override] src/rez/version/_version.py:0: error: Unsupported operand types for == ("_LowerBound" and "None") [operator] src/rez/version/_version.py:0: error: Unsupported operand types for == ("_UpperBound" and "None") [operator] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "contains_version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "contains_version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "contains_version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "contains_version" [union-attr] +src/rez/version/_version.py:0: error: Unsupported operand types for <= ("_LowerBound" and "None") [operator] +src/rez/version/_version.py:0: error: Unsupported operand types for >= ("_LowerBound" and "None") [operator] src/rez/version/_version.py:0: error: Unsupported left operand type for <= ("None") [operator] +src/rez/version/_version.py:0: error: Unsupported operand types for >= ("_UpperBound" and "None") [operator] +src/rez/version/_version.py:0: error: Unsupported operand types for <= ("_UpperBound" and "None") [operator] src/rez/version/_version.py:0: error: Unsupported left operand type for >= ("None") [operator] -src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "max" cannot be "_LowerBound | None" [type-var] -src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "min" cannot be "_UpperBound | None" [type-var] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "max" cannot be "_LowerBound | None" [type-var] -src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "min" cannot be "_UpperBound | None" [type-var] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "max" cannot be "Optional[_LowerBound]" [type-var] +src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "min" cannot be "Optional[_UpperBound]" [type-var] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "max" cannot be "Optional[_LowerBound]" [type-var] +src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "min" cannot be "Optional[_UpperBound]" [type-var] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "inclusive" [union-attr] src/rez/version/_version.py:0: error: Incompatible types in assignment (expression has type "_Bound", variable has type "None") [assignment] src/rez/version/_version.py:0: error: Argument 1 to "append" of "list" has incompatible type "None"; expected "_Bound" [arg-type] src/rez/version/_version.py:0: error: Unsupported operand types for == ("_Bound" and "None") [operator] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_Bound | None" has no attribute "upper" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | Any | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "max" cannot be "_UpperBound | None" [type-var] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_LowerBound | None" has no attribute "inclusive" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "version" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_UpperBound | None" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: "_ContainsVersionIterator" expects no type arguments, but 1 given [type-arg] +src/rez/version/_version.py:0: error: "_ContainsVersionIterator" expects no type arguments, but 1 given [type-arg] +src/rez/version/_version.py:0: error: "_ContainsVersionIterator" expects no type arguments, but 1 given [type-arg] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_Bound]" has no attribute "upper" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Union[_UpperBound, Any, None]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Value of type variable "SupportsRichComparisonT" of "max" cannot be "Optional[_UpperBound]" [type-var] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_LowerBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "version" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_UpperBound]" has no attribute "inclusive" [union-attr] +src/rez/version/_version.py:0: error: "_ContainsVersionIterator" expects no type arguments, but 1 given [type-arg] src/rez/version/_version.py:0: error: Unsupported operand types for > ("int" and "None") [operator] -src/rez/version/_version.py:0: error: Invalid index type "int | None" for "list[_Bound]"; expected type "SupportsIndex" [index] -src/rez/version/_version.py:0: error: Item "None" of "_Bound | None" has no attribute "upper_bounded" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_Bound | None" has no attribute "version_containment" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_Bound | None" has no attribute "lower_bounded" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_Bound | None" has no attribute "version_containment" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_Bound | None" has no attribute "version_containment" [union-attr] -src/rez/version/_version.py:0: error: Item "None" of "_Bound | None" has no attribute "lower_bounded" [union-attr] -src/rez/version/_requirement.py:0: error: Incompatible return value type (got "str | None", expected "str") [return-value] -src/rez/version/_requirement.py:0: error: Incompatible return value type (got "Version | None", expected "Version") [return-value] +src/rez/version/_version.py:0: error: Invalid index type "Optional[int]" for "List[_Bound]"; expected type "SupportsIndex" [index] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_Bound]" has no attribute "upper_bounded" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_Bound]" has no attribute "version_containment" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_Bound]" has no attribute "lower_bounded" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_Bound]" has no attribute "version_containment" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_Bound]" has no attribute "version_containment" [union-attr] +src/rez/version/_version.py:0: error: Item "None" of "Optional[_Bound]" has no attribute "lower_bounded" [union-attr] +src/rez/version/_requirement.py:0: error: Incompatible return value type (got "Optional[str]", expected "str") [return-value] +src/rez/version/_requirement.py:0: error: Incompatible return value type (got "Optional[Version]", expected "Version") [return-value] src/rez/version/_requirement.py:0: error: Unsupported left operand type for + ("None") [operator] src/rez/version/_requirement.py:0: error: Unsupported left operand type for + ("None") [operator] -src/rez/version/_requirement.py:0: error: Incompatible return value type (got "str | None", expected "str") [return-value] -src/rez/version/_requirement.py:0: error: Incompatible return value type (got "VersionRange | None", expected "VersionRange") [return-value] -src/rez/version/_requirement.py:0: error: Item "None" of "VersionRange | None" has no attribute "issuperset" [union-attr] -src/rez/version/_requirement.py:0: error: Argument 1 to "issuperset" of "VersionRange" has incompatible type "VersionRange | None"; expected "VersionRange" [arg-type] -src/rez/version/_requirement.py:0: error: Item "None" of "VersionRange | None" has no attribute "issuperset" [union-attr] -src/rez/version/_requirement.py:0: error: Argument 1 to "issuperset" of "VersionRange" has incompatible type "VersionRange | None"; expected "VersionRange" [arg-type] -src/rez/version/_requirement.py:0: error: Item "None" of "VersionRange | None" has no attribute "intersects" [union-attr] -src/rez/version/_requirement.py:0: error: Argument 1 to "intersects" of "VersionRange" has incompatible type "VersionRange | None"; expected "VersionRange" [arg-type] -src/rez/version/_requirement.py:0: error: Unsupported operand types for in ("Version | None" and "VersionRange") [operator] -src/rez/version/_requirement.py:0: error: Unsupported right operand type for in ("VersionRange | None") [operator] -src/rez/version/_requirement.py:0: error: Unsupported operand types for in ("Version | None" and "VersionRange") [operator] -src/rez/version/_requirement.py:0: error: Unsupported right operand type for in ("VersionRange | None") [operator] +src/rez/version/_requirement.py:0: error: Incompatible return value type (got "Optional[str]", expected "str") [return-value] +src/rez/version/_requirement.py:0: error: Incompatible return value type (got "Optional[VersionRange]", expected "VersionRange") [return-value] +src/rez/version/_requirement.py:0: error: Item "None" of "Optional[VersionRange]" has no attribute "issuperset" [union-attr] +src/rez/version/_requirement.py:0: error: Argument 1 to "issuperset" of "VersionRange" has incompatible type "Optional[VersionRange]"; expected "VersionRange" [arg-type] +src/rez/version/_requirement.py:0: error: Item "None" of "Optional[VersionRange]" has no attribute "issuperset" [union-attr] +src/rez/version/_requirement.py:0: error: Argument 1 to "issuperset" of "VersionRange" has incompatible type "Optional[VersionRange]"; expected "VersionRange" [arg-type] +src/rez/version/_requirement.py:0: error: Item "None" of "Optional[VersionRange]" has no attribute "intersects" [union-attr] +src/rez/version/_requirement.py:0: error: Argument 1 to "intersects" of "VersionRange" has incompatible type "Optional[VersionRange]"; expected "VersionRange" [arg-type] +src/rez/version/_requirement.py:0: error: Unsupported operand types for in ("Optional[Version]" and "VersionRange") [operator] +src/rez/version/_requirement.py:0: error: Unsupported right operand type for in ("Optional[VersionRange]") [operator] +src/rez/version/_requirement.py:0: error: Unsupported operand types for in ("Optional[Version]" and "VersionRange") [operator] +src/rez/version/_requirement.py:0: error: Unsupported right operand type for in ("Optional[VersionRange]") [operator] src/rez/version/_requirement.py:0: error: Unsupported operand types for | ("VersionRange" and "None") [operator] src/rez/version/_requirement.py:0: error: Unsupported left operand type for | ("None") [operator] src/rez/version/_requirement.py:0: error: Unsupported operand types for - ("VersionRange" and "None") [operator] src/rez/version/_requirement.py:0: error: Unsupported left operand type for - ("None") [operator] src/rez/version/_requirement.py:0: error: Unsupported operand types for & ("VersionRange" and "None") [operator] src/rez/version/_requirement.py:0: error: Unsupported left operand type for & ("None") [operator] -src/rez/version/_requirement.py:0: error: Item "None" of "VersionRange | None" has no attribute "is_any" [union-attr] +src/rez/version/_requirement.py:0: error: Item "None" of "Optional[VersionRange]" has no attribute "is_any" [union-attr] src/rez/version/_requirement.py:0: error: Unsupported operand types for + ("str" and "None") [operator] src/rez/version/_requirement.py:0: error: Incompatible types in assignment (expression has type "str", variable has type "None") [assignment] src/rez/version/_requirement.py:0: error: Incompatible return value type (got "None", expected "str") [return-value] -src/rez/utils/data_utils.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "tuple[Any, ...]") [assignment] -src/rez/utils/data_utils.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "dict[str, Any]") [assignment] +src/rez/utils/data_utils.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "Tuple[Any, ...]") [assignment] +src/rez/utils/data_utils.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "Dict[str, Any]") [assignment] src/rez/utils/data_utils.py:0: error: Unexpected keyword argument "name" for "property" [call-arg] -src/rez/util.py:0: error: Argument 1 to "module_from_spec" has incompatible type "ModuleSpec | None"; expected "ModuleSpec" [arg-type] -src/rez/util.py:0: error: Item "None" of "ModuleSpec | None" has no attribute "loader" [union-attr] -src/rez/util.py:0: error: Item "None" of "Loader | Any | None" has no attribute "exec_module" [union-attr] -src/rez/utils/backcompat.py:0: error: Item "None" of "Match[str] | None" has no attribute "groupdict" [union-attr] -src/rez/utils/backcompat.py:0: error: Item "None" of "Match[str] | None" has no attribute "groupdict" [union-attr] +src/rez/utils/scope.py:0: error: Module "typing" has no attribute "Self" [attr-defined] +src/rez/util.py:0: error: Module "typing" has no attribute "TypeGuard" [attr-defined] +src/rez/util.py:0: error: Argument 1 to "module_from_spec" has incompatible type "Optional[ModuleSpec]"; expected "ModuleSpec" [arg-type] +src/rez/util.py:0: error: Item "None" of "Optional[ModuleSpec]" has no attribute "loader" [union-attr] +src/rez/util.py:0: error: Item "None" of "Union[Loader, Any, None]" has no attribute "exec_module" [union-attr] +src/rez/utils/backcompat.py:0: error: Item "None" of "Optional[Match[str]]" has no attribute "groupdict" [union-attr] +src/rez/utils/backcompat.py:0: error: Item "None" of "Optional[Match[str]]" has no attribute "groupdict" [union-attr] src/rez/__init__.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "Callable[[Any, Any], None]") [assignment] src/rez/__init__.py:0: error: Function "callback" could always be true in boolean context [truthy-function] -src/rez/utils/sourcecode.py:0: error: Item "None" of "Callable[[], T] | None" has no attribute "__name__" [union-attr] -src/rez/utils/sourcecode.py:0: error: Argument 1 to "getsourcelines" has incompatible type "Callable[[], T] | None"; expected Module | type[Any] | MethodType | FunctionType | TracebackType | FrameType | CodeType | Callable[..., Any] [arg-type] +src/rez/utils/sourcecode.py:0: error: Item "None" of "Optional[Callable[[], T]]" has no attribute "__name__" [union-attr] +src/rez/utils/sourcecode.py:0: error: Argument 1 to "getsourcelines" has incompatible type "Optional[Callable[[], T]]"; expected "Union[Module, Type[Any], MethodType, FunctionType, TracebackType, FrameType, CodeType, Callable[..., Any]]" [arg-type] src/rez/utils/sourcecode.py:0: error: "PackageBaseResourceWrapper" has no attribute "base" [attr-defined] src/rez/utils/platform_.py:0: error: Module has no attribute "windll" [attr-defined] src/rez/utils/platform_.py:0: error: Module has no attribute "WinError" [attr-defined] src/rez/utils/platform_.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "Platform") [assignment] -src/rez/utils/filesystem.py:0: error: Argument 2 to "chmod" has incompatible type "int | None"; expected "int" [arg-type] +src/rez/utils/filesystem.py:0: error: Argument 2 to "chmod" has incompatible type "Optional[int]"; expected "int" [arg-type] src/rez/utils/filesystem.py:0: error: Module has no attribute "WindowsError" [attr-defined] +src/rez/utils/filesystem.py:0: error: Incompatible types in assignment (expression has type "bytes", variable has type "str") [assignment] +src/rez/utils/filesystem.py:0: error: Argument 1 to "ord" has incompatible type "int"; expected "Union[str, bytes, bytearray]" [arg-type] src/rez/utils/filesystem.py:0: error: Name "unicode" is not defined [name-defined] src/rez/utils/filesystem.py:0: error: Name "unicode" is not defined [name-defined] src/rez/utils/filesystem.py:0: error: Name "xrange" is not defined [name-defined] -src/rez/config.py:0: error: Incompatible return value type (got "list[str] | None", expected "list[str]") [return-value] -src/rez/rex.py:0: error: Item "None" of "ActionManager | None" has no attribute "environ" [union-attr] +src/rez/config.py:0: error: Incompatible return value type (got "Optional[List[str]]", expected "List[str]") [return-value] +src/rez/rex.py:0: error: Item "None" of "Optional[ActionManager]" has no attribute "environ" [union-attr] src/rez/rex.py:0: error: Signature of "format" incompatible with supertype "Formatter" [override] +src/rez/plugin_managers.py:0: error: List item 0 has incompatible type "MutableSequence[str]"; expected "str" [list-item] +src/rez/plugin_managers.py:0: error: Item "MetaPathFinderProtocol" of "Union[MetaPathFinderProtocol, PathEntryFinderProtocol]" has no attribute "find_module" [union-attr] +src/rez/plugin_managers.py:0: error: Item "PathEntryFinderProtocol" of "Union[MetaPathFinderProtocol, PathEntryFinderProtocol]" has no attribute "find_module" [union-attr] +src/rez/plugin_managers.py:0: error: Value of type variable "AnyOrLiteralStr" of "dirname" cannot be "Optional[str]" [type-var] +src/rez/plugin_managers.py:0: error: Item "MetaPathFinderProtocol" of "Union[MetaPathFinderProtocol, PathEntryFinderProtocol]" has no attribute "path" [union-attr] +src/rez/plugin_managers.py:0: error: Item "PathEntryFinderProtocol" of "Union[MetaPathFinderProtocol, PathEntryFinderProtocol]" has no attribute "path" [union-attr] +src/rez/plugin_managers.py:0: error: Argument 1 to "append" of "list" has incompatible type "Tuple[str, str, Union[Any, str], str]"; expected "List[str]" [arg-type] +src/rez/plugin_managers.py:0: error: Argument 1 to "append" of "list" has incompatible type "Tuple[str, str, str, str]"; expected "List[str]" [arg-type] +src/rez/utils/resources.py:0: error: Module "typing" has no attribute "Self" [attr-defined] src/rez/utils/resources.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] src/rez/utils/memcached.py:0: error: "Callable[[VarArg(Any), KwArg(Any)], Any]" has no attribute "forget" [attr-defined] src/rez/utils/memcached.py:0: error: "Callable[[VarArg(Any), KwArg(Any)], Any]" has no attribute "__wrapped__" [attr-defined] -src/rez/utils/amqp.py:0: error: Incompatible types in assignment (expression has type "PlainCredentials", target has type "dict[str, str]") [assignment] +src/rez/utils/amqp.py:0: error: Incompatible types in assignment (expression has type "PlainCredentials", target has type "Dict[str, str]") [assignment] +src/rez/shells.py:0: error: Module "typing" has no attribute "Self" [attr-defined] src/rez/shells.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] src/rez/shells.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] src/rez/shells.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] src/rez/shells.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] -src/rez/shells.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "list[str]") [assignment] -src/rez/plugin_managers.py:0: error: List item 0 has incompatible type "MutableSequence[str]"; expected "str" [list-item] -src/rez/plugin_managers.py:0: error: Item "MetaPathFinderProtocol" of "MetaPathFinderProtocol | PathEntryFinderProtocol" has no attribute "find_module" [union-attr] -src/rez/plugin_managers.py:0: error: Item "PathEntryFinderProtocol" of "MetaPathFinderProtocol | PathEntryFinderProtocol" has no attribute "find_module" [union-attr] -src/rez/plugin_managers.py:0: error: Value of type variable "AnyOrLiteralStr" of "dirname" cannot be "str | None" [type-var] -src/rez/plugin_managers.py:0: error: Item "MetaPathFinderProtocol" of "MetaPathFinderProtocol | PathEntryFinderProtocol" has no attribute "path" [union-attr] -src/rez/plugin_managers.py:0: error: Item "PathEntryFinderProtocol" of "MetaPathFinderProtocol | PathEntryFinderProtocol" has no attribute "path" [union-attr] -src/rez/plugin_managers.py:0: error: Argument 1 to "append" of "list" has incompatible type "tuple[str, str, Any | str, str]"; expected "list[str]" [arg-type] -src/rez/plugin_managers.py:0: error: Argument 1 to "append" of "list" has incompatible type "tuple[str, str, str, str]"; expected "list[str]" [arg-type] -src/rez/package_resources.py:0: error: Incompatible types in assignment (expression has type "type[PackageMetadataError]", base class "Resource" defined the type as "type[Exception]") [assignment] -src/rez/package_resources.py:0: error: Argument 1 to "get_resource" of "PackageRepository" has incompatible type "None"; expected "str" [arg-type] +src/rez/shells.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "List[str]") [assignment] +src/rez/package_resources.py:0: error: Incompatible types in assignment (expression has type "Type[PackageMetadataError]", base class "Resource" defined the type as "Type[Exception]") [assignment] +src/rez/package_resources.py:0: error: Argument 1 to "get_resource" of "PackageRepository" has incompatible type "None"; expected "Union[str, Type[Resource]]" [arg-type] src/rez/package_resources.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "VariantResourceHelper") [misc] src/rez/package_resources.py:0: error: "PackageRepositoryResource" has no attribute "hashed_variants" [attr-defined] src/rez/package_resources.py:0: error: "VariantResourceHelper" has no attribute "base" [attr-defined] @@ -179,53 +191,56 @@ src/rez/package_resources.py:0: error: "VariantResourceHelper" has no attribute src/rez/package_resources.py:0: error: "VariantResourceHelper" has no attribute "base" [attr-defined] src/rez/package_resources.py:0: error: "VariantResourceHelper" has no attribute "base" [attr-defined] src/rez/package_resources.py:0: error: "VariantResourceHelper" has no attribute "base" [attr-defined] -src/rez/package_resources.py:0: error: Argument 2 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/package_resources.py:0: error: Argument 2 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] src/rez/package_resources.py:0: error: "PackageRepositoryResource" has no attribute "variants" [attr-defined] -src/rez/serialise.py:0: error: Incompatible default for argument "filepath" (default has type "None", argument has type "str") [assignment] -src/rez/serialise.py:0: error: Incompatible default for argument "filepath" (default has type "None", argument has type "str") [assignment] -src/rez/serialise.py:0: error: Incompatible types in assignment (expression has type "list[FrameSummary]", variable has type "StackSummary") [assignment] -src/rez/serialise.py:0: error: Unsupported right operand type for in (Module) [operator] -src/rez/serialise.py:0: error: Value of type Module is not indexable [index] -src/rez/serialise.py:0: error: Incompatible default for argument "filepath" (default has type "None", argument has type "str") [assignment] -src/rez/serialise.py:0: error: Incompatible default for argument "filepath" (default has type "None", argument has type "str") [assignment] -src/rez/serialise.py:0: error: "Callable[[str, Any, Any], Any]" has no attribute "forget" [attr-defined] src/rez/package_repository.py:0: error: "PackageFamilyResource" has no attribute "iter_packages" [attr-defined] -src/rez/package_repository.py:0: error: Incompatible types in assignment (expression has type "tuple[str, str]", variable has type "list[str]") [assignment] +src/rez/package_repository.py:0: error: Argument 1 to "make_resource_handle" of "PackageRepository" has incompatible type "Union[str, Type[Resource]]"; expected "str" [arg-type] +src/rez/package_repository.py:0: error: Incompatible types in assignment (expression has type "Tuple[str, str]", variable has type "List[str]") [assignment] src/rezplugins/package_repository/memory.py:0: error: "PackageRepository" has no attribute "data" [attr-defined] src/rezplugins/package_repository/memory.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "MemoryPackageResource") [misc] src/rezplugins/package_repository/memory.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "MemoryPackageResource") [misc] src/rezplugins/package_repository/memory.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageResourceHelper" defined the type as "None") [assignment] -src/rezplugins/package_repository/memory.py:0: error: Argument 1 to "construct" of "VersionedObject" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/memory.py:0: error: Argument 1 to "construct" of "VersionedObject" has incompatible type "Optional[str]"; expected "str" [arg-type] src/rezplugins/package_repository/memory.py:0: error: Incompatible return value type (got "Resource", expected "PackageRepositoryResource") [return-value] src/rezplugins/package_repository/memory.py:0: error: "PackageRepository" has no attribute "data" [attr-defined] src/rezplugins/package_repository/memory.py:0: error: Incompatible return value type (got "Resource", expected "PackageRepositoryResource") [return-value] -src/rezplugins/package_repository/memory.py:0: error: Incompatible return value type (got "Resource", expected "MemoryPackageFamilyResource | None") [return-value] -src/rezplugins/package_repository/memory.py:0: error: Return type "Iterator[MemoryPackageFamilyResource | None]" of "iter_package_families" incompatible with return type "Iterator[PackageFamilyResource]" in supertype "PackageRepository" [override] +src/rezplugins/package_repository/memory.py:0: error: Incompatible return value type (got "Resource", expected "Optional[MemoryPackageFamilyResource]") [return-value] +src/rezplugins/package_repository/memory.py:0: error: Return type "Iterator[Optional[MemoryPackageFamilyResource]]" of "iter_package_families" incompatible with return type "Iterator[PackageFamilyResource]" in supertype "PackageRepository" [override] src/rezplugins/package_repository/memory.py:0: error: Argument 1 of "iter_packages" is incompatible with supertype "PackageRepository"; supertype defines the argument type as "PackageFamilyResource" [override] src/rezplugins/package_repository/memory.py:0: error: Argument 1 of "iter_variants" is incompatible with supertype "PackageRepository"; supertype defines the argument type as "PackageResource" [override] src/rezplugins/package_repository/memory.py:0: error: Incompatible return value type (got "PackageRepositoryResource", expected "PackageFamilyResource") [return-value] -src/rez/packages.py:0: error: Item "None" of "dict[str, Any] | None" has no attribute "items" [union-attr] -src/rez/packages.py:0: error: Incompatible types in assignment (expression has type "set[str]", base class "ResourceWrapper" defined the type as "None") [assignment] +src/rez/serialise.py:0: error: Incompatible default for argument "filepath" (default has type "None", argument has type "str") [assignment] +src/rez/serialise.py:0: error: Incompatible default for argument "filepath" (default has type "None", argument has type "str") [assignment] +src/rez/serialise.py:0: error: Incompatible types in assignment (expression has type "List[FrameSummary]", variable has type "StackSummary") [assignment] +src/rez/serialise.py:0: error: Unsupported right operand type for in (Module) [operator] +src/rez/serialise.py:0: error: Value of type Module is not indexable [index] +src/rez/serialise.py:0: error: Incompatible default for argument "filepath" (default has type "None", argument has type "str") [assignment] +src/rez/serialise.py:0: error: Incompatible default for argument "filepath" (default has type "None", argument has type "str") [assignment] +src/rez/serialise.py:0: error: "Callable[[str, Any, Any], Any]" has no attribute "forget" [attr-defined] +src/rez/packages.py:0: error: Item "None" of "Optional[Dict[str, Any]]" has no attribute "items" [union-attr] +src/rez/packages.py:0: error: Incompatible types in assignment (expression has type "Set[str]", base class "ResourceWrapper" defined the type as "None") [assignment] src/rez/packages.py:0: error: Argument 1 to "iter_packages" of "PackageRepository" has incompatible type "Resource"; expected "PackageFamilyResource" [arg-type] src/rez/packages.py:0: error: "Resource" has no attribute "uri" [attr-defined] src/rez/packages.py:0: error: "Resource" has no attribute "config" [attr-defined] -src/rez/packages.py:0: error: Item "None" of "dict[str, Any] | None" has no attribute "get" [union-attr] +src/rez/packages.py:0: error: Item "None" of "Optional[Dict[str, Any]]" has no attribute "get" [union-attr] src/rez/packages.py:0: error: "PackageBaseResourceWrapper" has no attribute "parent" [attr-defined] -src/rez/packages.py:0: error: Incompatible types in assignment (expression has type "set[str]", base class "ResourceWrapper" defined the type as "None") [assignment] -src/rez/packages.py:0: error: Unsupported right operand type for in ("dict[str, Any] | None") [operator] -src/rez/packages.py:0: error: Value of type "dict[str, Any] | None" is not indexable [index] -src/rez/packages.py:0: error: Item "None" of "dict[str, Any] | None" has no attribute "keys" [union-attr] +src/rez/packages.py:0: error: Incompatible types in assignment (expression has type "Set[str]", base class "ResourceWrapper" defined the type as "None") [assignment] +src/rez/packages.py:0: error: Unsupported right operand type for in ("Optional[Dict[str, Any]]") [operator] +src/rez/packages.py:0: error: Value of type "Optional[Dict[str, Any]]" is not indexable [index] +src/rez/packages.py:0: error: Item "None" of "Optional[Dict[str, Any]]" has no attribute "keys" [union-attr] src/rez/packages.py:0: error: Cannot determine type of "keys" [has-type] src/rez/packages.py:0: error: Argument 1 to "get_parent_package_family" of "PackageRepository" has incompatible type "Resource"; expected "PackageResourceHelper" [arg-type] -src/rez/packages.py:0: error: Item "None" of "dict[str, Any] | None" has no attribute "get" [union-attr] +src/rez/packages.py:0: error: Item "None" of "Optional[Dict[str, Any]]" has no attribute "get" [union-attr] src/rez/packages.py:0: error: Argument 1 to "iter_variants" of "PackageRepository" has incompatible type "Resource"; expected "PackageResource" [arg-type] -src/rez/packages.py:0: error: Incompatible types in assignment (expression has type "set[str]", base class "ResourceWrapper" defined the type as "None") [assignment] +src/rez/packages.py:0: error: Incompatible types in assignment (expression has type "Set[str]", base class "ResourceWrapper" defined the type as "None") [assignment] src/rez/packages.py:0: error: Cannot determine type of "keys" [has-type] src/rez/packages.py:0: error: Argument 1 to "get_parent_package" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] src/rez/packages.py:0: error: Argument 1 to "Package" has incompatible type "PackageRepositoryResource"; expected "PackageResource" [arg-type] src/rez/packages.py:0: error: Argument 1 to "install_variant" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] src/rez/packages.py:0: error: "Resource" has no attribute "_subpath" [attr-defined] +src/rez/packages.py:0: error: Argument 1 to "get_resource_from_handle" of "PackageRepositoryManager" has incompatible type "Union[ResourceHandle, Dict[Any, Any]]"; expected "ResourceHandle" [arg-type] src/rez/packages.py:0: error: Argument 1 to "Package" has incompatible type "Resource"; expected "PackageResource" [arg-type] +src/rez/packages.py:0: error: Argument 1 to "get_resource_from_handle" of "PackageRepositoryManager" has incompatible type "Union[ResourceHandle, Dict[Any, Any]]"; expected "ResourceHandle" [arg-type] src/rez/packages.py:0: error: Argument 1 to "Variant" has incompatible type "Resource"; expected "VariantResource" [arg-type] src/rez/packages.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] src/rez/packages.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] @@ -243,46 +258,48 @@ src/rez/solver.py:0: error: Argument 1 of "__eq__" is incompatible with supertyp src/rez/solver.py:0: error: Unsupported operand types for < ("int" and "None") [operator] src/rez/solver.py:0: error: Unsupported operand types for > ("int" and "None") [operator] src/rez/solver.py:0: error: Unsupported left operand type for < ("None") [operator] -src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "tuple[int, list[tuple[int, SupportsLessThan]], int, list[tuple[SupportsLessThan, str]], int | None]", variable has type "tuple[list[tuple[int, SupportsLessThan]], int, list[tuple[SupportsLessThan, str]], int | None]") [assignment] -src/rez/solver.py:0: error: Incompatible return value type (got "tuple[list[tuple[int, SupportsLessThan]], int, list[tuple[SupportsLessThan, str]], int | None]", expected "tuple[SupportsLessThan, ...]") [return-value] -src/rez/solver.py:0: error: Incompatible return value type (got "set[str] | None", expected "set[str]") [return-value] -src/rez/solver.py:0: error: Item "None" of "Requirement | None" has no attribute "range" [union-attr] -src/rez/solver.py:0: error: Item "None" of "Requirement | None" has no attribute "range" [union-attr] -src/rez/solver.py:0: error: Item "None" of "Requirement | None" has no attribute "range" [union-attr] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "Tuple[int, List[Tuple[int, SupportsLessThan]], int, List[Tuple[SupportsLessThan, str]], Optional[int]]", variable has type "Tuple[List[Tuple[int, SupportsLessThan]], int, List[Tuple[SupportsLessThan, str]], Optional[int]]") [assignment] +src/rez/solver.py:0: error: Incompatible return value type (got "Tuple[List[Tuple[int, SupportsLessThan]], int, List[Tuple[SupportsLessThan, str]], Optional[int]]", expected "Tuple[SupportsLessThan, ...]") [return-value] +src/rez/solver.py:0: error: Incompatible return value type (got "Optional[Set[str]]", expected "Set[str]") [return-value] +src/rez/solver.py:0: error: Item "None" of "Optional[Requirement]" has no attribute "range" [union-attr] +src/rez/solver.py:0: error: Item "None" of "Optional[Requirement]" has no attribute "range" [union-attr] +src/rez/solver.py:0: error: Item "None" of "Optional[Requirement]" has no attribute "range" [union-attr] src/rez/solver.py:0: error: Expected iterable as variadic argument [misc] -src/rez/solver.py:0: error: Item "None" of "_PackageVariantSlice | None" has no attribute "intersect" [union-attr] -src/rez/solver.py:0: error: Item "None" of "_PackageVariantSlice | None" has no attribute "extract" [union-attr] -src/rez/solver.py:0: error: Argument 1 to "len" has incompatible type "_PackageVariantSlice | None"; expected "Sized" [arg-type] -src/rez/solver.py:0: error: Item "None" of "_PackageVariantSlice | None" has no attribute "split" [union-attr] -src/rez/solver.py:0: error: Argument 1 to "len" has incompatible type "_PackageVariantSlice | None"; expected "Sized" [arg-type] -src/rez/solver.py:0: error: Item "None" of "_PackageVariantSlice | None" has no attribute "extractable" [union-attr] -src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "_PackageScope | None", variable has type "_PackageScope") [assignment] -src/rez/solver.py:0: error: Item "None" of "PackageVariant | None" has no attribute "version" [union-attr] -src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "dict_values[Any, Any]", variable has type "list[Any]") [assignment] -src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "dict_values[str, _PackageScope]", variable has type "list[_PackageScope]") [assignment] -src/rez/solver.py:0: error: Argument 1 to "_get_dependency_order" has incompatible type "digraph | None"; expected "digraph" [arg-type] -src/rez/solver.py:0: error: Argument 1 to "_add_request_node" has incompatible type "Requirement | None"; expected "Requirement" [arg-type] -src/rez/solver.py:0: error: Item "None" of "_ResolvePhase | None" has no attribute "get_graph" [union-attr] +src/rez/solver.py:0: error: Item "None" of "Optional[_PackageVariantSlice]" has no attribute "intersect" [union-attr] +src/rez/solver.py:0: error: Item "None" of "Optional[_PackageVariantSlice]" has no attribute "extract" [union-attr] +src/rez/solver.py:0: error: Argument 1 to "len" has incompatible type "Optional[_PackageVariantSlice]"; expected "Sized" [arg-type] +src/rez/solver.py:0: error: Item "None" of "Optional[_PackageVariantSlice]" has no attribute "split" [union-attr] +src/rez/solver.py:0: error: Argument 1 to "len" has incompatible type "Optional[_PackageVariantSlice]"; expected "Sized" [arg-type] +src/rez/solver.py:0: error: Item "None" of "Optional[_PackageVariantSlice]" has no attribute "extractable" [union-attr] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "Optional[_PackageScope]", variable has type "_PackageScope") [assignment] +src/rez/solver.py:0: error: Item "None" of "Optional[PackageVariant]" has no attribute "version" [union-attr] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "dict_values[Any, Any]", variable has type "List[Any]") [assignment] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "dict_values[str, _PackageScope]", variable has type "List[_PackageScope]") [assignment] +src/rez/solver.py:0: error: Argument 1 to "_get_dependency_order" has incompatible type "Optional[digraph]"; expected "digraph" [arg-type] +src/rez/solver.py:0: error: Argument 1 to "_add_request_node" has incompatible type "Optional[Requirement]"; expected "Requirement" [arg-type] +src/rez/solver.py:0: error: Item "None" of "Optional[_ResolvePhase]" has no attribute "get_graph" [union-attr] +src/rez/package_order.py:0: error: Module "typing" has no attribute "Self" [attr-defined] src/rez/package_order.py:0: error: "object" has no attribute "main_comparable" [attr-defined] src/rez/package_order.py:0: error: "object" has no attribute "fallback_comparable" [attr-defined] src/rez/package_order.py:0: error: "object" has no attribute "main_comparable" [attr-defined] src/rez/package_order.py:0: error: "object" has no attribute "fallback_comparable" [attr-defined] -src/rez/package_order.py:0: error: Argument 1 to "sort_key" of "PackageOrder" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/package_order.py:0: error: Argument 1 to "sort_key" of "PackageOrder" has incompatible type "Optional[str]"; expected "str" [arg-type] src/rez/package_order.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageOrder" defined the type as "None") [assignment] src/rez/package_order.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageOrder" defined the type as "None") [assignment] src/rez/package_order.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageOrder" defined the type as "None") [assignment] src/rez/package_order.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageOrder" defined the type as "None") [assignment] src/rez/package_order.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageOrder" defined the type as "None") [assignment] -src/rez/package_order.py:0: error: Value of type "list[VersionToken] | None" is not indexable [index] -src/rez/package_maker.py:0: error: Argument 1 to "iter_packages" of "MemoryPackageRepository" has incompatible type "MemoryPackageFamilyResource | None"; expected "MemoryPackageFamilyResource" [arg-type] -src/rez/package_maker.py:0: error: Incompatible types in assignment (expression has type "Iterator[Variant]", variable has type "list[Variant]") [assignment] -src/rez/package_maker.py:0: error: Item "None" of "Variant | None" has no attribute "base" [union-attr] -src/rez/package_maker.py:0: error: Argument 1 has incompatible type "Variant | None"; expected "Variant" [arg-type] -src/rez/package_maker.py:0: error: Item "None" of "Variant | None" has no attribute "root" [union-attr] -src/rez/package_maker.py:0: error: Argument 1 has incompatible type "Variant | None"; expected "Variant" [arg-type] -src/rez/package_filter.py:0: error: Argument 1 to "add_exclusion" of "PackageFilter" has incompatible type "list[Rule]"; expected "Rule" [arg-type] -src/rez/package_filter.py:0: error: Argument 1 to "add_inclusion" of "PackageFilter" has incompatible type "list[Rule]"; expected "Rule" [arg-type] -src/rez/package_filter.py:0: error: Incompatible return value type (got "tuple[str | None, list[Rule]]", expected "tuple[str, list[Rule]]") [return-value] +src/rez/package_order.py:0: error: Value of type "Optional[List[VersionToken]]" is not indexable [index] +src/rez/package_maker.py:0: error: Argument 1 to "iter_packages" of "MemoryPackageRepository" has incompatible type "Optional[MemoryPackageFamilyResource]"; expected "MemoryPackageFamilyResource" [arg-type] +src/rez/package_maker.py:0: error: Incompatible types in assignment (expression has type "Iterator[Variant]", variable has type "List[Variant]") [assignment] +src/rez/package_maker.py:0: error: Item "None" of "Optional[Variant]" has no attribute "base" [union-attr] +src/rez/package_maker.py:0: error: Argument 1 has incompatible type "Optional[Variant]"; expected "Variant" [arg-type] +src/rez/package_maker.py:0: error: Item "None" of "Optional[Variant]" has no attribute "root" [union-attr] +src/rez/package_maker.py:0: error: Argument 1 has incompatible type "Optional[Variant]"; expected "Variant" [arg-type] +src/rez/package_filter.py:0: error: Module "typing" has no attribute "Self" [attr-defined] +src/rez/package_filter.py:0: error: Argument 1 to "add_exclusion" of "PackageFilter" has incompatible type "List[Rule]"; expected "Rule" [arg-type] +src/rez/package_filter.py:0: error: Argument 1 to "add_inclusion" of "PackageFilter" has incompatible type "List[Rule]"; expected "Rule" [arg-type] +src/rez/package_filter.py:0: error: Incompatible return value type (got "Tuple[Optional[str], List[Rule]]", expected "Tuple[str, List[Rule]]") [return-value] src/rez/package_filter.py:0: error: "Rule" has no attribute "_family"; maybe "family"? [attr-defined] src/rez/package_filter.py:0: error: Too many arguments for "RegexRuleBase" [call-arg] src/rez/package_filter.py:0: error: Incompatible types in assignment (expression has type "str", base class "Rule" defined the type as "None") [assignment] @@ -290,72 +307,75 @@ src/rez/package_filter.py:0: error: Incompatible types in assignment (expression src/rez/package_filter.py:0: error: Incompatible types in assignment (expression has type "str", base class "Rule" defined the type as "None") [assignment] src/rez/package_filter.py:0: error: Cannot determine type of "name" [has-type] src/rez/package_filter.py:0: error: Incompatible types in assignment (expression has type "str", base class "Rule" defined the type as "None") [assignment] -src/rez/package_cache.py:0: error: Value of type "dict[str, Any] | None" is not indexable [index] +src/rez/package_cache.py:0: error: Value of type "Optional[Dict[str, Any]]" is not indexable [index] src/rez/package_cache.py:0: error: Module has no attribute "CREATE_NEW_PROCESS_GROUP" [attr-defined] src/rez/developer_package.py:0: error: Unsupported left operand type for | ("None") [operator] -src/rez/developer_package.py:0: error: Argument 1 to "from_path" of "DeveloperPackage" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/developer_package.py:0: error: Argument 1 to "from_path" of "DeveloperPackage" has incompatible type "Optional[str]"; expected "str" [arg-type] src/rez/resolver.py:0: error: Name "digraph" is not defined [name-defined] src/rez/resolver.py:0: error: Name "digraph" is not defined [name-defined] src/rez/resolver.py:0: error: Name "ResourceHandle" is not defined [name-defined] +src/rez/resolver.py:0: error: Incompatible return value type (got "Tuple[str, Union[Any, _Miss]]", expected "Tuple[str, Tuple[SolverDict, Dict[Any, Any], Dict[Any, Any]]]") [return-value] src/rez/resolver.py:0: error: Argument 1 to "get_variant_state_handle" of "PackageRepository" has incompatible type "Resource"; expected "PackageResource" [arg-type] -src/rez/resolver.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/resolver.py:0: error: Argument 1 to "get_variant_state_handle" of "PackageRepository" has incompatible type "Resource | Any"; expected "PackageResource" [arg-type] -src/rez/resolver.py:0: error: Incompatible types in assignment (expression has type "ResolverStatus | None", variable has type "ResolverStatus") [assignment] -src/rez/resolver.py:0: error: Item "None" of "list[PackageVariant] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/resolver.py:0: error: Item "None" of "list[Requirement] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/resolved_context.py:0: error: Argument "timestamp" to "Resolver" has incompatible type "float | None"; expected "int | None" [arg-type] -src/rez/resolved_context.py:0: error: Incompatible types in assignment (expression has type "float | None", variable has type "float") [assignment] -src/rez/resolved_context.py:0: error: Incompatible types in assignment (expression has type "float | None", variable has type "float") [assignment] -src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolver.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolver.py:0: error: Argument 1 to "get_variant_state_handle" of "PackageRepository" has incompatible type "Union[Resource, Any]"; expected "PackageResource" [arg-type] +src/rez/resolver.py:0: error: Incompatible types in assignment (expression has type "Optional[ResolverStatus]", variable has type "ResolverStatus") [assignment] +src/rez/resolver.py:0: error: Item "None" of "Optional[List[PackageVariant]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolver.py:0: error: Item "None" of "Optional[List[Requirement]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Argument "timestamp" to "Resolver" has incompatible type "Optional[float]"; expected "Optional[int]" [arg-type] +src/rez/resolved_context.py:0: error: Incompatible types in assignment (expression has type "Optional[float]", variable has type "float") [assignment] +src/rez/resolved_context.py:0: error: Incompatible types in assignment (expression has type "Optional[float]", variable has type "float") [assignment] +src/rez/resolved_context.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] src/rez/resolved_context.py:0: error: Argument 1 to "get_equivalent_variant" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] -src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/resolved_context.py:0: error: Item "None" of "list[PackageRequest] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "Optional[List[PackageRequest]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] src/rez/resolved_context.py:0: error: Incompatible types in assignment (expression has type "str", variable has type "PackageRequest") [assignment] -src/rez/resolved_context.py:0: error: Incompatible return value type (got "list[Requirement | PackageRequest]", expected "list[Requirement | PackageRequest | str]") [return-value] -src/rez/resolved_context.py:0: error: Argument 1 to "read_graph_from_string" has incompatible type "str | None"; expected "str" [arg-type] -src/rez/resolved_context.py:0: error: Argument 1 to "write_dot" has incompatible type "digraph | None"; expected "digraph" [arg-type] -src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/resolved_context.py:0: error: Item "None" of "list[Requirement] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/resolved_context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/resolved_context.py:0: error: Incompatible types in string interpolation (expression has type "str", placeholder has type "int | float | SupportsInt") [str-format] -src/rez/resolved_context.py:0: error: Incompatible types in assignment (expression has type "list[PackageOrder]", variable has type "PackageOrderList | None") [assignment] -src/rez/resolved_context.py:0: error: Argument 1 to "update" of "MutableMapping" has incompatible type "dict[str, Any] | None"; expected "SupportsKeysAndGetItem[str, Any]" [arg-type] -src/rez/resolved_context.py:0: error: Item "_NeverError" of "SourceCodeError | _NeverError" has no attribute "short_msg" [union-attr] -src/rez/wrapper.py:0: error: Incompatible types in assignment (expression has type "Callable[[Any], Any] | None", variable has type "Callable[[Any], Any]") [assignment] -src/rez/suite.py:0: error: Item "None" of "defaultdict[str, list[Tool]] | None" has no attribute "get" [union-attr] -src/rez/suite.py:0: error: Item "None" of "list[Tool] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/suite.py:0: error: Item "None" of "defaultdict[str, list[Tool]] | None" has no attribute "values" [union-attr] -src/rez/suite.py:0: error: Argument 1 to "append" of "list" has incompatible type "tuple[str, str, str, str, str]"; expected "list[str]" [arg-type] +src/rez/resolved_context.py:0: error: Incompatible return value type (got "List[Union[Requirement, PackageRequest]]", expected "List[Union[Requirement, PackageRequest, str]]") [return-value] +src/rez/resolved_context.py:0: error: Argument 1 to "read_graph_from_string" has incompatible type "Optional[str]"; expected "str" [arg-type] +src/rez/resolved_context.py:0: error: Argument 1 to "write_dot" has incompatible type "Optional[digraph]"; expected "digraph" [arg-type] +src/rez/resolved_context.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "Optional[List[Requirement]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Argument 1 to "join" of "Shell" has incompatible type "Union[str, Sequence[str], None]"; expected "Iterable[str]" [arg-type] +src/rez/resolved_context.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/resolved_context.py:0: error: Incompatible types in string interpolation (expression has type "str", placeholder has type "Union[int, float, SupportsInt]") [str-format] +src/rez/resolved_context.py:0: error: Argument 1 to "update" of "MutableMapping" has incompatible type "Optional[Dict[str, Any]]"; expected "SupportsKeysAndGetItem[str, Any]" [arg-type] +src/rez/resolved_context.py:0: error: Item "_NeverError" of "Union[SourceCodeError, _NeverError]" has no attribute "short_msg" [union-attr] +src/rez/wrapper.py:0: error: Incompatible types in assignment (expression has type "Optional[Callable[[Any], Any]]", variable has type "Callable[[Any], Any]") [assignment] +src/rez/suite.py:0: error: Item "None" of "Optional[defaultdict[str, List[Tool]]]" has no attribute "get" [union-attr] +src/rez/suite.py:0: error: Item "None" of "Optional[List[Tool]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/suite.py:0: error: Item "None" of "Optional[defaultdict[str, List[Tool]]]" has no attribute "values" [union-attr] +src/rez/suite.py:0: error: Argument 1 to "append" of "list" has incompatible type "Tuple[str, str, str, str, str]"; expected "List[str]" [arg-type] src/rez/suite.py:0: error: Argument 1 to "append" of "list" has incompatible type "Callable[[Any], Any]"; expected "None" [arg-type] src/rez/suite.py:0: error: Argument 1 to "append" of "list" has incompatible type "Callable[[Any], Any]"; expected "None" [arg-type] src/rezplugins/shell/cmd.py:0: error: Signature of "spawn_shell" incompatible with supertype "Shell" [override] src/rezplugins/shell/cmd.py:0: error: "setenv" of "CMD" does not return a value (it only ever returns None) [func-returns-value] src/rezplugins/shell/_utils/powershell_base.py:0: error: Signature of "spawn_shell" incompatible with supertype "Shell" [override] src/rezplugins/shell/_utils/powershell_base.py:0: error: "setenv" of "PowerShellBase" does not return a value (it only ever returns None) [func-returns-value] -src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Module "typing" has no attribute "Self" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] src/rezplugins/package_repository/filesystem.py:0: error: "PackageRepository" has no attribute "_get_file" [attr-defined] src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "FileSystemPackageResource") [misc] src/rezplugins/package_repository/filesystem.py:0: error: "PackageRepository" has no attribute "_get_version_dirs" [attr-defined] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "check_package_definition_files" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[Any]" has no attribute "check_package_definition_files" [union-attr] src/rezplugins/package_repository/filesystem.py:0: error: "PackageRepository" has no attribute "_get_file" [attr-defined] src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "FileSystemPackageResource") [misc] src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageResourceHelper" defined the type as "None") [assignment] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Optional[str]", expected "str") [return-value] src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Resource", expected "FileSystemPackageFamilyResource") [return-value] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "dict[str, Any] | None" has no attribute "get" [union-attr] -src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[Dict[str, Any]]" has no attribute "get" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] src/rezplugins/package_repository/filesystem.py:0: error: "PackageRepository" has no attribute "_get_file" [attr-defined] src/rezplugins/package_repository/filesystem.py:0: error: "PackageRepository" has no attribute "disable_memcache" [attr-defined] src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Resource", expected "FileSystemPackageResource") [return-value] -src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] src/rezplugins/package_repository/filesystem.py:0: error: "FileSystemCombinedPackageFamilyResource" has no attribute "versions" [attr-defined] src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "FileSystemCombinedPackageResource") [misc] src/rezplugins/package_repository/filesystem.py:0: error: "FileSystemCombinedPackageFamilyResource" has no attribute "versions" [attr-defined] @@ -363,72 +383,72 @@ src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in src/rezplugins/package_repository/filesystem.py:0: error: "PackageRepository" has no attribute "disable_memcache" [attr-defined] src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in assignment (expression has type "str", base class "PackageResourceHelper" defined the type as "None") [assignment] src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Resource", expected "FileSystemCombinedPackageFamilyResource") [return-value] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "dict[str, Any] | None" has no attribute "get" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[Dict[str, Any]]" has no attribute "get" [union-attr] src/rezplugins/package_repository/filesystem.py:0: error: Cannot determine type of "variant_key" [has-type] src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in "yield" (actual type "Resource", expected type "FileSystemCombinedVariantResource") [misc] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "dict[str, Any] | None" has no attribute "copy" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Return type "Optional[Dict[str, Any]]" of "_load" incompatible with return type "Dict[str, Any]" in supertype "Resource" [override] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[Dict[str, Any]]" has no attribute "copy" [union-attr] src/rezplugins/package_repository/filesystem.py:0: error: "FileSystemCombinedPackageFamilyResource" has no attribute "version_overrides" [attr-defined] src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Resource", expected "PackageRepositoryResource") [return-value] src/rezplugins/package_repository/filesystem.py:0: error: Cannot assign to a method [method-assign] src/rezplugins/package_repository/filesystem.py:0: error: Cannot assign to a method [method-assign] src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "str" [arg-type] -src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "PackageFamilyResource | None", expected "PackageFamilyResource") [return-value] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Optional[PackageFamilyResource]", expected "PackageFamilyResource") [return-value] src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 of "iter_variants" is incompatible with supertype "PackageRepository"; supertype defines the argument type as "PackageResource" [override] src/rezplugins/package_repository/filesystem.py:0: error: Return type "PackageRepositoryResource" of "get_parent_package_family" incompatible with return type "PackageFamilyResource" in supertype "PackageRepository" [override] src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 of "get_variant_state_handle" is incompatible with supertype "PackageRepository"; supertype defines the argument type as "PackageResource" [override] src/rezplugins/package_repository/filesystem.py:0: error: "PackageRepositoryResource" has no attribute "state_handle" [attr-defined] src/rezplugins/package_repository/filesystem.py:0: error: "PackageFamilyResource" has no attribute "get_last_release_time" [attr-defined] -src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in assignment (expression has type "str", variable has type "int | None") [assignment] -src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "str | None"; expected "str" [arg-type] -src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "remove_package" of "FileSystemPackageRepository" has incompatible type "str | None"; expected "str" [arg-type] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "file_lock_dir" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible types in assignment (expression has type "str", variable has type "Optional[int]") [assignment] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "remove_package" of "FileSystemPackageRepository" has incompatible type "Optional[str]"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[Any]" has no attribute "file_lock_dir" [union-attr] src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 of "pre_variant_install" is incompatible with supertype "PackageRepository"; supertype defines the argument type as "VariantResource" [override] -src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "str | None"; expected "str" [arg-type] -src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "VariantResource | None", expected "VariantResource") [return-value] -src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "_lock_package" of "FileSystemPackageRepository" has incompatible type "str | None"; expected "str" [arg-type] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "file_lock_type" [union-attr] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "file_lock_type" [union-attr] -src/rezplugins/package_repository/filesystem.py:0: error: Incompatible import of "LockFile" (imported name has type "type[MkdirLockFile]", local name has type "type[LinkLockFile]") [assignment] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "file_lock_type" [union-attr] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "file_lock_type" [union-attr] -src/rezplugins/package_repository/filesystem.py:0: error: Incompatible import of "LockFile" (imported name has type "type[SymlinkLockFile]", local name has type "type[LinkLockFile]") [assignment] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "file_lock_timeout" [union-attr] -src/rezplugins/package_repository/filesystem.py:0: error: "Callable[[], list[tuple[str, str | None]]]" has no attribute "forget" [attr-defined] -src/rezplugins/package_repository/filesystem.py:0: error: "Callable[[str], list[str]]" has no attribute "forget" [attr-defined] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "check_package_definition_files" [union-attr] -src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "list[Resource]", expected "list[PackageFamilyResource]") [return-value] -src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Resource", expected "PackageFamilyResource | None") [return-value] -src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Resource", expected "PackageFamilyResource | None") [return-value] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "FileFormat | None" has no attribute "extension" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Optional[VariantResource]", expected "VariantResource") [return-value] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "_lock_package" of "FileSystemPackageRepository" has incompatible type "Optional[str]"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[Any]" has no attribute "file_lock_type" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[Any]" has no attribute "file_lock_type" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible import of "LockFile" (imported name has type "Type[MkdirLockFile]", local name has type "Type[LinkLockFile]") [assignment] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[Any]" has no attribute "file_lock_type" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[Any]" has no attribute "file_lock_type" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible import of "LockFile" (imported name has type "Type[SymlinkLockFile]", local name has type "Type[LinkLockFile]") [assignment] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[Any]" has no attribute "file_lock_timeout" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: "Callable[[], List[Tuple[str, Optional[str]]]]" has no attribute "forget" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: "Callable[[str], List[str]]" has no attribute "forget" [attr-defined] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[Any]" has no attribute "check_package_definition_files" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "List[Resource]", expected "List[PackageFamilyResource]") [return-value] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Resource", expected "Optional[PackageFamilyResource]") [return-value] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible return value type (got "Resource", expected "Optional[PackageFamilyResource]") [return-value] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[FileFormat]" has no attribute "extension" [union-attr] src/rezplugins/package_repository/filesystem.py:0: error: "PackageFamilyResource" has no attribute "iter_packages" [attr-defined] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "package_filenames" [union-attr] -src/rezplugins/package_repository/filesystem.py:0: error: Incompatible default for argument "overrides" (default has type "None", argument has type "dict[str, Any]") [assignment] -src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "get_package_family" of "FileSystemPackageRepository" has incompatible type "Any | str | None"; expected "str" [arg-type] -src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "_create_family" of "FileSystemPackageRepository" has incompatible type "Any | str | None"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[Any]" has no attribute "package_filenames" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Incompatible default for argument "overrides" (default has type "None", argument has type "Dict[str, Any]") [assignment] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "get_package_family" of "FileSystemPackageRepository" has incompatible type "Union[Any, str, None]"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "_create_family" of "FileSystemPackageRepository" has incompatible type "Union[Any, str, None]"; expected "str" [arg-type] src/rezplugins/package_repository/filesystem.py:0: error: "VariantResource" has no attribute "uuid" [attr-defined] src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "_get_package_data" has incompatible type "PackageRepositoryResource"; expected "PackageRepositoryResourceWrapper" [arg-type] src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "iter_variants" of "FileSystemPackageRepository" has incompatible type "Package"; expected "PackageResourceHelper" [arg-type] src/rezplugins/package_repository/filesystem.py:0: error: "VariantResource" has no attribute "variant_requires" [attr-defined] src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "iter_variants" of "FileSystemPackageRepository" has incompatible type "Package"; expected "PackageResourceHelper" [arg-type] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "get" [union-attr] -src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Any | None" has no attribute "package_filenames" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[Any]" has no attribute "get" [union-attr] +src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Optional[Any]" has no attribute "package_filenames" [union-attr] src/rezplugins/package_repository/filesystem.py:0: error: "VariantResource" has no attribute "variant_requires" [attr-defined] -src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "Any | str | None"; expected "str" [arg-type] -src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "_on_changed" of "FileSystemPackageRepository" has incompatible type "Any | str | None"; expected "str" [arg-type] -src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "get_package" of "PackageRepository" has incompatible type "Any | str | None"; expected "str" [arg-type] -src/rez/status.py:0: error: Incompatible types in assignment (expression has type "list[Never]", variable has type "str") [assignment] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "Union[Any, str, None]"; expected "str" [arg-type] +src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "_on_changed" of "FileSystemPackageRepository" has incompatible type "Union[Any, str, None]"; expected "str" [arg-type] +src/rez/status.py:0: error: Incompatible types in assignment (expression has type "List[Never]", variable has type "str") [assignment] src/rez/status.py:0: error: "str" has no attribute "append" [attr-defined] -src/rez/status.py:0: error: Argument 1 to "append" of "list" has incompatible type "list[Any]"; expected "tuple[str, str, str, str, str, Callable[[str], str] | None]" [arg-type] -src/rez/status.py:0: error: Argument 2 to "print_colored_columns" has incompatible type "list[tuple[str, str, str, str, str, Callable[[str], str] | None] | list[str | None]]"; expected "Sequence[tuple[Any, ...]]" [arg-type] -src/rez/status.py:0: error: Argument 1 to "append" of "list" has incompatible type "str | None"; expected "str" [arg-type] -src/rez/package_test.py:0: error: Incompatible types in assignment (expression has type "Package | None", variable has type "Package") [assignment] -src/rez/package_test.py:0: error: Item "None" of "ResolvedContext | None" has no attribute "get_resolved_package" [union-attr] -src/rez/package_test.py:0: error: Item "None" of "Variant | Any | None" has no attribute "uri" [union-attr] -src/rez/package_test.py:0: error: Item "None" of "Variant | Any | None" has no attribute "variant_requires" [union-attr] -src/rez/package_test.py:0: error: Item "None" of "Variant | Any | None" has no attribute "handle" [union-attr] -src/rez/package_test.py:0: error: Item "None" of "Variant | Any | None" has no attribute "uri" [union-attr] -src/rez/package_test.py:0: error: Item "None" of "Variant | Any | None" has no attribute "format" [union-attr] -src/rez/package_test.py:0: error: Item "None" of "Variant | Any | None" has no attribute "format" [union-attr] +src/rez/status.py:0: error: Argument 1 to "append" of "list" has incompatible type "List[Any]"; expected "Tuple[str, str, str, str, str, Optional[Callable[[str], str]]]" [arg-type] +src/rez/status.py:0: error: Argument 2 to "print_colored_columns" has incompatible type "List[Union[Tuple[str, str, str, str, str, Optional[Callable[[str], str]]], List[Optional[str]]]]"; expected "Sequence[Tuple[Any, ...]]" [arg-type] +src/rez/status.py:0: error: Argument 1 to "append" of "list" has incompatible type "Optional[str]"; expected "str" [arg-type] +src/rez/package_test.py:0: error: Incompatible types in assignment (expression has type "Optional[Package]", variable has type "Package") [assignment] +src/rez/package_test.py:0: error: Item "None" of "Optional[ResolvedContext]" has no attribute "get_resolved_package" [union-attr] +src/rez/package_test.py:0: error: Item "None" of "Union[Variant, Any, None]" has no attribute "uri" [union-attr] +src/rez/package_test.py:0: error: Item "None" of "Union[Variant, Any, None]" has no attribute "variant_requires" [union-attr] +src/rez/package_test.py:0: error: Item "None" of "Union[Variant, Any, None]" has no attribute "handle" [union-attr] +src/rez/package_test.py:0: error: Item "None" of "Union[Variant, Any, None]" has no attribute "uri" [union-attr] +src/rez/package_test.py:0: error: Item "None" of "Union[Variant, Any, None]" has no attribute "format" [union-attr] +src/rez/package_test.py:0: error: Item "None" of "Union[Variant, Any, None]" has no attribute "format" [union-attr] src/rez/package_search.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] src/rez/package_search.py:0: error: Incompatible types in assignment (expression has type "Iterator[Package]", variable has type "Iterator[PackageFamily]") [assignment] src/rez/package_search.py:0: error: "PackageFamily" has no attribute "version" [attr-defined] @@ -437,91 +457,91 @@ src/rez/package_search.py:0: error: "PackageFamily" has no attribute "name" [at src/rez/package_search.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] src/rez/package_search.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] src/rez/package_search.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] -src/rez/package_search.py:0: error: Argument 2 to "expand_abbreviations" has incompatible type "tuple[str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str]"; expected "list[str]" [arg-type] -src/rez/package_search.py:0: error: Incompatible types in assignment (expression has type "tuple[Any, Callable[[Any], Any]]", variable has type "str") [assignment] -src/rez/package_search.py:0: error: Argument 1 to "append" of "list" has incompatible type "str"; expected "tuple[Any, Callable[[Any], Any]]" [arg-type] -src/rez/package_help.py:0: error: Item "None" of "Variant | None" has no attribute "base" [union-attr] -src/rez/package_help.py:0: error: Item "None" of "Variant | None" has no attribute "root" [union-attr] +src/rez/package_search.py:0: error: Argument 2 to "expand_abbreviations" has incompatible type "Tuple[str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str]"; expected "List[str]" [arg-type] +src/rez/package_search.py:0: error: Incompatible types in assignment (expression has type "Tuple[Any, Callable[[Any], Any]]", variable has type "str") [assignment] +src/rez/package_search.py:0: error: Argument 1 to "append" of "list" has incompatible type "str"; expected "Tuple[Any, Callable[[Any], Any]]" [arg-type] +src/rez/package_help.py:0: error: Item "None" of "Optional[Variant]" has no attribute "base" [union-attr] +src/rez/package_help.py:0: error: Item "None" of "Optional[Variant]" has no attribute "root" [union-attr] src/rez/package_copy.py:0: error: Argument 1 to "install_variant" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] src/rez/package_copy.py:0: error: Argument 1 to "pre_variant_install" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] src/rez/package_copy.py:0: error: Argument "variant_resource" to "install_variant" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] -src/rez/package_copy.py:0: error: Item "None" of "Variant | None" has no attribute "uri" [union-attr] -src/rez/package_bind.py:0: error: List comprehension has incompatible type List[tuple[Any, Any]]; expected List[list[str]] [misc] -src/rez/utils/pip.py:0: error: Incompatible types in assignment (expression has type "VersionRange | None", variable has type "VersionRange") [assignment] +src/rez/package_copy.py:0: error: Item "None" of "Optional[Variant]" has no attribute "uri" [union-attr] +src/rez/package_bind.py:0: error: List comprehension has incompatible type List[Tuple[Any, Any]]; expected List[List[str]] [misc] +src/rez/utils/pip.py:0: error: Incompatible types in assignment (expression has type "Optional[VersionRange]", variable has type "VersionRange") [assignment] src/rez/utils/installer.py:0: error: Name "env" is not defined [name-defined] src/rez/utils/diff_packages.py:0: error: "type" has no attribute "export" [attr-defined] src/rez/cli/pkg-cache.py:0: error: Argument 1 to "append" of "list" has incompatible type "Callable[[Any], Any]"; expected "str" [arg-type] -src/rez/cli/pkg-cache.py:0: error: Argument 2 to "print_colored_columns" has incompatible type "list[list[Any]]"; expected "Sequence[tuple[Any, ...]]" [arg-type] +src/rez/cli/pkg-cache.py:0: error: Argument 2 to "print_colored_columns" has incompatible type "List[List[Any]]"; expected "Sequence[Tuple[Any, ...]]" [arg-type] src/rez/cli/memcache.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] src/rez/cli/memcache.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] src/rez/cli/memcache.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] -src/rez/cli/memcache.py:0: error: Argument 1 to "append" of "list" has incompatible type "tuple[Any, str, str, str, str, str, str]"; expected "list[str]" [arg-type] -src/rez/cli/benchmark.py:0: error: List item 0 has incompatible type "str | None"; expected "str" [list-item] +src/rez/cli/memcache.py:0: error: Argument 1 to "append" of "list" has incompatible type "Tuple[Any, str, str, str, str, str, str]"; expected "List[str]" [arg-type] +src/rez/cli/benchmark.py:0: error: List item 0 has incompatible type "Optional[str]"; expected "str" [list-item] src/rez/cli/benchmark.py:0: error: Name "_opts" is not defined [name-defined] -src/rez/cli/benchmark.py:0: error: List item 0 has incompatible type "str | None"; expected "str" [list-item] +src/rez/cli/benchmark.py:0: error: List item 0 has incompatible type "Optional[str]"; expected "str" [list-item] src/rez/cli/benchmark.py:0: error: Name "_opts" is not defined [name-defined] -src/rez/cli/benchmark.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -src/rez/cli/benchmark.py:0: error: Argument 1 to "exists" has incompatible type "str | None"; expected "int | str | bytes | PathLike[str] | PathLike[bytes]" [arg-type] -src/rez/cli/benchmark.py:0: error: Argument 1 to "mkdir" has incompatible type "str | None"; expected "str | bytes | PathLike[str] | PathLike[bytes]" [arg-type] -src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/cli/benchmark.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] +src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] +src/rez/cli/benchmark.py:0: error: Argument 1 to "exists" has incompatible type "Optional[str]"; expected "Union[int, Union[str, bytes, PathLike[str], PathLike[bytes]]]" [arg-type] +src/rez/cli/benchmark.py:0: error: Argument 1 to "mkdir" has incompatible type "Optional[str]"; expected "Union[str, bytes, PathLike[str], PathLike[bytes]]" [arg-type] +src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] src/rez/cli/benchmark.py:0: error: Name "_opts" is not defined [name-defined] -src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] +src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] src/rez/cli/benchmark.py:0: error: Name "_opts" is not defined [name-defined] -src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] src/rez/cli/_util.py:0: error: Module has no attribute "CTRL_C_EVENT" [attr-defined] -src/rez/cli/_complete_util.py:0: error: Incompatible types in assignment (expression has type "Generator[str, None, None]", variable has type "list[str]") [assignment] +src/rez/cli/_complete_util.py:0: error: Incompatible types in assignment (expression has type "Generator[str, None, None]", variable has type "List[str]") [assignment] +src/rezplugins/shell/zsh.py:0: error: Incompatible types in assignment (expression has type "None", base class "UnixShell" defined the type as "str") [assignment] src/rezplugins/release_vcs/hg.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] -src/rezplugins/release_vcs/git.py:0: error: Item "None" of "Any | None" has no attribute "allow_no_upstream" [union-attr] +src/rezplugins/release_vcs/git.py:0: error: Item "None" of "Optional[Any]" has no attribute "allow_no_upstream" [union-attr] src/rezplugins/release_vcs/git.py:0: error: Name "retain_cwd" is not defined [name-defined] src/rezplugins/release_vcs/git.py:0: error: Name "git" is not defined [name-defined] src/rezplugins/release_vcs/git.py:0: error: Name "git" is not defined [name-defined] src/rezplugins/release_hook/emailer.py:0: error: Signature of "post_release" incompatible with supertype "ReleaseHook" [override] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "body" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "subject" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "recipients" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "smtp_host" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "sender" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "smtp_host" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "smtp_port" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "sender" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Any | None" has no attribute "recipients" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "print_error" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "print_output" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "body" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "subject" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "recipients" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "smtp_host" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "sender" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "smtp_host" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "smtp_port" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "sender" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "recipients" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "print_error" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "print_output" [union-attr] src/rezplugins/release_hook/command.py:0: error: Signature of "pre_build" incompatible with supertype "ReleaseHook" [override] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "pre_build_commands" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "cancel_on_error" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "pre_build_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "cancel_on_error" [union-attr] src/rezplugins/release_hook/command.py:0: error: Signature of "pre_release" incompatible with supertype "ReleaseHook" [override] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "pre_release_commands" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "cancel_on_error" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "pre_release_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "cancel_on_error" [union-attr] src/rezplugins/release_hook/command.py:0: error: Signature of "post_release" incompatible with supertype "ReleaseHook" [override] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "post_release_commands" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Argument 1 to "append" of "list" has incompatible type "dict[Any, Any]"; expected "int" [arg-type] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "print_commands" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "print_commands" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Any | None" has no attribute "stop_on_error" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "post_release_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Argument 1 to "append" of "list" has incompatible type "Dict[Any, Any]"; expected "int" [arg-type] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "print_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "print_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "stop_on_error" [union-attr] src/rezplugins/release_hook/amqp.py:0: error: Signature of "post_release" incompatible with supertype "ReleaseHook" [override] -src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Any | None" has no attribute "message_attributes" [union-attr] -src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Any | None" has no attribute "host" [union-attr] -src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Any | None" has no attribute "exchange_routing_key" [union-attr] -src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Any | None" has no attribute "host" [union-attr] -src/rez/pip.py:0: error: Item "None" of "Variant | None" has no attribute "parent" [union-attr] -src/rez/build_process.py:0: error: Item "None" of "ReleaseVCS | None" has no attribute "get_changelog" [union-attr] -src/rez/build_system.py:0: error: Unsupported operand types for - ("set[type[BuildSystem]]" and "set[str | None]") [operator] -src/rez/cli/interpret.py:0: error: Incompatible types in assignment (expression has type "Python", variable has type "Shell | None") [assignment] -src/rez/cli/help.py:0: error: Item "None" of "Package | None" has no attribute "uri" [union-attr] -src/rez/cli/help.py:0: error: Item "None" of "Package | None" has no attribute "description" [union-attr] -src/rez/cli/help.py:0: error: Item "None" of "Package | None" has no attribute "description" [union-attr] +src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Optional[Any]" has no attribute "message_attributes" [union-attr] +src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Optional[Any]" has no attribute "host" [union-attr] +src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Optional[Any]" has no attribute "exchange_routing_key" [union-attr] +src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Optional[Any]" has no attribute "host" [union-attr] +src/rez/build_process.py:0: error: Item "None" of "Optional[ReleaseVCS]" has no attribute "get_changelog" [union-attr] +src/rez/build_system.py:0: error: Unsupported operand types for - ("Set[Type[BuildSystem]]" and "Set[Optional[str]]") [operator] +src/rez/cli/interpret.py:0: error: Incompatible types in assignment (expression has type "Python", variable has type "Optional[Shell]") [assignment] +src/rez/cli/help.py:0: error: Item "None" of "Optional[Package]" has no attribute "uri" [union-attr] +src/rez/cli/help.py:0: error: Item "None" of "Optional[Package]" has no attribute "description" [union-attr] +src/rez/cli/help.py:0: error: Item "None" of "Optional[Package]" has no attribute "description" [union-attr] src/rez/cli/env.py:0: error: Function "graph" could always be true in boolean context [truthy-function] src/rez/cli/env.py:0: error: "Popen[Any]" object is not iterable [misc] src/rez/cli/env.py:0: error: Cannot determine type of "returncode" [has-type] -src/rez/cli/diff.py:0: error: Argument 1 to "diff_packages" has incompatible type "Package | None"; expected "Package" [arg-type] -src/rez/cli/cp.py:0: error: Incompatible types in assignment (expression has type "Any | None", variable has type "list[Any]") [assignment] -src/rez/cli/context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/cli/context.py:0: error: Item "None" of "list[Variant] | None" has no attribute "__iter__" (not iterable) [union-attr] -src/rez/cli/bind.py:0: error: Argument 1 to "sorted" has incompatible type "dict_items[str, str]"; expected "Iterable[list[str]]" [arg-type] +src/rez/cli/diff.py:0: error: Argument 1 to "diff_packages" has incompatible type "Optional[Package]"; expected "Package" [arg-type] +src/rez/cli/cp.py:0: error: Incompatible types in assignment (expression has type "Optional[Any]", variable has type "List[Any]") [assignment] +src/rez/cli/context.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/cli/context.py:0: error: Item "None" of "Optional[List[Variant]]" has no attribute "__iter__" (not iterable) [union-attr] +src/rez/cli/bind.py:0: error: Argument 1 to "sorted" has incompatible type "dict_items[str, str]"; expected "Iterable[List[str]]" [arg-type] src/rez/bind/python.py:0: error: Name "this" is not defined [name-defined] src/rez/bind/_pymodule.py:0: error: Name "env" is not defined [name-defined] src/rez/bind/_pymodule.py:0: error: Name "env" is not defined [name-defined] @@ -538,14 +558,15 @@ src/rezplugins/build_system/cmake.py:0: error: "Popen[Any]" object is not iterab src/rezplugins/build_system/cmake.py:0: error: Cannot determine type of "retcode'" [has-type] src/rezplugins/build_system/cmake.py:0: error: "Popen[Any]" object is not iterable [misc] src/rezplugins/build_system/cmake.py:0: error: Cannot determine type of "retcode" [has-type] -src/rezplugins/build_process/local.py:0: error: Argument 1 to "join" of "str" has incompatible type "list[str | None]"; expected "Iterable[str]" [arg-type] +src/rezplugins/build_process/local.py:0: error: Argument 1 to "join" of "str" has incompatible type "List[Optional[str]]"; expected "Iterable[str]" [arg-type] src/rezplugins/build_process/local.py:0: error: Argument 1 to "pre_variant_install" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] -src/rezplugins/build_process/local.py:0: error: Value of type "dict[str, Any] | None" is not indexable [index] -src/rezplugins/build_process/local.py:0: error: Argument 1 to "get_repository" of "PackageRepositoryManager" has incompatible type "str | None"; expected "str" [arg-type] +src/rezplugins/build_process/local.py:0: error: Value of type "Optional[Dict[str, Any]]" is not indexable [index] +src/rezplugins/build_process/local.py:0: error: Argument 1 to "get_repository" of "PackageRepositoryManager" has incompatible type "Optional[str]"; expected "str" [arg-type] src/rezplugins/build_process/local.py:0: error: Argument 1 to "on_variant_install_cancelled" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] src/rezplugins/build_process/local.py:0: error: Argument 1 to "on_variant_install_cancelled" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] -src/rez/cli/pip.py:0: error: "Config" has no attribute "debug_package_release" [attr-defined] +src/rez/pip.py:0: error: Item "None" of "Optional[Variant]" has no attribute "parent" [union-attr] src/rez/cli/release.py:0: error: Name "raw_input" is not defined [name-defined] -src/rez/cli/selftest.py:0: error: Argument 1 to "getfile" has incompatible type "FrameType | None"; expected Module | type[Any] | MethodType | FunctionType | TracebackType | FrameType | CodeType | Callable[..., Any] [arg-type] -src/rez/cli/selftest.py:0: error: Item "MetaPathFinderProtocol" of "MetaPathFinderProtocol | PathEntryFinderProtocol" has no attribute "find_module" [union-attr] -src/rez/cli/selftest.py:0: error: Item "PathEntryFinderProtocol" of "MetaPathFinderProtocol | PathEntryFinderProtocol" has no attribute "find_module" [union-attr] +src/rez/cli/pip.py:0: error: "Config" has no attribute "debug_package_release" [attr-defined] +src/rez/cli/selftest.py:0: error: Argument 1 to "getfile" has incompatible type "Optional[FrameType]"; expected "Union[Module, Type[Any], MethodType, FunctionType, TracebackType, FrameType, CodeType, Callable[..., Any]]" [arg-type] +src/rez/cli/selftest.py:0: error: Item "MetaPathFinderProtocol" of "Union[MetaPathFinderProtocol, PathEntryFinderProtocol]" has no attribute "find_module" [union-attr] +src/rez/cli/selftest.py:0: error: Item "PathEntryFinderProtocol" of "Union[MetaPathFinderProtocol, PathEntryFinderProtocol]" has no attribute "find_module" [union-attr] diff --git a/pyproject.toml b/pyproject.toml index 85b7f8fe1..7a025147e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools"] build-backend = "setuptools.build_meta" [tool.mypy] -python_version = "3.11" +python_version = "3.8" files = ["src/rez/", "src/rezplugins/"] exclude = [ '.*/rez/data/.*', diff --git a/src/rez/utils/memcached.py b/src/rez/utils/memcached.py index d613ccd5f..357583dc3 100644 --- a/src/rez/utils/memcached.py +++ b/src/rez/utils/memcached.py @@ -14,7 +14,7 @@ from inspect import isgeneratorfunction from hashlib import md5 from uuid import uuid4 -from typing import Callable, Iterator, TypeVar +from typing import Any, Callable, Iterator, TypeVar CallableT = TypeVar("CallableT", bound=Callable) From 2006b5f472cfe1e57ebf3be4b3f23df8dd44852d Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Fri, 27 Jun 2025 00:11:34 -0400 Subject: [PATCH 03/10] Add py.typed files --- setup.py | 2 ++ src/rez/py.typed | 0 src/rezplugins/py.typed | 0 3 files changed, 2 insertions(+) create mode 100644 src/rez/py.typed create mode 100644 src/rezplugins/py.typed diff --git a/setup.py b/setup.py index ad11b1edb..d80c6522e 100644 --- a/setup.py +++ b/setup.py @@ -72,10 +72,12 @@ def find_files(pattern, path=None, root="rez"): 'rez': ['utils/logging.conf'] + ['README*'] + + ["py.typed"] + find_files('*', 'completion') + find_files('*', 'data') + find_files('*.exe', 'vendor/distlib'), 'rezplugins': + ["py.typed"] + find_files('*.cmake', 'build_system', root='rezplugins') + find_files('*', 'build_system/template_files', root='rezplugins'), 'rezgui': diff --git a/src/rez/py.typed b/src/rez/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/src/rezplugins/py.typed b/src/rezplugins/py.typed new file mode 100644 index 000000000..e69de29bb From 360d10f1a13a1465f598f5804358059fab47038e Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Fri, 27 Jun 2025 00:28:25 -0400 Subject: [PATCH 04/10] fix for 3.8 --- src/rez/utils/data_utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rez/utils/data_utils.py b/src/rez/utils/data_utils.py index 6735950e1..bc20f0b65 100644 --- a/src/rez/utils/data_utils.py +++ b/src/rez/utils/data_utils.py @@ -10,11 +10,10 @@ import os.path import json import functools -from collections.abc import MutableMapping from rez.vendor.schema.schema import Schema, Optional from threading import Lock -from typing import Any, Callable, Generic, TypeVar, TYPE_CHECKING +from typing import Any, Callable, Generic, MutableMapping, TypeVar, TYPE_CHECKING T = TypeVar("T") From 29883a971c3086289c288057d4165ea36ef12d04 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Fri, 27 Jun 2025 01:23:53 -0400 Subject: [PATCH 05/10] lint fixes --- src/rez/bind/_utils.py | 2 +- src/rez/build_process.py | 2 +- src/rez/build_system.py | 2 +- src/rez/deprecations.py | 2 +- src/rez/package_help.py | 2 +- src/rez/package_maker.py | 4 ++-- src/rez/package_test.py | 2 +- src/rez/packages.py | 3 ++- src/rez/resolved_context.py | 4 ++-- src/rez/resolver.py | 2 +- src/rez/rex.py | 2 +- src/rez/solver.py | 7 +++++-- src/rez/suite.py | 2 +- src/rez/tests/test_formatter.py | 2 +- src/rez/tests/test_test.py | 2 +- src/rez/util.py | 4 ++-- src/rez/utils/execution.py | 2 -- src/rez/utils/formatting.py | 4 ++-- src/rez/utils/memcached.py | 6 +++--- src/rez/utils/scope.py | 2 +- src/rez/utils/typing.py | 1 + src/rez/version/_version.py | 7 +++++-- src/rezplugins/package_repository/filesystem.py | 2 +- 23 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/rez/bind/_utils.py b/src/rez/bind/_utils.py index d49e9c9f2..11a7c825e 100644 --- a/src/rez/bind/_utils.py +++ b/src/rez/bind/_utils.py @@ -82,7 +82,7 @@ def find_exe(name, filepath=None): return filepath -def extract_version(exepath, version_arg, word_index=-1, version_rank: int=3): +def extract_version(exepath, version_arg, word_index=-1, version_rank: int = 3): """Run an executable and get the program version. Args: diff --git a/src/rez/build_process.py b/src/rez/build_process.py index a5556b186..4802e1d84 100644 --- a/src/rez/build_process.py +++ b/src/rez/build_process.py @@ -450,7 +450,7 @@ def _print(self, txt, *nargs) -> None: txt = txt % nargs print(txt) - def _print_header(self, txt, n: int=1) -> None: + def _print_header(self, txt, n: int = 1) -> None: if self.quiet: return diff --git a/src/rez/build_system.py b/src/rez/build_system.py index eba04c4d3..503983a39 100644 --- a/src/rez/build_system.py +++ b/src/rez/build_system.py @@ -18,7 +18,7 @@ from typing import TypedDict # not available until python 3.8 from rez.developer_package import DeveloperPackage from rez.resolved_context import ResolvedContext - from rez.packages import Package, Variant + from rez.packages import Variant from rez.rex import RexExecutor # FIXME: move this out of TYPE_CHECKING block when python 3.7 support is dropped diff --git a/src/rez/deprecations.py b/src/rez/deprecations.py index c82bd3fea..73c1ef722 100644 --- a/src/rez/deprecations.py +++ b/src/rez/deprecations.py @@ -7,7 +7,7 @@ import warnings -def warn(message, category, pre_formatted: bool = False, stacklevel: int=1, filename=None, **kwargs) -> None: +def warn(message, category, pre_formatted: bool = False, stacklevel: int = 1, filename=None, **kwargs) -> None: """ Wrapper around warnings.warn that allows to pass a pre-formatter warning message. This allows to warn about things that aren't coming diff --git a/src/rez/package_help.py b/src/rez/package_help.py index 5702d9cc8..2ba53937a 100644 --- a/src/rez/package_help.py +++ b/src/rez/package_help.py @@ -94,7 +94,7 @@ def sections(self) -> list[list[str]]: """Returns a list of (name, uri) 2-tuples.""" return self._sections - def open(self, section_index: int=0) -> None: + def open(self, section_index: int = 0) -> None: """Launch a help section.""" uri = self._sections[section_index][1] if len(uri.split()) == 1: diff --git a/src/rez/package_maker.py b/src/rez/package_maker.py index d8f7f8b4c..98ac02550 100644 --- a/src/rez/package_maker.py +++ b/src/rez/package_maker.py @@ -20,7 +20,7 @@ from rez.version import Version from contextlib import contextmanager import os -from typing import Any, Callable, Iterable, Iterator +from typing import Any, Callable, Iterator # this schema will automatically harden request strings like 'python-*'; see @@ -159,7 +159,7 @@ def _get_data(self) -> dict: @contextmanager def make_package(name: str, path: str, make_base: Callable[[Variant, str], Any] | None = None, - make_root: Callable[[Variant, str], Any] | None= None, + make_root: Callable[[Variant, str], Any] | None = None, skip_existing: bool = True, warn_on_skip: bool = True) -> Iterator[PackageMaker]: """Make and install a package. diff --git a/src/rez/package_test.py b/src/rez/package_test.py index 3d62574fc..c9ac85ba7 100644 --- a/src/rez/package_test.py +++ b/src/rez/package_test.py @@ -51,7 +51,7 @@ class PackageTestRunner(object): """ def __init__(self, package_request, use_current_env: bool = False, extra_package_requests=None, package_paths=None, stdout=None, - stderr=None, verbose: int=0, dry_run: bool = False, stop_on_fail: bool = False, + stderr=None, verbose: int = 0, dry_run: bool = False, stop_on_fail: bool = False, cumulative_test_results=None, **context_kwargs) -> None: """Create a package tester. diff --git a/src/rez/packages.py b/src/rez/packages.py index 3ce68fefe..b667fa516 100644 --- a/src/rez/packages.py +++ b/src/rez/packages.py @@ -27,7 +27,6 @@ from typing import overload, Any, Iterator, TypeVar, TYPE_CHECKING if TYPE_CHECKING: - from typing import Literal # not available in typing module until 3.8 from rez.config import Config from rez.developer_package import DeveloperPackage from rez.version import Requirement @@ -710,10 +709,12 @@ def get_developer_package(path: str, format: FileFormat | None = None) -> Develo def create_package(name: str, data: dict, package_cls: type[PackageT]) -> PackageT: pass + @overload def create_package(name: str, data: dict) -> Package: pass + def create_package(name: str, data: dict, package_cls: type[Package] | None = None) -> Package: """Create a package given package data. diff --git a/src/rez/resolved_context.py b/src/rez/resolved_context.py index e4861e696..038b30e86 100644 --- a/src/rez/resolved_context.py +++ b/src/rez/resolved_context.py @@ -44,7 +44,7 @@ from contextlib import contextmanager from functools import wraps from enum import Enum -from typing import cast, Any, Callable, Iterable, Iterator, Mapping, NoReturn, Sequence, TypeVar, \ +from typing import Any, Callable, Iterable, Iterator, Mapping, NoReturn, Sequence, TypeVar, \ TYPE_CHECKING, overload import getpass import json @@ -853,7 +853,7 @@ def get_resolve_diff(self, other: ResolvedContext) -> dict: return d @pool_memcached_connections - def print_info(self, buf: SupportsWrite = sys.stdout, verbosity: int=0, + def print_info(self, buf: SupportsWrite = sys.stdout, verbosity: int = 0, source_order: bool = False, show_resolved_uris: bool = False) -> None: """Prints a message summarising the contents of the resolved context. diff --git a/src/rez/resolver.py b/src/rez/resolver.py index ed68374a3..2be85c406 100644 --- a/src/rez/resolver.py +++ b/src/rez/resolver.py @@ -18,7 +18,7 @@ from typing import Any, Callable, Iterator, TYPE_CHECKING if TYPE_CHECKING: - from rez.package_order import PackageOrder, PackageOrderList + from rez.package_order import PackageOrderList from rez.resolved_context import ResolvedContext from rez.utils.typing import SupportsWrite diff --git a/src/rez/rex.py b/src/rez/rex.py index ccf7a4eed..bbd6d8cb0 100644 --- a/src/rez/rex.py +++ b/src/rez/rex.py @@ -253,7 +253,7 @@ def _format(self, value): except (KeyError, ValueError): return value - def _expand(self, value: str) : + def _expand(self, value: str): def _fn(str_): str_ = expandvars(str_, self.environ) str_ = expandvars(str_, self.parent_environ) diff --git a/src/rez/solver.py b/src/rez/solver.py index 697dddcf6..62e47c0c9 100644 --- a/src/rez/solver.py +++ b/src/rez/solver.py @@ -784,7 +784,10 @@ def split(self) -> tuple[_PackageVariantSlice, _PackageVariantSlice]: # self.sort_versions() - def _split(i_entry: int, n_variants: int, common_fams: set[str] | None = None) -> tuple[_PackageVariantSlice, _PackageVariantSlice]: + def _split(i_entry: int, + n_variants: int, + common_fams: set[str] | None = None + ) -> tuple[_PackageVariantSlice, _PackageVariantSlice]: # perform a split at a specific point result = self.entries[i_entry].split(n_variants) @@ -1617,7 +1620,7 @@ def _uid() -> str: counter[0] += 1 return "_%d" % id_ - def _add_edge(id1: str, id2: str, arrowsize: float=0.5) -> tuple[str, str]: + def _add_edge(id1: str, id2: str, arrowsize: float = 0.5) -> tuple[str, str]: e = (id1, id2) if g.has_edge(e): g.del_edge(e) diff --git a/src/rez/suite.py b/src/rez/suite.py index 4b5056797..a1842f628 100644 --- a/src/rez/suite.py +++ b/src/rez/suite.py @@ -14,7 +14,7 @@ from rez.vendor.yaml.error import YAMLError from rez.utils.yaml import dump_yaml from collections import defaultdict -from typing import cast, Callable, TYPE_CHECKING, Any, NoReturn +from typing import cast, TYPE_CHECKING, Any, NoReturn import os import os.path import shutil diff --git a/src/rez/tests/test_formatter.py b/src/rez/tests/test_formatter.py index 8d69dc7f4..792ff8f4a 100644 --- a/src/rez/tests/test_formatter.py +++ b/src/rez/tests/test_formatter.py @@ -53,7 +53,7 @@ def test_formatter_stdlib(self) -> None: # classes we'll use for testing class C(object): - def __init__(self, x: int=100) -> None: + def __init__(self, x: int = 100) -> None: self._x = x def __format__(self, spec) -> str: diff --git a/src/rez/tests/test_test.py b/src/rez/tests/test_test.py index 8d104ca74..7af1b78e4 100644 --- a/src/rez/tests/test_test.py +++ b/src/rez/tests/test_test.py @@ -41,7 +41,7 @@ def test_2(self) -> None: # This will get us more code coverage :) self._run_tests(context, verbose=0) - def _run_tests(self, r, verbose: int=2) -> None: + def _run_tests(self, r, verbose: int = 2) -> None: """Run unit tests in package.py""" self.inject_python_repo() runner = PackageTestRunner( diff --git a/src/rez/util.py b/src/rez/util.py index a7e8e570b..745920cb9 100644 --- a/src/rez/util.py +++ b/src/rez/util.py @@ -102,7 +102,7 @@ def which(*programs, **shutilwhich_kwargs) -> str | None: # case-insensitive fuzzy string match -def get_close_matches(term: str, fields, fuzziness: float=0.4, key=None): +def get_close_matches(term: str, fields, fuzziness: float = 0.4, key=None): import math import difflib @@ -130,7 +130,7 @@ def _ratio(a, b): # fuzzy string matching on package names, such as 'boost', 'numpy-3.4' -def get_close_pkgs(pkg, pkgs, fuzziness: float=0.4): +def get_close_pkgs(pkg, pkgs, fuzziness: float = 0.4): matches = get_close_matches(pkg, pkgs, fuzziness=fuzziness) fam_matches = get_close_matches(pkg.split('-')[0], pkgs, fuzziness=fuzziness, diff --git a/src/rez/utils/execution.py b/src/rez/utils/execution.py index d1a2d83c6..486c98766 100644 --- a/src/rez/utils/execution.py +++ b/src/rez/utils/execution.py @@ -8,8 +8,6 @@ from __future__ import annotations -from collections.abc import Callable - from rez.utils.yaml import dump_yaml from contextlib import contextmanager from enum import Enum diff --git a/src/rez/utils/formatting.py b/src/rez/utils/formatting.py index 64bc847c2..d0ac49aab 100644 --- a/src/rez/utils/formatting.py +++ b/src/rez/utils/formatting.py @@ -308,7 +308,7 @@ def dict_to_attributes_code(dict_: dict) -> str: return '\n'.join(lines) -def columnise(rows: Sequence[Sequence[Any]], padding: int=2) -> list[str]: +def columnise(rows: Sequence[Sequence[Any]], padding: int = 2) -> list[str]: """Print rows of entries in aligned columns.""" strs = [] maxwidths = {} @@ -333,7 +333,7 @@ def columnise(rows: Sequence[Sequence[Any]], padding: int=2) -> list[str]: return strs -def print_colored_columns(printer: colorize.Printer, rows: Sequence[tuple], padding: int=2) -> None: +def print_colored_columns(printer: colorize.Printer, rows: Sequence[tuple], padding: int = 2) -> None: """Like `columnise`, but with colored rows. Args: diff --git a/src/rez/utils/memcached.py b/src/rez/utils/memcached.py index 357583dc3..00d46f20c 100644 --- a/src/rez/utils/memcached.py +++ b/src/rez/utils/memcached.py @@ -83,7 +83,7 @@ def test_servers(self) -> set[str]: responders.add(server) return responders - def set(self, key: str, val: Any, time: int=0, min_compress_len: int=0) -> None: + def set(self, key: str, val: Any, time: int = 0, min_compress_len: int = 0) -> None: """See memcache.Client.""" if not self.servers: return @@ -269,8 +269,8 @@ def wrapper(*nargs, **kwargs): return update_wrapper(wrapper, func) -def memcached(servers, key=None, from_cache=None, to_cache=None, time: int=0, - min_compress_len: int=0, debug: bool = False) -> Callable[[CallableT], CallableT]: +def memcached(servers, key=None, from_cache=None, to_cache=None, time: int = 0, + min_compress_len: int = 0, debug: bool = False) -> Callable[[CallableT], CallableT]: """memcached memoization function decorator. The wrapped function is expected to return a value that is stored to a diff --git a/src/rez/utils/scope.py b/src/rez/utils/scope.py index 4c3546d7b..517031c4e 100644 --- a/src/rez/utils/scope.py +++ b/src/rez/utils/scope.py @@ -8,7 +8,7 @@ from collections import UserDict import sys -from typing import cast, Any, TYPE_CHECKING, ClassVar, NoReturn +from typing import cast, Any, TYPE_CHECKING if TYPE_CHECKING: from typing import Self diff --git a/src/rez/utils/typing.py b/src/rez/utils/typing.py index 4b47e0ea7..8e0bb5adc 100644 --- a/src/rez/utils/typing.py +++ b/src/rez/utils/typing.py @@ -24,6 +24,7 @@ class SupportsWrite(Protocol): def write(self, __s: str) -> object: pass + class SupportsRead(Protocol): def read(self) -> str: pass diff --git a/src/rez/version/_version.py b/src/rez/version/_version.py index 7444b4731..a1456ed33 100644 --- a/src/rez/version/_version.py +++ b/src/rez/version/_version.py @@ -282,7 +282,8 @@ class Version(_Comparable): """ inf = None - def __init__(self, ver_str: str | None = '', make_token: Callable[[str], VersionToken] = AlphanumericVersionToken) -> None: + def __init__(self, ver_str: str | None = '', + make_token: Callable[[str], VersionToken] = AlphanumericVersionToken) -> None: """ Args: ver_str (str): Version string. @@ -697,7 +698,9 @@ class _VersionRangeParser(object): regex = re.compile(version_range_regex, re_flags) - def __init__(self, input_string: str, make_token: Callable[[str], VersionToken], invalid_bound_error: bool = True) -> None: + def __init__(self, input_string: str, + make_token: Callable[[str], VersionToken], + invalid_bound_error: bool = True) -> None: self.make_token = make_token self._groups: dict[str, Any | None] = {} self._input_string = input_string diff --git a/src/rezplugins/package_repository/filesystem.py b/src/rezplugins/package_repository/filesystem.py index 82367fde9..5d8e8276e 100644 --- a/src/rezplugins/package_repository/filesystem.py +++ b/src/rezplugins/package_repository/filesystem.py @@ -41,7 +41,7 @@ if TYPE_CHECKING: from typing import Self - from rez.packages import Package, Variant, PackageRepositoryResourceWrapper + from rez.packages import Package, PackageRepositoryResourceWrapper from rez.package_resources import PackageRepositoryResource, VariantResource debug_print = config.debug_printer("resources") From a007d854a0338130f04756bd6839080a7e574506 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Fri, 27 Jun 2025 09:52:32 -0400 Subject: [PATCH 06/10] fix --- src/rez/suite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rez/suite.py b/src/rez/suite.py index a1842f628..4c59649f3 100644 --- a/src/rez/suite.py +++ b/src/rez/suite.py @@ -430,7 +430,7 @@ def validate(self) -> None: def to_dict(self): contexts_ = {} for k, data in self.contexts.items(): - data_ = cast(dict[str, Any], data.copy()) + data_ = cast("dict[str, Any]", data.copy()) if "context" in data_: del data_["context"] if "loaded" in data_: From 78f796efe4c4c4634ef5fe941a822a675403ba8c Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Fri, 27 Jun 2025 11:43:16 -0400 Subject: [PATCH 07/10] Remove some changes --- src/rez/resolved_context.py | 2 -- src/rez/utils/patching.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/rez/resolved_context.py b/src/rez/resolved_context.py index 038b30e86..0840e897b 100644 --- a/src/rez/resolved_context.py +++ b/src/rez/resolved_context.py @@ -267,8 +267,6 @@ def __init__(self, self._package_requests: list[Requirement] = [] for req in package_requests: if isinstance(req, str): - # FIXME: Requirement seems like it would work fine here. the only difference - # appears to be that PackageRequest does some additional validation req = PackageRequest(req) self._package_requests.append(req) diff --git a/src/rez/utils/patching.py b/src/rez/utils/patching.py index 79037ae68..c0d72b52a 100644 --- a/src/rez/utils/patching.py +++ b/src/rez/utils/patching.py @@ -44,8 +44,8 @@ def get_patched_request(requires, patchlist): '^': (True, True, True) } - requires: list[Requirement | None] = [ - Requirement(x) if not isinstance(x, Requirement) else x for x in requires] + requires = [Requirement(x) if not isinstance(x, Requirement) else x + for x in requires] appended = [] for patch in patchlist: From f83ccc6cee21763994fef5a740afc7f6b0d406c1 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Fri, 27 Jun 2025 12:27:44 -0400 Subject: [PATCH 08/10] Remove runtime changes --- src/rez/solver.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/rez/solver.py b/src/rez/solver.py index 62e47c0c9..55656f9ee 100644 --- a/src/rez/solver.py +++ b/src/rez/solver.py @@ -1086,9 +1086,6 @@ def reduce_by(self, package_request: Requirement) -> tuple[_PackageScope | None, if self.is_conflict or self.is_ephemeral: return (self, []) - assert self.variant_slice is not None, \ - "variant_slice should always exist for non-conflicted non-ephemeral requests" - # perform the reduction new_slice, reductions = self.variant_slice.reduce_by(package_request) @@ -1299,7 +1296,7 @@ def _create_phase(status: SolverStatus | None = None) -> _ResolvePhase: # iteratively extract until no more extractions possible while True: self.pr.subheader("EXTRACTING:") - extracted_requests_ = [] + extracted_requests = [] # perform all possible extractions with self.solver.timed(self.solver.extraction_time): @@ -1308,7 +1305,7 @@ def _create_phase(status: SolverStatus | None = None) -> _ResolvePhase: scope_, extracted_request = scopes[i].extract() if extracted_request: - extracted_requests_.append(extracted_request) + extracted_requests.append(extracted_request) k = (scopes[i].package_name, extracted_request.name) extractions[k] = extracted_request self.solver.extractions_count += 1 @@ -1316,12 +1313,12 @@ def _create_phase(status: SolverStatus | None = None) -> _ResolvePhase: else: break - if not extracted_requests_: + if not extracted_requests: break # simplify extractions (there may be overlaps) self.pr.subheader("MERGE-EXTRACTIONS:") - extracted_requests = RequirementList(extracted_requests_) + extracted_requests = RequirementList(extracted_requests) if extracted_requests.conflict: # extractions are in conflict req1, req2 = extracted_requests.conflict @@ -1464,10 +1461,10 @@ def _create_phase(status: SolverStatus | None = None) -> _ResolvePhase: # A different order here wouldn't cause an invalid solve, however # rez solves must be deterministic, so this is why we sort. # - pending_reducts_ = sorted(pending_reducts) + pending_reducts = sorted(pending_reducts) - while pending_reducts_: - x, y = pending_reducts_.pop() + while pending_reducts: + x, y = pending_reducts.pop() if x == y: continue @@ -1484,7 +1481,7 @@ def _create_phase(status: SolverStatus | None = None) -> _ResolvePhase: # other scopes need to reduce against x again for j in all_scopes_i: if j != x: - pending_reducts_.append((j, x)) + pending_reducts.append((j, x)) changed_scopes_i = set() @@ -1974,12 +1971,12 @@ def __init__(self, self.optimised = optimised # these values are all set in _init() - self.phase_stack: list[_ResolvePhase] - self.failed_phase_list: list[_ResolvePhase] - self.depth_counts: dict - self.solve_begun: bool - self.solve_time: float - self.load_time: float + self.phase_stack: list[_ResolvePhase] = None + self.failed_phase_list: list[_ResolvePhase] = None + self.depth_counts: dict = None + self.solve_begun: bool = None + self.solve_time: float = None + self.load_time: float = None self.abort_reason: str | None = None self.callback_return: SolverCallbackReturn | None = None @@ -2320,7 +2317,7 @@ def dump(self) -> None: for i, phase in enumerate(self.phase_stack): rows.append((self._depth_label(i), phase.status, str(phase))) - print("status: %s (%s)" % (self.status.name, self.status.value[0])) + print("status: %s (%s)" % (self.status.name, self.status.description)) print("initial request: %s" % str(self.request_list)) print() print("solve stack:") From b8dd3583f4a636444ea9025fae5fa00c5dfc04e4 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Tue, 8 Jul 2025 18:30:57 -0400 Subject: [PATCH 09/10] Remove runtime changes --- src/rez/plugin_managers.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/rez/plugin_managers.py b/src/rez/plugin_managers.py index 64e7ec6bd..1e3f81573 100644 --- a/src/rez/plugin_managers.py +++ b/src/rez/plugin_managers.py @@ -373,9 +373,6 @@ def get_plugin_class(self, plugin_type: str, plugin_name: str, expected_type: ty """Return the class registered under the given plugin name.""" plugin = self._get_plugin_type(plugin_type) cls = plugin.get_plugin_class(plugin_name) - if expected_type is not None and not isinstance(cls, type) and issubclass(cls, expected_type): - raise RezPluginError("%s: Plugin class for %s was not the expected type: %s != %s" - % (plugin.pretty_type_name, plugin_name, cls, expected_type)) return cls def get_plugin_module(self, plugin_type: str, plugin_name: str) -> types.ModuleType: From faca3a9747c077f3f1827902374644964d6c09c7 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Tue, 8 Jul 2025 22:49:46 -0400 Subject: [PATCH 10/10] Revert changes to plugin manager --- mypy-baseline.txt | 128 +++++++++++++++++++--------------- src/rez/build_process.py | 2 +- src/rez/build_system.py | 6 +- src/rez/package_repository.py | 4 +- src/rez/plugin_managers.py | 41 +++++++++-- src/rez/release_vcs.py | 4 +- src/rez/shells.py | 2 +- 7 files changed, 116 insertions(+), 71 deletions(-) diff --git a/mypy-baseline.txt b/mypy-baseline.txt index c3f27d95e..f7174fa9f 100644 --- a/mypy-baseline.txt +++ b/mypy-baseline.txt @@ -137,6 +137,7 @@ src/rez/version/_requirement.py:0: error: Incompatible return value type (got "N src/rez/utils/data_utils.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "Tuple[Any, ...]") [assignment] src/rez/utils/data_utils.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "Dict[str, Any]") [assignment] src/rez/utils/data_utils.py:0: error: Unexpected keyword argument "name" for "property" [call-arg] +src/rez/utils/patching.py:0: error: No overload variant of "__setitem__" of "list" matches argument types "int", "None" [call-overload] src/rez/utils/scope.py:0: error: Module "typing" has no attribute "Self" [attr-defined] src/rez/util.py:0: error: Module "typing" has no attribute "TypeGuard" [attr-defined] src/rez/util.py:0: error: Argument 1 to "module_from_spec" has incompatible type "Optional[ModuleSpec]"; expected "ModuleSpec" [arg-type] @@ -168,6 +169,7 @@ src/rez/plugin_managers.py:0: error: Item "PathEntryFinderProtocol" of "Union[Me src/rez/plugin_managers.py:0: error: Value of type variable "AnyOrLiteralStr" of "dirname" cannot be "Optional[str]" [type-var] src/rez/plugin_managers.py:0: error: Item "MetaPathFinderProtocol" of "Union[MetaPathFinderProtocol, PathEntryFinderProtocol]" has no attribute "path" [union-attr] src/rez/plugin_managers.py:0: error: Item "PathEntryFinderProtocol" of "Union[MetaPathFinderProtocol, PathEntryFinderProtocol]" has no attribute "path" [union-attr] +src/rez/plugin_managers.py:0: error: Overloaded function signature 6 will never be matched: signature 4's parameter type(s) are the same or broader [overload-cannot-match] src/rez/plugin_managers.py:0: error: Argument 1 to "append" of "list" has incompatible type "Tuple[str, str, Union[Any, str], str]"; expected "List[str]" [arg-type] src/rez/plugin_managers.py:0: error: Argument 1 to "append" of "list" has incompatible type "Tuple[str, str, str, str]"; expected "List[str]" [arg-type] src/rez/utils/resources.py:0: error: Module "typing" has no attribute "Self" [attr-defined] @@ -193,6 +195,7 @@ src/rez/package_resources.py:0: error: "VariantResourceHelper" has no attribute src/rez/package_resources.py:0: error: "VariantResourceHelper" has no attribute "base" [attr-defined] src/rez/package_resources.py:0: error: Argument 2 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] src/rez/package_resources.py:0: error: "PackageRepositoryResource" has no attribute "variants" [attr-defined] +src/rez/package_repository.py:0: error: "Type[PackageRepository]" has no attribute "create_repository" [attr-defined] src/rez/package_repository.py:0: error: "PackageFamilyResource" has no attribute "iter_packages" [attr-defined] src/rez/package_repository.py:0: error: Argument 1 to "make_resource_handle" of "PackageRepository" has incompatible type "Union[str, Type[Resource]]"; expected "str" [arg-type] src/rez/package_repository.py:0: error: Incompatible types in assignment (expression has type "Tuple[str, str]", variable has type "List[str]") [assignment] @@ -266,17 +269,31 @@ src/rez/solver.py:0: error: Item "None" of "Optional[Requirement]" has no attrib src/rez/solver.py:0: error: Item "None" of "Optional[Requirement]" has no attribute "range" [union-attr] src/rez/solver.py:0: error: Expected iterable as variadic argument [misc] src/rez/solver.py:0: error: Item "None" of "Optional[_PackageVariantSlice]" has no attribute "intersect" [union-attr] +src/rez/solver.py:0: error: Item "None" of "Optional[_PackageVariantSlice]" has no attribute "reduce_by" [union-attr] src/rez/solver.py:0: error: Item "None" of "Optional[_PackageVariantSlice]" has no attribute "extract" [union-attr] src/rez/solver.py:0: error: Argument 1 to "len" has incompatible type "Optional[_PackageVariantSlice]"; expected "Sized" [arg-type] src/rez/solver.py:0: error: Item "None" of "Optional[_PackageVariantSlice]" has no attribute "split" [union-attr] src/rez/solver.py:0: error: Argument 1 to "len" has incompatible type "Optional[_PackageVariantSlice]"; expected "Sized" [arg-type] src/rez/solver.py:0: error: Item "None" of "Optional[_PackageVariantSlice]" has no attribute "extractable" [union-attr] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "RequirementList", variable has type "List[Requirement]") [assignment] +src/rez/solver.py:0: error: "List[Requirement]" has no attribute "conflict" [attr-defined] +src/rez/solver.py:0: error: "List[Requirement]" has no attribute "conflict" [attr-defined] +src/rez/solver.py:0: error: "List[Requirement]" has no attribute "get" [attr-defined] src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "Optional[_PackageScope]", variable has type "_PackageScope") [assignment] +src/rez/solver.py:0: error: "List[Requirement]" has no attribute "requirements" [attr-defined] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "List[Tuple[int, int]]", variable has type "Set[Tuple[int, int]]") [assignment] +src/rez/solver.py:0: error: "Set[Tuple[int, int]]" has no attribute "append" [attr-defined] src/rez/solver.py:0: error: Item "None" of "Optional[PackageVariant]" has no attribute "version" [union-attr] src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "dict_values[Any, Any]", variable has type "List[Any]") [assignment] src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "dict_values[str, _PackageScope]", variable has type "List[_PackageScope]") [assignment] src/rez/solver.py:0: error: Argument 1 to "_get_dependency_order" has incompatible type "Optional[digraph]"; expected "digraph" [arg-type] src/rez/solver.py:0: error: Argument 1 to "_add_request_node" has incompatible type "Optional[Requirement]"; expected "Requirement" [arg-type] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "List[_ResolvePhase]") [assignment] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "List[_ResolvePhase]") [assignment] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "Dict[Any, Any]") [assignment] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "bool") [assignment] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "float") [assignment] +src/rez/solver.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "float") [assignment] src/rez/solver.py:0: error: Item "None" of "Optional[_ResolvePhase]" has no attribute "get_graph" [union-attr] src/rez/package_order.py:0: error: Module "typing" has no attribute "Self" [attr-defined] src/rez/package_order.py:0: error: "object" has no attribute "main_comparable" [attr-defined] @@ -353,10 +370,46 @@ src/rez/suite.py:0: error: Item "None" of "Optional[defaultdict[str, List[Tool]] src/rez/suite.py:0: error: Argument 1 to "append" of "list" has incompatible type "Tuple[str, str, str, str, str]"; expected "List[str]" [arg-type] src/rez/suite.py:0: error: Argument 1 to "append" of "list" has incompatible type "Callable[[Any], Any]"; expected "None" [arg-type] src/rez/suite.py:0: error: Argument 1 to "append" of "list" has incompatible type "Callable[[Any], Any]"; expected "None" [arg-type] +src/rez/build_process.py:0: error: Item "None" of "Optional[ReleaseVCS]" has no attribute "get_changelog" [union-attr] +src/rez/build_system.py:0: error: Unsupported operand types for - ("Set[Type[BuildSystem]]" and "Set[Optional[str]]") [operator] src/rezplugins/shell/cmd.py:0: error: Signature of "spawn_shell" incompatible with supertype "Shell" [override] src/rezplugins/shell/cmd.py:0: error: "setenv" of "CMD" does not return a value (it only ever returns None) [func-returns-value] src/rezplugins/shell/_utils/powershell_base.py:0: error: Signature of "spawn_shell" incompatible with supertype "Shell" [override] src/rezplugins/shell/_utils/powershell_base.py:0: error: "setenv" of "PowerShellBase" does not return a value (it only ever returns None) [func-returns-value] +src/rezplugins/release_vcs/hg.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] +src/rezplugins/release_vcs/git.py:0: error: Item "None" of "Optional[Any]" has no attribute "allow_no_upstream" [union-attr] +src/rezplugins/release_vcs/git.py:0: error: Name "retain_cwd" is not defined [name-defined] +src/rezplugins/release_vcs/git.py:0: error: Name "git" is not defined [name-defined] +src/rezplugins/release_vcs/git.py:0: error: Name "git" is not defined [name-defined] +src/rezplugins/release_hook/emailer.py:0: error: Signature of "post_release" incompatible with supertype "ReleaseHook" [override] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "body" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "subject" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "recipients" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "smtp_host" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "sender" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "smtp_host" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "smtp_port" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "sender" [union-attr] +src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "recipients" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "print_error" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "print_output" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Signature of "pre_build" incompatible with supertype "ReleaseHook" [override] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "pre_build_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "cancel_on_error" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Signature of "pre_release" incompatible with supertype "ReleaseHook" [override] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "pre_release_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "cancel_on_error" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Signature of "post_release" incompatible with supertype "ReleaseHook" [override] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "post_release_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Argument 1 to "append" of "list" has incompatible type "Dict[Any, Any]"; expected "int" [arg-type] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "print_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "print_commands" [union-attr] +src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "stop_on_error" [union-attr] +src/rezplugins/release_hook/amqp.py:0: error: Signature of "post_release" incompatible with supertype "ReleaseHook" [override] +src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Optional[Any]" has no attribute "message_attributes" [union-attr] +src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Optional[Any]" has no attribute "host" [union-attr] +src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Optional[Any]" has no attribute "exchange_routing_key" [union-attr] +src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Optional[Any]" has no attribute "host" [union-attr] src/rezplugins/package_repository/filesystem.py:0: error: Module "typing" has no attribute "Self" [attr-defined] src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "Optional[str]"; expected "str" [arg-type] @@ -436,6 +489,18 @@ src/rezplugins/package_repository/filesystem.py:0: error: Item "None" of "Option src/rezplugins/package_repository/filesystem.py:0: error: "VariantResource" has no attribute "variant_requires" [attr-defined] src/rezplugins/package_repository/filesystem.py:0: error: Argument 2 to "join" has incompatible type "Union[Any, str, None]"; expected "str" [arg-type] src/rezplugins/package_repository/filesystem.py:0: error: Argument 1 to "_on_changed" of "FileSystemPackageRepository" has incompatible type "Union[Any, str, None]"; expected "str" [arg-type] +src/rezplugins/build_system/custom.py:0: error: "Popen[Any]" object is not iterable [misc] +src/rezplugins/build_system/custom.py:0: error: Cannot determine type of "retcode" [has-type] +src/rezplugins/build_system/custom.py:0: error: "Popen[Any]" object is not iterable [misc] +src/rezplugins/build_system/custom.py:0: error: Cannot determine type of "retcode" [has-type] +src/rezplugins/build_system/cmake.py:0: error: "Popen[Any]" object is not iterable [misc] +src/rezplugins/build_system/cmake.py:0: error: Cannot determine type of "retcode" [has-type] +src/rezplugins/build_system/cmake.py:0: error: "Popen[Any]" object is not iterable [misc] +src/rezplugins/build_system/cmake.py:0: error: Cannot determine type of "retcode'" [has-type] +src/rezplugins/build_system/cmake.py:0: error: "Popen[Any]" object is not iterable [misc] +src/rezplugins/build_system/cmake.py:0: error: Cannot determine type of "retcode'" [has-type] +src/rezplugins/build_system/cmake.py:0: error: "Popen[Any]" object is not iterable [misc] +src/rezplugins/build_system/cmake.py:0: error: Cannot determine type of "retcode" [has-type] src/rez/status.py:0: error: Incompatible types in assignment (expression has type "List[Never]", variable has type "str") [assignment] src/rez/status.py:0: error: "str" has no attribute "append" [attr-defined] src/rez/status.py:0: error: Argument 1 to "append" of "list" has incompatible type "List[Any]"; expected "Tuple[str, str, str, str, str, Optional[Callable[[str], str]]]" [arg-type] @@ -469,7 +534,6 @@ src/rez/package_copy.py:0: error: Item "None" of "Optional[Variant]" has no attr src/rez/package_bind.py:0: error: List comprehension has incompatible type List[Tuple[Any, Any]]; expected List[List[str]] [misc] src/rez/utils/pip.py:0: error: Incompatible types in assignment (expression has type "Optional[VersionRange]", variable has type "VersionRange") [assignment] src/rez/utils/installer.py:0: error: Name "env" is not defined [name-defined] -src/rez/utils/diff_packages.py:0: error: "type" has no attribute "export" [attr-defined] src/rez/cli/pkg-cache.py:0: error: Argument 1 to "append" of "list" has incompatible type "Callable[[Any], Any]"; expected "str" [arg-type] src/rez/cli/pkg-cache.py:0: error: Argument 2 to "print_colored_columns" has incompatible type "List[List[Any]]"; expected "Sequence[Tuple[Any, ...]]" [arg-type] src/rez/cli/memcache.py:0: error: "PackageFamily" has no attribute "name" [attr-defined] @@ -494,42 +558,13 @@ src/rez/cli/benchmark.py:0: error: Argument 1 to "join" has incompatible type "O src/rez/cli/_util.py:0: error: Module has no attribute "CTRL_C_EVENT" [attr-defined] src/rez/cli/_complete_util.py:0: error: Incompatible types in assignment (expression has type "Generator[str, None, None]", variable has type "List[str]") [assignment] src/rezplugins/shell/zsh.py:0: error: Incompatible types in assignment (expression has type "None", base class "UnixShell" defined the type as "str") [assignment] -src/rezplugins/release_vcs/hg.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] -src/rezplugins/release_vcs/git.py:0: error: Item "None" of "Optional[Any]" has no attribute "allow_no_upstream" [union-attr] -src/rezplugins/release_vcs/git.py:0: error: Name "retain_cwd" is not defined [name-defined] -src/rezplugins/release_vcs/git.py:0: error: Name "git" is not defined [name-defined] -src/rezplugins/release_vcs/git.py:0: error: Name "git" is not defined [name-defined] -src/rezplugins/release_hook/emailer.py:0: error: Signature of "post_release" incompatible with supertype "ReleaseHook" [override] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "body" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "subject" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "recipients" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "smtp_host" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "sender" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "smtp_host" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "smtp_port" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "sender" [union-attr] -src/rezplugins/release_hook/emailer.py:0: error: Item "None" of "Optional[Any]" has no attribute "recipients" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "print_error" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "print_output" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Signature of "pre_build" incompatible with supertype "ReleaseHook" [override] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "pre_build_commands" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "cancel_on_error" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Signature of "pre_release" incompatible with supertype "ReleaseHook" [override] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "pre_release_commands" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "cancel_on_error" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Signature of "post_release" incompatible with supertype "ReleaseHook" [override] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "post_release_commands" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Argument 1 to "append" of "list" has incompatible type "Dict[Any, Any]"; expected "int" [arg-type] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "print_commands" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "print_commands" [union-attr] -src/rezplugins/release_hook/command.py:0: error: Item "None" of "Optional[Any]" has no attribute "stop_on_error" [union-attr] -src/rezplugins/release_hook/amqp.py:0: error: Signature of "post_release" incompatible with supertype "ReleaseHook" [override] -src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Optional[Any]" has no attribute "message_attributes" [union-attr] -src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Optional[Any]" has no attribute "host" [union-attr] -src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Optional[Any]" has no attribute "exchange_routing_key" [union-attr] -src/rezplugins/release_hook/amqp.py:0: error: Item "None" of "Optional[Any]" has no attribute "host" [union-attr] -src/rez/build_process.py:0: error: Item "None" of "Optional[ReleaseVCS]" has no attribute "get_changelog" [union-attr] -src/rez/build_system.py:0: error: Unsupported operand types for - ("Set[Type[BuildSystem]]" and "Set[Optional[str]]") [operator] +src/rezplugins/build_process/local.py:0: error: Argument 1 to "join" of "str" has incompatible type "List[Optional[str]]"; expected "Iterable[str]" [arg-type] +src/rezplugins/build_process/local.py:0: error: Argument 1 to "pre_variant_install" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] +src/rezplugins/build_process/local.py:0: error: Value of type "Optional[Dict[str, Any]]" is not indexable [index] +src/rezplugins/build_process/local.py:0: error: Argument 1 to "get_repository" of "PackageRepositoryManager" has incompatible type "Optional[str]"; expected "str" [arg-type] +src/rezplugins/build_process/local.py:0: error: Argument 1 to "on_variant_install_cancelled" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] +src/rezplugins/build_process/local.py:0: error: Argument 1 to "on_variant_install_cancelled" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] +src/rez/cli/release.py:0: error: Name "raw_input" is not defined [name-defined] src/rez/cli/interpret.py:0: error: Incompatible types in assignment (expression has type "Python", variable has type "Optional[Shell]") [assignment] src/rez/cli/help.py:0: error: Item "None" of "Optional[Package]" has no attribute "uri" [union-attr] src/rez/cli/help.py:0: error: Item "None" of "Optional[Package]" has no attribute "description" [union-attr] @@ -546,26 +581,7 @@ src/rez/bind/python.py:0: error: Name "this" is not defined [name-defined] src/rez/bind/_pymodule.py:0: error: Name "env" is not defined [name-defined] src/rez/bind/_pymodule.py:0: error: Name "env" is not defined [name-defined] src/rez/bind/_pymodule.py:0: error: Name "env" is not defined [name-defined] -src/rezplugins/build_system/custom.py:0: error: "Popen[Any]" object is not iterable [misc] -src/rezplugins/build_system/custom.py:0: error: Cannot determine type of "retcode" [has-type] -src/rezplugins/build_system/custom.py:0: error: "Popen[Any]" object is not iterable [misc] -src/rezplugins/build_system/custom.py:0: error: Cannot determine type of "retcode" [has-type] -src/rezplugins/build_system/cmake.py:0: error: "Popen[Any]" object is not iterable [misc] -src/rezplugins/build_system/cmake.py:0: error: Cannot determine type of "retcode" [has-type] -src/rezplugins/build_system/cmake.py:0: error: "Popen[Any]" object is not iterable [misc] -src/rezplugins/build_system/cmake.py:0: error: Cannot determine type of "retcode'" [has-type] -src/rezplugins/build_system/cmake.py:0: error: "Popen[Any]" object is not iterable [misc] -src/rezplugins/build_system/cmake.py:0: error: Cannot determine type of "retcode'" [has-type] -src/rezplugins/build_system/cmake.py:0: error: "Popen[Any]" object is not iterable [misc] -src/rezplugins/build_system/cmake.py:0: error: Cannot determine type of "retcode" [has-type] -src/rezplugins/build_process/local.py:0: error: Argument 1 to "join" of "str" has incompatible type "List[Optional[str]]"; expected "Iterable[str]" [arg-type] -src/rezplugins/build_process/local.py:0: error: Argument 1 to "pre_variant_install" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] -src/rezplugins/build_process/local.py:0: error: Value of type "Optional[Dict[str, Any]]" is not indexable [index] -src/rezplugins/build_process/local.py:0: error: Argument 1 to "get_repository" of "PackageRepositoryManager" has incompatible type "Optional[str]"; expected "str" [arg-type] -src/rezplugins/build_process/local.py:0: error: Argument 1 to "on_variant_install_cancelled" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] -src/rezplugins/build_process/local.py:0: error: Argument 1 to "on_variant_install_cancelled" of "PackageRepository" has incompatible type "Resource"; expected "VariantResource" [arg-type] src/rez/pip.py:0: error: Item "None" of "Optional[Variant]" has no attribute "parent" [union-attr] -src/rez/cli/release.py:0: error: Name "raw_input" is not defined [name-defined] src/rez/cli/pip.py:0: error: "Config" has no attribute "debug_package_release" [attr-defined] src/rez/cli/selftest.py:0: error: Argument 1 to "getfile" has incompatible type "Optional[FrameType]"; expected "Union[Module, Type[Any], MethodType, FunctionType, TracebackType, FrameType, CodeType, Callable[..., Any]]" [arg-type] src/rez/cli/selftest.py:0: error: Item "MetaPathFinderProtocol" of "Union[MetaPathFinderProtocol, PathEntryFinderProtocol]" has no attribute "find_module" [union-attr] diff --git a/src/rez/build_process.py b/src/rez/build_process.py index 4802e1d84..2471cb221 100644 --- a/src/rez/build_process.py +++ b/src/rez/build_process.py @@ -60,7 +60,7 @@ def create_build_process(process_type: str, if process_type not in process_types: raise BuildProcessError("Unknown build process: %r" % process_type) - cls = plugin_manager.get_plugin_class('build_process', process_type, BuildProcess) + cls = plugin_manager.get_plugin_class('build_process', process_type) return cls(working_dir, # ignored (deprecated) build_system, diff --git a/src/rez/build_system.py b/src/rez/build_system.py index 503983a39..beff3519d 100644 --- a/src/rez/build_system.py +++ b/src/rez/build_system.py @@ -69,13 +69,13 @@ def get_valid_build_systems(working_dir: str, # package explicitly specifies build system if buildsys_name: - cls = plugin_manager.get_plugin_class('build_system', buildsys_name, BuildSystem) + cls = plugin_manager.get_plugin_class('build_system', buildsys_name) return [cls] # detect valid build systems clss = [] for buildsys_name_ in get_buildsys_types(): - cls = plugin_manager.get_plugin_class('build_system', buildsys_name_, BuildSystem) + cls = plugin_manager.get_plugin_class('build_system', buildsys_name_) if cls.is_valid_root(working_dir, package=package): clss.append(cls) @@ -115,7 +115,7 @@ def create_build_system(working_dir: str, buildsys_type = next(iter(clss)).name() # create instance of build system - cls_ = plugin_manager.get_plugin_class('build_system', buildsys_type, BuildSystem) + cls_ = plugin_manager.get_plugin_class('build_system', buildsys_type) return cls_(working_dir, opts=opts, diff --git a/src/rez/package_repository.py b/src/rez/package_repository.py index b0962bd30..53f8d2d85 100644 --- a/src/rez/package_repository.py +++ b/src/rez/package_repository.py @@ -40,7 +40,7 @@ def create_memory_package_repository(repository_data: dict) -> MemoryPackageRepo `PackageRepository` object. """ from rezplugins.package_repository.memory import MemoryPackageRepository # noqa - cls_ = plugin_manager.get_plugin_class("package_repository", "memory", MemoryPackageRepository) + cls_ = plugin_manager.get_plugin_class("package_repository", "memory") return cls_.create_repository(repository_data) @@ -658,7 +658,7 @@ def clear_caches(self) -> None: def _get_repository(self, path: str, **repo_args: Any) -> PackageRepository: repo_type, location = path.split('@', 1) - cls = plugin_manager.get_plugin_class('package_repository', repo_type, PackageRepository) + cls = plugin_manager.get_plugin_class('package_repository', repo_type) repo = cls(location, self.pool, **repo_args) return repo diff --git a/src/rez/plugin_managers.py b/src/rez/plugin_managers.py index 1e3f81573..b2ff8a37c 100644 --- a/src/rez/plugin_managers.py +++ b/src/rez/plugin_managers.py @@ -14,12 +14,22 @@ from rez.utils.logging_ import print_debug, print_warning from rez.exceptions import RezPluginError from zipimport import zipimporter -from typing import overload, Any, TypeVar +from typing import overload, Any, TypeVar, TYPE_CHECKING import pkgutil import os.path import sys import types +if TYPE_CHECKING: + from typing import Literal # not available in typing module until 3.8 + from rez.shells import Shell + from rez.release_vcs import ReleaseVCS + from rez.release_hook import ReleaseHook + from rez.build_process import BuildProcess + from rez.build_system import BuildSystem + from rez.package_repository import PackageRepository + from rez.command import Command + T = TypeVar("T") @@ -362,18 +372,37 @@ def get_plugins(self, plugin_type: str) -> list[str]: return list(self._get_plugin_type(plugin_type).plugin_classes.keys()) @overload - def get_plugin_class(self, plugin_type: str, plugin_name: str) -> type: + def get_plugin_class(self, plugin_type: Literal["shell"], plugin_name: str) -> type[Shell]: + pass + + @overload + def get_plugin_class(self, plugin_type: Literal["release_vcs"], plugin_name: str) -> type[ReleaseVCS]: + pass + + @overload + def get_plugin_class(self, plugin_type: Literal["release_hook"], plugin_name: str) -> type[ReleaseHook]: + pass + + @overload + def get_plugin_class(self, plugin_type: Literal["package_repository"], plugin_name: str) -> type[PackageRepository]: + pass + + @overload + def get_plugin_class(self, plugin_type: Literal["build_system"], plugin_name: str) -> type[BuildSystem]: pass @overload - def get_plugin_class(self, plugin_type: str, plugin_name: str, expected_type: type[T]) -> type[T]: + def get_plugin_class(self, plugin_type: Literal["build_process"], plugin_name: str) -> type[BuildProcess]: pass - def get_plugin_class(self, plugin_type: str, plugin_name: str, expected_type: type | None = None) -> type: + @overload + def get_plugin_class(self, plugin_type: Literal["command"], plugin_name: str) -> type[Command]: + pass + + def get_plugin_class(self, plugin_type: str, plugin_name: str) -> type: """Return the class registered under the given plugin name.""" plugin = self._get_plugin_type(plugin_type) - cls = plugin.get_plugin_class(plugin_name) - return cls + return plugin.get_plugin_class(plugin_name) def get_plugin_module(self, plugin_type: str, plugin_name: str) -> types.ModuleType: """Return the module defining the class registered under the given diff --git a/src/rez/release_vcs.py b/src/rez/release_vcs.py index c9d861131..1869f99cd 100644 --- a/src/rez/release_vcs.py +++ b/src/rez/release_vcs.py @@ -27,12 +27,12 @@ def create_release_vcs(path: str, vcs_name: str | None = None) -> ReleaseVCS: if vcs_name: if vcs_name not in vcs_types: raise ReleaseVCSError("Unknown version control system: %r" % vcs_name) - cls = plugin_manager.get_plugin_class('release_vcs', vcs_name, expected_type=ReleaseVCS) + cls = plugin_manager.get_plugin_class('release_vcs', vcs_name) return cls(path) classes_by_level = {} for vcs_name in vcs_types: - cls = plugin_manager.get_plugin_class('release_vcs', vcs_name, expected_type=ReleaseVCS) + cls = plugin_manager.get_plugin_class('release_vcs', vcs_name) result = cls.find_vcs_root(path) if not result: continue diff --git a/src/rez/shells.py b/src/rez/shells.py index ff8608589..4178f046d 100644 --- a/src/rez/shells.py +++ b/src/rez/shells.py @@ -52,7 +52,7 @@ def get_shell_class(shell: str | None = None) -> type[Shell]: shell = system.shell from rez.plugin_managers import plugin_manager - return plugin_manager.get_plugin_class("shell", shell, Shell) + return plugin_manager.get_plugin_class("shell", shell) def create_shell(shell: str | None = None, **kwargs: Any) -> Shell: