Repository: python-poetry/poetry
Branch: main
Commit: 2c270050111b
Files: 949
Total size: 3.5 MB
Directory structure:
gitextract_5_4n1xc3/
├── .cirrus.yml
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── ---bug-report.yml
│ │ ├── ---documentation.yml
│ │ ├── ---feature-request.yml
│ │ └── config.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── actions/
│ │ ├── bootstrap-poetry/
│ │ │ └── action.yaml
│ │ └── poetry-install/
│ │ └── action.yaml
│ ├── dependabot.yml
│ ├── scripts/
│ │ └── backport.sh
│ └── workflows/
│ ├── .tests-matrix.yaml
│ ├── backport.yaml
│ ├── docs.yaml
│ ├── lock-threads.yaml
│ ├── release.yaml
│ └── tests.yaml
├── .gitignore
├── .pre-commit-config.yaml
├── .pre-commit-hooks.yaml
├── CHANGELOG.md
├── CITATION.cff
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── docs/
│ ├── _index.md
│ ├── basic-usage.md
│ ├── building-extension-modules.md
│ ├── cli.md
│ ├── community.md
│ ├── configuration.md
│ ├── contributing.md
│ ├── dependency-specification.md
│ ├── faq.md
│ ├── libraries.md
│ ├── managing-dependencies.md
│ ├── managing-environments.md
│ ├── plugins.md
│ ├── pre-commit-hooks.md
│ ├── pyproject.md
│ └── repositories.md
├── pyproject.toml
├── src/
│ └── poetry/
│ ├── __main__.py
│ ├── __version__.py
│ ├── config/
│ │ ├── __init__.py
│ │ ├── config.py
│ │ ├── config_source.py
│ │ ├── dict_config_source.py
│ │ ├── file_config_source.py
│ │ └── source.py
│ ├── console/
│ │ ├── __init__.py
│ │ ├── application.py
│ │ ├── command_loader.py
│ │ ├── commands/
│ │ │ ├── __init__.py
│ │ │ ├── about.py
│ │ │ ├── add.py
│ │ │ ├── build.py
│ │ │ ├── cache/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── clear.py
│ │ │ │ └── list.py
│ │ │ ├── check.py
│ │ │ ├── command.py
│ │ │ ├── config.py
│ │ │ ├── debug/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── info.py
│ │ │ │ ├── resolve.py
│ │ │ │ └── tags.py
│ │ │ ├── env/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── activate.py
│ │ │ │ ├── info.py
│ │ │ │ ├── list.py
│ │ │ │ ├── remove.py
│ │ │ │ └── use.py
│ │ │ ├── env_command.py
│ │ │ ├── group_command.py
│ │ │ ├── init.py
│ │ │ ├── install.py
│ │ │ ├── installer_command.py
│ │ │ ├── lock.py
│ │ │ ├── new.py
│ │ │ ├── publish.py
│ │ │ ├── python/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── install.py
│ │ │ │ ├── list.py
│ │ │ │ └── remove.py
│ │ │ ├── remove.py
│ │ │ ├── run.py
│ │ │ ├── search.py
│ │ │ ├── self/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── add.py
│ │ │ │ ├── install.py
│ │ │ │ ├── lock.py
│ │ │ │ ├── remove.py
│ │ │ │ ├── self_command.py
│ │ │ │ ├── show/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ └── plugins.py
│ │ │ │ ├── sync.py
│ │ │ │ └── update.py
│ │ │ ├── show.py
│ │ │ ├── source/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── add.py
│ │ │ │ ├── remove.py
│ │ │ │ └── show.py
│ │ │ ├── sync.py
│ │ │ ├── update.py
│ │ │ └── version.py
│ │ ├── events/
│ │ │ ├── __init__.py
│ │ │ └── console_events.py
│ │ ├── exceptions.py
│ │ └── logging/
│ │ ├── __init__.py
│ │ ├── filters.py
│ │ ├── formatters/
│ │ │ ├── __init__.py
│ │ │ ├── builder_formatter.py
│ │ │ └── formatter.py
│ │ ├── io_formatter.py
│ │ └── io_handler.py
│ ├── exceptions.py
│ ├── factory.py
│ ├── inspection/
│ │ ├── __init__.py
│ │ ├── info.py
│ │ └── lazy_wheel.py
│ ├── installation/
│ │ ├── __init__.py
│ │ ├── chef.py
│ │ ├── chooser.py
│ │ ├── executor.py
│ │ ├── installer.py
│ │ ├── operations/
│ │ │ ├── __init__.py
│ │ │ ├── install.py
│ │ │ ├── operation.py
│ │ │ ├── uninstall.py
│ │ │ └── update.py
│ │ └── wheel_installer.py
│ ├── json/
│ │ ├── __init__.py
│ │ └── schemas/
│ │ └── poetry.json
│ ├── layouts/
│ │ ├── __init__.py
│ │ ├── layout.py
│ │ └── src.py
│ ├── locations.py
│ ├── masonry/
│ │ ├── __init__.py
│ │ ├── api.py
│ │ └── builders/
│ │ ├── __init__.py
│ │ └── editable.py
│ ├── mixology/
│ │ ├── __init__.py
│ │ ├── assignment.py
│ │ ├── failure.py
│ │ ├── incompatibility.py
│ │ ├── incompatibility_cause.py
│ │ ├── partial_solution.py
│ │ ├── result.py
│ │ ├── set_relation.py
│ │ ├── term.py
│ │ └── version_solver.py
│ ├── packages/
│ │ ├── __init__.py
│ │ ├── dependency_package.py
│ │ ├── direct_origin.py
│ │ ├── locker.py
│ │ ├── package_collection.py
│ │ └── transitive_package_info.py
│ ├── plugins/
│ │ ├── __init__.py
│ │ ├── application_plugin.py
│ │ ├── base_plugin.py
│ │ ├── plugin.py
│ │ └── plugin_manager.py
│ ├── poetry.py
│ ├── publishing/
│ │ ├── __init__.py
│ │ ├── hash_manager.py
│ │ ├── publisher.py
│ │ └── uploader.py
│ ├── puzzle/
│ │ ├── __init__.py
│ │ ├── exceptions.py
│ │ ├── provider.py
│ │ ├── solver.py
│ │ └── transaction.py
│ ├── py.typed
│ ├── pyproject/
│ │ ├── __init__.py
│ │ └── toml.py
│ ├── repositories/
│ │ ├── __init__.py
│ │ ├── abstract_repository.py
│ │ ├── cached_repository.py
│ │ ├── exceptions.py
│ │ ├── http_repository.py
│ │ ├── installed_repository.py
│ │ ├── legacy_repository.py
│ │ ├── link_sources/
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ ├── html.py
│ │ │ └── json.py
│ │ ├── lockfile_repository.py
│ │ ├── parsers/
│ │ │ ├── __init__.py
│ │ │ ├── html_page_parser.py
│ │ │ └── pypi_search_parser.py
│ │ ├── pypi_repository.py
│ │ ├── repository.py
│ │ ├── repository_pool.py
│ │ └── single_page_repository.py
│ ├── toml/
│ │ ├── __init__.py
│ │ ├── exceptions.py
│ │ └── file.py
│ ├── utils/
│ │ ├── __init__.py
│ │ ├── _compat.py
│ │ ├── authenticator.py
│ │ ├── cache.py
│ │ ├── constants.py
│ │ ├── dependency_specification.py
│ │ ├── env/
│ │ │ ├── __init__.py
│ │ │ ├── base_env.py
│ │ │ ├── env_manager.py
│ │ │ ├── exceptions.py
│ │ │ ├── generic_env.py
│ │ │ ├── mock_env.py
│ │ │ ├── null_env.py
│ │ │ ├── python/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── exceptions.py
│ │ │ │ ├── installer.py
│ │ │ │ ├── manager.py
│ │ │ │ └── providers.py
│ │ │ ├── script_strings.py
│ │ │ ├── site_packages.py
│ │ │ ├── system_env.py
│ │ │ └── virtual_env.py
│ │ ├── extras.py
│ │ ├── helpers.py
│ │ ├── isolated_build.py
│ │ ├── log_utils.py
│ │ ├── password_manager.py
│ │ ├── patterns.py
│ │ ├── pip.py
│ │ ├── threading.py
│ │ └── wheel.py
│ ├── vcs/
│ │ ├── __init__.py
│ │ └── git/
│ │ ├── __init__.py
│ │ ├── backend.py
│ │ └── system.py
│ └── version/
│ ├── __init__.py
│ └── version_selector.py
└── tests/
├── __init__.py
├── config/
│ ├── __init__.py
│ ├── test_config.py
│ ├── test_config_source.py
│ ├── test_dict_config_source.py
│ ├── test_file_config_source.py
│ └── test_source.py
├── conftest.py
├── console/
│ ├── __init__.py
│ ├── commands/
│ │ ├── __init__.py
│ │ ├── cache/
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── test_clear.py
│ │ │ └── test_list.py
│ │ ├── conftest.py
│ │ ├── debug/
│ │ │ ├── __init__.py
│ │ │ ├── test_info.py
│ │ │ └── test_resolve.py
│ │ ├── env/
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── helpers.py
│ │ │ ├── test_activate.py
│ │ │ ├── test_info.py
│ │ │ ├── test_list.py
│ │ │ ├── test_remove.py
│ │ │ └── test_use.py
│ │ ├── python/
│ │ │ ├── __init__.py
│ │ │ ├── test_python_install.py
│ │ │ ├── test_python_list.py
│ │ │ └── test_python_remove.py
│ │ ├── self/
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── fixtures/
│ │ │ │ └── poetry-1.0.5-darwin.sha256sum
│ │ │ ├── test_add_plugins.py
│ │ │ ├── test_install.py
│ │ │ ├── test_remove_plugins.py
│ │ │ ├── test_self_command.py
│ │ │ ├── test_show.py
│ │ │ ├── test_show_plugins.py
│ │ │ ├── test_sync.py
│ │ │ ├── test_update.py
│ │ │ └── utils.py
│ │ ├── source/
│ │ │ ├── __init__.py
│ │ │ ├── conftest.py
│ │ │ ├── test_add.py
│ │ │ ├── test_remove.py
│ │ │ └── test_show.py
│ │ ├── test_about.py
│ │ ├── test_add.py
│ │ ├── test_build.py
│ │ ├── test_check.py
│ │ ├── test_config.py
│ │ ├── test_init.py
│ │ ├── test_install.py
│ │ ├── test_lock.py
│ │ ├── test_new.py
│ │ ├── test_publish.py
│ │ ├── test_remove.py
│ │ ├── test_run.py
│ │ ├── test_search.py
│ │ ├── test_show.py
│ │ ├── test_sync.py
│ │ ├── test_update.py
│ │ └── test_version.py
│ ├── conftest.py
│ ├── logging/
│ │ ├── __init__.py
│ │ ├── formatters/
│ │ │ ├── __init__.py
│ │ │ └── test_builder_formatter.py
│ │ └── test_io_formatter.py
│ ├── test_application.py
│ ├── test_application_command_not_found.py
│ ├── test_application_global_options.py
│ ├── test_application_removed_commands.py
│ ├── test_exceptions_console_message.py
│ └── test_exections_poetry_runtime_error.py
├── fixtures/
│ ├── bad_scripts_project/
│ │ ├── no_colon/
│ │ │ ├── README.rst
│ │ │ ├── pyproject.toml
│ │ │ └── simple_project/
│ │ │ └── __init__.py
│ │ └── too_many_colon/
│ │ ├── README.rst
│ │ ├── pyproject.toml
│ │ └── simple_project/
│ │ └── __init__.py
│ ├── build_constraints/
│ │ └── pyproject.toml
│ ├── build_constraints_empty/
│ │ └── pyproject.toml
│ ├── build_system_requires_not_available/
│ │ ├── README.rst
│ │ ├── pyproject.toml
│ │ └── simple_project/
│ │ └── __init__.py
│ ├── build_systems/
│ │ ├── core_from_git/
│ │ │ ├── README.md
│ │ │ ├── pyproject.toml
│ │ │ └── simple_project/
│ │ │ └── __init__.py
│ │ ├── core_in_range/
│ │ │ ├── README.md
│ │ │ ├── pyproject.toml
│ │ │ └── simple_project/
│ │ │ └── __init__.py
│ │ ├── core_not_in_range/
│ │ │ ├── README.md
│ │ │ ├── pyproject.toml
│ │ │ └── simple_project/
│ │ │ └── __init__.py
│ │ ├── has_build_script/
│ │ │ ├── README.md
│ │ │ ├── pyproject.toml
│ │ │ └── simple_project/
│ │ │ └── __init__.py
│ │ ├── multiple_build_deps/
│ │ │ ├── README.md
│ │ │ ├── pyproject.toml
│ │ │ └── simple_project/
│ │ │ └── __init__.py
│ │ ├── no_build_backend/
│ │ │ ├── README.md
│ │ │ ├── pyproject.toml
│ │ │ └── simple_project/
│ │ │ └── __init__.py
│ │ ├── no_build_system/
│ │ │ ├── README.md
│ │ │ ├── pyproject.toml
│ │ │ └── simple_project/
│ │ │ └── __init__.py
│ │ └── no_core/
│ │ ├── README.md
│ │ ├── pyproject.toml
│ │ └── simple_project/
│ │ └── __init__.py
│ ├── complete.toml
│ ├── deleted_directory_dependency/
│ │ └── pyproject.toml
│ ├── deleted_file_dependency/
│ │ └── pyproject.toml
│ ├── directory/
│ │ ├── project_with_transitive_directory_dependencies/
│ │ │ ├── project_with_transitive_directory_dependencies/
│ │ │ │ └── __init__.py
│ │ │ ├── pyproject.toml
│ │ │ └── setup.py
│ │ └── project_with_transitive_file_dependencies/
│ │ ├── inner-directory-project/
│ │ │ └── pyproject.toml
│ │ ├── project_with_transitive_file_dependencies/
│ │ │ └── __init__.py
│ │ └── pyproject.toml
│ ├── distributions/
│ │ ├── demo-0.1.0-py2.py3-none-any.whl
│ │ ├── demo-0.1.2-py2.py3-none-any.whl
│ │ ├── demo_invalid_record-0.1.0-py2.py3-none-any.whl
│ │ ├── demo_invalid_record2-0.1.0-py2.py3-none-any.whl
│ │ ├── demo_metadata_version_23-0.1.0-py2.py3-none-any.whl
│ │ ├── demo_metadata_version_24-0.1.0-py2.py3-none-any.whl
│ │ ├── demo_metadata_version_299-0.1.0-py2.py3-none-any.whl
│ │ ├── demo_metadata_version_unknown-0.1.0-py2.py3-none-any.whl
│ │ └── demo_missing_dist_info-0.1.0-py2.py3-none-any.whl
│ ├── excluded_subpackage/
│ │ ├── README.rst
│ │ ├── example/
│ │ │ ├── __init__.py
│ │ │ └── test/
│ │ │ ├── __init__.py
│ │ │ └── excluded.py
│ │ └── pyproject.toml
│ ├── extended_project/
│ │ ├── README.rst
│ │ ├── build.py
│ │ ├── extended_project/
│ │ │ └── __init__.py
│ │ └── pyproject.toml
│ ├── extended_project_without_setup/
│ │ ├── README.rst
│ │ ├── build.py
│ │ ├── extended_project/
│ │ │ └── __init__.py
│ │ └── pyproject.toml
│ ├── extended_with_no_setup/
│ │ ├── README.md
│ │ ├── build.py
│ │ ├── extended/
│ │ │ ├── __init__.py
│ │ │ └── extended.c
│ │ └── pyproject.toml
│ ├── git/
│ │ └── github.com/
│ │ ├── demo/
│ │ │ ├── demo/
│ │ │ │ ├── demo/
│ │ │ │ │ └── __init__.py
│ │ │ │ └── pyproject.toml
│ │ │ ├── namespace-package-one/
│ │ │ │ ├── namespace_package/
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ └── one/
│ │ │ │ │ └── __init__.py
│ │ │ │ └── setup.py
│ │ │ ├── no-dependencies/
│ │ │ │ ├── demo/
│ │ │ │ │ └── __init__.py
│ │ │ │ └── setup.py
│ │ │ ├── no-version/
│ │ │ │ ├── demo/
│ │ │ │ │ └── __init__.py
│ │ │ │ └── setup.py
│ │ │ ├── non-canonical-name/
│ │ │ │ ├── demo/
│ │ │ │ │ └── __init__.py
│ │ │ │ └── setup.py
│ │ │ ├── poetry-plugin/
│ │ │ │ ├── poetry_plugin/
│ │ │ │ │ └── __init__.py
│ │ │ │ └── pyproject.toml
│ │ │ ├── poetry-plugin2/
│ │ │ │ └── subdir/
│ │ │ │ ├── poetry_plugin/
│ │ │ │ │ └── __init__.py
│ │ │ │ └── pyproject.toml
│ │ │ ├── prerelease/
│ │ │ │ ├── prerelease/
│ │ │ │ │ └── __init__.py
│ │ │ │ └── pyproject.toml
│ │ │ ├── pyproject-demo/
│ │ │ │ ├── demo/
│ │ │ │ │ └── __init__.py
│ │ │ │ └── pyproject.toml
│ │ │ └── subdirectories/
│ │ │ ├── one/
│ │ │ │ ├── one/
│ │ │ │ │ └── __init__.py
│ │ │ │ └── pyproject.toml
│ │ │ ├── one-copy/
│ │ │ │ ├── one/
│ │ │ │ │ └── __init__.py
│ │ │ │ └── pyproject.toml
│ │ │ └── two/
│ │ │ ├── pyproject.toml
│ │ │ └── two/
│ │ │ └── __init__.py
│ │ └── forked_demo/
│ │ └── subdirectories/
│ │ ├── one/
│ │ │ ├── one/
│ │ │ │ └── __init__.py
│ │ │ └── pyproject.toml
│ │ ├── one-copy/
│ │ │ ├── one/
│ │ │ │ └── __init__.py
│ │ │ └── pyproject.toml
│ │ └── two/
│ │ ├── pyproject.toml
│ │ └── two/
│ │ └── __init__.py
│ ├── incompatible_lock/
│ │ └── pyproject.toml
│ ├── inspection/
│ │ ├── demo/
│ │ │ └── pyproject.toml
│ │ ├── demo_no_setup_pkg_info_no_deps/
│ │ │ ├── PKG-INFO
│ │ │ └── pyproject.toml
│ │ ├── demo_no_setup_pkg_info_no_deps_dynamic/
│ │ │ ├── PKG-INFO
│ │ │ └── pyproject.toml
│ │ ├── demo_no_setup_pkg_info_no_deps_for_sure/
│ │ │ ├── PKG-INFO
│ │ │ └── pyproject.toml
│ │ ├── demo_only_requires_txt.egg-info/
│ │ │ ├── PKG-INFO
│ │ │ └── requires.txt
│ │ ├── demo_poetry_package/
│ │ │ └── pyproject.toml
│ │ └── demo_with_obsolete_egg_info/
│ │ ├── demo-0.1.0.egg-info/
│ │ │ ├── PKG-INFO
│ │ │ └── requires.txt
│ │ └── pyproject.toml
│ ├── invalid_lock/
│ │ └── pyproject.toml
│ ├── invalid_pyproject/
│ │ └── pyproject.toml
│ ├── invalid_pyproject_dep_name/
│ │ └── pyproject.toml
│ ├── missing_directory_dependency/
│ │ └── pyproject.toml
│ ├── missing_extra_directory_dependency/
│ │ └── pyproject.toml
│ ├── missing_file_dependency/
│ │ └── pyproject.toml
│ ├── nameless_pyproject/
│ │ └── pyproject.toml
│ ├── no_name_project/
│ │ ├── README.rst
│ │ └── pyproject.toml
│ ├── non_package_mode/
│ │ └── pyproject.toml
│ ├── old_lock/
│ │ └── pyproject.toml
│ ├── old_lock_path_dependency/
│ │ ├── pyproject.toml
│ │ └── quix/
│ │ └── pyproject.toml
│ ├── outdated_lock/
│ │ └── pyproject.toml
│ ├── private_pyproject/
│ │ └── pyproject.toml
│ ├── project_plugins/
│ │ ├── my_application_plugin-1.0.dist-info/
│ │ │ ├── METADATA
│ │ │ └── entry_points.txt
│ │ ├── my_application_plugin-2.0.dist-info/
│ │ │ ├── METADATA
│ │ │ └── entry_points.txt
│ │ ├── my_other_plugin-1.0.dist-info/
│ │ │ ├── METADATA
│ │ │ └── entry_points.txt
│ │ ├── pyproject.toml
│ │ ├── some_lib-1.0.dist-info/
│ │ │ └── METADATA
│ │ └── some_lib-2.0.dist-info/
│ │ └── METADATA
│ ├── project_with_extras/
│ │ ├── project_with_extras/
│ │ │ └── __init__.py
│ │ └── pyproject.toml
│ ├── project_with_git_dev_dependency/
│ │ └── pyproject.toml
│ ├── project_with_local_dependencies/
│ │ └── pyproject.toml
│ ├── project_with_multi_constraints_dependency/
│ │ ├── project/
│ │ │ └── __init__.py
│ │ └── pyproject.toml
│ ├── project_with_nested_local/
│ │ ├── bar/
│ │ │ └── pyproject.toml
│ │ ├── foo/
│ │ │ └── pyproject.toml
│ │ ├── pyproject.toml
│ │ └── quix/
│ │ └── pyproject.toml
│ ├── project_with_setup/
│ │ ├── my_package/
│ │ │ └── __init__.py
│ │ └── setup.py
│ ├── project_with_setup_calls_script/
│ │ ├── my_package/
│ │ │ └── __init__.py
│ │ ├── pyproject.toml
│ │ └── setup.py
│ ├── pypi_reference/
│ │ └── pyproject.toml
│ ├── sample_project/
│ │ ├── README.rst
│ │ └── pyproject.toml
│ ├── scripts/
│ │ ├── README.md
│ │ ├── pyproject.toml
│ │ └── scripts/
│ │ ├── __init__.py
│ │ ├── check_argv0.py
│ │ ├── exit_code.py
│ │ └── return_code.py
│ ├── self_version_not_ok/
│ │ └── pyproject.toml
│ ├── self_version_not_ok_invalid_config/
│ │ └── pyproject.toml
│ ├── self_version_ok/
│ │ └── pyproject.toml
│ ├── simple_project/
│ │ ├── LICENSE
│ │ ├── README.rst
│ │ ├── dist/
│ │ │ └── simple_project-1.2.3-py2.py3-none-any.whl
│ │ ├── pyproject.toml
│ │ └── simple_project/
│ │ └── __init__.py
│ ├── simple_project_legacy/
│ │ ├── LICENSE
│ │ ├── README.rst
│ │ ├── pyproject.toml
│ │ └── simple_project/
│ │ └── __init__.py
│ ├── up_to_date_lock/
│ │ └── pyproject.toml
│ ├── up_to_date_lock_non_package/
│ │ └── pyproject.toml
│ ├── wheel_with_no_requires_dist/
│ │ └── demo-0.1.0-py2.py3-none-any.whl
│ ├── with-include/
│ │ ├── LICENSE
│ │ ├── README.rst
│ │ ├── extra_dir/
│ │ │ ├── README.md
│ │ │ ├── __init__.py
│ │ │ ├── sub_pkg/
│ │ │ │ ├── __init__.py
│ │ │ │ └── vcs_excluded.txt
│ │ │ └── vcs_excluded.txt
│ │ ├── for_wheel_only/
│ │ │ └── __init__.py
│ │ ├── my_module.py
│ │ ├── notes.txt
│ │ ├── package_with_include/
│ │ │ └── __init__.py
│ │ ├── pyproject.toml
│ │ ├── src/
│ │ │ └── src_package/
│ │ │ └── __init__.py
│ │ └── tests/
│ │ └── __init__.py
│ ├── with_conditional_path_deps/
│ │ ├── demo_one/
│ │ │ └── pyproject.toml
│ │ ├── demo_two/
│ │ │ └── pyproject.toml
│ │ └── pyproject.toml
│ ├── with_explicit_pypi_and_other/
│ │ └── pyproject.toml
│ ├── with_explicit_pypi_and_other_explicit/
│ │ └── pyproject.toml
│ ├── with_explicit_pypi_no_other/
│ │ └── pyproject.toml
│ ├── with_explicit_source/
│ │ └── pyproject.toml
│ ├── with_local_config/
│ │ ├── README.rst
│ │ ├── poetry.toml
│ │ └── pyproject.toml
│ ├── with_multiple_dist_dir/
│ │ ├── README.rst
│ │ ├── dist/
│ │ │ └── simple_project-1.2.3-py2.py3-none-any.whl
│ │ ├── other_dist/
│ │ │ └── dist/
│ │ │ └── simple_project-1.2.3-py2.py3-none-any.whl
│ │ ├── pyproject.toml
│ │ └── simple_project/
│ │ └── __init__.py
│ ├── with_multiple_readme_files/
│ │ ├── README-1.rst
│ │ ├── README-2.rst
│ │ ├── my_package/
│ │ │ └── __init__.py
│ │ └── pyproject.toml
│ ├── with_multiple_sources/
│ │ └── pyproject.toml
│ ├── with_multiple_sources_pypi/
│ │ └── pyproject.toml
│ ├── with_multiple_supplemental_sources/
│ │ └── pyproject.toml
│ ├── with_path_dependency/
│ │ ├── bazz/
│ │ │ └── pyproject.toml
│ │ └── pyproject.toml
│ ├── with_primary_source_explicit/
│ │ └── pyproject.toml
│ ├── with_primary_source_implicit/
│ │ └── pyproject.toml
│ ├── with_source/
│ │ ├── README.rst
│ │ └── pyproject.toml
│ └── with_supplemental_source/
│ └── pyproject.toml
├── helpers.py
├── inspection/
│ ├── __init__.py
│ ├── test_info.py
│ └── test_lazy_wheel.py
├── installation/
│ ├── __init__.py
│ ├── conftest.py
│ ├── fixtures/
│ │ ├── extras-with-dependencies.test
│ │ ├── extras.test
│ │ ├── install-no-dev.test
│ │ ├── no-dependencies.test
│ │ ├── remove.test
│ │ ├── update-with-lock.test
│ │ ├── update-with-locked-extras.test
│ │ ├── with-conditional-dependency.test
│ │ ├── with-conflicting-dependency-extras-root.test
│ │ ├── with-conflicting-dependency-extras-transitive.test
│ │ ├── with-dependencies-differing-extras.test
│ │ ├── with-dependencies-extras.test
│ │ ├── with-dependencies-nested-extras.test
│ │ ├── with-dependencies.test
│ │ ├── with-directory-dependency-poetry-transitive.test
│ │ ├── with-directory-dependency-poetry.test
│ │ ├── with-directory-dependency-setuptools.test
│ │ ├── with-duplicate-dependencies-update.test
│ │ ├── with-duplicate-dependencies.test
│ │ ├── with-exclusive-extras.test
│ │ ├── with-file-dependency-transitive.test
│ │ ├── with-file-dependency.test
│ │ ├── with-multiple-updates.test
│ │ ├── with-optional-dependencies.test
│ │ ├── with-platform-dependencies.test
│ │ ├── with-prereleases.test
│ │ ├── with-pypi-repository.test
│ │ ├── with-python-versions.test
│ │ ├── with-same-version-url-dependencies.test
│ │ ├── with-self-referencing-extras-all-deep.test
│ │ ├── with-self-referencing-extras-all-top.test
│ │ ├── with-self-referencing-extras-b-markers.test
│ │ ├── with-self-referencing-extras-deep.test
│ │ ├── with-self-referencing-extras-download-deep.test
│ │ ├── with-self-referencing-extras-download-top.test
│ │ ├── with-self-referencing-extras-install-deep.test
│ │ ├── with-self-referencing-extras-install-download-deep.test
│ │ ├── with-self-referencing-extras-install-download-top.test
│ │ ├── with-self-referencing-extras-install-top.test
│ │ ├── with-self-referencing-extras-nested-deep.test
│ │ ├── with-self-referencing-extras-nested-top.test
│ │ ├── with-self-referencing-extras-top.test
│ │ ├── with-sub-dependencies.test
│ │ ├── with-url-dependency.test
│ │ ├── with-vcs-dependency-with-extras.test
│ │ ├── with-vcs-dependency-without-ref.test
│ │ └── with-wheel-dependency-no-requires-dist.test
│ ├── test_chef.py
│ ├── test_chooser.py
│ ├── test_chooser_errors.py
│ ├── test_executor.py
│ ├── test_installer.py
│ └── test_wheel_installer.py
├── integration/
│ ├── __init__.py
│ └── test_utils_vcs_git.py
├── json/
│ ├── __init__.py
│ ├── fixtures/
│ │ ├── build_constraints.toml
│ │ ├── self_invalid_plugin.toml
│ │ ├── self_invalid_version.toml
│ │ ├── self_valid.toml
│ │ └── source/
│ │ ├── complete_invalid_priority.toml
│ │ ├── complete_invalid_url.toml
│ │ └── complete_valid.toml
│ └── test_schema.py
├── masonry/
│ └── builders/
│ ├── __init__.py
│ ├── fixtures/
│ │ └── excluded_subpackage/
│ │ ├── README.rst
│ │ ├── example/
│ │ │ ├── __init__.py
│ │ │ └── test/
│ │ │ ├── __init__.py
│ │ │ └── excluded.py
│ │ └── pyproject.toml
│ └── test_editable_builder.py
├── mixology/
│ ├── __init__.py
│ ├── helpers.py
│ ├── test_incompatibility.py
│ └── version_solver/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_backtracking.py
│ ├── test_basic_graph.py
│ ├── test_dependency_cache.py
│ ├── test_python_constraint.py
│ ├── test_unsolvable.py
│ └── test_with_lock.py
├── packages/
│ ├── __init__.py
│ ├── test_direct_origin.py
│ ├── test_locker.py
│ └── test_transitive_package_info.py
├── plugins/
│ ├── __init__.py
│ └── test_plugin_manager.py
├── publishing/
│ ├── __init__.py
│ ├── test_hash_manager.py
│ ├── test_publisher.py
│ └── test_uploader.py
├── puzzle/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_provider.py
│ ├── test_solver.py
│ ├── test_solver_internals.py
│ └── test_transaction.py
├── pyproject/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_pyproject_toml.py
│ └── test_pyproject_toml_file.py
├── repositories/
│ ├── __init__.py
│ ├── conftest.py
│ ├── fixtures/
│ │ ├── __init__.py
│ │ ├── distribution_hashes.py
│ │ ├── installed/
│ │ │ ├── lib/
│ │ │ │ └── python3.7/
│ │ │ │ └── site-packages/
│ │ │ │ ├── cleo-0.7.6.dist-info/
│ │ │ │ │ └── METADATA
│ │ │ │ ├── directory_pep_610-1.2.3.dist-info/
│ │ │ │ │ ├── METADATA
│ │ │ │ │ └── direct_url.json
│ │ │ │ ├── editable-2.3.4.dist-info/
│ │ │ │ │ └── METADATA
│ │ │ │ ├── editable-src-dir-2.3.4.dist-info/
│ │ │ │ │ └── METADATA
│ │ │ │ ├── editable-src-dir.pth
│ │ │ │ ├── editable-with-import-2.3.4.dist-info/
│ │ │ │ │ └── METADATA
│ │ │ │ ├── editable-with-import.pth
│ │ │ │ ├── editable.pth
│ │ │ │ ├── editable_directory_pep_610-1.2.3.dist-info/
│ │ │ │ │ ├── METADATA
│ │ │ │ │ └── direct_url.json
│ │ │ │ ├── file_pep_610-1.2.3.dist-info/
│ │ │ │ │ ├── METADATA
│ │ │ │ │ └── direct_url.json
│ │ │ │ ├── foo-0.1.0-py3.8.egg
│ │ │ │ ├── git_pep_610-1.2.3.dist-info/
│ │ │ │ │ ├── METADATA
│ │ │ │ │ └── direct_url.json
│ │ │ │ ├── git_pep_610_no_requested_version-1.2.3.dist-info/
│ │ │ │ │ ├── METADATA
│ │ │ │ │ └── direct_url.json
│ │ │ │ ├── git_pep_610_subdirectory-1.2.3.dist-info/
│ │ │ │ │ ├── METADATA
│ │ │ │ │ └── direct_url.json
│ │ │ │ ├── standard-1.2.3.dist-info/
│ │ │ │ │ └── METADATA
│ │ │ │ ├── standard.pth
│ │ │ │ └── url_pep_610-1.2.3.dist-info/
│ │ │ │ ├── METADATA
│ │ │ │ └── direct_url.json
│ │ │ ├── lib64/
│ │ │ │ └── python3.7/
│ │ │ │ └── site-packages/
│ │ │ │ ├── bender-2.0.5.dist-info/
│ │ │ │ │ └── METADATA
│ │ │ │ ├── bender.pth
│ │ │ │ └── lib64-2.3.4.dist-info/
│ │ │ │ └── METADATA
│ │ │ ├── src/
│ │ │ │ ├── bender/
│ │ │ │ │ └── bender.egg-info/
│ │ │ │ │ └── PKG-INFO
│ │ │ │ └── pendulum/
│ │ │ │ └── pendulum.egg-info/
│ │ │ │ ├── PKG-INFO
│ │ │ │ └── requires.txt
│ │ │ └── vendor/
│ │ │ └── py3.7/
│ │ │ └── attrs-19.3.0.dist-info/
│ │ │ └── METADATA
│ │ ├── legacy/
│ │ │ ├── absolute.html
│ │ │ ├── black.html
│ │ │ ├── clikit.html
│ │ │ ├── demo.html
│ │ │ ├── discord-py.html
│ │ │ ├── futures-partial-yank.html
│ │ │ ├── futures.html
│ │ │ ├── invalid-version.html
│ │ │ ├── ipython.html
│ │ │ ├── isort-metadata.html
│ │ │ ├── isort.html
│ │ │ ├── json/
│ │ │ │ ├── _readme
│ │ │ │ ├── absolute.json
│ │ │ │ ├── demo.json
│ │ │ │ ├── invalid-version.json
│ │ │ │ ├── isort-metadata.json
│ │ │ │ ├── jupyter.json
│ │ │ │ ├── poetry-test-py2-py3-metadata-merge.json
│ │ │ │ ├── relative.json
│ │ │ │ └── sqlalchemy-legacy.json
│ │ │ ├── jupyter.html
│ │ │ ├── missing-version.html
│ │ │ ├── pastel.html
│ │ │ ├── poetry-test-py2-py3-metadata-merge.html
│ │ │ ├── pytest-with-extra-packages.html
│ │ │ ├── pytest.html
│ │ │ ├── python-language-server.html
│ │ │ ├── pyyaml.html
│ │ │ ├── relative.html
│ │ │ ├── sqlalchemy-legacy.html
│ │ │ └── tomlkit.html
│ │ ├── legacy.py
│ │ ├── pypi.org/
│ │ │ ├── dists/
│ │ │ │ ├── mocked/
│ │ │ │ │ ├── poetry_test_py2_py3_metadata_merge-0.1.0-py2-none-any.whl
│ │ │ │ │ └── poetry_test_py2_py3_metadata_merge-0.1.0-py3-none-any.whl
│ │ │ │ ├── poetry_core-1.5.0-py3-none-any.whl
│ │ │ │ ├── poetry_core-2.0.1-py3-none-any.whl
│ │ │ │ ├── setuptools-67.6.1-py3-none-any.whl
│ │ │ │ └── wheel-0.40.0-py3-none-any.whl
│ │ │ ├── generate.py
│ │ │ ├── json/
│ │ │ │ ├── attrs/
│ │ │ │ │ └── 17.4.0.json
│ │ │ │ ├── attrs.json
│ │ │ │ ├── black/
│ │ │ │ │ ├── 19.10b0.json
│ │ │ │ │ └── 21.11b0.json
│ │ │ │ ├── black.json
│ │ │ │ ├── cleo/
│ │ │ │ │ └── 1.0.0a5.json
│ │ │ │ ├── cleo.json
│ │ │ │ ├── clikit/
│ │ │ │ │ └── 0.2.4.json
│ │ │ │ ├── clikit.json
│ │ │ │ ├── colorama/
│ │ │ │ │ └── 0.3.9.json
│ │ │ │ ├── colorama.json
│ │ │ │ ├── discord-py/
│ │ │ │ │ └── 2.0.0.json
│ │ │ │ ├── discord-py.json
│ │ │ │ ├── filecache/
│ │ │ │ │ └── 0.81.json
│ │ │ │ ├── filecache.json
│ │ │ │ ├── funcsigs/
│ │ │ │ │ └── 1.0.2.json
│ │ │ │ ├── funcsigs.json
│ │ │ │ ├── futures/
│ │ │ │ │ └── 3.2.0.json
│ │ │ │ ├── futures.json
│ │ │ │ ├── hbmqtt/
│ │ │ │ │ └── 0.9.6.json
│ │ │ │ ├── hbmqtt.json
│ │ │ │ ├── importlib-metadata/
│ │ │ │ │ └── 1.7.0.json
│ │ │ │ ├── importlib-metadata.json
│ │ │ │ ├── ipython/
│ │ │ │ │ ├── 4.1.0rc1.json
│ │ │ │ │ ├── 5.7.0.json
│ │ │ │ │ └── 7.5.0.json
│ │ │ │ ├── ipython.json
│ │ │ │ ├── isodate/
│ │ │ │ │ └── 0.7.0.json
│ │ │ │ ├── isodate.json
│ │ │ │ ├── isort/
│ │ │ │ │ └── 4.3.4.json
│ │ │ │ ├── isort-metadata.json
│ │ │ │ ├── isort.json
│ │ │ │ ├── jupyter/
│ │ │ │ │ └── 1.0.0.json
│ │ │ │ ├── jupyter.json
│ │ │ │ ├── mocked/
│ │ │ │ │ ├── invalid-version-package.json
│ │ │ │ │ ├── isort-metadata/
│ │ │ │ │ │ └── 4.3.4.json
│ │ │ │ │ ├── six-unknown-version/
│ │ │ │ │ │ └── 1.11.0.json
│ │ │ │ │ ├── six-unknown-version.json
│ │ │ │ │ ├── with-extra-dependency/
│ │ │ │ │ │ └── 0.12.4.json
│ │ │ │ │ ├── with-extra-dependency.json
│ │ │ │ │ ├── with-transitive-extra-dependency/
│ │ │ │ │ │ └── 0.12.4.json
│ │ │ │ │ └── with-transitive-extra-dependency.json
│ │ │ │ ├── more-itertools/
│ │ │ │ │ └── 4.1.0.json
│ │ │ │ ├── more-itertools.json
│ │ │ │ ├── pastel/
│ │ │ │ │ └── 0.1.0.json
│ │ │ │ ├── pastel.json
│ │ │ │ ├── pluggy/
│ │ │ │ │ └── 0.6.0.json
│ │ │ │ ├── pluggy.json
│ │ │ │ ├── poetry-core/
│ │ │ │ │ ├── 1.5.0.json
│ │ │ │ │ └── 2.0.1.json
│ │ │ │ ├── poetry-core.json
│ │ │ │ ├── py/
│ │ │ │ │ └── 1.5.3.json
│ │ │ │ ├── py.json
│ │ │ │ ├── pylev/
│ │ │ │ │ └── 1.3.0.json
│ │ │ │ ├── pylev.json
│ │ │ │ ├── pytest/
│ │ │ │ │ ├── 3.5.0.json
│ │ │ │ │ └── 3.5.1.json
│ │ │ │ ├── pytest.json
│ │ │ │ ├── python-language-server/
│ │ │ │ │ └── 0.21.2.json
│ │ │ │ ├── python-language-server.json
│ │ │ │ ├── pyyaml/
│ │ │ │ │ └── 3.13.0.json
│ │ │ │ ├── pyyaml.json
│ │ │ │ ├── requests/
│ │ │ │ │ ├── 2.18.0.json
│ │ │ │ │ ├── 2.18.1.json
│ │ │ │ │ ├── 2.18.2.json
│ │ │ │ │ ├── 2.18.3.json
│ │ │ │ │ ├── 2.18.4.json
│ │ │ │ │ └── 2.19.0.json
│ │ │ │ ├── requests.json
│ │ │ │ ├── setuptools/
│ │ │ │ │ ├── 39.2.0.json
│ │ │ │ │ └── 67.6.1.json
│ │ │ │ ├── setuptools.json
│ │ │ │ ├── six/
│ │ │ │ │ └── 1.11.0.json
│ │ │ │ ├── six.json
│ │ │ │ ├── sqlalchemy/
│ │ │ │ │ └── 1.2.12.json
│ │ │ │ ├── sqlalchemy.json
│ │ │ │ ├── toga/
│ │ │ │ │ ├── 0.3.0.json
│ │ │ │ │ ├── 0.3.0dev1.json
│ │ │ │ │ ├── 0.3.0dev2.json
│ │ │ │ │ └── 0.4.0.json
│ │ │ │ ├── toga.json
│ │ │ │ ├── tomlkit/
│ │ │ │ │ ├── 0.5.2.json
│ │ │ │ │ └── 0.5.3.json
│ │ │ │ ├── tomlkit.json
│ │ │ │ ├── twisted/
│ │ │ │ │ └── 18.9.0.json
│ │ │ │ ├── twisted.json
│ │ │ │ ├── wheel/
│ │ │ │ │ └── 0.40.0.json
│ │ │ │ ├── wheel.json
│ │ │ │ ├── zipp/
│ │ │ │ │ └── 3.5.0.json
│ │ │ │ └── zipp.json
│ │ │ ├── metadata/
│ │ │ │ ├── PyYAML-3.13-cp27-cp27m-win32.whl.metadata
│ │ │ │ ├── PyYAML-3.13-cp27-cp27m-win_amd64.whl.metadata
│ │ │ │ ├── PyYAML-3.13-cp34-cp34m-win32.whl.metadata
│ │ │ │ ├── PyYAML-3.13-cp34-cp34m-win_amd64.whl.metadata
│ │ │ │ ├── PyYAML-3.13-cp35-cp35m-win32.whl.metadata
│ │ │ │ ├── PyYAML-3.13-cp35-cp35m-win_amd64.whl.metadata
│ │ │ │ ├── PyYAML-3.13-cp36-cp36m-win32.whl.metadata
│ │ │ │ ├── PyYAML-3.13-cp36-cp36m-win_amd64.whl.metadata
│ │ │ │ ├── PyYAML-3.13-cp37-cp37m-win32.whl.metadata
│ │ │ │ ├── PyYAML-3.13-cp37-cp37m-win_amd64.whl.metadata
│ │ │ │ ├── attrs-17.4.0-py2.py3-none-any.whl.metadata
│ │ │ │ ├── black-19.10b0-py36-none-any.whl.metadata
│ │ │ │ ├── black-21.11b0-py3-none-any.whl.metadata
│ │ │ │ ├── cleo-1.0.0a5-py3-none-any.whl.metadata
│ │ │ │ ├── clikit-0.2.4-py2.py3-none-any.whl.metadata
│ │ │ │ ├── colorama-0.3.9-py2.py3-none-any.whl.metadata
│ │ │ │ ├── discord.py-2.0.0-py3-none-any.whl.metadata
│ │ │ │ ├── filecache-0.81-py3-none-any.whl.metadata
│ │ │ │ ├── funcsigs-1.0.2-py2.py3-none-any.whl.metadata
│ │ │ │ ├── futures-3.2.0-py2-none-any.whl.metadata
│ │ │ │ ├── importlib_metadata-1.7.0-py2.py3-none-any.whl.metadata
│ │ │ │ ├── ipython-4.1.0rc1-py2.py3-none-any.whl.metadata
│ │ │ │ ├── ipython-5.7.0-py2-none-any.whl.metadata
│ │ │ │ ├── ipython-5.7.0-py3-none-any.whl.metadata
│ │ │ │ ├── ipython-7.5.0-py3-none-any.whl.metadata
│ │ │ │ ├── isodate-0.7.0-py3-none-any.whl.metadata
│ │ │ │ ├── isort-4.3.4-py2-none-any.whl.metadata
│ │ │ │ ├── isort-4.3.4-py3-none-any.whl.metadata
│ │ │ │ ├── isort-metadata-4.3.4-py2-none-any.whl.metadata
│ │ │ │ ├── isort-metadata-4.3.4-py3-none-any.whl.metadata
│ │ │ │ ├── jupyter-1.0.0-py2.py3-none-any.whl.metadata
│ │ │ │ ├── mocked/
│ │ │ │ │ ├── with_extra_dependency-0.12.4-py3-none-any.whl.metadata
│ │ │ │ │ └── with_transitive_extra_dependency-0.12.4-py3-none-any.whl.metadata
│ │ │ │ ├── more_itertools-4.1.0-py2-none-any.whl.metadata
│ │ │ │ ├── more_itertools-4.1.0-py3-none-any.whl.metadata
│ │ │ │ ├── pastel-0.1.0-py3-none-any.whl.metadata
│ │ │ │ ├── pluggy-0.6.0-py2-none-any.whl.metadata
│ │ │ │ ├── pluggy-0.6.0-py3-none-any.whl.metadata
│ │ │ │ ├── poetry_core-1.5.0-py3-none-any.whl.metadata
│ │ │ │ ├── poetry_core-2.0.1-py3-none-any.whl.metadata
│ │ │ │ ├── py-1.5.3-py2.py3-none-any.whl.metadata
│ │ │ │ ├── pylev-1.3.0-py2.py3-none-any.whl.metadata
│ │ │ │ ├── pytest-3.5.0-py2.py3-none-any.whl.metadata
│ │ │ │ ├── pytest-3.5.1-py2.py3-none-any.whl.metadata
│ │ │ │ ├── requests-2.18.0-py2.py3-none-any.whl.metadata
│ │ │ │ ├── requests-2.18.1-py2.py3-none-any.whl.metadata
│ │ │ │ ├── requests-2.18.2-py2.py3-none-any.whl.metadata
│ │ │ │ ├── requests-2.18.3-py2.py3-none-any.whl.metadata
│ │ │ │ ├── requests-2.18.4-py2.py3-none-any.whl.metadata
│ │ │ │ ├── requests-2.19.0-py2.py3-none-any.whl.metadata
│ │ │ │ ├── setuptools-39.2.0-py2.py3-none-any.whl.metadata
│ │ │ │ ├── setuptools-67.6.1-py3-none-any.whl.metadata
│ │ │ │ ├── six-1.11.0-py2.py3-none-any.whl.metadata
│ │ │ │ ├── toga-0.3.0-py3-none-any.whl.metadata
│ │ │ │ ├── toga-0.3.0.dev1-py3-none-any.whl.metadata
│ │ │ │ ├── toga-0.3.0.dev2-py3-none-any.whl.metadata
│ │ │ │ ├── toga-0.4.0-py3-none-any.whl.metadata
│ │ │ │ ├── tomlkit-0.5.2-py2.py3-none-any.whl.metadata
│ │ │ │ ├── tomlkit-0.5.3-py2.py3-none-any.whl.metadata
│ │ │ │ ├── wheel-0.40.0-py3-none-any.whl.metadata
│ │ │ │ └── zipp-3.5.0-py3-none-any.whl.metadata
│ │ │ ├── search/
│ │ │ │ ├── search-disallowed.html
│ │ │ │ └── search.html
│ │ │ └── stubbed/
│ │ │ ├── Twisted-18.9.0.tar.bz2
│ │ │ ├── attrs-17.4.0-py2.py3-none-any.whl
│ │ │ ├── black-19.10b0-py36-none-any.whl
│ │ │ ├── black-21.11b0-py3-none-any.whl
│ │ │ ├── cleo-1.0.0a5-py3-none-any.whl
│ │ │ ├── clikit-0.2.4-py2.py3-none-any.whl
│ │ │ ├── colorama-0.3.9-py2.py3-none-any.whl
│ │ │ ├── discord.py-2.0.0-py3-none-any.whl
│ │ │ ├── futures-3.2.0-py2-none-any.whl
│ │ │ ├── ipython-5.7.0-py2-none-any.whl
│ │ │ ├── ipython-5.7.0-py3-none-any.whl
│ │ │ ├── ipython-7.5.0-py3-none-any.whl
│ │ │ ├── isodate-0.7.0-py3-none-any.whl
│ │ │ ├── isort-4.3.4-py2-none-any.whl
│ │ │ ├── isort-4.3.4-py3-none-any.whl
│ │ │ ├── jupyter-1.0.0-py2.py3-none-any.whl
│ │ │ ├── more_itertools-4.1.0-py2-none-any.whl
│ │ │ ├── more_itertools-4.1.0-py3-none-any.whl
│ │ │ ├── pastel-0.1.0-py3-none-any.whl
│ │ │ ├── pluggy-0.6.0-py2-none-any.whl
│ │ │ ├── pluggy-0.6.0-py3-none-any.whl
│ │ │ ├── py-1.5.3-py2.py3-none-any.whl
│ │ │ ├── pytest-3.5.0-py2.py3-none-any.whl
│ │ │ ├── pytest-3.5.1-py2.py3-none-any.whl
│ │ │ ├── requests-2.18.4-py2.py3-none-any.whl
│ │ │ ├── six-1.11.0-py2.py3-none-any.whl
│ │ │ ├── tomlkit-0.5.2-py2.py3-none-any.whl
│ │ │ ├── tomlkit-0.5.3-py2.py3-none-any.whl
│ │ │ └── zipp-3.5.0-py3-none-any.whl
│ │ ├── pypi.py
│ │ ├── python_hosted.py
│ │ └── single-page/
│ │ ├── jax_releases.html
│ │ └── mmcv_torch_releases.html
│ ├── link_sources/
│ │ ├── __init__.py
│ │ ├── test_base.py
│ │ ├── test_html.py
│ │ └── test_json.py
│ ├── parsers/
│ │ ├── __init__.py
│ │ ├── test_html_page_parser.py
│ │ └── test_pypi_search_parser.py
│ ├── test_cached_repository.py
│ ├── test_http_repository.py
│ ├── test_installed_repository.py
│ ├── test_legacy_repository.py
│ ├── test_lockfile_repository.py
│ ├── test_pypi_repository.py
│ ├── test_repository.py
│ ├── test_repository_pool.py
│ └── test_single_page_repository.py
├── test_conftest.py
├── test_factory.py
├── test_helpers.py
├── types.py
├── utils/
│ ├── __init__.py
│ ├── conftest.py
│ ├── env/
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ ├── python/
│ │ │ ├── __init__.py
│ │ │ ├── test_manager.py
│ │ │ ├── test_python_installer.py
│ │ │ └── test_python_providers.py
│ │ ├── test_env.py
│ │ ├── test_env_manager.py
│ │ ├── test_env_site_packages.py
│ │ └── test_system_env.py
│ ├── fixtures/
│ │ └── pyproject.toml
│ ├── test_authenticator.py
│ ├── test_cache.py
│ ├── test_dependency_specification.py
│ ├── test_extras.py
│ ├── test_helpers.py
│ ├── test_isolated_build.py
│ ├── test_log_utils.py
│ ├── test_password_manager.py
│ ├── test_patterns.py
│ ├── test_pip.py
│ ├── test_python_manager.py
│ └── test_threading.py
└── vcs/
└── git/
├── conftest.py
├── git_fixture.py
├── test_backend.py
└── test_system.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .cirrus.yml
================================================
tests_task:
# We only use Cirrus CI for FreeBSD at present; the rest of the task will assume FreeBSD.
freebsd_instance:
image_family: freebsd-14-3
# Cirrus has a concurrency limit of 8 vCPUs for FreeBSD. Allow executing 4 tasks in parallel.
cpu: 2
memory: 2G
env:
matrix:
- PYTHON: python3.11
PYTHON_VERSION: 3.11
PYTHON_PACKAGE: python311
SQLITE_PACKAGE: py311-sqlite3
# FIXME: Python 3.12 is not available in Ports.
# - PYTHON: python3.12
# PYTHON_VERSION: 3.12
# PYTHON_PACKAGE: python312
# SQLITE_PACKAGE: py312-sqlite3
# FIXME: use pipx for install. pipx is currently broken in Ports.
POETRY_HOME: /opt/poetry
# SHELL is not set by default, and we have tests that depend on it.
SHELL: sh
bootstrap_poetry_script:
- pkg install -y git $PYTHON_PACKAGE $SQLITE_PACKAGE
- $PYTHON -m venv $POETRY_HOME
- $POETRY_HOME/bin/pip install poetry
- echo "PATH=${POETRY_HOME}/bin:${PATH}" >> $CIRRUS_ENV
setup_environment_script:
# TODO: caching
- poetry install
- poetry env info
- poetry show
matrix:
- alias: pytest
name: "Tests / FreeBSD (Python ${PYTHON_VERSION}) / pytest"
skip: "!changesInclude('.cirrus.yml', 'poetry.lock', 'pyproject.toml', 'src/**.py', 'tests/**')"
pytest_script: poetry run pytest --integration -v --junitxml=junit.xml
on_failure:
annotate_failure_artifacts:
path: junit.xml
format: junit
type: text/xml
status_task:
name: "Tests / FreeBSD Status"
depends_on:
- pytest
container:
image: alpine:latest
cpu: 0.5
memory: 512M
# No-op the clone.
clone_script: true
================================================
FILE: .gitattributes
================================================
# Do not mess with line endings in metadata files or the hash will be wrong.
*.metadata binary
================================================
FILE: .github/ISSUE_TEMPLATE/---bug-report.yml
================================================
name: "\U0001F41E Bug Report"
labels: ["kind/bug", "status/triage"]
description: "Poetry not working the way it is documented?"
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to file a complete bug report.
Before submitting your issue, please review the [Before submitting a bug report](https://python-poetry.org/docs/contributing/#before-submitting-a-bug-report) section of our documentation.
- type: textarea
attributes:
label: Description
description: |
Please describe what happened, with as much pertinent information as you can. Feel free to use markdown syntax.
Also, ensure that the issue is not already fixed in the [latest](https://github.com/python-poetry/poetry/releases/latest) Poetry release.
validations:
required: true
- type: textarea
attributes:
label: Workarounds
description: |
Is there a mitigation or workaround that allows users to avoid the issue today?
validations:
required: true
- type: dropdown
attributes:
label: Poetry Installation Method
description: |
How did you install Poetry?
options:
- "pipx"
- "install.python-poetry.org"
- "system package manager (eg: dnf, apt etc.)"
- "pip"
- "other"
validations:
required: true
- type: input
attributes:
label: Operating System
description: |
What Operating System are you using?
placeholder: "Fedora 39"
validations:
required: true
- type: input
attributes:
label: Poetry Version
description: |
Please attach output from `poetry --version`
validations:
required: true
- type: textarea
attributes:
label: Poetry Configuration
description: |
Please attach output from `poetry config --list`
render: 'bash session'
validations:
required: true
- type: textarea
attributes:
label: Python Sysconfig
description: |
Please attach output from `python -m sysconfig`
Note:_ You can paste the output into the placeholder below. If it is too long, you can attach it as a file.
value: |
sysconfig.log
```
Paste the output of 'python -m sysconfig', over this line.
```
validations:
required: false
- type: textarea
attributes:
label: Example pyproject.toml
description: |
Please provide an example `pyproject.toml` demonstrating the issue.
render: 'TOML'
validations:
required: false
- type: textarea
attributes:
label: Poetry Runtime Logs
description: |
Please attach logs from the failing command using `poetry -vvv `
Note:_ You can paste the output into the placeholder below. If it is too long, you can attach it as a file.
value: |
poetry-runtime.log
```
Paste the output of 'poetry -vvv ', over this line.
```
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/---documentation.yml
================================================
name: "\U0001F4DA Documentation Issue"
labels: ["area/docs", "status/triage"]
description: "Did you find errors, omissions, or anything unintelligible in the documentation?"
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to file a complete bug report.
Before submitting your issue, please review the [Suggesting enhancements](https://python-poetry.org/docs/contributing/#suggesting-enhancements) section of our documentation.
Please also confirm the following:
- You have searched the [issues](https://github.com/python-poetry/poetry/issues) of this repository and believe that this is not a duplicate.
- You have searched the [FAQ](https://python-poetry.org/docs/faq/) and general [documentation](https://python-poetry.org/docs/) and believe that your question is not already covered.
- type: dropdown
attributes:
label: Issue Kind
description: |
What best describes the issue?
options:
- "Improving documentation"
- "Missing documentation"
- "Error in existing documentation"
- "Unclear documentation"
- "Other concerns with documentation"
validations:
required: true
- type: input
attributes:
label: Existing Link
description: |
If the documentation in question exists, please provide a link to it.
placeholder: "https://python-poetry.org/docs/dependency-specification/#version-constraints"
validations:
required: true
- type: textarea
attributes:
label: Description
description: |
Please describe the feature, with as much pertinent information as you can. Feel free to use markdown syntax.
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/---feature-request.yml
================================================
name: "\U0001F381 Feature Request"
labels: ["kind/feature", "status/triage"]
description: "Want something new?"
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to file a complete bug report.
Before submitting your issue, please search [issues](https://github.com/python-poetry/poetry/issues) to ensure this is not a duplicate.
If the issue is trivial, why not submit a pull request instead?
- type: dropdown
attributes:
label: Issue Kind
description: |
What best describes this issue?
options:
- "Brand new capability"
- "Change in current behaviour"
- "Other"
validations:
required: true
- type: textarea
attributes:
label: Description
description: |
Please describe the issue, with as much pertinent information as you can. Feel free to use markdown syntax.
Also, ensure that the issue is not already fixed in the [development documentation](https://python-poetry.org/docs/main/).
validations:
required: true
- type: textarea
attributes:
label: Impact
description: |
Please describe the motivation for this issue. Describe, as best you can, how this improves or impacts the users of Poetry and why this is important.
validations:
required: true
- type: textarea
attributes:
label: Workarounds
description: |
Is there a mitigation, workaround, or addon that allows users to achieve the same functionality today?
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
blank_issues_enabled: false
contact_links:
- name: '💬 Discussions'
url: https://github.com/python-poetry/poetry/discussions
about: |
Ask questions about using Poetry, Poetry's features and roadmap, or get support and feedback for your usage of Poetry.
- name: '💬 Discord Server'
url: https://discordapp.com/invite/awxPgve
about: |
Chat with the community and Poetry maintainers about both the usage of and development of the project.
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
# Pull Request Check List
Resolves: #issue-number-here
- [ ] Added **tests** for changed code.
- [ ] Updated **documentation** for changed code.
================================================
FILE: .github/actions/bootstrap-poetry/action.yaml
================================================
name: Bootstrap Poetry
description: Configure the environment with the specified Python and Poetry version.
inputs:
python-version:
description: Desired node-semver compatible Python version expression (or 'default')
default: 'default'
python-latest:
description: Use an uncached Python if a newer match is available
default: 'false'
poetry-spec:
description: pip-compatible installation specification to use for Poetry
default: 'poetry'
outputs:
python-path:
description: Path to the installed Python interpreter
value: ${{ steps.setup-python.outputs.python-path }}
python-version:
description: Version of the installed Python interpreter
value: ${{ steps.setup-python.outputs.python-version }}
runs:
using: composite
steps:
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
id: setup-python
if: inputs.python-version != 'default'
with:
python-version: ${{ inputs.python-version }}
check-latest: ${{ inputs.python-latest == 'true' }}
allow-prereleases: true
update-environment: false
- run: pipx install ${PYTHON_PATH:+--python "$PYTHON_PATH"} "${POETRY_SPEC}"
shell: bash
env:
PYTHON_PATH: ${{ inputs.python-version != 'default' && steps.setup-python.outputs.python-path || '' }}
POETRY_SPEC: ${{ inputs.poetry-spec }}
# Enable handling long path names (+260 char) on the Windows platform
# https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation
- run: git config --system core.longpaths true
if: runner.os == 'Windows'
shell: pwsh
# Use Poetry Python for virtual environments
# (Otherwise, the system Python will be used per default instead of the Python version we just installed)
- run: poetry config virtualenvs.use-poetry-python true
shell: bash
================================================
FILE: .github/actions/poetry-install/action.yaml
================================================
name: Poetry Install
description: Run `poetry install` with optional artifact and metadata caching
inputs:
args:
description: Arguments for `poetry install`
cache:
description: Enable transparent Poetry artifact and metadata caching
default: 'true'
outputs:
cache-hit:
description: Whether an exact cache hit occured
value: ${{ steps.cache.outputs.cache-hit }}
runs:
using: composite
steps:
- run: printf 'cache-dir=%s\n' "$(poetry config cache-dir)" >> $GITHUB_OUTPUT
id: poetry-config
shell: bash
# Bust the cache every 24 hours to prevent it from expanding over time.
- run: printf 'date=%s\n' "$(date -I)" >> $GITHUB_OUTPUT
id: get-date
if: inputs.cache == 'true'
shell: bash
- uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
id: cache
if: inputs.cache == 'true'
with:
path: |
${{ steps.poetry-config.outputs.cache-dir }}/artifacts
${{ steps.poetry-config.outputs.cache-dir }}/cache
key: poetry-${{ steps.get-date.outputs.date }}-${{ runner.os }}-${{ hashFiles('pyproject.toml', 'poetry.lock') }}
# The cache is cross-platform, and other platforms are used to seed cache misses.
restore-keys: |
poetry-${{ steps.get-date.outputs.date }}-${{ runner.os }}-
poetry-${{ steps.get-date.outputs.date }}-
enableCrossOsArchive: true
- run: poetry install ${ARGS}
shell: bash
env:
ARGS: ${{ inputs.args }}
- run: poetry env info
shell: bash
- run: poetry show
shell: bash
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7
labels:
- "area/ci"
# Grouped updates to reduce PR noise.
#
# See:
# - https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/optimizing-pr-creation-version-updates#prioritizing-meaningful-updates
# - https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#groups--
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "monthly"
cooldown:
default-days: 7
labels:
- "area/project/deps"
groups:
production-dependencies:
dependency-type: "production"
development-dependencies:
dependency-type: "development"
open-pull-requests-limit: 2
================================================
FILE: .github/scripts/backport.sh
================================================
#!/usr/bin/env bash
#
# Copyright 2024 Bjorn Neergaard
#
# This work is licensed under the terms of the MIT license.
# For a copy, see .
#
# This script is used to automatically create backport pull requests. It is
# smart enough to require no arguments if run against a PR branch, assuming the
# upstream repo conforms to the default prefixes. Target branches will be
# determined by labels present on the original pull request.
#
# It is capable of handling PRs merged with a commit, by rebase and by
# squash-and-rebase. The backport pull request will have its title, body and
# labels derived from the original. The cherry-picked comments can be signed off,
# and a comment will be created if requested.
#
# In particular, this script assumes the 'origin' remote points to the target
# repository for backports. We also assume we can freely clobber local and remote
# branches using our backport branch naming scheme and that you don't mind if we
# prune your worktrees for you.
#
# This script can push the backport branches to a fork if a corresponding
# --remote is passed.
basename=${0##*/}
# Check for Homebrew-installed getopt on macOS.
for g in /{usr/local,opt/homebrew}/opt/gnu-getopt/bin/getopt; do
test -x "$g" && : GETOPT="${GETOPT:=$g}" && break
done || : GETOPT="${GETOPT:=getopt}"
"${GETOPT}" --test >/dev/null
if [ $? -ne 4 ]; then
printf >&2 '%s: GNU getopt is required, please ensure it is available in the PATH\n' "$basename"
exit 1
fi
if ! command -v gh >/dev/null; then
printf >&2 "%s: the GitHub CLI (\`gh') is required, please ensure it is available in the PATH\n" "$basename"
fi
usage() {
printf >&2 '%s -h|--help\n' "$basename"
printf >&2 '%s [-s|--signoff] [-c|--comment] --pr [pr] --remote [remote] --branch-prefix [prefix] --label-prefix [prefix]\n' "$basename"
}
args="$("${GETOPT}" -o h,s,c -l help,signoff,comment,pr:,remote:,branch-prefix:,label-prefix: -n "$basename" -- "$@")" || exit $?
eval set -- "$args"
unset args
while [ "$#" -gt 0 ]; do
case "$1" in
--pr | --remote | --branch-prefix | --label-prefix)
flag="${1:2}"
printf -v "${flag//-/_}" '%s' "$2"
shift 2
;;
-s | --signoff)
signoff=1
shift
;;
-c | --comment)
comment=1
shift
;;
-h | --help)
usage
exit
;;
-- | *)
shift
break
;;
esac
done
set -eux -o pipefail
# Determine the number of the target pull request, if not already supplied.
: pr="${pr:=$(gh pr view --json number --jq '.number')}"
# Use the 'origin' remote by default; if a fork is desired, a corresponding remote should
# be specified, e.g. `gh repo fork --remote-name fork` and `--remote fork`.
: remote="${remote:=origin}"
# Use 'backport/' as a default prefix for both the resulting branch and the triggering label.
: branch_prefix="${branch_prefix:=backport/}"
: label_prefix="${label_prefix:=backport/}"
# Determine the owner of the target remote (necessary to open a pull request) based on the URL.
remote_owner=$(basename "$(dirname "$(git remote get-url --push "$remote")")")
# Get the state, base branch and merge commit (if it exists) of the pull request.
pr_meta=$(gh pr view --json state,baseRefName,mergeCommit --jq '[.state,.baseRefName,.mergeCommit.oid][]' "$pr")
pr_state=$(sed '1q;d' <<<"$pr_meta")
pr_base=$(sed '2q;d' <<<"$pr_meta")
pr_mergecommit=$(sed '3q;d' <<<"$pr_meta")
# Get the list of commits present in the pull request.
pr_commits=$(gh pr view --json commits --jq '.commits[].oid' "$pr")
# Get the title and body of the pull request.
pr_title_body=$(gh pr view --json title,body --jq '[.title,.body][]' "$pr")
pr_title=$(head -n 1 <<<"$pr_title_body")
pr_body=$(tail -n +2 <<<"$pr_title_body")
# Gather the list of labels on the pull request.
pr_labels=$(gh pr view --json labels --jq '.labels[].name' "$pr")
# Fetch origin, to ensure we have the latest commits on all upstream branches.
git fetch origin
# Fetch the latest pull request head, to ensure we have all commits available locally.
# It will be available as FETCH_HEAD for the remainder of this script.
git fetch origin "refs/pull/${pr}/head"
# Determine which commits should be cherry-picked. This can be surprisingly complex,
# but the typical cases present on GitHub are handled here.
if [ "$pr_state" = OPEN ] || [ "$(git rev-list --no-walk --count --merges "$pr_mergecommit")" -eq 1 ]; then
# Unmerged, or merge commit: the list of commits is equivalent to the pull request.
backport_commits=$pr_commits
else
# The cherry commits represent those commits that were cherry-picked from the pull request to the base.
pr_cherry_commits=$(git cherry refs/remotes/origin/main FETCH_HEAD | sed -n '/^- / s/- //p')
# The rebased commits represent those commits present in the base that correspond to the pull request.
pr_rebased_commits=$(git cherry FETCH_HEAD refs/remotes/origin/main | sed -n '/^- / s/- //p')
# Look for cherry-picks (which is what a conflict-free and non-interactive rebase merge
# effectively does). Note that a squash confuses the list of rebased commits;
# to make our heuristics as effective as possible, we have two checks:
# * Git must successfully identify the list of commits cherry-picked from the PR.
# * The number of commits in the pull request and identified for backport must match.
if [ "$pr_cherry_commits" = "$pr_commits" ] \
&& [ "$(wc -l <<<"$pr_rebased_commits")" -eq "$(wc -l <<<"$pr_commits")" ]; then
# Rebase: the list of commits is those rebased into the base branch.
backport_commits=$pr_rebased_commits
else
# Squash-and-rebase: the list of commits is the singular merged commit.
backport_commits=$pr_mergecommit
fi
fi
# Create a temporary directory in which to hold worktrees for each backport attempt.
workdir="$(mktemp -d)"
trap 'rm -rf "${workdir}"; git worktree prune -v' EXIT
# Create some arrays to track success and failure.
backport_urls=()
failed_backports=()
# Iterate over all labels matching the prefix to determine what branches must be backported.
while IFS= read -r backport_label; do
target_branch="${backport_label/#${label_prefix}}"
backport_branch="${branch_prefix}${pr}-${target_branch}"
# Check that the target branch and base branch are not the same. This heads off some
# potential errors.
if [ "$target_branch" = "$pr_base" ]; then
continue
fi
# Create a new backport branch, in a new worktree, based on the target branch.
backport_worktree="${workdir}/${backport_branch}"
git worktree add -B "$backport_branch" "$backport_worktree" "refs/remotes/origin/${target_branch}"
# Cherry-pick the commits from the target branch in order.
for commit in $backport_commits; do
if ! git -C "$backport_worktree" cherry-pick -x ${signoff:+-s} "$commit"; then
# If a cherry-pick fails, record the branch and move on.
failed_backports+=("$target_branch")
continue 2
fi
done
# Push the resulting backport branch to the configured remote.
git push -f "$remote" "$backport_branch"
# Create a derived title and label for the PR.
backport_title="[${target_branch} backport] ${pr_title}"
backport_body="Backport #${pr} to ${target_branch}."
if [ -n "$pr_body" ]; then
backport_body+=$'\n\n'"---"$'\n\n'"$pr_body"
fi
# Determine which labels should be brought over to the new pull request, formatted as the CLI expects.
backport_labels=$(grep -v "^${label_prefix}" <<<"$pr_labels" | head -c -1 | tr '\n' ',')
# Check for any open backports; note this is a heuristic as we just grab the first pull request
# that matches our generated branch name. This is unlikely to fail as we filter by author, however.
backport_url=$(gh pr list --author '@me' --head "$backport_branch" --json url --jq 'first(.[].url)')
if [ -n "$backport_url" ]; then
# Update the pull request title and body.
# TODO: update labels?
gh pr edit "$backport_url" --title "$backport_title" --body "$backport_body"
found_backport=1
else
# Create a new pull request from the backport branch, against the target branch.
backport_url=$(gh pr create --base "$target_branch" --head "${remote_owner}:${backport_branch}" \
--title "$backport_title" --body-file - \
--label "$backport_labels" \
<<<"$backport_body" | tail -n 1)
fi
# Track this successful backport.
backport_urls+=("$backport_url")
done < <(grep "^${label_prefix}" <<<"$pr_labels")
if [ -n "${comment:-}" ]; then
# Generate a comment on the original PR, recording what backports we opened (or failed to open).
if [ "${#backport_urls[@]}" -gt 0 ]; then
comment_body+="Automated backport PRs opened:"$'\n'
for backport_url in "${backport_urls[@]}"; do
comment_body+="* ${backport_url}"$'\n'
done
comment_body+=$'\n'
fi
if [ "${#failed_backports[@]}" -gt 0 ]; then
comment_body+="Backports failed on the following branches:"
for failed_backport in "${failed_backports[@]}"; do
comment_body+="* ${failed_backport}"$'\n'
done
comment_body+=$'\n'
# If we're running in GitHub actions, link to the run log to diagnose why a backport failed.
if [ -n "${GITHUB_ACTIONS:-}" ]; then
comment_body+="Inspect the run at ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
fi
fi
if [ -n "$comment_body" ]; then
# If we had any matches for an existing PR, we'll go ahead and assume we already commented.
# Edit the existing comment instead.
gh pr comment "$pr" ${found_backport:+--edit-last} --body-file - <<<"$comment_body"
fi
fi
================================================
FILE: .github/workflows/.tests-matrix.yaml
================================================
# Reusable workflow consumed by tests.yaml; used to share a single matrix across jobs.
on:
workflow_call:
inputs:
runner:
required: true
type: string
python-version:
required: true
type: string
run-mypy:
required: true
type: boolean
run-pytest:
required: true
type: boolean
run-pytest-export:
required: true
type: boolean
defaults:
run:
shell: bash
env:
PYTHONWARNDEFAULTENCODING: 'true'
jobs:
mypy:
name: mypy
runs-on: ${{ inputs.runner }}
if: inputs.run-mypy
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: ./.github/actions/bootstrap-poetry
id: bootstrap-poetry
with:
python-version: ${{ inputs.python-version }}
- uses: ./.github/actions/poetry-install
- uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
with:
path: .mypy_cache
key: mypy-${{ runner.os }}-py${{ steps.bootstrap-poetry.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'poetry.lock') }}
restore-keys: |
mypy-${{ runner.os }}-py${{ steps.bootstrap-poetry.outputs.python-version }}-
mypy-${{ runner.os }}-
- run: poetry run mypy
pytest:
name: pytest
runs-on: ${{ inputs.runner }}
if: inputs.run-pytest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: ./.github/actions/bootstrap-poetry
with:
python-version: ${{ inputs.python-version }}
- uses: ./.github/actions/poetry-install
with:
args: --with github-actions
- run: poetry run pytest --integration -v
env:
POETRY_TEST_INTEGRATION_GIT_USERNAME: ${{ github.actor }}
POETRY_TEST_INTEGRATION_GIT_PASSWORD: ${{ github.token }}
- run: git diff --exit-code --stat HEAD
pytest-export:
name: pytest (poetry-plugin-export)
runs-on: ${{ inputs.runner }}
if: inputs.run-pytest-export
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
path: poetry
- uses: ./poetry/.github/actions/bootstrap-poetry
with:
python-version: ${{ inputs.python-version }}
- name: Get poetry-plugin-export version
run: |
PLUGIN_VERSION=$(curl -s https://pypi.org/pypi/poetry-plugin-export/json | jq -r ".info.version")
echo "Found version ${PLUGIN_VERSION}"
echo version=${PLUGIN_VERSION} >> $GITHUB_OUTPUT
id: poetry-plugin-export-version
- name: Check out poetry-plugin-export
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
path: poetry-plugin-export
repository: python-poetry/poetry-plugin-export
# use main for now because of poetry-core#826
# ref: refs/tags/${{ steps.poetry-plugin-export-version.outputs.version }}
- name: Use local poetry
working-directory: poetry-plugin-export
# Replace the python version to avoid conflicts
# if the plugin still supports a wider range than Poetry itself.
run: |
perl -pi -e 's/^requires-python =.*$/requires-python = "~='"${PYTHON_VERSION}"'"/' pyproject.toml
poetry remove --lock poetry-core # use whatever poetry uses
poetry add --lock --group dev ../poetry
env:
PYTHON_VERSION: ${{ inputs.python-version }}
# This step can be removed after having released a poetry-plugin-export version
# that has cffi>=1.17.0 in its lock file.
- name: Force more recent cffi (workaround for Python 3.13)
working-directory: poetry-plugin-export
run: poetry update --lock cffi
- name: Install
working-directory: poetry-plugin-export
run: poetry install
- name: Run tests
working-directory: poetry-plugin-export
run: poetry run pytest -v
- name: Check for clean working tree
working-directory: poetry-plugin-export
run: |
git checkout -- pyproject.toml poetry.lock
git diff --exit-code --stat HEAD
================================================
FILE: .github/workflows/backport.yaml
================================================
name: Backport
on:
pull_request_target: # zizmor: ignore[dangerous-triggers]
types:
- closed
- labeled
# we create the token we need later on
permissions: {}
jobs:
backport:
name: Create backport
runs-on: ubuntu-latest
# This workflow only applies to merged PRs; and triggers on a PR being closed, or the backport label being applied.
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
if: >
github.event.pull_request.merged
&& (
github.event.action == 'closed'
||
(github.event.action == 'labeled' && contains(github.event.label.name, 'backport/')
)
)
steps:
- uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
id: app-token
with:
app-id: ${{ secrets.POETRY_TOKEN_APP_ID }}
private-key: ${{ secrets.POETRY_TOKEN_APP_KEY }}
- uses: tibdex/backport@9565281eda0731b1d20c4025c43339fb0a23812e # v2.0.4
with:
github_token: ${{ steps.app-token.outputs.token }}
title_template: "[<%= base %>] <%= title %>"
label_pattern: "^backport/(?([^ ]+))$"
================================================
FILE: .github/workflows/docs.yaml
================================================
name: Documentation Preview
on:
pull_request:
# allow repository maintainers to modify and test workflow
paths:
- ".github/workflows/docs.yaml"
pull_request_target: # zizmor: ignore[dangerous-triggers]
# enable runs for this workflow when labeled as documentation only
# prevent execution when the workflow itself is modified from a fork
types:
- labeled
- synchronize
paths:
- "docs/**"
jobs:
deploy:
name: Build & Deploy
runs-on: ubuntu-latest
if: >
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'impact/docs'))
|| (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
repository: python-poetry/website
# use .github from pull request target instead of pull_request.head
# for pull_request_target trigger to avoid arbitrary code execution
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
path: poetry-github
sparse-checkout: .github
# only checkout docs from pull_request.head to not use something else by accident
# for pull_request_target trigger (security)
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
path: poetry-docs
ref: ${{ github.event.pull_request.head.sha }}
sparse-checkout: docs
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: "18"
- uses: ./poetry-github/.github/actions/bootstrap-poetry
- uses: ./poetry-github/.github/actions/poetry-install
with:
args: --no-root --only main
- name: website-build
run: |
# Rebuild the docs files from the PR checkout.
poetry run python bin/website build --local ./poetry-docs
# Build website assets (CSS/JS).
npm ci && npm run prod
# Build the static website.
npx hugo --minify --logLevel info
- uses: amondnet/vercel-action@888da851026e0573da056b061931bcb765a915c4 # v41.1.4
with:
vercel-version: 41.1.4
vercel-token: ${{ secrets.VERCEL_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
scope: python-poetry
github-comment: true
working-directory: public
================================================
FILE: .github/workflows/lock-threads.yaml
================================================
name: Lock Threads
on:
schedule:
- cron: '0 0 * * *' # every day at midnight
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}
jobs:
lock-issues:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 # v6.0.0
with:
process-only: issues
issue-inactive-days: 30
issue-comment: >
This issue has been automatically locked since there
has not been any recent activity after it was closed.
Please open a new issue for related bugs.
lock-prs:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 # v6.0.0
with:
process-only: prs
pr-inactive-days: 30
pr-comment: >
This pull request has been automatically locked since there
has not been any recent activity after it was closed.
Please open a new issue for related bugs.
================================================
FILE: .github/workflows/release.yaml
================================================
name: Release
on:
release:
types: [published]
permissions: {}
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- run: pipx run build
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: distfiles
path: dist/
if-no-files-found: error
upload-github:
name: Upload (GitHub)
runs-on: ubuntu-latest
permissions:
contents: write
needs: build
steps:
# We need to be in a git repo for gh to work.
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: distfiles
path: dist/
- run: gh release upload "${TAG_NAME}" dist/*.{tar.gz,whl}
env:
GH_TOKEN: ${{ github.token }}
TAG_NAME: ${{ github.event.release.tag_name }}
upload-pypi:
name: Upload (PyPI)
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/poetry/
permissions:
id-token: write
needs: build
steps:
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: distfiles
path: dist/
- uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
print-hash: true
================================================
FILE: .github/workflows/tests.yaml
================================================
name: Tests
on:
push:
pull_request:
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
defaults:
run:
shell: bash
permissions: {}
jobs:
changes:
name: Detect changed files
runs-on: ubuntu-latest
outputs:
project: ${{ steps.changes.outputs.project }}
fixtures-pypi: ${{ steps.changes.outputs.fixtures-pypi }}
src: ${{ steps.changes.outputs.src }}
tests: ${{ steps.changes.outputs.tests }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
filters: |
workflow: &workflow
- '.github/actions/**'
- '.github/workflows/tests.yaml'
- '.github/workflows/.tests-matrix.yaml'
project: &project
- *workflow
- 'poetry.lock'
- 'pyproject.toml'
fixtures-pypi:
- *workflow
- 'tests/repositories/fixtures/pypi.org/**'
src:
- *project
- 'src/**/*.py'
tests:
- *project
- 'src/**/*.py'
- 'tests/**'
lockfile:
name: Check poetry.lock
runs-on: ubuntu-latest
if: needs.changes.outputs.project == 'true'
needs: changes
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: ./.github/actions/bootstrap-poetry
- run: poetry check --lock
smoke:
name: Smoke-test build and install
runs-on: ubuntu-latest
if: needs.changes.outputs.project == 'true'
needs: lockfile
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- run: pipx run build
- run: pipx run twine check --strict dist/*
- run: pipx install --suffix=@build dist/*.whl
- uses: ./.github/actions/bootstrap-poetry
# Smoke test: confirm the version of the installed wheel matches the project.
- run: poetry@build --version | grep $(poetry version --short)
fixtures-pypi:
name: Check fixtures (PyPI)
runs-on: ubuntu-latest
if: needs.changes.outputs.fixtures-pypi == 'true'
needs: changes
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: ./.github/actions/bootstrap-poetry
- uses: ./.github/actions/poetry-install
with:
args: --only main,test
- run: poetry run env PYTHONPATH="$GITHUB_WORKSPACE" python tests/repositories/fixtures/pypi.org/generate.py
- run: git diff --exit-code --stat HEAD tests/repositories/fixtures/pypi.org
tests-matrix:
# Use this matrix with multiple jobs defined in a reusable workflow:
uses: ./.github/workflows/.tests-matrix.yaml
name: "${{ matrix.os.name }} (Python ${{ matrix.python-version }})"
if: '!failure()'
needs:
- lockfile
- changes
with:
runner: ${{ matrix.os.image }}
python-version: ${{ matrix.python-version }}
run-mypy: ${{ needs.changes.outputs.tests == 'true' }}
run-pytest: ${{ needs.changes.outputs.tests == 'true' }}
run-pytest-export: ${{ needs.changes.outputs.src == 'true' }}
secrets: inherit # zizmor: ignore[secrets-inherit]
strategy:
matrix:
os:
- name: Ubuntu
image: ubuntu-22.04
- name: Windows
image: windows-2022
- name: macOS aarch64
image: macos-14
python-version:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
fail-fast: false
status:
name: Status
runs-on: ubuntu-latest
if: always()
needs:
- lockfile
- smoke
- fixtures-pypi
- tests-matrix
steps:
- run: ${{ (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) && 'false' || 'true' }}
================================================
FILE: .gitignore
================================================
*.pyc
# Packages
/dist/*
# Unit test / coverage reports
.coverage
.pytest_cache
.DS_Store
.idea/*
.python-version
.vscode/*
/docs/site/*
.mypy_cache
.venv
/poetry.toml
================================================
FILE: .pre-commit-config.yaml
================================================
ci:
autofix_prs: false
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
exclude: tests/repositories/fixtures/pypi.org/metadata/.*\.metadata
- id: end-of-file-fixer
exclude: ^.*\.egg-info/|tests/repositories/fixtures/pypi.org/metadata/.*\.metadata
- id: check-merge-conflict
exclude: tests/repositories/fixtures/installed/vendor/py3.7/attrs-19.3.0.dist-info/METADATA
- id: check-case-conflict
- id: check-json
- id: check-toml
exclude: tests/fixtures/invalid_lock/poetry\.lock
- id: check-yaml
- id: pretty-format-json
args: [--autofix, --no-ensure-ascii, --no-sort-keys]
- id: check-ast
- id: debug-statements
- id: check-docstring-first
- repo: https://github.com/pre-commit/pre-commit
rev: v4.5.1
hooks:
- id: validate_manifest
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.4
hooks:
- id: ruff-check
- id: ruff-format
- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.22.0
hooks:
- id: zizmor
================================================
FILE: .pre-commit-hooks.yaml
================================================
- id: poetry-check
name: poetry-check
description: run poetry check to validate config
entry: poetry check
language: python
pass_filenames: false
files: ^(.*/)?(poetry\.lock|pyproject\.toml)$
- id: poetry-lock
name: poetry-lock
description: run poetry lock to update lock file
entry: poetry lock
language: python
pass_filenames: false
files: ^(.*/)?(poetry\.lock|pyproject\.toml)$
- id: poetry-install
name: poetry-install
description: run poetry install to install dependencies from the lock file
entry: poetry install
language: python
pass_filenames: false
stages: [post-checkout, post-merge]
always_run: true
================================================
FILE: CHANGELOG.md
================================================
# Change Log
## [2.3.2] - 2026-02-01
### Changed
- Allow `dulwich>=1.0` ([#10701](https://github.com/python-poetry/poetry/pull/10701)).
### poetry-core ([`2.3.1`](https://github.com/python-poetry/poetry-core/releases/tag/2.3.1))
- Fix an issue where `platform_release` could not be parsed on Windows Server ([#911](https://github.com/python-poetry/poetry-core/pull/911)).
## [2.3.1] - 2026-01-20
### Fixed
- Fix an issue where cached information about each package was always considered outdated ([#10699](https://github.com/python-poetry/poetry/pull/10699)).
### Docs
- Document SHELL_VERBOSITY environment variable ([#10678](https://github.com/python-poetry/poetry/pull/10678)).
## [2.3.0] - 2026-01-18
### Added
- **Add support for exporting `pylock.toml` files with `poetry-plugin-export`** ([#10677](https://github.com/python-poetry/poetry/pull/10677)).
- Add support for specifying build constraints for dependencies ([#10388](https://github.com/python-poetry/poetry/pull/10388)).
- Add support for publishing artifacts whose version is determined dynamically by the build-backend ([#10644](https://github.com/python-poetry/poetry/pull/10644)).
- Add support for editable project plugins ([#10661](https://github.com/python-poetry/poetry/pull/10661)).
- Check `requires-poetry` before any other validation ([#10593](https://github.com/python-poetry/poetry/pull/10593)).
- Validate the content of `project.readme` when running `poetry check` ([#10604](https://github.com/python-poetry/poetry/pull/10604)).
- Add the option to clear all caches by making the cache name in `poetry cache clear` optional ([#10627](https://github.com/python-poetry/poetry/pull/10627)).
- Automatically update the cache for packages where the locked files differ from cached files ([#10657](https://github.com/python-poetry/poetry/pull/10657)).
- Suggest to clear the cache if running a command with `--no-cache` solves an issue ([#10585](https://github.com/python-poetry/poetry/pull/10585)).
- Propose `poetry init` when trying `poetry new` for an existing directory ([#10563](https://github.com/python-poetry/poetry/pull/10563)).
- Add support for `poetry publish --skip-existing` for new Nexus OSS versions ([#10603](https://github.com/python-poetry/poetry/pull/10603)).
- Show Poetry's own Python's path in `poetry debug info` ([#10588](https://github.com/python-poetry/poetry/pull/10588)).
### Changed
- **Drop support for Python 3.9** ([#10634](https://github.com/python-poetry/poetry/pull/10634)).
- **Change the default of `installer.re-resolve` from `true` to `false`** ([#10622](https://github.com/python-poetry/poetry/pull/10622)).
- **PEP 735 dependency groups are considered in the lock file hash** ([#10621](https://github.com/python-poetry/poetry/pull/10621)).
- Deprecate `poetry.utils._compat.metadata`, which is sometimes used in plugins, in favor of `importlib.metadata` ([#10634](https://github.com/python-poetry/poetry/pull/10634)).
- Improve managing free-threaded Python versions with `poetry python` ([#10606](https://github.com/python-poetry/poetry/pull/10606)).
- Prefer JSON API to HTML API in legacy repositories ([#10672](https://github.com/python-poetry/poetry/pull/10672)).
- When running `poetry init`, only add the readme field in the `pyproject.toml` if the readme file exists ([#10679](https://github.com/python-poetry/poetry/pull/10679)).
- Raise an error if no hash can be determined for any distribution link of a package ([#10673](https://github.com/python-poetry/poetry/pull/10673)).
- Require `dulwich>=0.25.0` ([#10674](https://github.com/python-poetry/poetry/pull/10674)).
### Fixed
- Fix an issue where `poetry remove` did not work for PEP 735 dependency groups with `include-group` items ([#10587](https://github.com/python-poetry/poetry/pull/10587)).
- Fix an issue where `poetry remove` caused dangling `include-group` references in PEP 735 dependency groups ([#10590](https://github.com/python-poetry/poetry/pull/10590)).
- Fix an issue where `poetry add` did not work for PEP 735 dependency groups with `include-group` items ([#10636](https://github.com/python-poetry/poetry/pull/10636)).
- Fix an issue where PEP 735 dependency groups were not considered in the lock file hash ([#10621](https://github.com/python-poetry/poetry/pull/10621)).
- Fix an issue where wrong markers were locked for a dependency that was required by several groups with different markers ([#10613](https://github.com/python-poetry/poetry/pull/10613)).
- Fix an issue where non-deterministic markers were created in a method used by `poetry-plugin-export` ([#10667](https://github.com/python-poetry/poetry/pull/10667)).
- Fix an issue where wrong wheels were chosen for installation in free-threaded Python environments if Poetry itself was not installed with free-threaded Python ([#10614](https://github.com/python-poetry/poetry/pull/10614)).
- Fix an issue where `poetry publish` used the metadata of the project instead of the metadata of the build artifact ([#10624](https://github.com/python-poetry/poetry/pull/10624)).
- Fix an issue where `poetry env use` just used another Python version instead of failing when the requested version was not supported by the project ([#10685](https://github.com/python-poetry/poetry/pull/10685)).
- Fix an issue where `poetry env activate` returned the wrong command for `dash` ([#10696](https://github.com/python-poetry/poetry/pull/10696)).
- Fix an issue where `data-dir` and `python.installation-dir` could not be set ([#10595](https://github.com/python-poetry/poetry/pull/10595)).
- Fix an issue where Python and pip executables were not correctly detected on Windows ([#10645](https://github.com/python-poetry/poetry/pull/10645)).
- Fix an issue where invalid template variables in `virtualenvs.prompt` caused an incomprehensible error message ([#10648](https://github.com/python-poetry/poetry/pull/10648)).
### Docs
- Add a warning about `~/.netrc` for Poetry credential configuration ([#10630](https://github.com/python-poetry/poetry/pull/10630)).
- Clarify that the local configuration takes precedence over the global configuration ([#10676](https://github.com/python-poetry/poetry/pull/10676)).
- Add an explanation in which cases `packages` are automatically detected ([#10680](https://github.com/python-poetry/poetry/pull/10680)).
### poetry-core ([`2.3.0`](https://github.com/python-poetry/poetry-core/releases/tag/2.3.0))
- Normalize versions ([#893](https://github.com/python-poetry/poetry-core/pull/893)).
- Fix an issue where unsatisfiable requirements did not raise an error ([#891](https://github.com/python-poetry/poetry-core/pull/891)).
- Fix an issue where the implicit main group did not exist if it was explicitly declared as not having any dependencies ([#892](https://github.com/python-poetry/poetry-core/pull/892)).
- Fix an issue where `python_full_version` markers with pre-release versions were parsed incorrectly ([#893](https://github.com/python-poetry/poetry-core/pull/893)).
## [2.2.1] - 2025-09-21
### Fixed
- Fix an issue where `poetry self show` failed with a message about an invalid output format ([#10560](https://github.com/python-poetry/poetry/pull/10560)).
### Docs
- Remove outdated statements about dependency groups ([#10561](https://github.com/python-poetry/poetry/pull/10561)).
### poetry-core ([`2.2.1`](https://github.com/python-poetry/poetry-core/releases/tag/2.2.1))
- Fix an issue where it was not possible to declare a PEP 735 dependency group as optional ([#888](https://github.com/python-poetry/poetry-core/pull/888)).
## [2.2.0] - 2025-09-14
### Added
- **Add support for nesting dependency groups** ([#10166](https://github.com/python-poetry/poetry/pull/10166)).
- **Add support for PEP 735 dependency groups** ([#10130](https://github.com/python-poetry/poetry/pull/10130)).
- **Add support for PEP 639 license clarity** ([#10413](https://github.com/python-poetry/poetry/pull/10413)).
- Add a `--format` option to `poetry show` to alternatively output json format ([#10487](https://github.com/python-poetry/poetry/pull/10487)).
- Add official support for Python 3.14 ([#10514](https://github.com/python-poetry/poetry/pull/10514)).
### Changed
- **Normalize dependency group names** ([#10387](https://github.com/python-poetry/poetry/pull/10387)).
- Change `installer.no-binary` and `installer.only-binary` so that explicit package names will take precedence over `:all:` ([#10278](https://github.com/python-poetry/poetry/pull/10278)).
- Improve log output during `poetry install` when a wheel is built from source ([#10404](https://github.com/python-poetry/poetry/pull/10404)).
- Improve error message in case a file lock could not be acquired while cloning a git repository ([#10535](https://github.com/python-poetry/poetry/pull/10535)).
- Require `dulwich>=0.24.0` ([#10492](https://github.com/python-poetry/poetry/pull/10492)).
- Allow `virtualenv>=20.33` again ([#10506](https://github.com/python-poetry/poetry/pull/10506)).
- Allow `findpython>=0.7` ([#10510](https://github.com/python-poetry/poetry/pull/10510)).
- Allow `importlib-metadata>=8.7` ([#10511](https://github.com/python-poetry/poetry/pull/10511)).
### Fixed
- Fix an issue where `poetry new` did not create the project structure in an existing empty directory ([#10431](https://github.com/python-poetry/poetry/pull/10431)).
- Fix an issue where a dependency that was required for a specific Python version was not installed into an environment of a pre-release Python version ([#10516](https://github.com/python-poetry/poetry/pull/10516)).
### poetry-core ([`2.2.0`](https://github.com/python-poetry/poetry-core/releases/tag/2.2.0))
- Deprecate table values and values that are not valid SPDX expressions for `[project.license]` ([#870](https://github.com/python-poetry/poetry-core/pull/870)).
- Fix an issue where explicitly included files that are in `.gitignore` were not included in the distribution ([#874](https://github.com/python-poetry/poetry-core/pull/874)).
- Fix an issue where marker operations could result in invalid markers ([#875](https://github.com/python-poetry/poetry-core/pull/875)).
## [2.1.4] - 2025-08-05
### Changed
- Require `virtualenv<20.33` to work around an issue where Poetry uses the wrong Python version ([#10491](https://github.com/python-poetry/poetry/pull/10491)).
- Improve the error messages for the validation of the `pyproject.toml` file ([#10471](https://github.com/python-poetry/poetry/pull/10471)).
### Fixed
- Fix an issue where project plugins were installed even though `poetry install` was called with `--no-plugins` ([#10405](https://github.com/python-poetry/poetry/pull/10405)).
- Fix an issue where dependency resolution failed for self-referential extras with duplicate dependencies ([#10488](https://github.com/python-poetry/poetry/pull/10488)).
### Docs
- Clarify how to include files that were automatically excluded via VCS ignore settings ([#10442](https://github.com/python-poetry/poetry/pull/10442)).
- Clarify the behavior of `poetry add` if no version constraint is explicitly specified ([#10445](https://github.com/python-poetry/poetry/pull/10445)).
## [2.1.3] - 2025-05-04
### Changed
- Require `importlib-metadata<8.7` for Python 3.9 because of a breaking change in importlib-metadata 8.7 ([#10374](https://github.com/python-poetry/poetry/pull/10374)).
### Fixed
- Fix an issue where re-locking failed for incomplete multiple-constraints dependencies with explicit sources ([#10324](https://github.com/python-poetry/poetry/pull/10324)).
- Fix an issue where the `--directory` option did not work if a plugin, which accesses the poetry instance during its activation, was installed ([#10352](https://github.com/python-poetry/poetry/pull/10352)).
- Fix an issue where `poetry env activate -v` printed additional information to stdout instead of stderr so that the output could not be used as designed ([#10353](https://github.com/python-poetry/poetry/pull/10353)).
- Fix an issue where the original error was not printed if building a git dependency failed ([#10366](https://github.com/python-poetry/poetry/pull/10366)).
- Fix an issue where wheels for the wrong platform were installed in rare cases. ([#10361](https://github.com/python-poetry/poetry/pull/10361)).
### poetry-core ([`2.1.3`](https://github.com/python-poetry/poetry-core/releases/tag/2.1.3))
- Fix an issue where the union of specific inverse or partially inverse markers was not simplified ([#858](https://github.com/python-poetry/poetry-core/pull/858)).
- Fix an issue where optional dependencies defined in the `project` section were treated as non-optional when a source was defined for them in the `tool.poetry` section ([#857](https://github.com/python-poetry/poetry-core/pull/857)).
- Fix an issue where markers with `===` were not parsed correctly ([#860](https://github.com/python-poetry/poetry-core/pull/860)).
- Fix an issue where local versions with upper case letters caused an error ([#859](https://github.com/python-poetry/poetry-core/pull/859)).
- Fix an issue where `extra` markers with a value starting with "in" were not validated correctly ([#862](https://github.com/python-poetry/poetry-core/pull/862)).
## [2.1.2] - 2025-03-29
### Changed
- Improve performance of locking dependencies ([#10275](https://github.com/python-poetry/poetry/pull/10275)).
### Fixed
- Fix an issue where markers were not locked correctly ([#10240](https://github.com/python-poetry/poetry/pull/10240)).
- Fix an issue where the result of `poetry lock` was not deterministic ([#10276](https://github.com/python-poetry/poetry/pull/10276)).
- Fix an issue where `poetry env activate` returned the wrong command for `tcsh` ([#10243](https://github.com/python-poetry/poetry/pull/10243)).
- Fix an issue where `poetry env activate` returned the wrong command for `pwsh` on Linux ([#10256](https://github.com/python-poetry/poetry/pull/10256)).
### Docs
- Update basic usage section to reflect new default layout ([#10203](https://github.com/python-poetry/poetry/pull/10203)).
### poetry-core ([`2.1.2`](https://github.com/python-poetry/poetry-core/releases/tag/2.1.2))
- Improve performance of marker operations ([#851](https://github.com/python-poetry/poetry-core/pull/851)).
- Fix an issue where incorrect markers were calculated when removing parts covered by the project's Python constraint ([#841](https://github.com/python-poetry/poetry-core/pull/841),
[#846](https://github.com/python-poetry/poetry-core/pull/846)).
- Fix an issue where `extra` markers were not simplified ([#842](https://github.com/python-poetry/poetry-core/pull/842),
[#845](https://github.com/python-poetry/poetry-core/pull/845),
[#847](https://github.com/python-poetry/poetry-core/pull/847)).
- Fix an issue where the intersection and union of markers was not deterministic ([#843](https://github.com/python-poetry/poetry-core/pull/843)).
- Fix an issue where the intersection of `python_version` markers was not recognized as empty ([#849](https://github.com/python-poetry/poetry-core/pull/849)).
- Fix an issue where `python_version` markers were not simplified ([#848](https://github.com/python-poetry/poetry-core/pull/848),
[#851](https://github.com/python-poetry/poetry-core/pull/851)).
- Fix an issue where Python constraints on a package were converted into invalid markers ([#853](https://github.com/python-poetry/poetry-core/pull/853)).
## [2.1.1] - 2025-02-16
### Fixed
- Fix an issue where `poetry env use python` does not choose the Python from the PATH ([#10187](https://github.com/python-poetry/poetry/pull/10187)).
### poetry-core ([`2.1.1`](https://github.com/python-poetry/poetry-core/releases/tag/2.1.1))
- Fix an issue where simplifying a `python_version` marker resulted in an invalid marker ([#838](https://github.com/python-poetry/poetry-core/pull/838)).
## [2.1.0] - 2025-02-15
### Added
- **Make `build` command build-system agnostic** ([#10059](https://github.com/python-poetry/poetry/pull/10059),
[#10092](https://github.com/python-poetry/poetry/pull/10092)).
- Add a `--config-settings` option to `poetry build` ([#10059](https://github.com/python-poetry/poetry/pull/10059)).
- Add support for defining `config-settings` when building dependencies ([#10129](https://github.com/python-poetry/poetry/pull/10129)).
- **Add (experimental) commands to manage Python installations** ([#10112](https://github.com/python-poetry/poetry/pull/10112)).
- Use `findpython` to find the Python interpreters ([#10097](https://github.com/python-poetry/poetry/pull/10097)).
- Add a `--no-truncate` option to `poetry show` ([#9580](https://github.com/python-poetry/poetry/pull/9580)).
- Re-add support for passwords with empty usernames ([#10088](https://github.com/python-poetry/poetry/pull/10088)).
- Add better error messages ([#10053](https://github.com/python-poetry/poetry/pull/10053),
[#10065]( https://github.com/python-poetry/poetry/pull/10065),
[#10126](https://github.com/python-poetry/poetry/pull/10126),
[#10127](https://github.com/python-poetry/poetry/pull/10127),
[#10132](https://github.com/python-poetry/poetry/pull/10132)).
### Changed
- **`poetry new` defaults to "src" layout by default** ([#10135](https://github.com/python-poetry/poetry/pull/10135)).
- Improve performance of locking dependencies ([#10111](https://github.com/python-poetry/poetry/pull/10111),
[#10114](https://github.com/python-poetry/poetry/pull/10114),
[#10138](https://github.com/python-poetry/poetry/pull/10138),
[#10146](https://github.com/python-poetry/poetry/pull/10146)).
- Deprecate adding sources without specifying `--priority` ([#10134](https://github.com/python-poetry/poetry/pull/10134)).
### Fixed
- Fix an issue where global options were not handled correctly when positioned after command options ([#10021](https://github.com/python-poetry/poetry/pull/10021),
[#10067](https://github.com/python-poetry/poetry/pull/10067),
[#10128](https://github.com/python-poetry/poetry/pull/10128)).
- Fix an issue where building a dependency from source failed because of a conflict between build-system dependencies that were not required for the target environment ([#10048](https://github.com/python-poetry/poetry/pull/10048)).
- Fix an issue where `poetry init` was not able to find a package on PyPI while adding dependencies interactively ([#10055](https://github.com/python-poetry/poetry/pull/10055)).
- Fix an issue where the `@latest` descriptor was incorrectly passed to the core requirement parser ([#10069](https://github.com/python-poetry/poetry/pull/10069)).
- Fix an issue where Boolean environment variables set to `True` (in contrast to `true`) were interpreted as `false` ([#10080](https://github.com/python-poetry/poetry/pull/10080)).
- Fix an issue where `poetry env activate` reported a misleading error message ([#10087](https://github.com/python-poetry/poetry/pull/10087)).
- Fix an issue where adding an optional dependency with `poetry add --optional` would not correctly update the lock file ([#10076](https://github.com/python-poetry/poetry/pull/10076)).
- Fix an issue where `pip` was not installed/updated before other dependencies resulting in a race condition ([#10102](https://github.com/python-poetry/poetry/pull/10102)).
- Fix an issue where Poetry freezes when multiple threads attempt to unlock the `keyring` simultaneously ([#10062](https://github.com/python-poetry/poetry/pull/10062)).
- Fix an issue where markers with extras were not locked correctly ([#10119](https://github.com/python-poetry/poetry/pull/10119)).
- Fix an issue where self-referential extras were not resolved correctly ([#10106](https://github.com/python-poetry/poetry/pull/10106)).
- Fix an issue where Poetry could not be run from a `zipapp` ([#10074](https://github.com/python-poetry/poetry/pull/10074)).
- Fix an issue where installation failed with a permission error when using the system environment as a user without write access to system site packages ([#9014](https://github.com/python-poetry/poetry/pull/9014)).
- Fix an issue where a version of a dependency that is not compatible with the project's python constraint was locked. ([#10141](https://github.com/python-poetry/poetry/pull/10141)).
- Fix an issue where Poetry wrongly reported that the current project's supported Python range is not compatible with some of the required packages Python requirement ([#10157](https://github.com/python-poetry/poetry/pull/10157)).
- Fix an issue where the requested extras of a dependency were ignored if the same dependency (with same extras) was specified in multiple groups ([#10158](https://github.com/python-poetry/poetry/pull/10158)).
### Docs
- Sort commands by name in the CLI reference ([#10035](https://github.com/python-poetry/poetry/pull/10035)).
- Add missing documentation for `env` commands ([#10027](https://github.com/python-poetry/poetry/pull/10027)).
- Clarify that the `name` and `version` fields are always required if the `project` section is specified ([#10033](https://github.com/python-poetry/poetry/pull/10033)).
- Add a note about restarting the shell for tab completion changes to take effect ([#10070](https://github.com/python-poetry/poetry/pull/10070)).
- Fix the example for `project.gui-scripts` [#10121](https://github.com/python-poetry/poetry/pull/10121).
- Explain how to include files as scripts in the project configuration ([#9572](https://github.com/python-poetry/poetry/pull/9572),
[#10133](https://github.com/python-poetry/poetry/pull/10133)).
- Add additional information on specifying required python versions ([#10104](https://github.com/python-poetry/poetry/pull/10104)).
### poetry-core ([`2.1.0`](https://github.com/python-poetry/poetry-core/releases/tag/2.1.0))
- Fix an issue where inclusive ordering with post releases was inconsistent with PEP 440 ([#379](https://github.com/python-poetry/poetry-core/pull/379)).
- Fix an issue where invalid URI tokens in PEP 508 requirement strings were silently discarded ([#817](https://github.com/python-poetry/poetry-core/pull/817)).
- Fix an issue where wrong markers were calculated when removing parts covered by the project's python constraint ([#824](https://github.com/python-poetry/poetry-core/pull/824)).
- Fix an issue where optional dependencies that are not part of an extra were included in the wheel metadata ([#830](https://github.com/python-poetry/poetry-core/pull/830)).
- Fix an issue where the `__pycache__` directory and `*.pyc` files were included in sdists and wheels ([#835](https://github.com/python-poetry/poetry-core/pull/835)).
## [2.0.1] - 2025-01-11
### Added
- Add support for `poetry search` in legacy sources ([#9949](https://github.com/python-poetry/poetry/pull/9949)).
- Add a message in the `poetry source show` output when PyPI is implicitly enabled ([#9974](https://github.com/python-poetry/poetry/pull/9974)).
### Changed
- Improve performance for merging markers from overrides at the end of dependency resolution ([#10018](https://github.com/python-poetry/poetry/pull/10018)).
### Fixed
- Fix an issue where `poetry sync` did not remove packages that were not requested ([#9946](https://github.com/python-poetry/poetry/pull/9946)).
- Fix an issue where `poetry check` failed even though there were just warnings and add a `--strict` option to fail on warnings ([#9983](https://github.com/python-poetry/poetry/pull/9983)).
- Fix an issue where `poetry update`, `poetry add` and `poetry remove` with `--only` uninstalled packages from other groups ([#10014](https://github.com/python-poetry/poetry/pull/10014)).
- Fix an issue where `poetry update`, `poetry add` and `poetry remove` uninstalled all extra packages ([#10016](https://github.com/python-poetry/poetry/pull/10016)).
- Fix an issue where `poetry self update` did not recognize Poetry's own environment ([#9995](https://github.com/python-poetry/poetry/pull/9995)).
- Fix an issue where read-only system site-packages were not considered when loading an environment with system site-packages ([#9942](https://github.com/python-poetry/poetry/pull/9942)).
- Fix an issue where an error message in `poetry install` started with `Warning:` instead of `Error:` ([#9945](https://github.com/python-poetry/poetry/pull/9945)).
- Fix an issue where `Command.set_poetry`, which is used by plugins, was removed ([#9981](https://github.com/python-poetry/poetry/pull/9981)).
- Fix an issue where the help text of `poetry build --clean` showed a malformed short option instead of the description ([#9994](https://github.com/python-poetry/poetry/pull/9994)).
### Docs
- Add a FAQ entry for the migration from Poetry-specific fields to the `project` section ([#9996](https://github.com/python-poetry/poetry/pull/9996)).
- Fix examples for `project.readme` and `project.urls` ([#9948](https://github.com/python-poetry/poetry/pull/9948)).
- Add a warning that package sources are a Poetry-specific feature that is not included in core metadata ([#9935](https://github.com/python-poetry/poetry/pull/9935)).
- Replace `poetry install --sync` with `poetry sync` in the section about synchronizing dependencies ([#9944](https://github.com/python-poetry/poetry/pull/9944)).
- Replace `poetry shell` with `poetry env activate` in the basic usage section ([#9963](https://github.com/python-poetry/poetry/pull/9963)).
- Mention that `project.name` is always required when the `project` section is used ([#9989](https://github.com/python-poetry/poetry/pull/9989)).
- Fix the constraint of `poetry-plugin-export` in the section about `poetry export` ([#9954](https://github.com/python-poetry/poetry/pull/9954)).
### poetry-core ([`2.0.1`](https://github.com/python-poetry/poetry-core/releases/tag/2.0.1))
- Replace the deprecated core metadata field `Home-page` with `Project-URL: Homepage` ([#807](https://github.com/python-poetry/poetry-core/pull/807)).
- Fix an issue where includes from `tool.poetry.packages` without a specified `format` were not initialized with the default value resulting in a `KeyError` ([#805](https://github.com/python-poetry/poetry-core/pull/805)).
- Fix an issue where some `project.urls` entries were not processed correctly resulting in a `KeyError` ([#807](https://github.com/python-poetry/poetry-core/pull/807)).
- Fix an issue where dynamic `project.dependencies` via `tool.poetry.dependencies` were ignored if `project.optional-dependencies` were defined ([#811](https://github.com/python-poetry/poetry-core/pull/811)).
## [2.0.0] - 2025-01-05
### Added
- **Add support for the `project` section in the `pyproject.toml` file according to PEP 621** ([#9135](https://github.com/python-poetry/poetry/pull/9135),
[#9917](https://github.com/python-poetry/poetry/pull/9917)).
- **Add support for defining Poetry plugins that are required by the project and automatically installed if not present** ([#9547](https://github.com/python-poetry/poetry/pull/9547)).
- **Lock resulting markers and groups and add a `installer.re-resolve` option (default: `true`) to allow installation without re-resolving** ([#9427](https://github.com/python-poetry/poetry/pull/9427)).
- Add a `--local-version` option to `poetry build` ([#9064](https://github.com/python-poetry/poetry/pull/9064)).
- Add a `--clean` option to `poetry build` ([#9067](https://github.com/python-poetry/poetry/pull/9067)).
- Add FIPS support for `poetry publish` ([#9101](https://github.com/python-poetry/poetry/pull/9101)).
- Add the option to use `poetry new` interactively and configure more fields ([#9101](https://github.com/python-poetry/poetry/pull/9101)).
- Add a config option `installer.only-binary` to enforce the use of binary distribution formats ([#9150](https://github.com/python-poetry/poetry/pull/9150)).
- Add backend support for legacy repository search ([#9132](https://github.com/python-poetry/poetry/pull/9132)).
- Add support to resume downloads from connection resets ([#9422](https://github.com/python-poetry/poetry/pull/9422)).
- Add the option to define a constraint for the required Poetry version to manage the project ([#9547](https://github.com/python-poetry/poetry/pull/9547)).
- Add an `--all-groups` option to `poetry install` ([#9744](https://github.com/python-poetry/poetry/pull/9744)).
- Add an `poetry env activate` command as replacement of `poetry shell` ([#9763](https://github.com/python-poetry/poetry/pull/9763)).
- Add a `--markers` option to `poetry add` to add a dependency with markers ([#9814](https://github.com/python-poetry/poetry/pull/9814)).
- Add a `--migrate` option to `poetry config` to migrate outdated configs ([#9830](https://github.com/python-poetry/poetry/pull/9830)).
- Add a `--project` option to search the `pyproject.toml` file in another directory without switching the directory ([#9831](https://github.com/python-poetry/poetry/pull/9831)).
- Add support for shortened hashes to define git dependencies ([#9748](https://github.com/python-poetry/poetry/pull/9748)).
- Add partial support for conflicting extras ([#9553](https://github.com/python-poetry/poetry/pull/9553)).
- Add a `poetry sync` command as replacement of `poetry install --sync` ([#9801](https://github.com/python-poetry/poetry/pull/9801)).
### Changed
- **Change the default behavior of `poetry lock` to `--no-update` and introduce a `--regenerate` option for the old default behavior** ([#9327](https://github.com/python-poetry/poetry/pull/9327)).
- **Remove the dependency on `poetry-plugin-export` so that `poetry export` is not included per default** ([#5980](https://github.com/python-poetry/poetry/pull/5980)).
- **Outsource `poetry shell` into `poetry-plugin-shell`** ([#9763](https://github.com/python-poetry/poetry/pull/9763)).
- **Change the interface of `poetry add --optional` to require an extra the optional dependency is added to** ([#9135](https://github.com/python-poetry/poetry/pull/9135)).
- **Actually switch the directory when using `--directory`/`-C`** ([#9831](https://github.com/python-poetry/poetry/pull/9831)).
- **Drop support for Python 3.8** ([#9692](https://github.com/python-poetry/poetry/pull/9692)).
- Rename `experimental.system-git-client` to `experimental.system-git` ([#9787](https://github.com/python-poetry/poetry/pull/9787), [#9795](https://github.com/python-poetry/poetry/pull/9795)).
- Replace `virtualenvs.prefer-active-python` by the inverse setting `virtualenvs.use-poetry-python` and prefer the active Python by default ([#9786](https://github.com/python-poetry/poetry/pull/9786)).
- Deprecate several fields in the `tool.poetry` section in favor of the respective fields in the `project` section in the `pyproject.toml` file ([#9135](https://github.com/python-poetry/poetry/pull/9135)).
- Deprecate `poetry install --sync` in favor of `poetry sync` ([#9801](https://github.com/python-poetry/poetry/pull/9801)).
- Upgrade the warning if the current project cannot be installed to an error ([#9333](https://github.com/python-poetry/poetry/pull/9333)).
- Remove special handling for `platformdirs 2.0` macOS config directory ([#8916](https://github.com/python-poetry/poetry/pull/8916)).
- Tweak PEP 517 builds ([#9094](https://github.com/python-poetry/poetry/pull/9094)).
- Use Poetry instead of pip to manage dependencies in isolated build environments ([#9168](https://github.com/python-poetry/poetry/pull/9168),
[#9227](https://github.com/python-poetry/poetry/pull/9227)).
- Trust empty `Requires-Dist` with modern metadata ([#9078](https://github.com/python-poetry/poetry/pull/9078)).
- Do PEP 517 builds instead of parsing `setup.py` to determine dependencies ([#9099](https://github.com/python-poetry/poetry/pull/9099)).
- Drop support for reading lock files prior version 1.0 (created with Poetry prior 1.1) ([#9345](https://github.com/python-poetry/poetry/pull/9345)).
- Default to `>=` instead of `^` for the Python requirement when initializing a new project ([#9558](https://github.com/python-poetry/poetry/pull/9558)).
- Limit `build-system` to the current major version of `poetry-core` when initializing a new project ([#9812](https://github.com/python-poetry/poetry/pull/9812)).
- Remove pip-based installation, i.e. `installer.modern-installation = false` ([#9392](https://github.com/python-poetry/poetry/pull/9392)).
- Remove `virtualenvs.options.no-setuptools` config option and never include `setuptools` per default ([#9331](https://github.com/python-poetry/poetry/pull/9331)).
- Rename exceptions to have an `Error` suffix ([#9705](https://github.com/python-poetry/poetry/pull/9705)).
- Remove deprecated CLI options and methods and revoke the deprecation of `--dev` ([#9732](https://github.com/python-poetry/poetry/pull/9732)).
- Ignore installed packages during dependency resolution ([#9851](https://github.com/python-poetry/poetry/pull/9851)).
- Improve the error message on upload failure ([#9701](https://github.com/python-poetry/poetry/pull/9701)).
- Improve the error message if the current project cannot be installed to include another root cause ([#9651](https://github.com/python-poetry/poetry/pull/9651)).
- Improve the output of `poetry show ` ([#9750](https://github.com/python-poetry/poetry/pull/9750)).
- Improve the error message for build errors ([#9870](https://github.com/python-poetry/poetry/pull/9870)).
- Improve the error message when trying to remove a package from a project without any dependencies ([#9918](https://github.com/python-poetry/poetry/pull/9918)).
- Drop the direct dependency on `crashtest` ([#9108](https://github.com/python-poetry/poetry/pull/9108)).
- Require `keyring>=23.3.1` ([#9167](https://github.com/python-poetry/poetry/pull/9167)).
- Require `build>=1.2.1` ([#9283](https://github.com/python-poetry/poetry/pull/9283)).
- Require `dulwich>=0.22.6` ([#9748](https://github.com/python-poetry/poetry/pull/9748)).
### Fixed
- Fix an issue where git dependencies with extras could only be cloned if a branch was specified explicitly ([#7028](https://github.com/python-poetry/poetry/pull/7028)).
- Fix an issue where `poetry env remove` failed if `virtualenvs.in-project` was set to `true` ([#9118](https://github.com/python-poetry/poetry/pull/9118)).
- Fix an issue where locking packages with a digit at the end of the name and non-standard sdist names failed ([#9189](https://github.com/python-poetry/poetry/pull/9189)).
- Fix an issue where credentials where not passed when trying to download an URL dependency ([#9202](https://github.com/python-poetry/poetry/pull/9202)).
- Fix an issue where using uncommon group names with `poetry add` resulted in a broken `pyproject.toml` ([#9277](https://github.com/python-poetry/poetry/pull/9277)).
- Fix an issue where an inconsistent entry regarding the patch version of Python was kept in `envs.toml` ([#9286](https://github.com/python-poetry/poetry/pull/9286)).
- Fix an issue where relative paths were not resolved properly when using `poetry build --directory` ([#9433](https://github.com/python-poetry/poetry/pull/9433)).
- Fix an issue where unrequested extras were not uninstalled when running `poetry install` without an existing lock file ([#9345](https://github.com/python-poetry/poetry/pull/9345)).
- Fix an issue where the `poetry-check` pre-commit hook did not trigger if only `poetry.lock` has changed ([#9504](https://github.com/python-poetry/poetry/pull/9504)).
- Fix an issue where files (rather than directories) could not be added as single page source ([#9166](https://github.com/python-poetry/poetry/pull/9166)).
- Fix an issue where invalid constraints were generated when adding a package with a local version specifier ([#9603](https://github.com/python-poetry/poetry/pull/9603)).
- Fix several encoding warnings ([#8893](https://github.com/python-poetry/poetry/pull/8893)).
- Fix an issue where `virtualenvs.prefer-active-python` was not respected ([#9278](https://github.com/python-poetry/poetry/pull/9278)).
- Fix an issue where the line endings of the lock file were changed ([#9468](https://github.com/python-poetry/poetry/pull/9468)).
- Fix an issue where installing multiple dependencies from the same git repository failed sporadically due to a race condition ([#9658](https://github.com/python-poetry/poetry/pull/9658)).
- Fix an issue where installing multiple dependencies from forked monorepos failed sporadically due to a race condition ([#9723](https://github.com/python-poetry/poetry/pull/9723)).
- Fix an issue where an extra package was not installed if it is required by multiple extras ([#9700](https://github.com/python-poetry/poetry/pull/9700)).
- Fix an issue where a `direct_url.json` with vcs URLs not compliant with PEP 610 was written ([#9007](https://github.com/python-poetry/poetry/pull/9007)).
- Fix an issue where other files than wheels were recognized as wheels ([#9770](https://github.com/python-poetry/poetry/pull/9770)).
- Fix an issue where `installer.max-workers` was ignored for the implicit PyPI source ([#9815](https://github.com/python-poetry/poetry/pull/9815)).
- Fix an issue where local settings (from `poetry.toml`) were ignored for the implicit PyPI source ([#9816](https://github.com/python-poetry/poetry/pull/9816)).
- Fix an issue where different `dulwich` versions resulted in different hashes for a git dependency from a tag ([#9849](https://github.com/python-poetry/poetry/pull/9849)).
- Fix an issue where installing a yanked package with no dependencies failed with an `IndexError` ([#9505](https://github.com/python-poetry/poetry/pull/9505)).
- Fix an issue where a package could not be added from a source that required an empty password ([#9850](https://github.com/python-poetry/poetry/pull/9850)).
- Fix an issue where setting `allow-prereleases = false` still allowed pre-releases if no other solution was found ([#9798](https://github.com/python-poetry/poetry/pull/9798)).
- Fix an issue where the wrong environment was used for checking if an installed package is from system site packages ([#9861](https://github.com/python-poetry/poetry/pull/9861)).
- Fix an issue where build errors from builds to retrieve metadata information were hidden ([#9870](https://github.com/python-poetry/poetry/pull/9870)).
- Fix an issue where `poetry check` falsely reported that an invalid source "pypi" is referenced in dependencies ([#9475](https://github.com/python-poetry/poetry/pull/9475)).
- Fix an issue where `poetry install --sync` tried to uninstall system site packages if the virtual environment was created with `virtualenvs.options.system-site-packages = true` ([#9863](https://github.com/python-poetry/poetry/pull/9863)).
- Fix an issue where HTTP streaming requests were not closed properly when not completely consumed ([#9899](https://github.com/python-poetry/poetry/pull/9899)).
### Docs
- Add information about getting test coverage in the contribution guide ([#9726](https://github.com/python-poetry/poetry/pull/9726)).
- Mention `pre-commit-update` as an alternative to `pre-commit autoupdate` ([#9716](https://github.com/python-poetry/poetry/pull/9716)).
- Improve the explanation of `exclude` and `include` ([#9734](https://github.com/python-poetry/poetry/pull/9734)).
- Add information about compatible release requirements, i.e. `~=` ([#9783](https://github.com/python-poetry/poetry/pull/9783)).
- Add documentation for using a build script to build extension modules ([#9864](https://github.com/python-poetry/poetry/pull/9864)).
### poetry-core ([`2.0.0`](https://github.com/python-poetry/poetry-core/releases/tag/2.0.0))
- Add support for non PEP440 compliant version in the `platform_release` marker ([#722](https://github.com/python-poetry/poetry-core/pull/722)).
- Add support for string comparisons with `in` / `not in` in generic constraints ([#722](https://github.com/python-poetry/poetry-core/pull/722)).
- Add support for script files that are generated by a build script ([#710](https://github.com/python-poetry/poetry-core/pull/710)).
- Add support for `SOURCE_DATE_EPOCH` when building packages ([#766](https://github.com/python-poetry/poetry-core/pull/766),
[#781](https://github.com/python-poetry/poetry-core/pull/781)).
- Create `METADATA` files with version 2.3 instead of 2.2 ([#707](https://github.com/python-poetry/poetry-core/pull/707)).
- Remove support for `x` in version constraints ([#770](https://github.com/python-poetry/poetry-core/pull/770)).
- Remove support for scripts with extras ([#708](https://github.com/python-poetry/poetry-core/pull/708)).
- Remove deprecated features and interfaces ([#702](https://github.com/python-poetry/poetry-core/pull/702),
[#769](https://github.com/python-poetry/poetry-core/pull/769)).
- Deprecate `tool.poetry.dev-dependencies` in favor of `tool.poetry.group.dev.dependencies` ([#754](https://github.com/python-poetry/poetry-core/pull/754)).
- Fix an issue where the `platlib` directory of the wrong Python was used ([#726](https://github.com/python-poetry/poetry-core/pull/726)).
- Fix an issue where building a wheel in a nested output directory results in an error ([#762](https://github.com/python-poetry/poetry-core/pull/762)).
- Fix an issue where `+` was not allowed in git URL paths ([#765](https://github.com/python-poetry/poetry-core/pull/765)).
- Fix an issue where the temporary directory was not cleaned up on error ([#775](https://github.com/python-poetry/poetry-core/pull/775)).
- Fix an issue where the regular expression for author names was too restrictive ([#517](https://github.com/python-poetry/poetry-core/pull/517)).
- Fix an issue where basic auth http(s) credentials could not be parsed ([#791](https://github.com/python-poetry/poetry-core/pull/791)).
## [1.8.5] - 2024-12-06
### Changed
- Require `pkginfo>=1.12` to fix an issue with an unknown metadata version 2.4 ([#9888](https://github.com/python-poetry/poetry/pull/9888)).
- Do not fail if the unknown metadata version is only a minor version update ([#9888](https://github.com/python-poetry/poetry/pull/9888)).
## [1.8.4] - 2024-10-14
### Added
- **Add official support for Python 3.13** ([#9523](https://github.com/python-poetry/poetry/pull/9523)).
### Changed
- Require `virtualenv>=20.26.6` to mitigate potential command injection when running `poetry shell` in untrusted projects ([#9757](https://github.com/python-poetry/poetry/pull/9757)).
### poetry-core ([`1.9.1`](https://github.com/python-poetry/poetry-core/releases/tag/1.9.1))
- Add `3.13` to the list of available Python versions ([#747](https://github.com/python-poetry/poetry-core/pull/747)).
## [1.8.3] - 2024-05-08
### Added
- Add support for untagged CPython builds with versions ending with a `+` ([#9207](https://github.com/python-poetry/poetry/pull/9207)).
### Changed
- Require `pkginfo>=1.10` to ensure support for packages with metadata version 2.3 ([#9130](https://github.com/python-poetry/poetry/pull/9130)).
- Improve locking on FIPS systems ([#9152](https://github.com/python-poetry/poetry/pull/9152)).
### Fixed
- Fix an issue where unrecognized package metadata versions silently resulted in empty dependencies ([#9203](https://github.com/python-poetry/poetry/pull/9203),
[#9226](https://github.com/python-poetry/poetry/pull/9226)).
- Fix an issue where trailing slashes in git URLs where not handled correctly ([#9205](https://github.com/python-poetry/poetry/pull/9205)).
- Fix an issue where `poetry self` commands printed a warning that the current project cannot be installed ([#9302](https://github.com/python-poetry/poetry/pull/9302)).
- Fix an issue where `poetry install` sporadically failed with a `KeyError` due to a race condition ([#9335](https://github.com/python-poetry/poetry/pull/9335)).
### Docs
- Fix incorrect information about `poetry shell` ([#9060](https://github.com/python-poetry/poetry/pull/9060)).
- Add a git subdirectory example to `poetry add` ([#9080](https://github.com/python-poetry/poetry/pull/9080)).
- Mention interactive credential configuration ([#9074](https://github.com/python-poetry/poetry/pull/9074)).
- Add notes for optional advanced installation steps ([#9098](https://github.com/python-poetry/poetry/pull/9098)).
- Add reference to configuration credentials in documentation of poetry `publish` ([#9110](https://github.com/python-poetry/poetry/pull/9110)).
- Improve documentation for configuring credentials via environment variables ([#9121](https://github.com/python-poetry/poetry/pull/9121)).
- Remove misleading wording around virtual environments ([#9213](https://github.com/python-poetry/poetry/pull/9213)).
- Remove outdated advice regarding seeding keyring backends ([#9164](https://github.com/python-poetry/poetry/pull/9164)).
- Add a `pyproject.toml` example for a dependency with multiple extras ([#9138](https://github.com/python-poetry/poetry/pull/9138)).
- Clarify help of `poetry add` ([#9230](https://github.com/python-poetry/poetry/pull/9230)).
- Add a note how to configure credentials for TestPyPI for `poetry publish` ([#9255](https://github.com/python-poetry/poetry/pull/9255)).
- Fix information about the `--readme` option in `poetry new` ([#9260](https://github.com/python-poetry/poetry/pull/9260)).
- Clarify what is special about the Python constraint in `dependencies` ([#9256](https://github.com/python-poetry/poetry/pull/9256)).
- Update how to uninstall plugins via `pipx` ([#9320](https://github.com/python-poetry/poetry/pull/9320)).
## [1.8.2] - 2024-03-02
### Fixed
- Harden `lazy-wheel` error handling if the index server is behaving badly in an unexpected way ([#9051](https://github.com/python-poetry/poetry/pull/9051)).
- Improve `lazy-wheel` error handling if the index server does not handle HTTP range requests correctly ([#9082](https://github.com/python-poetry/poetry/pull/9082)).
- Improve `lazy-wheel` error handling if the index server pretends to support HTTP range requests but does not respect them ([#9084](https://github.com/python-poetry/poetry/pull/9084)).
- Improve `lazy-wheel` to allow redirects for HEAD requests ([#9087](https://github.com/python-poetry/poetry/pull/9087)).
- Improve debug logging for `lazy-wheel` errors ([#9059](https://github.com/python-poetry/poetry/pull/9059)).
- Fix an issue where the hash of a metadata file could not be calculated correctly due to an encoding issue ([#9049](https://github.com/python-poetry/poetry/pull/9049)).
- Fix an issue where `poetry add` failed in non-package mode if no project name was set ([#9046](https://github.com/python-poetry/poetry/pull/9046)).
- Fix an issue where a hint to non-package mode was not compliant with the final name of the setting ([#9073](https://github.com/python-poetry/poetry/pull/9073)).
## [1.8.1] - 2024-02-26
### Fixed
- Update the minimum required version of `packaging` ([#9031](https://github.com/python-poetry/poetry/pull/9031)).
- Handle unexpected responses from servers that do not support HTTP range requests with negative offsets more robust ([#9030](https://github.com/python-poetry/poetry/pull/9030)).
### Docs
- Rename `master` branch to `main` ([#9022](https://github.com/python-poetry/poetry/pull/9022)).
## [1.8.0] - 2024-02-25
### Added
- **Add a `non-package` mode for use cases where Poetry is only used for dependency management** ([#8650](https://github.com/python-poetry/poetry/pull/8650)).
- **Add support for PEP 658 to fetch metadata without having to download wheels** ([#5509](https://github.com/python-poetry/poetry/pull/5509)).
- **Add a `lazy-wheel` config option (default: `true`) to reduce wheel downloads during dependency resolution** ([#8815](https://github.com/python-poetry/poetry/pull/8815),
[#8941](https://github.com/python-poetry/poetry/pull/8941)).
- Improve performance of dependency resolution by using shallow copies instead of deep copies ([#8671](https://github.com/python-poetry/poetry/pull/8671)).
- `poetry check` validates that no unknown sources are referenced in dependencies ([#8709](https://github.com/python-poetry/poetry/pull/8709)).
- Add archive validation during installation for further hash algorithms ([#8851](https://github.com/python-poetry/poetry/pull/8851)).
- Add a `to` key in `tool.poetry.packages` to allow custom subpackage names ([#8791](https://github.com/python-poetry/poetry/pull/8791)).
- Add a config option to disable `keyring` ([#8910](https://github.com/python-poetry/poetry/pull/8910)).
- Add a `--sync` option to `poetry update` ([#8931](https://github.com/python-poetry/poetry/pull/8931)).
- Add an `--output` option to `poetry build` ([#8828](https://github.com/python-poetry/poetry/pull/8828)).
- Add a `--dist-dir` option to `poetry publish` ([#8828](https://github.com/python-poetry/poetry/pull/8828)).
### Changed
- **The implicit PyPI source is disabled if at least one primary source is configured** ([#8771](https://github.com/python-poetry/poetry/pull/8771)).
- **Deprecate source priority `default`** ([#8771](https://github.com/python-poetry/poetry/pull/8771)).
- **Upgrade the warning about an inconsistent lockfile to an error** ([#8737](https://github.com/python-poetry/poetry/pull/8737)).
- Deprecate setting `installer.modern-installation` to `false` ([#8988](https://github.com/python-poetry/poetry/pull/8988)).
- Drop support for `pip<19` ([#8894](https://github.com/python-poetry/poetry/pull/8894)).
- Require `requests-toolbelt>=1` ([#8680](https://github.com/python-poetry/poetry/pull/8680)).
- Allow `platformdirs` 4.x ([#8668](https://github.com/python-poetry/poetry/pull/8668)).
- Allow and require `xattr` 1.x on macOS ([#8801](https://github.com/python-poetry/poetry/pull/8801)).
- Improve venv shell activation in `fish` ([#8804](https://github.com/python-poetry/poetry/pull/8804)).
- Rename `system` to `base` in output of `poetry env info` ([#8832](https://github.com/python-poetry/poetry/pull/8832)).
- Use pretty name in output of `poetry version` ([#8849](https://github.com/python-poetry/poetry/pull/8849)).
- Improve error handling for invalid entries in `tool.poetry.scripts` ([#8898](https://github.com/python-poetry/poetry/pull/8898)).
- Improve verbose output for dependencies with extras during dependency resolution ([#8834](https://github.com/python-poetry/poetry/pull/8834)).
- Improve message about an outdated lockfile ([#8962](https://github.com/python-poetry/poetry/pull/8962)).
### Fixed
- Fix an issue where `poetry shell` failed when Python has been installed with MSYS2 ([#8644](https://github.com/python-poetry/poetry/pull/8644)).
- Fix an issue where Poetry commands failed in a terminal with a non-UTF-8 encoding ([#8608](https://github.com/python-poetry/poetry/pull/8608)).
- Fix an issue where a missing project name caused an incomprehensible error message ([#8691](https://github.com/python-poetry/poetry/pull/8691)).
- Fix an issue where Poetry failed to install an `sdist` path dependency ([#8682](https://github.com/python-poetry/poetry/pull/8682)).
- Fix an issue where `poetry install` failed because an unused extra was not available ([#8548](https://github.com/python-poetry/poetry/pull/8548)).
- Fix an issue where `poetry install --sync` did not remove an unrequested extra ([#8621](https://github.com/python-poetry/poetry/pull/8621)).
- Fix an issue where `poetry init` did not allow specific characters in the author field ([#8779](https://github.com/python-poetry/poetry/pull/8779)).
- Fix an issue where Poetry could not download `sdists` from misconfigured servers ([#8701](https://github.com/python-poetry/poetry/pull/8701)).
- Fix an issue where metadata of sdists that call CLI tools of their build requirements could not be determined ([#8827](https://github.com/python-poetry/poetry/pull/8827)).
- Fix an issue where Poetry failed to use the currently activated environment ([#8831](https://github.com/python-poetry/poetry/pull/8831)).
- Fix an issue where `poetry shell` failed in `zsh` if a space was in the venv path ([#7245](https://github.com/python-poetry/poetry/pull/7245)).
- Fix an issue where scripts with extras could not be installed ([#8900](https://github.com/python-poetry/poetry/pull/8900)).
- Fix an issue where explicit sources where not propagated correctly ([#8835](https://github.com/python-poetry/poetry/pull/8835)).
- Fix an issue where debug prints where swallowed when using a build script ([#8760](https://github.com/python-poetry/poetry/pull/8760)).
- Fix an issue where explicit sources of locked dependencies where not propagated correctly ([#8948](https://github.com/python-poetry/poetry/pull/8948)).
- Fix an issue where Poetry's own environment was falsely identified as system environment ([#8970](https://github.com/python-poetry/poetry/pull/8970)).
- Fix an issue where dependencies from a `setup.py` were ignored silently ([#9000](https://github.com/python-poetry/poetry/pull/9000)).
- Fix an issue where environment variables for `virtualenv.options` were ignored ([#9015](https://github.com/python-poetry/poetry/pull/9015)).
- Fix an issue where `virtualenvs.options.no-pip` and `virtualenvs.options.no-setuptools` were not normalized ([#9015](https://github.com/python-poetry/poetry/pull/9015)).
### Docs
- Replace deprecated `--no-dev` with `--without dev` in the FAQ ([#8659](https://github.com/python-poetry/poetry/pull/8659)).
- Recommend `poetry-check` instead of the deprecated `poetry-lock` pre-commit hook ([#8675](https://github.com/python-poetry/poetry/pull/8675)).
- Clarify the names of the environment variables to provide credentials for repositories ([#8782](https://github.com/python-poetry/poetry/pull/8782)).
- Add note how to install several version of Poetry in parallel ([#8814](https://github.com/python-poetry/poetry/pull/8814)).
- Improve description of `poetry show --why` ([#8817](https://github.com/python-poetry/poetry/pull/8817)).
- Improve documentation of `poetry update` ([#8706](https://github.com/python-poetry/poetry/pull/8706)).
- Add a warning about passing variables that may start with a hyphen via command line ([#8850](https://github.com/python-poetry/poetry/pull/8850)).
- Mention that the virtual environment in which Poetry itself is installed should not be activated ([#8833](https://github.com/python-poetry/poetry/pull/8833)).
- Add note about `poetry run` and externally managed environments ([#8748](https://github.com/python-poetry/poetry/pull/8748)).
- Update FAQ entry about `tox` for `tox` 4.x ([#8658](https://github.com/python-poetry/poetry/pull/8658)).
- Fix documentation for default `format` option for `include` and `exclude` value ([#8852](https://github.com/python-poetry/poetry/pull/8852)).
- Add note about `tox` and configured credentials ([#8888](https://github.com/python-poetry/poetry/pull/8888)).
- Add note and link how to install `pipx` ([#8878](https://github.com/python-poetry/poetry/pull/8878)).
- Fix examples for `poetry add` with git dependencies over ssh ([#8911](https://github.com/python-poetry/poetry/pull/8911)).
- Remove reference to deprecated scripts extras feature ([#8903](https://github.com/python-poetry/poetry/pull/8903)).
- Change examples to prefer `--only main` instead of `--without dev` ([#8921](https://github.com/python-poetry/poetry/pull/8921)).
- Mention that the `develop` attribute is a Poetry-specific feature and not propagated to other tools ([#8971](https://github.com/python-poetry/poetry/pull/8971)).
- Fix examples for adding supplemental and secondary sources ([#8953](https://github.com/python-poetry/poetry/pull/8953)).
- Add PyTorch example for explicit sources ([#9006](https://github.com/python-poetry/poetry/pull/9006)).
### poetry-core ([`1.9.0`](https://github.com/python-poetry/poetry-core/releases/tag/1.9.0))
- **Deprecate scripts that depend on extras** ([#690](https://github.com/python-poetry/poetry-core/pull/690)).
- Add support for path dependencies that do not define a build system ([#675](https://github.com/python-poetry/poetry-core/pull/675)).
- Update list of supported licenses ([#659](https://github.com/python-poetry/poetry-core/pull/659),
[#669](https://github.com/python-poetry/poetry-core/pull/669),
[#678](https://github.com/python-poetry/poetry-core/pull/678),
[#694](https://github.com/python-poetry/poetry-core/pull/694)).
- Rework list of files included in build artifacts ([#666](https://github.com/python-poetry/poetry-core/pull/666)).
- Fix an issue where insignificant errors were printed if the working directory is not inside a git repository ([#684](https://github.com/python-poetry/poetry-core/pull/684)).
- Fix an issue where the project's directory was not recognized as git repository on Windows due to an encoding issue ([#685](https://github.com/python-poetry/poetry-core/pull/685)).
## [1.7.1] - 2023-11-16
### Fixed
- Fix an issue where sdists that call CLI tools of their build requirements could not be installed ([#8630](https://github.com/python-poetry/poetry/pull/8630)).
- Fix an issue where sdists with symlinks could not be installed due to a broken tarfile datafilter ([#8649](https://github.com/python-poetry/poetry/pull/8649)).
- Fix an issue where `poetry init` failed when trying to add dependencies ([#8655](https://github.com/python-poetry/poetry/pull/8655)).
- Fix an issue where `poetry install` failed if `virtualenvs.create` was set to `false` ([#8672](https://github.com/python-poetry/poetry/pull/8672)).
## [1.7.0] - 2023-11-03
### Added
- **Add official support for Python 3.12** ([#7803](https://github.com/python-poetry/poetry/pull/7803), [#8544](https://github.com/python-poetry/poetry/pull/8544)).
- **Print a future warning that `poetry-plugin-export` will not be installed by default anymore** ([#8562](https://github.com/python-poetry/poetry/pull/8562)).
- Add `poetry-install` pre-commit hook ([#8327](https://github.com/python-poetry/poetry/pull/8327)).
- Add `--next-phase` option to `poetry version` ([#8089](https://github.com/python-poetry/poetry/pull/8089)).
- Print a warning when overwriting files from another package at installation ([#8386](https://github.com/python-poetry/poetry/pull/8386)).
- Print a warning if the current project cannot be installed ([#8369](https://github.com/python-poetry/poetry/pull/8369)).
- Report more details on build backend exceptions ([#8464](https://github.com/python-poetry/poetry/pull/8464)).
### Changed
- Set Poetry as `user-agent` for all HTTP requests ([#8394](https://github.com/python-poetry/poetry/pull/8394)).
- Do not install `setuptools` per default in Python 3.12 ([#7803](https://github.com/python-poetry/poetry/pull/7803)).
- Do not install `wheel` per default ([#7803](https://github.com/python-poetry/poetry/pull/7803)).
- Remove `setuptools` and `wheel` when running `poetry install --sync` if they are not required by the project ([#8600](https://github.com/python-poetry/poetry/pull/8600)).
- Improve error message about PEP-517 support ([#8463](https://github.com/python-poetry/poetry/pull/8463)).
- Improve `keyring` handling ([#8227](https://github.com/python-poetry/poetry/pull/8227)).
- Read the `description` field when extracting metadata from `setup.py` files ([#8545](https://github.com/python-poetry/poetry/pull/8545)).
### Fixed
- **Fix an issue where dependencies of inactive extras were locked and installed** ([#8399](https://github.com/python-poetry/poetry/pull/8399)).
- **Fix an issue where build requirements were not installed due to a race condition in the artifact cache** ([#8517](https://github.com/python-poetry/poetry/pull/8517)).
- Fix an issue where packages included in the system site packages were installed even though `virtualenvs.options.system-site-packages` was set ([#8359](https://github.com/python-poetry/poetry/pull/8359)).
- Fix an issue where git dependencies' submodules with relative URLs were handled incorrectly ([#8020](https://github.com/python-poetry/poetry/pull/8020)).
- Fix an issue where a failed installation of build dependencies was not noticed directly ([#8479](https://github.com/python-poetry/poetry/pull/8479)).
- Fix an issue where `poetry shell` did not work completely with `nushell` ([#8478](https://github.com/python-poetry/poetry/pull/8478)).
- Fix an issue where a confusing error messages was displayed when running `poetry config pypi-token.pypi` without a value ([#8502](https://github.com/python-poetry/poetry/pull/8502)).
- Fix an issue where a cryptic error message is printed if there is no metadata entry in the lockfile ([#8523](https://github.com/python-poetry/poetry/pull/8523)).
- Fix an issue with the encoding with special characters in the virtualenv's path ([#8565](https://github.com/python-poetry/poetry/pull/8565)).
- Fix an issue where the connection pool size was not adjusted to the number of workers ([#8559](https://github.com/python-poetry/poetry/pull/8559)).
### Docs
- Improve the wording regarding a project's supported Python range ([#8423](https://github.com/python-poetry/poetry/pull/8423)).
- Make `pipx` the preferred (first mentioned) installation method ([#8090](https://github.com/python-poetry/poetry/pull/8090)).
- Add a warning about `poetry self` on Windows ([#8090](https://github.com/python-poetry/poetry/pull/8090)).
- Fix example for `poetry add` with a git dependency ([#8438](https://github.com/python-poetry/poetry/pull/8438)).
- Add information about auto-included files in wheels and sdist ([#8555](https://github.com/python-poetry/poetry/pull/8555)).
- Fix documentation of the `POETRY_REPOSITORIES_` variables docs ([#8492](https://github.com/python-poetry/poetry/pull/8492)).
- Add `CITATION.cff` file ([#8510](https://github.com/python-poetry/poetry/pull/8510)).
### poetry-core ([`1.8.1`](https://github.com/python-poetry/poetry-core/releases/tag/1.8.1))
- Add support for creating packages dynamically in the build script ([#629](https://github.com/python-poetry/poetry-core/pull/629)).
- Improve marker logic for `extra` markers ([#636](https://github.com/python-poetry/poetry-core/pull/636)).
- Update list of supported licenses ([#635](https://github.com/python-poetry/poetry-core/pull/635), [#646](https://github.com/python-poetry/poetry-core/pull/646)).
- Fix an issue where projects with extension modules were not installed in editable mode ([#633](https://github.com/python-poetry/poetry-core/pull/633)).
- Fix an issue where the wrong or no `lib` folder was added to the wheel ([#634](https://github.com/python-poetry/poetry-core/pull/634)).
### poetry-plugin-export ([`^1.6.0`](https://github.com/python-poetry/poetry-plugin-export/releases/tag/1.6.0))
- Add an `--all-extras` option ([#241](https://github.com/python-poetry/poetry-plugin-export/pull/241)).
- Fix an issue where git dependencies are exported with the branch name instead of the resolved commit hash ([#213](https://github.com/python-poetry/poetry-plugin-export/pull/213)).
## [1.6.1] - 2023-08-21
### Fixed
- Update the minimum required version of `requests` ([#8336](https://github.com/python-poetry/poetry/pull/8336)).
## [1.6.0] - 2023-08-20
### Added
- **Add support for repositories that do not provide a supported hash algorithm** ([#8118](https://github.com/python-poetry/poetry/pull/8118)).
- **Add full support for duplicate dependencies with overlapping markers** ([#7257](https://github.com/python-poetry/poetry/pull/7257)).
- **Improve performance of `poetry lock` for certain edge cases** ([#8256](https://github.com/python-poetry/poetry/pull/8256)).
- Improve performance of `poetry install` ([#8031](https://github.com/python-poetry/poetry/pull/8031)).
- `poetry check` validates that specified `readme` files do exist ([#7444](https://github.com/python-poetry/poetry/pull/7444)).
- Add a downgrading note when updating to an older version ([#8176](https://github.com/python-poetry/poetry/pull/8176)).
- Add support for `vox` in the `xonsh` shell ([#8203](https://github.com/python-poetry/poetry/pull/8203)).
- Add support for `pre-commit` hooks for projects where the pyproject.toml file is located in a subfolder ([#8204](https://github.com/python-poetry/poetry/pull/8204)).
- Add support for the `git+http://` scheme ([#6619](https://github.com/python-poetry/poetry/pull/6619)).
### Changed
- **Drop support for Python 3.7** ([#7674](https://github.com/python-poetry/poetry/pull/7674)).
- Move `poetry lock --check` to `poetry check --lock` and deprecate the former ([#8015](https://github.com/python-poetry/poetry/pull/8015)).
- Change future warning that PyPI will only be disabled automatically if there are no primary sources ([#8151](https://github.com/python-poetry/poetry/pull/8151)).
### Fixed
- Fix an issue where `build-system.requires` were not respected for projects with build scripts ([#7975](https://github.com/python-poetry/poetry/pull/7975)).
- Fix an issue where the encoding was not handled correctly when calling a subprocess ([#8060](https://github.com/python-poetry/poetry/pull/8060)).
- Fix an issue where `poetry show --top-level` did not show top level dependencies with extras ([#8076](https://github.com/python-poetry/poetry/pull/8076)).
- Fix an issue where `poetry init` handled projects with `src` layout incorrectly ([#8218](https://github.com/python-poetry/poetry/pull/8218)).
- Fix an issue where Poetry wrote `.pth` files with the wrong encoding ([#8041](https://github.com/python-poetry/poetry/pull/8041)).
- Fix an issue where `poetry install` did not respect the source if the same version of a package has been locked from different sources ([#8304](https://github.com/python-poetry/poetry/pull/8304)).
### Docs
- Document **official Poetry badge** ([#8066](https://github.com/python-poetry/poetry/pull/8066)).
- Update configuration folder path for macOS ([#8062](https://github.com/python-poetry/poetry/pull/8062)).
- Add a warning about pip ignoring lock files ([#8117](https://github.com/python-poetry/poetry/pull/8117)).
- Clarify the use of the `virtualenvs.in-project` setting. ([#8126](https://github.com/python-poetry/poetry/pull/8126)).
- Change `pre-commit` YAML style to be consistent with pre-commit's own examples ([#8146](https://github.com/python-poetry/poetry/pull/8146)).
- Fix command for listing installed plugins ([#8200](https://github.com/python-poetry/poetry/pull/8200)).
- Mention the `nox-poetry` package ([#8173](https://github.com/python-poetry/poetry/pull/8173)).
- Add an example with a PyPI source in the pyproject.toml file ([#8171](https://github.com/python-poetry/poetry/pull/8171)).
- Use `reference` instead of deprecated `callable` in the scripts example ([#8211](https://github.com/python-poetry/poetry/pull/8211)).
### poetry-core ([`1.7.0`](https://github.com/python-poetry/poetry-core/releases/tag/1.7.0))
- Improve performance of marker handling ([#609](https://github.com/python-poetry/poetry-core/pull/609)).
- Allow `|` as a value separator in markers with the operators `in` and `not in` ([#608](https://github.com/python-poetry/poetry-core/pull/608)).
- Put pretty name (instead of normalized name) in metadata ([#620](https://github.com/python-poetry/poetry-core/pull/620)).
- Update list of supported licenses ([#623](https://github.com/python-poetry/poetry-core/pull/623)).
- Fix an issue where PEP 508 dependency specifications with names starting with a digit could not be parsed ([#607](https://github.com/python-poetry/poetry-core/pull/607)).
- Fix an issue where Poetry considered an unrelated `.gitignore` file resulting in an empty wheel ([#611](https://github.com/python-poetry/poetry-core/pull/611)).
### poetry-plugin-export ([`^1.5.0`](https://github.com/python-poetry/poetry-plugin-export/releases/tag/1.5.0))
- Fix an issue where markers for dependencies required by an extra were not generated correctly ([#209](https://github.com/python-poetry/poetry-plugin-export/pull/209)).
## [1.5.1] - 2023-05-29
### Added
- Improve dependency resolution performance in cases with a lot of backtracking ([#7950](https://github.com/python-poetry/poetry/pull/7950)).
### Changed
- Disable wheel content validation during installation ([#7987](https://github.com/python-poetry/poetry/pull/7987)).
### Fixed
- Fix an issue where partially downloaded wheels were cached ([#7968](https://github.com/python-poetry/poetry/pull/7968)).
- Fix an issue where `poetry run` did no longer execute relative-path scripts ([#7963](https://github.com/python-poetry/poetry/pull/7963)).
- Fix an issue where dependencies were not installed in `in-project` environments ([#7977](https://github.com/python-poetry/poetry/pull/7977)).
- Fix an issue where no solution was found for a transitive dependency on a pre-release of a package ([#7978](https://github.com/python-poetry/poetry/pull/7978)).
- Fix an issue where cached repository packages were incorrectly parsed, leading to its dependencies being ignored ([#7995](https://github.com/python-poetry/poetry/pull/7995)).
- Fix an issue where an explicit source was ignored so that a direct origin dependency was used instead ([#7973](https://github.com/python-poetry/poetry/pull/7973)).
- Fix an issue where the installation of big wheels consumed a lot of memory ([#7987](https://github.com/python-poetry/poetry/pull/7987)).
### Docs
- Add information about multiple constraints dependencies with direct origin and version dependencies ([#7973](https://github.com/python-poetry/poetry/pull/7973)).
### poetry-core ([`1.6.1`](https://github.com/python-poetry/poetry-core/releases/tag/1.6.1))
- Fix an endless recursion in marker handling ([#593](https://github.com/python-poetry/poetry-core/pull/593)).
- Fix an issue where the wheel tag was not built correctly under certain circumstances ([#591](https://github.com/python-poetry/poetry-core/pull/591)).
### poetry-plugin-export ([`^1.4.0`](https://github.com/python-poetry/poetry-plugin-export/releases/tag/1.4.0))
- Fix an issue where `--extra-index-url` and `--trusted-host` was not generated for sources with priority `explicit` ([#205](https://github.com/python-poetry/poetry-plugin-export/pull/205)).
## [1.5.0] - 2023-05-19
### Added
- **Introduce the new source priorities `explicit` and `supplemental`** ([#7658](https://github.com/python-poetry/poetry/pull/7658),
[#6879](https://github.com/python-poetry/poetry/pull/6879)).
- **Introduce the option to configure the priority of the implicit PyPI source** ([#7801](https://github.com/python-poetry/poetry/pull/7801)).
- Add handling for corrupt cache files ([#7453](https://github.com/python-poetry/poetry/pull/7453)).
- Improve caching of URL and git dependencies ([#7693](https://github.com/python-poetry/poetry/pull/7693),
[#7473](https://github.com/python-poetry/poetry/pull/7473)).
- Add option to skip installing directory dependencies ([#6845](https://github.com/python-poetry/poetry/pull/6845),
[#7923](https://github.com/python-poetry/poetry/pull/7923)).
- Add `--executable` option to `poetry env info` ([#7547](https://github.com/python-poetry/poetry/pull/7547)).
- Add `--top-level` option to `poetry show` ([#7415](https://github.com/python-poetry/poetry/pull/7415)).
- Add `--lock` option to `poetry remove` ([#7917](https://github.com/python-poetry/poetry/pull/7917)).
- Add experimental `POETRY_REQUESTS_TIMEOUT` option ([#7081](https://github.com/python-poetry/poetry/pull/7081)).
- Improve performance of wheel inspection by avoiding unnecessary file copy operations ([#7916](https://github.com/python-poetry/poetry/pull/7916)).
### Changed
- **Remove the old deprecated installer and the corresponding setting `experimental.new-installer`** ([#7356](https://github.com/python-poetry/poetry/pull/7356)).
- **Introduce `priority` key for sources and deprecate flags `default` and `secondary`** ([#7658](https://github.com/python-poetry/poetry/pull/7658)).
- Deprecate `poetry run ` if the entry point was not previously installed via `poetry install` ([#7606](https://github.com/python-poetry/poetry/pull/7606)).
- Only write the lock file if the installation succeeds ([#7498](https://github.com/python-poetry/poetry/pull/7498)).
- Do not write the unused package category into the lock file ([#7637](https://github.com/python-poetry/poetry/pull/7637)).
### Fixed
- Fix an issue where Poetry's internal pyproject.toml continually grows larger with empty lines ([#7705](https://github.com/python-poetry/poetry/pull/7705)).
- Fix an issue where Poetry crashes due to corrupt cache files ([#7453](https://github.com/python-poetry/poetry/pull/7453)).
- Fix an issue where the `Retry-After` in HTTP responses was not respected and retries were handled inconsistently ([#7072](https://github.com/python-poetry/poetry/pull/7072)).
- Fix an issue where Poetry silently ignored invalid groups ([#7529](https://github.com/python-poetry/poetry/pull/7529)).
- Fix an issue where Poetry does not find a compatible Python version if not given explicitly ([#7771](https://github.com/python-poetry/poetry/pull/7771)).
- Fix an issue where the `direct_url.json` of an editable install from a git dependency was invalid ([#7473](https://github.com/python-poetry/poetry/pull/7473)).
- Fix an issue where error messages from build backends were not decoded correctly ([#7781](https://github.com/python-poetry/poetry/pull/7781)).
- Fix an infinite loop when adding certain dependencies ([#7405](https://github.com/python-poetry/poetry/pull/7405)).
- Fix an issue where pre-commit hooks skip pyproject.toml files in subdirectories ([#7239](https://github.com/python-poetry/poetry/pull/7239)).
- Fix an issue where pre-commit hooks do not use the expected Python version ([#6989](https://github.com/python-poetry/poetry/pull/6989)).
- Fix an issue where an unclear error message is printed if the project name is the same as one of its dependencies ([#7757](https://github.com/python-poetry/poetry/pull/7757)).
- Fix an issue where `poetry install` returns a zero exit status even though the build script failed ([#7812](https://github.com/python-poetry/poetry/pull/7812)).
- Fix an issue where an existing `.venv` was not used if `in-project` was not set ([#7792](https://github.com/python-poetry/poetry/pull/7792)).
- Fix an issue where multiple extras passed to `poetry add` were not parsed correctly ([#7836](https://github.com/python-poetry/poetry/pull/7836)).
- Fix an issue where `poetry shell` did not send a newline to `fish` ([#7884](https://github.com/python-poetry/poetry/pull/7884)).
- Fix an issue where `poetry update --lock` printed operations that were not executed ([#7915](https://github.com/python-poetry/poetry/pull/7915)).
- Fix an issue where `poetry add --lock` did perform a full update of all dependencies ([#7920](https://github.com/python-poetry/poetry/pull/7920)).
- Fix an issue where `poetry shell` did not work with `nushell` ([#7919](https://github.com/python-poetry/poetry/pull/7919)).
- Fix an issue where subprocess calls failed on Python 3.7 ([#7932](https://github.com/python-poetry/poetry/pull/7932)).
- Fix an issue where keyring was called even though the password was stored in an environment variable ([#7928](https://github.com/python-poetry/poetry/pull/7928)).
### Docs
- Add information about what to use instead of `--dev` ([#7647](https://github.com/python-poetry/poetry/pull/7647)).
- Promote semantic versioning less aggressively ([#7517](https://github.com/python-poetry/poetry/pull/7517)).
- Explain Poetry's own versioning scheme in the FAQ ([#7517](https://github.com/python-poetry/poetry/pull/7517)).
- Update documentation for configuration with environment variables ([#6711](https://github.com/python-poetry/poetry/pull/6711)).
- Add details how to disable the virtualenv prompt ([#7874](https://github.com/python-poetry/poetry/pull/7874)).
- Improve documentation on whether to commit `poetry.lock` ([#7506](https://github.com/python-poetry/poetry/pull/7506)).
- Improve documentation of `virtualenv.create` ([#7608](https://github.com/python-poetry/poetry/pull/7608)).
### poetry-core ([`1.6.0`](https://github.com/python-poetry/poetry-core/releases/tag/1.6.0))
- Improve error message for invalid markers ([#569](https://github.com/python-poetry/poetry-core/pull/569)).
- Increase robustness when deleting temporary directories on Windows ([#460](https://github.com/python-poetry/poetry-core/pull/460)).
- Replace `tomlkit` with `tomli`, which changes the interface of some _internal_ classes ([#483](https://github.com/python-poetry/poetry-core/pull/483)).
- Deprecate `Package.category` ([#561](https://github.com/python-poetry/poetry-core/pull/561)).
- Fix a performance regression in marker handling ([#568](https://github.com/python-poetry/poetry-core/pull/568)).
- Fix an issue where wildcard version constraints were not handled correctly ([#402](https://github.com/python-poetry/poetry-core/pull/402)).
- Fix an issue where `poetry build` created duplicate Python classifiers if they were specified manually ([#578](https://github.com/python-poetry/poetry-core/pull/578)).
- Fix an issue where local versions where not handled correctly ([#579](https://github.com/python-poetry/poetry-core/pull/579)).
## [1.4.2] - 2023-04-02
### Changed
- When trying to install wheels with invalid `RECORD` files, Poetry does not fail anymore but only prints a warning.
This mitigates an unintended change introduced in Poetry 1.4.1 ([#7694](https://github.com/python-poetry/poetry/pull/7694)).
### Fixed
- Fix an issue where relative git submodule urls were not parsed correctly ([#7017](https://github.com/python-poetry/poetry/pull/7017)).
- Fix an issue where Poetry could freeze when building a project with a build script if it generated enough output to fill the OS pipe buffer ([#7699](https://github.com/python-poetry/poetry/pull/7699)).
## [1.4.1] - 2023-03-19
### Fixed
- Fix an issue where `poetry install` did not respect the requirements for building editable dependencies ([#7579](https://github.com/python-poetry/poetry/pull/7579)).
- Fix an issue where `poetry init` crashed due to bad input when adding packages interactively ([#7569](https://github.com/python-poetry/poetry/pull/7569)).
- Fix an issue where `poetry install` ignored the `subdirectory` argument of git dependencies ([#7580](https://github.com/python-poetry/poetry/pull/7580)).
- Fix an issue where installing packages with `no-binary` could result in a false hash mismatch ([#7594](https://github.com/python-poetry/poetry/pull/7594)).
- Fix an issue where the hash of sdists was neither validated nor written to the `direct_url.json` during installation ([#7594](https://github.com/python-poetry/poetry/pull/7594)).
- Fix an issue where `poetry install --sync` attempted to remove itself ([#7626](https://github.com/python-poetry/poetry/pull/7626)).
- Fix an issue where wheels with non-normalized `dist-info` directory names could not be installed ([#7671](https://github.com/python-poetry/poetry/pull/7671)).
- Fix an issue where `poetry install --compile` compiled with optimization level 1 ([#7666](https://github.com/python-poetry/poetry/pull/7666)).
### Docs
- Clarify the behavior of the `--extras` option ([#7563](https://github.com/python-poetry/poetry/pull/7563)).
- Expand the FAQ on reasons for slow dependency resolution ([#7620](https://github.com/python-poetry/poetry/pull/7620)).
### poetry-core ([`1.5.2`](https://github.com/python-poetry/poetry-core/releases/tag/1.5.2))
- Fix an issue where wheels built on Windows could contain duplicate entries in the RECORD file ([#555](https://github.com/python-poetry/poetry-core/pull/555)).
## [1.4.0] - 2023-02-27
### Added
- **Add a modern installer (`installer.modern-installation`) for faster installation of packages and independence from pip** ([#6205](https://github.com/python-poetry/poetry/pull/6205)).
- Add support for `Private ::` trove classifiers ([#7271](https://github.com/python-poetry/poetry/pull/7271)).
- Add the version of poetry in the `@generated` comment at the beginning of the lock file ([#7339](https://github.com/python-poetry/poetry/pull/7339)).
- Add support for `virtualenvs.prefer-active-python` when running `poetry new` and `poetry init` ([#7100](https://github.com/python-poetry/poetry/pull/7100)).
### Changed
- **Deprecate the old installer, i.e. setting `experimental.new-installer` to `false`** ([#7358](https://github.com/python-poetry/poetry/pull/7358)).
- Remove unused `platform` field from cached package info and bump the cache version ([#7304](https://github.com/python-poetry/poetry/pull/7304)).
- Extra dependencies of the root project are now sorted in the lock file ([#7375](https://github.com/python-poetry/poetry/pull/7375)).
- Remove upper boundary for `importlib-metadata` dependency ([#7434](https://github.com/python-poetry/poetry/pull/7434)).
- Validate path dependencies during use instead of during construction ([#6844](https://github.com/python-poetry/poetry/pull/6844)).
- Remove the deprecated `repository` modules ([#7468](https://github.com/python-poetry/poetry/pull/7468)).
### Fixed
- Fix an issue where an unconditional dependency of an extra was not installed in specific environments ([#7175](https://github.com/python-poetry/poetry/pull/7175)).
- Fix an issue where a pre-release of a dependency was chosen even if a stable release fulfilled the constraint ([#7225](https://github.com/python-poetry/poetry/pull/7225), [#7236](https://github.com/python-poetry/poetry/pull/7236)).
- Fix an issue where HTTP redirects were not handled correctly during publishing ([#7160](https://github.com/python-poetry/poetry/pull/7160)).
- Fix an issue where `poetry check` did not handle the `-C, --directory` option correctly ([#7241](https://github.com/python-poetry/poetry/pull/7241)).
- Fix an issue where the subdirectory information of a git dependency was not written to the lock file ([#7367](https://github.com/python-poetry/poetry/pull/7367)).
- Fix an issue where the wrong Python version was selected when creating an virtual environment ([#7221](https://github.com/python-poetry/poetry/pull/7221)).
- Fix an issue where packages that should be kept were uninstalled when calling `poetry install --sync` ([#7389](https://github.com/python-poetry/poetry/pull/7389)).
- Fix an issue where an incorrect value was set for `sys.argv[0]` when running installed scripts ([#6737](https://github.com/python-poetry/poetry/pull/6737)).
- Fix an issue where hashes in `direct_url.json` files were not written according to the specification ([#7475](https://github.com/python-poetry/poetry/pull/7475)).
- Fix an issue where poetry commands failed due to special characters in the path of the project or virtual environment ([#7471](https://github.com/python-poetry/poetry/pull/7471)).
- Fix an issue where poetry crashed with a `JSONDecodeError` when running a Python script that produced certain warnings ([#6665](https://github.com/python-poetry/poetry/pull/6665)).
### Docs
- Add advice on how to maintain a poetry plugin ([#6977](https://github.com/python-poetry/poetry/pull/6977)).
- Update tox examples to comply with the latest tox release ([#7341](https://github.com/python-poetry/poetry/pull/7341)).
- Mention that the `poetry export` can export `constraints.txt` files ([#7383](https://github.com/python-poetry/poetry/pull/7383)).
- Add clarifications for moving configuration files ([#6864](https://github.com/python-poetry/poetry/pull/6864)).
- Mention the different types of exact version specifications ([#7503](https://github.com/python-poetry/poetry/pull/7503)).
### poetry-core ([`1.5.1`](https://github.com/python-poetry/poetry-core/releases/tag/1.5.1))
- Improve marker handling ([#528](https://github.com/python-poetry/poetry-core/pull/528),
[#534](https://github.com/python-poetry/poetry-core/pull/534),
[#530](https://github.com/python-poetry/poetry-core/pull/530),
[#546](https://github.com/python-poetry/poetry-core/pull/546),
[#547](https://github.com/python-poetry/poetry-core/pull/547)).
- Validate whether dependencies referenced in `extras` are defined in the main dependency group ([#542](https://github.com/python-poetry/poetry-core/pull/542)).
- Poetry no longer generates a `setup.py` file in sdists by default ([#318](https://github.com/python-poetry/poetry-core/pull/318)).
- Fix an issue where trailing newlines were allowed in `tool.poetry.description` ([#505](https://github.com/python-poetry/poetry-core/pull/505)).
- Fix an issue where the name of the data folder in wheels was not normalized ([#532](https://github.com/python-poetry/poetry-core/pull/532)).
- Fix an issue where the order of entries in the RECORD file was not deterministic ([#545](https://github.com/python-poetry/poetry-core/pull/545)).
- Fix an issue where zero padding was not correctly handled in version comparisons ([#540](https://github.com/python-poetry/poetry-core/pull/540)).
- Fix an issue where sdist builds did not support multiple READMEs ([#486](https://github.com/python-poetry/poetry-core/pull/486)).
### poetry-plugin-export ([`^1.3.0`](https://github.com/python-poetry/poetry-plugin-export/releases/tag/1.3.0))
- Fix an issue where the export failed if there was a circular dependency on the root package ([#118](https://github.com/python-poetry/poetry-plugin-export/pull/118)).
## [1.3.2] - 2023-01-10
### Fixed
- Fix a performance regression when locking dependencies from PyPI ([#7232](https://github.com/python-poetry/poetry/pull/7232)).
- Fix an issue where passing a relative path via `-C, --directory` fails ([#7266](https://github.com/python-poetry/poetry/pull/7266)).
### Docs
- Update docs to reflect the removal of the deprecated `get-poetry.py` installer from the repository ([#7288](https://github.com/python-poetry/poetry/pull/7288)).
- Add clarifications for `virtualenvs.path` settings ([#7286](https://github.com/python-poetry/poetry/pull/7286)).
## [1.3.1] - 2022-12-12
### Fixed
- Fix an issue where an explicit dependency on `lockfile` was missing, resulting in a broken Poetry in rare circumstances ([7169](https://github.com/python-poetry/poetry/pull/7169)).
## [1.3.0] - 2022-12-09
### Added
- Mark the lock file with an `@generated` comment as used by common tooling ([#2773](https://github.com/python-poetry/poetry/pull/2773)).
- `poetry check` validates trove classifiers and warns for deprecations ([#2881](https://github.com/python-poetry/poetry/pull/2881)).
- Introduce a top level `-C, --directory` option to set the working path ([#6810](https://github.com/python-poetry/poetry/pull/6810)).
### Changed
- **New lock file format (version 2.0)** ([#6393](https://github.com/python-poetry/poetry/pull/6393)).
- Path dependency metadata is unconditionally re-locked ([#6843](https://github.com/python-poetry/poetry/pull/6843)).
- URL dependency hashes are locked ([#7121](https://github.com/python-poetry/poetry/pull/7121)).
- `poetry update` and `poetry lock` should now resolve dependencies more similarly ([#6477](https://github.com/python-poetry/poetry/pull/6477)).
- `poetry publish` will report more useful errors when a file does not exist ([#4417](https://github.com/python-poetry/poetry/pull/4417)).
- `poetry add` will check for duplicate entries using canonical names ([#6832](https://github.com/python-poetry/poetry/pull/6832)).
- Wheels are preferred to source distributions when gathering metadata ([#6547](https://github.com/python-poetry/poetry/pull/6547)).
- Git dependencies of extras are only fetched if the extra is requested ([#6615](https://github.com/python-poetry/poetry/pull/6615)).
- Invoke `pip` with `--no-input` to prevent hanging without feedback ([#6724](https://github.com/python-poetry/poetry/pull/6724), [#6966](https://github.com/python-poetry/poetry/pull/6966)).
- Invoke `pip` with `--isolated` to prevent the influence of user configuration ([#6531](https://github.com/python-poetry/poetry/pull/6531)).
- Interrogate environments with Python in isolated (`-I`) mode ([#6628](https://github.com/python-poetry/poetry/pull/6628)).
- Raise an informative error when multiple version constraints overlap and are incompatible ([#7098](https://github.com/python-poetry/poetry/pull/7098)).
### Fixed
- **Fix an issue where concurrent instances of Poetry would corrupt the artifact cache** ([#6186](https://github.com/python-poetry/poetry/pull/6186)).
- **Fix an issue where Poetry can hang after being interrupted due to stale locking in cache** ([#6471](https://github.com/python-poetry/poetry/pull/6471)).
- Fix an issue where the output of commands executed with `--dry-run` contained duplicate entries ([#4660](https://github.com/python-poetry/poetry/pull/4660)).
- Fix an issue where `requests`'s pool size did not match the number of installer workers ([#6805](https://github.com/python-poetry/poetry/pull/6805)).
- Fix an issue where `poetry show --outdated` failed with a runtime error related to direct origin dependencies ([#6016](https://github.com/python-poetry/poetry/pull/6016)).
- Fix an issue where only the last command of an `ApplicationPlugin` is registered ([#6304](https://github.com/python-poetry/poetry/pull/6304)).
- Fix an issue where git dependencies were fetched unnecessarily when running `poetry lock --no-update` ([#6131](https://github.com/python-poetry/poetry/pull/6131)).
- Fix an issue where stdout was polluted with messages that should go to stderr ([#6429](https://github.com/python-poetry/poetry/pull/6429)).
- Fix an issue with `poetry shell` activation and zsh ([#5795](https://github.com/python-poetry/poetry/pull/5795)).
- Fix an issue where a url dependencies were shown as outdated ([#6396](https://github.com/python-poetry/poetry/pull/6396)).
- Fix an issue where the `source` field of a dependency with extras was ignored ([#6472](https://github.com/python-poetry/poetry/pull/6472)).
- Fix an issue where a package from the wrong source was installed for a multiple-constraints dependency with different sources ([#6747](https://github.com/python-poetry/poetry/pull/6747)).
- Fix an issue where dependencies from different sources where merged during dependency resolution ([#6679](https://github.com/python-poetry/poetry/pull/6679)).
- Fix an issue where `experimental.system-git-client` could not be used via environment variable ([#6783](https://github.com/python-poetry/poetry/pull/6783)).
- Fix an issue where Poetry fails with an `AssertionError` due to `distribution.files` being `None` ([#6788](https://github.com/python-poetry/poetry/pull/6788)).
- Fix an issue where `poetry env info` did not respect `virtualenvs.prefer-active-python` ([#6986](https://github.com/python-poetry/poetry/pull/6986)).
- Fix an issue where `poetry env list` does not list the in-project environment ([#6979](https://github.com/python-poetry/poetry/pull/6979)).
- Fix an issue where `poetry env remove` removed the wrong environment ([#6195](https://github.com/python-poetry/poetry/pull/6195)).
- Fix an issue where the return code of a script was not relayed as exit code ([#6824](https://github.com/python-poetry/poetry/pull/6824)).
- Fix an issue where the solver could silently swallow `ValueError` ([#6790](https://github.com/python-poetry/poetry/pull/6790)).
### Docs
- Improve documentation of package sources ([#5605](https://github.com/python-poetry/poetry/pull/5605)).
- Correct the default cache path on Windows ([#7012](https://github.com/python-poetry/poetry/pull/7012)).
### poetry-core ([`1.4.0`](https://github.com/python-poetry/poetry-core/releases/tag/1.4.0))
- The PEP 517 `metadata_directory` is now respected as an input to the `build_wheel` hook ([#487](https://github.com/python-poetry/poetry-core/pull/487)).
- `ParseConstraintError` is now raised on version and constraint parsing errors, and includes information on the package that caused the error ([#514](https://github.com/python-poetry/poetry-core/pull/514)).
- Fix an issue where invalid PEP 508 requirements were generated due to a missing space before semicolons ([#510](https://github.com/python-poetry/poetry-core/pull/510)).
- Fix an issue where relative paths were encoded into package requirements, instead of a file:// URL as required by PEP 508 ([#512](https://github.com/python-poetry/poetry-core/pull/512)).
### poetry-plugin-export ([`^1.2.0`](https://github.com/python-poetry/poetry-plugin-export/releases/tag/1.2.0))
- Ensure compatibility with Poetry 1.3.0. No functional changes.
### cleo ([`^2.0.0`](https://github.com/python-poetry/poetry-core/releases/tag/2.0.0))
- Fix an issue where shell completions had syntax errors ([#247](https://github.com/python-poetry/cleo/pull/247)).
- Fix an issue where not reading all the output of a command resulted in a "Broken pipe" error ([#165](https://github.com/python-poetry/cleo/pull/165)).
- Fix an issue where errors were not shown in non-verbose mode ([#166](https://github.com/python-poetry/cleo/pull/166)).
## [1.2.2] - 2022-10-10
### Added
- Add forward compatibility for lock file format 2.0, which will be used by Poetry 1.3 ([#6608](https://github.com/python-poetry/poetry/pull/6608)).
### Changed
- Allow `poetry lock` to re-generate the lock file when invalid or incompatible ([#6753](https://github.com/python-poetry/poetry/pull/6753)).
### Fixed
- Fix an issue where the deprecated JSON API was used to query PyPI for available versions of a package ([#6081](https://github.com/python-poetry/poetry/pull/6081)).
- Fix an issue where versions were escaped wrongly when building the wheel name ([#6476](https://github.com/python-poetry/poetry/pull/6476)).
- Fix an issue where the installation of dependencies failed if pip is a dependency and is updated in parallel to other dependencies ([#6582](https://github.com/python-poetry/poetry/pull/6582)).
- Fix an issue where the names of extras were not normalized according to PEP 685 ([#6541](https://github.com/python-poetry/poetry/pull/6541)).
- Fix an issue where sdist names were not normalized ([#6621](https://github.com/python-poetry/poetry/pull/6621)).
- Fix an issue where invalid constraints, which are ignored, were only reported in a debug message instead of a warning ([#6730](https://github.com/python-poetry/poetry/pull/6730)).
- Fix an issue where `poetry shell` was broken in git bash on Windows ([#6560](https://github.com/python-poetry/poetry/pull/6560)).
### Docs
- Rework the README and contribution docs ([#6552](https://github.com/python-poetry/poetry/pull/6552)).
- Fix for inconsistent docs for multiple-constraint dependencies ([#6604](https://github.com/python-poetry/poetry/pull/6604)).
- Rephrase plugin configuration ([#6557](https://github.com/python-poetry/poetry/pull/6557)).
- Add a note about publishable repositories to `publish` ([#6641](https://github.com/python-poetry/poetry/pull/6641)).
- Fix the path for lazy-loaded bash completion ([#6656](https://github.com/python-poetry/poetry/pull/6656)).
- Fix a reference to the invalid option `--require` ([#6672](https://github.com/python-poetry/poetry/pull/6672)).
- Add a PowerShell one-liner to the basic usage section ([#6683](https://github.com/python-poetry/poetry/pull/6683)).
- Fix the minimum poetry version in the example for plugins ([#6739](https://github.com/python-poetry/poetry/pull/6739)).
### poetry-core ([`1.3.2`](https://github.com/python-poetry/poetry-core/releases/tag/1.3.2))
- Add `3.11` to the list of available Python versions ([#477](https://github.com/python-poetry/poetry-core/pull/477)).
- Fix an issue where caret constraints of pre-releases with a major version of 0 resulted in an empty version range ([#475](https://github.com/python-poetry/poetry-core/pull/475)).
### poetry-plugin-export ([`^1.1.2`](https://github.com/python-poetry/poetry-plugin-export/releases/tag/1.1.2))
- Add support for exporting `constraints.txt` files ([#128](https://github.com/python-poetry/poetry-plugin-export/pull/128)).
- Fix an issue where a relative path passed via `-o` was not interpreted relative to the current working directory ([#130](https://github.com/python-poetry/poetry-plugin-export/pull/130)).
## [1.2.1] - 2022-09-16
### Changed
- Bump `poetry-core` to [`1.2.0`](https://github.com/python-poetry/poetry-core/releases/tag/1.2.0).
- Bump `poetry-plugin-export` to [`^1.0.7`](https://github.com/python-poetry/poetry-plugin-export/releases/tag/1.0.7).
### Fixed
- Fix an issue where `poetry cache clear` did not respect the `-n/--no-interaction` flag ([#6338](https://github.com/python-poetry/poetry/pull/6338)).
- Fix an issue where `poetry lock --no-update` updated dependencies from non-PyPI package sources ([#6335](https://github.com/python-poetry/poetry/pull/6335)).
- Fix a `poetry install` performance regression by falling back to internal pip ([#6062](https://github.com/python-poetry/poetry/pull/6062)).
- Fix an issue where a virtual environment was created unnecessarily when running `poetry export` ([#6282](https://github.com/python-poetry/poetry/pull/6282)).
- Fix an issue where `poetry lock --no-update` added duplicate hashes to the lock file ([#6389](https://github.com/python-poetry/poetry/pull/6389)).
- Fix an issue where `poetry install` fails because of missing hashes for `url` dependencies ([#6389](https://github.com/python-poetry/poetry/pull/6389)).
- Fix an issue where Poetry was not able to update pip in Windows virtual environments ([#6430](https://github.com/python-poetry/poetry/pull/6430)).
- Fix an issue where Poetry was not able to install releases that contained less common link types ([#5767](https://github.com/python-poetry/poetry/pull/5767)).
- Fix a `poetry lock` performance regression when checking non-PyPI sources for yanked versions ([#6442](https://github.com/python-poetry/poetry/pull/6442)).
- Fix an issue where `--no-cache` was not respected when running `poetry install` ([#6479](https://github.com/python-poetry/poetry/pull/6479)).
- Fix an issue where deprecation warnings for `--dev` were missing ([#6475](https://github.com/python-poetry/poetry/pull/6475)).
- Fix an issue where Git dependencies failed to clone when `insteadOf` was used in `.gitconfig` using the Dulwich Git client ([#6506](https://github.com/python-poetry/poetry/pull/6506)).
- Fix an issue where no cache entry is found when calling `poetry cache clear` with a non-normalized package name ([#6537](https://github.com/python-poetry/poetry/pull/6537)).
- Fix an invalid virtualenv constraint on Poetry ([#6402](https://github.com/python-poetry/poetry/pull/6402)).
- Fix outdated build system requirements for Poetry ([#6509](https://github.com/python-poetry/poetry/pull/6509)).
### Docs
- Add missing path segment to paths used by install.python-poetry.org ([#6311](https://github.com/python-poetry/poetry/pull/6311)).
- Add recommendations about how to install Poetry in a CI environment ([#6345](https://github.com/python-poetry/poetry/pull/6345)).
- Fix examples for `--with` and `--without` ([#6318](https://github.com/python-poetry/poetry/pull/6318)).
- Update configuration folder path for macOS ([#6395](https://github.com/python-poetry/poetry/pull/6395)).
- Improve the description of the `virtualenv.create` option ([#6460](https://github.com/python-poetry/poetry/pull/6460)).
- Clarify that `poetry install` removes dependencies of non-installed extras ([#6229](https://github.com/python-poetry/poetry/pull/6229)).
- Add a note about `pre-commit autoupdate` and Poetry's hooks ([#6497](https://github.com/python-poetry/poetry/pull/6497)).
## [1.2.0] - 2022-08-31
### Docs
- Added note about how to add a git dependency with a subdirectory ([#6218](https://github.com/python-poetry/poetry/pull/6218))
- Fixed several style issues in the docs ([#6254](https://github.com/python-poetry/poetry/pull/6254))
- Fixed outdated info about `--only` parameter ([#6263](https://github.com/python-poetry/poetry/pull/6263))
## [1.2.0rc2] - 2022-08-26
### Fixed
- Fixed an issue where virtual environments were created unnecessarily when running `poetry self` commands ([#6225](https://github.com/python-poetry/poetry/pull/6225))
- Ensure that packages' `pretty_name` are written to the lock file ([#6237](https://github.com/python-poetry/poetry/pull/6237))
### Improvements
- Improved the consistency of `Pool().remove_repository()` to make it easier to write poetry plugins ([#6214](https://github.com/python-poetry/poetry/pull/6214))
### Docs
- Removed mentions of Python 2.7 from docs ([#6234](https://github.com/python-poetry/poetry/pull/6234))
- Added note about the difference between groups and extras ([#6230](https://github.com/python-poetry/poetry/pull/6230))
## [1.2.0rc1] - 2022-08-22
### Added
- Added support for subdirectories in git dependencies ([#5172](https://github.com/python-poetry/poetry/pull/5172))
- Added support for yanked releases and files (PEP-592) ([#5841](https://github.com/python-poetry/poetry/pull/5841))
- Virtual environments can now be created even with empty project names ([#5856](https://github.com/python-poetry/poetry/pull/5856))
- Added support for `nushell` in `poetry shell` ([#6063](https://github.com/python-poetry/poetry/pull/6063))
### Changed
- Poetry now falls back to gather metadata for dependencies via pep517 if parsing `pyproject.toml` fails ([#5834](https://github.com/python-poetry/poetry/pull/5834))
- Replaced Poetry's helper method `canonicalize_name()` with `packaging.utils.canonicalize_name()` ([#6022](https://github.com/python-poetry/poetry/pull/6022))
- Removed code for the `export` command, which is now provided via plugin ([#6128](https://github.com/python-poetry/poetry/pull/6128))
- Extras and extras dependencies are now sorted in the lock file ([#6169](https://github.com/python-poetry/poetry/pull/6169))
- Removed deprecated (1.2-only) CLI options ([#6210](https://github.com/python-poetry/poetry/pull/6210))
### Fixed
- Fixed an issue where symlinks in the lock file were not resolved ([#5850](https://github.com/python-poetry/poetry/pull/5850))
- Fixed a `tomlkit` regression resulting in inconsistent line endings ([#5870](https://github.com/python-poetry/poetry/pull/5870))
- Fixed an issue where the `POETRY_PYPI_TOKEN_PYPI` environment variable wasn't respected ([#5911](https://github.com/python-poetry/poetry/pull/5911))
- Fixed an issue where neither Python nor a managed venv can be found, when using Python from MS Store ([#5931](https://github.com/python-poetry/poetry/pull/5931))
- Improved error message of `poetry publish` in the event of an upload error ([#6043](https://github.com/python-poetry/poetry/pull/6043))
- Fixed an issue where `poetry lock` fails without output ([#6058](https://github.com/python-poetry/poetry/pull/6058))
- Fixed an issue where Windows drive mappings break virtual environment names ([#6110](https://github.com/python-poetry/poetry/pull/6110))
- `tomlkit` versions with memory leak are now avoided ([#6160](https://github.com/python-poetry/poetry/pull/6160))
- Fixed an infinite loop in the solver ([#6178](https://github.com/python-poetry/poetry/pull/6178))
- Fixed an issue where latest version was used instead of locked one for vcs dependencies with extras ([#6185](https://github.com/python-poetry/poetry/pull/6185))
### Docs
- Document use of the `subdirectory` parameter ([#5949](https://github.com/python-poetry/poetry/pull/5949))
- Document suggested `tox` config for different use cases ([#6026](https://github.com/python-poetry/poetry/pull/6026))
## [1.1.15] - 2022-08-22
### Changed
- Poetry now fallback to gather metadata for dependencies via pep517 if parsing pyproject.toml fail ([#6206](https://github.com/python-poetry/poetry/pull/6206))
- Extras and extras dependencies are now sorted in lock file ([#6207](https://github.com/python-poetry/poetry/pull/6207))
## [1.2.0b3] - 2022-07-13
**Important**: This release fixes a critical issue that prevented hashes from being retrieved when locking dependencies,
due to a breaking change on PyPI JSON API (see [#5972](https://github.com/python-poetry/poetry/pull/5972)
and [the upstream change](https://github.com/pypi/warehouse/pull/11775) for more details).
After upgrading, you have to clear Poetry cache manually to get that feature working correctly again:
```bash
$ poetry cache clear pypi --all
```
### Added
- Added `--only-root` to `poetry install` to install a project without its
dependencies ([#5783](https://github.com/python-poetry/poetry/pull/5783))
### Changed
- Improved user experience of `poetry init` ([#5838](https://github.com/python-poetry/poetry/pull/5838))
- Added default timeout for all HTTP requests, to avoid hanging
requests ([#5881](https://github.com/python-poetry/poetry/pull/5881))
- Updated `poetry init` to better specify how to skip adding
dependencies ([#5946](https://github.com/python-poetry/poetry/pull/5946))
- Updated Poetry repository names to avoid clashes with user-defined
repositories ([#5910](https://github.com/python-poetry/poetry/pull/5910))
### Fixed
- Fixed an issue where extras where not handled if they did not match the case-sensitive name of the
packages ([#4122](https://github.com/python-poetry/poetry/pull/4122))
- Fixed configuration of `experimental.system-git-client` option
through `poetry config` ([#5818](https://github.com/python-poetry/poetry/pull/5818))
- Fixed uninstallation of git dependencies on Windows ([#5836](https://github.com/python-poetry/poetry/pull/5836))
- Fixed an issue where `~` was not correctly expanded
in `virtualenvs.path` ([#5848](https://github.com/python-poetry/poetry/pull/5848))
- Fixed an issue where installing/locking dependencies would hang when setting an incorrect git
repository ([#5880](https://github.com/python-poetry/poetry/pull/5880))
- Fixed an issue in `poetry publish` when keyring was not properly
configured ([#5889](https://github.com/python-poetry/poetry/pull/5889))
- Fixed duplicated line output in console ([#5890](https://github.com/python-poetry/poetry/pull/5890))
- Fixed an issue where the same wheels where downloaded multiple times during
installation ([#5871](https://github.com/python-poetry/poetry/pull/5871))
- Fixed an issue where dependencies hashes could not be retrieved when locking due to a breaking change on PyPI JSON
API ([#5973](https://github.com/python-poetry/poetry/pull/5973))
- Fixed an issue where a dependency with non-requested extras could not be installed if it is requested with extras by
another dependency ([#5770](https://github.com/python-poetry/poetry/pull/5770))
- Updated git backend to correctly read local/global git config when using dulwich as a git
backend ([#5935](https://github.com/python-poetry/poetry/pull/5935))
- Fixed an issue where optional dependencies where not correctly exported when defining
groups ([#5819](https://github.com/python-poetry/poetry/pull/5819))
### Docs
- Fixed configuration instructions for repositories
specification ([#5809](https://github.com/python-poetry/poetry/pull/5809))
- Added a link to dependency specification
from `pyproject.toml` ([#5815](https://github.com/python-poetry/poetry/pull/5815))
- Improved `zsh` autocompletion instructions ([#5859](https://github.com/python-poetry/poetry/pull/5859))
- Improved installation and update documentations ([#5857](https://github.com/python-poetry/poetry/pull/5857))
- Improved exact requirements documentation ([#5874](https://github.com/python-poetry/poetry/pull/5874))
- Added documentation for `@` operator ([#5822](https://github.com/python-poetry/poetry/pull/5822))
- Improved autocompletion documentation ([#5879](https://github.com/python-poetry/poetry/pull/5879))
- Improved `scripts` definition documentation ([#5884](https://github.com/python-poetry/poetry/pull/5884))
## [1.1.14] - 2022-07-08
### Fixed
- Fixed an issue where dependencies hashes could not be retrieved when locking due to a breaking change on PyPI JSON API ([#5973](https://github.com/python-poetry/poetry/pull/5973))
## [1.2.0b2] - 2022-06-07
### Added
- Added support for multiple-constraint direct origin dependencies with the same
version ([#5715](https://github.com/python-poetry/poetry/pull/5715))
- Added support disabling TLS verification for custom package sources via `poetry config certificates..cert false` ([#5719](https://github.com/python-poetry/poetry/pull/5719)
- Added new configuration (`virtualenvs.prompt`) to customize the prompt of the Poetry-managed virtual environment ([#5606](https://github.com/python-poetry/poetry/pull/5606))
- Added progress indicator to `download_file` (used when downloading dists) ([#5451](https://github.com/python-poetry/poetry/pull/5451))
- Added `--dry-run` to `poetry version` command ([#5603](https://github.com/python-poetry/poetry/pull/5603))
- Added `--why` to `poetry show` ([#5444](https://github.com/python-poetry/poetry/pull/5444))
- Added support for single page (html) repositories ([#5517](https://github.com/python-poetry/poetry/pull/5517))
- Added support for PEP 508 strings when adding
dependencies via `poetry add` command ([#5554](https://github.com/python-poetry/poetry/pull/5554))
- Added `--no-cache` as a global option ([#5519](https://github.com/python-poetry/poetry/pull/5519))
- Added cert retrieval for HTTP requests made by Poetry ([#5320](https://github.com/python-poetry/poetry/pull/5320))
- Added `--skip-existing` to `poetry publish` ([#2812](https://github.com/python-poetry/poetry/pull/2812))
- Added `--all-extras` to `poetry install` ([#5452](https://github.com/python-poetry/poetry/pull/5452))
- Added new `poetry self` sub-commands to manage plugins and/or system environment packages, eg: keyring backends ([#5450](https://github.com/python-poetry/poetry/pull/5450))
- Added new configuration (`installer.no-binary`) to allow selection of non-binary distributions when installing a dependency ([#5609](https://github.com/python-poetry/poetry/pull/5609))
### Changed
- `poetry plugin` commands are now deprecated in favor of the more generic `poetry self`
commands ([#5450](https://github.com/python-poetry/poetry/pull/5450))
- When creating new projects, Poetry no longer restricts README extensions to `md` and `rst` ([#5357](https://github.com/python-poetry/poetry/pull/5357))
- Changed the provider to allow fallback to installed packages ([#5704](https://github.com/python-poetry/poetry/pull/5704))
- Solver now correctly handles and prefers direct reference constraints (vcs, file etc.) over public version identifiers ([#5654](https://github.com/python-poetry/poetry/pull/5654))
- Changed the build script behavior to create an ephemeral build environment when a build script is
specified ([#5401](https://github.com/python-poetry/poetry/pull/5401))
- Improved performance when determining PEP 517 metadata from sources ([#5601](https://github.com/python-poetry/poetry/pull/5601))
- Project package sources no longer need to be redefined as global repositories when configuring credentials ([#5563](https://github.com/python-poetry/poetry/pull/5563))
- Replaced external git command use with dulwich, in order to force the legacy behaviour set `experimental.system-git-client` configuration to `true` ([#5428](https://github.com/python-poetry/poetry/pull/5428))
- Improved http request handling for sources and multiple paths on same netloc ([#5518](https://github.com/python-poetry/poetry/pull/5518))
- Made `no-pip` and `no-setuptools` configuration explicit ([#5455](https://github.com/python-poetry/poetry/pull/5455))
- Improved application logging, use of `-vv` now provides more debug information ([#5503](https://github.com/python-poetry/poetry/pull/5503))
- Renamed implicit group `default` to `main` ([#5465](https://github.com/python-poetry/poetry/pull/5465))
- Replaced in-tree implementation of `poetry export`
with `poetry-plugin-export` ([#5413](https://github.com/python-poetry/poetry/pull/5413))
- Changed the password manager behavior to use a `"null"` keyring when
disabled ([#5251](https://github.com/python-poetry/poetry/pull/5251))
- Incremental improvement of Solver performance ([#5335](https://github.com/python-poetry/poetry/pull/5335))
- Newly created virtual environments on macOS now are excluded from Time Machine backups ([#4599](https://github.com/python-poetry/poetry/pull/4599))
- Poetry no longer raises an exception when a package is not found on PyPI ([#5698](https://github.com/python-poetry/poetry/pull/5698))
- Update `packaging` dependency to use major version 21, this change forces Poetry to drop support for managing Python 2.7 environments ([#4749](https://github.com/python-poetry/poetry/pull/4749))
### Fixed
- Fixed `poetry update --dry-run` to not modify `poetry.lock` ([#5718](https://github.com/python-poetry/poetry/pull/5718), [#3666](https://github.com/python-poetry/poetry/issues/3666), [#3766](https://github.com/python-poetry/poetry/issues/3766))
- Fixed [#5537](https://github.com/python-poetry/poetry/issues/5537) where export fails to resolve dependencies with more than one
path ([#5688](https://github.com/python-poetry/poetry/pull/5688))
- Fixed an issue where the environment variables `POETRY_CONFIG_DIR` and `POETRY_CACHE_DIR` were not being respected ([#5672](https://github.com/python-poetry/poetry/pull/5672))
- Fixed [#3628](https://github.com/python-poetry/poetry/issues/3628) and [#4702](https://github.com/python-poetry/poetry/issues/4702) by handling invalid distributions
gracefully ([#5645](https://github.com/python-poetry/poetry/pull/5645))
- Fixed an issue where the provider ignored subdirectory when merging and improve subdirectory support for vcs
deps ([#5648](https://github.com/python-poetry/poetry/pull/5648))
- Fixed an issue where users could not select an empty choice when selecting
dependencies ([#4606](https://github.com/python-poetry/poetry/pull/4606))
- Fixed an issue where `poetry init -n` crashes in a root directory ([#5612](https://github.com/python-poetry/poetry/pull/5612))
- Fixed an issue where Solver errors arise due to wheels having different Python
constraints ([#5616](https://github.com/python-poetry/poetry/pull/5616))
- Fixed an issue where editable path dependencies using `setuptools` could not be correctly installed ([#5590](https://github.com/python-poetry/poetry/pull/5590))
- Fixed flicker when displaying executor operations ([#5556](https://github.com/python-poetry/poetry/pull/5556))
- Fixed an issue where the `poetry lock --no-update` only sorted by name and not by name and
version ([#5446](https://github.com/python-poetry/poetry/pull/5446))
- Fixed an issue where the Solver fails when a dependency has multiple constrained dependency definitions for the same
package ([#5403](https://github.com/python-poetry/poetry/pull/5403))
- Fixed an issue where dependency resolution takes a while because Poetry checks all possible combinations
even markers are mutually exclusive ([#4695](https://github.com/python-poetry/poetry/pull/4695))
- Fixed incorrect version selector constraint ([#5500](https://github.com/python-poetry/poetry/pull/5500))
- Fixed an issue where `poetry lock --no-update` dropped
packages ([#5435](https://github.com/python-poetry/poetry/pull/5435))
- Fixed an issue where packages were incorrectly grouped when
exporting ([#5156](https://github.com/python-poetry/poetry/pull/5156))
- Fixed an issue where lockfile always updates when using private
sources ([#5362](https://github.com/python-poetry/poetry/pull/5362))
- Fixed an issue where the solver did not account for selected package features ([#5305](https://github.com/python-poetry/poetry/pull/5305))
- Fixed an issue with console script execution of editable dependencies on Windows ([#3339](https://github.com/python-poetry/poetry/pull/3339))
- Fixed an issue where editable builder did not write PEP-610 metadata ([#5703](https://github.com/python-poetry/poetry/pull/5703))
- Fixed an issue where Poetry 1.1 lock files were incorrectly identified as not fresh ([#5458](https://github.com/python-poetry/poetry/pull/5458))
### Docs
- Updated plugin management commands ([#5450](https://github.com/python-poetry/poetry/pull/5450))
- Added the `--readme` flag to documentation ([#5357](https://github.com/python-poetry/poetry/pull/5357))
- Added example for multiple maintainers ([#5661](https://github.com/python-poetry/poetry/pull/5661))
- Updated documentation for issues [#4800](https://github.com/python-poetry/poetry/issues/4800), [#3709](https://github.com/python-poetry/poetry/issues/3709), [#3573](https://github.com/python-poetry/poetry/issues/3573), [#2211](https://github.com/python-poetry/poetry/issues/2211) and [#2414](https://github.com/python-poetry/poetry/pull/2414) ([#5656](https://github.com/python-poetry/poetry/pull/5656))
- Added `poetry.toml` note in configuration ([#5492](https://github.com/python-poetry/poetry/pull/5492))
- Add documentation for `poetry about`, `poetry help`, `poetrylist`, and the `--full-path` and `--all` options
documentation ([#5664](https://github.com/python-poetry/poetry/pull/5664))
- Added more clarification to the `--why` flag ([#5653](https://github.com/python-poetry/poetry/pull/5653))
- Updated documentation to refer to PowerShell for Windows, including
instructions ([#3978](https://github.com/python-poetry/poetry/pull/3978), [#5618](https://github.com/python-poetry/poetry/pull/5618))
- Added PEP 508 name requirement ([#5642](https://github.com/python-poetry/poetry/pull/5642))
- Added example for each section of pyproject.toml ([#5585](https://github.com/python-poetry/poetry/pull/5642))
- Added documentation for `--local` to fix issue [#5623](https://github.com/python-poetry/poetry/issues/5623) ([#5629](https://github.com/python-poetry/poetry/pull/5629))
- Added troubleshooting documentation for using proper quotation with
ZSH ([#4847](https://github.com/python-poetry/poetry/pull/4847))
- Added information on git and basic http auth ([#5578](https://github.com/python-poetry/poetry/pull/5578))
- Removed ambiguity about PEP 440 and semver ([#5576](https://github.com/python-poetry/poetry/pull/5576))
- Removed Pipenv comparison ([#5561](https://github.com/python-poetry/poetry/pull/5561))
- Improved dependency group related documentation ([#5338](https://github.com/python-poetry/poetry/pull/5338))
- Added documentation for default directories used by Poetry ([#5391](https://github.com/python-poetry/poetry/pull/5301))
- Added warning about credentials preserved in shell history ([#5726](https://github.com/python-poetry/poetry/pull/5726))
- Improved documentation of the `readme` option, including multiple files and additional formats ([#5158](https://github.com/python-poetry/poetry/pull/5158))
- Improved contributing documentation ([#5708](https://github.com/python-poetry/poetry/pull/5708))
- Remove all references to `--dev-only` option ([#5771](https://github.com/python-poetry/poetry/pull/5771))
## [1.2.0b1] - 2022-03-17
### Fixed
- Fixed an issue where the system environment couldn't be detected ([#4406](https://github.com/python-poetry/poetry/pull/4406)).
- Fixed another issue where the system environment couldn't be detected ([#4433](https://github.com/python-poetry/poetry/pull/4433)).
- Replace deprecated requests parameter in uploader ([#4580](https://github.com/python-poetry/poetry/pull/4580)).
- Fix an issue where venv are detected as broken when using MSys2 on windows ([#4482](https://github.com/python-poetry/poetry/pull/4482)).
- Fixed an issue where the cache breaks on windows ([#4531](https://github.com/python-poetry/poetry/pull/4531)).
- Fixed an issue where a whitespace before a semicolon was missing on `poetry export` ([#4575](https://github.com/python-poetry/poetry/issues/4575)).
- Fixed an issue where markers were not correctly assigned to nested dependencies ([#3511](https://github.com/python-poetry/poetry/issues/3511)).
- Recognize one digit version in wheel filenames ([#3338](https://github.com/python-poetry/poetry/pull/3338)).
- Fixed an issue when `locale` is unset ([#4038](https://github.com/python-poetry/poetry/pull/4038)).
- Fixed an issue where the fallback to another interpreter didn't work ([#3475](https://github.com/python-poetry/poetry/pull/3475)).
- Merge any marker constraints into constraints with specific markers ([#4590](https://github.com/python-poetry/poetry/pull/4590)).
- Normalize path before hashing so that the generated venv name is independent of case on Windows ([#4813](https://github.com/python-poetry/poetry/pull/4813)).
- Fixed an issue where a dependency wasn't upgrade by using `@latest` on `poetry update` ([#4945](https://github.com/python-poetry/poetry/pull/4945)).
- Fixed an issue where conda envs in windows are always reported as broken([#5007](https://github.com/python-poetry/poetry/pull/5007)).
- Fixed an issue where Poetry doesn't find its own venv on `poetry self update` ([#5049](https://github.com/python-poetry/poetry/pull/5049)).
- Fix misuse of pretty_constraint ([#4932](https://github.com/python-poetry/poetry/pull/4932)).
- Fixed an issue where the reported python version used for venv creation wasn't correct ([#5086](https://github.com/python-poetry/poetry/pull/5086)).
- Fixed an issue where the searched package wasn't display in the interactive dialog of `poetry init` ([#5076](https://github.com/python-poetry/poetry/pull/5076)).
- Fixed an issue where Poetry raises an exception on `poetry show` when no lock files exists ([#5242](https://github.com/python-poetry/poetry/pull/5242)).
- Fixed an issue where Poetry crashes when optional `vcs_info.requested_version` in `direct_url.json` wasn't included ([#5274](https://github.com/python-poetry/poetry/pull/5274)).
- Fixed an issue where dependencies with extras were updated despite using `--no-update` ([#4618](https://github.com/python-poetry/poetry/pull/4618)).
- Fixed various places where poetry writes messages to stdout instead of stderr ([#4110](https://github.com/python-poetry/poetry/pull/4110), [#5179](https://github.com/python-poetry/poetry/pull/5179)).
- Ensured that when complete packages are created dependency inherits source and resolved refs from package ([#4604](https://github.com/python-poetry/poetry/pull/4604)).
- Ensured that when complete packages are created dependency inherits subdirectory from package if supported ([#4604](https://github.com/python-poetry/poetry/pull/4604)).
- Fixed an issue where `POETRY_EXPERIMENTAL_NEW_INSTALLER` needs to be set to an empty string to disable it ([#3811](https://github.com/python-poetry/poetry/pull/3811)).
### Added
- `poetry show ` now also shows which packages depend on it ([#2351](https://github.com/python-poetry/poetry/pull/2351)).
- The info dialog by `poetry about` now contains version information about installed poetry and poetry-core ([#5288](https://github.com/python-poetry/poetry/pull/5288)).
- Print error message when `poetry publish` fails ([#3549](https://github.com/python-poetry/poetry/pull/3549)).
- Added in info output to `poetry lock --check` ([#5081](https://github.com/python-poetry/poetry/pull/5081)).
- Added new argument `--all` for `poetry env remove` to delete all venv of a project at once ([#3212](https://github.com/python-poetry/poetry/pull/3212)).
- Added new argument `--without-urls` for `poetry export` to exclude source repository urls from the exported file ([#4763](https://github.com/python-poetry/poetry/pull/4763)).
- Added a new `installer.max-workers` property to the configuration ([#3516](https://github.com/python-poetry/poetry/pull/3516)).
- Added experimental option `virtualenvs.prefer-active-python` to detect current activated python ([#4852](https://github.com/python-poetry/poetry/pull/4852)).
- Added better windows shell support ([#5053](https://github.com/python-poetry/poetry/pull/5053)).
### Changed
- Drop python3.6 support ([#5055](https://github.com/python-poetry/poetry/pull/5055)).
- Exit with callable return code in generated script ([#4456](https://github.com/python-poetry/poetry/pull/4456)).
- Internal use of the `pep517` high level interfaces for package metadata inspections have been replaced with the `build` package. ([#5155](https://github.com/python-poetry/poetry/pull/5155)).
- Poetry now raises an error if the python version in the project environment is no longer compatible with the project ([#4520](https://github.com/python-poetry/poetry/pull/4520)).
## [1.1.13] - 2022-02-10
### Fixed
- Fixed an issue where envs in MSYS2 always reported as broken ([#4942](https://github.com/python-poetry/poetry/pull/4942))
- Fixed an issue where conda envs in windows are always reported as broken([#5008](https://github.com/python-poetry/poetry/pull/5008))
- Fixed an issue where Poetry doesn't find its own venv on `poetry self update` ([#5048](https://github.com/python-poetry/poetry/pull/5048))
## [1.1.12] - 2021-11-27
### Fixed
- Fixed broken caches on Windows due to `Path` starting with a slash ([#4549](https://github.com/python-poetry/poetry/pull/4549))
- Fixed `JSONDecodeError` when installing packages by updating `cachecontrol` version ([#4831](https://github.com/python-poetry/poetry/pull/4831))
- Fixed dropped markers in dependency walk ([#4686](https://github.com/python-poetry/poetry/pull/4686))
## [1.1.11] - 2021-10-04
### Fixed
- Fixed errors when installing packages on Python 3.10. ([#4592](https://github.com/python-poetry/poetry/pull/4592))
- Fixed an issue where the wrong `git` executable could be used on Windows. ([python-poetry/poetry-core#213](https://github.com/python-poetry/poetry-core/pull/213))
- Fixed an issue where the Python 3.10 classifier was not automatically added. ([python-poetry/poetry-core#215](https://github.com/python-poetry/poetry-core/pull/215))
## [1.1.10] - 2021-09-21
### Fixed
- Fixed an issue where non-sha256 hashes were not checked. ([#4529](https://github.com/python-poetry/poetry/pull/4529))
## [1.1.9] - 2021-09-18
### Fixed
- Fixed a security issue where file hashes were not checked prior to installation. ([#4420](https://github.com/python-poetry/poetry/pull/4420), [#4444](https://github.com/python-poetry/poetry/pull/4444), [python-poetry/poetry-core#193](https://github.com/python-poetry/poetry-core/pull/193))
- Fixed the detection of the system environment when the setting `virtualenvs.create` is deactivated. ([#4507](https://github.com/python-poetry/poetry/pull/4507))
- Fixed an issue where unsafe parameters could be passed to `git` commands. ([python-poetry/poetry-core#203](https://github.com/python-poetry/poetry-core/pull/203))
- Fixed an issue where the wrong `git` executable could be used on Windows. ([python-poetry/poetry-core#205](https://github.com/python-poetry/poetry-core/pull/205))
## [1.1.8] - 2021-08-19
### Fixed
- Fixed an error with repository prioritization when specifying secondary repositories. ([#4241](https://github.com/python-poetry/poetry/pull/4241))
- Fixed the detection of the system environment when the setting `virtualenvs.create` is deactivated. ([#4330](https://github.com/python-poetry/poetry/pull/4330), [#4407](https://github.com/python-poetry/poetry/pull/4407))
- Fixed the evaluation of relative path dependencies. ([#4246](https://github.com/python-poetry/poetry/pull/4246))
- Fixed environment detection for Python 3.10 environments. ([#4387](https://github.com/python-poetry/poetry/pull/4387))
- Fixed an error in the evaluation of `in/not in` markers ([python-poetry/poetry-core#189](https://github.com/python-poetry/poetry-core/pull/189))
## [1.2.0a2] - 2021-08-01
### Added
- Poetry now supports dependency groups. ([#4260](https://github.com/python-poetry/poetry/pull/4260))
- The `install` command now supports a `--sync` option to synchronize the environment with the lock file. ([#4336](https://github.com/python-poetry/poetry/pull/4336))
### Changed
- Improved the way credentials are retrieved to better support keyring backends. ([#4086](https://github.com/python-poetry/poetry/pull/4086))
- The `--remove-untracked` option of the `install` command is now deprecated in favor of the new `--sync` option. ([#4336](https://github.com/python-poetry/poetry/pull/4336))
- The user experience when installing dependency groups has been improved. ([#4336](https://github.com/python-poetry/poetry/pull/4336))
### Fixed
- Fixed performance issues when resolving dependencies. ([#3839](https://github.com/python-poetry/poetry/pull/3839))
- Fixed an issue where transitive dependencies of directory or VCS dependencies were not installed or otherwise removed. ([#4202](https://github.com/python-poetry/poetry/pull/4202))
- Fixed the behavior of the `init` command in non-interactive mode. ([#2899](https://github.com/python-poetry/poetry/pull/2899))
- Fixed the detection of the system environment when the setting `virtualenvs.create` is deactivated. ([#4329](https://github.com/python-poetry/poetry/pull/4329))
- Fixed the display of possible solutions for some common errors. ([#4332](https://github.com/python-poetry/poetry/pull/4332))
## [1.1.7] - 2021-06-25
**Note**: Lock files might need to be regenerated for the first fix below to take effect.
You can use `poetry lock` to do so **without** the `--no-update` option.
### Changed
- This release is compatible with the `install-poetry.py` installation script to ease the migration path from `1.1` releases to `1.2` releases. ([#4192](https://github.com/python-poetry/poetry/pull/4192))
### Fixed
- Fixed an issue where transitive dependencies of directory or VCS dependencies were not installed or otherwise removed. ([#4203](https://github.com/python-poetry/poetry/pull/4203))
- Fixed an issue where the combination of the `--tree` and `--no-dev` options for the show command was still displaying development dependencies. ([#3992](https://github.com/python-poetry/poetry/pull/3992))
## [1.2.0a1] - 2021-05-21
This release is the first testing release of the upcoming 1.2.0 version.
It **drops** support for Python 2.7 and 3.5.
### Added
- Poetry now supports a plugin system to alter or expand Poetry's functionality. ([#3733](https://github.com/python-poetry/poetry/pull/3733))
- Poetry now supports [PEP 610](https://www.python.org/dev/peps/pep-0610/). ([#3876](https://github.com/python-poetry/poetry/pull/3876))
- Several configuration options to better control the way virtual environments are created are now available. ([#3157](https://github.com/python-poetry/poetry/pull/3157), [#3711](https://github.com/python-poetry/poetry/pull/3711)).
- The `new` command now supports namespace packages. ([#2768](https://github.com/python-poetry/poetry/pull/2768))
- The `add` command now supports the `--editable` option to add packages in editable mode. ([#3940](https://github.com/python-poetry/poetry/pull/3940))
### Changed
- Python 2.7 and 3.5 are no longer supported. ([#3405](https://github.com/python-poetry/poetry/pull/3405))
- The usage of the `get-poetry.py` script is now deprecated and is replaced by the `install-poetry.py` script. ([#3706](https://github.com/python-poetry/poetry/pull/3706))
- Directory dependencies are now in non-develop mode by default. ([poetry-core#98](https://github.com/python-poetry/poetry-core/pull/98))
- Improved support for PEP 440 specific versions that do not abide by semantic versioning. ([poetry-core#140](https://github.com/python-poetry/poetry-core/pull/140))
- Improved the CLI experience and performance by migrating to the latest version of Cleo. ([#3618](https://github.com/python-poetry/poetry/pull/3618))
- Packages previously considered as unsafe (`pip`, `setuptools`, `wheels` and `distribute`) can now be managed as any other package. ([#2826](https://github.com/python-poetry/poetry/pull/2826))
- The `new` command now defaults to the Markdown format for README files. ([#2768](https://github.com/python-poetry/poetry/pull/2768))
### Fixed
- Fixed an error where command line options were not taken into account when using the `run` command. ([#3618](https://github.com/python-poetry/poetry/pull/3618))
- Fixed an error in the way custom repositories were resolved. ([#3406](https://github.com/python-poetry/poetry/pull/3406))
## [1.1.6] - 2021-04-14
### Fixed
- Fixed export format for path dependencies. ([#3121](https://github.com/python-poetry/poetry/pull/3121))
- Fixed errors caused by environment modification when executing some commands. ([#3253](https://github.com/python-poetry/poetry/pull/3253))
- Fixed handling of wheel files with single-digit versions. ([#3338](https://github.com/python-poetry/poetry/pull/3338))
- Fixed an error when handling single-digit Python markers. ([poetry-core#156](https://github.com/python-poetry/poetry-core/pull/156))
- Fixed dependency markers not being properly copied when changing the constraint leading to resolution errors. ([poetry-core#163](https://github.com/python-poetry/poetry-core/pull/163))
- Fixed an error where VCS dependencies were always updated. ([#3947](https://github.com/python-poetry/poetry/pull/3947))
- Fixed an error where the incorrect version of a package was locked when using environment markers. ([#3945](https://github.com/python-poetry/poetry/pull/3945))
## [1.1.5] - 2021-03-04
### Fixed
- Fixed an error in the `export` command when no lock file existed and a verbose flag was passed to the command. ([#3310](https://github.com/python-poetry/poetry/pull/3310))
- Fixed an error where the `pyproject.toml` was not reverted when using the `add` command. ([#3622](https://github.com/python-poetry/poetry/pull/3622))
- Fixed errors when using non-HTTPS indices. ([#3622](https://github.com/python-poetry/poetry/pull/3622))
- Fixed errors when handling simple indices redirection. ([#3622](https://github.com/python-poetry/poetry/pull/3622))
- Fixed errors when trying to handle newer wheels by using the latest version of `poetry-core` and `packaging`. ([#3677](https://github.com/python-poetry/poetry/pull/3677))
- Fixed an error when using some versions of `poetry-core` due to an incorrect import . ([#3696](https://github.com/python-poetry/poetry/pull/3696))
## [1.1.4] - 2020-10-23
### Added
- Added `installer.parallel` boolean flag (defaults to `true`) configuration to enable/disable parallel execution of operations when using the new installer. ([#3088](https://github.com/python-poetry/poetry/pull/3088))
### Changed
- When using system environments as an unprivileged user, user site and bin directories are created if they do not already exist. ([#3107](https://github.com/python-poetry/poetry/pull/3107))
### Fixed
- Fixed editable installation of poetry projects when using system environments. ([#3107](https://github.com/python-poetry/poetry/pull/3107))
- Fixed locking of nested extra activations. If you were affected by this issue, you will need to regenerate the lock file using `poetry lock --no-update`. ([#3229](https://github.com/python-poetry/poetry/pull/3229))
- Fixed prioritisation of non-default custom package sources. ([#3251](https://github.com/python-poetry/poetry/pull/3251))
- Fixed detection of installed editable packages when non-poetry managed `.pth` file exists. ([#3210](https://github.com/python-poetry/poetry/pull/3210))
- Fixed scripts generated by editable builder to use valid import statements. ([#3214](https://github.com/python-poetry/poetry/pull/3214))
- Fixed recursion error when locked dependencies contain cyclic dependencies. ([#3237](https://github.com/python-poetry/poetry/pull/3237))
- Fixed propagation of editable flag for VCS dependencies. ([#3264](https://github.com/python-poetry/poetry/pull/3264))
## [1.1.3] - 2020-10-14
### Changed
- Python version support deprecation warning is now written to `stderr`. ([#3131](https://github.com/python-poetry/poetry/pull/3131))
### Fixed
- Fixed `KeyError` when `PATH` is not defined in environment variables. ([#3159](https://github.com/python-poetry/poetry/pull/3159))
- Fixed error when using `config` command in a directory with an existing `pyproject.toml` without any Poetry configuration. ([#3172](https://github.com/python-poetry/poetry/pull/3172))
- Fixed incorrect inspection of package requirements when same dependency is specified multiple times with unique markers. ([#3147](https://github.com/python-poetry/poetry/pull/3147))
- Fixed `show` command to use already resolved package metadata. ([#3117](https://github.com/python-poetry/poetry/pull/3117))
- Fixed multiple issues with `export` command output when using `requirements.txt` format. ([#3119](https://github.com/python-poetry/poetry/pull/3119))
## [1.1.2] - 2020-10-06
### Changed
- Dependency installation of editable packages and all uninstall operations are now performed serially within their corresponding priority groups. ([#3099](https://github.com/python-poetry/poetry/pull/3099))
- Improved package metadata inspection of nested poetry projects within project path dependencies. ([#3105](https://github.com/python-poetry/poetry/pull/3105))
### Fixed
- Fixed export of `requirements.txt` when project dependency contains git dependencies. ([#3100](https://github.com/python-poetry/poetry/pull/3100))
## [1.1.1] - 2020-10-05
### Added
- Added `--no-update` option to `lock` command. ([#3034](https://github.com/python-poetry/poetry/pull/3034))
### Fixed
- Fixed resolution of packages with missing required extras. ([#3035](https://github.com/python-poetry/poetry/pull/3035))
- Fixed export of `requirements.txt` dependencies to include development dependencies. ([#3024](https://github.com/python-poetry/poetry/pull/3024))
- Fixed incorrect selection of unsupported binary distribution formats when selecting a package artifact to install. ([#3058](https://github.com/python-poetry/poetry/pull/3058))
- Fixed incorrect use of system executable when building package distributions via `build` command. ([#3056](https://github.com/python-poetry/poetry/pull/3056))
- Fixed errors in `init` command when specifying `--dependency` in non-interactive mode when a `pyproject.toml` file already exists. ([#3076](https://github.com/python-poetry/poetry/pull/3076))
- Fixed incorrect selection of configured source url when a publish repository url configuration with the same name already exists. ([#3047](https://github.com/python-poetry/poetry/pull/3047))
- Fixed dependency resolution issues when the same package is specified in multiple dependency extras. ([#3046](https://github.com/python-poetry/poetry/pull/3046))
## [1.1.0] - 2020-10-01
### Changed
- The `init` command will now use existing `pyproject.toml` if possible ([#2448](https://github.com/python-poetry/poetry/pull/2448)).
- Error messages when metadata information retrieval fails have been improved ([#2997](https://github.com/python-poetry/poetry/pull/2997)).
### Fixed
- Fixed parsing of version constraint for `rc` prereleases ([#2978](https://github.com/python-poetry/poetry/pull/2978)).
- Fixed how some metadata information are extracted from `setup.cfg` files ([#2957](https://github.com/python-poetry/poetry/pull/2957)).
- Fixed return codes returned by the executor ([#2981](https://github.com/python-poetry/poetry/pull/2981)).
- Fixed whitespaces not being accepted for the list of extras when adding packages ([#2985](https://github.com/python-poetry/poetry/pull/2985)).
- Fixed repositories specified in the `pyproject.toml` file not being taken into account for authentication when downloading packages ([#2990](https://github.com/python-poetry/poetry/pull/2990)).
- Fixed permission errors when installing the root project if the `site-packages` directory is not writeable ([#3002](https://github.com/python-poetry/poetry/pull/3002)).
- Fixed environment marker propagation when exporting to the `requirements.txt` format ([#3002](https://github.com/python-poetry/poetry/pull/3002)).
- Fixed errors when paths in run command contained spaces ([#3015](https://github.com/python-poetry/poetry/pull/3015)).
## [1.1.0rc1] - 2020-09-25
### Changed
- The `virtualenvs.in-project` setting will now always be honored, if set explicitly, regardless of the presence of a `.venv` directory ([#2771](https://github.com/python-poetry/poetry/pull/2771)).
- Adding packages already present in the `pyproject.toml` file will no longer raise an error ([#2886](https://github.com/python-poetry/poetry/pull/2886)).
- Errors when authenticating against custom repositories will now be logged ([#2577](https://github.com/python-poetry/poetry/pull/2577)).
### Fixed
- Fixed an error on Python 3.5 when resolving URL dependencies ([#2954](https://github.com/python-poetry/poetry/pull/2954)).
- Fixed the `dependency` option of the `init` command being ignored ([#2587](https://github.com/python-poetry/poetry/pull/2587)).
- Fixed the `show` command displaying erroneous information following the changes in the lock file format ([#2967](https://github.com/python-poetry/poetry/pull/2967)).
- Fixed dependency resolution errors due to invalid python constraints propagation ([#2968](https://github.com/python-poetry/poetry/pull/2968)).
## [1.1.0b4] - 2020-09-23
### Changed
- When running under Python 2.7 on Windows, install command will be limited to one worker to mitigate threading issue ([#2941](https://github.com/python-poetry/poetry/pull/2941)).
## [1.1.0b3] - 2020-09-18
### Changed
- Improved the error reporting when HTTP error are encountered for legacy repositories ([#2459](https://github.com/python-poetry/poetry/pull/2459)).
- When displaying the name of packages retrieved from remote repositories, the original name will now be used ([#2305](https://github.com/python-poetry/poetry/pull/2305)).
- Failed package downloads will now be retried on connection errors ([#2813](https://github.com/python-poetry/poetry/pull/2813)).
- Path dependencies will now be installed as editable only when `develop` option is set to `true` ([#2887](https://github.com/python-poetry/poetry/pull/2887)).
### Fixed
- Fixed the detection of the type of installed packages ([#2722](https://github.com/python-poetry/poetry/pull/2722)).
- Fixed deadlocks when installing packages on systems not supporting non-ascii characters ([#2721](https://github.com/python-poetry/poetry/pull/2721)).
- Fixed handling of wildcard constraints for packages with prereleases only ([#2821](https://github.com/python-poetry/poetry/pull/2821)).
- Fixed dependencies of some packages not being discovered by ensuring we use the PEP-516 backend if specified ([#2810](https://github.com/python-poetry/poetry/pull/2810)).
- Fixed recursion errors when retrieving extras ([#2787](https://github.com/python-poetry/poetry/pull/2787)).
- Fixed `PyPI` always being displayed when publishing even for custom repositories ([#2905](https://github.com/python-poetry/poetry/pull/2905)).
- Fixed handling of packages extras when resolving dependencies ([#2887](https://github.com/python-poetry/poetry/pull/2887)).
## [1.1.0b2] - 2020-07-24
### Changed
- Added support for build scripts without the `setup.py` file generation in the editable builder ([#2718](https://github.com/python-poetry/poetry/pull/2718)).
### Fixed
- Fixed an error occurring when using older lock files ([#2717](https://github.com/python-poetry/poetry/pull/2717)).
## [1.1.0b1] - 2020-07-24
### Changed
- Virtual environments will now exclusively be built with `virtualenv` ([#2666](https://github.com/python-poetry/poetry/pull/2666)).
- Support for Python 2.7 and 3.5 is now officially deprecated and a warning message will be displayed ([#2683](https://github.com/python-poetry/poetry/pull/2683)).
- Improved metadata inspection of packages by using the PEP-517 build system ([#2632](https://github.com/python-poetry/poetry/pull/2632)).
### Fixed
- Fixed parallel tasks not being cancelled when the installation is interrupted or has failed ([#2656](https://github.com/python-poetry/poetry/pull/2656)).
- Fixed an error where the editable builder would not expose all packages ([#2664](https://github.com/python-poetry/poetry/pull/2656)).
- Fixed an error for Python 2.7 when a file could not be downloaded in the installer ([#2709](https://github.com/python-poetry/poetry/pull/2709)).
- Fixed the lock file `content-hash` value not being updated when using the `add` and `remove` commands ([#2710](https://github.com/python-poetry/poetry/pull/2710)).
- Fixed incorrect resolution errors being raised for packages with python requirements ([#2712](https://github.com/python-poetry/poetry/pull/2712)).
- Fixed an error causing the build log messages to no longer be displayed ([#2715](https://github.com/python-poetry/poetry/pull/2715)).
## [1.0.10] - 2020-07-21
### Changed
- The lock files are now versioned to ease transitions for lock file format changes, with warnings being displayed on incompatibility detection ([#2695](https://github.com/python-poetry/poetry/pull/2695)).
- The `init` and `new` commands will now provide hints on invalid given licenses ([#1634](https://github.com/python-poetry/poetry/pull/1634)).
### Fixed
- Fixed error messages when the authors specified in the `pyproject.toml` file are invalid ([#2525](https://github.com/python-poetry/poetry/pull/2525)).
- Fixed empty `.venv` directories being deleted ([#2064](https://github.com/python-poetry/poetry/pull/2064)).
- Fixed the `shell` command for `tcsh` shells ([#2583](https://github.com/python-poetry/poetry/pull/2583)).
- Fixed errors when installing directory or file dependencies in some cases ([#2582](https://github.com/python-poetry/poetry/pull/2582)).
## [1.1.0a3] - 2020-07-10
### Added
- New installer which provides a faster and better experience ([#2595](https://github.com/python-poetry/poetry/pull/2595)).
### Fixed
- Fixed resolution error when handling duplicate dependencies with environment markers ([#2622](https://github.com/python-poetry/poetry/pull/2622)).
- Fixed erroneous resolution errors when resolving packages to install ([#2625](https://github.com/python-poetry/poetry/pull/2625)).
- Fixed errors when detecting installed editable packages ([#2602](https://github.com/python-poetry/poetry/pull/2602)).
## [1.1.0a2] - 2020-06-26
Note that lock files generated with this release are not compatible with previous releases of Poetry.
### Added
- The `install` command now supports a `--remove-untracked` option to ensure only packages from the lock file are present in the environment ([#2172](https://github.com/python-poetry/poetry/pull/2172)).
- Some errors will now be provided with possible solutions and links to the documentation ([#2396](https://github.com/python-poetry/poetry/pull/2396)).
### Changed
- Editable installations of Poetry projects have been improved and are now faster ([#2360](https://github.com/python-poetry/poetry/pull/2360)).
- Improved the accuracy of the dependency resolver in case of dependencies with environment markers ([#2361](https://github.com/python-poetry/poetry/pull/2361))
- Environment markers of dependencies are no longer stored in the lock file ([#2361](https://github.com/python-poetry/poetry/pull/2361)).
- Improved the way connection errors are handled when publishing ([#2285](https://github.com/python-poetry/poetry/pull/2285)).
### Fixed
- Fixed errors when handling duplicate dependencies with environment markers ([#2342](https://github.com/python-poetry/poetry/pull/2342)).
- Fixed the detection of installed packages ([#2360](https://github.com/python-poetry/poetry/pull/2360)).
## [1.1.0a1] - 2020-03-27
This release **must** be downloaded via the `get-poetry.py` script and not via the `self update` command.
### Added
- Added a new `--dry-run` option to the `publish` command ([#2199](https://github.com/python-poetry/poetry/pull/2199)).
### Changed
- The core features of Poetry have been extracted in to a separate library: `poetry-core` ([#2212](https://github.com/python-poetry/poetry/pull/2212)).
- The build backend is no longer `poetry.masonry.api` but `poetry.core.masonry.api` which requires `poetry-core>=1.0.0a5` ([#2212](https://github.com/python-poetry/poetry/pull/2212)).
- The exceptions are now beautifully displayed in the terminal with various level of details depending on the verbosity ([2230](https://github.com/python-poetry/poetry/pull/2230)).
## [1.0.9] - 2020-06-09
### Fixed
- Fixed an issue where packages from custom indices where continuously updated ([#2525](https://github.com/python-poetry/poetry/pull/2525)).
- Fixed errors in the way Python environment markers were parsed and generated ([#2526](https://github.com/python-poetry/poetry/pull/2526)).
## [1.0.8] - 2020-06-05
### Fixed
- Fixed a possible error when installing the root package ([#2505](https://github.com/python-poetry/poetry/pull/2505)).
- Fixed an error where directory and VCS dependencies were not installed ([#2505](https://github.com/python-poetry/poetry/pull/2505)).
## [1.0.7] - 2020-06-05
### Fixed
- Fixed an error when trying to execute some packages `setup.py` file ([#2349](https://github.com/python-poetry/poetry/pull/2349)).
## [1.0.6] - 2020-06-05
### Changed
- The `self update` command has been updated in order to handle future releases of Poetry ([#2429](https://github.com/python-poetry/poetry/pull/2429)).
### Fixed
- Fixed an error were a new line was not written when displaying the virtual environment's path with `env info` ([#2196](https://github.com/python-poetry/poetry/pull/2196)).
- Fixed a misleading error message when the `packages` property was empty ([#2265](https://github.com/python-poetry/poetry/pull/2265)).
- Fixed shell detection by using environment variables ([#2147](https://github.com/python-poetry/poetry/pull/2147)).
- Fixed the removal of VCS dependencies ([#2239](https://github.com/python-poetry/poetry/pull/2239)).
- Fixed generated wheel ABI tags for Python 3.8 ([#2121](https://github.com/python-poetry/poetry/pull/2121)).
- Fixed a regression when building stub-only packages ([#2000](https://github.com/python-poetry/poetry/pull/2000)).
- Fixed errors when parsing PEP-440 constraints with whitespace ([#2347](https://github.com/python-poetry/poetry/pull/2347)).
- Fixed PEP 508 representation of VCS dependencies ([#2349](https://github.com/python-poetry/poetry/pull/2349)).
- Fixed errors when source distributions were read-only ([#1140](https://github.com/python-poetry/poetry/pull/1140)).
- Fixed dependency resolution errors and inconsistencies with directory, file and VCS dependencies ([#2398](https://github.com/python-poetry/poetry/pull/2398)).
- Fixed custom repositories information not being properly locked ([#2484](https://github.com/python-poetry/poetry/pull/2484)).
## [1.0.5] - 2020-02-29
### Fixed
- Fixed an error when building distributions if the `git` executable was not found ([#2105](https://github.com/python-poetry/poetry/pull/2105)).
- Fixed various errors when reading Poetry's TOML files by upgrading [tomlkit](https://github.com/sdispater/tomlkit).
## [1.0.4] - 2020-02-28
### Fixed
- Fixed the PyPI URL used when installing packages ([#2099](https://github.com/python-poetry/poetry/pull/2099)).
- Fixed errors when the author's name contains special characters ([#2006](https://github.com/python-poetry/poetry/pull/2006)).
- Fixed VCS excluded files detection when building wheels ([#1947](https://github.com/python-poetry/poetry/pull/1947)).
- Fixed packages detection when building sdists ([#1626](https://github.com/python-poetry/poetry/pull/1626)).
- Fixed the local `.venv` virtual environment not being displayed in `env list` ([#1762](https://github.com/python-poetry/poetry/pull/1762)).
- Fixed incompatibilities with the most recent versions of `virtualenv` ([#2096](https://github.com/python-poetry/poetry/pull/2096)).
- Fixed Poetry's own vendor dependencies being retrieved when updating dependencies ([#1981](https://github.com/python-poetry/poetry/pull/1981)).
- Fixed encoding of credentials in URLs ([#1911](https://github.com/python-poetry/poetry/pull/1911)).
- Fixed url constraints not being accepted in multi-constraints dependencies ([#2035](https://github.com/python-poetry/poetry/pull/2035)).
- Fixed an error where credentials specified via environment variables were not retrieved ([#2061](https://github.com/python-poetry/poetry/pull/2061)).
- Fixed an error where git dependencies referencing tags were not locked to the corresponding commit ([#1948](https://github.com/python-poetry/poetry/pull/1948)).
- Fixed an error when parsing packages `setup.py` files ([#2041](https://github.com/python-poetry/poetry/pull/2041)).
- Fixed an error when parsing some git URLs ([#2018](https://github.com/python-poetry/poetry/pull/2018)).
## [1.0.3] - 2020-01-31
### Fixed
- Fixed an error which caused the configuration environment variables (like `POETRY_HTTP_BASIC_XXX_PASSWORD`) to not be used ([#1909](https://github.com/python-poetry/poetry/pull/1909)).
- Fixed an error where the `--help` option was not working ([#1910](https://github.com/python-poetry/poetry/pull/1910)).
- Fixed an error where packages from private indices were not decompressed properly ([#1851](https://github.com/python-poetry/poetry/pull/1851)).
- Fixed an error where the version of some PEP-508-formatted wheel dependencies was not properly retrieved ([#1932](https://github.com/python-poetry/poetry/pull/1932)).
- Fixed internal regexps to avoid potential catastrophic backtracking errors ([#1913](https://github.com/python-poetry/poetry/pull/1913)).
- Fixed performance issues when custom indices were defined in the `pyproject.toml` file ([#1892](https://github.com/python-poetry/poetry/pull/1892)).
- Fixed the `get_requires_for_build_wheel()` function of `masonry.api` which wasn't returning the proper result ([#1875](https://github.com/python-poetry/poetry/pull/1875)).
## [1.0.2] - 2020-01-10
### Fixed
- Reverted a previous fix ([#1796](https://github.com/python-poetry/poetry/pull/1796)) which was causing errors for projects with file and/or directory dependencies ([#1865](https://github.com/python-poetry/poetry/pull/1865)).
## [1.0.1] - 2020-01-10
### Fixed
- Fixed an error in `env use` where the wrong Python executable was being used to check compatibility ([#1736](https://github.com/python-poetry/poetry/pull/1736)).
- Fixed an error where VCS dependencies were not properly categorized as development dependencies ([#1725](https://github.com/python-poetry/poetry/pull/1725)).
- Fixed an error where some shells would no longer be usable after using the `shell` command ([#1673](https://github.com/python-poetry/poetry/pull/1673)).
- Fixed an error where explicitly included files where not included in wheel distributions ([#1750](https://github.com/python-poetry/poetry/pull/1750)).
- Fixed an error where some Git dependencies url were not properly parsed ([#1756](https://github.com/python-poetry/poetry/pull/1756)).
- Fixed an error in the `env` commands on Windows if the path to the executable contained a space ([#1774](https://github.com/python-poetry/poetry/pull/1774)).
- Fixed several errors and UX issues caused by `keyring` on some systems ([#1788](https://github.com/python-poetry/poetry/pull/1788)).
- Fixed errors when trying to detect installed packages ([#1786](https://github.com/python-poetry/poetry/pull/1786)).
- Fixed an error when packaging projects where Python packages were not properly detected ([#1592](https://github.com/python-poetry/poetry/pull/1592)).
- Fixed an error where local file dependencies were exported as editable when using the `export` command ([#1840](https://github.com/python-poetry/poetry/pull/1840)).
- Fixed the way environment markers are propagated and evaluated when resolving dependencies ([#1829](https://github.com/python-poetry/poetry/pull/1829), [#1789](https://github.com/python-poetry/poetry/pull/1789)).
- Fixed an error in the PEP-508 compliant representation of directory and file dependencies ([#1796](https://github.com/python-poetry/poetry/pull/1796)).
- Fixed an error where invalid virtual environments would be silently used. They will not be recreated and a warning will be displayed ([#1797](https://github.com/python-poetry/poetry/pull/1797)).
- Fixed an error where dependencies were not properly detected when reading the `setup.py` file in some cases ([#1764](https://github.com/python-poetry/poetry/pull/1764)).
## [1.0.0] - 2019-12-12
### Added
- Added an `export` command to export the lock file to other formats (only `requirements.txt` is currently supported).
- Added a `env info` command to get basic information about the current environment.
- Added a `env use` command to control the Python version used by the project.
- Added a `env list` command to list the virtualenvs associated with the current project.
- Added a `env remove` command to delete virtualenvs associated with the current project.
- Added support for `POETRY_HOME` declaration within `get-poetry.py`.
- Added support for declaring a specific source for dependencies.
- Added support for disabling PyPI and making another repository the default one.
- Added support for declaring private repositories as secondary.
- Added the ability to specify packages on a per-format basis.
- Added support for custom urls in metadata.
- Full environment markers are now supported for dependencies via the `markers` property.
- Added the ability to specify git dependencies directly in `add`, it no longer requires the `--git` option.
- Added the ability to specify path dependencies directly in `add`, it no longer requires the `--path` option.
- Added support for url dependencies ([#1260](https://github.com/python-poetry/poetry/pull/1260)).
- Publishing to PyPI using [API tokens](https://pypi.org/help/#apitoken) is now supported ([#1275](https://github.com/python-poetry/poetry/pull/1275)).
- Licenses can now be identified by their full name.
- Added support for custom certificate authority and client certificates for private repositories.
- Poetry can now detect and use Conda environments.
### Changed
- Slightly changed the lock file, making it potentially incompatible with previous Poetry versions.
- The `cache:clear` command has been renamed to `cache clear`.
- The `debug:info` command has been renamed to `debug info`.
- The `debug:resolve` command has been renamed to `debug resolve`.
- The `self:update` command has been renamed to `self update`.
- Changed the way virtualenvs are stored (names now depend on the project's path).
- The `--git` option of the `add` command has been removed.
- The `--path` option of the `add` command has been removed.
- The `add` command will now automatically select the latest prerelease if only prereleases are available.
- The `add` command can now update a dependencies if an explicit constraint is given ([#1221](https://github.com/python-poetry/poetry/pull/1221)).
- Removed the `--develop` option from the `install` command.
- Improved UX when searching for packages in the `init` command.
- The `shell` command has been improved.
- The `poetry run` command now uses `os.execvp()` rather than spawning a new subprocess.
- Specifying dependencies with `allows-prereleases` in the `pyproject.toml` file is deprecated for consistency with the `add` command. Use `allow-prereleases` instead.
- Improved the error message when the lock file is invalid.
- Whenever Poetry needs to use the "system" Python, it will now call `sys.executable` instead of the `python` command.
- Improved the error message displayed on conflicting Python requirements ([#1681](https://github.com/python-poetry/poetry/pull/1681)).
- Improved the `site-packages` directory detection ([#1683](https://github.com/python-poetry/poetry/pull/1683)).
### Fixed
- Fixed transitive extra dependencies being removed when updating a specific dependency.
- The `pyproject.toml` configuration is now properly validated.
- Fixed installing Poetry-based packages breaking with `pip`.
- Fixed packages with empty markers being added to the lock file.
- Fixed invalid lock file generation in some cases.
- Fixed local version identifier handling in wheel file names.
- Fixed packages with invalid metadata triggering an error instead of being skipped.
- Fixed the generation of invalid lock files in some cases.
- Git dependencies are now properly locked to a specific revision when specifying a branch or a tag.
- Fixed the behavior of the `~=` operator.
- Fixed dependency resolution for conditional development dependencies.
- Fixed generated dependency constraints when they contain inequality operators.
- The `run` command now properly handles the `--` separator.
- Fixed some issues with `path` dependencies being seen as `git` dependencies.
- Fixed various issues with the way `extra` markers in dependencies were handled.
- Fixed the option conflicts in the `run` command.
- Fixed wrong latest version being displayed when executing `show -l`.
- Fixed `TooManyRedirects` errors being raised when resolving dependencies.
- Fixed custom indices dependencies being constantly updated.
- Fixed the behavior of the `--install` option of the debug resolve command.
- Fixed an error in `show` when using the `-o/--outdated` option.
- Fixed PEP 508 url dependency handling.
- Fixed excluded files via the `exclude` being included in distributions.
- Fixed an error in `env use` if the `virtualenvs.in-project` setting is activated ([#1682](https://github.com/python-poetry/poetry/pull/1682))
- Fixed handling of `empty` and `any` markers in unions of markers ([#1650](https://github.com/python-poetry/poetry/pull/1650)).
## [0.12.17] - 2019-07-03
### Fixed
- Fixed dependency resolution with circular dependencies.
- Fixed encoding errors when reading files on Windows. (Thanks to [@vlcinsky](https://github.com/vlcinsky))
- Fixed unclear errors when executing commands in virtual environments. (Thanks to [@Imaclean74](https://github.com/Imaclean74))
- Fixed handling of `.venv` when it's not a directory. (Thanks to [@mpanarin](https://github.com/mpanarin))
## [0.12.16] - 2019-05-17
### Fixed
- Fixed packages with no hashes retrieval for legacy repositories.
- Fixed multiple constraints for dev dependencies.
- Fixed dependency resolution failing on badly formed package versions instead of skipping.
- Fixed permissions of built wheels.
## [0.12.15] - 2019-05-03
### Fixed
- Fixed an `AttributeError` in the editable builder.
- Fixed resolution of packages with only Python 3 wheels and sdist when resolving for legacy repositories.
- Fixed non-sha256 hashes retrieval for legacy repositories.
## [0.12.14] - 2019-04-26
### Fixed
- Fixed root package installation for pure Python packages.
## [0.12.13] - 2019-04-26
### Fixed
- Fixed root package installation with `pip>=19.0`.
- Fixed packages not being removed after using the `remove` command.
## [0.12.12] - 2019-04-11
### Fixed
- Fix lock idempotency.
- Fix markers evaluation for `python_version` with precision < 3.
- Fix permissions of the `dist-info` files.
- Fix `prepare_metadata_for_build_wheel()` missing in the build backend.
- Fix metadata inconsistency between wheels and sdists.
- Fix parsing of `platform_release` markers.
- Fix metadata information when the project has git dependencies.
- Fix error reporting when publishing fails.
- Fix retrieval of `extras_require` in some `setup.py` files. (Thanks to [@asodeur](https://github.com/asodeur))
- Fix wheel compression when building. (Thanks to [@ccosby](https://github.com/ccosby))
- Improve retrieval of information for packages with two python specific wheels.
- Fix request authentication when credentials are included in URLs. (Thanks to [@connorbrinton](https://github.com/connorbrinton))
## [0.12.11] - 2019-01-13
### Fixed
- Fixed the way packages information are retrieved for legacy repositories.
- Fixed an error when adding packages with invalid versions.
- Fixed an error when resolving directory dependencies with no sub dependencies.
- Fixed an error when locking packages with no description.
- Fixed path resolution for transitive file dependencies.
- Fixed multiple constraints handling for the root package.
- Fixed exclude functionality on case sensitive systems.
## [0.12.10] - 2018-11-22
### Fixed
- Fixed `run` not executing scripts.
- Fixed environment detection.
- Fixed handling of authentication for legacy repositories.
## [0.12.9] - 2018-11-19
### Fixed
- Fixed executables from outside the virtualenv not being accessible.
- Fixed a possible error when building distributions with the `exclude` option.
- Fixed the `run` command for namespaced packages.
- Fixed errors for virtualenvs with spaces in their path.
- Fixed prerelease versions being selected with the `add` command.
## [0.12.8] - 2018-11-13
### Fixed
- Fixed permission errors when adding/removing git dependencies on Windows.
- Fixed `Pool` not raising an exception when no package could be found.
- Fixed reading `bz2` source distribution.
- Fixed handling of arbitrary equals in `InstalledRepository`.
## [0.12.7] - 2018-11-08
### Fixed
- Fixed reading of some `setup.py` files.
- Fixed a `KeyError` when getting information for packages which require reading setup files.
- Fixed the building of wheels with C extensions and an `src` layout.
- Fixed extras being selected when resolving dependencies even when not required.
- Fixed performance issues when packaging projects if a lot of files were excluded.
- Fixed installation of files.
- Fixed extras not being retrieved for legacy repositories.
- Fixed invalid transitive constraints raising an error for legacy repositories.
## [0.12.6] - 2018-11-05
### Changed
- Poetry will now try to read, without executing, setup files (`setup.py` and/or `setup.cfg`) if the `egg_info` command fails when resolving dependencies.
### Fixed
- Fixed installation of directory dependencies.
- Fixed handling of dependencies with a `not in` marker operator.
- Fixed support for VCS dependencies.
- Fixed the `exclude` property not being respected if no VCS was available.
## [0.12.5] - 2018-10-26
### Fixed
- Fixed installation of Poetry git dependencies with a build system.
- Fixed possible errors when resolving dependencies for specific packages.
- Fixed handling of Python versions compatibility.
- Fixed the dependency resolver picking up unnecessary dependencies due to not using the `python_full_version` marker.
- Fixed the `Python-Requires` metadata being invalid for single Python versions.
## [0.12.4] - 2018-10-21
### Fixed
- Fixed possible error on some combinations of markers.
- Fixed venv detection so that it only uses `VIRTUAL_ENV` to detect activated virtualenvs.
## [0.12.3] - 2018-10-18
### Fixed
- Fixed the `--no-dev` option in `install` not working properly.
- Fixed prereleases being selected even if another constraint conflicted with them.
- Fixed an error when installing current package in development mode if the generated `setup.py` had special characters.
- Fixed an error in `install` for applications not following a known structure.
- Fixed an error when trying to retrieve the current environment.
- Fixed `debug:info` not showing the current project's virtualenv.
## [0.12.2] - 2018-10-17
### Fixed
- Fixed an error when installing from private repositories.
- Fixed an error when trying to move the lock file on Python 2.7.
## [0.12.1] - 2018-10-17
### Fixed
- Fixed an error when license is unspecified.
## [0.12.0] - 2018-10-17
### Added
- Added a brand new installer.
- Added support for multi-constraints dependencies.
- Added a cache version system.
- Added a `--lock` option to `update` to only update the lock file without executing operations. (Thanks to [@greysteil](https://github.com/greysteil))
- Added support for the `Project-URL` metadata.
- Added support for optional scripts.
- Added a `--no-dev` option to `show`. (Thanks to [@rodcloutier](https://github.com/rodcloutier))
### Changed
- Improved virtualenv detection and management.
- Wildcard `python` dependencies are now equivalent to `~2.7 || ^3.4`.
- Changed behavior of the resolver for conditional dependencies.
- The `install` command will now install the current project in editable mode.
- The `develop` command is now deprecated in favor of `install`.
- Improved the `check` command.
- Empty passwords are now supported when publishing.
### Fixed
- Fixed a memory leak in the resolver.
- Fixed a recursion error on duplicate dependencies with only different extras.
- Fixed handling of extras.
- Fixed duplicate entries in both sdist and wheel.
- Fixed excluded files appearing in the `package_data` of the generated `setup.py`.
- Fixed transitive directory dependencies installation.
- Fixed file permissions for configuration and authentication files.
- Fixed an error in `cache:clear` for Python 2.7.
- Fixed publishing for the first time with a prerelease.
## [0.11.5] - 2018-09-04
### Fixed
- Fixed a recursion error with circular dependencies.
- Fixed the `config` command setting incorrect values for paths.
- Fixed an `OSError` on Python >= 3.5 for `git` dependencies with recursive symlinks.
- Fixed the possible deletion of system paths by `cache:clear`.
- Fixed a performance issue when parsing the lock file by upgrading `tomlkit`.
## [0.11.4] - 2018-07-30
### Fixed
- Fixed wrong wheel being selected when resolving dependencies.
- Fixed an error when publishing.
- Fixed an error when building wheels with the `packages` property set.
- Fixed single value display in `config` command.
## [0.11.3] - 2018-07-26
### Changed
- Poetry now only uses [TOML Kit](https://github.com/sdispater/tomlkit) for TOML files manipulation.
- Improved dependency resolution debug information.
### Fixed
- Fixed missing dependency information for some packages.
- Fixed handling of single versions when packaging.
- Fixed dependency information retrieval from `.zip` and `.bz2` archives.
- Fixed searching for and installing packages from private repositories with authentication. (Thanks to [@MarcDufresne](https://github.com/MarcDufresne))
- Fixed a potential error when checking the `pyproject.toml` validity. (Thanks to [@ojii](https://github.com/ojii))
- Fixed the lock file not tracking the `extras` information from `pyproject.toml`. (Thanks to [@cauebs](https://github.com/cauebs))
- Fixed missing trailing slash in the Simple API urls for private repositories. (Thanks to [@bradsbrown](https://github.com/bradsbrown))
## [0.11.2] - 2018-07-03
### Fixed
- Fixed missing dependencies when resolving in some cases.
- Fixed path dependencies not working in `dev-dependencies`.
- Fixed license validation in `init`. (Thanks to [@cauebs](https://github.com/cauebs))
## [0.11.1] - 2018-06-29
### Fixed
- Fixed an error when locking dependencies on Python 2.7.
## [0.11.0] - 2018-06-28
### Added
- Added support for `packages`, `include` and `exclude` properties.
- Added a new `shell` command. (Thanks to [@cauebs](https://github.com/cauebs))
- Added license validation in `init` command.
### Changed
- Changed the dependency installation order, deepest dependencies are now installed first.
- Improved solver error messages.
- `poetry` now always reads/writes the `pyproject.toml` file with the `utf-8` encoding.
- `config --list` now lists all available settings.
- `init` no longer adds `pytest` to development dependencies.
### Fixed
- Fixed handling of duplicate dependencies with different constraints.
- Fixed system requirements in lock file for sub dependencies.
- Fixed detection of new prereleases.
- Fixed unsafe packages being locked.
- Fixed versions detection in custom repositories.
- Fixed package finding with multiple custom repositories.
- Fixed handling of root incompatibilities.
- Fixed an error where packages from custom repositories would not be found.
- Fixed wildcard Python requirement being wrongly set in distributions metadata.
- Fixed installation of packages from a custom repository.
- Fixed `remove` command's case sensitivity. (Thanks to [@cauebs](https://github.com/cauebs))
- Fixed detection of `.egg-info` directory for non-poetry projects. (Thanks to [@gtors](https://github.com/gtors))
- Fixed only-wheel builds. (Thanks to [@gtors](https://github.com/gtors))
- Fixed key and array order in lock file to avoid having differences when relocking.
- Fixed errors when `git` could not be found.
## [0.10.3] - 2018-06-04
### Fixed
- Fixed `self:update` command on Windows.
- Fixed `self:update` not picking up new versions.
- Fixed a `RuntimeError` on Python 3.7.
- Fixed bad version number being picked with private repositories.
- Fixed handling of duplicate dependencies with same constraint.
- Fixed installation from custom repositories.
- Fixed setting an explicit version in `version` command.
- Fixed parsing of wildcards version constraints.
## [0.10.2] - 2018-05-31
### Fixed
- Fixed handling of `in` environment markers with commas.
- Fixed a `UnicodeDecodeError` when an error occurs in venv.
- Fixed Python requirements not properly set when resolving dependencies.
- Fixed terminal coloring being activated even if not supported.
- Fixed wrong executable being picked up on Windows in `poetry run`.
- Fixed error when listing distribution links for private repositories.
- Fixed handling of PEP 440 `~=` version constraint.
## [0.10.1] - 2018-05-28
### Fixed
- Fixed packages not found for prerelease version constraints when resolving dependencies.
- Fixed `init` and `add` commands.
## [0.10.0] - 2018-05-28
### Added
- Added a new, more efficient dependency resolver.
- Added a new `init` command to generate a `pyproject.toml` file in existing projects.
- Added a new setting `settings.virtualenvs.in-project` to make `poetry` create the project's virtualenv inside the project's directory.
- Added the `--extras` and `--python` options to `debug:resolve` to help debug dependency resolution.
- Added a `--src` option to `new` command to create an `src` layout.
- Added support for specifying the `platform` for dependencies.
- Added the `--python` option to the `add` command.
- Added the `--platform` option to the `add` command.
- Added a `--develop` option to the install command to install path dependencies in development/editable mode.
- Added a `develop` command to install the current project in development mode.
### Changed
- Improved the `show` command to make it easier to check if packages are properly installed.
- The `script` command has been deprecated, use `run` instead.
- The `publish` command no longer build packages by default. Use `--build` to retrieve the previous behavior.
- Improved support for private repositories.
- Expanded version constraints now keep the original version's precision.
- The lock file hash no longer uses the project's name and version.
- The `LICENSE` file, or similar, is now automatically added to the built packages.
### Fixed
- Fixed the dependency resolver selecting incompatible packages.
- Fixed override of dependency with dependency with extras in `dev-dependencies`.
## [0.9.1] - 2018-05-18
### Fixed
- Fixed handling of package names with dots. (Thanks to [bertjwregeer](https://github.com/bertjwregeer))
- Fixed path dependencies being resolved from the current path instead of the `pyproject.toml` file. (Thanks to [radix](https://github.com/radix))
## [0.9.0] - 2018-05-07
### Added
- Added the `cache:clear` command.
- Added support for `git` dependencies in the `add` command.
- Added support for `path` dependencies in the `add` command.
- Added support for extras in the `add` command.
- Added support for directory dependencies.
- Added support for `src/` layout for packages.
- Added automatic detection of `.venv` virtualenvs.
### Changed
- Drastically improved dependency resolution speed.
- Dependency resolution caches now use sha256 hashes.
- Changed CLI error style.
- Improved debugging of dependency resolution.
- Poetry now attempts to find `pyproject.toml` not only in the directory it was
invoked in, but in all its parents up to the root. This allows to run Poetry
commands in project subdirectories.
- Made the email address for authors optional.
### Fixed
- Fixed handling of extras when resolving dependencies.
- Fixed `self:update` command for some installation.
- Fixed handling of extras when building projects.
- Fixed handling of wildcard dependencies wen packaging/publishing.
- Fixed an error when adding a new packages with prereleases in lock file.
- Fixed packages name normalization.
## [0.8.6] - 2018-04-30
### Fixed
- Fixed config files not being created.
## [0.8.5] - 2018-04-19
### Fixed
- Fixed a bug in dependency resolution which led to installation errors.
- Fixed a bug where malformed sdists would lead to dependency resolution failing.
## [0.8.4] - 2018-04-18
### Fixed
- Fixed a bug where dependencies constraints in lock were too strict.
- Fixed unicode error in `search` command for Python 2.7.
- Fixed error with git dependencies.
## [0.8.3] - 2018-04-16
### Fixed
- Fixed platform verification which led to missing packages.
- Fixed duplicates in `pyproject.lock`.
## [0.8.2] - 2018-04-14
### Fixed
- Fixed `add` command picking up prereleases by default.
- Fixed dependency resolution on Windows when unpacking distributions.
- Fixed dependency resolution with post releases.
- Fixed dependencies being installed even if not necessary for current system.
## [0.8.1] - 2018-04-13
### Fixed
- Fixed resolution with bad (empty) releases.
- Fixed `version` for prereleases.
- Fixed `search` not working outside of a project.
- Fixed `self:update` not working outside of a project.
## [0.8.0] - 2018-04-13
### Added
- Added support for Python 2.7.
- Added a fallback mechanism for missing dependencies.
- Added the `search` command.
- Added support for local files as dependencies.
- Added the `self:update` command.
### Changes
- Improved dependency resolution time by using cache control.
### Fixed
- Fixed `install_requires` and `extras` in generated sdist.
- Fixed dependency resolution crash with malformed dependencies.
- Fixed errors when `license` metadata is not set.
- Fixed missing information in lock file.
## [0.7.1] - 2018-04-05
### Fixed
- Fixed dependency resolution for custom repositories.
## [0.7.0] - 2018-04-04
### Added
- Added compatibility with Python 3.4 and 3.5.
- Added the `version` command to automatically bump the package's version.
- Added a standalone installer to install `poetry` isolated.
- Added support for classifiers in `pyproject.toml`.
- Added the `script` command.
### Changed
- Improved dependency resolution to avoid unnecessary operations.
- Improved dependency resolution speed.
- Improved CLI reactivity by deferring imports.
- License classifier is not automatically added to classifiers.
### Fixed
- Fixed handling of markers with the `in` operator.
- Fixed `update` not properly adding new packages to the lock file.
- Fixed solver adding uninstall operations for non-installed packages.
- Fixed `new` command creating invalid `pyproject.toml` files.
## [0.6.5] - 2018-03-22
### Fixed
- Fixed handling of extras in wheels metadata.
## [0.6.4] - 2018-03-21
### Added
- Added a `debug:info` command to get information about current environment.
### Fixed
- Fixed Python version retrieval inside virtualenvs.
- Fixed optional dependencies being set as required in sdist.
- Fixed `--optional` option in the `add` command not being used.
## [0.6.3] - 2018-03-20
### Fixed
- Fixed built wheels not getting information from the virtualenv.
- Fixed building wheel with conditional extensions.
- Fixed missing files in built wheel with extensions.
- Fixed call to venv binaries on windows.
- Fixed subdependencies representation in lock file.
## [0.6.2] - 2018-03-19
### Changed
- Changed how wildcard constraints are handled.
### Fixed
- Fixed errors with pip 9.0.2.
## [0.6.1] - 2018-02-18
### Fixed
- Fixed wheel entry points being written on a single line.
- Fixed wheel metadata (Tag and Root-Is-Purelib).
## [0.6.0] - 2018-03-16
### Added
- Added support for virtualenv autogeneration (Python 3.6+ only).
- Added the `run` command to execute commands inside the created virtualenvs.
- Added the `debug:resolve` command to debug dependency resolution.
- Added `pyproject.toml` file validation.
- Added support for Markdown readme files.
### Fixed
- Fixed color displayed in `show` command for semver-compatible updates.
- Fixed Python requirements in publishing metadata.
- Fixed `update` command reinstalling every dependency.
## [0.5.0] - 2018-03-14
### Added
- Added experimental support for package with C extensions.
### Changed
- Added hashes check when installing packages.
### Fixed
- Fixed handling of post releases.
- Fixed python restricted dependencies not being checked against virtualenv version.
- Fixed python/platform constraint not being picked up for subdependencies.
- Fixed skipped packages appearing as installing.
- Fixed platform specification not being used when resolving dependencies.
## [0.4.2] - 2018-03-10
### Fixed
- Fixed TypeError when `requires_dist` is null on PyPI.
## [0.4.1] - 2018-03-08
### Fixed
- Fixed missing entry point
## [0.4.0] - 2018-03-08
### Added
- Added packaging support (sdist and pure-python wheel).
- Added the `build` command.
- Added support for extras definition.
- Added support for dependencies extras specification.
- Added the `config` command.
- Added the `publish` command.
### Changed
- Dependencies system constraints are now respected when installing packages.
- Complied with PEP 440
### Fixed
- Fixed `show` command for VCS dependencies.
- Fixed handling of releases with bad markers in PyPiRepository.
## [0.3.0] - 2018-03-05
### Added
- Added `show` command.
- Added the `--dry-run` option to the `add` command.
### Changed
- Changed the `poetry.toml` file for the new, standardized `pyproject.toml`.
- Dependencies of each package is now stored in the lock file.
- Improved TOML file management.
- Dependency resolver now respects the root package python version requirements.
### Fixed
- Fixed the `add` command for packages with dots in their names.
## [0.2.0] - 2018-03-01
### Added
- Added `remove` command.
- Added basic support for VCS (git) dependencies.
- Added support for private repositories.
### Changed
- Changed `poetry.lock` format.
### Fixed
- Fixed dependencies solving that would lead to dependencies not being written to lock.
## [0.1.0] - 2018-02-28
Initial release
[Unreleased]: https://github.com/python-poetry/poetry/compare/2.3.2...main
[2.3.2]: https://github.com/python-poetry/poetry/releases/tag/2.3.2
[2.3.1]: https://github.com/python-poetry/poetry/releases/tag/2.3.1
[2.3.0]: https://github.com/python-poetry/poetry/releases/tag/2.3.0
[2.2.1]: https://github.com/python-poetry/poetry/releases/tag/2.2.1
[2.2.0]: https://github.com/python-poetry/poetry/releases/tag/2.2.0
[2.1.4]: https://github.com/python-poetry/poetry/releases/tag/2.1.4
[2.1.3]: https://github.com/python-poetry/poetry/releases/tag/2.1.3
[2.1.2]: https://github.com/python-poetry/poetry/releases/tag/2.1.2
[2.1.1]: https://github.com/python-poetry/poetry/releases/tag/2.1.1
[2.1.0]: https://github.com/python-poetry/poetry/releases/tag/2.1.0
[2.0.1]: https://github.com/python-poetry/poetry/releases/tag/2.0.1
[2.0.0]: https://github.com/python-poetry/poetry/releases/tag/2.0.0
[1.8.5]: https://github.com/python-poetry/poetry/releases/tag/1.8.5
[1.8.4]: https://github.com/python-poetry/poetry/releases/tag/1.8.4
[1.8.3]: https://github.com/python-poetry/poetry/releases/tag/1.8.3
[1.8.2]: https://github.com/python-poetry/poetry/releases/tag/1.8.2
[1.8.1]: https://github.com/python-poetry/poetry/releases/tag/1.8.1
[1.8.0]: https://github.com/python-poetry/poetry/releases/tag/1.8.0
[1.7.1]: https://github.com/python-poetry/poetry/releases/tag/1.7.1
[1.7.0]: https://github.com/python-poetry/poetry/releases/tag/1.7.0
[1.6.1]: https://github.com/python-poetry/poetry/releases/tag/1.6.1
[1.6.0]: https://github.com/python-poetry/poetry/releases/tag/1.6.0
[1.5.1]: https://github.com/python-poetry/poetry/releases/tag/1.5.1
[1.5.0]: https://github.com/python-poetry/poetry/releases/tag/1.5.0
[1.4.2]: https://github.com/python-poetry/poetry/releases/tag/1.4.2
[1.4.1]: https://github.com/python-poetry/poetry/releases/tag/1.4.1
[1.4.0]: https://github.com/python-poetry/poetry/releases/tag/1.4.0
[1.3.2]: https://github.com/python-poetry/poetry/releases/tag/1.3.2
[1.3.1]: https://github.com/python-poetry/poetry/releases/tag/1.3.1
[1.3.0]: https://github.com/python-poetry/poetry/releases/tag/1.3.0
[1.2.2]: https://github.com/python-poetry/poetry/releases/tag/1.2.2
[1.2.1]: https://github.com/python-poetry/poetry/releases/tag/1.2.1
[1.2.0]: https://github.com/python-poetry/poetry/releases/tag/1.2.0
[1.2.0rc2]: https://github.com/python-poetry/poetry/releases/tag/1.2.0rc2
[1.2.0rc1]: https://github.com/python-poetry/poetry/releases/tag/1.2.0rc1
[1.2.0b3]: https://github.com/python-poetry/poetry/releases/tag/1.2.0b3
[1.2.0b2]: https://github.com/python-poetry/poetry/releases/tag/1.2.0b2
[1.2.0b1]: https://github.com/python-poetry/poetry/releases/tag/1.2.0b1
[1.2.0a2]: https://github.com/python-poetry/poetry/releases/tag/1.2.0a2
[1.2.0a1]: https://github.com/python-poetry/poetry/releases/tag/1.2.0a1
[1.1.15]: https://github.com/python-poetry/poetry/releases/tag/1.1.15
[1.1.14]: https://github.com/python-poetry/poetry/releases/tag/1.1.14
[1.1.13]: https://github.com/python-poetry/poetry/releases/tag/1.1.13
[1.1.12]: https://github.com/python-poetry/poetry/releases/tag/1.1.12
[1.1.11]: https://github.com/python-poetry/poetry/releases/tag/1.1.11
[1.1.10]: https://github.com/python-poetry/poetry/releases/tag/1.1.10
[1.1.9]: https://github.com/python-poetry/poetry/releases/tag/1.1.9
[1.1.8]: https://github.com/python-poetry/poetry/releases/tag/1.1.8
[1.1.7]: https://github.com/python-poetry/poetry/releases/tag/1.1.7
[1.1.6]: https://github.com/python-poetry/poetry/releases/tag/1.1.6
[1.1.5]: https://github.com/python-poetry/poetry/releases/tag/1.1.5
[1.1.4]: https://github.com/python-poetry/poetry/releases/tag/1.1.4
[1.1.3]: https://github.com/python-poetry/poetry/releases/tag/1.1.3
[1.1.2]: https://github.com/python-poetry/poetry/releases/tag/1.1.2
[1.1.1]: https://github.com/python-poetry/poetry/releases/tag/1.1.1
[1.1.0]: https://github.com/python-poetry/poetry/releases/tag/1.1.0
[1.1.0rc1]: https://github.com/python-poetry/poetry/releases/tag/1.1.0rc1
[1.1.0b4]: https://github.com/python-poetry/poetry/releases/tag/1.1.0b4
[1.1.0b3]: https://github.com/python-poetry/poetry/releases/tag/1.1.0b3
[1.1.0b2]: https://github.com/python-poetry/poetry/releases/tag/1.1.0b2
[1.1.0b1]: https://github.com/python-poetry/poetry/releases/tag/1.1.0b1
[1.1.0a3]: https://github.com/python-poetry/poetry/releases/tag/1.1.0a3
[1.1.0a2]: https://github.com/python-poetry/poetry/releases/tag/1.1.0a2
[1.1.0a1]: https://github.com/python-poetry/poetry/releases/tag/1.1.0a1
[1.0.10]: https://github.com/python-poetry/poetry/releases/tag/1.0.10
[1.0.9]: https://github.com/python-poetry/poetry/releases/tag/1.0.9
[1.0.8]: https://github.com/python-poetry/poetry/releases/tag/1.0.8
[1.0.7]: https://github.com/python-poetry/poetry/releases/tag/1.0.7
[1.0.6]: https://github.com/python-poetry/poetry/releases/tag/1.0.6
[1.0.5]: https://github.com/python-poetry/poetry/releases/tag/1.0.5
[1.0.4]: https://github.com/python-poetry/poetry/releases/tag/1.0.4
[1.0.3]: https://github.com/python-poetry/poetry/releases/tag/1.0.3
[1.0.2]: https://github.com/python-poetry/poetry/releases/tag/1.0.2
[1.0.1]: https://github.com/python-poetry/poetry/releases/tag/1.0.1
[1.0.0]: https://github.com/python-poetry/poetry/releases/tag/1.0.0
[0.12.17]: https://github.com/python-poetry/poetry/releases/tag/0.12.17
[0.12.16]: https://github.com/python-poetry/poetry/releases/tag/0.12.16
[0.12.15]: https://github.com/python-poetry/poetry/releases/tag/0.12.15
[0.12.14]: https://github.com/python-poetry/poetry/releases/tag/0.12.14
[0.12.13]: https://github.com/python-poetry/poetry/releases/tag/0.12.13
[0.12.12]: https://github.com/python-poetry/poetry/releases/tag/0.12.12
[0.12.11]: https://github.com/python-poetry/poetry/releases/tag/0.12.11
[0.12.10]: https://github.com/python-poetry/poetry/releases/tag/0.12.10
[0.12.9]: https://github.com/python-poetry/poetry/releases/tag/0.12.9
[0.12.8]: https://github.com/python-poetry/poetry/releases/tag/0.12.8
[0.12.7]: https://github.com/python-poetry/poetry/releases/tag/0.12.7
[0.12.6]: https://github.com/python-poetry/poetry/releases/tag/0.12.6
[0.12.5]: https://github.com/python-poetry/poetry/releases/tag/0.12.5
[0.12.4]: https://github.com/python-poetry/poetry/releases/tag/0.12.4
[0.12.3]: https://github.com/python-poetry/poetry/releases/tag/0.12.3
[0.12.2]: https://github.com/python-poetry/poetry/releases/tag/0.12.2
[0.12.1]: https://github.com/python-poetry/poetry/releases/tag/0.12.1
[0.12.0]: https://github.com/python-poetry/poetry/releases/tag/0.12.0
[0.11.5]: https://github.com/python-poetry/poetry/releases/tag/0.11.5
[0.11.4]: https://github.com/python-poetry/poetry/releases/tag/0.11.4
[0.11.3]: https://github.com/python-poetry/poetry/releases/tag/0.11.3
[0.11.2]: https://github.com/python-poetry/poetry/releases/tag/0.11.2
[0.11.1]: https://github.com/python-poetry/poetry/releases/tag/0.11.1
[0.11.0]: https://github.com/python-poetry/poetry/releases/tag/0.11.0
[0.10.3]: https://github.com/python-poetry/poetry/releases/tag/0.10.3
[0.10.2]: https://github.com/python-poetry/poetry/releases/tag/0.10.2
[0.10.1]: https://github.com/python-poetry/poetry/releases/tag/0.10.1
[0.10.0]: https://github.com/python-poetry/poetry/releases/tag/0.10.0
[0.9.1]: https://github.com/python-poetry/poetry/releases/tag/0.9.1
[0.9.0]: https://github.com/python-poetry/poetry/releases/tag/0.9.0
[0.8.6]: https://github.com/python-poetry/poetry/releases/tag/0.8.6
[0.8.5]: https://github.com/python-poetry/poetry/releases/tag/0.8.5
[0.8.4]: https://github.com/python-poetry/poetry/releases/tag/0.8.4
[0.8.3]: https://github.com/python-poetry/poetry/releases/tag/0.8.3
[0.8.2]: https://github.com/python-poetry/poetry/releases/tag/0.8.2
[0.8.1]: https://github.com/python-poetry/poetry/releases/tag/0.8.1
[0.8.0]: https://github.com/python-poetry/poetry/releases/tag/0.8.0
[0.7.1]: https://github.com/python-poetry/poetry/releases/tag/0.7.1
[0.7.0]: https://github.com/python-poetry/poetry/releases/tag/0.7.0
[0.6.5]: https://github.com/python-poetry/poetry/releases/tag/0.6.5
[0.6.4]: https://github.com/python-poetry/poetry/releases/tag/0.6.4
[0.6.3]: https://github.com/python-poetry/poetry/releases/tag/0.6.3
[0.6.2]: https://github.com/python-poetry/poetry/releases/tag/0.6.2
[0.6.1]: https://github.com/python-poetry/poetry/releases/tag/0.6.1
[0.6.0]: https://github.com/python-poetry/poetry/releases/tag/0.6.0
[0.5.0]: https://github.com/python-poetry/poetry/releases/tag/0.5.0
[0.4.2]: https://github.com/python-poetry/poetry/releases/tag/0.4.2
[0.4.1]: https://github.com/python-poetry/poetry/releases/tag/0.4.1
[0.4.0]: https://github.com/python-poetry/poetry/releases/tag/0.4.0
[0.3.0]: https://github.com/python-poetry/poetry/releases/tag/0.3.0
[0.2.0]: https://github.com/python-poetry/poetry/releases/tag/0.2.0
[0.1.0]: https://github.com/python-poetry/poetry/releases/tag/0.1.0
================================================
FILE: CITATION.cff
================================================
cff-version: 1.2.0
title: "Poetry: Python packaging and dependency management made easy"
message: >-
If you use this software, please cite it using the
metadata from this file.
authors:
- family-names: Eustace
given-names: Sébastien
- name: "The Poetry contributors"
abstract: >-
Poetry helps you declare, manage and install dependencies of Python projects, ensuring you have the right stack everywhere.
Poetry replaces setup.py, requirements.txt, setup.cfg, MANIFEST.in and Pipfile with a simple pyproject.toml based project format.
license: MIT
license-url: "https://github.com/python-poetry/poetry/blob/main/LICENSE"
repository-code: "https://github.com/python-poetry/poetry"
keywords:
- python
- packaging
- dependency management
type: software
url: "https://python-poetry.org"
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at sebastien@eustace.io. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: LICENSE
================================================
Copyright (c) 2018-present Sébastien Eustace
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# Poetry: Python packaging and dependency management made easy
[](https://python-poetry.org/)
[][PyPI Releases]
[][PyPI Releases]
[][PyPI]
[](https://pypistats.org/packages/poetry)
[][Discord]
Poetry helps you declare, manage and install dependencies of Python projects,
ensuring you have the right stack everywhere.

Poetry replaces `setup.py`, `requirements.txt`, `setup.cfg`, `MANIFEST.in` and `Pipfile` with a simple `pyproject.toml`
based project format.
```toml
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
[project]
name = "my-package"
version = "0.1.0"
description = "The description of the package"
readme = "README.md"
license = "MIT"
license-files = ["LICENSE"]
# No Python upper bound for package metadata
requires-python = ">=3.9"
authors = [
{ name = "Sébastien Eustace", email = "sebastien@eustace.io" },
]
# Keywords (translated to tags on the package index)
keywords = ["packaging", "poetry"]
dependencies = [
# equivalent to ^3.8.1 with semver constraints
"aiohttp (>=3.8.1,<4.0.0)",
# dependency with extras
"requests[security] (>=2.28,<3.0)",
# version-specific dependency with prereleases allowed (see below)
"tomli (>=2.0.1,<3.0.0) ; python_version < '3.11'",
# git dependency with branch specified
"cleo @ git+https://github.com/python-poetry/cleo.git@main",
]
[project.urls]
repository = "https://github.com/python-poetry/poetry"
homepage = "https://python-poetry.org"
# Scripts are easily expressed
[project.scripts]
my_package_cli = "my_package.console:run"
[project.optional-dependencies]
# optional dependency to be installed via 'poetry install -E my-extra'
my-extra = ["pendulum (>=3.1.0,<4.0.0)"]
[tool.poetry.dependencies]
# Python upper bound for locking
python = ">=3.9,<4.0"
# Version-specific dependencies with prereleases allowed
tomli = { allow-prereleases = true }
# Dependency groups are supported for organizing your dependencies
[dependency-groups]
dev = ["pytest (>=7.1.2,<8.0.0)", "pytest-cov (>=3.0,<4.0)"]
docs = ["Sphinx (>=5.1.1,<6.0.0)"]
# ...and can be installed only when explicitly requested
# via 'poetry install --with docs'
[tool.poetry.group.docs]
optional = true
# Alternatively, you can use Poetry specific syntax
# to specify dependency groups
[tool.poetry.group.lint]
optional = true
[tool.poetry.group.lint.dependencies]
ruff = ">=0.10.0"
```
## Installation
Poetry supports multiple installation methods, including a simple script found at [install.python-poetry.org]. For full
installation instructions, including advanced usage of the script, alternate install methods, and CI best practices, see
the full [installation documentation].
## Documentation
[Documentation] for the current version of Poetry (as well as the development branch and recently out of support
versions) is available from the [official website].
## Contribute
Poetry is a large, complex project always in need of contributors. For those new to the project, a list of
[suggested issues] to work on in Poetry and poetry-core is available. The full [contributing documentation] also
provides helpful guidance.
## Resources
* [Releases][PyPI Releases]
* [Official Website]
* [Documentation]
* [Issue Tracker]
* [Discord]
[PyPI]: https://pypi.org/project/poetry/
[PyPI Releases]: https://pypi.org/project/poetry/#history
[Official Website]: https://python-poetry.org
[Documentation]: https://python-poetry.org/docs/
[Issue Tracker]: https://github.com/python-poetry/poetry/issues
[Suggested Issues]: https://github.com/python-poetry/poetry/contribute
[Contributing Documentation]: https://python-poetry.org/docs/contributing
[Discord]: https://discord.com/invite/awxPgve
[install.python-poetry.org]: https://install.python-poetry.org
[Installation Documentation]: https://python-poetry.org/docs/#installation
## Related Projects
* [poetry-core](https://github.com/python-poetry/poetry-core): PEP 517 build-system for Poetry projects, and
dependency-free core functionality of the Poetry frontend
* [poetry-plugin-export](https://github.com/python-poetry/poetry-plugin-export): Export Poetry projects/lock files to
foreign formats like requirements.txt
* [poetry-plugin-bundle](https://github.com/python-poetry/poetry-plugin-bundle): Install Poetry projects/lock files to
external formats like virtual environments
* [install.python-poetry.org](https://github.com/python-poetry/install.python-poetry.org): The official Poetry
installation script
* [website](https://github.com/python-poetry/website): The official Poetry website and blog
## Supporters
Thanks to [JetBrains](https://www.jetbrains.com) for supporting us with licenses for their tools.
[
](https://www.jetbrains.com)
================================================
FILE: docs/_index.md
================================================
---
title: "Introduction"
draft: false
type: docs
layout: "single"
menu:
docs:
weight: 0
---
# Introduction
Poetry is a tool for **dependency management** and **packaging** in Python.
It allows you to declare the libraries your project depends on and it will manage (install/update) them for you.
Poetry offers a lockfile to ensure repeatable installs, and can build your project for distribution.
## System requirements
Poetry requires **Python 3.10+**. It is multi-platform and the goal is to make it work equally well
on Linux, macOS and Windows.
## Installation
{{% note %}}
If you are viewing documentation for the development branch, you may wish to install a preview or development version of Poetry.
See the **advanced** installation instructions to use a preview or alternate version of Poetry.
{{% /note %}}
{{< tabs tabTotal="4" tabID1="installing-with-pipx" tabID2="installing-with-the-official-installer" tabID3="installing-manually" tabID4="ci-recommendations" tabName1="With pipx" tabName2="With the official installer" tabName3="Manually (advanced)" tabName4="CI recommendations">}}
{{< tab tabID="installing-with-pipx" >}}
[`pipx`](https://github.com/pypa/pipx) is used to install Python CLI applications globally while still isolating them in virtual environments.
`pipx` will manage upgrades and uninstalls when used to install Poetry.
{{< steps >}}
{{< step >}}
**Install pipx**
If `pipx` is not already installed, you can follow any of the options in the
[official pipx installation instructions](https://pipx.pypa.io/stable/installation/).
Any non-ancient version of `pipx` will do.
{{< /step >}}
{{< step >}}
**Install Poetry**
```bash
pipx install poetry
```
{{< /step >}}
{{< step >}}
**Install Poetry (advanced)**
{{% note %}}
You can skip this step, if you simply want the latest version and already installed Poetry as described in the
previous step. This step details advanced usages of this installation method. For example, installing Poetry from
source, having multiple versions installed at the same time etc.
{{% /note %}}
`pipx` can install different versions of Poetry, using the same syntax as pip:
```bash
pipx install poetry==1.8.4
```
`pipx` can also install versions of Poetry in parallel, which allows for easy testing of alternate or prerelease
versions. Each version is given a unique, user-specified suffix, which will be used to create a unique binary name:
```bash
pipx install --suffix=@1.8.4 poetry==1.8.4
poetry@1.8.4 --version
```
```bash
pipx install --suffix=@preview --pip-args=--pre poetry
poetry@preview --version
```
Finally, `pipx` can install any valid [pip requirement spec](https://pip.pypa.io/en/stable/cli/pip_install/), which
allows for installations of the development version from `git`, or even for local testing of pull requests:
```bash
pipx install --suffix @main git+https://github.com/python-poetry/poetry.git@main
pipx install --suffix @pr1234 git+https://github.com/python-poetry/poetry.git@refs/pull/1234/head
```
{{< /step >}}
{{< step >}}
**Update Poetry**
```bash
pipx upgrade poetry
```
{{< /step >}}
{{< step >}}
**Uninstall Poetry**
```bash
pipx uninstall poetry
```
{{< /step >}}
{{< /steps >}}
{{< /tab >}}
{{< tab tabID="installing-with-the-official-installer" >}}
We provide a custom installer that will install Poetry in a new virtual environment
and allows Poetry to manage its own environment.
{{< steps >}}
{{< step >}}
**Install Poetry**
The installer script is available directly at [install.python-poetry.org](https://install.python-poetry.org),
and is developed in [its own repository](https://github.com/python-poetry/install.python-poetry.org).
The script can be executed directly (i.e. 'curl python') or downloaded and then executed from disk
(e.g. in a CI environment).
**Linux, macOS, Windows (WSL)**
```bash
curl -sSL https://install.python-poetry.org | python3 -
```
**Windows (Powershell)**
```powershell
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py -
```
{{% note %}}
If you have installed Python through the Microsoft Store, replace `py` with `python` in the command
above.
{{% /note %}}
{{< /step >}}
{{< step >}}
**Install Poetry (advanced)**
{{% note %}}
You can skip this step, if you simply want the latest version and already installed Poetry as described in the
previous step. This step details advanced usages of this installation method. For example, installing Poetry from
source, using a pre-release build, configuring a different installation location etc.
{{% /note %}}
By default, Poetry is installed into a platform and user-specific directory:
- `~/Library/Application Support/pypoetry` on macOS.
- `~/.local/share/pypoetry` on Linux/Unix.
- `%APPDATA%\pypoetry` on Windows.
If you wish to change this, you may define the `$POETRY_HOME` environment variable:
```bash
curl -sSL https://install.python-poetry.org | POETRY_HOME=/etc/poetry python3 -
```
If you want to install prerelease versions, you can do so by passing the `--preview` option to the installation script
or by using the `$POETRY_PREVIEW` environment variable:
```bash
curl -sSL https://install.python-poetry.org | python3 - --preview
curl -sSL https://install.python-poetry.org | POETRY_PREVIEW=1 python3 -
```
Similarly, if you want to install a specific version, you can use `--version` option or the `$POETRY_VERSION`
environment variable:
```bash
curl -sSL https://install.python-poetry.org | python3 - --version 1.8.4
curl -sSL https://install.python-poetry.org | POETRY_VERSION=1.8.4 python3 -
```
You can also install Poetry from a `git` repository by using the `--git` option:
```bash
curl -sSL https://install.python-poetry.org | python3 - --git https://github.com/python-poetry/poetry.git@main
````
If you want to install different versions of Poetry in parallel, a good approach is the installation with pipx and suffix.
{{< /step >}}
{{< step >}}
**Add Poetry to your PATH**
The installer creates a `poetry` wrapper in a well-known, platform-specific directory:
- `$HOME/.local/bin` on Unix.
- `%APPDATA%\Python\Scripts` on Windows.
- `$POETRY_HOME/bin` if `$POETRY_HOME` is set.
{{% note %}}
If you have installed Python through the Microsoft Store, the `poetry` binary
will be installed to a different location, for example:
```
%LOCALAPPDATA%\Packages\PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0
\LocalCache\Roaming\Python\Scripts
```
Replace `3.12` with your installed Python version and `qbz5n2kfra8p0` with your suffix.
{{% /note %}}
If this directory is not present in your `$PATH`, you can add it in order to invoke Poetry
as `poetry`.
Alternatively, the full path to the `poetry` binary can always be used:
- `~/Library/Application Support/pypoetry/venv/bin/poetry` on macOS.
- `~/.local/share/pypoetry/venv/bin/poetry` on Linux/Unix.
- `%APPDATA%\pypoetry\venv\Scripts\poetry` on Windows.
- `$POETRY_HOME/venv/bin/poetry` if `$POETRY_HOME` is set.
{{< /step >}}
{{< step >}}
**Use Poetry**
Once Poetry is installed and in your `$PATH`, you can execute the following:
```bash
poetry --version
```
If you see something like `Poetry (version 2.0.0)`, your installation is ready to use!
{{< /step >}}
{{< step >}}
**Update Poetry**
Poetry is able to update itself when installed using the official installer.
{{% warning %}}
Especially on Windows, `self update` may be problematic
so that a re-install with the installer should be preferred.
{{% /warning %}}
```bash
poetry self update
```
If you want to install pre-release versions, you can use the `--preview` option.
```bash
poetry self update --preview
```
And finally, if you want to install a specific version, you can pass it as an argument
to `self update`.
```bash
poetry self update 1.8.4
```
{{< /step >}}
{{< step >}}
**Uninstall Poetry**
If you decide Poetry isn't your thing, you can completely remove it from your system
by running the installer again with the `--uninstall` option or by setting
the `POETRY_UNINSTALL` environment variable before executing the installer.
```bash
curl -sSL https://install.python-poetry.org | python3 - --uninstall
curl -sSL https://install.python-poetry.org | POETRY_UNINSTALL=1 python3 -
```
{{< /step >}}
{{< /steps >}}
{{< /tab >}}
{{< tab tabID="installing-manually" >}}
Poetry can be installed manually using `pip` and the `venv` module. By doing so you will essentially perform the steps carried
out by the official installer. As this is an advanced installation method, these instructions are Unix-only and omit specific
examples such as installing from `git`.
The variable `$VENV_PATH` will be used to indicate the path at which the virtual environment was created.
```bash
python3 -m venv $VENV_PATH
$VENV_PATH/bin/pip install -U pip setuptools
$VENV_PATH/bin/pip install poetry
```
Poetry will be available at `$VENV_PATH/bin/poetry` and can be invoked directly or symlinked elsewhere.
To uninstall Poetry, simply delete the entire `$VENV_PATH` directory.
{{< /tab >}}
{{< tab tabID="ci-recommendations" >}}
Unlike development environments, where making use of the latest tools is desirable, in a CI environment reproducibility
should be made the priority. Here are some suggestions for installing Poetry in such an environment.
**Version pinning**
Whatever method you use, it is highly recommended to explicitly control the version of Poetry used, so that you are able
to upgrade after performing your own validation. Each install method has a different syntax for setting the version that
is used in the following examples.
**Using pipx**
Just as `pipx` is a powerful tool for development use, it is equally useful in a CI environment
and should be one of your top choices for use of Poetry in CI.
```bash
pipx install poetry==2.0.0
```
**Using install.python-poetry.org**
{{% note %}}
The official installer script ([install.python-poetry.org](https://install.python-poetry.org)) offers a streamlined and
simplified installation of Poetry, sufficient for developer use or for simple pipelines. However, in a CI environment
the other two supported installation methods (pipx and manual) should be seriously considered.
{{% /note %}}
Downloading a copy of the installer script to a place accessible by your CI pipelines (or maintaining a copy of the
[repository](https://github.com/python-poetry/install.python-poetry.org)) is strongly suggested, to ensure your
pipeline's stability and to maintain control over what code is executed.
By default, the installer will install to a user-specific directory. In more complex pipelines that may make accessing
Poetry difficult (especially in cases like multi-stage container builds). It is highly suggested to make use of
`$POETRY_HOME` when using the official installer in CI, as that way the exact paths can be controlled.
```bash
export POETRY_HOME=/opt/poetry
python3 install-poetry.py --version 2.0.0
$POETRY_HOME/bin/poetry --version
```
**Using pip (aka manually)**
For maximum control in your CI environment, installation with `pip` is fully supported and something you should
consider. While this requires more explicit commands and knowledge of Python packaging from you, it in return offers the
best debugging experience, and leaves you subject to the fewest external tools.
```bash
export POETRY_HOME=/opt/poetry
python3 -m venv $POETRY_HOME
$POETRY_HOME/bin/pip install poetry==2.0.0
$POETRY_HOME/bin/poetry --version
```
{{% note %}}
If you install Poetry via `pip`, ensure you have Poetry installed into an isolated environment that is **not the same**
as the target environment managed by Poetry. If Poetry and your project are installed into the same environment, Poetry
is likely to upgrade or uninstall its own dependencies (causing hard-to-debug and understand errors).
{{% /note %}}
{{< /tab >}}
{{< /tabs >}}
{{% warning %}}
Poetry should always be installed in a dedicated virtual environment to isolate it from the rest of your system.
Each of the above described installation methods ensures that.
It should in no case be installed in the environment of the project that is to be managed by Poetry.
This ensures that Poetry's own dependencies will not be accidentally upgraded or uninstalled.
In addition, the isolated virtual environment in which poetry is installed should not be activated for running poetry commands.
{{% /warning %}}
## Enable tab completion for Bash, Fish, or Zsh
`poetry` supports generating completion scripts for Bash, Fish, and Zsh.
{{% note %}}
You may need to restart your shell in order for these changes to take effect.
{{% /note %}}
See `poetry help completions` for full details, but the gist is as simple as using one of the following:
### Bash
#### Auto-loaded (recommended)
```bash
poetry completions bash >> ~/.bash_completion
```
#### Lazy-loaded
```bash
poetry completions bash > ${XDG_DATA_HOME:-~/.local/share}/bash-completion/completions/poetry
```
### Fish
```fish
poetry completions fish > ~/.config/fish/completions/poetry.fish
```
### Zsh
```zsh
poetry completions zsh > ~/.zfunc/_poetry
```
You must then add the following lines in your `~/.zshrc`, if they do not already exist:
```bash
fpath+=~/.zfunc
autoload -Uz compinit && compinit
```
#### Oh My Zsh
```zsh
mkdir $ZSH_CUSTOM/plugins/poetry
poetry completions zsh > $ZSH_CUSTOM/plugins/poetry/_poetry
```
You must then add `poetry` to your plugins array in `~/.zshrc`:
```text
plugins(
poetry
...
)
```
#### Prezto
```zsh
poetry completions zsh > ~/.zprezto/modules/completion/external/src/_poetry
```
If completions still don't work, try removing `~/.cache/prezto/zcompcache` and starting a new shell.
================================================
FILE: docs/basic-usage.md
================================================
---
title: "Basic usage"
draft: false
type: docs
layout: single
menu:
docs:
weight: 10
---
# Basic usage
For the basic usage introduction we will be installing `pendulum`, a datetime library.
If you have not yet installed Poetry, refer to the [Introduction]({{< relref "docs" >}} "Introduction") chapter.
## Project setup
First, let's create our new project, let's call it `poetry-demo`:
```bash
poetry new poetry-demo
```
This will create the `poetry-demo` directory with the following content:
```text
poetry-demo
├── pyproject.toml
├── README.md
├── src
│ └── poetry_demo
│ └── __init__.py
└── tests
└── __init__.py
```
The `pyproject.toml` file is what is the most important here. This will orchestrate
your project and its dependencies. For now, it looks like this:
```toml
[project]
name = "poetry-demo"
version = "0.1.0"
description = ""
authors = [
{name = "Sébastien Eustace", email = "sebastien@eustace.io"}
]
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
```
Poetry assumes your package contains a package with the same name as `project.name` located in the root of your
project. If this is not the case, populate [`tool.poetry.packages`]({{< relref "pyproject#packages" >}}) to specify
your packages and their locations.
Similarly, the traditional `MANIFEST.in` file is replaced by the `project.readme`, `tool.poetry.include`, and
`tool.poetry.exclude` sections. `tool.poetry.exclude` is additionally implicitly populated by your `.gitignore`. For
full documentation on the project format, see the [pyproject section]({{< relref "pyproject" >}}) of the documentation.
### Setting a Python Version
{{% note %}}
Unlike with other packages, Poetry will not automatically install a python interpreter for you.
If you want to run Python files in your package like a script or application, you must _bring your own_ python interpreter to run them.
{{% /note %}}
Poetry will require you to explicitly specify what versions of Python you intend to support, and its universal locking
will guarantee that your project is installable (and all dependencies claim support for) all supported Python versions.
Again, it's important to remember that -- unlike other dependencies -- setting a Python version is merely specifying which versions of Python you intend to support.
For example, in this `pyproject.toml` file:
```toml
[project]
requires-python = ">=3.9"
```
we are allowing any version of Python 3 that is greater or equal than `3.9.0`.
When you run `poetry install`, you must have access to some version of a Python interpreter that satisfies this constraint available on your system.
Poetry will not install a Python interpreter for you.
### Initialising a pre-existing project
Instead of creating a new project, Poetry can be used to 'initialize' a pre-populated
directory. To interactively create a `pyproject.toml` file in directory `pre-existing-project`:
```bash
cd pre-existing-project
poetry init
```
### Operating modes
Poetry can be operated in two different modes. The default mode is the **package mode**, which is the right mode
if you want to package your project into an sdist or a wheel and perhaps publish it to a package index.
In this mode, some metadata such as `name` and `version`, which are required for packaging, are mandatory.
Further, the project itself will be installed in editable mode when running `poetry install`.
If you want to use Poetry only for dependency management but not for packaging, you can use the **non-package mode**:
```toml
[tool.poetry]
package-mode = false
```
In this mode, metadata such as `name` and `version` are optional.
Therefore, it is not possible to build a distribution or publish the project to a package index.
Further, when running `poetry install`, Poetry does not try to install the project itself,
but only its dependencies (same as `poetry install --no-root`).
{{% note %}}
In the [pyproject section]({{< relref "pyproject" >}}) you can see which fields are required in package mode.
{{% /note %}}
### Specifying dependencies
If you want to add dependencies to your project, you can specify them in the
`project` section.
```toml
[project]
# ...
dependencies = [
"pendulum (>=2.1,<3.0)"
]
```
As you can see, it takes a mapping of **package names** and **version constraints**.
Poetry uses this information to search for the right set of files in package "repositories" that you register
in the `tool.poetry.source` section, or on [PyPI](https://pypi.org) by default.
Also, instead of modifying the `pyproject.toml` file by hand, you can use the `add` command.
```bash
$ poetry add pendulum
```
It will automatically find a suitable version constraint **and install** the package and sub-dependencies.
Poetry supports a rich [dependency specification]({{< relref "dependency-specification" >}}) syntax, including caret,
tilde, wildcard, inequality and
[multiple constraints]({{< relref "dependency-specification#multiple-constraints-dependencies" >}}) requirements.
## Using your virtual environment
By default, Poetry creates a virtual environment in `{cache-dir}/virtualenvs`.
You can change the [`cache-dir`]({{< relref "configuration#cache-dir" >}} "cache-dir configuration documentation") value
by editing the Poetry configuration.
Additionally, you can use the
[`virtualenvs.in-project`]({{< relref "configuration#virtualenvsin-project" >}}) configuration variable to create
virtual environments within your project directory.
There are several ways to run commands within this virtual environment.
{{% note %}}
**External virtual environment management**
Poetry will detect and respect an existing virtual environment that has been externally activated. This is a powerful
mechanism that is intended to be an alternative to Poetry's built-in, simplified environment management.
To take advantage of this, simply activate a virtual environment using your preferred method or tooling, before running
any Poetry commands that expect to manipulate an environment.
{{% /note %}}
### Using `poetry run`
To run your script simply use `poetry run python your_script.py`.
Likewise if you have command line tools such as `pytest` or `black` you can run them using `poetry run pytest`.
{{% note %}}
If managing your own virtual environment externally, you do not need to use `poetry run` since
you will, presumably, already have activated that virtual environment and made available the correct python instance.
For example, these commands should output the same python path:
```shell
conda activate your_env_name
which python
poetry run which python
eval "$(poetry env activate)"
which python
```
{{% /note %}}
### Activating the virtual environment
See [Activating the virtual environment]({{< relref "managing-environments#activating-the-environment" >}}).
## Version constraints
In our example, we are requesting the `pendulum` package with the version constraint `>=2.1.0 <3.0.0`.
This means any version greater or equal to 2.1.0 and less than 3.0.0.
Please read [Dependency specification]({{< relref "dependency-specification" >}} "Dependency specification documentation")
for more in-depth information on versions, how versions relate to each other, and on the different ways you can specify
dependencies.
{{% note %}}
**How does Poetry download the right files?**
When you specify a dependency in `pyproject.toml`, Poetry first takes the name of the package
that you have requested and searches for it in any repository you have registered using the `repositories` key.
If you have not registered any extra repositories, or it does not find a package with that name in the
repositories you have specified, it falls back to PyPI.
When Poetry finds the right package, it then attempts to find the best match for the version constraint you have
specified.
{{% /note %}}
## Installing dependencies
To install the defined dependencies for your project, just run the [`install`]({{< relref "cli#install" >}}) command.
```bash
poetry install
```
When you run this command, one of two things may happen:
### Installing without `poetry.lock`
If you have never run the command before and there is also no `poetry.lock` file present,
Poetry simply resolves all dependencies listed in your `pyproject.toml` file and downloads the latest version of their files.
When Poetry has finished installing, it writes all the packages and their exact versions that it downloaded to the `poetry.lock` file,
locking the project to those specific versions.
You should commit the `poetry.lock` file to your project repo so that all people working on the project are locked to the same versions of dependencies (more below).
### Installing with `poetry.lock`
This brings us to the second scenario. If there is already a `poetry.lock` file as well as a `pyproject.toml` file
when you run `poetry install`, it means either you ran the `install` command before,
or someone else on the project ran the `install` command and committed the `poetry.lock` file to the project (which is good).
Either way, running `install` when a `poetry.lock` file is present resolves and installs all dependencies that you listed in `pyproject.toml`,
but Poetry uses the exact versions listed in `poetry.lock` to ensure that the package versions are consistent for everyone working on your project.
As a result you will have all dependencies requested by your `pyproject.toml` file,
but they may not all be at the very latest available versions
(some dependencies listed in the `poetry.lock` file may have released newer versions since the file was created).
This is by design, it ensures that your project does not break because of unexpected changes in dependencies.
### Committing your `poetry.lock` file to version control
#### As an application developer
Application developers commit `poetry.lock` to get more reproducible builds.
Committing this file to VC is important because it will cause anyone who sets up the project
to use the exact same versions of the dependencies that you are using.
Your CI server, production machines, other developers in your team,
everything and everyone runs on the same dependencies,
which mitigates the potential for bugs affecting only some parts of the deployments.
Even if you develop alone, in six months when reinstalling the project you can feel confident
the dependencies installed are still working even if your dependencies released many new versions since then.
(See note below about using the update command.)
{{% warning %}} If you have added the recommended [`[build-system]`]({{< relref "pyproject#poetry-and-pep-517" >}}) section to your project's pyproject.toml then you _can_ successfully install your project and its dependencies into a virtual environment using a command like `pip install -e .`. However, pip will not use the lock file to determine dependency versions as the poetry-core build system is intended for library developers (see next section).
{{% /warning %}}
#### As a library developer
Library developers have more to consider. Your users are application developers, and your library will run in a Python environment you don't control.
The application ignores your library's lock file. It can use whatever dependency version meets the constraints in your `pyproject.toml`. The application will probably use the latest compatible dependency version. If your library's `poetry.lock` falls behind some new dependency version that breaks things for your users, you're likely to be the last to find out about it.
A simple way to avoid such a scenario is to omit the `poetry.lock` file. However, by doing so, you sacrifice reproducibility and performance to a certain extent. Without a lockfile, it can be difficult to find the reason for failing tests, because in addition to obvious code changes an unnoticed library update might be the culprit. Further, Poetry will have to lock before installing a dependency if `poetry.lock` has been omitted. Depending on the number of dependencies, locking may take a significant amount of time.
If you do not want to give up the reproducibility and performance benefits, consider a regular refresh of `poetry.lock` to stay up-to-date and reduce the risk of sudden breakage for users.
### Installing dependencies only
The current project is installed in [editable](https://pip.pypa.io/en/stable/topics/local-project-installs/) mode by default.
If you want to install the dependencies only, run the `install` command with the `--no-root` flag:
```bash
poetry install --no-root
```
## Updating dependencies to their latest versions
As mentioned above, the `poetry.lock` file prevents you from automatically getting the latest versions
of your dependencies.
To update to the latest versions, use the `update` command.
This will fetch the latest matching versions (according to your `pyproject.toml` file)
and update the lock file with the new versions.
(This is equivalent to deleting the `poetry.lock` file and running `install` again.)
{{% note %}}
Poetry will display a **Warning** when executing an install command if `poetry.lock` and `pyproject.toml`
are not synchronized.
{{% /note %}}
================================================
FILE: docs/building-extension-modules.md
================================================
---
title: "Building extension modules"
draft: false
type: docs
layout: single
menu:
docs:
weight: 125
---
# Building Extension Modules
{{% warning %}}
While this feature has been around since almost the beginning of the Poetry project and has needed minimal changes,
it is still considered unstable. You can participate in the discussions about stabilizing this feature
[here](https://github.com/python-poetry/poetry/issues/2740).
And as always, your contributions towards the goal of improving this feature are also welcome.
{{% /warning %}}
Poetry allows a project developer to introduce support for, build and distribute native extensions within their project.
In order to achieve this, at the highest level, the following steps are required.
{{< steps >}}
{{< step >}}
**Add Build Dependencies**
The build dependencies, in this context, refer to those Python packages that are required in order to successfully execute
your build script. Common examples include `cython`, `meson`, `maturin`, `setuptools` etc., depending on how your
extension is built.
{{% note %}}
You must assume that only Python built-ins are available by default in a build environment. This means, if you need
even packages like `setuptools`, it must be explicitly declared.
{{% /note %}}
The necessary build dependencies must be added to the `build-system.requires` section of your `pyproject.toml` file.
```toml
[build-system]
requires = ["poetry-core", "setuptools", "cython"]
build-backend = "poetry.core.masonry.api"
```
{{% note %}}
It is recommended that you consider specifying version constraints to all entries in `build-system.requires` in order to
avoid surprises if one of the packages introduce a breaking change. For example, you can set `cython` to
`cython>=3.0.11,<4.0.0` to ensure no major version upgrades are used.
{{% /note %}}
{{% note %}}
If you wish to develop the build script within your project's virtual environment, then you must also add the
dependencies to your project explicitly to a dependency group - the name of which is not important.
```sh
poetry add --group=build setuptools cython
```
{{% /note %}}
{{< /step >}}
{{< step >}}
**Add Build Script**
The build script can be a free-form Python script that uses any dependency specified in the previous step. This can be
named as needed, but **must** be located within the project root directory (or a subdirectory) and also **must**
be included in your source distribution. You can see the [example snippets section]({{< relref "#example-snippets" >}})
for inspiration.
{{% note %}}
The build script is always executed from the project root. And it is expected to move files around to their destinations
as expected by Poetry as per your `pyproject.toml` file.
{{% /note %}}
```toml
[tool.poetry.build]
script = "relative/path/to/build-extension.py"
```
{{% note %}}
The name of the build script is arbitrary. Common practice has been to name it `build.py`, however, this is not
mandatory. You **should** consider [using a subdirectory]({{< relref "#can-i-store-the-build-script-in-a-subdirectory" >}})
if feasible.
{{% /note %}}
{{< /step >}}
{{< step >}}
**Specify Distribution Files**
{{% warning %}}
The following is an example, and should not be considered as complete.
{{% /warning %}}
```toml
[tool.poetry]
...
include = [
{ path = "package/**/*.so", format = "wheel" },
# sources must be present in sdist, can be ignored if you only have *.pyx sources
{ path = "package/**/*.c", format = "sdist" },
]
```
The key takeaway here should be the following. You can refer to the [`pyproject.toml`]({{< relref "pyproject#exclude-and-include" >}})
documentation for information on each of the relevant sections.
1. Include your build outputs in your wheel.
2. Exclude your build inputs from your wheel.
3. Include your build inputs to your source distribution.
{{< /step >}}
{{< /steps >}}
## Example Snippets
### Cython
{{< tabs tabTotal="3" tabID1="cython-pyproject" tabName1="pyproject.toml" tabID2="cython-build-script" tabName2="build-extension.py" tabID3="cython-src-tree" tabName3="Source Tree">}}
{{< tab tabID="cython-pyproject" >}}
```toml
[build-system]
requires = ["poetry-core", "cython", "setuptools"]
build-backend = "poetry.core.masonry.api"
[tool.poetry]
...
packages = [
{ include = "package", from = "src"},
]
include = [
{ path = "src/package/**/*.so", format = "wheel" },
]
# if not already excluded via .gitignore
exclude = [
"**/*.c"
]
[tool.poetry.build]
script = "scripts/build-extension.py"
```
{{< /tab >}}
{{< tab tabID="cython-build-script" >}}
```py
from __future__ import annotations
import os
import shutil
from pathlib import Path
from Cython.Build import cythonize
from setuptools import Distribution
from setuptools import Extension
from setuptools.command.build_ext import build_ext
COMPILE_ARGS = ["-march=native", "-O3", "-msse", "-msse2", "-mfma", "-mfpmath=sse"]
LINK_ARGS = []
INCLUDE_DIRS = []
LIBRARIES = ["m"]
def build() -> None:
extensions = [
Extension(
"*",
["src/package/*.pyx"],
extra_compile_args=COMPILE_ARGS,
extra_link_args=LINK_ARGS,
include_dirs=INCLUDE_DIRS,
libraries=LIBRARIES,
)
]
ext_modules = cythonize(
extensions,
include_path=INCLUDE_DIRS,
compiler_directives={"binding": True, "language_level": 3},
)
distribution = Distribution({
"name": "package",
"ext_modules": ext_modules
})
cmd = build_ext(distribution)
cmd.ensure_finalized()
cmd.run()
# Copy built extensions back to the project
for output in cmd.get_outputs():
output = Path(output)
relative_extension = Path("src") / output.relative_to(cmd.build_lib)
shutil.copyfile(output, relative_extension)
mode = os.stat(relative_extension).st_mode
mode |= (mode & 0o444) >> 2
os.chmod(relative_extension, mode)
if __name__ == "__main__":
build()
```
{{< /tab >}}
{{< tab tabID="cython-src-tree" >}}
```
scripts/
└── build-extension.py
src/
└── package
├── example.pyx
└── __init__.py
```
{{< /tab >}}
{{< /tabs >}}
### Meson
{{< tabs tabTotal="2" tabID1="meson-pyproject" tabName1="pyproject.toml" tabID2="meson-build-script" tabName2="build-extension.py">}}
{{< tab tabID="meson-pyproject" >}}
```toml
[tool.poetry.build]
script = "build-extension.py"
[build-system]
requires = ["poetry-core", "meson"]
build-backend = "poetry.core.masonry.api"
```
{{< /tab >}}
{{< tab tabID="meson-build-script" >}}
```py
from __future__ import annotations
import subprocess
from pathlib import Path
def meson(*args):
subprocess.call(["meson", *args])
def build():
build_dir = Path(__file__).parent.joinpath("build")
build_dir.mkdir(parents=True, exist_ok=True)
meson("setup", build_dir.as_posix())
meson("compile", "-C", build_dir.as_posix())
meson("install", "-C", build_dir.as_posix())
if __name__ == "__main__":
build()
```
{{< /tab >}}
{{< /tabs >}}
### Maturin
{{< tabs tabTotal="2" tabID1="maturin-pyproject" tabName1="pyproject.toml" tabID2="maturin-build-script" tabName2="build-extension.py">}}
{{< tab tabID="maturin-pyproject" >}}
```toml
[tool.poetry.build]
script = "build-extension.py"
[build-system]
requires = ["poetry-core", "maturin"]
build-backend = "poetry.core.masonry.api"
```
{{< /tab >}}
{{< tab tabID="maturin-build-script" >}}
```py
import os
import shlex
import shutil
import subprocess
import zipfile
from pathlib import Path
def maturin(*args):
subprocess.call(["maturin", *list(args)])
def build():
build_dir = Path(__file__).parent.joinpath("build")
build_dir.mkdir(parents=True, exist_ok=True)
wheels_dir = Path(__file__).parent.joinpath("target/wheels")
if wheels_dir.exists():
shutil.rmtree(wheels_dir)
cargo_args = []
if os.getenv("MATURIN_BUILD_ARGS"):
cargo_args = shlex.split(os.getenv("MATURIN_BUILD_ARGS", ""))
maturin("build", "-r", *cargo_args)
# We won't use the wheel built by maturin directly since
# we want Poetry to build it, but we need to retrieve the
# compiled extensions from the maturin wheel.
wheel = next(iter(wheels_dir.glob("*.whl")))
with zipfile.ZipFile(wheel.as_posix()) as whl:
whl.extractall(wheels_dir.as_posix())
for extension in wheels_dir.rglob("**/*.so"):
shutil.copyfile(extension, Path(__file__).parent.joinpath(extension.name))
shutil.rmtree(wheels_dir)
if __name__ == "__main__":
build()
```
{{< /tab >}}
{{< /tabs >}}
## FAQ
### When is my build script executed?
If your project uses a build script, it is run implicitly in the following scenarios.
1. When `poetry install` is run, it is executed prior to installing the project's root package.
2. When `poetry build` is run, it is executed prior to building distributions.
3. When a PEP 517 build is triggered from source or sdist by another build frontend.
### How does Poetry ensure my build script's dependencies are met?
Prior to executing the build script, Poetry creates a temporary virtual environment with your project's active Python
version and then installs all dependencies specified under `build-system.requires` into this environment. It should be
noted that no packages will be present in this environment at the time of creation.
### Can I store the build script in a subdirectory?
Yes you can. If storing the script in a subdirectory, your `pyproject.toml` might look something like this.
```toml
[tool.poetry]
...
packages = [
{ include = "package", from = "src"}
]
include = [
{ path = "src/package/**/*.so", format = "wheel" },
]
# exclude any intermediate source files
exclude = [
"**/*.c"
]
[tool.poetry.build]
script = "scripts/build-extension.py"
```
================================================
FILE: docs/cli.md
================================================
---
title: "Commands"
draft: false
type: docs
layout: single
menu:
docs:
weight: 30
---
# Commands
You've already learned how to use the command-line interface to do some things.
This chapter documents all the available commands.
To get help from the command-line, simply call `poetry` to see the complete list of commands,
then `--help` combined with any of those can give you more information.
## Global Options
* `--verbose (-v|vv|vvv)`: Increase the verbosity of messages: "-v" for normal output, "-vv" for more verbose output and "-vvv" for debug.
{{% note %}}
You can also set the verbosity level using the `SHELL_VERBOSITY` environment variable.
This is useful in CI/CD pipelines or scripts where you cannot easily modify command-line arguments.
| Value | Equivalent | Description |
|--------|------------|---------------------|
| `-1` | `-q` | Quiet mode |
| `0` | (default) | Normal output |
| `1` | `-v` | Verbose output |
| `2` | `-vv` | More verbose output |
| `3` | `-vvv` | Debug output |
{{% /note %}}
* `--help (-h)` : Display help information.
* `--quiet (-q)` : Do not output any message.
* `--ansi`: Force ANSI output.
* `--no-ansi`: Disable ANSI output.
* `--version (-V)`: Display this application version.
* `--no-interaction (-n)`: Do not ask any interactive question.
* `--no-plugins`: Disables plugins.
* `--no-cache`: Disables Poetry source caches.
* `--directory=DIRECTORY (-C)`: The working directory for the Poetry command (defaults to the current working directory). All command-line arguments will be resolved relative to the given directory.
* `--project=PROJECT (-P)`: Specify another path as the project root. All command-line arguments will be resolved relative to the current working directory or directory specified using `--directory` option if used.
## about
The `about` command displays global information about Poetry, including the current version and version of `poetry-core`.
```bash
poetry about
```
## add
The `add` command adds required packages to your `pyproject.toml` and installs them.
If you do not specify a version constraint, poetry will attempt to use the latest version.
```bash
poetry add requests pendulum
```
{{% note %}}
A package is looked up, by default, only from [PyPI](https://pypi.org).
You can modify the default source (PyPI);
or add and use [Supplemental Package Sources]({{< relref "repositories/#supplemental-package-sources" >}})
or [Explicit Package Sources]({{< relref "repositories/#explicit-package-sources" >}}).
For more information, refer to the [Package Sources]({{< relref "repositories/#package-sources" >}}) documentation.
{{% /note %}}
You can also specify a constraint when adding a package:
```bash
# Allow >=2.0.5, <3.0.0 versions
poetry add pendulum@^2.0.5
# Allow >=2.0.5, <2.1.0 versions
poetry add pendulum@~2.0.5
# Allow >=2.0.5 versions, without upper bound
poetry add "pendulum>=2.0.5"
# Allow only 2.0.5 version
poetry add pendulum==2.0.5
```
{{% note %}}
See the [Dependency specification]({{< relref "dependency-specification#using-the--operator" >}}) page for more information about the `@` operator.
{{% /note %}}
If you try to add a package that is already present, you will get an error.
However, if you specify a constraint, like above, the dependency will be updated
by using the specified constraint.
If you want to get the latest version of an already
present dependency, you can use the special `latest` constraint:
```bash
poetry add pendulum@latest
```
{{% note %}}
See the [Dependency specification]({{< relref "dependency-specification" >}}) for more information on setting the version constraints for a package.
{{% /note %}}
You can also add `git` dependencies:
```bash
poetry add git+https://github.com/sdispater/pendulum.git
```
or use ssh instead of https:
```bash
poetry add git+ssh://git@github.com/sdispater/pendulum.git
# or alternatively:
poetry add git+ssh://git@github.com:sdispater/pendulum.git
```
If you need to checkout a specific branch, tag or revision,
you can specify it when using `add`:
```bash
poetry add git+https://github.com/sdispater/pendulum.git#develop
poetry add git+https://github.com/sdispater/pendulum.git#2.0.5
# or using SSH instead:
poetry add git+ssh://git@github.com:sdispater/pendulum.git#develop
poetry add git+ssh://git@github.com:sdispater/pendulum.git#2.0.5
```
or reference a subdirectory:
```bash
poetry add git+https://github.com/myorg/mypackage_with_subdirs.git@main#subdirectory=subdir
```
You can also add a local directory or file:
```bash
poetry add ./my-package/
poetry add ../my-package/dist/my-package-0.1.0.tar.gz
poetry add ../my-package/dist/my_package-0.1.0.whl
```
If you want the dependency to be installed in editable mode you can use the `--editable` option.
```bash
poetry add --editable ./my-package/
poetry add --editable git+ssh://github.com/sdispater/pendulum.git#develop
```
Alternatively, you can specify it in the `pyproject.toml` file. It means that changes in the local directory will be reflected directly in environment.
```toml
[tool.poetry.dependencies]
my-package = {path = "../my/path", develop = true}
```
{{% note %}}
The `develop` attribute is a Poetry-specific feature, so it is not included in the package distribution metadata.
In other words, it is only considered when using Poetry to install the project.
{{% /note %}}
If the package(s) you want to install provide extras, you can specify them
when adding the package:
```bash
poetry add "requests[security,socks]"
poetry add "requests[security,socks]~=2.22.0"
poetry add "git+https://github.com/pallets/flask.git@1.1.1[dotenv,dev]"
```
{{% warning %}}
Some shells may treat square braces (`[` and `]`) as special characters. It is suggested to always quote arguments containing these characters to prevent unexpected shell expansion.
{{% /warning %}}
If you want to add a package to a specific group of dependencies, you can use the `--group (-G)` option:
```bash
poetry add mkdocs --group docs
```
See [Dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) for more information
about dependency groups.
#### Options
* `--group (-G)`: The group to add the dependency to.
* `--dev (-D)`: Add package as development dependency. (shortcut for `-G dev`)
* `--editable (-e)`: Add vcs/path dependencies as editable.
* `--extras (-E)`: Extras to activate for the dependency. (multiple values allowed)
* `--optional`: Add as an optional dependency to an extra.
* `--python`: Python version for which the dependency must be installed.
* `--platform`: Platforms for which the dependency must be installed.
* `--markers`: Environment markers which describe when the dependency should be installed.
* `--source`: Name of the source to use to install the package.
* `--allow-prereleases`: Accept prereleases.
* `--dry-run`: Output the operations but do not execute anything (implicitly enables `--verbose`).
* `--lock`: Do not perform install (only update the lockfile).
## build
The `build` command builds the source and wheels archives.
```bash
poetry build
```
The command will trigger the build system defined in the `pyproject.toml` file according to [PEP 517](https://peps.python.org/pep-0517/).
If necessary the build process happens in an isolated environment.
#### Options
* `--format (-f)`: Limit the format to either `wheel` or `sdist`.
* `--clean`: Clean output directory before building.
* `--local-version (-l)`: Add or replace a local version label to the build (deprecated).
* `--output (-o)`: Set output directory for build artifacts. Default is `dist`.
* `--config-settings== (-c)`: Config settings to be passed to the build back-end. (multiple allowed)
{{% note %}}
When using `--local-version`, the identifier must be [PEP 440](https://peps.python.org/pep-0440/#local-version-identifiers)
compliant. This is useful for adding build numbers, platform specificities, etc. for private packages.
`--local-version` is deprecated and will be removed in a future version of Poetry.
Use `--config-settings local-version=` instead.
{{% /note %}}
{{% warning %}}
Local version identifiers SHOULD NOT be used when publishing upstream projects to a public index server, but MAY be
used to identify private builds created directly from the project source.
See [PEP 440](https://peps.python.org/pep-0440/#local-version-identifiers) for more information.
{{% /warning %}}
## cache
The `cache` command groups subcommands to interact with Poetry's cache.
### cache clear
The `cache clear` command removes packages from cached repositories.
For example, to clear the whole cache of packages from all repositories, run:
```bash
poetry cache clear --all
```
To only clear all packages from the `PyPI` repository, run:
```bash
poetry cache clear PyPI --all
```
To only remove a specific package from a cache, you have to specify the cache entry in the following form `cache:package:version`:
```bash
poetry cache clear PyPI:requests:2.24.0
```
### cache list
The `cache list` command lists Poetry's available caches.
```bash
poetry cache list
```
## check
The `check` command validates the content of the `pyproject.toml` file
and its consistency with the `poetry.lock` file.
It returns a detailed report if there are any errors.
{{% note %}}
This command is also available as a pre-commit hook. See [pre-commit hooks]({{< relref "pre-commit-hooks#poetry-check">}}) for more information.
{{% /note %}}
```bash
poetry check
```
#### Options
* `--lock`: Verifies that `poetry.lock` exists for the current `pyproject.toml`.
* `--strict`: Fail if check reports warnings.
## config
The `config` command allows you to edit poetry config settings and repositories.
```bash
poetry config --list
```
### Usage
````bash
poetry config [options] [setting-key] [setting-value1] ... [setting-valueN]
````
`setting-key` is a configuration option name and `setting-value1` is a configuration value.
See [Configuration]({{< relref "configuration" >}}) for all available settings.
{{% warning %}}
Use `--` to terminate option parsing if your values may start with a hyphen (`-`), e.g.
```bash
poetry config http-basic.custom-repo gitlab-ci-token -- ${GITLAB_JOB_TOKEN}
```
Without `--` this command will fail if `${GITLAB_JOB_TOKEN}` starts with a hyphen.
{{% /warning%}}
#### Options
* `--unset`: Remove the configuration element named by `setting-key`.
* `--list`: Show the list of current config variables.
* `--local`: Set/Get settings that are specific to a project (in the local configuration file `poetry.toml`).
* `--migrate`: Migrate outdated configuration settings.
## debug
The `debug` command groups subcommands that are useful for, as the name suggests, debugging issues you might have
when using Poetry with your projects.
### debug info
The `debug info` command shows debug information about Poetry and your project's virtual environment.
### debug resolve
The `debug resolve` command helps when debugging dependency resolution issues. The command attempts to resolve your
dependencies and list the chosen packages and versions.
### debug tags
The `debug tags` command is useful when you want to see the supported packaging tags for your project's active
virtual environment. This is useful when Poetry cannot install any known binary distributions for a dependency.
## env
The `env` command groups subcommands to interact with the virtualenvs
associated with a specific project.
See [Managing environments]({{< relref "managing-environments" >}}) for more information about these commands.
### env activate
The `env activate` command prints the command to activate a virtual environment in your current shell.
{{% note %}}
This command does not activate the virtual environment, but only displays the activation command, for more information
on how to use this command see [here]({{< relref "managing-environments#activating-the-environment" >}}).
{{% /note %}}
### env info
The `env info` command displays information about the current environment.
#### Options
* `--path (-p)`: Only display the environment's path.
* `--executable (-e)`: Only display the environment's python executable path.
### env list
The `env list` command lists all virtualenvs associated with the current project.
#### Options
* `--full-path`: Output the full paths of the virtualenvs.
### env remove
The `env remove` command removes virtual environments associated with the project. You can specify multiple Python
executables or virtual environment names to remove all matching ones. Alternatively, you can remove all associated
virtual environments using the `--all` option.
{{% note %}}
If `virtualenvs.in-project` config is set to `true`, no argument or option is required. Your in project virtual environment is removed.
{{% /note %}}
#### Arguments
* `python`: The python executables associated with, or names of the virtual environments which are to be removed. Can be specified multiple times.
#### Options
* `--all`: Remove all managed virtual environments associated with the project.
### env use
The `env use` command activates or creates a new virtualenv for the current project.
#### Arguments
* `python`: The python executable to use. This can be a version number (if not on Windows) or a path to the python binary.
## export
{{% warning %}}
This command is provided by the [Export Poetry Plugin](https://github.com/python-poetry/poetry-plugin-export).
The plugin is no longer installed by default with Poetry 2.0.
See [Using plugins]({{< relref "plugins#using-plugins" >}}) for information on how to install a plugin.
As described in [Project plugins]({{< relref "plugins#project-plugins" >}}),
you can also define in your `pyproject.toml` that the plugin is required for the development of your project:
```toml
[tool.poetry.requires-plugins]
poetry-plugin-export = ">=1.8"
```
{{% /warning %}}
{{% note %}}
The `export` command is also available as a pre-commit hook.
See [pre-commit hooks]({{< relref "pre-commit-hooks#poetry-export" >}}) for more information.
{{% /note %}}
## help
The `help` command displays global help, or help for a specific command.
To display global help:
```bash
poetry help
```
To display help for a specific command, for instance `show`:
```bash
poetry help show
```
{{% note %}}
The `--help` option can also be passed to any command to get help for a specific command.
For instance:
```bash
poetry show --help
```
{{% /note %}}
## init
This command will help you create a `pyproject.toml` file interactively
by prompting you to provide basic information about your package.
It will interactively ask you to fill in the fields, while using some smart defaults.
```bash
poetry init
```
#### Options
* `--name`: Name of the package.
* `--description`: Description of the package.
* `--author`: Author of the package.
* `--python` Compatible Python versions.
* `--dependency`: Package to require with a version constraint. Should be in format `foo:1.0.0`.
* `--dev-dependency`: Development requirements, see `--dependency`.
## install
The `install` command reads the `pyproject.toml` file from the current project,
resolves the dependencies, and installs them.
```bash
poetry install
```
If there is a `poetry.lock` file in the current directory,
it will use the exact versions from there instead of resolving them.
This ensures that everyone using the library will get the same versions of the dependencies.
If there is no `poetry.lock` file, Poetry will create one after dependency resolution.
{{% note %}}
**When to use `install` vs `update`:**
- Use `poetry install` to install dependencies as specified in `poetry.lock` (or resolve dependencies and create the lock file if it is missing).
This is what you run after cloning a project. For reproducible installs, prefer `poetry sync`,
which also removes packages that are not in the lock file.
- Use `poetry update` when you want to update dependencies to their latest versions (within the constraints from the `pyproject.toml`)
and refresh `poetry.lock`.
{{% /note %}}
{{% note %}}
Normally, you should prefer `poetry sync` to `poetry install` to avoid untracked outdated packages.
However, if you have set `virtualenvs.create = false` to install dependencies into your system environment,
which is discouraged, or `virtualenvs.options.system-site-packages = true` to make
system site-packages available in your virtual environment, you should use `poetry install`
because `poetry sync` will normally not work well in these cases.
{{% /note %}}
If you want to exclude one or more dependency groups for the installation, you can use
the `--without` option.
```bash
poetry install --without test,docs
```
You can also select optional dependency groups with the `--with` option.
```bash
poetry install --with test,docs
```
To install all dependency groups including the optional groups, use the ``--all-groups`` flag.
```bash
poetry install --all-groups
```
It's also possible to only install specific dependency groups by using the `only` option.
```bash
poetry install --only test,docs
```
To only install the project itself with no dependencies, use the `--only-root` flag.
```bash
poetry install --only-root
```
See [Dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) for more information
about dependency groups.
You can also specify the extras you want installed
by passing the `-E|--extras` option (See [Extras]({{< relref "pyproject#extras" >}}) for more info).
Pass `--all-extras` to install all defined extras for a project.
```bash
poetry install --extras "mysql pgsql"
poetry install -E mysql -E pgsql
poetry install --all-extras
```
Any extras not specified will be kept but not installed:
```bash
poetry install --extras "A B" # C is kept if already installed
```
If you want to remove unspecified extras, use the `sync` command.
By default `poetry` will install your project's package every time you run `install`:
```bash
$ poetry install
Installing dependencies from lock file
No dependencies to install or update
- Installing (x.x.x)
```
If you want to skip this installation, use the `--no-root` option.
```bash
poetry install --no-root
```
Similar to `--no-root` you can use `--no-directory` to skip directory path dependencies:
```bash
poetry install --no-directory
```
This is mainly useful for caching in CI or when building Docker images. See the [FAQ entry]({{< relref "faq#poetry-busts-my-docker-cache-because-it-requires-me-to-copy-my-source-files-in-before-installing-3rd-party-dependencies" >}}) for more information on this option.
By default `poetry` does not compile Python source files to bytecode during installation.
This speeds up the installation process, but the first execution may take a little more
time because Python then compiles source files to bytecode automatically.
If you want to compile source files to bytecode during installation,
you can use the `--compile` option:
```bash
poetry install --compile
```
#### Options
* `--without`: The dependency groups to ignore.
* `--with`: The optional dependency groups to include.
* `--only`: The only dependency groups to include.
* `--only-root`: Install only the root project, exclude all dependencies.
* `--sync`: Synchronize the environment with the locked packages and the specified groups. (**Deprecated**, use `poetry sync` instead)
* `--no-root`: Do not install the root package (your project).
* `--no-directory`: Skip all directory path dependencies (including transitive ones).
* `--dry-run`: Output the operations but do not execute anything (implicitly enables `--verbose`).
* `--extras (-E)`: Features to install (multiple values allowed).
* `--all-extras`: Install all extra features (conflicts with `--extras`).
* `--all-groups`: Install dependencies from all groups (conflicts with `--only`, `--with`, and `--without`).
* `--compile`: Compile Python source files to bytecode.
{{% note %}}
When `--only` is specified, `--with` and `--without` options are ignored.
{{% /note %}}
## list
The `list` command displays all the available Poetry commands.
```bash
poetry list
```
## lock
This command locks (without installing) the dependencies specified in `pyproject.toml`.
{{% note %}}
By default, packages that have already been added to the lock file before will not be updated.
To update all dependencies to the latest available compatible versions, use `poetry update --lock`
or `poetry lock --regenerate`, which normally produce the same result.
This command is also available as a pre-commit hook. See [pre-commit hooks]({{< relref "pre-commit-hooks#poetry-lock">}}) for more information.
{{% /note %}}
```bash
poetry lock
```
#### Options
* `--regenerate`: Ignore existing lock file and overwrite it with a new lock file created from scratch.
## new
This command will help you kickstart your new Python project by creating a new Poetry project. By default, a `src`
layout is chosen.
```bash
poetry new my-package
```
will create a folder as follows:
```text
my-package
├── pyproject.toml
├── README.md
├── src
│ └── my_package
│ └── __init__.py
└── tests
└── __init__.py
```
If you want to name your project differently than the folder, you can pass
the `--name` option:
```bash
poetry new my-folder --name my-package
```
If you want to use a `flat` project layout, you can use the `--flat` option:
```bash
poetry new --flat my-package
```
That will create a folder structure as follows:
```text
my-package
├── pyproject.toml
├── README.md
├── my_package
│ └── __init__.py
└── tests
└── __init__.py
```
{{% note %}}
For an overview of the differences between `flat` and `src` layouts, please see
[here](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/).
{{% /note %}}
The `--name` option is smart enough to detect namespace packages and create
the required structure for you.
```bash
poetry new --name my.package my-package
```
will create the following structure:
```text
my-package
├── pyproject.toml
├── README.md
├── src
│ └── my
│ └── package
│ └── __init__.py
└── tests
└── __init__.py
```
#### Options
* `--interactive (-i)`: Allow interactive specification of project configuration.
* `--name`: Set the resulting package name.
* `--flat`: Use the flat layout for the project.
* `--readme`: Specify the readme file extension. Default is `md`. If you intend to publish to PyPI
keep the [recommendations for a PyPI-friendly README](https://packaging.python.org/en/latest/guides/making-a-pypi-friendly-readme/)
in mind.
* `--description`: Description of the package.
* `--author`: Author of the package.
* `--python` Compatible Python versions.
* `--dependency`: Package to require with a version constraint. Should be in format `foo:1.0.0`.
* `--dev-dependency`: Development requirements, see `--dependency`.
## publish
This command publishes the package, previously built with the [`build`](#build) command, to the remote repository.
It will automatically register the package before uploading if this is the first time it is submitted.
```bash
poetry publish
```
It can also build the package if you pass it the `--build` option.
{{% note %}}
See [Publishable Repositories]({{< relref "repositories/#publishable-repositories" >}}) for more information
on how to configure and use publishable repositories.
{{% /note %}}
{{% warning %}}
Only artifacts of the latest version of your package in the dist directory will be uploaded.
Older versions from previous builds as well as artifacts of other packages are ignored.
{{% /warning %}}
#### Options
* `--repository (-r)`: The repository to register the package to (default: `pypi`).
Should match a repository name set by the [`config`](#config) command.
* `--username (-u)`: The username to access the repository.
* `--password (-p)`: The password to access the repository.
* `--cert`: Certificate authority to access the repository.
* `--client-cert`: Client certificate to access the repository.
* `--dist-dir`: Dist directory where built artifacts are stored. Default is `dist`.
* `--build`: Build the package before publishing.
* `--dry-run`: Perform all actions except upload the package.
* `--skip-existing`: Ignore errors from files already existing in the repository.
{{% note %}}
See [Configuring Credentials]({{< relref "repositories/#configuring-credentials" >}}) for more information on how to configure credentials.
{{% /note %}}
## python
The `python` namespace groups subcommands to manage Python versions.
{{% warning %}}
This is an experimental feature, and can change behaviour in upcoming releases.
{{% /warning %}}
*Introduced in 2.1.0*
### python install
The `python install` command installs the specified Python version from the Python Standalone Builds project.
```bash
poetry python install
```
#### Options
* `--clean (-c)`: Clean up installation if check fails.
* `--free-threaded (-t)`: Use free-threaded version if available. (Same as requesting a version with trailing "t".)
* `--implementation (-i)`: Python implementation to use. (cpython, pypy)
* `--reinstall (-r)`: Reinstall if installation already exists.
### python list
The `python list` command shows Python versions available in the environment. This includes both installed and
discovered System managed and Poetry managed installations.
```bash
poetry python list
```
#### Options
* `--all (-a)`: List all versions, including those available for download.
* `--free-threaded (-t)`: List only free-threaded Python versions.
* `--implementation (-i)`: Python implementation to search for.
* `--managed (-m)`: List only Poetry managed Python versions.
### python remove
The `python remove` command removes the specified Python version if managed by Poetry.
```bash
poetry python remove
```
#### Options
* `--free-threaded (-t)`: Remove free-threaded version (Same as requesting a version with trailing "t".)
* `--implementation (-i)`: Python implementation to remove. (cpython, pypy)
## remove
The `remove` command removes a package from the current
list of installed packages.
```bash
poetry remove pendulum
```
If you want to remove a package from a specific group of dependencies, you can use the `--group (-G)` option:
```bash
poetry remove mkdocs --group docs
```
See [Dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) for more information
about dependency groups.
#### Options
* `--group (-G)`: The group to remove the dependency from.
* `--dev (-D)`: Removes a package from the development dependencies. (shortcut for `-G dev`)
* `--dry-run` : Outputs the operations but will not execute anything (implicitly enables `--verbose`).
* `--lock`: Do not perform operations (only update the lockfile).
## run
The `run` command executes the given command inside the project's virtualenv.
```bash
poetry run python -V
```
It can also execute one of the scripts defined in `pyproject.toml`.
So, if you have a script defined like this:
{{< tabs tabTotal="2" tabID1="script-project" tabID2=script-poetry" tabName1="[project]" tabName2="[tool.poetry]">}}
{{< tab tabID="script-project" >}}
```toml
[project]
# ...
[project.scripts]
my-script = "my_module:main"
```
{{< /tab >}}
{{< tab tabID="script-poetry" >}}
```toml
[tool.poetry.scripts]
my-script = "my_module:main"
```
{{< /tab >}}
{{< /tabs >}}
You can execute it like so:
```bash
poetry run my-script
```
Note that this command has no option.
## search
This command searches for packages on a remote index.
```bash
poetry search requests pendulum
```
{{% note %}}
PyPI no longer allows for the search of packages without a browser. Please use https://pypi.org/search
(via a browser) instead.
For more information, please see [warehouse documentation](https://warehouse.pypa.io/api-reference/xml-rpc.html#deprecated-methods)
and this [discussion](https://discuss.python.org/t/fastly-interfering-with-pypi-search/73597/6).
{{% /note %}}
## self
The `self` namespace groups subcommands to manage the Poetry installation itself.
{{% note %}}
Use of these commands will create the required `pyproject.toml` and `poetry.lock` files in your
[configuration directory]({{< relref "configuration" >}}).
{{% /note %}}
{{% warning %}}
Especially on Windows, `self` commands that update or remove packages may be problematic
so that other methods for installing plugins and updating Poetry are recommended.
See [Using plugins]({{< relref "plugins#using-plugins" >}}) and
[Installing Poetry]({{< relref "docs#installation" >}}) for more information.
{{% /warning %}}
### self add
The `self add` command installs Poetry plugins and make them available at runtime. Additionally, it can
also be used to upgrade Poetry's own dependencies or inject additional packages into the runtime
environment
{{% note %}}
The `self add` command works exactly like the [`add` command](#add). However, is different in that the packages
managed are for Poetry's runtime environment.
The package specification formats supported by the `self add` command are the same as the ones supported
by the [`add` command](#add).
{{% /note %}}
For example, to install the `poetry-plugin-export` plugin, you can run:
```bash
poetry self add poetry-plugin-export
```
To update to the latest `poetry-core` version, you can run:
```bash
poetry self add poetry-core@latest
```
To add a keyring provider `artifacts-keyring`, you can run:
```bash
poetry self add artifacts-keyring
```
#### Options
* `--editable (-e)`: Add vcs/path dependencies as editable.
* `--extras (-E)`: Extras to activate for the dependency. (multiple values allowed)
* `--allow-prereleases`: Accept prereleases.
* `--source`: Name of the source to use to install the package.
* `--dry-run`: Output the operations but do not execute anything (implicitly enables `--verbose`).
### self install
The `self install` command ensures all additional packages specified are installed in the current
runtime environment.
{{% note %}}
The `self install` command works similar to the [`install` command](#install). However,
it is different in that the packages managed are for Poetry's runtime environment.
{{% /note %}}
```bash
poetry self install
```
#### Options
* `--sync`: Synchronize the environment with the locked packages and the specified groups. (**Deprecated**, use `poetry self sync` instead)
* `--dry-run`: Output the operations but do not execute anything (implicitly enables `--verbose`).
### self lock
The `self lock` command reads this Poetry installation's system `pyproject.toml` file. The system
dependencies are locked in the corresponding `poetry.lock` file.
```bash
poetry self lock
```
#### Options
* `--regenerate`: Ignore existing lock file and overwrite it with a new lock file created from scratch.
### self remove
The `self remove` command removes an installed addon package.
```bash
poetry self remove poetry-plugin-export
```
#### Options
* `--dry-run`: Outputs the operations but will not execute anything (implicitly enables `--verbose`).
### self show
The `self show` command behaves similar to the show command, but
working within Poetry's runtime environment. This lists all packages installed within
the Poetry install environment.
To show only additional packages that have been added via self add and their
dependencies use `self show --addons`.
```bash
poetry self show
```
#### Options
* `--addons`: List only add-on packages installed.
* `--tree`: List the dependencies as a tree.
* `--latest (-l)`: Show the latest version.
* `--outdated (-o)`: Show the latest version but only for packages that are outdated.
* `--format (-f)`: Specify the output format (`json` or `text`). Default is `text`. `json` cannot be combined with the `--tree` option.
### self show plugins
The `self show plugins` command lists all the currently installed plugins.
```bash
poetry self show plugins
```
### self sync
The `self sync` command ensures all additional (and no other) packages specified
are installed in the current runtime environment.
{{% note %}}
The `self sync` command works similar to the [`sync` command](#sync). However,
it is different in that the packages managed are for Poetry's runtime environment.
{{% /note %}}
```bash
poetry self sync
```
#### Options
* `--dry-run`: Output the operations but do not execute anything (implicitly enables `--verbose`).
### self update
The `self update` command updates Poetry version in its current runtime environment.
{{% note %}}
The `self update` command works exactly like the [`update` command](#update). However,
is different in that the packages managed are for Poetry's runtime environment.
{{% /note %}}
```bash
poetry self update
```
#### Options
* `--preview`: Allow the installation of pre-release versions.
* `--dry-run`: Output the operations but do not execute anything (implicitly enables `--verbose`).
## shell
The `shell` command was moved to a plugin: [`poetry-plugin-shell`](https://github.com/python-poetry/poetry-plugin-shell)
## show
To list all the available packages, you can use the `show` command.
```bash
poetry show
```
If you want to see the details of a certain package, you can pass the package name.
```bash
poetry show pendulum
name : pendulum
version : 1.4.2
description : Python datetimes made easy
dependencies
- python-dateutil >=2.6.1
- tzlocal >=1.4
- pytzdata >=2017.2.2
required by
- calendar requires >=1.4.0
```
#### Options
* `--without`: The dependency groups to ignore.
* `--why`: When showing the full list, or a `--tree` for a single package, display whether they are a direct dependency or required by other packages.
* `--with`: The optional dependency groups to include.
* `--only`: The only dependency groups to include.
* `--tree`: List the dependencies as a tree.
* `--latest (-l)`: Show the latest version.
* `--outdated (-o)`: Show the latest version but only for packages that are outdated.
* `--all (-a)`: Show all packages (even those not compatible with current system).
* `--top-level (-T)`: Only show explicitly defined packages.
* `--no-truncate`: Do not truncate the output based on the terminal width.
* `--format (-f)`: Specify the output format (`json` or `text`). Default is `text`. `json` cannot be combined with the `--tree` option.
{{% note %}}
When `--only` is specified, `--with` and `--without` options are ignored.
{{% /note %}}
## source
The `source` namespace groups subcommands to manage repository sources for a Poetry project.
### source add
The `source add` command adds source configuration to the project.
For example, to add the `pypi-test` source, you can run:
```bash
poetry source add --priority supplemental pypi-test https://test.pypi.org/simple/
```
You cannot use the name `pypi` for a custom repository as it is reserved for use by
the default PyPI source. However, you can set the priority of PyPI:
```bash
poetry source add --priority=explicit pypi
```
#### Options
* `--priority`: Set the priority of this source. Accepted values are: [`primary`]({{< relref "repositories#primary-package-sources" >}}), [`supplemental`]({{< relref "repositories#supplemental-package-sources" >}}), and [`explicit`]({{< relref "repositories#explicit-package-sources" >}}). Refer to the dedicated sections in [Repositories]({{< relref "repositories" >}}) for more information.
### source show
The `source show` command displays information on all configured sources for the project.
```bash
poetry source show
```
Optionally, you can show information of one or more sources by specifying their names.
```bash
poetry source show pypi-test
```
{{% note %}}
This command will only show sources configured via the `pyproject.toml`
and does not include the implicit default PyPI.
{{% /note %}}
### source remove
The `source remove` command removes a configured source from your `pyproject.toml`.
```bash
poetry source remove pypi-test
```
## sync
The `sync` command makes sure that the project's environment is in sync with the `poetry.lock` file.
It is similar to `poetry install` but it additionally removes packages that are not tracked in the lock file.
```bash
poetry sync
```
If there is a `poetry.lock` file in the current directory,
it will use the exact versions from there instead of resolving them.
This ensures that everyone using the library will get the same versions of the dependencies.
If there is no `poetry.lock` file, Poetry will create one after dependency resolution.
If you want to exclude one or more dependency groups for the installation, you can use
the `--without` option.
```bash
poetry sync --without test,docs
```
You can also select optional dependency groups with the `--with` option.
```bash
poetry sync --with test,docs
```
To install all dependency groups including the optional groups, use the ``--all-groups`` flag.
```bash
poetry sync --all-groups
```
It's also possible to only install specific dependency groups by using the `only` option.
```bash
poetry sync --only test,docs
```
To only install the project itself with no dependencies, use the `--only-root` flag.
```bash
poetry sync --only-root
```
See [Dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) for more information
about dependency groups.
You can also specify the extras you want installed
by passing the `-E|--extras` option (See [Extras]({{< relref "pyproject#extras" >}}) for more info).
Pass `--all-extras` to install all defined extras for a project.
```bash
poetry sync --extras "mysql pgsql"
poetry sync -E mysql -E pgsql
poetry sync --all-extras
```
Any extras not specified will always be removed.
```bash
poetry sync --extras "A B" # C is removed
```
By default `poetry` will install your project's package every time you run `sync`:
```bash
$ poetry sync
Installing dependencies from lock file
No dependencies to install or update
- Installing (x.x.x)
```
If you want to skip this installation, use the `--no-root` option.
```bash
poetry sync --no-root
```
Similar to `--no-root` you can use `--no-directory` to skip directory path dependencies:
```bash
poetry sync --no-directory
```
This is mainly useful for caching in CI or when building Docker images. See the [FAQ entry]({{< relref "faq#poetry-busts-my-docker-cache-because-it-requires-me-to-copy-my-source-files-in-before-installing-3rd-party-dependencies" >}}) for more information on this option.
By default `poetry` does not compile Python source files to bytecode during installation.
This speeds up the installation process, but the first execution may take a little more
time because Python then compiles source files to bytecode automatically.
If you want to compile source files to bytecode during installation,
you can use the `--compile` option:
```bash
poetry sync --compile
```
#### Options
* `--without`: The dependency groups to ignore.
* `--with`: The optional dependency groups to include.
* `--only`: The only dependency groups to include.
* `--only-root`: Install only the root project, exclude all dependencies.
* `--no-root`: Do not install the root package (your project).
* `--no-directory`: Skip all directory path dependencies (including transitive ones).
* `--dry-run`: Output the operations but do not execute anything (implicitly enables `--verbose`).
* `--extras (-E)`: Features to install (multiple values allowed).
* `--all-extras`: Install all extra features (conflicts with `--extras`).
* `--all-groups`: Install dependencies from all groups (conflicts with `--only`, `--with`, and `--without`).
* `--compile`: Compile Python source files to bytecode.
{{% note %}}
When `--only` is specified, `--with` and `--without` options are ignored.
{{% /note %}}
## update
In order to get the latest versions of the dependencies and to update the `poetry.lock` file,
you should use the `update` command.
```bash
poetry update
```
This will resolve all dependencies of the project, write the exact versions into `poetry.lock`,
and install them into your environment.
{{% note %}}
The `update` command **does not** modify your `pyproject.toml` file. It only updates the
`poetry.lock` file with the latest compatible versions based on the constraints already
defined in `pyproject.toml`. To change version constraints, use the `add` command instead.
{{% /note %}}
If you just want to update a few packages and not all, you can list them as such:
```bash
poetry update requests toml
```
Note that this will not update versions for dependencies outside their
[version constraints]({{< relref "dependency-specification#version-constraints" >}})
specified in the `pyproject.toml` file.
In other terms, `poetry update foo` will be a no-op if the version constraint
specified for `foo` is `~2.3` or `2.3` and `2.4` is available.
In order for `foo` to be updated, you must update the constraint, for example `^2.3`.
You can do this using the `add` command.
#### Options
* `--without`: The dependency groups to ignore.
* `--with`: The optional dependency groups to include.
* `--only`: The only dependency groups to include.
* `--dry-run` : Outputs the operations but will not execute anything (implicitly enables `--verbose`).
* `--lock` : Do not perform install (only update the lockfile).
* `--sync`: Synchronize the environment with the locked packages and the specified groups.
{{% note %}}
When `--only` is specified, `--with` and `--without` options are ignored.
{{% /note %}}
## version
This command shows the current version of the project or bumps the version of
the project and writes the new version back to `pyproject.toml` if a valid
bump rule is provided.
The new version should be a valid [PEP 440](https://peps.python.org/pep-0440/)
string or a valid bump rule: `patch`, `minor`, `major`, `prepatch`, `preminor`,
`premajor`, `prerelease`.
{{% note %}}
If you would like to use semantic versioning for your project, please see
[here]({{< relref "libraries#versioning" >}}).
{{% /note %}}
The table below illustrates the effect of these rules with concrete examples.
| rule | before | after |
| ---------- |---------|---------|
| major | 1.3.0 | 2.0.0 |
| minor | 2.1.4 | 2.2.0 |
| patch | 4.1.1 | 4.1.2 |
| premajor | 1.0.2 | 2.0.0a0 |
| preminor | 1.0.2 | 1.1.0a0 |
| prepatch | 1.0.2 | 1.0.3a0 |
| prerelease | 1.0.2 | 1.0.3a0 |
| prerelease | 1.0.3a0 | 1.0.3a1 |
| prerelease | 1.0.3b0 | 1.0.3b1 |
The option `--next-phase` allows the increment of prerelease phase versions.
| rule | before | after |
|-------------------------|----------|----------|
| prerelease --next-phase | 1.0.3a0 | 1.0.3b0 |
| prerelease --next-phase | 1.0.3b0 | 1.0.3rc0 |
| prerelease --next-phase | 1.0.3rc0 | 1.0.3 |
#### Options
* `--next-phase`: Increment the phase of the current version.
* `--short (-s)`: Output the version number only.
* `--dry-run`: Do not update pyproject.toml file.
================================================
FILE: docs/community.md
================================================
---
title: "Community"
draft: false
type: docs
layout: single
menu:
docs:
weight: 105
---
# Community
## Badge
For any projects using Poetry, you may add its official badge somewhere prominent like the README.
[](https://python-poetry.org/)
**Markdown**
```md
[](https://python-poetry.org/)
```
**reStructuredText**
```rst
.. image:: https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json
:alt: Poetry
:target: https://python-poetry.org/
```
================================================
FILE: docs/configuration.md
================================================
---
title: "Configuration"
draft: false
type: docs
layout: single
menu:
docs:
weight: 40
---
# Configuration
Poetry can be configured via the `config` command ([see more about its usage here]({{< relref "cli#config" >}} "config command documentation"))
or directly in the `config.toml` file that will be automatically created when you first run that command.
This file can typically be found in one of the following directories:
- macOS: `~/Library/Application Support/pypoetry`
- Windows: `%APPDATA%\pypoetry`
For Unix, we follow the XDG spec and support `$XDG_CONFIG_HOME`.
That means, by default `~/.config/pypoetry`.
## Local configuration
Poetry also provides the ability to have settings that are specific to a project
by passing the `--local` option to the `config` command.
```bash
poetry config virtualenvs.create false --local
```
{{% note %}}
Your local configuration of Poetry application is stored in the `poetry.toml` file,
which is separate from `pyproject.toml`.
{{% /note %}}
{{% note %}}
If a setting is defined in both `poetry.toml` (local/project) and `config.toml` (global),
the local/project configuration takes precedence over the global configuration.
{{% /note %}}
{{% warning %}}
Be mindful when checking in this file into your repository since it may contain user-specific or sensitive information.
{{% /warning %}}
## Listing the current configuration
To list the current configuration you can use the `--list` option
of the `config` command:
```bash
poetry config --list
```
which will give you something similar to this:
```toml
cache-dir = "/path/to/cache/directory"
virtualenvs.create = true
virtualenvs.in-project = null
virtualenvs.options.always-copy = true
virtualenvs.options.no-pip = false
virtualenvs.options.system-site-packages = false
virtualenvs.path = "{cache-dir}/virtualenvs" # /path/to/cache/directory/virtualenvs
virtualenvs.prompt = "{project_name}-py{python_version}"
virtualenvs.use-poetry-python = false
```
## Displaying a single configuration setting
If you want to see the value of a specific setting, you can
give its name to the `config` command
```bash
poetry config virtualenvs.path
```
For a full list of the supported settings see [Available settings](#available-settings).
## Adding or updating a configuration setting
To change or otherwise add a new configuration setting, you can pass
a value after the setting's name:
```bash
poetry config virtualenvs.path /path/to/cache/directory/virtualenvs
```
For a full list of the supported settings see [Available settings](#available-settings).
## Removing a specific setting
If you want to remove a previously set setting, you can use the `--unset` option:
```bash
poetry config virtualenvs.path --unset
```
The setting will then retrieve its default value.
## Using environment variables
Sometimes, in particular when using Poetry with CI tools, it's easier
to use environment variables and not have to execute configuration commands.
Poetry supports this and any setting can be set by using environment variables.
The environment variables must be prefixed by `POETRY_` and are comprised of the uppercase
name of the setting and with dots and dashes replaced by underscore, here is an example:
```bash
export POETRY_VIRTUALENVS_PATH=/path/to/virtualenvs/directory
```
This also works for secret settings, like credentials:
```bash
export POETRY_HTTP_BASIC_MY_REPOSITORY_PASSWORD=secret
```
## Migrate outdated configs
If Poetry renames or remove config options it might be necessary to migrate explicit set options. This is possible
by running:
```bash
poetry config --migrate
```
If you need to migrate a local config run:
```bash
poetry config --migrate --local
```
## Default Directories
Poetry uses the following default directories:
### Config Directory
- Linux: `$XDG_CONFIG_HOME/pypoetry` or `~/.config/pypoetry`
- Windows: `%APPDATA%\pypoetry`
- macOS: `~/Library/Application Support/pypoetry`
You can override the config directory by setting the `POETRY_CONFIG_DIR` environment variable.
### Data Directory
- Linux: `$XDG_DATA_HOME/pypoetry` or `~/.local/share/pypoetry`
- Windows: `%APPDATA%\pypoetry`
- macOS: `~/Library/Application Support/pypoetry`
You can override the data directory by setting the `POETRY_DATA_DIR` or `POETRY_HOME` environment variables. If `POETRY_HOME` is set, it will be given higher priority.
### Cache Directory
- Linux: `$XDG_CACHE_HOME/pypoetry` or `~/.cache/pypoetry`
- Windows: `%LOCALAPPDATA%\pypoetry`
- macOS: `~/Library/Caches/pypoetry`
You can override the cache directory by setting the `POETRY_CACHE_DIR` environment variable.
## Available settings
### `cache-dir`
**Type**: `string`
**Environment Variable**: `POETRY_CACHE_DIR`
The path to the cache directory used by Poetry.
Defaults to one of the following directories:
- macOS: `~/Library/Caches/pypoetry`
- Windows: `C:\Users\\AppData\Local\pypoetry\Cache`
- Unix: `~/.cache/pypoetry`
### `data-dir`
**Type**: `string`
**Environment Variable**: `POETRY_DATA_DIR`
The path to the data directory used by Poetry.
- Linux: `$XDG_DATA_HOME/pypoetry` or `~/.local/share/pypoetry`
- Windows: `%APPDATA%\pypoetry`
- macOS: `~/Library/Application Support/pypoetry`
You can override the data directory by setting the `POETRY_DATA_DIR` or `POETRY_HOME` environment variables. If
`POETRY_HOME` is set, it will be given higher priority.
### `installer.max-workers`
**Type**: `int`
**Default**: `number_of_cores + 4`
**Environment Variable**: `POETRY_INSTALLER_MAX_WORKERS`
*Introduced in 1.2.0*
Set the maximum number of workers while using the parallel installer.
The `number_of_cores` is determined by `os.cpu_count()`.
If this raises a `NotImplementedError` exception, `number_of_cores` is assumed to be 1.
If this configuration parameter is set to a value greater than `number_of_cores + 4`,
the number of maximum workers is still limited at `number_of_cores + 4`.
{{% note %}}
This configuration is ignored when `installer.parallel` is set to `false`.
{{% /note %}}
### `installer.no-binary`
**Type**: `string | boolean`
**Default**: `false`
**Environment Variable**: `POETRY_INSTALLER_NO_BINARY`
*Introduced in 1.2.0*
When set, this configuration allows users to disallow the use of binary distribution format for all, none or specific packages.
| Configuration | Description |
|------------------------|------------------------------------------------------------|
| `:all:` or `true` | Disallow binary distributions for all packages. |
| `:none:` or `false` | Allow binary distributions for all packages. |
| `package[,package,..]` | Disallow binary distributions for specified packages only. |
If both `installer.no-binary` and `installer.only-binary` are set, explicit package names will take precedence over `:all:`.
{{% note %}}
As with all configurations described here, this is a user specific configuration. This means that this
is not taken into consideration when a lockfile is generated or dependencies are resolved. This is
applied only when selecting which distribution for dependency should be installed into a Poetry managed
environment.
{{% /note %}}
{{% note %}}
For project specific usage, it is recommended that this be configured with the `--local`.
```bash
poetry config --local installer.no-binary :all:
```
{{% /note %}}
{{% note %}}
For CI or container environments using [environment variable](#using-environment-variables)
to configure this might be useful.
```bash
export POETRY_INSTALLER_NO_BINARY=:all:
```
{{% /note %}}
{{% warning %}}
Unless this is required system-wide, if configured globally, you could encounter slower install times
across all your projects if incorrectly set.
{{% /warning %}}
### `installer.only-binary`
**Type**: `string | boolean`
**Default**: `false`
**Environment Variable**: `POETRY_INSTALLER_ONLY_BINARY`
*Introduced in 2.0.0*
When set, this configuration allows users to enforce the use of binary distribution format for all, none or
specific packages.
{{% note %}}
Please refer to [`installer.no-binary`]({{< relref "configuration#installerno-binary" >}}) for information on allowed
values, usage instructions and warnings.
{{% /note %}}
### `installer.parallel`
**Type**: `boolean`
**Default**: `true`
**Environment Variable**: `POETRY_INSTALLER_PARALLEL`
*Introduced in 1.1.4*
Use parallel execution when using the new (`>=1.1.0`) installer.
### `installer.build-config-settings.`
**Type**: `Serialised JSON with string or list of string properties`
**Default**: `None`
**Environment Variable**: `POETRY_INSTALLER_BUILD_CONFIG_SETTINGS_`
*Introduced in 2.1.0*
{{% warning %}}
This is an **experimental** configuration and can be subject to changes in upcoming releases until it is considered
stable.
{{% /warning %}}
Configure [PEP 517 config settings](https://peps.python.org/pep-0517/#config-settings) to be passed to a package's
build backend if it has to be built from a directory or vcs source; or a source distribution during installation.
This is only used when a compatible binary distribution (wheel) is not available for a package. This can be used along
with [`installer.no-binary`]({{< relref "configuration#installerno-binary" >}}) option to force a build with these
configurations when a dependency of your project with the specified name is being installed.
{{% note %}}
Poetry does not offer a similar option in the `pyproject.toml` file as these are, in majority of cases, not universal
and vary depending on the target installation environment.
If you want to use a project specific configuration it is recommended that this configuration be set locally, in your
project's `poetry.toml` file.
```bash
poetry config --local installer.build-config-settings.grpcio \
'{"CC": "gcc", "--global-option": ["--some-global-option"], "--build-option": ["--build-option1", "--build-option2"]}'
```
If you want to modify a single key, you can do, by setting the same key again.
```bash
poetry config --local installer.build-config-settings.grpcio \
'{"CC": "g++"}'
```
{{% /note %}}
### `requests.max-retries`
**Type**: `int`
**Default**: `0`
**Environment Variable**: `POETRY_REQUESTS_MAX_RETRIES`
*Introduced in 2.0.0*
Set the maximum number of retries in an unstable network.
This setting has no effect if the server does not support HTTP range requests.
### `installer.re-resolve`
**Type**: `boolean`
**Default**: `false`
**Environment Variable**: `POETRY_INSTALLER_RE_RESOLVE`
*Changed default from `true` to `false` in 2.3.0*
*Introduced in 2.0.0*
If the config option is _not_ set and the lock file is at least version 2.1
(created by Poetry 2.0 or above), the installer will not re-resolve dependencies
but evaluate the locked markers to decide which of the locked dependencies have to
be installed into the target environment.
### `python.installation-dir`
**Type**: `string`
**Default**: `{data-dir}/python`
**Environment Variable**: `POETRY_PYTHON_INSTALLATION_DIR`
*Introduced in 2.1.0*
The directory in which Poetry managed Python versions are installed to.
### `solver.lazy-wheel`
**Type**: `boolean`
**Default**: `true`
**Environment Variable**: `POETRY_SOLVER_LAZY_WHEEL`
*Introduced in 1.8.0*
Do not download entire wheels to extract metadata but use
[HTTP range requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests)
to only download the METADATA files of wheels.
Especially with slow network connections, this setting can speed up dependency resolution significantly.
If the cache has already been filled or the server does not support HTTP range requests,
this setting makes no difference.
### `system-git-client`
**Type**: `boolean`
**Default**: `false`
**Environment Variable**: `POETRY_SYSTEM_GIT_CLIENT`
*Renamed to `system-git-client` in 2.0.0*
*Introduced in 1.2.0 as `experimental.system-git-client`*
Use system git client backend for git related tasks.
Poetry uses `dulwich` by default for git related tasks to not rely on the availability of a git client.
If you encounter any problems with it, set to `true` to use the system git backend.
### `virtualenvs.create`
**Type**: `boolean`
**Default**: `true`
**Environment Variable**: `POETRY_VIRTUALENVS_CREATE`
Create a new virtual environment if one doesn't already exist.
If set to `false`, Poetry will not create a new virtual environment. If it detects an already enabled virtual
environment or an existing one in `{cache-dir}/virtualenvs` or `{project-dir}/.venv` it will
install dependencies into them, otherwise it will install dependencies into the systems python environment.
{{% note %}}
If Poetry detects it's running within an activated virtual environment, it will never create a new virtual environment,
regardless of the value set for `virtualenvs.create`.
{{% /note %}}
{{% note %}}
Be aware that installing dependencies into the system environment likely upgrade or uninstall existing packages and thus
break other applications. Installing additional Python packages after installing the project might break the Poetry
project in return.
This is why it is recommended to always create a virtual environment. This is also true in Docker containers, as they
might contain additional Python packages as well.
{{% /note %}}
### `virtualenvs.in-project`
**Type**: `boolean`
**Default**: `None`
**Environment Variable**: `POETRY_VIRTUALENVS_IN_PROJECT`
Create the virtualenv inside the project's root directory.
If not set explicitly, `poetry` by default will create a virtual environment under
`{cache-dir}/virtualenvs` or use the `{project-dir}/.venv` directory if one already exists.
If set to `true`, the virtualenv will be created and expected in a folder named
`.venv` within the root directory of the project.
{{% note %}}
If a virtual environment has already been created for the project under `{cache-dir}/virtualenvs`, setting this variable to `true` will not cause `poetry` to create or use a local virtual environment.
In order for this setting to take effect for a project already in that state, you must delete the virtual environment folder located in `{cache-dir}/virtualenvs`.
You can find out where the current project's virtual environment (if there is one) is stored
with the command `poetry env info --path`.
{{% /note %}}
If set to `false`, `poetry` will ignore any existing `.venv` directory.
### `virtualenvs.options.always-copy`
**Type**: `boolean`
**Default**: `false`
**Environment Variable**: `POETRY_VIRTUALENVS_OPTIONS_ALWAYS_COPY`
*Introduced in 1.2.0*
If set to `true` the `--always-copy` parameter is passed to `virtualenv` on creation of the virtual environment, so that
all needed files are copied into it instead of symlinked.
### `virtualenvs.options.no-pip`
**Type**: `boolean`
**Default**: `false`
**Environment Variable**: `POETRY_VIRTUALENVS_OPTIONS_NO_PIP`
*Introduced in 1.2.0*
If set to `true` the `--no-pip` parameter is passed to `virtualenv` on creation of the virtual environment. This means
when a new virtual environment is created, `pip` will not be installed in the environment.
{{% note %}}
Poetry, for its internal operations, uses the `pip` wheel embedded in the `virtualenv` package installed as a dependency
in Poetry's runtime environment. If a user runs `poetry run pip` when this option is set to `true`, the `pip` the
embedded instance of `pip` is used.
You can safely set this to `true`, if you desire a virtual environment with no additional packages.
This is desirable for production environments.
{{% /note %}}
### `virtualenvs.options.system-site-packages`
**Type**: `boolean`
**Default**: `false`
**Environment Variable**: `POETRY_VIRTUALENVS_OPTIONS_SYSTEM_SITE_PACKAGES`
Give the virtual environment access to the system site-packages directory.
Applies on virtualenv creation.
### `virtualenvs.path`
**Type**: `string`
**Default**: `{cache-dir}/virtualenvs`
**Environment Variable**: `POETRY_VIRTUALENVS_PATH`
Directory where virtual environments will be created.
{{% note %}}
This setting controls the global virtual environment storage path. It most likely will not be useful at the local level. To store virtual environments in the project root, see `virtualenvs.in-project`.
{{% /note %}}
### `virtualenvs.prompt`
**Type**: `string`
**Default**: `{project_name}-py{python_version}`
**Environment Variable**: `POETRY_VIRTUALENVS_PROMPT`
*Introduced in 1.2.0*
Format string defining the prompt to be displayed when the virtual environment is activated.
The variables `project_name` and `python_version` are available for formatting.
### `virtualenvs.use-poetry-python`
**Type**: `boolean`
**Default**: `false`
**Environment Variable**: `POETRY_VIRTUALENVS_USE_POETRY_PYTHON`
*Introduced in 2.0.0*
By default, Poetry will use the activated Python version to create a new virtual environment.
If set to `true`, the Python version used during Poetry installation is used.
### `repositories..url`
**Type**: `string`
**Environment Variable**: `POETRY_REPOSITORIES__URL`
Set the repository URL for ``.
See [Publishable Repositories]({{< relref "repositories#publishable-repositories" >}}) for more information.
### `http-basic..[username|password]`
**Type**: `string`
**Environment Variables**: `POETRY_HTTP_BASIC__USERNAME`, `POETRY_HTTP_BASIC__PASSWORD`
Set repository credentials (`username` and `password`) for ``.
See [Repositories - Configuring credentials]({{< relref "repositories#configuring-credentials" >}})
for more information.
### `pypi-token.`
**Type**: `string`
**Environment Variable**: `POETRY_PYPI_TOKEN_`
Set repository credentials (using an API token) for ``.
See [Repositories - Configuring credentials]({{< relref "repositories#configuring-credentials" >}})
for more information.
### `certificates..cert`
**Type**: `string | boolean`
**Environment Variable**: `POETRY_CERTIFICATES__CERT`
Set custom certificate authority for repository ``.
See [Repositories - Configuring credentials - Custom certificate authority]({{< relref "repositories#custom-certificate-authority-and-mutual-tls-authentication" >}})
for more information.
This configuration can be set to `false`, if TLS certificate verification should be skipped for this
repository.
### `certificates..client-cert`
**Type**: `string`
**Environment Variable**: `POETRY_CERTIFICATES__CLIENT_CERT`
Set client certificate for repository ``.
See [Repositories - Configuring credentials - Custom certificate authority]({{< relref "repositories#custom-certificate-authority-and-mutual-tls-authentication" >}})
for more information.
### `keyring.enabled`
**Type**: `boolean`
**Default**: `true`
**Environment Variable**: `POETRY_KEYRING_ENABLED`
Enable the system keyring for storing credentials.
See [Repositories - Configuring credentials]({{< relref "repositories#configuring-credentials" >}})
for more information.
================================================
FILE: docs/contributing.md
================================================
---
title: "Contributing to Poetry"
draft: false
type: docs
layout: single
menu:
docs:
weight: 100
note: "Are you viewing this document on GitHub? For the best experience, view it on the website https://python-poetry.org/docs/contributing."
---
# Contributing to Poetry
First off, thanks for taking the time to contribute!
The following is a set of guidelines for contributing to Poetry on GitHub. These are mostly guidelines, not rules. Use
your best judgement, and feel free to propose changes to this document in a pull request.
## How to contribute
### Reporting bugs
This section guides you through submitting a bug report for Poetry.
Following these guidelines helps maintainers and the community understands your report, reproduces the behavior, and finds
related reports.
#### Before submitting a bug report
* **Check the [FAQ]** for a list of common questions and problems.
* **Check the [blog]** for release notes from recent releases, including steps for upgrading and known issues.
* **Check that your issue does not already exist** in the [issue tracker].
* **Make sure your issue is really a bug, and is not a support request or question** better suited for [Discussions]
or [Discord].
* **Try running your commands with the** `--no-cache` **flag**.
* **Try clearing your cache with** `poetry cache clear --all PyPI` **and rerunning your commands**.
{{% note %}}
If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and
include a link to the original issue in the body of your new one.
{{% /note %}}
#### How do I submit a bug report?
Bugs concerning Poetry and poetry-core should be submitted to the main [issue tracker], using the correct
[issue template].
Explain the problem and make it easy for others to search for and understand:
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in as many details as possible.
* **Describe the behavior you observed after following the steps** and point out how this is a bug.
* **Explain which behavior you expected to see instead and why.**
* **If the problem involves an unexpected error being raised**, execute the problematic command in **debug** mode
(with `-vvv` flag).
Provide detailed steps for reproduction of your issue:
* **Provide your pyproject.toml file** in a [Gist](https://gist.github.com), pastebin, or example repository after
removing potential private information like private package repositories or names.
* **Provide specific examples to demonstrate the steps to reproduce the issue**. This could be an example repository, a
sequence of steps run in a container, or just a pyproject.toml for very simple cases.
* **Are you unable to reliably reproduce the issue?** If so, provide details about how often the problem happens
and under which conditions it normally happens.
Provide more context by answering these questions:
* **Did the problem start happening recently** (e.g., after updating to a new version of Poetry) or was this always a
problem?
* If the problem started happening recently, **can you reproduce the problem in an older version of Poetry?** What's the
most recent version in which the problem does not happen?
* **Is there anything exotic or unusual about your environment?** This could include use of special container images,
newer CPU architectures like Apple Silicon, or corporate proxies that intercept or modify your network traffic.
Include details about your configuration and environment:
* **Which version of Poetry are you using?** You can get the exact version by running `poetry --version`.
* **What version of Python is being used to run Poetry?** Execute the `poetry debug info` to get this information.
* **What's the name and version of the OS you're using?** Examples include Ubuntu 22.04 or macOS 12.6.
To give others the best chance to understand and reproduce your issue, please be sure to put extra effort into your
reproduction steps. You can both rule out local configuration issues on your end, and ensure others can cleanly
reproduce your issue if attempt all reproductions in a pristine container (or VM), and provide the steps you performed
inside that container/VM in your issue report.
### Suggesting enhancements
This section guides you through submitting an enhancement suggestion for Poetry, including completely new features as
well as improvements to existing functionality. Following these guidelines helps maintainers and the community
understand your suggestion and find related suggestions.
#### Before submitting a suggested enhancement
* **Check the [FAQ]** for a list of common questions and problems.
* **Check that your issue does not already exist** in the [issue tracker].
#### How do I submit a suggested enhancement?
Suggested enhancements concerning Poetry and poetry-core should be submitted to the main [issue tracker], using the
correct [issue template].
* **Use a clear and descriptive title** for the issue to identify the suggestion.
* **Provide a detailed description of the proposed enhancement**, with specific steps or examples when possible.
* **Describe the current behavior** and **explain which behavior you would like to see instead**, and why.
### Documentation contributions
One of the simplest ways to get started contributing to a project is through improving documentation. Poetry is
constantly evolving, and this means that sometimes our documentation has gaps. You can help by adding missing sections,
editing the existing content to be more accessible, or creating new content such as tutorials, FAQs, etc.
{{% note %}}
GitHub [Discussions](https://github.com/python-poetry/poetry/discussions) and the
[kind/question label](https://github.com/python-poetry/poetry/labels/kind/question) are excellent sources for FAQ
candidates.
{{% /note %}}
Issues pertaining to the documentation are usually marked with the [area/docs label], which will also trigger a preview
of the changes as rendered by this website.
### Code contributions
#### Picking an issue
{{% note %}}
If you are a first time contributor, and are looking for an issue to take on, you might want to look for
at the [contributing page](https://github.com/python-poetry/poetry/contribute) for candidates. We do our best to curate
good issues for first-time contributors there, but do fall behind -- so if you don't see anything good, feel free to
ask.
{{% /note %}}
If you would like to take on an issue, feel free to comment on the issue tagging `@python-poetry/triage`.
We are more than happy to discuss solutions on the issue. If you would like help with navigating the code base, are
looking for something to work on, or want feedback on a design or change, join us on our [Discord server][Discord] or
start a [Discussion][Discussions].
#### Local development
Poetry is developed using Poetry. Refer to the [documentation] to install Poetry in your local environment.
{{% note %}}
Poetry's development toolchain requires Python 3.9 or newer.
{{% /note %}}
You should first fork the Poetry repository and then clone it locally, so that you can make pull requests against the
project. If you are new to Git and pull request-based development, GitHub provides a
[guide](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) you will find helpful.
Next, you should install Poetry's dependencies, and run the test suite to make sure everything is working as expected:
```bash
poetry install
poetry run pytest
```
{{% note %}}
If you want to see the coverage stats after the tests are complete, use:
```bash
poetry run pytest --cov=src/poetry --cov-report term
```
{{% /note %}}
When you contribute to Poetry, automated tools will be run to make sure your code is suitable to be merged. Besides
pytest, you will need to make sure your code typechecks properly using [mypy](http://mypy-lang.org/):
```bash
poetry run mypy
```
Finally, a great deal of linting tools are run on your code, to try and ensure consistent code style and root out common
mistakes. The [pre-commit](https://pre-commit.com/) tool is used to install and run these tools, and requires one-time
setup:
```bash
poetry run pre-commit install
```
pre-commit will now run and check your code every time you make a commit. By default, it will only run on changed files,
but you can run it on all files manually (this may be useful if you altered the pre-commit config):
```bash
poetry run pre-commit run --all-files
```
#### Pull requests
* Fill out the pull request body completely and describe your changes as accurately as possible. The pull request body
should be kept up to date as it will usually form the base for the final merge commit and the changelog entry.
* Be sure that your pull request contains tests that cover the changed or added code. Tests are generally required for
code be to be considered mergeable, and code without passing tests will not be merged.
* Ensure your pull request passes the mypy and pre-commit checks. Remember that you can run these tools locally
instead of relying on remote CI.
* If your changes warrant a documentation change, the pull request must also update the documentation. Make sure to
review the documentation preview generated by CI for any rendering issues.
{{% note %}}
Make sure your branch is [rebased](https://docs.github.com/en/get-started/using-git/about-git-rebase) against the latest
base branch. A maintainer might ask you to ensure the branch is up-to-date prior to merging your pull request
(especially if there have been CI changes on the base branch), and will also ask you to fix any conflicts.
{{% /note %}}
All pull requests, unless otherwise instructed, need to be first accepted into the `main` branch. Maintainers will
generally decide if any backports to other branches are required, and carry them out as needed.
### Issue triage
{{% note %}}
If you have an issue that hasn't had any attention, you can ping us `@python-poetry/triage` on the issue. Please give us
reasonable time to get to your issue first, and avoid pinging any individuals directly, especially if they are not part
of the Poetry team.
{{% /note %}}
If you are helping with the triage of reported issues, this section provides some useful information to assist you in
your contribution.
#### Triage steps
1. Determine what area and versions of Poetry the issue is related to, and set the appropriate labels (e.g.
`version/x.x.x`, `area/docs`, `area/venv`), and remove the `status/triage` label.
2. If requested information (such as debug logs, pyproject.toml, etc.) is not provided and is relevant, request it from
the author.
1. Set the `status/waiting-on-response` label while waiting to hear back from the author.
3. Attempt to reproduce the issue with the reported Poetry version or request further clarification from the author.
4. Ensure the issue is not already resolved. Try reproducing it on the latest stable release, the latest prerelease (if
present), and the development branch.
5. If the issue cannot be reproduced,
1. request more reproduction steps and clarification from the issue's author,
2. set the `status/needs-reproduction` label,
3. close the issue if there is no reproduction forthcoming.
6. If the issue can be reproduced,
1. comment on the issue confirming so,
2. set the `status/confirmed` label,
3. if possible, identify the root cause of the issue,
4. if interested, attempt to fix it via a pull request.
#### Multiple versions
When trying to reproduce issues, you often want to use multiple versions of Poetry at the same time.
[pipx](https://pypa.github.io/pipx/) makes this easy to do:
```sh
pipx install --suffix @1.2.1 'poetry==1.2.1'
pipx install --suffix @1.3.0rc1 'poetry==1.3.0rc1'
pipx install --suffix @main 'poetry @ git+https://github.com/python-poetry/poetry'
pipx install --suffix @local '/path/to/local/clone/of/poetry'
# now you can use any of the chosen versions of Poetry with their configured suffix, e.g.
poetry@main --version
```
{{% note %}}
Do not forget to `pipx upgrade poetry@main` before using it, to make sure you have the latest changes.
{{% /note %}}
{{% note %}}
This mechanism can also be used to test pull requests by using GitHub's pull request remote refs:
```sh
pipx install --suffix @pr1234 git+https://github.com/python-poetry/poetry.git@refs/pull/1234/head
```
{{% /note %}}
[Blog]: {{< ref "/blog" >}}
[Documentation]: {{< ref "/docs" >}}
[FAQ]: {{< relref "faq" >}}
[Issue Tracker]: https://github.com/python-poetry/poetry/issues
[area/docs label]: https://github.com/python-poetry/poetry/labels/area/docs
[kind/question label]: https://github.com/python-poetry/poetry/labels/kind/question
[Issue Template]: https://github.com/python-poetry/poetry/issues/new/choose
[Discussions]: https://github.com/python-poetry/poetry/discussions
[Discord]: https://discord.com/invite/awxPgve
================================================
FILE: docs/dependency-specification.md
================================================
---
title: "Dependency specification"
draft: false
type: docs
layout: single
menu:
docs:
weight: 70
---
# Dependency specification
Dependencies for a project can be specified in various forms, which depend on the type
of the dependency and on the optional constraints that might be needed for it to be installed.
## `project.dependencies` and `tool.poetry.dependencies`
Prior Poetry 2.0, dependencies had to be declared in the `tool.poetry.dependencies`
section of the `pyproject.toml` file.
```toml
[tool.poetry.dependencies]
requests = "^2.13.0"
```
With Poetry 2.0, you should consider using the `project.dependencies` section instead.
```toml
[project]
# ...
dependencies = [
"requests (>=2.23.0,<3.0.0)"
]
```
While dependencies in `tool.poetry.dependencies` are specified using toml tables,
dependencies in `project.dependencies` are specified as strings according
to [PEP 508](https://peps.python.org/pep-0508/).
In many cases, `tool.poetry.dependencies` can be replaced with `project.dependencies`.
However, there are some cases where you might still need to use `tool.poetry.dependencies`.
For example, if you want to define additional information that is not required for building
but only for locking (for example, an explicit source), you can enrich dependency
information in the `tool.poetry` section.
```toml
[project]
# ...
dependencies = [
"requests>=2.13.0",
]
[tool.poetry.dependencies]
requests = { source = "private-source" }
```
When both are specified, `project.dependencies` are used for metadata when building the project,
`tool.poetry.dependencies` is only used to enrich `project.dependencies` for locking.
Alternatively, you can add `dependencies` to `dynamic` and define your dependencies
completely in the `tool.poetry` section. Using only the `tool.poetry` section might
make sense in non-package mode when you will not build an sdist or a wheel.
```toml
[project]
# ...
dynamic = [ "dependencies" ]
[tool.poetry.dependencies]
requests = { version = ">=2.13.0", source = "private-source" }
```
{{% note %}}
Another use case for `tool.poetry.dependencies` are relative path dependencies
since `project.dependencies` only support absolute paths.
{{% /note %}}
{{% note %}}
Only main dependencies can be specified in the `project` section.
Other [Dependency groups]({{< relref "managing-dependencies#dependency-groups" >}})
must still be specified in the `tool.poetry` section.
{{% /note %}}
## Version constraints
### Compatible release requirements
**Compatible release requirements** specify a minimal version with the ability to update to later versions of the same level.
For example, if you specify a major, minor, and patch version, only patch-level changes are allowed.
If you only specify a major, and minor version, then minor- and patch-level changes are allowed.
`~=1.2.3` is an example of a compatible release requirement.
| Requirement | Versions allowed |
| ----------- | ---------------- |
| ~=1.2.3 | >=1.2.3 <1.3.0 |
| ~=1.2 | >=1.2.0 <2.0.0 |
### Wildcard requirements
**Wildcard requirements** allow for the latest (dependency-dependent) version where the wildcard is positioned.
`*`, `1.*` and `1.2.*` are examples of wildcard requirements.
| Requirement | Versions allowed |
| ----------- | ---------------- |
| * | >=0.0.0 |
| 1.* | >=1.0.0 <2.0.0 |
| 1.2.* | >=1.2.0 <1.3.0 |
### Inequality requirements
**Inequality requirements** allow manually specifying a version range or an exact version to depend on.
Here are some examples of inequality requirements:
```text
>= 1.2.0
> 1
< 2
!= 1.2.3
```
#### Multiple requirements
Multiple version requirements can also be separated with a comma, e.g. `>= 1.2, < 1.5`.
### Exact requirements
You can specify the exact version of a package.
`1.2.3` is an example of an exact version specification.
This will tell Poetry to install this version and this version only.
If other dependencies require a different version, the solver will ultimately fail and abort any installation or update procedures.
Exact versions can also be specified with `==` according to [PEP 440](https://peps.python.org/pep-0440/).
`==1.2.3` is an example of this.
### Caret requirements
{{% warning %}}
Not supported in `project.dependencies`.
When using `poetry add` such constraints are automatically converted into an equivalent constraint.
{{% /warning %}}
**Caret requirements** allow [SemVer](https://semver.org/) compatible updates to a specified version. An update is allowed if the new version number does not modify the left-most non-zero digit in the major, minor, patch grouping. For instance, if we previously ran `poetry add requests@^2.13.0` and wanted to update the library and ran `poetry update requests`, poetry would update us to version `2.14.0` if it was available, but would not update us to `3.0.0`. If instead, we had specified the version string as `^0.1.13`, poetry would update to `0.1.14` but not `0.2.0`. `0.0.x` is not considered compatible with any other version.
Here are some more examples of caret requirements and the versions that would be allowed with them:
| Requirement | Versions allowed |
| ----------- | ---------------- |
| ^1.2.3 | >=1.2.3 <2.0.0 |
| ^1.2 | >=1.2.0 <2.0.0 |
| ^1 | >=1.0.0 <2.0.0 |
| ^0.2.3 | >=0.2.3 <0.3.0 |
| ^0.0.3 | >=0.0.3 <0.0.4 |
| ^0.0 | >=0.0.0 <0.1.0 |
| ^0 | >=0.0.0 <1.0.0 |
### Tilde requirements
{{% warning %}}
Not supported in `project.dependencies`.
When using `poetry add` such constraints are automatically converted into an equivalent constraint.
{{% /warning %}}
**Tilde requirements** specify a minimal version with some ability to update.
If you specify a major, minor, and patch version or only a major and minor version, only patch-level changes are allowed.
If you only specify a major version, then minor- and patch-level changes are allowed.
`~1.2.3` is an example of a tilde requirement.
| Requirement | Versions allowed |
| ----------- | ---------------- |
| ~1.2.3 | >=1.2.3 <1.3.0 |
| ~1.2 | >=1.2.0 <1.3.0 |
| ~1 | >=1.0.0 <2.0.0 |
### Using the `@` operator
When adding dependencies via `poetry add`, you can use the `@` operator.
This is understood similarly to the `==` syntax, but also allows prefixing any
specifiers that are valid in `pyproject.toml`. For example:
```shell
poetry add "django@^4.0.0"
```
The above would translate to the following entry in `pyproject.toml`:
{{< tabs tabTotal="2" tabID1="at-project" tabID2="at-poetry" tabName1="[project]" tabName2="[tool.poetry]">}}
{{< tab tabID="at-project" >}}
```toml
[project]
# ...
dependencies = [
"django (>=4.0.0,<5.0.0)",
]
```
{{< /tab >}}
{{< tab tabID="at-poetry" >}}
```toml
[tool.poetry.dependencies]
django = "^4.0.0"
```
{{< /tab >}}
{{< /tabs >}}
The special keyword `latest` is also understood by the `@` operator:
```shell
poetry add django@latest
```
The above would translate to the following entry in `pyproject.toml`, assuming the latest release of `django` is `5.1.3`:
{{< tabs tabTotal="2" tabID1="at-latest-project" tabID2="at-latest-poetry" tabName1="[project]" tabName2="[tool.poetry]">}}
{{< tab tabID="at-latest-project" >}}
```toml
[project]
# ...
dependencies = [
"django (>=5.1.3,<6.0.0)",
]
```
{{< /tab >}}
{{< tab tabID="at-latest-poetry" >}}
```toml
[tool.poetry.dependencies]
django = "^5.1.3"
```
{{< /tab >}}
{{< /tabs >}}
#### Extras
Extras and `@` can be combined as one might expect (`package[extra]@version`):
```shell
poetry add django[bcrypt]@^4.0.0
```
## `git` dependencies
To depend on a library located in a `git` repository,
the minimum information you need to specify is the location of the repository:
{{< tabs tabTotal="2" tabID1="git-project" tabID2="git-poetry" tabName1="[project]" tabName2="[tool.poetry]">}}
{{< tab tabID="git-project" >}}
```toml
[project]
# ...
dependencies = [
"requests @ git+https://github.com/requests/requests.git",
]
```
{{< /tab >}}
{{< tab tabID="git-poetry" >}}
In the `tool.poetry` section you use the `git` key:
```toml
[tool.poetry.dependencies]
requests = { git = "https://github.com/requests/requests.git" }
```
{{< /tab >}}
{{< /tabs >}}
Since we haven’t specified any other information,
Poetry assumes that we intend to use the latest commit on the `main` branch
to build our project.
You can explicitly specify which branch, commit hash or tagged ref should be used:
{{< tabs tabTotal="2" tabID1="git-rev-project" tabID2="git-rev-poetry" tabName1="[project]" tabName2="[tool.poetry]">}}
{{< tab tabID="git-rev-project" >}}
Append the information to the git url.
```toml
[project]
# ...
dependencies = [
"requests @ git+https://github.com/requests/requests.git@next",
"flask @ git+https://github.com/pallets/flask.git@38eb5d3b",
"numpy @ git+https://github.com/numpy/numpy.git@v0.13.2",
]
```
{{< /tab >}}
{{< tab tabID="git-rev-poetry" >}}
Combine the `git` key with the `branch`, `rev` or `tag` key respectively.
```toml
[tool.poetry.dependencies]
# Get the latest revision on the branch named "next"
requests = { git = "https://github.com/kennethreitz/requests.git", branch = "next" }
# Get a revision by its commit hash
flask = { git = "https://github.com/pallets/flask.git", rev = "38eb5d3b" }
# Get a revision by its tag
numpy = { git = "https://github.com/numpy/numpy.git", tag = "v0.13.2" }
```
{{< /tab >}}
{{< /tabs >}}
It's possible to add a package that is located in a subdirectory of the VCS repository.
{{< tabs tabTotal="2" tabID1="git-subdir-project" tabID2="git-subdir-poetry" tabName1="[project]" tabName2="[tool.poetry]">}}
{{< tab tabID="git-subdir-project" >}}
Provide the subdirectory as a URL fragment similarly to what [pip](https://pip.pypa.io/en/stable/topics/vcs-support/#url-fragments) provides.
```toml
[project]
# ...
dependencies = [
"subdir_package @ git+https://github.com/myorg/mypackage_with_subdirs.git#subdirectory=subdir"
]
```
{{< /tab >}}
{{< tab tabID="git-subdir-poetry" >}}
Use the `subdirectory` key in the `tool.poetry` section:
```toml
[tool.poetry.dependencies]
# Install a package named `subdir_package` from a folder called `subdir` within the repository
subdir_package = { git = "https://github.com/myorg/mypackage_with_subdirs.git", subdirectory = "subdir" }
```
{{< /tab >}}
{{< /tabs >}}
The corresponding `add` call looks like this:
```bash
poetry add "git+https://github.com/myorg/mypackage_with_subdirs.git#subdirectory=subdir"
```
To use an SSH connection, for example, in the case of private repositories, use the following example syntax:
{{< tabs tabTotal="2" tabID1="git-ssh-project" tabID2="git-ssh-poetry" tabName1="[project]" tabName2="[tool.poetry]">}}
{{< tab tabID="git-ssh-project" >}}
```toml
[project]
# ...
dependencies = [
"pendulum @ git+ssh://git@github.com/sdispater/pendulum.git"
]
```
{{< /tab >}}
{{< tab tabID="git-ssh-poetry" >}}
```toml
[tool.poetry.dependencies]
pendulum = { git = "git@github.com/sdispater/pendulum.git" }
```
{{< /tab >}}
{{< /tabs >}}
### Credentials for git dependencies
To use HTTP basic authentication with your git repositories, you can configure credentials similar to
how [repository credentials]({{< relref "repositories#configuring-credentials" >}}) are configured.
```bash
poetry config repositories.git-org-project https://github.com/org/project.git
poetry config http-basic.git-org-project username token
poetry add git+https://github.com/org/project.git
```
{{% note %}}
The default git client used is [Dulwich](https://www.dulwich.io/).
We fall back to legacy system git client implementation in cases where
[gitcredentials](https://git-scm.com/docs/gitcredentials) is used. This fallback will be removed in
a future release where `gitcredentials` helpers can be better supported natively.
In cases where you encounter issues with the default implementation, you may wish to
explicitly configure the use of the system git client via a shell subprocess call.
```bash
poetry config system-git-client true
```
{{% /note %}}
## `path` dependencies
{{< tabs tabTotal="2" tabID1="path-project" tabID2="path-poetry" tabName1="[project]" tabName2="[tool.poetry]">}}
{{< tab tabID="path-project" >}}
In the `project` section, you can only use absolute paths:
```toml
[project]
# directory
dependencies = [
"my-package @ file:///absolute/path/to/my-package"
]
# file
dependencies = [
"my-package @ file:///absolute/path/to/my-package/dist/my-package-0.1.0.tar.gz"
]
```
{{< /tab >}}
{{< tab tabID="path-poetry" >}}
To depend on a library located in a local directory or file,
you can use the `path` property:
```toml
[tool.poetry.dependencies]
# directory
my-package = { path = "../my-package/", develop = true }
# file
my-package = { path = "../my-package/dist/my-package-0.1.0.tar.gz" }
```
To install directory path dependencies in editable mode, use the `develop` keyword and set it to `true`.
{{< /tab >}}
{{< /tabs >}}
## `url` dependencies
`url` dependencies are libraries located on a remote archive.
{{< tabs tabTotal="2" tabID1="url-project" tabID2="url-poetry" tabName1="[project]" tabName2="[tool.poetry]">}}
{{< tab tabID="url-project" >}}
```toml
[project]
# ...
dependencies = [
"my-package @ https://example.com/my-package-0.1.0.tar.gz"
]
```
{{< /tab >}}
{{< tab tabID="url-poetry" >}}
Use the `url` property.
```toml
[tool.poetry.dependencies]
# directory
my-package = { url = "https://example.com/my-package-0.1.0.tar.gz" }
```
{{< /tab >}}
{{< /tabs >}}
The corresponding `add` call is:
```bash
poetry add https://example.com/my-package-0.1.0.tar.gz
```
## Dependency `extras`
You can specify [PEP-508 Extras](https://www.python.org/dev/peps/pep-0508/#extras)
for a dependency as shown here.
{{< tabs tabTotal="2" tabID1="extras-project" tabID2="extras-poetry" tabName1="[project]" tabName2="[tool.poetry]">}}
{{< tab tabID="extras-project" >}}
```toml
[project]
# ...
dependencies = [
"gunicorn[gevent] (>=20.1,<21.0)"
]
```
{{< /tab >}}
{{< tab tabID="extras-poetry" >}}
```toml
[tool.poetry.dependencies]
gunicorn = { version = "^20.1", extras = ["gevent"] }
```
{{< /tab >}}
{{< /tabs >}}
{{% note %}}
These activate extra defined for the dependency, to configure an optional dependency
for extras in your project refer to [`extras`]({{< relref "pyproject#extras" >}}).
{{% /note %}}
## `source` dependencies
{{% note %}}
It is not possible to define source dependencies in the `project` section.
{{% /note %}}
To depend on a package from an [alternate repository]({{< relref "repositories#installing-from-private-package-sources" >}}),
you can use the `source` property:
```toml
[[tool.poetry.source]]
name = "foo"
url = "https://foo.bar/simple/"
priority = "supplemental"
[tool.poetry.dependencies]
my-cool-package = { version = "*", source = "foo" }
```
with the corresponding `add` call:
```sh
poetry add my-cool-package --source foo
```
{{% note %}}
In this example, we expect `foo` to be configured correctly. See [using a private repository]({{< relref "repositories#installing-from-private-package-sources" >}})
for further information.
{{% /note %}}
## Python restricted dependencies
You can also specify that a dependency should be installed only for specific Python versions:
{{< tabs tabTotal="2" tabID1="python-restriction-project" tabID2="python-restriction-poetry" tabName1="[project]" tabName2="[tool.poetry]">}}
{{< tab tabID="python-restriction-project" >}}
```toml
[project]
# ...
dependencies = [
"tomli (>=2.0.1,<3.0) ; python_version < '3.11'",
"pathlib2 (>=2.2,<3.0) ; python_version >= '3.9' and python_version < '4.0'"
]
```
{{< /tab >}}
{{< tab tabID="python-restriction-poetry" >}}
```toml
[tool.poetry.dependencies]
tomli = { version = "^2.0.1", python = "<3.11" }
pathlib2 = { version = "^2.2", python = "^3.9" }
```
{{< /tab >}}
{{< /tabs >}}
## Using environment markers
If you need more complex install conditions for your dependencies,
Poetry supports [environment markers](https://www.python.org/dev/peps/pep-0508/#environment-markers):
{{< tabs tabTotal="2" tabID1="markers-project" tabID2="markers-poetry" tabName1="[project]" tabName2="[tool.poetry]">}}
{{< tab tabID="markers-project" >}}
```toml
[project]
# ...
dependencies = [
"pathlib2 (>=2.2,<3.0) ; python_version <= '3.4' or sys_platform == 'win32'"
]
```
{{< /tab >}}
{{< tab tabID="markers-poetry" >}}
Use the `markers` property:
```toml
[tool.poetry.dependencies]
pathlib2 = { version = "^2.2", markers = "python_version <= '3.4' or sys_platform == 'win32'" }
```
{{< /tab >}}
{{< /tabs >}}
### `extra` environment marker
Poetry populates the `extra` marker with each of the selected extras of the root package.
For example, consider the following dependency:
```toml
[project.optional-dependencies]
paths = [
"pathlib2 (>=2.2,<3.0) ; sys_platform == 'win32'"
]
```
`pathlib2` will be installed when you install your package with `--extras paths` on a `win32` machine.
#### Exclusive extras
{{% warning %}}
The first example will only work completely if you configure Poetry to not re-resolve for installation:
```bash
poetry config installer.re-resolve false
```
This was a new feature of Poetry 2.0 and became the default behavior in Poetry 2.3.
{{% /warning %}}
Keep in mind that all combinations of possible extras available in your project need to be compatible with each other.
This means that in order to use differing or incompatible versions across different combinations, you need to make your
extra markers *exclusive*. For example, the following installs PyTorch from one source repository with CPU versions
when the `cuda` extra is *not* specified, while the other installs from another repository with a separate version set
for GPUs when the `cuda` extra *is* specified:
```toml
[project]
name = "torch-example"
requires-python = ">=3.10"
dependencies = [
"torch (==2.3.1+cpu) ; extra != 'cuda'",
]
[project.optional-dependencies]
cuda = [
"torch (==2.3.1+cu118)",
]
[tool.poetry]
package-mode = false
[tool.poetry.dependencies]
torch = [
{ markers = "extra != 'cuda'", source = "pytorch-cpu"},
{ markers = "extra == 'cuda'", source = "pytorch-cuda"},
]
[[tool.poetry.source]]
name = "pytorch-cpu"
url = "https://download.pytorch.org/whl/cpu"
priority = "explicit"
[[tool.poetry.source]]
name = "pytorch-cuda"
url = "https://download.pytorch.org/whl/cu118"
priority = "explicit"
```
For the CPU case, we have to specify `"extra != 'cuda'"` because the version specified is not compatible with the
GPU (`cuda`) version.
This same logic applies when you want either-or extras:
```toml
[project]
name = "torch-example"
requires-python = ">=3.10"
[project.optional-dependencies]
cpu = [
"torch (==2.3.1+cpu)",
]
cuda = [
"torch (==2.3.1+cu118)",
]
[tool.poetry]
package-mode = false
[tool.poetry.dependencies]
torch = [
{ markers = "extra == 'cpu' and extra != 'cuda'", source = "pytorch-cpu"},
{ markers = "extra == 'cuda' and extra != 'cpu'", source = "pytorch-cuda"},
]
[[tool.poetry.source]]
name = "pytorch-cpu"
url = "https://download.pytorch.org/whl/cpu"
priority = "explicit"
[[tool.poetry.source]]
name = "pytorch-cuda"
url = "https://download.pytorch.org/whl/cu118"
priority = "explicit"
```
## Multiple constraints dependencies
Sometimes, one of your dependencies may have different version ranges depending
on the target Python versions.
Let's say you have a dependency on the package `foo` which is only compatible
with Python 3.6–3.7 up to version 1.9, and compatible with Python 3.8+ from version 2.0:
you would declare it like so:
{{< tabs tabTotal="2" tabID1="multiple-constraints-project" tabID2="multiple-constraints-poetry" tabName1="[project]" tabName2="[tool.poetry]">}}
{{< tab tabID="multiple-constraints-project" >}}
```toml
[project]
# ...
dependencies = [
"foo (<=1.9) ; python_version >= '3.6' and python_version < '3.8'",
"foo (>=2.0,<3.0) ; python_version >= '3.8'"
]
```
{{< /tab >}}
{{< tab tabID="multiple-constraints-poetry" >}}
```toml
[tool.poetry.dependencies]
foo = [
{version = "<=1.9", python = ">=3.6,<3.8"},
{version = "^2.0", python = ">=3.8"}
]
```
{{< /tab >}}
{{< /tabs >}}
{{% note %}}
The constraints **must** have different requirements (like `python`)
otherwise it will cause an error when resolving dependencies.
{{% /note %}}
### Combining git / url / path dependencies with source repositories
Direct origin (`git`/ `url`/ `path`) dependencies can satisfy the requirement of a dependency that
doesn't explicitly specify a source, even when mutually exclusive markers are used. For instance,
in the following example, the url package will also be a valid solution for the second requirement:
```toml
foo = [
{ platform = "darwin", url = "https://example.com/example-1.0-py3-none-any.whl" },
{ platform = "linux", version = "^1.0" },
]
```
Sometimes you may instead want to use a direct origin dependency for specific conditions
(i.e., a compiled package that is not available on PyPI for a certain platform/architecture) while
falling back on source repositories in other cases. In this case you should explicitly ask for your
dependency to be satisfied by another `source`. For example:
```toml
foo = [
{ platform = "darwin", url = "https://example.com/foo-1.0.0-py3-none-macosx_11_0_arm64.whl" },
{ platform = "linux", version = "^1.0", source = "pypi" },
]
```
## Expanded dependency specification syntax
In the case of more complex dependency specifications, you may find that you
end up with lines which are very long and difficult to read. In these cases,
you can shift from using "inline table" syntax to the "standard table" syntax.
An example where this might be useful is the following:
```toml
[tool.poetry.group.dev.dependencies]
black = {version = "19.10b0", allow-prereleases = true, python = "^3.7", markers = "platform_python_implementation == 'CPython'"}
```
As a single line, this is a lot to digest. To make this a bit easier to
work with, you can do the following:
```toml
[tool.poetry.group.dev.dependencies.black]
version = "19.10b0"
allow-prereleases = true
python = "^3.7"
markers = "platform_python_implementation == 'CPython'"
```
The same information is still present, and ends up providing the exact
same specification. It's simply split into multiple, slightly more readable,
lines.
### Handling of pre-releases
Per default, Poetry will prefer stable releases and only choose a pre-release
if no stable release satisfies a version constraint. In some cases, this may result in
a solution containing pre-releases even if another solution without pre-releases exists.
If you want to disallow pre-releases for a specific dependency,
you can set `allow-prereleases` to `false`. In this case, dependency resolution will
fail if there is no solution without choosing a pre-release.
If you want to prefer the latest version of a dependency even if it is a pre-release,
you can set `allow-prereleases` to `true` so that Poetry makes no distinction
between stable and pre-release versions during dependency resolution.
================================================
FILE: docs/faq.md
================================================
---
title: "FAQ"
draft: false
type: docs
layout: single
menu:
docs:
weight: 110
---
# FAQ
### Why is the dependency resolution process slow?
While the dependency resolver at the heart of Poetry is highly optimized and
should be fast enough for most cases, with certain sets of dependencies,
it can take time to find a valid solution.
This is due to the fact that not all libraries on PyPI have properly declared their metadata
and, as such, they are not available via the PyPI JSON API. At this point, Poetry has no choice
but to download the packages and inspect them to get the necessary information. This is an expensive
operation, both in bandwidth and time, which is why it seems this is a long process.
At the moment, there is no way around it. However, if you notice that Poetry
is downloading many versions of a single package, you can lessen the workload
by constraining that one package in your pyproject.toml more narrowly. That way,
Poetry does not have to sift through so many versions of it, which may speed up
the locking process considerably in some cases.
{{% note %}}
Once Poetry has cached the releases' information on your machine, the dependency resolution process
will be much faster.
{{% /note %}}
### What kind of versioning scheme does Poetry use for itself?
Poetry uses "major.minor.micro" version identifiers as mentioned in
[PEP 440](https://peps.python.org/pep-0440/#final-releases).
Version bumps are done similar to Python's versioning:
* A major version bump (incrementing the first number) is only done for breaking changes
if a deprecation cycle is not possible, and many users have to perform some manual steps
to migrate from one version to the next one.
* A minor version bump (incrementing the second number) may include new features as well
as new deprecations and drop features deprecated in an earlier minor release.
* A micro version bump (incrementing the third number) usually only includes bug fixes.
Deprecated features will not be dropped in a micro release.
### Why does Poetry not adhere to semantic versioning?
Because of its large user base, even small changes not considered relevant by most users
can turn out to be a breaking change for some users in hindsight.
Sticking to strict [semantic versioning](https://semver.org) and (almost) always bumping
the major version instead of the minor version does not seem desirable
since the minor version will not carry any meaning anymore.
### Are unbound version constraints a bad idea?
A version constraint without an upper bound such as `*` or `>=3.4` will allow updates to any future version of the dependency.
This includes major versions breaking backward compatibility.
Once a release of your package is published, you cannot tweak its dependencies anymore in case a dependency breaks BC
– you have to do a new release but the previous one stays broken.
(Users can still work around the broken dependency by restricting it by themselves.)
To avoid such issues, you can define an upper bound on your constraints,
which you can increase in a new release after testing that your package is compatible
with the new major version of your dependency.
For example, instead of using `>=3.4` you can use `^3.4` which allows all versions `<4.0`.
The `^` operator works very well with libraries following [semantic versioning](https://semver.org).
However, when defining an upper bound, users of your package are not able to update
a dependency beyond the upper bound even if it does not break anything
and is fully compatible with your package.
You have to release a new version of your package with an increased upper-bound first.
If your package is used as a library in other packages, it might be better to avoid
upper bounds and thus unnecessary dependency conflicts (unless you already know for sure
that the next release of the dependency will break your package).
If your package is used as an application, it might be worth defining an upper bound.
### Is tox supported?
**Yes**. Provided that you are using `tox` >= 4, you can use it in combination with
the PEP 517 compliant build system provided by Poetry. (With tox 3, you have to set the
[isolated build](https://tox.wiki/en/3.27.1/config.html#conf-isolated_build) option.)
So, in your `pyproject.toml` file, add this section if it does not already exist:
```toml
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
```
`tox` can be configured in multiple ways. It depends on what should be the code under test and which dependencies
should be installed.
#### Use case #1
```ini
[tox]
[testenv]
deps =
pytest
commands =
pytest tests/ --import-mode importlib
```
`tox` will create an `sdist` package of the project and uses `pip` to install it in a fresh environment.
Thus, dependencies are resolved by `pip`.
#### Use case #2
```ini
[tox]
[testenv]
allowlist_externals = poetry
commands_pre =
poetry install --no-root --sync
commands =
poetry run pytest tests/ --import-mode importlib
```
`tox` will create an `sdist` package of the project and uses `pip` to install it in a fresh environment.
Thus, dependencies are resolved by `pip` in the first place. But afterward, we run Poetry,
which will install the locked dependencies into the environment.
#### Use case #3
```ini
[tox]
[testenv]
skip_install = true
allowlist_externals = poetry
commands_pre =
poetry install
commands =
poetry run pytest tests/ --import-mode importlib
```
`tox` will not do any install. Poetry installs all the dependencies and the current package in editable mode.
Thus, tests are running against the local files and not the built and installed package.
#### Note about credentials
Note that `tox` does not forward the environment variables of your current shell session by default.
This may cause Poetry to not be able to install dependencies in the `tox` environments if you have configured
credentials using the system keyring on Linux systems or using environment variables in general.
You can use the `passenv` [configuration option](https://tox.wiki/en/latest/config.html#passenv) to forward the
required variables explicitly or `passenv = "*"` to forward all of them.
Linux systems may require forwarding the `DBUS_SESSION_BUS_ADDRESS` variable to allow access to the system keyring,
though this may vary between desktop environments.
Alternatively, you can disable the keyring completely:
```bash
poetry config keyring.enabled false
```
Be aware that this will cause Poetry to write passwords to plaintext config files.
You will need to set the credentials again after changing this setting.
### Is Nox supported?
Use the [`nox-poetry`](https://github.com/cjolowicz/nox-poetry) package to install locked versions of
dependencies specified in `poetry.lock` into [Nox](https://nox.thea.codes/en/stable/) sessions.
### I don't want Poetry to manage my virtual environments. Can I disable it?
While Poetry automatically creates virtual environments to always work isolated
from the global Python installation, there are rare scenarios where the use of a Poetry managed
virtual environment is not possible or preferred.
In this case, you can disable this feature by setting the `virtualenvs.create` setting to `false`:
```bash
poetry config virtualenvs.create false
```
{{% warning %}}
The recommended best practice, including when installing an application within a container, is to make
use of a virtual environment. This can also be managed by another tool.
The Poetry team strongly encourages the use of a virtual environment.
{{% /warning %}}
### Why is Poetry telling me that the current project's supported Python range is not compatible with one or more packages' Python requirements?
Unlike `pip`, Poetry doesn't resolve for just the Python in the current environment. Instead, it makes sure that a dependency
is resolvable within the given Python version range in `pyproject.toml`.
Assume you have the following `pyproject.toml`:
```toml
[tool.poetry.dependencies]
python = "^3.7"
```
This means your project aims to be compatible with any Python version >=3.7,<4.0. Whenever you try to add a dependency
whose Python requirement doesn't match the whole range, Poetry will tell you this, e.g.:
```
The current project's supported Python range (>=3.7.0,<4.0.0) is not compatible with some of the required packages Python requirement:
- scipy requires Python >=3.7,<3.11, so it will not be installable for Python >=3.11,<4.0.0
```
Usually you will want to match the supported Python range of your project with the upper bound of the failing dependency.
Alternatively, you can tell Poetry to install this dependency [only for a specific range of Python versions]({{< relref "dependency-specification#multiple-constraints-dependencies" >}}),
if you know that it's not needed in all versions.
If you do not want to set an upper bound in the metadata when building your project,
you can omit it in the `project` section and only set it in `tool.poetry.dependencies`:
```toml
[project]
# ...
requires-python = ">=3.7" # used for metadata when building the project
[tool.poetry.dependencies]
python = ">=3.7,<3.11" # used for locking dependencies
```
### Why does Poetry enforce PEP 440 versions?
This is done to be compliant with the broader Python ecosystem.
For example, if Poetry builds a distribution for a project that uses a version that is not valid, according to
[PEP 440](https://peps.python.org/pep-0440), third party tools will be unable to parse the version correctly.
### Poetry busts my Docker cache because it requires me to COPY my source files in before installing 3rd party dependencies
By default, running `poetry install ...` requires you to have your source files present (both the "root" package and any directory path dependencies you might have).
This interacts poorly with Docker's caching mechanisms because any change to a source file will make any layers (subsequent commands in your Dockerfile) re-run.
For example, you might have a Dockerfile that looks something like this:
```text
FROM python
COPY pyproject.toml poetry.lock .
COPY src/ ./src
RUN pip install poetry && poetry install --only main
```
As soon as *any* source file changes, the cache for the `RUN` layer will be invalidated, which forces all 3rd party dependencies (likely the slowest step out of these) to be installed again if you changed any files in `src/`.
To avoid this cache busting you can split this into two steps:
1. Install 3rd party dependencies.
2. Copy over your source code and install just the source code.
This might look something like this:
```text
FROM python
COPY pyproject.toml poetry.lock .
RUN pip install poetry && poetry install --only main --no-root --no-directory
COPY src/ ./src
RUN poetry install --only main
```
The two key options we are using here are `--no-root` (skips installing the project source) and `--no-directory` (skips installing any local directory path dependencies, you can omit this if you don't have any).
[More information on the options available for `poetry install`]({{< relref "cli#install" >}}).
### My requests are timing out!
Poetry's default HTTP request timeout is 15 seconds, the same as `pip`.
Similar to `PIP_REQUESTS_TIMEOUT`, the **experimental** environment variable `POETRY_REQUESTS_TIMEOUT`
can be set to alter this value.
### How do I migrate an existing Poetry project using `tools.poetry` section to use the new `project` section (PEP 621)?
{{% note %}}
Poetry `>=2.0.0` should seamlessly support both `tools.poetry` section only configuration as well using the `project` section. This
lets you decide when and if you would like to migrate to using the `project` section as [described by PyPA](https://packaging.python.org/en/latest/specifications/pyproject-toml/#declaring-project-metadata-the-project-table).
See documentation on [the `pyproject.toml` file]({{< relref "pyproject" >}}), for information specific to Poetry.
{{% /note %}}
Due to the nature of this change some manual changes to your `pyproject.toml` file is unavoidable in order start using the `project` section. The following tabs
show a transition example. If you wish to retain Poetry's richer [dependency specification]({{< relref "dependency-specification" >}}) syntax it is recommended that
you use dynamic dependencies as described in the second tab below.
{{< tabs tabTotal="3" tabID1="migrate-pep621-old" tabName1="Original" tabID2="migrate-pep621-new-dynamic" tabName2="Using Dynamic Dependencies" tabID3="migrate-pep621-new-static" tabName3="Using Static Dependencies">}}
{{< tab tabID="migrate-pep621-old" >}}
```toml
[tool.poetry]
name = "foobar"
version = "0.1.0"
description = ""
authors = ["Baz Qux "]
readme = "README.md"
packages = [{ include = "awesome", from = "src" }]
include = [{ path = "tests", format = "sdist" }]
homepage = "https://python-foobar.org/"
repository = "https://github.com/python-foobar/foobar"
documentation = "https://python-foobar.org/docs"
keywords = ["packaging", "dependency", "foobar"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules",
]
[tool.poetry.scripts]
foobar = "foobar.console.application:main"
[tool.poetry.dependencies]
python = "^3.13"
httpx = "^0.28.1"
[tool.poetry.group.dev.dependencies]
pre-commit = ">=2.10"
[tool.poetry.group.test.dependencies]
pytest = ">=8.0"
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
```
{{< /tab >}}
{{< tab tabID="migrate-pep621-new-static" >}}
```toml
[project]
name = "foobar"
version = "0.1.0"
description = ""
authors = [
{ name = "Baz Qux", email = "baz.qux@example.com" }
]
readme = "README.md"
requires-python = ">=3.13"
keywords = ["packaging", "dependency", "foobar"]
# classifiers property is dynamic because we want to create Python classifiers automatically
# dependencies are dynamic because we want to keep Poetry's rich dependency definition format
dynamic = ["classifiers", "dependencies"]
[project.urls]
homepage = "https://python-foobar.org/"
repository = "https://github.com/python-foobar/foobar"
documentation = "https://python-foobar.org/docs"
[project.scripts]
foobar = "foobar.console.application:main"
[tool.poetry]
requires-poetry = ">=2.0"
packages = [{ include = "foobar", from = "src" }]
include = [{ path = "tests", format = "sdist" }]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules",
]
[tool.poetry.dependencies]
httpx = "^0.28.1"
[tool.poetry.group.dev.dependencies]
pre-commit = ">=2.10"
[tool.poetry.group.test.dependencies]
pytest = ">=8.0"
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
```
{{< /tab >}}
{{< tab tabID="migrate-pep621-new-static" >}}
```toml
[project]
name = "foobar"
version = "0.1.0"
description = ""
authors = [
{ name = "Baz Qux", email = "baz.qux@example.com" }
]
readme = "README.md"
requires-python = ">=3.13"
keywords = ["packaging", "dependency", "foobar"]
# classifiers property is dynamic because we want to create Python classifiers automatically
dynamic = ["classifiers"]
dependencies = [
"httpx (>=0.28.1,<0.29.0)"
]
[project.urls]
homepage = "https://python-foobar.org/"
repository = "https://github.com/python-foobar/foobar"
documentation = "https://python-foobar.org/docs"
[project.scripts]
foobar = "foobar.console.application:main"
[tool.poetry]
requires-poetry = ">=2.0"
packages = [{ include = "foobar", from = "src" }]
include = [{ path = "tests", format = "sdist" }]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules",
]
[tool.poetry.group.dev.dependencies]
pre-commit = ">=2.10"
[tool.poetry.group.test.dependencies]
pytest = ">=8.0"
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
```
{{< /tab >}}
{{< /tabs >}}
{{% note %}}
- The `classifiers` property is dynamic to allow Poetry to create and manage Python classifiers in accordance with supported Python version.
- The `python` dependency, in this example was replaced with `project.requires-python`. However, note that if you need an upper bound on supported Python versions refer to the documentation [here]({{< relref "pyproject#requires-python" >}}).
- The [`requires-poetry`]({{< relref "pyproject#requires-poetry" >}}) is added to the `tools.poetry` section.
{{% /note %}}
================================================
FILE: docs/libraries.md
================================================
---
title: "Libraries"
draft: false
type: docs
layout: "docs"
menu:
docs:
weight: 20
---
# Libraries
This chapter will tell you how to make your library installable through Poetry.
## Versioning
Poetry requires [PEP 440](https://peps.python.org/pep-0440)-compliant versions for all projects.
While Poetry does not enforce any release convention, it used to encourage the use of
[semantic versioning](https://semver.org/) within the scope of
[PEP 440](https://peps.python.org/pep-0440/#semantic-versioning) and supports
[version constraints]({{< relref "dependency-specification/#caret-requirements" >}})
that are especially suitable for semver.
{{% note %}}
As an example, `1.0.0-hotfix.1` is not compatible with [PEP 440](https://peps.python.org/pep-0440). You can instead
choose to use `1.0.0-post1` or `1.0.0.post1`.
{{% /note %}}
## Lock file
For your library, you may commit the `poetry.lock` file if you want to.
This can help your team to always test against the same dependency versions.
However, this lock file will not have any effect on other projects that depend on it.
It only has an effect on the main project.
If you do not want to commit the lock file and you are using git, add it to the `.gitignore`.
## Packaging
Before you can actually publish your library, you will need to package it.
You need to define a build-system according to [PEP 517](https://peps.python.org/pep-0517/) in the `pyproject.toml` file:
```toml
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
```
Then you can package your library by running:
```bash
poetry build
```
This command will package your library in two different formats: `sdist` which is
the source format, and `wheel` which is a `compiled` package.
Poetry will automatically include some license-related files when building a package -
in the `.dist-info/licenses` directory when building a `wheel`,
and in the root folder when building an `sdist`:
- `LICENSE*`
- `LICENCE*`
- `COPYING*`
- `AUTHORS*`
- `NOTICE*`
- `LICENSES/**/*`
You can override this behavior by specifying
[`license-files`]({{< relref "pyproject/#license-files" >}})
in the `pyproject.toml` file.
### Alternative build backends
If you want to use a different build backend, you can specify it in the `pyproject.toml` file:
```toml
[build-system]
requires = ["maturin>=0.8.1,<0.9"]
build-backend = "maturin"
```
The `poetry build` command will then use the specified build backend to build your package in
an isolated environment. Ensure you have specified any additional settings according to the
documentation of the build backend you are using.
Once building is done, you are ready to publish your library.
## Publishing to PyPI
Alright, so now you can publish packages.
Poetry will publish to [PyPI](https://pypi.org) by default. Anything that is published to PyPI
is available automatically through Poetry. Since [pendulum](https://pypi.org/project/pendulum/)
is on PyPI we can depend on it without having to specify any additional repositories.
If we wanted to share `poetry-demo` with the Python community, we would publish on PyPI as well.
Doing so is really easy.
```bash
poetry publish
```
This will package and publish the library to PyPI, on the condition that you are a registered user
and you have [configured your credentials]({{< relref "repositories#configuring-credentials" >}}) properly.
{{% note %}}
The `publish` command does not execute `build` by default.
If you want to build and publish your packages together,
just pass the `--build` option.
{{% /note %}}
Once this is done, your library will be available to anyone.
## Publishing to a private repository
Sometimes, you may want to keep your library private but also be accessible to your team.
In this case, you will need to use a private repository.
In order to publish to a private repository, you will need to add it to your
global list of repositories. See [Adding a repository]({{< relref "repositories#adding-a-repository" >}})
for more information.
Once this is done, you can publish your package to the repository like so:
```bash
poetry publish -r my-repository
```
================================================
FILE: docs/managing-dependencies.md
================================================
---
draft: false
layout: single
menu:
docs:
weight: 11
title: Managing dependencies
type: docs
---
# Managing dependencies
Poetry supports specifying main dependencies in the [`project.dependencies`]({{< relref "pyproject#dependencies" >}}) section of your `pyproject.toml`
according to PEP 621. For legacy reasons and to define additional information that are only used by Poetry
the [`tool.poetry.dependencies`]({{< relref "pyproject#dependencies-and-dependency-groups" >}}) sections can be used.
See [Dependency specification]({{< relref "dependency-specification" >}}) for more information.
## Dependency groups
Poetry provides a way to **organize** your dependencies by **groups**.
The dependencies declared in `project.dependencies` respectively `tool.poetry.dependencies`
are part of an implicit `main` group. Those dependencies are required by your project during runtime.
Besides the `main` dependencies, you might have dependencies that are only needed to test your project
or to build the documentation.
To declare a new dependency group, use a `dependency-groups` section according to PEP 735 or
a `tool.poetry.group.` section where `` is the name of your dependency group (for instance, `test`):
{{< tabs tabTotal="2" tabID1="group-pep735" tabID2="group-poetry" tabName1="[dependency-groups]" tabName2="[tool.poetry]">}}
{{< tab tabID="group-pep735" >}}
```toml
[dependency-groups]
test = [
"pytest (>=6.0.0,<7.0.0)",
"pytest-mock",
]
```
{{< /tab >}}
{{< tab tabID="group-poetry" >}}
```toml
[tool.poetry.group.test.dependencies]
pytest = "^6.0.0"
pytest-mock = "*"
```
{{< /tab >}}
{{< /tabs >}}
{{% note %}}
All dependencies **must be compatible with each other** across groups since they will
be resolved regardless of whether they are required for installation or not (see [Installing group dependencies]({{< relref "#installing-group-dependencies" >}})).
Think of dependency groups as **labels** associated with your dependencies: they don't have any bearings
on whether their dependencies will be resolved and installed **by default**, they are simply a way to organize
the dependencies logically.
{{% /note %}}
{{% note %}}
Dependency groups, other than the implicit `main` group,
must only contain dependencies you need in your development process.
To declare a set of dependencies, which add additional functionality to the project
during runtime, use [extras]({{< relref "pyproject#extras" >}}) instead.
{{% /note %}}
### Optional groups
A dependency group can be declared as optional. This makes sense when you have
a group of dependencies that are only required in a particular environment or for
a specific purpose.
{{< tabs tabTotal="2" tabID1="group-optional-pep735" tabID2="group-optional-poetry" tabName1="[dependency-groups]" tabName2="[tool.poetry]">}}
{{< tab tabID="group-optional-pep735" >}}
```toml
[dependency-groups]
docs = [
"mkdocs",
]
[tool.poetry.group.docs]
optional = true
```
{{< /tab >}}
{{< tab tabID="group-optional-poetry" >}}
```toml
[tool.poetry.group.docs]
optional = true
[tool.poetry.group.docs.dependencies]
mkdocs = "*"
```
{{< /tab >}}
{{< /tabs >}}
Optional groups can be installed in addition to the **default** dependencies by using the `--with`
option of the [`install`]({{< relref "cli#install" >}}) command.
```bash
poetry install --with docs
```
{{% warning %}}
Optional group dependencies will **still** be resolved alongside other dependencies, so
special care should be taken to ensure they are compatible with each other.
{{% /warning %}}
### Including dependencies from other groups
You can include dependencies from one group in another group.
This is useful when you want to aggregate dependencies from multiple groups into a single group.
{{< tabs tabTotal="2" tabID1="group-include-pep735" tabID2="group-include-poetry" tabName1="[dependency-groups]" tabName2="[tool.poetry]">}}
{{< tab tabID="group-include-pep735" >}}
```toml
[dependency-groups]
test = [
"pytest (>=8.0.0,<9.0.0)",
]
lint = [
"ruff (>=0.11.0,<0.12.0)",
]
dev = [
{ include-group = "test" },
{ include-group = "lint" },
"tox",
]
```
{{< /tab >}}
{{< tab tabID="group-include-poetry" >}}
```toml
[tool.poetry.group.test.dependencies]
pytest = "^8.0.0"
[tool.poetry.group.lint.dependencies]
ruff = "^0.11"
[tool.poetry.group.dev]
include-groups = [
"test",
"lint",
]
[tool.poetry.group.dev.dependencies]
tox = "*"
```
{{< /tab >}}
{{< /tabs >}}
In this example, the `dev` group includes all dependencies from the `test` and `lint` groups.
### Adding a dependency to a group
The [`add`]({{< relref "cli#add" >}}) command is the preferred way to add dependencies
to a group. This is done by using the `--group (-G)` option.
```bash
poetry add pytest --group test
```
If the group does not already exist, it will be created automatically.
### Installing group dependencies
**By default**, dependencies across **all non-optional groups** will be installed when executing
`poetry install`.
{{% note %}}
The default set of dependencies for a project includes the implicit `main` group as well as all
groups that are not explicitly marked as an [optional group]({{< relref "#optional-groups" >}}).
{{% /note %}}
You can **exclude** one or more groups with the `--without` option:
```bash
poetry install --without test,docs
```
You can also opt in [optional groups]({{< relref "#optional-groups" >}}) by using the `--with` option:
```bash
poetry install --with docs
```
{{% warning %}}
When used together, `--without` takes precedence over `--with`. For example, the following command
will only install the dependencies specified in the optional `test` group.
```bash
poetry install --with test,docs --without docs
```
{{% /warning %}}
Finally, in some case you might want to install **only specific groups** of dependencies
without installing the default set of dependencies. For that purpose, you can use
the `--only` option.
```bash
poetry install --only docs
```
{{% note %}}
If you only want to install the project's runtime dependencies, you can do so with the
`--only main` notation:
```bash
poetry install --only main
```
{{% /note %}}
{{% note %}}
If you want to install the project root, and no other dependencies, you can use
the `--only-root` option.
```bash
poetry install --only-root
```
{{% /note %}}
### Removing dependencies from a group
The [`remove`]({{< relref "cli#remove" >}}) command supports a `--group` option
to remove packages from a specific group:
```bash
poetry remove mkdocs --group docs
```
## Synchronizing dependencies
Poetry supports what's called dependency synchronization. Dependency synchronization ensures
that the locked dependencies in the `poetry.lock` file are the only ones present
in the environment, removing anything that's not necessary.
This is done by using the `sync` command:
```bash
poetry sync
```
The `sync` command can be combined with any [dependency groups]({{< relref "#dependency-groups" >}}) related options
to synchronize the environment with specific groups. Note that extras are separate.
Any extras not selected for install are always removed.
```bash
poetry sync --without dev
poetry sync --with docs
poetry sync --only dev
```
## Layering optional groups
When using the `install` command without the `--sync` option, you can install any subset of optional groups without removing
those that are already installed. This is very useful, for example, in multi-stage
Docker builds, where you run `poetry install` multiple times in different build stages.
================================================
FILE: docs/managing-environments.md
================================================
---
title: "Managing environments"
draft: false
type: docs
layout: "docs"
menu:
docs:
weight: 60
---
# Managing environments
Poetry makes project environment isolation one of its core features.
What this means is that it will always work isolated from your global Python installation.
To achieve this, it will first check if it's currently running inside a virtual environment.
If it is, it will use it directly without creating a new one. But if it's not, it will use
one that it has already created or create a brand new one for you.
By default, Poetry will try to use the Python version used during Poetry's installation
to create the virtual environment for the current project.
However, for various reasons, this Python version might not be compatible
with the `python` range supported by the project. In this case, Poetry will try
to find one that is and use it. If it's unable to do so then you will be prompted
to activate one explicitly, see [Switching environments](#switching-between-environments).
{{% note %}}
If you use a tool like [pyenv](https://github.com/pyenv/pyenv) to manage different Python versions,
you can switch the current `python` of your shell and Poetry will use it to create
the new environment.
For instance, if your project requires a newer Python than is available with
your system, a standard workflow would be:
```bash
pyenv install 3.9.8
pyenv local 3.9.8 # Activate Python 3.9 for the current project
poetry install
```
{{% /note %}}
{{% note %}}
Since version 1.2, Poetry no longer supports managing environments for Python 2.7.
{{% /note %}}
## Switching between environments
Sometimes this might not be feasible for your system, especially Windows where `pyenv`
is not available, or you simply prefer to have a more explicit control over your environment.
For this specific purpose, you can use the `env use` command to tell Poetry
which Python version to use for the current project.
```bash
poetry env use /full/path/to/python
```
If you have the python executable in your `PATH` you can use it:
```bash
poetry env use python3.7
```
You can even just use the minor Python version in this case:
```bash
poetry env use 3.7
```
If you want to disable the explicitly activated virtual environment, you can use the
special `system` Python version to retrieve the default behavior:
```bash
poetry env use system
```
## Activating the environment
{{% note %}}
Looking for `poetry shell`? It was moved to a plugin: [`poetry-plugin-shell`](https://github.com/python-poetry/poetry-plugin-shell)
{{% /note %}}
The `poetry env activate` command prints the activate command of the virtual environment to the console.
You can run the output command manually or feed it to the eval command of your shell to activate the environment.
This way you won't leave the current shell.
{{< tabs tabTotal="3" tabID1="bash-csh-zsh" tabID2="fish" tabID3="powershell" tabName1="Bash/Zsh/Csh" tabName2="Fish" tabName3="Powershell" >}}
{{< tab tabID="bash-csh-zsh" >}}
```bash
$ eval $(poetry env activate)
(test-project-for-test) $ # Virtualenv entered
```
{{< /tab >}}
{{< tab tabID="fish" >}}
```bash
$ eval (poetry env activate)
(test-project-for-test) $ # Virtualenv entered
```
{{< /tab >}}
{{< tab tabID="powershell" >}}
```ps1
PS1> Invoke-Expression (poetry env activate)
(test-project-for-test) PS1> # Virtualenv entered
```
{{< /tab >}}
{{< /tabs >}}
## Displaying the environment information
If you want to get basic information about the currently activated virtual environment,
you can use the `env info` command:
```bash
poetry env info
```
will output something similar to this:
```text
Virtualenv
Python: 3.7.1
Implementation: CPython
Path: /path/to/poetry/cache/virtualenvs/test-O3eWbxRl-py3.7
Valid: True
Base
Platform: darwin
OS: posix
Python: /path/to/main/python
```
If you only want to know the path to the virtual environment, you can pass the `--path` option
to `env info`:
```bash
poetry env info --path
```
If you only want to know the path to the python executable (useful for running mypy from a global environment without installing it in the virtual environment), you can pass the `--executable` option
to `env info`:
```bash
poetry env info --executable
```
## Listing the environments associated with the project
You can also list all the virtual environments associated with the current project
with the `env list` command:
```bash
poetry env list
```
will output something like the following:
```text
test-O3eWbxRl-py3.6
test-O3eWbxRl-py3.7 (Activated)
```
You can pass the option `--full-path` to display the full path to the environments:
```bash
poetry env list --full-path
```
## Deleting the environments
Finally, you can delete existing virtual environments by using `env remove`:
```bash
poetry env remove /full/path/to/python
poetry env remove python3.7
poetry env remove 3.7
poetry env remove test-O3eWbxRl-py3.7
```
You can delete more than one environment at a time.
```bash
poetry env remove python3.6 python3.7 python3.8
```
Use the `--all` option to delete all virtual environments at once.
```bash
poetry env remove --all
```
If you remove the currently activated virtual environment, it will be automatically deactivated.
{{% note %}}
If you use the [`virtualenvs.in-project`]({{< relref "configuration#virtualenvsin-project" >}}) configuration, you
can simply use the command as shown below.
```bash
poetry env remove
```
{{% /note %}}
================================================
FILE: docs/plugins.md
================================================
---
title: "Plugins"
draft: false
type: docs
layout: single
menu:
docs:
weight: 80
---
# Plugins
Poetry supports using and building plugins if you wish to
alter or expand Poetry's functionality with your own.
For example if your environment poses special requirements
on the behaviour of Poetry which do not apply to the majority of its users
or if you wish to accomplish something with Poetry in a way that is not desired by most users.
In these cases you could consider creating a plugin to handle your specific logic.
## Creating a plugin
A plugin is a regular Python package which ships its code as part of the package
and may also depend on further packages.
### Plugin package
The plugin package must depend on Poetry
and declare a proper [plugin]({{< relref "pyproject#plugins" >}}) in the `pyproject.toml` file.
```toml
[project]
name = "my-poetry-plugin"
version = "1.0.0"
# ...
requires-python = ">=3.7"
dependencies = [
"poetry (>=1.2,<2.0)",
]
[project.entry-points."poetry.plugin"]
demo = "poetry_demo_plugin.plugin:MyPlugin"
```
### Generic plugins
Every plugin has to supply a class which implements the `poetry.plugins.Plugin` interface.
The `activate()` method of the plugin is called after the plugin is loaded
and receives an instance of `Poetry` as well as an instance of `cleo.io.io.IO`.
Using these two objects all configuration can be read
and all public internal objects and state can be manipulated as desired.
Example:
```python
from cleo.io.io import IO
from poetry.plugins.plugin import Plugin
from poetry.poetry import Poetry
class MyPlugin(Plugin):
def activate(self, poetry: Poetry, io: IO):
io.write_line("Setting readme")
poetry.package.readme = "README.md"
...
```
### Application plugins
If you want to add commands or options to the `poetry` script you need
to create an application plugin which implements the `poetry.plugins.ApplicationPlugin` interface.
The `activate()` method of the application plugin is called after the plugin is loaded
and receives an instance of `poetry.console.Application`.
```python
from cleo.commands.command import Command
from poetry.plugins.application_plugin import ApplicationPlugin
class CustomCommand(Command):
name = "my-command"
def handle(self) -> int:
self.line("My command")
return 0
def factory():
return CustomCommand()
class MyApplicationPlugin(ApplicationPlugin):
def activate(self, application):
application.command_loader.register_factory("my-command", factory)
```
{{% note %}}
It's possible to do the following to register the command:
```python
application.add(MyCommand())
```
However, it is **strongly** recommended to register a new factory
in the command loader to defer the loading of the command when it's actually
called.
This will help keep the performances of Poetry good.
{{% /note %}}
The plugin also must be declared in the `pyproject.toml` file of the plugin package
as a `poetry.application.plugin` plugin:
```toml
[tool.poetry.plugins."poetry.application.plugin"]
foo-command = "poetry_demo_plugin.plugin:MyApplicationPlugin"
```
{{% warning %}}
A plugin **must not** remove or modify in any way the core commands of Poetry.
{{% /warning %}}
### Event handler
Plugins can also listen to specific events and act on them if necessary.
These events are fired by [Cleo](https://github.com/python-poetry/cleo)
and are accessible from the `cleo.events.console_events` module.
- `COMMAND`: this event allows attaching listeners before any command is executed.
- `SIGNAL`: this event allows some actions to be performed after the command execution is interrupted.
- `TERMINATE`: this event allows listeners to be attached after the command.
- `ERROR`: this event occurs when an uncaught exception is raised.
Let's see how to implement an application event handler. For this example
we will see how to load environment variables from a `.env` file before executing
a command.
```python
from cleo.events.console_events import COMMAND
from cleo.events.console_command_event import ConsoleCommandEvent
from cleo.events.event_dispatcher import EventDispatcher
from dotenv import load_dotenv
from poetry.console.application import Application
from poetry.console.commands.env_command import EnvCommand
from poetry.plugins.application_plugin import ApplicationPlugin
class MyApplicationPlugin(ApplicationPlugin):
def activate(self, application: Application):
application.event_dispatcher.add_listener(
COMMAND, self.load_dotenv
)
def load_dotenv(
self,
event: ConsoleCommandEvent,
event_name: str,
dispatcher: EventDispatcher
) -> None:
command = event.command
if not isinstance(command, EnvCommand):
return
io = event.io
if io.is_debug():
io.write_line(
"Loading environment variables."
)
load_dotenv()
```
## Using plugins
Installed plugin packages are automatically loaded when Poetry starts up.
You have multiple ways to install plugins for Poetry
### With `pipx inject`
If you used `pipx` to install Poetry you can add the plugin packages via the `pipx inject` command.
```shell
pipx inject poetry poetry-plugin
```
If you want to uninstall a plugin, you can run:
```shell
pipx uninject poetry poetry-plugin # For pipx versions >= 1.2.0
pipx runpip poetry uninstall poetry-plugin # For pipx versions < 1.2.0
```
### With `pip`
The `pip` binary in Poetry's virtual environment can also be used to install and remove plugins.
The environment variable `$POETRY_HOME` here is used to represent the path to the virtual environment.
The [installation instructions](/docs/) can be referenced if you are not
sure where Poetry has been installed.
To add a plugin, you can use `pip install`:
```shell
$POETRY_HOME/bin/pip install --user poetry-plugin
```
If you want to uninstall a plugin, you can run:
```shell
$POETRY_HOME/bin/pip uninstall poetry-plugin
```
### The `self add` command
{{% warning %}}
Especially on Windows, `self add` and `self remove` may be problematic
so that other methods should be preferred.
{{% /warning %}}
```bash
poetry self add poetry-plugin
```
The `self add` command will ensure that the plugin is compatible with the current version of Poetry
and install the needed packages for the plugin to work.
The package specification formats supported by the `self add` command are the same as the ones supported
by the [`add` command]({{< relref "cli#add" >}}).
If you no longer need a plugin and want to uninstall it, you can use the `self remove` command.
```shell
poetry self remove poetry-plugin
```
You can also list all currently installed plugins by running:
```shell
poetry self show plugins
```
### Project plugins
You can also specify that a plugin is required for your project
in the `tool.poetry.requires-plugins` section of the pyproject.toml file:
```toml
[tool.poetry.requires-plugins]
my-application-plugin = ">1.0"
custom-plugin = {path = "custom_plugin", develop = true}
```
If the plugin is not installed in Poetry's own environment when running `poetry install`,
it will be installed only for the current project under `.poetry/plugins`
in the project's directory.
The syntax to specify `plugins` is the same as for [dependencies]({{< relref "managing-dependencies" >}}).
Plugins can be installed in editable mode using path dependencies with `develop = true`,
which is useful for plugin development.
{{% warning %}}
You can even overwrite a plugin in Poetry's own environment with another version.
However, if a plugin's dependencies are not compatible with packages in Poetry's own
environment, installation will fail.
{{% /warning %}}
## Maintaining a plugin
When writing a plugin, you will probably access internals of Poetry, since there is no
stable public API. Although we try our best to deprecate methods first, before
removing them, sometimes the signature of an internal method has to be changed.
As the author of a plugin, you are probably testing your plugin
against the latest release of Poetry.
Additionally, you should consider testing against the latest release branch and the
main branch of Poetry and schedule a CI job that runs regularly even if you did not
make any changes to your plugin.
This way, you will notice internal changes that break your plugin immediately
and can prepare for the next Poetry release.
================================================
FILE: docs/pre-commit-hooks.md
================================================
---
title: "pre-commit hooks"
draft: false
type: docs
layout: single
menu:
docs:
weight: 120
---
# pre-commit hooks
pre-commit is a framework for building and running
[git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks).
See the official documentation for more information: [pre-commit.com](https://pre-commit.com/)
This document provides a list of available pre-commit hooks provided by Poetry.
{{% note %}}
If you specify the `args:` for a hook in your `.pre-commit-config.yaml`,
the defaults are overwritten. You must fully specify all arguments for
your hook if you make use of `args:`.
{{% /note %}}
{{% note %}}
If the `pyproject.toml` file is not in the root directory, you can specify `args: ["-C", "./subdirectory"]`.
{{% /note %}}
## poetry-check
The `poetry-check` hook calls the `poetry check` command
to make sure the poetry configuration does not get committed in a broken state.
### Arguments
The hook takes the same arguments as the poetry command.
For more information see the [check command]({{< relref "cli#check" >}}).
## poetry-lock
The `poetry-lock` hook calls the `poetry lock` command
to make sure the lock file is up-to-date when committing changes.
### Arguments
The hook takes the same arguments as the poetry command.
For more information see the [lock command]({{< relref "cli#lock" >}}).
## poetry-export
The `poetry-export` hook calls the `poetry export` command
to sync your `requirements.txt` file with your current dependencies.
{{% warning %}}
This hook is provided by the [Export Poetry Plugin](https://github.com/python-poetry/poetry-plugin-export).
{{% /warning %}}
{{% note %}}
It is recommended to run the [`poetry-lock`](#poetry-lock) hook or [`poetry-check`](#poetry-check) with argument `--lock` prior to this one.
{{% /note %}}
### Arguments
The hook takes the same arguments as the poetry command.
For more information, see the [export command]({{< relref "cli#export" >}}).
The default arguments are `args: ["-f", "requirements.txt", "-o", "requirements.txt"]`,
which will create/update the `requirements.txt` file in the current working directory.
You may add `verbose: true` in your `.pre-commit-config.yaml` in order to output to the
console:
```yaml
hooks:
- id: poetry-export
args: ["-f", "requirements.txt"]
verbose: true
```
Also, `--dev` can be added to `args` to write dev-dependencies to `requirements.txt`:
```yaml
hooks:
- id: poetry-export
args: ["--dev", "-f", "requirements.txt", "-o", "requirements.txt"]
```
## poetry-install
The `poetry-install` hook calls the `poetry install` command to make sure all locked packages are installed.
In order to install this hook, you either need to specify `default_install_hook_types`, or you have
to install it via `pre-commit install --install-hooks -t post-checkout -t post-merge`.
### Arguments
The hook takes the same arguments as the poetry command.
For more information, see the [install command]({{< relref "cli#install" >}}).
## Usage
For more information on how to use pre-commit, please see the [official documentation](https://pre-commit.com/).
A minimalistic `.pre-commit-config.yaml` example:
```yaml
repos:
- repo: https://github.com/python-poetry/poetry
rev: '' # add version here
hooks:
- id: poetry-check
- id: poetry-lock
- id: poetry-export
- id: poetry-install
```
A `.pre-commit-config.yaml` example for a monorepo setup or if the `pyproject.toml` file is not in the root directory:
```yaml
repos:
- repo: https://github.com/python-poetry/poetry
rev: '' # add version here
hooks:
- id: poetry-check
args: ["-C", "./subdirectory"]
- id: poetry-lock
args: ["-C", "./subdirectory"]
- id: poetry-export
args: ["-C", "./subdirectory", "-f", "requirements.txt", "-o", "./subdirectory/requirements.txt"]
- id: poetry-install
args: ["-C", "./subdirectory"]
```
## FAQ
### Why does `pre-commit autoupdate` not update to the latest version?
`pre-commit autoupdate` updates the `rev` for each repository defined in your `.pre-commit-config.yaml`
to the latest available tag in the default branch.
Poetry follows a branching strategy where the default branch is the active development branch,
and fixes get backported to stable branches. New tags are assigned in these stable branches.
`pre-commit` does not support such a branching strategy and has decided to not implement
an option, either on the [user's side](https://github.com/pre-commit/pre-commit/issues/2512)
or the [hook author's side](https://github.com/pre-commit/pre-commit/issues/2508), to define a branch for looking
up the latest available tag.
Thus, `pre-commit autoupdate` is not usable for the hooks described here.
You can avoid changing the `rev` to an unexpected value by using the `--repo` parameter (may be specified multiple
times), to explicitly list repositories that should be updated. An option to explicitly exclude
repositories [will not be implemented](https://github.com/pre-commit/pre-commit/issues/1959) into `pre-commit`.
### Are there any alternatives to `pre-commit autoupdate`?
You may use [pre-commit-update](https://pypi.org/project/pre-commit-update/) as an alternative to
`pre-commit autoupdate`.
Since `pre-commit-update` can be used as a pre-commit hook itself, the easiest way
to make use of it would be to include it inside `.pre-commit-config.yaml`:
```yaml
repos:
- repo: https://gitlab.com/vojko.pribudic.foss/pre-commit-update
rev: v0.5.1post1
hooks:
- id: pre-commit-update
- repo: https://github.com/python-poetry/poetry
rev: 1.8.3
hooks:
- id: poetry-check
- id: poetry-lock
- id: poetry-export
- id: poetry-install
```
Your `.pre-commit-config.yaml` repos will be checked and updated every time pre-commit hooks run.
For more advanced configuration, please check the `pre-commit-update` documentation.
================================================
FILE: docs/pyproject.md
================================================
---
title: "The pyproject.toml file"
draft: false
type: docs
layout: single
menu:
docs:
weight: 90
---
# The `pyproject.toml` file
In package mode, the only required fields are `name` and `version`
(either in the `project` section or in the `tool.poetry` section).
Other fields are optional.
In non-package mode, the `name` and `version` fields are required
if using the `project` section.
{{% note %}}
Run `poetry check` to print warnings about deprecated fields.
{{% /note %}}
## The `project` section
The `project` section of the `pyproject.toml` file according to the
[specification of the PyPA](https://packaging.python.org/en/latest/specifications/pyproject-toml/#declaring-project-metadata-the-project-table).
### name
The name of the package. **Always required when the `project` section is specified**
This should be a valid name as defined by [PEP 508](https://peps.python.org/pep-0508/#names).
```toml
[project]
name = "my-package"
```
### version
The version of the package. **Always required when the `project` section is specified**
This should be a valid [PEP 440](https://peps.python.org/pep-0440/) string.
```toml
[project]
# ...
version = "0.1.0"
```
If you want to set the version dynamically via `poetry build --local-version`
or you are using a plugin, which sets the version dynamically, you should add `version`
to dynamic and define the base version in the `tool.poetry` section, for example:
```toml
[project]
name = "my-package"
dynamic = [ "version" ]
[tool.poetry]
version = "1.0" # base version
```
### description
A short description of the package.
```toml
[project]
# ...
description = "A short description of the package."
```
### license
An [SPDX expression](https://packaging.python.org/en/latest/glossary/#term-License-Expression)
representing the license of the package.
The recommended notation for the most common licenses is (alphabetical):
* Apache-2.0
* BSD-2-Clause
* BSD-3-Clause
* BSD-4-Clause
* GPL-2.0-only
* GPL-2.0-or-later
* GPL-3.0-only
* GPL-3.0-or-later
* LGPL-2.1-only
* LGPL-2.1-or-later
* LGPL-3.0-only
* LGPL-3.0-or-later
* MIT
Optional, but it is highly recommended to supply this.
More identifiers are listed at the [SPDX Open Source License Registry](https://spdx.org/licenses/).
```toml
[project]
# ...
license = "MIT"
```
{{% warning %}}
Specifying license as a table, e.g. `{ text = "MIT" }` is deprecated.
If you used to specify a license file, e.g. `{ file = "LICENSE" }`,
use `license-files` instead.
{{% /warning %}}
### license-files
A list of glob patterns that match the license files of the package
relative to the root of the project source tree.
```toml
[project]
# ...
license-files = [
"*-LICENSE",
"CONTRIBUTORS",
"MY-SPECIAL-LICENSE-DIR/**/*"
]
```
By default, Poetry will include the following files:
- `LICENSE*`
- `LICENCE*`
- `COPYING*`
- `AUTHORS*`
- `NOTICE*`
- `LICENSES/**/*`
{{% note %}}
The default applies only if the `license-files` field is not specified.
Specifying an empty list results in no license files being included.
{{% /note %}}
### readme
A path to the README file or the content.
```toml
[project]
# ...
readme = "README.md"
```
{{% note %}}
If you want to define multiple README files, you have to add `readme` to `dynamic`
and define them in the `tool.poetry` section.
{{% /note %}}
```toml
[project]
# ...
dynamic = [ "readme" ]
[tool.poetry]
# ...
readme = ["docs/README1.md", "docs/README2.md"]
```
### requires-python
The Python version requirements of the project.
```toml
[project]
# ...
requires-python = ">=3.8"
```
{{% note %}}
If you need an upper bound for locking, but do not want to define an upper bound
in your package metadata, you can omit the upper bound in the `requires-python` field
and add it in the `tool.poetry.dependencies` section.
{{% /note %}}
```toml
[project]
# ...
requires-python = ">=3.8"
[tool.poetry.dependencies]
python = ">=3.8,<4.0"
```
### authors
The authors of the package.
This is a list of authors and should contain at least one author.
```toml
[project]
# ...
authors = [
{ name = "Sébastien Eustace", email = "sebastien@eustace.io" },
]
```
### maintainers
The maintainers of the package.
This is a list of maintainers and should be distinct from authors.
```toml
[project]
# ...
maintainers = [
{ name = "John Smith", email = "johnsmith@example.org" },
{ name = "Jane Smith", email = "janesmith@example.org" },
]
```
### keywords
A list of keywords that the package is related to.
```toml
[project]
# ...
keywords = [ "packaging", "poetry" ]
```
### classifiers
A list of PyPI [trove classifiers](https://pypi.org/classifiers/) that describe the project.
```toml
[project]
# ...
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
```
{{% warning %}}
Note that suitable classifiers based on your `python` requirement
are **not** automatically added for you if you define classifiers statically
in the `project` section.
If you want to enrich classifiers automatically, you should add `classifiers` to `dynamic`
and use the `tool.poetry` section instead.
{{% /warning %}}
```toml
[project]
# ...
dynamic = [ "classifiers" ]
[tool.poetry]
# ...
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
```
### urls
The URLs of the project.
```toml
[project.urls]
homepage = "https://python-poetry.org/"
repository = "https://github.com/python-poetry/poetry"
documentation = "https://python-poetry.org/docs/"
"Bug Tracker" = "https://github.com/python-poetry/poetry/issues"
```
If you publish your package on PyPI, they will appear in the `Project Links` section.
### scripts
This section describes the console scripts that will be installed when installing the package.
```toml
[project.scripts]
my_package_cli = 'my_package.console:run'
```
Here, we will have the `my_package_cli` script installed which will execute the `run` function in the `console` module in the `my_package` package.
{{% note %}}
When a script is added or updated, run `poetry install` to make them available in the project's virtualenv.
{{% /note %}}
{{% note %}}
To include a file as a script, use [`tool.poetry.scripts`]({{< relref "#scripts-1" >}}) instead.
{{% /note %}}
### gui-scripts
This section describes the GUI scripts that will be installed when installing the package.
```toml
[project.gui-scripts]
my_package_gui = 'my_package.gui:run'
```
Here, we will have the `my_package_gui` script installed which will execute the `run` function in the `gui` module in the `my_package` package.
{{% note %}}
When a script is added or updated, run `poetry install` to make them available in the project's virtualenv.
{{% /note %}}
### entry-points
Entry points can be used to define plugins for your package.
Poetry supports arbitrary plugins, which are exposed as the ecosystem-standard
[entry points](https://packaging.python.org/en/latest/specifications/entry-points/)
and discoverable using `importlib.metadata`.
This is similar to (and compatible with) the entry points feature of `setuptools`.
The syntax for registering a plugin is:
```toml
[project.entry-points] # Optional super table
[project.entry-points."A"]
B = "C:D"
```
Which are:
- `A` - type of the plugin, for example `poetry.plugin` or `flake8.extension`
- `B` - name of the plugin
- `C` - python module import path
- `D` - the entry point of the plugin (a function or class)
Example (from [`poetry-plugin-export`](http://github.com/python-poetry/poetry-plugin-export)):
```toml
[project.entry-points."poetry.application.plugin"]
export = "poetry_plugin_export.plugins:ExportApplicationPlugin"
```
### dependencies
The `dependencies` of the project.
```toml
[project]
# ...
dependencies = [
"requests>=2.13.0",
]
```
These are the dependencies that will be declared when building an sdist or a wheel.
See [Dependency specification]({{< relref "dependency-specification" >}}) for more information
about the relation between `project.dependencies` and `tool.poetry.dependencies`.
### optional-dependencies
The optional dependencies of the project (also known as extras).
```toml
[project.optional-dependencies]
mysql = [ "mysqlclient>=1.3,<2.0" ]
pgsql = [ "psycopg2>=2.9,<3.0" ]
databases = [ "mysqlclient>=1.3,<2.0", "psycopg2>=2.9,<3.0" ]
```
{{% note %}}
You can enrich optional dependencies for locking in the `tool.poetry` section
analogous to `dependencies`.
{{% /note %}}
## The `tool.poetry` section
The `tool.poetry` section of the `pyproject.toml` file is composed of multiple sections.
### package-mode
Whether Poetry operates in package mode (default) or not.
See [basic usage]({{< relref "basic-usage#operating-modes" >}}) for more information.
```toml
[tool.poetry]
# ...
package-mode = false
```
### name
**Deprecated**: Use `project.name` instead.
The name of the package. **Required in package mode if not defined in the project section**
This should be a valid name as defined by [PEP 508](https://peps.python.org/pep-0508/#names).
```toml
[tool.poetry]
name = "my-package"
```
### version
{{% note %}}
If you do not want to set the version dynamically via `poetry build --local-version`
and you are not using a plugin, which sets the version dynamically,
prefer `project.version` over this setting.
{{% /note %}}
The version of the package. **Required in package mode if not defined in the project section**
This should be a valid [PEP 440](https://peps.python.org/pep-0440/) string.
```toml
[tool.poetry]
# ...
version = "0.1.0"
```
{{% note %}}
If you would like to use semantic versioning for your project, please see
[here]({{< relref "libraries#versioning" >}}).
{{% /note %}}
### description
**Deprecated**: Use `project.description` instead.
A short description of the package.
```toml
[tool.poetry]
# ...
description = "A short description of the package."
```
### license
**Deprecated**: Use `project.license` instead.
The license of the package.
The recommended notation for the most common licenses is (alphabetical):
* Apache-2.0
* BSD-2-Clause
* BSD-3-Clause
* BSD-4-Clause
* GPL-2.0-only
* GPL-2.0-or-later
* GPL-3.0-only
* GPL-3.0-or-later
* LGPL-2.1-only
* LGPL-2.1-or-later
* LGPL-3.0-only
* LGPL-3.0-or-later
* MIT
Optional, but it is highly recommended to supply this.
More identifiers are listed at the [SPDX Open Source License Registry](https://spdx.org/licenses/).
```toml
[tool.poetry]
# ...
license = "MIT"
```
### authors
**Deprecated**: Use `project.authors` instead.
The authors of the package.
This is a list of authors and should contain at least one author. Authors must be in the form `name `.
```toml
[tool.poetry]
# ...
authors = [
"Sébastien Eustace ",
]
```
### maintainers
**Deprecated**: Use `project.maintainers` instead.
The maintainers of the package.
This is a list of maintainers and should be distinct from authors. Maintainers may contain an email and be in the form `name `.
```toml
[tool.poetry]
# ...
maintainers = [
"John Smith ",
"Jane Smith ",
]
```
### readme
{{% note %}}
If you do not want to set multiple README files, prefer `project.readme` over this setting.
{{% /note %}}
A path, or list of paths corresponding to the README file(s) of the package.
The file(s) can be of any format, but if you intend to publish to PyPI keep the
[recommendations for a PyPI-friendly README](
https://packaging.python.org/en/latest/guides/making-a-pypi-friendly-readme/) in
mind. README paths are implicitly relative to `pyproject.toml`.
{{% note %}}
Whether paths are case-sensitive follows platform defaults, but it is recommended to keep cases.
To be specific, you can set `readme = "rEaDmE.mD"` for `README.md` on macOS and Windows, but Linux users can't `poetry install` after cloning your repo. This is because macOS and Windows are case-insensitive and case-preserving.
{{% /note %}}
The contents of the README file(s) are used to populate the [Description
field](https://packaging.python.org/en/latest/specifications/core-metadata/#description-optional)
of your distribution's metadata (similar to `long_description` in setuptools).
When multiple files are specified they are concatenated with newlines.
```toml
[tool.poetry]
# ...
readme = "README.md"
```
```toml
[tool.poetry]
# ...
readme = ["docs/README1.md", "docs/README2.md"]
```
### homepage
**Deprecated**: Use `project.urls` instead.
A URL to the website of the project.
```toml
[tool.poetry]
# ...
homepage = "https://python-poetry.org/"
```
### repository
**Deprecated**: Use `project.urls` instead.
A URL to the repository of the project.
```toml
[tool.poetry]
# ...
repository = "https://github.com/python-poetry/poetry"
```
### documentation
**Deprecated**: Use `project.urls` instead.
A URL to the documentation of the project.
```toml
[tool.poetry]
# ...
documentation = "https://python-poetry.org/docs/"
```
### keywords
**Deprecated**: Use `project.keywords` instead.
A list of keywords that the package is related to.
```toml
[tool.poetry]
# ...
keywords = ["packaging", "poetry"]
```
### classifiers
A list of PyPI [trove classifiers](https://pypi.org/classifiers/) that describe the project.
```toml
[tool.poetry]
# ...
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
```
{{% note %}}
Note that Python classifiers are automatically added for you
and are determined by your `python` requirement.
If you do not want Poetry to automatically add suitable classifiers
based on the `python` requirement, use `project.classifiers` instead of this setting.
{{% /note %}}
### packages
A list of packages and modules to include in the final distribution.
If packages are not automatically detected, you can specify the packages you want
to include in the final distribution.
{{% note %}}
Poetry automatically detects a single **module** or **package** whose name matches the
[normalized](https://packaging.python.org/en/latest/specifications/name-normalization/#name-normalization)
project name with `-` replaced with `_`.
The detected module or package must be located either:
- at the same level as the `pyproject.toml` file (flat layout), or
- inside a `src/` directory (src layout).
{{% /note %}}
```toml
[tool.poetry]
# ...
packages = [
{ include = "my_package" },
{ include = "extra_package/**/*.py" },
]
```
If your package is stored inside a "lib" directory, you must specify it:
```toml
[tool.poetry]
# ...
packages = [
{ include = "my_package", from = "lib" },
]
```
The `to` parameter is designed to specify the relative destination path
where the package will be located upon installation. This allows for
greater control over the organization of packages within your project's structure.
```toml
[tool.poetry]
# ...
packages = [
{ include = "my_package", from = "lib", to = "target_package" },
]
```
If you want to restrict a package to a specific build format, you can specify
it by using `format`:
```toml
[tool.poetry]
# ...
packages = [
{ include = "my_package" },
{ include = "my_other_package", format = "sdist" },
]
```
From now on, only the `sdist` build archive will include the `my_other_package` package.
{{% note %}}
Using `packages` disables the package auto-detection feature meaning you have to
**explicitly** specify the "default" package.
For instance, if you have a package named `my_package` and you want to also include
another package named `extra_package`, you will need to specify `my_package` explicitly:
```toml
[tool.poetry]
# ...
packages = [
{ include = "my_package" },
{ include = "extra_package" },
]
```
{{% /note %}}
{{% note %}}
Poetry is clever enough to detect Python subpackages.
Thus, you only have to specify the directory where your root package resides.
{{% /note %}}
### exclude and include
{{% note %}}
If you just want to include a package or module, which is not picked up automatically,
use [packages]({{< relref "#packages" >}}) instead of `include`.
{{% /note %}}
A list of patterns that will be excluded or included in the final package.
```toml
[tool.poetry]
# ...
exclude = ["my_package/excluded.py"]
include = ["CHANGELOG.md"]
```
You can explicitly specify to Poetry that a set of globs should be ignored or included for the purposes of packaging.
The globs specified in the exclude field identify a set of files that are not included when a package is built.
`include` has priority over `exclude`.
You can also specify the formats for which these patterns have to be included, as shown here:
```toml
[tool.poetry]
# ...
include = [
{ path = "tests", format = "sdist" },
{ path = "my_package/for_sdist_and_wheel.txt", format = ["sdist", "wheel"] }
]
```
If no format is specified, `include` defaults to only `sdist`.
In contrast, `exclude` defaults to both `sdist` and `wheel`.
{{% warning %}}
When a wheel is installed, its includes are unpacked straight into the `site-packages` directory.
Pay attention to include top level files and directories with common names like
`CHANGELOG.md`, `LICENSE`, `tests` or `docs` only in sdists and **not** in wheels.
{{% /warning %}}
If a VCS is being used for a package, the exclude field will be seeded with the VCS’ ignore settings (`.gitignore` for git, for example).
{{% note %}}
VCS ignore settings can be negated by adding entries in `include`; be sure to explicitly set the `format` as above.
{{% /note %}}
### dependencies and dependency groups
Poetry is configured to look for dependencies on [PyPI](https://pypi.org) by default.
Only the name and a version string are required in this case.
```toml
[tool.poetry.dependencies]
requests = "^2.13.0"
```
If you want to use a [private repository]({{< relref "repositories#using-a-private-repository" >}}),
you can add it to your `pyproject.toml` file, like so:
```toml
[[tool.poetry.source]]
name = "private"
url = "http://example.com/simple"
```
If you have multiple repositories configured, you can explicitly tell poetry where to look for a specific package:
```toml
[tool.poetry.dependencies]
requests = { version = "^2.13.0", source = "private" }
```
You may also specify your project's compatible python versions in this section, instead of or in addition to `project.requires-python`.
```toml
[tool.poetry.dependencies]
python = "^3.7"
```
{{% note %}}
If you specify the compatible python versions in both `tool.poetry.dependencies` and in `project.requires-python`, then Poetry will use the information in `tool.poetry.dependencies` for locking, but the python versions must be a subset of those allowed by `project.requires-python`.
For example, the following is invalid and will result in an error, because versions `4.0` and greater are allowed by `tool.poetry.dependencies`, but not by `project.requires-python`.
```toml
[project]
# ...
requires-python = ">=3.8,<4.0"
[tool.poetry.dependencies]
python = ">=3.8" # not valid!
```
{{% /note %}}
You can organize your dependencies in [groups]({{< relref "managing-dependencies#dependency-groups" >}})
to manage them in a more granular way.
```toml
[tool.poetry.group.test.dependencies]
pytest = "*"
[tool.poetry.group.docs.dependencies]
mkdocs = "*"
```
See [Dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) for a more in-depth look
at how to manage dependency groups and [Dependency specification]({{< relref "dependency-specification" >}})
for more information on other keys and specifying version ranges.
### scripts
{{% note %}}
**Deprecated**: Use [`project.scripts`]({{< relref "#scripts" >}}) instead for `console` and `gui` scripts. Use
`[tool.poetry.scripts]` only for scripts of type `file`.
{{% /note %}}
This section describes the scripts or executables that will be installed when installing the package
```toml
[tool.poetry.scripts]
my_package_cli = 'my_package.console:run'
```
Here, we will have the `my_package_cli` script installed which will execute the `run` function in the `console` module in the `my_package` package.
{{% note %}}
When a script is added or updated, run `poetry install` to make them available in the project's virtualenv.
{{% /note %}}
```toml
[tool.poetry.scripts]
my_executable = { reference = "some_binary.exe", type = "file" }
```
This tells Poetry to include the specified file, relative to your project directory, in distribution builds. It will then be copied to the appropriate installation directory for your operating system when your package is installed.
* On Windows the file is placed in the `Scripts/` directory.
* On *nix system the file is placed in the `bin/` directory.
In its table form, the value of each script can contain a `reference` and `type`. The supported types are
`console` and `file`. When the value is a string, it is inferred to be a `console` script.
### extras
**Deprecated**: Use `project.optional-dependencies` instead.
Poetry supports extras to allow expression of:
* optional dependencies, which enhance a package, but are not required; and
* clusters of optional dependencies.
```toml
[tool.poetry]
name = "awesome"
[tool.poetry.dependencies]
# These packages are mandatory and form the core of this package’s distribution.
mandatory = "^1.0"
# A list of all of the optional dependencies, some of which are included in the
# below `extras`. They can be opted into by apps.
psycopg2 = { version = "^2.9", optional = true }
mysqlclient = { version = "^1.3", optional = true }
[tool.poetry.extras]
mysql = ["mysqlclient"]
pgsql = ["psycopg2"]
databases = ["mysqlclient", "psycopg2"]
```
When installing packages with Poetry, you can specify extras by using the `-E|--extras` option:
```bash
poetry install --extras "mysql pgsql"
poetry install -E mysql -E pgsql
```
Any extras you don't specify will be removed. Note this behavior is different from
[optional dependency groups]({{< relref "managing-dependencies#optional-groups" >}}) not
selected for installation, e.g., those not specified via `install --with`.
You can install all extras with the `--all-extras` option:
```bash
poetry install --all-extras
```
{{% note %}}
Note that `install --extras` and the variations mentioned above (`--all-extras`, `--extras foo`, etc.) only work on dependencies defined in the current project. If you want to install extras defined by dependencies, you'll have to express that in the dependency itself:
```toml
[tool.poetry.dependencies]
pandas = {version="^2.2.1", extras=["computation", "performance"]}
```
```toml
[tool.poetry.group.dev.dependencies]
fastapi = {version="^0.92.0", extras=["all"]}
```
{{% /note %}}
When installing or specifying Poetry-built packages, the extras defined in this section can be activated
as described in [PEP 508](https://www.python.org/dev/peps/pep-0508/#extras).
For example, when installing the package using `pip`, the dependencies required by
the `databases` extra can be installed as shown below.
```bash
pip install awesome[databases]
```
{{% note %}}
The dependencies specified for each `extra` must already be defined as project dependencies.
Dependencies listed in [dependency groups]({{< relref "managing-dependencies#dependency-groups" >}}) cannot be specified as extras.
{{% /note %}}
### plugins
**Deprecated**: Use `project.entry-points` instead.
Poetry supports arbitrary plugins, which are exposed as the ecosystem-standard [entry points](https://packaging.python.org/en/latest/specifications/entry-points/) and discoverable using `importlib.metadata`. This is similar to (and compatible with) the entry points feature of `setuptools`.
The syntax for registering a plugin is:
```toml
[tool.poetry.plugins] # Optional super table
[tool.poetry.plugins."A"]
B = "C:D"
```
Which are:
- `A` - type of the plugin, for example `poetry.plugin` or `flake8.extension`
- `B` - name of the plugin
- `C` - python module import path
- `D` - the entry point of the plugin (a function or class)
Example (from [`poetry-plugin-export`](http://github.com/python-poetry/poetry-plugin-export)):
```toml
[tool.poetry.plugins."poetry.application.plugin"]
export = "poetry_plugin_export.plugins:ExportApplicationPlugin"
```
### urls
**Deprecated**: Use `project.urls` instead.
In addition to the basic urls (`homepage`, `repository` and `documentation`), you can specify
any custom url in the `urls` section.
```toml
[tool.poetry.urls]
"Bug Tracker" = "https://github.com/python-poetry/poetry/issues"
```
If you publish your package on PyPI, they will appear in the `Project Links` section.
### requires-poetry
A constraint for the Poetry version that is required for this project.
If you are using a Poetry version that is not allowed by this constraint,
an error will be raised.
```toml
[tool.poetry]
# ...
requires-poetry = ">=2.0"
```
### requires-plugins
In this section, you can specify that certain plugins are required for your project:
```toml
[tool.poetry.requires-plugins]
my-application-plugin = ">=1.0"
my-plugin = ">=1.0,<2.0"
```
See [Project plugins]({{< relref "plugins#project-plugins" >}}) for more information.
### build-constraints
In this section, you can specify additional constraints to apply when creating the build
environment for a dependency. This is useful if a package does not provide wheels
(or shall be built from source for other reasons)
and specifies too loose build requirements (without an upper bound)
and is not compatible with current versions of one of its build requirements.
For example, if your project depends on `some-package`, which only provides an sdist
and defines its build requirements as `build-requires = ["setuptools"]`,
but is incompatible with `setuptools >= 78`, building the package will probably fail
because per default the latest setuptools will be chosen. In this case, you can
work around this issue of `some-package` as follows:
```toml
[tool.poetry.build-constraints]
some-package = { setuptools = "<78" }
```
The syntax for specifying constraints is the same as for specifying dependencies
in the `tool.poetry` section.
## Poetry and PEP-517
[PEP-517](https://www.python.org/dev/peps/pep-0517/) introduces a standard way
to define alternative build systems to build a Python project.
Poetry is compliant with PEP-517, by providing a lightweight core library,
so if you use Poetry to manage your Python project, you should reference
it in the `build-system` section of the `pyproject.toml` file like so:
```toml
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
```
{{% note %}}
When using the `new` or `init` command this section will be automatically added.
{{% /note %}}
{{% note %}}
If your `pyproject.toml` file still references `poetry` directly as a build backend,
you should update it to reference `poetry-core` instead.
{{% /note %}}
================================================
FILE: docs/repositories.md
================================================
---
title: "Repositories"
draft: false
type: docs
layout: "docs"
menu:
docs:
weight: 50
---
# Repositories
Poetry supports the use of [PyPI](https://pypi.org) and private repositories for discovery of
packages as well as for publishing your projects.
By default, Poetry is configured to use the [PyPI](https://pypi.org) repository,
for package installation and publishing.
So, when you add dependencies to your project, Poetry will assume they are available
on PyPI.
This represents most cases and will likely be enough for most users.
### Private Repository Example
#### Installing from private package sources
By default, Poetry discovers and installs packages from [PyPI](https://pypi.org). But, you want to
install a dependency to your project for a [simple API repository](#simple-api-repository)? Let's
do it.
First, [configure](#project-configuration) the [package source](#package-sources) as a [supplemental](#supplemental-package-sources) (or [explicit](#explicit-package-sources)) package source to your
project.
```bash
poetry source add --priority=supplemental foo https://pypi.example.org/simple/
```
Then, assuming the repository requires authentication, configure credentials for it.
```bash
poetry config http-basic.foo
```
{{% warning %}}
If you have completed configuring credentials and are receiving authorization failures, check for the presence of `~/.netrc`, which has been known to conflict with Poetry's configured authentication.
{{% /warning %}}
{{% warning %}}
Depending on your system configuration, credentials might be saved in your command line history.
Many shells do not save commands to history when they are prefixed by a space character. For more information, please refer to your shell's documentation.
{{% /warning %}}
{{% note %}}
If you would like to provide the password interactively, you can simply omit `` in your command. And
Poetry will prompt you to enter the credential manually.
```bash
poetry config http-basic.foo
```
{{% /note %}}
Once this is done, you can add dependencies to your project from this source.
```bash
poetry add --source foo private-package
```
#### Publishing to a private repository
Great, now all that is left is to publish your package. Assuming you'd want to share it privately
with your team, you can configure the
[Upload API](https://warehouse.pypa.io/api-reference/legacy.html#upload-api) endpoint for your
[publishable repository](#publishable-repositories).
```bash
poetry config repositories.foo https://pypi.example.org/legacy/
```
{{% note %}}
If you need to use a different credential for your [package source](#package-sources), then it is
recommended to use a different name for your publishing repository.
```bash
poetry config repositories.foo-pub https://pypi.example.org/legacy/
poetry config http-basic.foo-pub
```
{{% /note %}}
{{% note %}}
When configuring a repository using environment variables, note that correct suffixes need to be used.
```bash
export POETRY_REPOSITORIES_FOO_URL=https://pypi.example.org/legacy/
export POETRY_HTTP_BASIC_FOO_USERNAME=
export POETRY_HTTP_BASIC_FOO_PASSWORD=
```
{{% /note %}}
Now, all that is left is to build and publish your project using the
[`publish`]({{< relref "cli#publish" >}}).
```bash
poetry publish --build --repository foo-pub
```
## Package Sources
By default, if you have not configured any primary source,
Poetry is configured to use the Python ecosystem's canonical package index
[PyPI](https://pypi.org).
You can alter this behavior and exclusively look up packages only from the configured
package sources by adding at least one primary source.
{{% note %}}
Except for the implicitly configured source for [PyPI](https://pypi.org) named `PyPI`,
package sources are local to a project and must be configured within the project's
[`pyproject.toml`]({{< relref "pyproject" >}}) file. This is **not** the same configuration used
when publishing a package.
{{% /note %}}
{{% warning %}}
Package sources are a Poetry-specific feature and **not** included in
[core metadata](https://packaging.python.org/en/latest/specifications/core-metadata/) produced by
the poetry-core build backend.
Consequently, when a Poetry project is e.g., installed using Pip (as a normal package or in editable
mode), package sources will be ignored and the dependencies in question downloaded from PyPI by
default.
{{% /warning %}}
### Project Configuration
These package sources may be managed using the [`source`]({{< relref "cli#source" >}}) command for
your project.
```bash
poetry source add foo https://foo.bar/simple/
```
{{% note %}}
If your package source requires [credentials](#configuring-credentials) or
[certificates](#certificates), please refer to the relevant sections below.
{{% /note %}}
This will generate the following configuration snippet in your
[`pyproject.toml`]({{< relref "pyproject" >}}) file.
```toml
[[tool.poetry.source]]
name = "foo"
url = "https://foo.bar/simple/"
priority = "primary"
```
If `priority` is undefined, the source is considered a primary source,
which disables the implicit PyPI source and takes precedence over supplemental sources.
Package sources are considered in the following order:
1. [primary sources](#primary-package-sources) or implicit PyPI (if there are no primary sources),
2. [supplemental sources](#supplemental-package-sources).
[Explicit sources](#explicit-package-sources) are considered only for packages that explicitly [indicate their source](#package-source-constraint).
Within each priority class, package sources are considered in order of appearance in `pyproject.toml`.
#### Primary Package Sources
All primary package sources are searched for each dependency without a [source constraint](#package-source-constraint).
If you configure at least one primary source, the implicit PyPI source is disabled.
```bash
poetry source add --priority=primary foo https://foo.bar/simple/
```
Sources without a priority are considered primary sources, too.
```bash
poetry source add foo https://foo.bar/simple/
```
{{% warning %}}
The implicit PyPI source is disabled automatically if at least one primary source is configured.
If you want to use PyPI in addition to a primary source, configure it explicitly
with a certain priority, e.g.
```bash
poetry source add --priority=primary PyPI
```
This way, the priority of PyPI can be set in a fine-granular way.
The equivalent specification in `pyproject.toml` is:
```toml
[[tool.poetry.source]]
name = "pypi"
priority = "primary"
```
**Omit the `url` when specifying PyPI explicitly.** Because PyPI is internally configured
with Poetry, the PyPI repository cannot be configured with a given URL. Remember, you can always use
`poetry check` to ensure the validity of the `pyproject.toml` file.
{{% /warning %}}
#### Supplemental Package Sources
*Introduced in 1.5.0*
Package sources configured as supplemental are only searched if no other (higher-priority) source yields a compatible package distribution. This is particularly convenient if the response time of the source is high and relatively few package distributions are to be fetched from this source.
You can configure a package source as a supplemental source with `priority = "supplemental"` in your package
source configuration.
```bash
poetry source add --priority=supplemental foo https://foo.bar/simple/
```
There can be more than one supplemental package source.
{{% warning %}}
Take into account that someone could publish a new package to a primary source
which matches a package in your supplemental source. They could coincidentally
or intentionally replace your dependency with something you did not expect.
{{% /warning %}}
#### Explicit Package Sources
*Introduced in 1.5.0*
If package sources are configured as explicit, these sources are only searched when a package configuration [explicitly indicates](#package-source-constraint) that it should be found on this package source.
You can configure a package source as an explicit source with `priority = "explicit"` in your package source configuration.
```bash
poetry source add --priority=explicit foo https://foo.bar/simple/
```
There can be more than one explicit package source.
{{% note %}}
A real-world example where an explicit package source is useful, is for PyTorch GPU packages.
```bash
poetry source add --priority=explicit pytorch-gpu-src https://download.pytorch.org/whl/cu118
poetry add --source pytorch-gpu-src torch torchvision torchaudio
```
{{% /note %}}
#### Package Source Constraint
All package sources (including possibly supplemental sources) will be searched
during the package lookup process.
These network requests will occur for all primary sources, regardless of if the package is
found at one or more sources, and all supplemental sources until the package is found.
In order to limit the search for a specific package to a particular package repository, you can specify the source explicitly.
```bash
poetry add --source internal-pypi httpx
```
This results in the following configuration in `pyproject.toml`:
```toml
[tool.poetry.dependencies]
...
httpx = { version = "^0.22", source = "internal-pypi" }
[[tool.poetry.source]]
name = "internal-pypi"
url = ...
priority = ...
```
{{% note %}}
A repository that is configured to be the only source for retrieving a certain package can itself have any priority.
In particular, it does not need to have priority `"explicit"`.
If a repository is configured to be the source of a package, it will be the only source that is considered for that package
and the repository priority will have no effect on the resolution.
{{% /note %}}
{{% note %}}
Package `source` keys are not inherited by their dependencies.
In particular, if `package-A` is configured to be found in `source = internal-pypi`,
and `package-A` depends on `package-B` that is also to be found on `internal-pypi`,
then `package-B` needs to be configured as such in `pyproject.toml`.
The easiest way to achieve this is to add `package-B` with a wildcard constraint:
```bash
poetry add --source internal-pypi package-B@*
```
This will ensure that `package-B` is searched only in the `internal-pypi` package source.
The version constraints on `package-B` are derived from `package-A` (and other client packages), as usual.
If you want to avoid additional main dependencies,
you can add `package-B` to a dedicated [dependency group]({{< relref "managing-dependencies#dependency-groups" >}}):
```bash
poetry add --group explicit --source internal-pypi package-B@*
```
{{% /note %}}
{{% note %}}
Package source constraints are strongly suggested for all packages that are expected
to be provided only by one specific source to avoid dependency confusion attacks.
{{% /note %}}
### Supported Package Sources
#### Python Package Index (PyPI)
Poetry interacts with [PyPI](https://pypi.org) via its
[JSON API](https://warehouse.pypa.io/api-reference/json.html). This is used to retrieve a requested
package's versions, metadata, files, etc.
{{% note %}}
If the package's published metadata is invalid, Poetry will download the available bdist/sdist to
inspect it locally to identify the relevant metadata.
{{% /note %}}
If you want to explicitly select a package from [PyPI](https://pypi.org) you can use the `--source`
option with the [`add`]({{< relref "cli#add" >}}) command, like shown below.
```bash
poetry add --source pypi httpx@^0.22.0
```
This will generate the following configuration snippet in your `pyproject.toml` file.
```toml
httpx = {version = "^0.22.0", source = "pypi"}
```
{{% warning %}}
The implicit `PyPI` source will be disabled and not used for any packages
if at least one [primary source](#primary-package-sources) is configured.
{{% /warning %}}
#### Simple API Repository
Poetry can fetch and install package dependencies from public or private custom repositories that
implement the simple repository API as described in [PEP 503](https://peps.python.org/pep-0503/).
{{% warning %}}
When using sources that distribute large wheels without providing file checksum in file URLs,
Poetry will download each candidate wheel at least once in order to generate the checksum. This can
manifest as long dependency resolution times when adding packages from this source.
{{% /warning %}}
These package sources may be configured via the following command in your project.
```bash
poetry source add testpypi https://test.pypi.org/simple/
```
{{% note %}}
Note the trailing `/simple/`. This is important when configuring
[PEP 503](https://peps.python.org/pep-0503/) compliant package sources.
{{% /note %}}
In addition to [PEP 503](https://peps.python.org/pep-0503/), Poetry can also handle simple API
repositories that implement [PEP 658](https://peps.python.org/pep-0658/) (*Introduced in 1.2.0*).
This is helpful in reducing dependency resolution time for packages from these sources as Poetry can
avoid having to download each candidate distribution, in order to determine associated metadata.
{{% note %}}
*Why does Poetry insist on downloading all candidate distributions for all platforms when metadata
is not available?*
The need for this stems from the fact that Poetry's lock file is platform-agnostic. This means, in
order to resolve dependencies for a project, Poetry needs metadata for all platform-specific
distributions. And when this metadata is not readily available, downloading the distribution and
inspecting it locally is the only remaining option.
{{% /note %}}
#### Single Page Link Source
*Introduced in 1.2.0*
Some projects choose to release their binary distributions via a single page link source that
partially follows the structure of a package page in [PEP 503](https://peps.python.org/pep-0503/).
These package sources may be configured via the following command in your project.
```bash
poetry source add jax https://storage.googleapis.com/jax-releases/jax_releases.html
```
{{% note %}}
All caveats regarding slower resolution times described for simple API repositories do apply here as
well.
{{% /note %}}
## Publishable Repositories
Poetry treats repositories to which you publish packages as user-specific and not project-specific
configuration unlike [package sources](#package-sources). Poetry, today, only supports the
[Legacy Upload API](https://warehouse.pypa.io/api-reference/legacy.html#upload-api) when publishing
your project.
These are configured using the [`config`]({{< relref "cli#config" >}}) command, under the
`repositories` key.
```bash
poetry config repositories.testpypi https://test.pypi.org/legacy/
```
{{% note %}}
[Legacy Upload API](https://warehouse.pypa.io/api-reference/legacy.html#upload-api) URLs are
typically different to the same one provided by the repository for the simple API. You'll note that
in the example of [Test PyPI](https://test.pypi.org/), both the host (`test.pypi.org`) as
well as the path (`/legacy`) are different to its simple API (`https://test.pypi.org/simple`).
{{% /note %}}
## Configuring Credentials
If you want to store your credentials for a specific repository, you can do so easily:
```bash
poetry config http-basic.foo
```
If you do not specify the password, you will be prompted to write it.
{{% note %}}
To publish to PyPI, you can set your credentials for the repository named `pypi`.
Note that it is recommended to use [API tokens](https://pypi.org/help/#apitoken)
when uploading packages to PyPI.
Once you have created a new token, you can tell Poetry to use it:
```bash
poetry config pypi-token.pypi
```
If you have configured **testpypi** as a [Publishable Repository](#publishable-repositories), the token can be set using
```bash
poetry config pypi-token.testpypi
```
If you still want to use your username and password, you can do so with the following
call to `config`.
```bash
poetry config http-basic.pypi
```
{{% /note %}}
You can also specify the username and password when using the `publish` command
with the `--username` and `--password` options.
If a system keyring is available and supported, the password is stored to and retrieved from the keyring. In the above example, the credential will be stored using the name `poetry-repository-pypi`. If access to keyring fails or is unsupported, this will fall back to writing the password to the `auth.toml` file along with the username.
Keyring support is enabled using the [keyring library](https://pypi.org/project/keyring/). For more information on supported backends refer to the [library documentation](https://keyring.readthedocs.io/en/latest/?badge=latest).
If you do not want to use the keyring, you can tell Poetry to disable it and store the credentials in plaintext config files:
```bash
poetry config keyring.enabled false
```
{{% note %}}
Poetry will fall back to Pip style use of keyring so that backends like
Microsoft's [artifacts-keyring](https://pypi.org/project/artifacts-keyring/) get a chance to retrieve
valid credentials. It will need to be properly installed into Poetry's virtualenv,
preferably by installing a plugin.
{{% /note %}}
Alternatively, you can use environment variables to provide the credentials:
```bash
export POETRY_PYPI_TOKEN_FOO=my-token
export POETRY_HTTP_BASIC_FOO_USERNAME=
export POETRY_HTTP_BASIC_FOO_PASSWORD=
```
where `FOO` is the name of the repository in uppercase (e.g. `PYPI`).
See [Using environment variables]({{< relref "configuration#using-environment-variables" >}}) for more information
on how to configure Poetry with environment variables.
If your password starts with a dash (e.g., randomly generated tokens in a CI environment), it will be parsed as a
command line option instead of a password.
You can prevent this by adding double dashes to prevent any following argument from being parsed as an option.
```bash
poetry config -- http-basic.pypi myUsername -myPasswordStartingWithDash
```
{{% note %}}
In some cases like that of [Gemfury](https://gemfury.com/help/errors/repo-url-password/) repositories, it might be
required to set an empty password. This is supported by Poetry.
```bash
poetry config http-basic.foo ""
```
**Note:** Empty usernames are discouraged. However, Poetry will honor them if a password is configured without it. This
is unfortunately commonplace practice, while not best practice, for private indices that use tokens. When a password is
stored into the system keyring with an empty username, Poetry will use a literal `__poetry_source_empty_username__` as
the username to circumvent [keyring#687](https://github.com/jaraco/keyring/pull/687).
{{% /note %}}
## Certificates
### Custom certificate authority and mutual TLS authentication
Poetry supports repositories that are secured by a custom certificate authority as well as those that require
certificate-based client authentication. The following will configure the "foo" repository to validate the repository's
certificate using a custom certificate authority and use a client certificate (note that these config variables do not
both need to be set):
```bash
poetry config certificates.foo.cert /path/to/ca.pem
poetry config certificates.foo.client-cert /path/to/client.pem
```
{{% note %}}
The value of `certificates..cert` can be set to `false` if certificate verification is
required to be skipped. This is useful for cases where a package source with self-signed certificates
is used.
```bash
poetry config certificates.foo.cert false
```
{{% warning %}}
Disabling certificate verification is not recommended as it does not conform to security
best practices.
{{% /warning %}}
{{% /note %}}
## Caches
Poetry employs multiple caches for package sources in order to improve user experience and avoid duplicate network
requests.
The first level cache is a [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)
header-based cache for almost all HTTP requests.
Further, every HTTP backed package source caches metadata associated with a package once it is fetched or generated.
Additionally, downloaded files (package distributions) are also cached.
## Debugging Issues
If you encounter issues with package sources, one of the simplest steps you might take to debug an issue is rerunning
your command with the `--no-cache` flag.
```bash
poetry --no-cache add pycowsay
```
If this solves your issue, you can consider clearing your cache using the [`cache`]({{< relref "cli#cache-clear" >}})
command.
Alternatively, you could also consider enabling very verbose logging `-vvv` along with the `--no-cache` to see network
requests being made in the logs.
================================================
FILE: pyproject.toml
================================================
[project]
name = "poetry"
version = "2.3.2"
description = "Python dependency management and packaging made easy."
requires-python = ">=3.10,<4.0"
dependencies = [
"poetry-core (==2.3.1)",
"build (>=1.2.1,<2.0.0)",
"cachecontrol[filecache] (>=0.14.0,<0.15.0)",
"cleo (>=2.1.0,<3.0.0)",
"dulwich (>=0.25.0,<2)",
"fastjsonschema (>=2.18.0,<3.0.0)",
"installer (>=0.7.0,<0.8.0)",
"keyring (>=25.1.0,<26.0.0)",
# packaging uses calver, so version is unclamped
"packaging (>=24.2)", # PEP 639 support was added in 24.2
"pkginfo (>=1.12,<2.0)",
"platformdirs (>=3.0.0,<5)",
"pyproject-hooks (>=1.0.0,<2.0.0)",
"requests (>=2.26,<3.0)",
"requests-toolbelt (>=1.0.0,<2.0.0)",
"shellingham (>=1.5,<2.0)",
"tomli (>=2.0.1,<3.0.0) ; python_version < '3.11'",
"tomlkit (>=0.11.4,<1.0.0)",
# trove-classifiers uses calver, so version is unclamped
"trove-classifiers (>=2022.5.19)",
"virtualenv (>=20.26.6)",
"xattr (>=1.0.0,<2.0.0) ; sys_platform == 'darwin'",
"findpython (>=0.6.2,<0.8.0)",
# pbs-installer uses calver, so version is unclamped
"pbs-installer[download,install] (>=2025.6.10)",
]
authors = [
{ name = "Sébastien Eustace", email = "sebastien@eustace.io" }
]
maintainers = [
{ name = "Arun Babu Neelicattu", email = "arun.neelicattu@gmail.com" },
{ name = "Bjorn Neergaard", email = "bjorn@neersighted.com" },
{ name = "Branch Vincent", email = "branchevincent@gmail.com" },
{ name = "Randy Döring", email = "radoering.poetry@gmail.com" },
{ name = "Steph Samson", email = "hello@stephsamson.com" },
{ name = "finswimmer", email = "finswimmer77@gmail.com" },
{ name = "Bartosz Sokorski", email = "b.sokorski@gmail.com" },
]
license = "MIT"
readme = "README.md"
keywords = ["packaging", "dependency", "poetry"]
# classifiers are dynamic because we want to create Python classifiers automatically
dynamic = [ "classifiers" ]
[project.urls]
Homepage = "https://python-poetry.org/"
Changelog = "https://python-poetry.org/history/"
Repository = "https://github.com/python-poetry/poetry"
Documentation = "https://python-poetry.org/docs"
[project.scripts]
poetry = "poetry.console.application:main"
[tool.poetry]
requires-poetry = ">=2.0"
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules",
]
include = [{ path = "tests", format = "sdist" }]
[tool.poetry.group.dev.dependencies]
pre-commit = ">=2.10"
[tool.poetry.group.test.dependencies]
coverage = ">=7.2.0"
deepdiff = ">=6.3"
responses = ">=0.25.8"
jaraco-classes = ">=3.3.1"
pytest = ">=8.0"
pytest-cov = ">=4.0"
pytest-mock = ">=3.9"
pytest-randomly = ">=3.12"
pytest-xdist = { version = ">=3.1", extras = ["psutil"] }
[tool.poetry.group.typing.dependencies]
mypy = ">=1.8.0"
types-requests = ">=2.28.8"
# only used in github actions
[tool.poetry.group.github-actions]
optional = true
[tool.poetry.group.github-actions.dependencies]
pytest-github-actions-annotate-failures = "^0.1.7"
[build-system]
requires = ["poetry-core>=2.0"]
build-backend = "poetry.core.masonry.api"
[tool.ruff]
extend-exclude = [
"docs/*",
# External to the project's coding standards
"tests/fixtures/git/*",
"tests/fixtures/project_with_setup*/*",
]
fix = true
line-length = 88
[tool.ruff.lint]
extend-select = [
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"ERA", # flake8-eradicate/eradicate
"I", # isort
"N", # pep8-naming
"PIE", # flake8-pie
"PGH", # pygrep
"RUF", # ruff checks
"SIM", # flake8-simplify
"T20", # flake8-print
"TC", # flake8-type-checking
"TID", # flake8-tidy-imports
"UP", # pyupgrade
]
ignore = [
"B904", # use 'raise ... from err'
"B905", # use explicit 'strict=' parameter with 'zip()'
]
extend-safe-fixes = [
"TC", # move import from and to TYPE_CHECKING blocks
]
unfixable = [
"ERA", # do not autoremove commented out code
]
[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"
[tool.ruff.lint.isort]
force-single-line = true
lines-between-types = 1
lines-after-imports = 2
known-first-party = ["poetry"]
known-third-party = ["poetry.core"]
required-imports = ["from __future__ import annotations"]
[tool.mypy]
files = "src, tests"
mypy_path = "src"
namespace_packages = true
explicit_package_bases = true
strict = true
enable_error_code = [
"ignore-without-code",
"redundant-expr",
"truthy-bool",
]
exclude = [
"tests/fixtures",
"tests/masonry/builders/fixtures",
"tests/utils/fixtures",
]
# use of importlib-metadata backport makes it impossible to satisfy mypy
# without some ignores: but we get different sets of ignores at different
# python versions.
[[tool.mypy.overrides]]
module = [
'poetry.repositories.installed_repository',
'tests.console.commands.self.test_show_plugins',
'tests.repositories.test_installed_repository',
'tests.helpers',
]
warn_unused_ignores = false
[[tool.mypy.overrides]]
module = [
'deepdiff.*',
'fastjsonschema.*',
'findpython.*',
'requests_toolbelt.*',
'shellingham.*',
'virtualenv.*',
'xattr.*',
]
ignore_missing_imports = true
[tool.pytest.ini_options]
addopts = [ "-n", "logical", "-ra", "--strict-config", "--strict-markers" ]
testpaths = ["tests"]
markers = [
"network: mark tests that require internet access",
"skip_git_mock: mark tests that should not auto-apply git_mock"
]
log_cli_level = "INFO"
xfail_strict = true
[tool.coverage.report]
exclude_also = [
"if TYPE_CHECKING:"
]
[tool.repo-review]
ignore = [
"PY007",
"PP302",
"PP309",
"GH101",
"GH212",
"PC111",
"PC140",
"PC160",
"PC170",
"PC180",
"PC180",
"PC901",
"MY103",
"RTD100",
]
================================================
FILE: src/poetry/__main__.py
================================================
from __future__ import annotations
import sys
if __name__ == "__main__":
from poetry.console.application import main
sys.exit(main())
================================================
FILE: src/poetry/__version__.py
================================================
from __future__ import annotations
from importlib import metadata
__version__ = metadata.version("poetry")
================================================
FILE: src/poetry/config/__init__.py
================================================
================================================
FILE: src/poetry/config/config.py
================================================
from __future__ import annotations
import dataclasses
import json
import logging
import os
import re
from copy import deepcopy
from json import JSONDecodeError
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from packaging.utils import NormalizedName
from packaging.utils import canonicalize_name
from poetry.config.dict_config_source import DictConfigSource
from poetry.config.file_config_source import FileConfigSource
from poetry.locations import CONFIG_DIR
from poetry.locations import DEFAULT_CACHE_DIR
from poetry.locations import data_dir
from poetry.toml import TOMLFile
if TYPE_CHECKING:
from collections.abc import Callable
from collections.abc import Mapping
from collections.abc import Sequence
from poetry.config.config_source import ConfigSource
def boolean_validator(val: str) -> bool:
return val in {"true", "false", "1", "0"}
def boolean_normalizer(val: str) -> bool:
return val.lower() in ["true", "1"]
def int_normalizer(val: str) -> int:
return int(val)
def build_config_setting_validator(val: str) -> bool:
try:
value = build_config_setting_normalizer(val)
except JSONDecodeError:
return False
if not isinstance(value, dict):
return False
for key, item in value.items():
# keys should be string
if not isinstance(key, str):
return False
# items are allowed to be a string
if isinstance(item, str):
continue
# list items should only contain strings
is_valid_list = isinstance(item, list) and all(isinstance(i, str) for i in item)
if not is_valid_list:
return False
return True
def build_config_setting_normalizer(val: str) -> Mapping[str, str | Sequence[str]]:
value: Mapping[str, str | Sequence[str]] = json.loads(val)
return value
@dataclasses.dataclass
class PackageFilterPolicy:
policy: dataclasses.InitVar[str | list[str] | None]
packages: list[str] = dataclasses.field(init=False)
def __post_init__(self, policy: str | list[str] | None) -> None:
if not policy:
policy = []
elif isinstance(policy, str):
policy = self.normalize(policy)
self.packages = policy
def allows(self, package_name: str) -> bool:
if ":all:" in self.packages:
return False
return (
not self.packages
or ":none:" in self.packages
or canonicalize_name(package_name) not in self.packages
)
def has_exact_package(self, package_name: str) -> bool:
return canonicalize_name(package_name) in self.packages
@classmethod
def is_reserved(cls, name: str) -> bool:
return bool(re.match(r":(all|none):", name))
@classmethod
def normalize(cls, policy: str) -> list[str]:
if boolean_validator(policy):
if boolean_normalizer(policy):
return [":all:"]
else:
return [":none:"]
return list(
{
name.strip() if cls.is_reserved(name) else canonicalize_name(name)
for name in policy.strip().split(",")
if name
}
)
@classmethod
def validator(cls, policy: str) -> bool:
if boolean_validator(policy):
return True
names = policy.strip().split(",")
for name in names:
if (
not name
or (cls.is_reserved(name) and len(names) == 1)
or re.match(r"^[a-zA-Z\d_-]+$", name)
):
continue
return False
return True
logger = logging.getLogger(__name__)
_default_config: Config | None = None
class Config:
default_config: ClassVar[dict[str, Any]] = {
"cache-dir": str(DEFAULT_CACHE_DIR),
"data-dir": str(data_dir()),
"virtualenvs": {
"create": True,
"in-project": None,
"path": os.path.join("{cache-dir}", "virtualenvs"),
"options": {
"always-copy": False,
"system-site-packages": False,
"no-pip": False,
},
"use-poetry-python": False,
"prompt": "{project_name}-py{python_version}",
},
"requests": {
"max-retries": 0,
},
"installer": {
"re-resolve": False,
"parallel": True,
"max-workers": None,
"no-binary": None,
"only-binary": None,
"build-config-settings": {},
},
"python": {"installation-dir": os.path.join("{data-dir}", "python")},
"solver": {
"lazy-wheel": True,
},
"system-git-client": False,
"keyring": {
"enabled": True,
},
}
def __init__(self, use_environment: bool = True) -> None:
self._config = deepcopy(self.default_config)
self._use_environment = use_environment
self._config_source: ConfigSource = DictConfigSource()
self._auth_config_source: ConfigSource = DictConfigSource()
@property
def config(self) -> dict[str, Any]:
return self._config
@property
def config_source(self) -> ConfigSource:
return self._config_source
@property
def auth_config_source(self) -> ConfigSource:
return self._auth_config_source
def set_config_source(self, config_source: ConfigSource) -> Config:
self._config_source = config_source
return self
def set_auth_config_source(self, config_source: ConfigSource) -> Config:
self._auth_config_source = config_source
return self
def merge(self, config: dict[str, Any]) -> None:
from poetry.utils.helpers import merge_dicts
merge_dicts(self._config, config)
def all(self) -> dict[str, Any]:
def _all(config: dict[str, Any], parent_key: str = "") -> dict[str, Any]:
all_ = {}
for key in config:
value = self.get(parent_key + key)
if isinstance(value, dict):
if parent_key != "":
current_parent = parent_key + key + "."
else:
current_parent = key + "."
all_[key] = _all(config[key], parent_key=current_parent)
continue
all_[key] = value
return all_
return _all(self.config)
def raw(self) -> dict[str, Any]:
return self._config
@staticmethod
def _get_environment_repositories() -> dict[str, dict[str, str]]:
repositories = {}
pattern = re.compile(r"POETRY_REPOSITORIES_(?P[A-Z_]+)_URL")
for env_key in os.environ:
match = pattern.match(env_key)
if match:
repositories[match.group("name").lower().replace("_", "-")] = {
"url": os.environ[env_key]
}
return repositories
@staticmethod
def _get_environment_build_config_settings() -> Mapping[
NormalizedName, Mapping[str, str | Sequence[str]]
]:
build_config_settings = {}
pattern = re.compile(r"POETRY_INSTALLER_BUILD_CONFIG_SETTINGS_(?P[^.]+)")
for env_key in os.environ:
if match := pattern.match(env_key):
if not build_config_setting_validator(os.environ[env_key]):
logger.debug(
"Invalid value set for environment variable %s", env_key
)
continue
build_config_settings[canonicalize_name(match.group("name"))] = (
build_config_setting_normalizer(os.environ[env_key])
)
return build_config_settings
@property
def repository_cache_directory(self) -> Path:
return Path(self.get("cache-dir")).expanduser() / "cache" / "repositories"
@property
def artifacts_cache_directory(self) -> Path:
return Path(self.get("cache-dir")).expanduser() / "artifacts"
@property
def virtualenvs_path(self) -> Path:
path = self.get("virtualenvs.path")
if path is None:
path = Path(self.get("cache-dir")) / "virtualenvs"
return Path(path).expanduser()
@property
def python_installation_dir(self) -> Path:
path = self.get("python.installation-dir")
if path is None:
path = Path(self.get("data-dir")) / "python"
return Path(path).expanduser()
@property
def installer_max_workers(self) -> int:
# This should be directly handled by ThreadPoolExecutor
# however, on some systems the number of CPUs cannot be determined
# (it raises a NotImplementedError), so, in this case, we assume
# that the system only has one CPU.
try:
default_max_workers = (os.cpu_count() or 1) + 4
except NotImplementedError:
default_max_workers = 5
desired_max_workers = self.get("installer.max-workers")
if desired_max_workers is None:
return default_max_workers
return min(default_max_workers, int(desired_max_workers))
def get(self, setting_name: str, default: Any = None) -> Any:
"""
Retrieve a setting value.
"""
keys = setting_name.split(".")
build_config_settings: Mapping[
NormalizedName, Mapping[str, str | Sequence[str]]
] = {}
# Looking in the environment if the setting
# is set via a POETRY_* environment variable
if self._use_environment:
if setting_name == "repositories":
# repositories setting is special for now
repositories = self._get_environment_repositories()
if repositories:
return repositories
build_config_settings_key = "installer.build-config-settings"
if setting_name == build_config_settings_key or setting_name.startswith(
f"{build_config_settings_key}."
):
build_config_settings = self._get_environment_build_config_settings()
else:
env = "POETRY_" + "_".join(k.upper().replace("-", "_") for k in keys)
env_value = os.getenv(env)
if env_value is not None:
return self.process(self._get_normalizer(setting_name)(env_value))
value = self._config
# merge installer build config settings from the environment
for package_name in build_config_settings:
value["installer"]["build-config-settings"][package_name] = (
build_config_settings[package_name]
)
for key in keys:
if key not in value:
return self.process(default)
value = value[key]
if self._use_environment and isinstance(value, dict):
# this is a configuration table, it is likely that we missed env vars
# in order to capture them recurse, eg: virtualenvs.options
return {k: self.get(f"{setting_name}.{k}") for k in value}
return self.process(value)
def process(self, value: Any) -> Any:
if not isinstance(value, str):
return value
def resolve_from_config(match: re.Match[str]) -> Any:
key = match.group(1)
config_value = self.get(key)
if config_value:
return config_value
# The key doesn't exist in the config but might be resolved later,
# so we keep it as a format variable.
return f"{{{key}}}"
return re.sub(r"{(.+?)}", resolve_from_config, value)
@staticmethod
def _get_normalizer(name: str) -> Callable[[str], Any]:
if name in {
"virtualenvs.create",
"virtualenvs.in-project",
"virtualenvs.options.always-copy",
"virtualenvs.options.no-pip",
"virtualenvs.options.system-site-packages",
"virtualenvs.use-poetry-python",
"installer.re-resolve",
"installer.parallel",
"solver.lazy-wheel",
"system-git-client",
"keyring.enabled",
}:
return boolean_normalizer
if name == "virtualenvs.path":
return lambda val: str(Path(val))
if name in {
"installer.max-workers",
"requests.max-retries",
}:
return int_normalizer
if name in ["installer.no-binary", "installer.only-binary"]:
return PackageFilterPolicy.normalize
if name.startswith("installer.build-config-settings."):
return build_config_setting_normalizer
return lambda val: val
@classmethod
def create(cls, reload: bool = False) -> Config:
global _default_config
if _default_config is None or reload:
_default_config = cls()
# Load global config
config_file = TOMLFile(CONFIG_DIR / "config.toml")
if config_file.exists():
logger.debug("Loading configuration file %s", config_file.path)
_default_config.merge(config_file.read())
_default_config.set_config_source(FileConfigSource(config_file))
# Load global auth config
auth_config_file = TOMLFile(CONFIG_DIR / "auth.toml")
if auth_config_file.exists():
logger.debug("Loading configuration file %s", auth_config_file.path)
_default_config.merge(auth_config_file.read())
_default_config.set_auth_config_source(FileConfigSource(auth_config_file))
return _default_config
================================================
FILE: src/poetry/config/config_source.py
================================================
from __future__ import annotations
import dataclasses
import json
from abc import ABC
from abc import abstractmethod
from typing import TYPE_CHECKING
from typing import Any
from cleo.io.null_io import NullIO
if TYPE_CHECKING:
from cleo.io.io import IO
UNSET = object()
class PropertyNotFoundError(ValueError):
pass
class ConfigSource(ABC):
@abstractmethod
def get_property(self, key: str) -> Any: ...
@abstractmethod
def add_property(self, key: str, value: Any) -> None: ...
@abstractmethod
def remove_property(self, key: str) -> None: ...
@dataclasses.dataclass
class ConfigSourceMigration:
old_key: str
new_key: str | None
value_migration: dict[Any, Any] = dataclasses.field(default_factory=dict)
def dry_run(self, config_source: ConfigSource, io: IO | None = None) -> bool:
io = io or NullIO()
try:
old_value = config_source.get_property(self.old_key)
except PropertyNotFoundError:
return False
new_value = (
self.value_migration[old_value] if self.value_migration else old_value
)
msg = f"{self.old_key} = {json.dumps(old_value)}"
if self.new_key is not None and new_value is not UNSET:
msg += f" -> {self.new_key} = {json.dumps(new_value)}"
elif self.new_key is None:
msg += " -> Removed from config"
elif self.new_key and new_value is UNSET:
msg += f" -> {self.new_key} = Not explicit set"
io.write_line(msg)
return True
def apply(self, config_source: ConfigSource) -> None:
try:
old_value = config_source.get_property(self.old_key)
except PropertyNotFoundError:
return
new_value = (
self.value_migration[old_value] if self.value_migration else old_value
)
config_source.remove_property(self.old_key)
if self.new_key is not None and new_value is not UNSET:
config_source.add_property(self.new_key, new_value)
def drop_empty_config_category(
keys: list[str], config: dict[Any, Any]
) -> dict[Any, Any]:
config_ = {}
for key, value in config.items():
if not keys or key != keys[0]:
config_[key] = value
continue
if keys and key == keys[0]:
if isinstance(value, dict):
value = drop_empty_config_category(keys[1:], value)
if value != {}:
config_[key] = value
return config_
================================================
FILE: src/poetry/config/dict_config_source.py
================================================
from __future__ import annotations
from typing import Any
from poetry.config.config_source import ConfigSource
from poetry.config.config_source import PropertyNotFoundError
class DictConfigSource(ConfigSource):
def __init__(self) -> None:
self._config: dict[str, Any] = {}
@property
def config(self) -> dict[str, Any]:
return self._config
def get_property(self, key: str) -> Any:
keys = key.split(".")
config = self._config
for i, key in enumerate(keys):
if key not in config:
raise PropertyNotFoundError(f"Key {'.'.join(keys)} not in config")
if i == len(keys) - 1:
return config[key]
config = config[key]
def add_property(self, key: str, value: Any) -> None:
keys = key.split(".")
config = self._config
for i, key in enumerate(keys):
if key not in config and i < len(keys) - 1:
config[key] = {}
if i == len(keys) - 1:
config[key] = value
break
config = config[key]
def remove_property(self, key: str) -> None:
keys = key.split(".")
config = self._config
for i, key in enumerate(keys):
if key not in config:
return
if i == len(keys) - 1:
del config[key]
break
config = config[key]
================================================
FILE: src/poetry/config/file_config_source.py
================================================
from __future__ import annotations
from contextlib import contextmanager
from typing import TYPE_CHECKING
from typing import Any
from tomlkit import document
from tomlkit import table
from poetry.config.config_source import ConfigSource
from poetry.config.config_source import PropertyNotFoundError
from poetry.config.config_source import drop_empty_config_category
if TYPE_CHECKING:
from collections.abc import Iterator
from tomlkit.toml_document import TOMLDocument
from poetry.toml.file import TOMLFile
class FileConfigSource(ConfigSource):
def __init__(self, file: TOMLFile) -> None:
self._file = file
@property
def name(self) -> str:
return str(self._file.path)
@property
def file(self) -> TOMLFile:
return self._file
def get_property(self, key: str) -> Any:
keys = key.split(".")
config = self.file.read() if self.file.exists() else {}
for i, key in enumerate(keys):
if key not in config:
raise PropertyNotFoundError(f"Key {'.'.join(keys)} not in config")
if i == len(keys) - 1:
return config[key]
config = config[key]
def add_property(self, key: str, value: Any) -> None:
with self.secure() as toml:
config: dict[str, Any] = toml
keys = key.split(".")
for i, key in enumerate(keys):
if key not in config and i < len(keys) - 1:
config[key] = table()
if i == len(keys) - 1:
config[key] = value
break
config = config[key]
def remove_property(self, key: str) -> None:
with self.secure() as toml:
config: dict[str, Any] = toml
keys = key.split(".")
current_config = config
for i, key in enumerate(keys):
if key not in current_config:
return
if i == len(keys) - 1:
del current_config[key]
break
current_config = current_config[key]
current_config = drop_empty_config_category(keys=keys[:-1], config=config)
config.clear()
config.update(current_config)
@contextmanager
def secure(self) -> Iterator[TOMLDocument]:
if self.file.exists():
initial_config = self.file.read()
config = self.file.read()
else:
initial_config = document()
config = document()
new_file = not self.file.exists()
yield config
try:
# Ensuring the file is only readable and writable
# by the current user
mode = 0o600
if new_file:
self.file.path.touch(mode=mode)
self.file.write(config)
except Exception:
self.file.write(initial_config)
raise
================================================
FILE: src/poetry/config/source.py
================================================
from __future__ import annotations
import dataclasses
from typing import TYPE_CHECKING
from poetry.repositories.repository_pool import Priority
if TYPE_CHECKING:
from tomlkit.items import Table
@dataclasses.dataclass(order=True, eq=True)
class Source:
name: str
url: str = ""
priority: Priority = (
Priority.PRIMARY
) # cheating in annotation: str will be converted to Priority in __post_init__
def __post_init__(self) -> None:
if isinstance(self.priority, str):
self.priority = Priority[self.priority.upper()]
def to_dict(self) -> dict[str, str | bool]:
return dataclasses.asdict(
self,
dict_factory=lambda x: {
k: v if not isinstance(v, Priority) else v.name.lower()
for (k, v) in x
if v
},
)
def to_toml_table(self) -> Table:
from tomlkit import nl
from tomlkit import table
source_table: Table = table()
for key, value in self.to_dict().items():
source_table.add(key, value)
source_table.add(nl())
return source_table
================================================
FILE: src/poetry/console/__init__.py
================================================
================================================
FILE: src/poetry/console/application.py
================================================
from __future__ import annotations
import argparse
import logging
from contextlib import suppress
from importlib import import_module
from pathlib import Path
from typing import TYPE_CHECKING
from typing import cast
from cleo._utils import find_similar_names
from cleo.application import Application as BaseApplication
from cleo.events.console_command_event import ConsoleCommandEvent
from cleo.events.console_events import COMMAND
from cleo.events.event_dispatcher import EventDispatcher
from cleo.exceptions import CleoCommandNotFoundError
from cleo.exceptions import CleoError
from cleo.formatters.style import Style
from cleo.io.inputs.argv_input import ArgvInput
from poetry.__version__ import __version__
from poetry.console.command_loader import CommandLoader
from poetry.console.commands.command import Command
from poetry.console.exceptions import PoetryRuntimeError
from poetry.utils.helpers import directory
from poetry.utils.helpers import ensure_path
if TYPE_CHECKING:
from collections.abc import Callable
from cleo.events.event import Event
from cleo.io.inputs.definition import Definition
from cleo.io.inputs.input import Input
from cleo.io.io import IO
from cleo.io.outputs.output import Output
from poetry.console.commands.installer_command import InstallerCommand
from poetry.poetry import Poetry
def load_command(name: str) -> Callable[[], Command]:
def _load() -> Command:
words = name.split(" ")
module = import_module("poetry.console.commands." + ".".join(words))
command_class = getattr(module, "".join(c.title() for c in words) + "Command")
command: Command = command_class()
return command
return _load
COMMANDS = [
"about",
"add",
"build",
"check",
"config",
"init",
"install",
"lock",
"new",
"publish",
"remove",
"run",
"search",
"show",
"sync",
"update",
"version",
# Cache commands
"cache clear",
"cache list",
# Debug commands
"debug info",
"debug resolve",
"debug tags",
# Env commands
"env activate",
"env info",
"env list",
"env remove",
"env use",
# Python commands,
"python install",
"python list",
"python remove",
# Self commands
"self add",
"self install",
"self lock",
"self remove",
"self update",
"self show",
"self show plugins",
"self sync",
# Source commands
"source add",
"source remove",
"source show",
]
# these are special messages to override the default message when a command is not found
# in cases where a previously existing command has been moved to a plugin or outright
# removed for various reasons
COMMAND_NOT_FOUND_PREFIX_MESSAGE = (
"Looks like you're trying to use a Poetry command that is not available."
)
COMMAND_NOT_FOUND_MESSAGES = {
"shell": """
Since Poetry (2.0.0>)>, the shell> command is not installed by default. You can use,
- the new env activate> command (recommended>); or
- the shell plugin> to install the shell> command
Documentation:> https://python-poetry.org/docs/managing-environments/#activating-the-environment
Note that the env activate> command is not a direct replacement for shell> command.
"""
}
class Application(BaseApplication):
def __init__(self) -> None:
super().__init__("poetry", __version__)
self._poetry: Poetry | None = None
self._io: IO | None = None
self._disable_plugins = False
self._disable_cache = False
self._plugins_loaded = False
self._working_directory = Path.cwd()
self._project_directory: Path | None = None
dispatcher = EventDispatcher()
dispatcher.add_listener(COMMAND, self.register_command_loggers)
dispatcher.add_listener(COMMAND, self.configure_env)
dispatcher.add_listener(COMMAND, self.configure_installer_for_event)
self.set_event_dispatcher(dispatcher)
command_loader = CommandLoader({name: load_command(name) for name in COMMANDS})
self.set_command_loader(command_loader)
@property
def _default_definition(self) -> Definition:
from cleo.io.inputs.option import Option
definition = super()._default_definition
definition.add_option(
Option("--no-plugins", flag=True, description="Disables plugins.")
)
definition.add_option(
Option(
"--no-cache", flag=True, description="Disables Poetry source caches."
)
)
definition.add_option(
Option(
"--project",
"-P",
flag=False,
description=(
"Specify another path as the project root."
" All command-line arguments will be resolved relative to the current working directory."
),
)
)
definition.add_option(
Option(
"--directory",
"-C",
flag=False,
description=(
"The working directory for the Poetry command (defaults to the"
" current working directory). All command-line arguments will be"
" resolved relative to the given directory."
),
)
)
return definition
@property
def project_directory(self) -> Path:
return self._project_directory or self._working_directory
@property
def poetry(self) -> Poetry:
from poetry.factory import Factory
if self._poetry is not None:
return self._poetry
self._poetry = Factory().create_poetry(
cwd=self.project_directory,
io=self._io,
disable_plugins=self._disable_plugins,
disable_cache=self._disable_cache,
)
return self._poetry
@property
def command_loader(self) -> CommandLoader:
command_loader = self._command_loader
assert isinstance(command_loader, CommandLoader)
return command_loader
def reset_poetry(self) -> None:
self._poetry = None
def create_io(
self,
input: Input | None = None,
output: Output | None = None,
error_output: Output | None = None,
) -> IO:
io = super().create_io(input, output, error_output)
# Set our own CLI styles
formatter = io.output.formatter
formatter.set_style("c1", Style("cyan"))
formatter.set_style("c2", Style("default", options=["bold"]))
formatter.set_style("info", Style("blue"))
formatter.set_style("comment", Style("green"))
formatter.set_style("warning", Style("yellow"))
formatter.set_style("debug", Style("default", options=["dark"]))
formatter.set_style("success", Style("green"))
# Dark variants
formatter.set_style("c1_dark", Style("cyan", options=["dark"]))
formatter.set_style("c2_dark", Style("default", options=["bold", "dark"]))
formatter.set_style("success_dark", Style("green", options=["dark"]))
io.output.set_formatter(formatter)
io.error_output.set_formatter(formatter)
self._io = io
return io
def _run(self, io: IO) -> int:
# we do this here and not inside the _configure_io implementation in order
# to ensure the users are not exposed to a stack trace for providing invalid values to
# the options --directory or --project, configuring the options here allow cleo to trap and
# display the error cleanly unless the user uses verbose or debug
self._configure_global_options(io)
with directory(self._working_directory):
self._load_plugins(io)
exit_code: int = 1
try:
exit_code = super()._run(io)
except PoetryRuntimeError as e:
io.write_error_line("")
e.write(io)
io.write_error_line("")
except CleoCommandNotFoundError as e:
command = self._get_command_name(io)
if command is not None and (
message := COMMAND_NOT_FOUND_MESSAGES.get(command)
):
io.write_error_line("")
io.write_error_line(COMMAND_NOT_FOUND_PREFIX_MESSAGE)
io.write_error_line(message)
return 1
if command is not None and command in self.get_namespaces():
sub_commands = []
for key in self._commands:
if key.startswith(f"{command} "):
sub_commands.append(key)
io.write_error_line(
f"The requested command does not exist in the {command}> namespace."
)
suggested_names = find_similar_names(command, sub_commands)
self._error_write_command_suggestions(
io, suggested_names, f"#{command}"
)
return 1
if command is not None:
suggested_names = find_similar_names(
command, list(self._commands.keys())
)
io.write_error_line(
f"The requested command {command}> does not exist."
)
self._error_write_command_suggestions(io, suggested_names)
return 1
raise e
return exit_code
def _error_write_command_suggestions(
self, io: IO, suggested_names: list[str], doc_tag: str | None = None
) -> None:
if suggested_names:
suggestion_lines = [
f"{name.replace(' ', '> ', 1)}>: {self._commands[name].description}"
for name in suggested_names
]
suggestions = "\n ".join(["", *sorted(suggestion_lines)])
io.write_error_line(
f"\nDid you mean one of these perhaps?>{suggestions}"
)
io.write_error_line(
"\nDocumentation: >"
f"https://python-poetry.org/docs/cli/{doc_tag or ''}>"
)
def _configure_global_options(self, io: IO) -> None:
"""
Configures global options for the application by setting up the relevant
directories, disabling plugins or cache, and managing the working and
project directories. This method ensures that all directories are valid
paths and handles the resolution of the project directory relative to the
working directory if necessary.
:param io: The IO instance whose input and options are being read.
:return: Nothing.
"""
self._disable_plugins = io.input.option("no-plugins")
self._disable_cache = io.input.option("no-cache")
# we use ensure_path for the directories to make sure these are valid paths
# this will raise an exception if the path is invalid
self._working_directory = ensure_path(
io.input.option("directory") or Path.cwd(), is_directory=True
)
self._project_directory = io.input.option("project")
if self._project_directory is not None:
self._project_directory = Path(self._project_directory)
self._project_directory = ensure_path(
self._project_directory
if self._project_directory.is_absolute()
else self._working_directory.joinpath(self._project_directory).resolve(
strict=False
),
is_directory=True,
)
def _sort_global_options(self, io: IO) -> None:
"""
Sorts global options of the provided IO instance according to the
definition of the available options, reordering and parsing arguments
to ensure consistency in input handling.
The function interprets the options and their corresponding values
using an argument parser, constructs a sorted list of tokens, and
recreates the input with the rearranged sequence while maintaining
compatibility with the initially provided input stream.
If using in conjunction with `_configure_run_command`, it is recommended that
it be called first in order to correctly handling cases like
`poetry run -V python -V`.
:param io: The IO instance whose input and options are being processed
and reordered.
:return: Nothing.
"""
original_input = cast("ArgvInput", io.input)
tokens: list[str] = original_input._tokens
parser = argparse.ArgumentParser(add_help=False)
for option in self.definition.options:
parser.add_argument(
f"--{option.name}",
*([f"-{option.shortcut}"] if option.shortcut else []),
action="store_true" if option.is_flag() else "store",
)
args, remaining_args = parser.parse_known_args(tokens)
tokens = []
for option in self.definition.options:
key = option.name.replace("-", "_")
value = getattr(args, key, None)
if value is not None:
if value: # is truthy
tokens.append(f"--{option.name}")
if option.accepts_value():
tokens.append(str(value))
sorted_input = ArgvInput([self._name or "", *tokens, *remaining_args])
# this is required to ensure stdin is transferred
sorted_input.set_stream(original_input.stream)
# this is required as cleo internally checks for `io.input._interactive`
# when configuring io, and cleo's test applications overrides this attribute
# explicitly causing test setups to fail
sorted_input.interactive(io.input.is_interactive())
with suppress(CleoError):
sorted_input.bind(self.definition)
io.set_input(sorted_input)
def _configure_run_command(self, io: IO) -> None:
"""
Configures the input for the "run" command to properly handle cases where the user
executes commands such as "poetry run -- ". This involves reorganizing
input tokens to ensure correct parsing and execution of the run command.
"""
with suppress(CleoError):
io.input.bind(self.definition)
command_name = io.input.first_argument
if command_name == "run":
original_input = cast("ArgvInput", io.input)
tokens: list[str] = original_input._tokens
if "--" in tokens:
# this means the user has done the right thing and used "poetry run -- echo hello"
# in this case there is not much we need to do, we can skip the rest
return
# find the correct command index, in some cases this might not be first occurrence
# eg: poetry -C run run echo
command_index = tokens.index(command_name)
while command_index < (len(tokens) - 1):
try:
# try parsing the tokens so far
_ = ArgvInput(
[self._name or "", *tokens[: command_index + 1]],
definition=self.definition,
)
break
except CleoError:
# parsing failed, try finding the next "run" token
try:
command_index += (
tokens[command_index + 1 :].index(command_name) + 1
)
except ValueError:
command_index = len(tokens)
else:
# looks like we reached the end of the road, let cleo deal with it
return
# fetch tokens after the "run" command
tokens_without_command = tokens[command_index + 1 :]
# we create a new input for parsing the subcommand pretending
# it is poetry command
without_command = ArgvInput(
[self._name or "", *tokens_without_command], None
)
with suppress(CleoError):
# we want to bind the definition here so that cleo knows what should be
# parsed, and how
without_command.bind(self.definition)
# the first argument here is the subcommand
subcommand = without_command.first_argument
subcommand_index = (
(tokens_without_command.index(subcommand) if subcommand else 0)
+ command_index
+ 1
)
# recreate the original input reordering in the following order
# - all tokens before "run" command
# - all tokens after "run" command but before the subcommand
# - the "run" command token
# - the "--" token to normalise the form
# - all remaining tokens starting with the subcommand
run_input = ArgvInput(
[
self._name or "",
*tokens[:command_index],
*tokens[command_index + 1 : subcommand_index],
command_name,
"--",
*tokens[subcommand_index:],
]
)
run_input.set_stream(original_input.stream)
with suppress(CleoError):
run_input.bind(self.definition)
# reset the input to our constructed form
io.set_input(run_input)
def _configure_io(self, io: IO) -> None:
self._configure_run_command(io)
self._sort_global_options(io)
super()._configure_io(io)
def register_command_loggers(
self, event: Event, event_name: str, _: EventDispatcher
) -> None:
from poetry.console.logging.filters import POETRY_FILTER
from poetry.console.logging.io_formatter import IOFormatter
from poetry.console.logging.io_handler import IOHandler
assert isinstance(event, ConsoleCommandEvent)
command = event.command
if not isinstance(command, Command):
return
io = event.io
loggers = [
"poetry.packages.locker",
"poetry.packages.package",
"poetry.utils.password_manager",
]
loggers += command.loggers
handler = IOHandler(io)
handler.setFormatter(IOFormatter())
level = logging.WARNING
if io.is_debug():
level = logging.DEBUG
elif io.is_very_verbose() or io.is_verbose():
level = logging.INFO
logging.basicConfig(level=level, handlers=[handler])
# only log third-party packages when very verbose
if not io.is_very_verbose():
handler.addFilter(POETRY_FILTER)
for name in loggers:
logger = logging.getLogger(name)
_level = level
# The builders loggers are special and we can actually
# start at the INFO level.
if (
logger.name.startswith("poetry.core.masonry.builders")
and _level > logging.INFO
):
_level = logging.INFO
logger.setLevel(_level)
def configure_env(self, event: Event, event_name: str, _: EventDispatcher) -> None:
from poetry.console.commands.env_command import EnvCommand
from poetry.console.commands.self.self_command import SelfCommand
assert isinstance(event, ConsoleCommandEvent)
command = event.command
if not isinstance(command, EnvCommand) or isinstance(command, SelfCommand):
return
if command._env is not None:
return
from poetry.utils.env import EnvManager
io = event.io
poetry = command.poetry
env_manager = EnvManager(poetry, io=io)
env = env_manager.create_venv()
if env.is_venv() and io.is_verbose():
io.write_error_line(f"Using virtualenv: {env.path}>")
command.set_env(env)
@classmethod
def configure_installer_for_event(
cls, event: Event, event_name: str, _: EventDispatcher
) -> None:
from poetry.console.commands.installer_command import InstallerCommand
assert isinstance(event, ConsoleCommandEvent)
command = event.command
if not isinstance(command, InstallerCommand):
return
# If the command already has an installer
# we skip this step
if command._installer is not None:
return
cls.configure_installer_for_command(command, event.io)
@staticmethod
def configure_installer_for_command(command: InstallerCommand, io: IO) -> None:
from poetry.installation.installer import Installer
poetry = command.poetry
installer = Installer(
io,
command.env,
poetry.package,
poetry.locker,
poetry.pool,
poetry.config,
disable_cache=poetry.disable_cache,
build_constraints=poetry.build_constraints,
)
command.set_installer(installer)
def _load_plugins(self, io: IO) -> None:
if self._plugins_loaded:
return
self._disable_plugins = io.input.has_parameter_option("--no-plugins")
if not self._disable_plugins:
from poetry.plugins.application_plugin import ApplicationPlugin
from poetry.plugins.plugin_manager import PluginManager
PluginManager.add_project_plugin_path(self.project_directory)
manager = PluginManager(ApplicationPlugin.group)
manager.load_plugins()
manager.activate(self)
self._plugins_loaded = True
def main() -> int:
exit_code: int = Application().run()
return exit_code
if __name__ == "__main__":
main()
================================================
FILE: src/poetry/console/command_loader.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from cleo.exceptions import CleoLogicError
from cleo.loaders.factory_command_loader import FactoryCommandLoader
if TYPE_CHECKING:
from collections.abc import Callable
from cleo.commands.command import Command
class CommandLoader(FactoryCommandLoader):
def register_factory(
self, command_name: str, factory: Callable[[], Command]
) -> None:
if command_name in self._factories:
raise CleoLogicError(f'The command "{command_name}" already exists.')
self._factories[command_name] = factory
================================================
FILE: src/poetry/console/commands/__init__.py
================================================
================================================
FILE: src/poetry/console/commands/about.py
================================================
from __future__ import annotations
from poetry.console.commands.command import Command
class AboutCommand(Command):
name = "about"
description = "Shows information about Poetry."
def handle(self) -> int:
from importlib import metadata
self.line(
f"""\
Poetry - Package Management for Python
Version: {metadata.version("poetry")}
Poetry-Core Version: {metadata.version("poetry-core")}
Poetry is a dependency manager tracking local dependencies of your projects\
and libraries.
See https://github.com/python-poetry/poetry> for more information.\
"""
)
return 0
================================================
FILE: src/poetry/console/commands/add.py
================================================
from __future__ import annotations
import contextlib
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from typing import Literal
from cleo.helpers import argument
from cleo.helpers import option
from packaging.utils import canonicalize_name
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.dependency_group import MAIN_GROUP
from tomlkit.toml_document import TOMLDocument
from poetry.console.commands.init import InitCommand
from poetry.console.commands.installer_command import InstallerCommand
if TYPE_CHECKING:
from collections.abc import Collection
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
from packaging.utils import NormalizedName
class AddCommand(InstallerCommand, InitCommand):
name = "add"
description = "Adds a new dependency to pyproject.toml> and installs it."
arguments: ClassVar[list[Argument]] = [
argument("name", "The packages to add.", multiple=True)
]
options: ClassVar[list[Option]] = [
option(
"group",
"-G",
"The group to add the dependency to.",
flag=False,
default=MAIN_GROUP,
),
option(
"dev",
"D",
"Add as a development dependency. (shortcut for '-G dev')",
),
option("editable", "e", "Add vcs/path dependencies as editable."),
option(
"extras",
"E",
"Extras to activate for the dependency.",
flag=False,
multiple=True,
),
option(
"optional",
None,
"Add as an optional dependency to an extra.",
flag=False,
),
option(
"python",
None,
"Python version for which the dependency must be installed.",
flag=False,
),
option(
"platform",
None,
"Platforms for which the dependency must be installed.",
flag=False,
),
option(
"markers",
None,
"Environment markers which describe when the dependency should be installed.",
flag=False,
),
option(
"source",
None,
"Name of the source to use to install the package.",
flag=False,
),
option("allow-prereleases", None, "Accept prereleases."),
option(
"dry-run",
None,
"Output the operations but do not execute anything (implicitly enables"
" --verbose).",
),
option("lock", None, "Do not perform operations (only update the lockfile)."),
]
examples = """\
If you do not specify a version constraint, poetry will choose a suitable one based on\
the available package versions.
You can specify a package in the following forms:
- A single name (requests)
- A name and a constraint (requests@^2.23.0)
- A git url (git+https://github.com/python-poetry/poetry.git)
- A git url with a revision\
(git+https://github.com/python-poetry/poetry.git#develop)
- A subdirectory of a git repository\
(git+https://github.com/python-poetry/poetry.git#subdirectory=tests/fixtures/sample_project)
- A git SSH url (git+ssh://git@github.com/python-poetry/poetry.git)
- A git SSH url with a revision\
(git+ssh://git@github.com/python-poetry/poetry.git#develop)
- A file path (../my-package/my-package.whl)
- A directory (../my-package/)
- A url (https://example.com/packages/my-package-0.1.0.tar.gz)
"""
help = f"""\
The add command adds required packages to your pyproject.toml> and installs\
them.
{examples}
"""
loggers: ClassVar[list[str]] = [
"poetry.repositories.pypi_repository",
"poetry.inspection.info",
]
def handle(self) -> int:
from poetry.core.constraints.version import parse_constraint
from tomlkit import array
from tomlkit import inline_table
from tomlkit import nl
from tomlkit import table
from poetry.factory import Factory
packages = self.argument("name")
if self.option("dev"):
group = "dev"
else:
group = self.option("group", self.default_group or MAIN_GROUP)
if self.option("extras") and len(packages) > 1:
raise ValueError(
"You can only specify one package when using the --extras option"
)
optional = self.option("optional")
if optional and group != MAIN_GROUP:
raise ValueError("You can only add optional dependencies to the main group")
# tomlkit types are awkward to work with, treat content as a mostly untyped
# dictionary.
content: dict[str, Any] = self.poetry.file.read()
project_content = content.get("project", table())
poetry_content = content.get("tool", {}).get("poetry", table())
groups_content = content.get("dependency-groups", {})
project_name = (
canonicalize_name(name)
if (name := project_content.get("name", poetry_content.get("name")))
else None
)
use_project_section = False
use_groups_section = False
project_dependency_names: list[NormalizedName | Literal[""]] = []
# Run-Time Deps incl. extras
if group == MAIN_GROUP:
if (
"dependencies" in project_content
or "optional-dependencies" in project_content
):
use_project_section = True
if optional:
project_section = project_content.get(
"optional-dependencies", {}
).get(optional, array())
else:
project_section = project_content.get("dependencies", array())
project_dependency_names = [
Dependency.create_from_pep_508(dep).name for dep in project_section
]
else:
project_section = array()
poetry_section = poetry_content.get("dependencies", table())
# Dependency Groups
else:
if groups_content or "group" not in poetry_content:
use_groups_section = True
if not groups_content:
groups_content = table(is_super_table=True)
if group not in groups_content:
groups_content[group] = array("[\n]")
project_dependency_names = [
Dependency.create_from_pep_508(dep).name
if isinstance(dep, str)
# We have to add an entry for "include-group" items because
# later the index of the dependency is used to replace it.
# We just choose a name that cannot be a package's name.
else ""
for dep in groups_content[group]
]
poetry_section = (
poetry_content.get("group", {})
.get(group, {})
.get("dependencies", table())
)
project_section = []
existing_packages = self.get_existing_packages_from_input(
packages, poetry_section, project_dependency_names
)
if existing_packages:
self.notify_about_existing_packages(existing_packages)
packages = [name for name in packages if name not in existing_packages]
if not packages:
self.line("Nothing to add.")
return 0
if optional and not use_project_section:
self.line_error(
"Optional dependencies will not be added to extras"
" in legacy mode. Consider converting your project to use the [project]"
" section."
)
requirements = self._determine_requirements(
packages,
allow_prereleases=self.option("allow-prereleases") or None,
source=self.option("source"),
)
for _constraint in requirements:
version = _constraint.get("version")
if version is not None:
# Validate version constraint
assert isinstance(version, str)
parse_constraint(version)
constraint: dict[str, Any] = inline_table()
for key, value in _constraint.items():
if key == "name":
continue
constraint[key] = value
if optional:
constraint["optional"] = True
if self.option("allow-prereleases"):
constraint["allow-prereleases"] = True
if self.option("extras"):
extras = []
for extra in self.option("extras"):
extras += extra.split()
constraint["extras"] = extras
if self.option("editable"):
if "git" in _constraint or "path" in _constraint:
constraint["develop"] = True
else:
self.line_error(
"\n"
"Failed to add packages. "
"Only vcs/path dependencies support editable installs. "
f"{_constraint['name']} is neither."
)
self.line_error("\nNo changes were applied.")
return 1
if python := self.option("python"):
constraint["python"] = python
if platform := self.option("platform"):
constraint["platform"] = platform
if markers := self.option("markers"):
constraint["markers"] = markers
if source := self.option("source"):
constraint["source"] = source
if len(constraint) == 1 and "version" in constraint:
constraint = constraint["version"]
constraint_name = _constraint["name"]
assert isinstance(constraint_name, str)
canonical_constraint_name = canonicalize_name(constraint_name)
if canonical_constraint_name == project_name:
self.line_error(
f"Cannot add dependency on {constraint_name} to"
" project with the same name."
)
self.line_error("\nNo changes were applied.")
return 1
with contextlib.suppress(ValueError):
self.poetry.package.dependency_group(group).remove_dependency(
constraint_name
)
dependency = Factory.create_dependency(
constraint_name,
constraint,
groups=[group],
root_dir=self.poetry.file.path.parent,
)
self.poetry.package.add_dependency(dependency)
if use_project_section or use_groups_section:
pep_section = (
project_section if use_project_section else groups_content[group]
)
try:
index = project_dependency_names.index(canonical_constraint_name)
except ValueError:
pep_section.append(dependency.to_pep_508())
else:
pep_section[index] = dependency.to_pep_508()
# create a second constraint for tool.poetry.dependencies with keys
# that cannot be stored in the project section
poetry_constraint: dict[str, Any] = inline_table()
if not isinstance(constraint, str):
for key in ["allow-prereleases", "develop", "source"]:
if value := constraint.get(key):
poetry_constraint[key] = value
if poetry_constraint:
# add marker related keys to avoid ambiguity
for key in ["python", "platform"]:
if value := constraint.get(key):
poetry_constraint[key] = value
else:
poetry_constraint = constraint
if poetry_constraint:
for key in poetry_section:
if canonicalize_name(key) == canonical_constraint_name:
poetry_section[key] = poetry_constraint
break
else:
poetry_section[constraint_name] = poetry_constraint
if optional:
extra_name = canonicalize_name(optional)
# _in_extras must be set after converting the dependency to PEP 508
# and adding it to the project section to avoid a redundant extra marker
dependency._in_extras = [extra_name]
self._add_dependency_to_extras(dependency, extra_name)
# Refresh the locker
if project_section:
assert group == MAIN_GROUP
if optional:
if "optional-dependencies" not in project_content:
project_content["optional-dependencies"] = table()
if optional not in project_content["optional-dependencies"]:
project_content["optional-dependencies"][optional] = project_section
elif "dependencies" not in project_content:
project_content["dependencies"] = project_section
if poetry_section:
if "tool" not in content:
content["tool"] = table()
if "poetry" not in content["tool"]:
content["tool"]["poetry"] = poetry_content
if group == MAIN_GROUP:
if "dependencies" not in poetry_content:
poetry_content["dependencies"] = poetry_section
else:
if "group" not in poetry_content:
poetry_content["group"] = table(is_super_table=True)
groups = poetry_content["group"]
if group not in groups:
groups[group] = table()
groups.add(nl())
if "dependencies" not in groups[group]:
groups[group]["dependencies"] = poetry_section
if groups_content and group != MAIN_GROUP:
if "dependency-groups" not in content:
content["dependency-groups"] = table()
content["dependency-groups"][group] = groups_content[group]
self.poetry.locker.set_pyproject_data(content)
self.installer.set_locker(self.poetry.locker)
# Cosmetic new line
self.line("")
self.installer.set_package(self.poetry.package)
self.installer.dry_run(self.option("dry-run"))
self.installer.verbose(self.io.is_verbose())
self.installer.update(True)
self.installer.execute_operations(not self.option("lock"))
self.installer.whitelist([r["name"] for r in requirements])
status = self.installer.run()
if status == 0 and not self.option("dry-run"):
assert isinstance(content, TOMLDocument)
self.poetry.file.write(content)
return status
def get_existing_packages_from_input(
self,
packages: list[str],
section: dict[str, Any],
project_dependencies: Collection[NormalizedName | Literal[""]],
) -> list[str]:
existing_packages = []
for name in packages:
normalized_name = canonicalize_name(name)
if normalized_name in project_dependencies:
existing_packages.append(name)
continue
for key in section:
if normalized_name == canonicalize_name(key):
existing_packages.append(name)
return existing_packages
@property
def _hint_update_packages(self) -> str:
return (
"\nIf you want to update it to the latest compatible version, you can use"
" `poetry update package`.\nIf you prefer to upgrade it to the latest"
" available version, you can use `poetry add package@latest`.\n"
)
def notify_about_existing_packages(self, existing_packages: list[str]) -> None:
self.line(
"The following packages are already present in the pyproject.toml and will"
" be skipped:\n"
)
for name in existing_packages:
self.line(f" - {name}")
self.line(self._hint_update_packages)
def _add_dependency_to_extras(
self, dependency: Dependency, extra_name: NormalizedName
) -> None:
extras = dict(self.poetry.package.extras)
extra_deps = []
replaced = False
for dep in extras.get(extra_name, ()):
if dep.name == dependency.name:
extra_deps.append(dependency)
replaced = True
else:
extra_deps.append(dep)
if not replaced:
extra_deps.append(dependency)
extras[extra_name] = extra_deps
self.poetry.package.extras = extras
================================================
FILE: src/poetry/console/commands/build.py
================================================
from __future__ import annotations
import dataclasses
from importlib import metadata
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from typing import Literal
from cleo.helpers import option
from poetry.core.constraints.version import Version
from poetry.console.commands.env_command import EnvCommand
from poetry.masonry.builders import BUILD_FORMATS
from poetry.utils.helpers import remove_directory
from poetry.utils.isolated_build import isolated_builder
if TYPE_CHECKING:
from collections.abc import Callable
from cleo.io.inputs.option import Option
from cleo.io.io import IO
from poetry.poetry import Poetry
from poetry.utils.env import Env
DistributionType = Literal["sdist", "wheel"]
@dataclasses.dataclass(frozen=True)
class BuildOptions:
clean: bool
formats: list[DistributionType]
output: str
config_settings: dict[str, Any] = dataclasses.field(default_factory=dict)
def __post_init__(self) -> None:
for fmt in self.formats:
if fmt not in BUILD_FORMATS:
raise ValueError(f"Invalid format: {fmt}")
class BuildHandler:
def __init__(self, poetry: Poetry, env: Env, io: IO) -> None:
self.poetry = poetry
self.env = env
self.io = io
def _build(
self,
fmt: DistributionType,
executable: Path,
target_dir: Path,
config_settings: dict[str, Any],
) -> None:
builder = BUILD_FORMATS[fmt]
builder(
self.poetry,
executable=executable,
config_settings=config_settings,
).build(target_dir)
def _isolated_build(
self,
fmt: DistributionType,
executable: Path,
target_dir: Path,
config_settings: dict[str, Any],
) -> None:
with isolated_builder(
source=self.poetry.file.path.parent,
distribution=fmt,
python_executable=executable,
) as builder:
builder.build(fmt, target_dir, config_settings=config_settings)
def _requires_isolated_build(self) -> bool:
"""
Determines if an isolated build is required.
An isolated build is required if:
- The package has a build script.
- There are multiple build system dependencies.
- The build dependency is not `poetry-core`.
- The installed `poetry-core` version does not satisfy the build dependency constraints.
- The build dependency has a source type (e.g. is a VcsDependency).
:returns: True if an isolated build is required, False otherwise.
"""
if not self._has_build_backend_defined():
self.io.write_error_line(
"WARNING>: No build backend defined. Please define one in the pyproject.toml>.\n"
"Falling back to using the built-in `poetry-core` version.\n"
"In a future release Poetry will fallback to `setuptools` as defined by PEP 517.\n"
"More details can be found at https://python-poetry.org/docs/libraries/#packaging>"
)
return False
if (
self.poetry.package.build_script
or len(self.poetry.build_system_dependencies) != 1
):
return True
build_dependency = self.poetry.build_system_dependencies[0]
if build_dependency.name != "poetry-core":
return True
poetry_core_version = Version.parse(metadata.version("poetry-core"))
return bool(
not build_dependency.constraint.allows(poetry_core_version)
or build_dependency.source_type
)
def _get_builder(self) -> Callable[..., None]:
if self._requires_isolated_build():
return self._isolated_build
return self._build
def _has_build_backend_defined(self) -> bool:
return "build-backend" in self.poetry.pyproject.data.get("build-system", {})
def build(self, options: BuildOptions) -> int:
if not self.poetry.is_package_mode:
self.io.write_error_line(
"Building a package is not possible in non-package mode."
)
return 1
dist_dir = Path(options.output)
package = self.poetry.package
self.io.write_line(
f"Building {package.pretty_name} ({package.version})"
)
if not dist_dir.is_absolute():
dist_dir = self.poetry.pyproject_path.parent / dist_dir
if options.clean:
remove_directory(path=dist_dir, force=True)
build = self._get_builder()
for fmt in options.formats:
self.io.write_line(f"Building {fmt}")
build(
fmt,
executable=self.env.python,
target_dir=dist_dir,
config_settings=options.config_settings,
)
return 0
class BuildCommand(EnvCommand):
name = "build"
description = "Builds a package, as a tarball and a wheel by default."
options: ClassVar[list[Option]] = [
option("format", "f", "Limit the format to either sdist or wheel.", flag=False),
option(
"clean",
description="Clean output directory before building.",
flag=True,
),
option(
"local-version",
"l",
"Add or replace a local version label to the build. (Deprecated)",
flag=False,
),
option(
"output",
"o",
"Set output directory for build artifacts. Default is `dist`.",
default="dist",
flag=False,
),
option(
"config-settings",
"c",
description="Provide config settings that should be passed to backend in = format.",
flag=False,
multiple=True,
),
]
loggers: ClassVar[list[str]] = [
"poetry.core.masonry.builders.builder",
"poetry.core.masonry.builders.sdist",
"poetry.core.masonry.builders.wheel",
]
@staticmethod
def _prepare_config_settings(
local_version: str | None, config_settings: list[str] | None, io: IO
) -> dict[str, str]:
config_settings = config_settings or []
result = {}
if local_version:
io.write_error_line(
f"`--local-version>` is deprecated."
f" Use `--config-settings local-version={local_version}>`"
f" instead."
)
result["local-version"] = local_version
for config_setting in config_settings:
if "=" not in config_setting:
raise ValueError(
f"Invalid config setting format: {config_setting}. "
"Config settings must be in the format 'key=value'"
)
key, _, value = config_setting.partition("=")
result[key] = value
return result
@staticmethod
def _prepare_formats(fmt: str | None) -> list[str]:
fmt = fmt or "all"
return ["sdist", "wheel"] if fmt == "all" else [fmt]
def handle(self) -> int:
build_handler = BuildHandler(
poetry=self.poetry,
env=self.env,
io=self.io,
)
build_options = BuildOptions(
clean=self.option("clean"),
formats=self._prepare_formats(self.option("format")), # type: ignore[arg-type]
output=self.option("output"),
config_settings=self._prepare_config_settings(
local_version=self.option("local-version"),
config_settings=self.option("config-settings"),
io=self.io,
),
)
return build_handler.build(options=build_options)
================================================
FILE: src/poetry/console/commands/cache/__init__.py
================================================
================================================
FILE: src/poetry/console/commands/cache/clear.py
================================================
from __future__ import annotations
import os
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from cleo.helpers import option
from packaging.utils import canonicalize_name
from poetry.config.config import Config
from poetry.console.commands.command import Command
from poetry.utils.cache import FileCache
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
class CacheClearCommand(Command):
name = "cache clear"
description = "Clear Poetry's caches."
arguments: ClassVar[list[Argument]] = [
argument("cache", description="The name of the cache to clear.", optional=True)
]
options: ClassVar[list[Option]] = [
option("all", description="Clear all entries in the cache.")
]
def handle(self) -> int:
cache = self.argument("cache")
if cache:
parts = cache.split(":")
root = parts[0]
else:
parts = []
root = ""
config = Config.create()
cache_dir = config.repository_cache_directory / root
try:
cache_dir.relative_to(config.repository_cache_directory)
except ValueError:
raise ValueError(f"{root} is not a valid repository cache")
cache = FileCache(cache_dir)
if len(parts) < 2:
if not self.option("all"):
raise RuntimeError(
"Add the --all option if you want to clear all cache entries"
)
if not cache_dir.exists():
self.line(
f"No cache entries for {root}" if root else "No cache entries"
)
return 0
# Calculate number of entries
entries_count = sum(
len(files) for _path, _dirs, files in os.walk(str(cache_dir))
)
delete = self.confirm(f"Delete {entries_count} entries?>", True)
if not delete:
return 0
cache.flush()
elif len(parts) == 2:
raise RuntimeError(
"Only specifying the package name is not yet supported. "
"Add a specific version to clear"
)
elif len(parts) == 3:
package = canonicalize_name(parts[1])
version = parts[2]
if not cache.has(f"{package}:{version}"):
self.line(f"No cache entries for {package}:{version}")
return 0
delete = self.confirm(f"Delete cache entry {package}:{version}", True)
if not delete:
return 0
cache.forget(f"{package}:{version}")
else:
raise ValueError("Invalid cache key")
return 0
================================================
FILE: src/poetry/console/commands/cache/list.py
================================================
from __future__ import annotations
from poetry.config.config import Config
from poetry.console.commands.command import Command
class CacheListCommand(Command):
name = "cache list"
description = "List Poetry's caches."
def handle(self) -> int:
config = Config.create()
if config.repository_cache_directory.exists():
caches = sorted(config.repository_cache_directory.iterdir())
if caches:
for cache in caches:
self.line(f"{cache.name}>")
return 0
self.line_error("No caches found>")
return 0
================================================
FILE: src/poetry/console/commands/check.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from cleo.helpers import option
from poetry.console.commands.command import Command
if TYPE_CHECKING:
from pathlib import Path
from cleo.io.inputs.option import Option
class CheckCommand(Command):
name = "check"
description = (
"Validates the content of the pyproject.toml> file and its"
" consistency with the poetry.lock file."
)
options: ClassVar[list[Option]] = [
option(
"lock",
None,
"Checks that poetry.lock> exists for the current"
" version of pyproject.toml>.",
),
option(
"strict",
None,
"Fail if check reports warnings.",
),
]
def _validate_classifiers(
self, project_classifiers: set[str]
) -> tuple[list[str], list[str]]:
"""Identify unrecognized and deprecated trove classifiers.
A fully-qualified classifier is a string delimited by `` :: `` separators. To
make the error message more readable we need to have visual clues to
materialize the start and end of a classifier string. That way the user can
easily copy and paste it from the messages while reducing mistakes because of
extra spaces.
We use ``!r`` (``repr()``) for classifiers and list of classifiers for
consistency. That way all strings will be rendered with the same kind of quotes
(i.e. simple tick: ``'``).
"""
from trove_classifiers import classifiers
from trove_classifiers import deprecated_classifiers
errors = []
warnings = []
unrecognized = sorted(
project_classifiers - set(classifiers) - set(deprecated_classifiers)
)
# Allow "Private ::" classifiers as recommended on PyPI and the packaging guide
# to allow users to avoid accidentally publishing private packages to PyPI.
# https://pypi.org/classifiers/
unrecognized = [u for u in unrecognized if not u.startswith("Private ::")]
if unrecognized:
errors.append(f"Unrecognized classifiers: {unrecognized!r}.")
deprecated = sorted(
project_classifiers.intersection(set(deprecated_classifiers))
)
if deprecated:
for old_classifier in deprecated:
new_classifiers = deprecated_classifiers[old_classifier]
if new_classifiers:
message = (
f"Deprecated classifier {old_classifier!r}. "
f"Must be replaced by {new_classifiers!r}."
)
else:
message = (
f"Deprecated classifier {old_classifier!r}. Must be removed."
)
warnings.append(message)
return errors, warnings
def _validate_readme(self, readme: str | list[str], poetry_file: Path) -> list[str]:
"""Check existence of referenced readme files"""
readmes = [readme] if isinstance(readme, str) else readme
errors = []
for name in readmes:
if not name:
errors.append("Declared README file is an empty string.")
elif not (poetry_file.parent / name).exists():
errors.append(f"Declared README file does not exist: {name}")
return errors
def _validate_dependencies_source(self, config: dict[str, Any]) -> list[str]:
"""Check dependencies's source are valid"""
sources = {repository.name for repository in self.poetry.pool.all_repositories}
dependency_declarations: list[
dict[str, str | dict[str, str] | list[dict[str, str]]]
] = []
# scan dependencies and group dependencies settings in pyproject.toml
if "dependencies" in config:
dependency_declarations.append(config["dependencies"])
for group in config.get("group", {}).values():
if "dependencies" in group:
dependency_declarations.append(group["dependencies"])
all_referenced_sources: set[str] = set()
for dependency_declaration in dependency_declarations:
for declaration in dependency_declaration.values():
if isinstance(declaration, list):
for item in declaration:
if "source" in item:
all_referenced_sources.add(item["source"])
elif isinstance(declaration, dict) and "source" in declaration:
all_referenced_sources.add(declaration["source"])
return [
f'Invalid source "{source}" referenced in dependencies.'
for source in sorted(all_referenced_sources - sources)
]
def handle(self) -> int:
from poetry.core.pyproject.toml import PyProjectTOML
from poetry.factory import Factory
# Load poetry config and display errors, if any
poetry_file = self.poetry.file.path
toml_data = PyProjectTOML(poetry_file).data
check_result = Factory.validate(toml_data, strict=True)
project = toml_data.get("project", {})
poetry_config = toml_data["tool"]["poetry"]
# Validate trove classifiers
project_classifiers = set(
project.get("classifiers") or poetry_config.get("classifiers", [])
)
errors, warnings = self._validate_classifiers(project_classifiers)
check_result["errors"].extend(errors)
check_result["warnings"].extend(warnings)
readme_errors = []
# Check poetry readme
if "readme" in poetry_config:
readme_errors += self._validate_readme(poetry_config["readme"], poetry_file)
project_readme = project.get("readme")
if project_readme is not None:
if isinstance(project_readme, dict):
readme_path = project_readme.get("file")
if readme_path is not None:
readme_errors += self._validate_readme(readme_path, poetry_file)
elif isinstance(project_readme, str):
readme_errors += self._validate_readme(project_readme, poetry_file)
else:
# should not happen due to prior schema validation, but just in case
readme_errors.append(
f"Invalid format for [project.readme]: {project_readme!r}"
)
check_result["errors"].extend(readme_errors)
# Validate dependencies' sources
check_result["errors"] += self._validate_dependencies_source(poetry_config)
# Verify that lock file is consistent
if self.option("lock") and not self.poetry.locker.is_locked():
check_result["errors"] += ["poetry.lock was not found."]
if self.poetry.locker.is_locked() and not self.poetry.locker.is_fresh():
check_result["errors"] += [
"pyproject.toml changed significantly since poetry.lock was last generated. "
"Run `poetry lock` to fix the lock file."
]
return_code = 0
if check_result["errors"] or (
check_result["warnings"] and self.option("strict")
):
return_code = 1
if not check_result["errors"] and not check_result["warnings"]:
self.info("All set!")
for error in check_result["errors"]:
self.line_error(f"Error: {error}")
for error in check_result["warnings"]:
self.line_error(f"Warning: {error}")
return return_code
================================================
FILE: src/poetry/console/commands/command.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from cleo.commands.command import Command as BaseCommand
from cleo.exceptions import CleoValueError
if TYPE_CHECKING:
from poetry.console.application import Application
from poetry.poetry import Poetry
class Command(BaseCommand):
loggers: ClassVar[list[str]] = []
_poetry: Poetry | None = None
@property
def poetry(self) -> Poetry:
if self._poetry is None:
return self.get_application().poetry
return self._poetry
def set_poetry(self, poetry: Poetry) -> None:
"""Explicitly set the current Poetry.
Useful for Plugins that extends the features of a Poetry CLI Command.
"""
self._poetry = poetry
def get_application(self) -> Application:
from poetry.console.application import Application
application = self.application
assert isinstance(application, Application)
return application
def reset_poetry(self) -> None:
self.get_application().reset_poetry()
def option(self, name: str, default: Any = None) -> Any:
try:
return super().option(name)
except CleoValueError:
return default
================================================
FILE: src/poetry/console/commands/config.py
================================================
from __future__ import annotations
import json
import re
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from typing import cast
from cleo.helpers import argument
from cleo.helpers import option
from installer.utils import canonicalize_name
from poetry.config.config import PackageFilterPolicy
from poetry.config.config import boolean_normalizer
from poetry.config.config import boolean_validator
from poetry.config.config import build_config_setting_normalizer
from poetry.config.config import build_config_setting_validator
from poetry.config.config import int_normalizer
from poetry.config.config_source import UNSET
from poetry.config.config_source import ConfigSourceMigration
from poetry.config.config_source import PropertyNotFoundError
from poetry.console.commands.command import Command
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
from poetry.config.config_source import ConfigSource
CONFIG_MIGRATIONS = [
ConfigSourceMigration(
old_key="experimental.system-git-client", new_key="system-git-client"
),
ConfigSourceMigration(
old_key="virtualenvs.prefer-active-python",
new_key="virtualenvs.use-poetry-python",
value_migration={True: UNSET, False: True},
),
]
class ConfigCommand(Command):
name = "config"
description = "Manages configuration settings."
arguments: ClassVar[list[Argument]] = [
argument("key", "Setting key.", optional=True),
argument("value", "Setting value.", optional=True, multiple=True),
]
options: ClassVar[list[Option]] = [
option("list", None, "List configuration settings."),
option("unset", None, "Unset configuration setting."),
option("local", None, "Set/Get from the project's local configuration."),
option("migrate", None, "Migrate outdated configuration settings."),
]
help = """\
This command allows you to edit the poetry config settings and repositories.
To add a repository:
poetry config repositories.foo https://bar.com/simple/
To remove a repository (repo is a short alias for repositories):
poetry config --unset repo.foo"""
LIST_PROHIBITED_SETTINGS: ClassVar[set[str]] = {"http-basic", "pypi-token"}
@property
def unique_config_values(self) -> dict[str, tuple[Any, Any]]:
unique_config_values = {
"cache-dir": (str, lambda val: str(Path(val))),
"data-dir": (str, lambda val: str(Path(val))),
"virtualenvs.create": (boolean_validator, boolean_normalizer),
"virtualenvs.in-project": (boolean_validator, boolean_normalizer),
"virtualenvs.options.always-copy": (boolean_validator, boolean_normalizer),
"virtualenvs.options.system-site-packages": (
boolean_validator,
boolean_normalizer,
),
"virtualenvs.options.no-pip": (boolean_validator, boolean_normalizer),
"virtualenvs.path": (str, lambda val: str(Path(val))),
"virtualenvs.use-poetry-python": (boolean_validator, boolean_normalizer),
"virtualenvs.prompt": (str, str),
"system-git-client": (boolean_validator, boolean_normalizer),
"requests.max-retries": (lambda val: int(val) >= 0, int_normalizer),
"installer.re-resolve": (boolean_validator, boolean_normalizer),
"installer.parallel": (boolean_validator, boolean_normalizer),
"installer.max-workers": (lambda val: int(val) > 0, int_normalizer),
"installer.no-binary": (
PackageFilterPolicy.validator,
PackageFilterPolicy.normalize,
),
"installer.only-binary": (
PackageFilterPolicy.validator,
PackageFilterPolicy.normalize,
),
"solver.lazy-wheel": (boolean_validator, boolean_normalizer),
"keyring.enabled": (boolean_validator, boolean_normalizer),
"python.installation-dir": (str, lambda val: str(Path(val))),
}
return unique_config_values
def handle(self) -> int:
from pathlib import Path
from poetry.core.pyproject.exceptions import PyProjectError
from poetry.config.config import Config
from poetry.config.file_config_source import FileConfigSource
from poetry.locations import CONFIG_DIR
from poetry.toml.file import TOMLFile
if self.option("migrate"):
self._migrate()
config = Config.create()
config_file = TOMLFile(CONFIG_DIR / "config.toml")
try:
local_config_file = TOMLFile(self.poetry.file.path.parent / "poetry.toml")
if local_config_file.exists():
config.merge(local_config_file.read())
except (RuntimeError, PyProjectError):
local_config_file = TOMLFile(Path.cwd() / "poetry.toml")
if self.option("local"):
config.set_config_source(FileConfigSource(local_config_file))
if not config_file.exists():
config_file.path.parent.mkdir(parents=True, exist_ok=True)
config_file.path.touch(mode=0o0600)
if self.option("list"):
self._list_configuration(config.all(), config.raw())
return 0
setting_key = self.argument("key")
if not setting_key:
return 0
if self.argument("value") and self.option("unset"):
raise RuntimeError("You can not combine a setting value with --unset")
# show the value if no value is provided
if not self.argument("value") and not self.option("unset"):
if setting_key.split(".")[0] in self.LIST_PROHIBITED_SETTINGS:
raise ValueError(f"Expected a value for {setting_key} setting.")
value: str | dict[str, Any] | list[str]
if m := re.match(
r"installer\.build-config-settings(\.([^.]+))?", self.argument("key")
):
if not m.group(1):
if value := config.get("installer.build-config-settings"):
self._list_configuration(value, value)
else:
self.line("No packages configured with build config settings.")
else:
package_name = canonicalize_name(m.group(2))
key = f"installer.build-config-settings.{package_name}"
if value := config.get(key):
self.line(json.dumps(value))
else:
self.line(
f"No build config settings configured for {package_name}>."
)
return 0
elif m := re.match(r"^repos?(?:itories)?(?:\.(.+))?", self.argument("key")):
if not m.group(1):
value = {}
if config.get("repositories") is not None:
value = config.get("repositories")
else:
repo = config.get(f"repositories.{m.group(1)}")
if repo is None:
raise ValueError(f"There is no {m.group(1)} repository defined")
value = repo
self.line(str(value))
else:
if setting_key not in self.unique_config_values:
raise ValueError(f"There is no {setting_key} setting.")
value = config.get(setting_key)
if not isinstance(value, str):
value = json.dumps(value)
self.line(value)
return 0
values: list[str] = self.argument("value")
if setting_key in self.unique_config_values:
if self.option("unset"):
config.config_source.remove_property(setting_key)
return 0
return self._handle_single_value(
config.config_source,
setting_key,
self.unique_config_values[setting_key],
values,
)
# handle repositories
m = re.match(r"^repos?(?:itories)?(?:\.(.+))?", self.argument("key"))
if m:
if not m.group(1):
raise ValueError("You cannot remove the [repositories] section")
if self.option("unset"):
repo = config.get(f"repositories.{m.group(1)}")
if repo is None:
raise ValueError(f"There is no {m.group(1)} repository defined")
config.config_source.remove_property(f"repositories.{m.group(1)}")
return 0
if len(values) == 1:
url = values[0]
config.config_source.add_property(f"repositories.{m.group(1)}.url", url)
return 0
raise ValueError(
"You must pass the url. "
"Example: poetry config repositories.foo https://bar.com"
)
# handle auth
m = re.match(r"^(http-basic|pypi-token)\.(.+)", self.argument("key"))
if m:
from poetry.utils.password_manager import PasswordManager
password_manager = PasswordManager(config)
if self.option("unset"):
if m.group(1) == "http-basic":
password_manager.delete_http_password(m.group(2))
elif m.group(1) == "pypi-token":
password_manager.delete_pypi_token(m.group(2))
return 0
if m.group(1) == "http-basic":
if len(values) == 1:
username = values[0]
# Only username, so we prompt for password
password = self.secret("Password:")
assert isinstance(password, str)
elif len(values) != 2:
raise ValueError(
"Expected one or two arguments "
f"(username, password), got {len(values)}"
)
else:
username = values[0]
password = values[1]
password_manager.set_http_password(m.group(2), username, password)
elif m.group(1) == "pypi-token":
if len(values) != 1:
raise ValueError(
f"Expected only one argument (token), got {len(values)}"
)
token = values[0]
password_manager.set_pypi_token(m.group(2), token)
return 0
# handle certs
m = re.match(r"certificates\.([^.]+)\.(cert|client-cert)", self.argument("key"))
if m:
repository = m.group(1)
key = m.group(2)
if self.option("unset"):
config.auth_config_source.remove_property(
f"certificates.{repository}.{key}"
)
return 0
if len(values) == 1:
new_value: str | bool = values[0]
if key == "cert" and boolean_validator(values[0]):
new_value = boolean_normalizer(values[0])
config.auth_config_source.add_property(
f"certificates.{repository}.{key}", new_value
)
else:
raise ValueError("You must pass exactly 1 value")
return 0
# handle build config settings
m = re.match(r"installer\.build-config-settings\.([^.]+)", self.argument("key"))
if m:
key = f"installer.build-config-settings.{canonicalize_name(m.group(1))}"
if self.option("unset"):
config.config_source.remove_property(key)
return 0
try:
settings = config.config_source.get_property(key)
except PropertyNotFoundError:
settings = {}
for value in values:
if build_config_setting_validator(value):
config_settings = build_config_setting_normalizer(value)
for setting_name, item in config_settings.items():
settings[setting_name] = item
else:
raise ValueError(
f"Invalid build config setting '{value}'. "
"It must be a valid JSON with each property a string or a list of strings."
)
config.config_source.add_property(key, settings)
return 0
raise ValueError(f"Setting {self.argument('key')} does not exist")
def _handle_single_value(
self,
source: ConfigSource,
key: str,
callbacks: tuple[Any, Any],
values: list[Any],
) -> int:
validator, normalizer = callbacks
if len(values) > 1:
raise RuntimeError("You can only pass one value.")
value = values[0]
if not validator(value):
raise RuntimeError(f'"{value}" is an invalid value for {key}')
source.add_property(key, normalizer(value))
return 0
def _list_configuration(
self, config: dict[str, Any], raw: dict[str, Any], k: str = ""
) -> None:
orig_k = k
for key, value in sorted(config.items()):
if k + key in self.LIST_PROHIBITED_SETTINGS:
continue
raw_val = raw.get(key)
if isinstance(value, dict):
k += f"{key}."
raw_val = cast("dict[str, Any]", raw_val)
self._list_configuration(value, raw_val, k=k)
k = orig_k
continue
elif isinstance(value, list):
value = ", ".join(
json.dumps(val) if isinstance(val, list) else val for val in value
)
value = f"[{value}]"
if k.startswith("repositories."):
message = f"{k + key} = {json.dumps(raw_val)}"
elif isinstance(raw_val, str) and raw_val != value:
message = (
f"{k + key} = {json.dumps(raw_val)} # {value}"
)
else:
message = f"{k + key} = {json.dumps(value)}"
self.line(message)
def _migrate(self) -> None:
from poetry.config.file_config_source import FileConfigSource
from poetry.locations import CONFIG_DIR
from poetry.toml.file import TOMLFile
config_file = TOMLFile(CONFIG_DIR / "config.toml")
if self.option("local"):
config_file = TOMLFile(self.poetry.file.path.parent / "poetry.toml")
if not config_file.exists():
raise RuntimeError("No local config file found")
config_source = FileConfigSource(config_file)
self.io.write_line("Checking for required migrations ...")
required_migrations = [
migration
for migration in CONFIG_MIGRATIONS
if migration.dry_run(config_source, io=self.io)
]
if not required_migrations:
self.io.write_line("Already up to date.")
return
if not self.io.is_interactive() or self.confirm(
"Proceed with migration?: ", False
):
for migration in required_migrations:
migration.apply(config_source)
self.io.write_line("Config migration successfully done.")
================================================
FILE: src/poetry/console/commands/debug/__init__.py
================================================
================================================
FILE: src/poetry/console/commands/debug/info.py
================================================
from __future__ import annotations
import sys
from pathlib import Path
from poetry.console.commands.command import Command
class DebugInfoCommand(Command):
name = "debug info"
description = "Shows debug information."
def handle(self) -> int:
poetry_python_version = ".".join(str(s) for s in sys.version_info[:3])
self.line("")
self.line("Poetry")
self.line(
"\n".join(
[
f"Version: {self.poetry.VERSION}>",
f"Python: {poetry_python_version}>",
f"Path: {Path(sys.prefix)}>",
f"Executable: {Path(sys.executable) if sys.executable else 'Unknown'}>",
]
)
)
command = self.get_application().get("env info")
exit_code: int = command.run(self.io)
return exit_code
================================================
FILE: src/poetry/console/commands/debug/resolve.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from cleo.helpers import option
from cleo.io.outputs.output import Verbosity
from poetry.console.commands.init import InitCommand
from poetry.console.commands.show import ShowCommand
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
from cleo.ui.table import Rows
class DebugResolveCommand(InitCommand):
name = "debug resolve"
description = "Debugs dependency resolution."
arguments: ClassVar[list[Argument]] = [
argument("package", "The packages to resolve.", optional=True, multiple=True)
]
options: ClassVar[list[Option]] = [
option(
"extras",
"E",
"Extras to activate for the dependency.",
flag=False,
multiple=True,
),
option("python", None, "Python version(s) to use for resolution.", flag=False),
option("tree", None, "Display the dependency tree."),
option("install", None, "Show what would be installed for the current system."),
]
loggers: ClassVar[list[str]] = [
"poetry.repositories.pypi_repository",
"poetry.inspection.info",
]
def handle(self) -> int:
from cleo.io.null_io import NullIO
from poetry.core.packages.project_package import ProjectPackage
from poetry.factory import Factory
from poetry.puzzle.solver import Solver
from poetry.repositories.repository import Repository
from poetry.repositories.repository_pool import RepositoryPool
from poetry.utils.env import EnvManager
packages = self.argument("package")
if not packages:
package = self.poetry.package
else:
# Using current pool for determine_requirements()
self._pool = self.poetry.pool
package = ProjectPackage(
self.poetry.package.name, self.poetry.package.version
)
# Silencing output
verbosity = self.io.output.verbosity
self.io.output.set_verbosity(Verbosity.QUIET)
requirements = self._determine_requirements(packages)
self.io.output.set_verbosity(verbosity)
for constraint in requirements:
name = constraint.pop("name")
assert isinstance(name, str)
extras = []
for extra in self.option("extras"):
extras += extra.split()
constraint["extras"] = extras
package.add_dependency(Factory.create_dependency(name, constraint))
package.python_versions = self.option("python") or (
self.poetry.package.python_versions
)
pool = self.poetry.pool
solver = Solver(package, pool, [], [], self.io)
ops = solver.solve().calculate_operations()
self.line("")
self.line("Resolution results:")
self.line("")
if self.option("tree"):
show_command = self.get_application().find("show")
assert isinstance(show_command, ShowCommand)
show_command.init_styles(self.io)
packages = [op.package for op in ops]
requires = package.all_requires
for pkg in packages:
for require in requires:
if pkg.name == require.name:
show_command.display_package_tree(self.io, pkg, packages)
break
return 0
table = self.table(style="compact")
table.style.set_vertical_border_chars("", " ")
rows: Rows = []
if self.option("install"):
env = EnvManager(self.poetry).get()
pool = RepositoryPool(config=self.poetry.config)
locked_repository = Repository("poetry-locked")
for op in ops:
locked_repository.add_package(op.package)
pool.add_repository(locked_repository)
solver = Solver(package, pool, [], [], NullIO())
with solver.use_environment(env):
ops = solver.solve().calculate_operations()
for op in ops:
if self.option("install") and op.skipped:
continue
pkg = op.package
row = [
f"{pkg.complete_name}",
f"{pkg.version}",
]
if not pkg.marker.is_any():
row[2] = str(pkg.marker)
rows.append(row)
table.set_rows(rows)
table.render()
return 0
================================================
FILE: src/poetry/console/commands/debug/tags.py
================================================
from __future__ import annotations
from poetry.console.commands.env_command import EnvCommand
class DebugTagsCommand(EnvCommand):
name = "debug tags"
description = "Shows compatible tags for your project's current active environment."
def handle(self) -> int:
for tag in self.env.get_supported_tags():
self.io.write_line(
f"{tag.interpreter}>"
f"-{tag.abi}>"
f"-{tag.platform}>"
)
return 0
================================================
FILE: src/poetry/console/commands/env/__init__.py
================================================
================================================
FILE: src/poetry/console/commands/env/activate.py
================================================
from __future__ import annotations
import shlex
from typing import TYPE_CHECKING
import shellingham
from poetry.console.commands.env_command import EnvCommand
from poetry.utils._compat import WINDOWS
if TYPE_CHECKING:
from pathlib import Path
from poetry.utils.env import Env
class ShellNotSupportedError(Exception):
"""Raised when a shell doesn't have an activator in virtual environment"""
class EnvActivateCommand(EnvCommand):
name = "env activate"
description = "Print the command to activate a virtual environment."
def handle(self) -> int:
from poetry.utils.env import EnvManager
env = EnvManager(self.poetry).get()
try:
shell, _ = shellingham.detect_shell()
except shellingham.ShellDetectionFailure:
shell = ""
if command := self._get_activate_command(env, shell):
self.line(command)
return 0
raise ShellNotSupportedError(
f"Discovered shell '{shell}' doesn't have an activator in virtual environment"
)
def _get_activate_command(self, env: Env, shell: str) -> str:
if shell == "fish":
command, filename = "source", "activate.fish"
elif shell == "nu":
command, filename = "overlay use", "activate.nu"
elif shell in ["csh", "tcsh"]:
command, filename = "source", "activate.csh"
elif shell in ["powershell", "pwsh"]:
command, filename = "&", "activate.ps1"
elif shell == "cmd":
command, filename = "", "activate.bat"
elif shell in ["bash", "mksh", "zsh"]:
command, filename = "source", "activate"
else:
command, filename = ".", "activate"
if (activation_script := env.bin_dir / filename).exists():
quoted = self._quote(activation_script, shell)
return f"{command} {quoted}".strip()
return ""
@staticmethod
def _quote(activation_script: Path, shell: str) -> str:
if WINDOWS and shell in {"cmd", "powershell", "pwsh"}:
return f'"{activation_script}"'
return shlex.quote(activation_script.as_posix())
================================================
FILE: src/poetry/console/commands/env/info.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import option
from poetry.console.commands.command import Command
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
from poetry.utils.env import Env
class EnvInfoCommand(Command):
name = "env info"
description = "Displays information about the current environment."
options: ClassVar[list[Option]] = [
option("path", "p", "Only display the environment's path."),
option(
"executable", "e", "Only display the environment's python executable path."
),
]
def handle(self) -> int:
from poetry.utils.env import EnvManager
env = EnvManager(self.poetry).get()
if self.option("path"):
if not env.is_venv():
return 1
self.line(str(env.path))
return 0
if self.option("executable"):
if not env.is_venv():
return 1
self.line(str(env.python))
return 0
self._display_complete_info(env)
return 0
def _display_complete_info(self, env: Env) -> None:
env_python_version = ".".join(str(s) for s in env.version_info[:3])
self.line("")
self.line("Virtualenv")
listing = [
f"Python: {env_python_version}>",
f"Implementation: {env.python_implementation}>",
(
"Path: "
f" {env.path if env.is_venv() else 'NA'}>"
),
(
"Executable: "
f" {env.python if env.is_venv() else 'NA'}>"
),
]
if env.is_venv():
listing.append(
"Valid: "
f" <{'comment' if env.is_sane() else 'error'}>{env.is_sane()}>"
)
self.line("\n".join(listing))
self.line("")
base_env = env.parent_env
python = ".".join(str(v) for v in base_env.version_info[:3])
self.line("Base")
self.line(
"\n".join(
[
f"Platform: {env.platform}>",
f"OS: {env.os}>",
f"Python: {python}>",
f"Path: {base_env.path}>",
f"Executable: {base_env.python}>",
]
)
)
================================================
FILE: src/poetry/console/commands/env/list.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import option
from poetry.console.commands.command import Command
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
class EnvListCommand(Command):
name = "env list"
description = "Lists all virtualenvs associated with the current project."
options: ClassVar[list[Option]] = [
option("full-path", None, "Output the full paths of the virtualenvs.")
]
def handle(self) -> int:
from poetry.utils.env import EnvManager
manager = EnvManager(self.poetry)
current_env = manager.get()
for venv in manager.list():
name = venv.path.name
if self.option("full-path"):
name = str(venv.path)
if venv == current_env:
self.line(f"{name} (Activated)")
continue
self.line(name)
return 0
================================================
FILE: src/poetry/console/commands/env/remove.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from cleo.helpers import option
from poetry.console.commands.command import Command
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
class EnvRemoveCommand(Command):
name = "env remove"
description = "Remove virtual environments associated with the project."
arguments: ClassVar[list[Argument]] = [
argument(
"python",
"The python executables associated with, or names of the virtual"
" environments which are to be removed.",
optional=True,
multiple=True,
)
]
options: ClassVar[list[Option]] = [
option(
"all",
description=(
"Remove all managed virtual environments associated with the project."
),
),
]
def handle(self) -> int:
from poetry.utils.env import EnvManager
is_in_project = self.poetry.config.get("virtualenvs.in-project")
pythons = self.argument("python")
remove_all_envs = self.option("all")
if not (pythons or remove_all_envs or is_in_project):
self.line("No virtualenv provided.")
manager = EnvManager(self.poetry)
# TODO: refactor env.py to allow removal with one loop
for python in pythons:
venv = manager.remove(python)
self.line(f"Deleted virtualenv: {venv.path}")
if remove_all_envs or is_in_project:
for venv in manager.list():
if not is_in_project or venv.path.is_relative_to(
self.poetry.pyproject_path.parent
):
manager.remove_venv(venv.path)
self.line(f"Deleted virtualenv: {venv.path}")
# Since we remove all the virtualenvs, we can also remove the entry
# in the envs file. (Strictly speaking, we should do this explicitly,
# in case it points to a virtualenv that had been removed manually before.)
if remove_all_envs and manager.envs_file.exists():
manager.envs_file.remove_section(manager.base_env_name)
return 0
================================================
FILE: src/poetry/console/commands/env/use.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from poetry.console.commands.command import Command
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
class EnvUseCommand(Command):
name = "env use"
description = "Activates or creates a new virtualenv for the current project."
arguments: ClassVar[list[Argument]] = [
argument("python", "The python executable to use.")
]
def handle(self) -> int:
from poetry.utils.env import EnvManager
manager = EnvManager(self.poetry, io=self.io)
if self.argument("python") == "system":
manager.deactivate()
return 0
env = manager.activate(self.argument("python"))
self.line(f"Using virtualenv: {env.path}>")
return 0
================================================
FILE: src/poetry/console/commands/env_command.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from poetry.console.commands.command import Command
if TYPE_CHECKING:
from poetry.utils.env import Env
class EnvCommand(Command):
def __init__(self) -> None:
# Set in poetry.console.application.Application.configure_env
self._env: Env | None = None
super().__init__()
@property
def env(self) -> Env:
assert self._env is not None
return self._env
def set_env(self, env: Env) -> None:
self._env = env
================================================
FILE: src/poetry/console/commands/group_command.py
================================================
from __future__ import annotations
from collections import defaultdict
from typing import TYPE_CHECKING
from cleo.helpers import option
from packaging.utils import NormalizedName
from packaging.utils import canonicalize_name
from poetry.console.commands.command import Command
from poetry.console.exceptions import GroupNotFoundError
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
from poetry.core.packages.project_package import ProjectPackage
class GroupCommand(Command):
@staticmethod
def _group_dependency_options() -> list[Option]:
return [
option(
"without",
None,
"The dependency groups to ignore.",
flag=False,
multiple=True,
),
option(
"with",
None,
"The optional dependency groups to include.",
flag=False,
multiple=True,
),
option(
"only",
None,
"The only dependency groups to include.",
flag=False,
multiple=True,
),
]
@property
def non_optional_groups(self) -> set[str]:
# TODO: this should move into poetry-core
return {
group.name
for group in self.poetry.package._dependency_groups.values()
if not group.is_optional()
}
@property
def default_group(self) -> str | None:
"""
The default group to use when no group is specified. This is useful
for command that have the `--group` option, eg: add, remove.
Can be overridden to adapt behavior.
"""
return None
@property
def default_groups(self) -> set[str]:
"""
The groups that are considered by the command by default.
Can be overridden to adapt behavior.
"""
return self.non_optional_groups
@property
def activated_groups(self) -> set[NormalizedName]:
groups = {}
for key in {"with", "without", "only"}:
groups[key] = {
group.strip()
for groups in self.option(key, "")
for group in groups.split(",")
}
if self.option("all-groups"):
groups["with"] = self.poetry.package.dependency_group_names(
include_optional=True
)
self._validate_group_options(groups)
if groups["only"] and (groups["with"] or groups["without"]):
self.line_error(
"The `--with>` and "
"`--without>` options are ignored when used"
" along with the `--only>` option."
""
)
# Normalize after validating so that original names are printed
# in case of an error.
norm_groups = {
key: {canonicalize_name(group) for group in key_groups}
for key, key_groups in groups.items()
}
norm_default_groups = {canonicalize_name(name) for name in self.default_groups}
return norm_groups["only"] or norm_default_groups.union(
norm_groups["with"]
).difference(norm_groups["without"])
def project_with_activated_groups_only(self) -> ProjectPackage:
return self.poetry.package.with_dependency_groups(
list(self.activated_groups), only=True
)
def _validate_group_options(self, group_options: dict[str, set[str]]) -> None:
"""
Raises an error if it detects that a group is not part of pyproject.toml
"""
invalid_options = defaultdict(set)
for opt, groups in group_options.items():
for group in groups:
if not self.poetry.package.has_dependency_group(group):
invalid_options[group].add(opt)
if invalid_options:
message_parts = []
for group in sorted(invalid_options):
opts = ", ".join(
f"--{opt}>"
for opt in sorted(invalid_options[group])
)
message_parts.append(f"{group} (via {opts})")
raise GroupNotFoundError(f"Group(s) not found: {', '.join(message_parts)}")
================================================
FILE: src/poetry/console/commands/init.py
================================================
from __future__ import annotations
import re
from collections.abc import Mapping
from contextlib import suppress
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from cleo.helpers import option
from packaging.utils import canonicalize_name
from tomlkit import inline_table
from poetry.console.commands.command import Command
from poetry.console.commands.env_command import EnvCommand
from poetry.utils.dependency_specification import RequirementsParser
from poetry.utils.env.python import Python
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
from packaging.utils import NormalizedName
from poetry.core.packages.package import Package
from tomlkit.items import InlineTable
from poetry.repositories import RepositoryPool
Requirements = dict[str, str | Mapping[str, Any]]
class InitCommand(Command):
name = "init"
description = (
"Creates a basic pyproject.toml> file in the current directory."
)
options: ClassVar[list[Option]] = [
option("name", None, "Name of the package.", flag=False),
option("description", None, "Description of the package.", flag=False),
option("author", None, "Author name of the package.", flag=False),
option("python", None, "Compatible Python versions.", flag=False),
option(
"dependency",
None,
"Package to require, with an optional version constraint, "
"e.g. requests:^2.10.0 or requests=2.11.1.",
flag=False,
multiple=True,
),
option(
"dev-dependency",
None,
"Package to require for development, with an optional version"
" constraint, e.g. requests:^2.10.0 or requests=2.11.1.",
flag=False,
multiple=True,
),
option("license", "l", "License of the package.", flag=False),
]
help = """\
The init command creates a basic pyproject.toml> file in the\
current directory.
"""
def __init__(self) -> None:
super().__init__()
self._pool: RepositoryPool | None = None
def handle(self) -> int:
from pathlib import Path
project_path = Path.cwd()
if self.io.input.option("project"):
project_path = Path(self.io.input.option("project"))
if not project_path.exists() or not project_path.is_dir():
self.line_error("The --project path is not a directory.")
return 1
return self._init_pyproject(project_path=project_path)
def _init_pyproject(
self,
project_path: Path,
allow_interactive: bool = True,
layout_name: str = "standard",
readme_format: str = "md",
allow_layout_creation_on_empty: bool = False,
) -> int:
from poetry.core.vcs.git import GitConfig
from poetry.config.config import Config
from poetry.layouts import layout
from poetry.pyproject.toml import PyProjectTOML
is_interactive = self.io.is_interactive() and allow_interactive
pyproject = PyProjectTOML(project_path / "pyproject.toml")
if pyproject.file.exists():
if pyproject.is_poetry_project():
self.line_error(
"A pyproject.toml file with a project and/or"
" a poetry section already exists."
)
return 1
if pyproject.data.get("build-system"):
self.line_error(
"A pyproject.toml file with a defined build-system already"
" exists."
)
return 1
vcs_config = GitConfig()
if is_interactive:
self.line("")
self.line(
"This command will guide you through creating your"
" pyproject.toml> config."
)
self.line("")
name = self.option("name")
if not name:
name = project_path.name.lower()
if is_interactive:
question = self.create_question(
f"Package name [{name}]: ", default=name
)
name = self.ask(question)
version = "0.1.0"
if is_interactive:
question = self.create_question(
f"Version [{version}]: ", default=version
)
version = self.ask(question)
description = self.option("description") or ""
if not description and is_interactive:
description = self.ask(self.create_question("Description []: ", default=""))
author = self.option("author")
if not author and vcs_config.get("user.name"):
author = vcs_config["user.name"]
author_email = vcs_config.get("user.email")
if author_email:
author += f" <{author_email}>"
if is_interactive:
question = self.create_question(
f"Author [{author}, n to skip]: ", default=author
)
question.set_validator(lambda v: self._validate_author(v, author))
author = self.ask(question)
authors = [author] if author else []
license_name = self.option("license")
if not license_name and is_interactive:
license_name = self.ask(self.create_question("License []: ", default=""))
python = self.option("python")
if not python:
config = Config.create()
python = (
">="
+ Python.get_preferred_python(config, self.io).minor_version.to_string()
)
if is_interactive:
question = self.create_question(
f"Compatible Python versions [{python}]: ",
default=python,
)
python = self.ask(question)
if is_interactive:
self.line("")
requirements: Requirements = {}
if self.option("dependency"):
requirements = self._format_requirements(
self._determine_requirements(self.option("dependency"))
)
question_text = "Would you like to define your main dependencies interactively?"
help_message = """\
You can specify a package in the following forms:
- A single name (requests): this will search for matches on PyPI
- A name and a constraint (requests@^2.23.0)
- A git url (git+https://github.com/python-poetry/poetry.git)
- A git url with a revision\
(git+https://github.com/python-poetry/poetry.git#develop)
- A file path (../my-package/my-package.whl)
- A directory (../my-package/)
- A url (https://example.com/packages/my-package-0.1.0.tar.gz)
"""
help_displayed = False
if is_interactive and self.confirm(question_text, True):
self.line(help_message)
help_displayed = True
requirements.update(
self._format_requirements(self._determine_requirements([]))
)
self.line("")
dev_requirements: Requirements = {}
if self.option("dev-dependency"):
dev_requirements = self._format_requirements(
self._determine_requirements(self.option("dev-dependency"))
)
question_text = (
"Would you like to define your development dependencies interactively?"
)
if is_interactive and self.confirm(question_text, True):
if not help_displayed:
self.line(help_message)
dev_requirements.update(
self._format_requirements(self._determine_requirements([]))
)
self.line("")
layout_ = layout(layout_name)(
name,
version,
description=description,
author=authors[0] if authors else None,
readme_format=readme_format,
license=license_name,
python=python,
dependencies=requirements,
dev_dependencies=dev_requirements,
)
create_layout = not project_path.exists() or (
allow_layout_creation_on_empty and not any(project_path.iterdir())
)
if create_layout:
layout_.create(project_path, with_pyproject=False)
content = layout_.generate_project_content(project_path)
for section, item in content.items():
pyproject.data.append(section, item)
if is_interactive:
self.line("Generated file")
self.line("")
self.line(pyproject.data.as_string().replace("\r\n", "\n"))
self.line("")
if is_interactive and not self.confirm("Do you confirm generation?", True):
self.line_error("Command aborted")
return 1
pyproject.save()
if create_layout:
path = project_path.resolve()
with suppress(ValueError):
path = path.relative_to(Path.cwd())
self.line(
f"Created package {layout_._package_name}> in"
f" {path.as_posix()}>"
)
return 0
def _generate_choice_list(
self, matches: list[Package], canonicalized_name: NormalizedName
) -> list[str]:
choices = []
matches_names = [p.name for p in matches]
exact_match = canonicalized_name in matches_names
if exact_match:
choices.append(matches[matches_names.index(canonicalized_name)].pretty_name)
for found_package in matches:
if len(choices) >= 10:
break
if found_package.name == canonicalized_name:
continue
choices.append(found_package.pretty_name)
return choices
def _determine_requirements(
self,
requires: list[str],
allow_prereleases: bool | None = None,
source: str | None = None,
is_interactive: bool | None = None,
) -> list[dict[str, Any]]:
if is_interactive is None:
is_interactive = self.io.is_interactive()
if not requires:
result = []
question = self.create_question(
"Package to add or search for (leave blank to skip):"
)
question.set_validator(self._validate_package)
follow_up_question = self.create_question(
"\nAdd a package (leave blank to skip):"
)
follow_up_question.set_validator(self._validate_package)
package = self.ask(question)
while package:
constraint = self._parse_requirements([package])[0]
if (
"git" in constraint
or "url" in constraint
or "path" in constraint
or "version" in constraint
):
self.line(f"Adding {package}")
result.append(constraint)
package = self.ask(follow_up_question)
continue
canonicalized_name = canonicalize_name(constraint["name"])
matches = self._get_pool().search(canonicalized_name)
if not matches:
self.line_error("Unable to find package")
package = False
else:
choices = self._generate_choice_list(matches, canonicalized_name)
info_string = (
f"Found {len(matches)} packages matching"
f" {package}"
)
if len(matches) > 10:
info_string += "\nShowing the first 10 matches"
self.line(info_string)
# Default to an empty value to signal no package was selected
choices.append("")
package = self.choice(
"\nEnter package # to add, or the complete package name if"
" it is not listed",
choices,
attempts=3,
default=len(choices) - 1,
)
if not package:
self.line("No package selected")
# package selected by user, set constraint name to package name
if package:
constraint["name"] = package
# no constraint yet, determine the best version automatically
if package and "version" not in constraint:
question = self.create_question(
"Enter the version constraint to require "
"(or leave blank to use the latest version):"
)
question.set_max_attempts(3)
question.set_validator(lambda x: (x or "").strip() or None)
package_constraint = self.ask(question)
if package_constraint is None:
_, package_constraint = self._find_best_version_for_package(
package
)
self.line(
f"Using version {package_constraint} for"
f" {package}"
)
constraint["version"] = package_constraint
if package:
result.append(constraint)
if is_interactive:
package = self.ask(follow_up_question)
return result
result = []
for requirement in self._parse_requirements(requires):
if "git" in requirement or "url" in requirement or "path" in requirement:
result.append(requirement)
continue
elif "version" not in requirement:
# determine the best version automatically
name, version = self._find_best_version_for_package(
requirement["name"],
allow_prereleases=allow_prereleases,
source=source,
)
requirement["version"] = version
requirement["name"] = name
self.line(f"Using version {version} for {name}")
else:
# check that the specified version/constraint exists
# before we proceed
name, _ = self._find_best_version_for_package(
requirement["name"],
requirement["version"],
allow_prereleases=allow_prereleases,
source=source,
)
requirement["name"] = name
result.append(requirement)
return result
def _find_best_version_for_package(
self,
name: str,
required_version: str | None = None,
allow_prereleases: bool | None = None,
source: str | None = None,
) -> tuple[str, str]:
from poetry.version.version_selector import VersionSelector
selector = VersionSelector(self._get_pool())
package = selector.find_best_candidate(
name, required_version, allow_prereleases=allow_prereleases, source=source
)
if not package:
# TODO: find similar
raise ValueError(f"Could not find a matching version of package {name}")
version = package.version.without_local()
return package.pretty_name, f"^{version.to_string()}"
def _parse_requirements(self, requirements: list[str]) -> list[dict[str, Any]]:
from poetry.core.pyproject.exceptions import PyProjectError
try:
cwd = self.poetry.file.path.parent
artifact_cache = self.poetry.pool.artifact_cache
except (PyProjectError, RuntimeError):
cwd = Path.cwd()
artifact_cache = self._get_pool().artifact_cache
parser = RequirementsParser(
artifact_cache=artifact_cache,
env=self.env if isinstance(self, EnvCommand) else None,
cwd=cwd,
)
return [
parser.parse(re.sub(r"@\s*latest$", "", requirement, flags=re.I))
for requirement in requirements
]
def _format_requirements(self, requirements: list[dict[str, str]]) -> Requirements:
requires: Requirements = {}
for requirement in requirements:
name = requirement.pop("name")
constraint: str | InlineTable
if "version" in requirement and len(requirement) == 1:
constraint = requirement["version"]
else:
constraint = inline_table()
constraint.trivia.trail = "\n"
constraint.update(requirement)
requires[name] = constraint
return requires
@staticmethod
def _validate_author(author: str, default: str) -> str | None:
from poetry.core.utils.helpers import combine_unicode
from poetry.core.utils.patterns import AUTHOR_REGEX
author = combine_unicode(author or default)
if author in ["n", "no"]:
return None
m = AUTHOR_REGEX.match(author)
if not m:
raise ValueError(
"Invalid author string. Must be in the format: "
"John Smith "
)
return author
@staticmethod
def _validate_package(package: str | None) -> str | None:
if package and len(package.split()) > 2:
raise ValueError("Invalid package definition.")
return package
def _get_pool(self) -> RepositoryPool:
from poetry.config.config import Config
from poetry.repositories import RepositoryPool
from poetry.repositories.pypi_repository import PyPiRepository
if isinstance(self, EnvCommand):
return self.poetry.pool
if self._pool is None:
self._pool = RepositoryPool()
pool_size = Config.create().installer_max_workers
self._pool.add_repository(PyPiRepository(pool_size=pool_size))
return self._pool
================================================
FILE: src/poetry/console/commands/install.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import option
from poetry.console.commands.installer_command import InstallerCommand
from poetry.plugins.plugin_manager import PluginManager
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
from packaging.utils import NormalizedName
class InstallCommand(InstallerCommand):
name = "install"
description = "Installs the project dependencies."
options: ClassVar[list[Option]] = [
*InstallerCommand._group_dependency_options(),
option(
"sync",
None,
"Synchronize the environment with the locked packages and the specified"
" groups. (Deprecated)",
),
option(
"no-root", None, "Do not install the root package (the current project)."
),
option(
"no-directory",
None,
"Do not install any directory path dependencies; useful to install"
" dependencies without source code, e.g. for caching of Docker layers)",
flag=True,
multiple=False,
),
option(
"dry-run",
None,
"Output the operations but do not execute anything "
"(implicitly enables --verbose).",
),
option(
"extras",
"E",
"Extra sets of dependencies to install.",
flag=False,
multiple=True,
),
option("all-extras", None, "Install all extra dependencies."),
option("all-groups", None, "Install dependencies from all groups."),
option("only-root", None, "Exclude all dependencies."),
option(
"compile",
None,
"Compile Python source files to bytecode.",
),
]
help = """\
The install command reads the poetry.lock> file from
the current directory, processes it, and downloads and installs all the
libraries and dependencies outlined in that file. If the file does not
exist it will look for pyproject.toml> and do the same.
poetry install
By default, the above command will also install the current project. To install only the
dependencies and not including the current project, run the command with the
--no-root option like below:
poetry install --no-root
If you want to use Poetry only for dependency management but not for packaging,
you can set the "package-mode" to false in your pyproject.toml file.
"""
_loggers: ClassVar[list[str]] = [
"poetry.repositories.pypi_repository",
"poetry.inspection.info",
]
@property
def activated_groups(self) -> set[NormalizedName]:
if self.option("only-root"):
return set()
else:
return super().activated_groups
@property
def _alternative_sync_command(self) -> str:
return "poetry sync"
@property
def _with_synchronization(self) -> bool:
with_synchronization = self.option("sync")
if with_synchronization:
self.line_error(
"The `--sync>` option is"
" deprecated and slated for removal, use the"
f" `{self._alternative_sync_command}>`"
" command instead."
)
return bool(with_synchronization)
def handle(self) -> int:
from poetry.core.masonry.utils.module import ModuleOrPackageNotFoundError
from poetry.masonry.builders.editable import EditableBuilder
if not self.option("no-plugins"):
PluginManager.ensure_project_plugins(self.poetry, self.io)
if self.option("extras") and self.option("all-extras"):
self.line_error(
"You cannot specify explicit"
" `--extras>` while installing"
" using `--all-extras>`."
)
return 1
if self.option("only-root") and any(
self.option(key) for key in {"with", "without", "only", "all-groups"}
):
self.line_error(
"The `--with>`,"
" `--without>`,"
" `--only>` and"
" `--all-groups>`"
" options cannot be used with"
" the `--only-root>`"
" option."
)
return 1
if self.option("only-root") and self.option("no-root"):
self.line_error(
"You cannot specify `--no-root>`"
" when using `--only-root>`."
)
return 1
if (
self.option("only") or self.option("with") or self.option("without")
) and self.option("all-groups"):
self.line_error(
"You cannot specify `--with>`,"
" `--without>`, or"
" `--only>` when using"
" `--all-groups>`."
)
return 1
extras: list[str]
if self.option("all-extras"):
extras = list(self.poetry.package.extras.keys())
else:
extras = []
for extra in self.option("extras", []):
extras += extra.split()
self.installer.extras(extras)
self.installer.only_groups(self.activated_groups)
self.installer.skip_directory(self.option("no-directory"))
self.installer.dry_run(self.option("dry-run"))
self.installer.requires_synchronization(self._with_synchronization)
self.installer.executor.enable_bytecode_compilation(self.option("compile"))
self.installer.verbose(self.io.is_verbose())
return_code = self.installer.run()
if return_code != 0:
return return_code
if self.option("no-root") or not self.poetry.is_package_mode:
return 0
log_install = (
"Installing> the current project:"
f" {self.poetry.package.pretty_name}"
f" (<{{tag}}>{self.poetry.package.pretty_version}>)"
)
overwrite = self.io.output.is_decorated() and not self.io.is_debug()
self.line("")
self.write(log_install.format(tag="c2"))
if not overwrite:
self.line("")
if self.option("dry-run"):
self.line("")
return 0
# Prior to https://github.com/python-poetry/poetry-core/pull/629
# the existence of a module/package was checked when creating the
# EditableBuilder. Afterwards, the existence is checked after
# executing the build script (if there is one),
# i.e. during EditableBuilder.build().
try:
builder = EditableBuilder(self.poetry, self.env, self.io)
builder.build()
except (ModuleOrPackageNotFoundError, FileNotFoundError) as e:
# This is likely due to the fact that the project is an application
# not following the structure expected by Poetry.
# No need for an editable install in this case.
self.line("")
self.line_error(
f"Error: The current project could not be installed: {e}\n"
"If you do not want to install the current project"
" use --no-root.\n"
"If you want to use Poetry only for dependency management"
" but not for packaging, you can disable package mode by setting"
" package-mode = false> in your pyproject.toml file.\n"
"If you did intend to install the current project, you may need"
" to set `packages` in your pyproject.toml file.\n",
style="error",
)
return 1
if overwrite:
self.overwrite(log_install.format(tag="success"))
self.line("")
return 0
================================================
FILE: src/poetry/console/commands/installer_command.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from poetry.console.commands.env_command import EnvCommand
from poetry.console.commands.group_command import GroupCommand
from poetry.utils.password_manager import PoetryKeyring
if TYPE_CHECKING:
from cleo.io.io import IO
from poetry.installation.installer import Installer
class InstallerCommand(GroupCommand, EnvCommand):
def __init__(self) -> None:
# Set in poetry.console.application.Application.configure_installer
self._installer: Installer | None = None
super().__init__()
def reset_poetry(self) -> None:
super().reset_poetry()
self.installer.set_package(self.poetry.package)
self.installer.set_locker(self.poetry.locker)
@property
def installer(self) -> Installer:
assert self._installer is not None
return self._installer
def set_installer(self, installer: Installer) -> None:
self._installer = installer
def execute(self, io: IO) -> int:
PoetryKeyring.preflight_check(io, self.poetry.config)
return super().execute(io)
================================================
FILE: src/poetry/console/commands/lock.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import option
from poetry.console.commands.installer_command import InstallerCommand
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
class LockCommand(InstallerCommand):
name = "lock"
description = "Locks the project dependencies."
options: ClassVar[list[Option]] = [
option(
"regenerate",
None,
"Ignore existing lock file"
" and overwrite it with a new lock file created from scratch.",
),
]
help = """
The lock command reads the pyproject.toml> file from the
current directory, processes it, and locks the dependencies in the\
poetry.lock>
file.
By default, packages that have already been added to the lock file before
will not be updated.
poetry lock
"""
loggers: ClassVar[list[str]] = ["poetry.repositories.pypi_repository"]
def handle(self) -> int:
self.installer.lock(update=self.option("regenerate"))
return self.installer.run()
================================================
FILE: src/poetry/console/commands/new.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from cleo.helpers import option
from poetry.console.commands.init import InitCommand
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
class NewCommand(InitCommand):
name = "new"
description = "Creates a new Python project at ."
arguments: ClassVar[list[Argument]] = [
argument("path", "The path to create the project at.")
]
options: ClassVar[list[Option]] = [
option(
"interactive",
"i",
"Allow interactive specification of project configuration.",
flag=True,
),
option("name", None, "Set the resulting package name.", flag=False),
option(
"src",
None,
"Use the src layout for the project. "
"Deprecated>: This is the default option now.",
),
option("flat", None, "Use the flat layout for the project."),
option(
"readme",
None,
"Specify the readme file format. Default is md.",
flag=False,
),
*[
o
for o in InitCommand.options
if o.name
in {
"description",
"author",
"python",
"dependency",
"dev-dependency",
"license",
}
],
]
def handle(self) -> int:
from pathlib import Path
if self.io.input.option("project"):
self.line_error(
"--project only makes sense with existing projects, and will"
" be ignored. You should consider the option --path instead."
)
path = Path(self.argument("path"))
if not path.is_absolute():
# we do not use resolve here due to compatibility issues
# for path.resolve(strict=False)
path = Path.cwd().joinpath(path)
if path.exists() and list(path.glob("*")):
# Directory is not empty. Aborting.
raise RuntimeError(
f"Destination {path}> exists and is not empty. Did you mean `poetry init`?"
)
if self.option("src"):
self.line_error(
"The --src> option is now the default and will be removed in a future version."
)
return self._init_pyproject(
project_path=path,
allow_interactive=self.option("interactive"),
layout_name="standard" if self.option("flat") else "src",
readme_format=self.option("readme") or "md",
allow_layout_creation_on_empty=True,
)
================================================
FILE: src/poetry/console/commands/publish.py
================================================
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import option
from poetry.console.commands.command import Command
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
class PublishCommand(Command):
name = "publish"
description = "Publishes a package to a remote repository."
options: ClassVar[list[Option]] = [
option(
"repository", "r", "The repository to publish the package to.", flag=False
),
option("username", "u", "The username to access the repository.", flag=False),
option("password", "p", "The password to access the repository.", flag=False),
option(
"cert", None, "Certificate authority to access the repository.", flag=False
),
option(
"client-cert",
None,
"Client certificate to access the repository.",
flag=False,
),
option(
"dist-dir",
None,
"Dist directory where built artifact are stored. Default is `dist`.",
default="dist",
flag=False,
),
option("build", None, "Build the package before publishing."),
option("dry-run", None, "Perform all actions except upload the package."),
option(
"skip-existing",
None,
"Ignore errors from files already existing in the repository.",
),
]
help = """The publish command builds and uploads the package to a remote repository.
By default, it will upload to PyPI but if you pass the --repository option it will
upload to it instead.
The --repository option should match the name of a configured repository using
the config command.
"""
loggers: ClassVar[list[str]] = ["poetry.publishing.publisher"]
def handle(self) -> int:
from poetry.publishing.publisher import Publisher
if not self.poetry.is_package_mode:
self.line_error("Publishing a package is not possible in non-package mode.")
return 1
dist_dir = self.option("dist-dir")
publisher = Publisher(self.poetry, self.io, Path(dist_dir))
# Building package first, if told
if self.option("build"):
if publisher.files and not self.confirm(
f"There are {len(publisher.files)} files ready for"
" publishing. Build anyway?"
):
self.line_error("Aborted!")
return 1
self.call("build", args=f"--output {dist_dir}")
publisher = Publisher(self.poetry, self.io, Path(dist_dir))
if not publisher.files:
self.line_error(
"No files to publish. "
"Run poetry build first or use the --build option."
)
return 1
self.line("")
cert = Path(self.option("cert")) if self.option("cert") else None
client_cert = (
Path(self.option("client-cert")) if self.option("client-cert") else None
)
publisher.publish(
self.option("repository"),
self.option("username"),
self.option("password"),
cert,
client_cert,
self.option("dry-run"),
self.option("skip-existing"),
)
return 0
================================================
FILE: src/poetry/console/commands/python/__init__.py
================================================
from __future__ import annotations
def get_request_title(request: str, implementation: str, free_threaded: bool) -> str:
add_info = implementation
if free_threaded:
add_info += ", free-threaded"
return f"{request}> ({add_info}>)"
================================================
FILE: src/poetry/console/commands/python/install.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from cleo.helpers import option
from poetry.core.constraints.version.version import Version
from poetry.core.version.exceptions import InvalidVersionError
from poetry.console.commands.command import Command
from poetry.console.commands.python import get_request_title
from poetry.console.commands.python.remove import PythonRemoveCommand
from poetry.console.exceptions import PoetryRuntimeError
from poetry.utils.env.python.installer import PythonDownloadNotFoundError
from poetry.utils.env.python.installer import PythonInstallationError
from poetry.utils.env.python.installer import PythonInstaller
from poetry.utils.env.python.providers import PoetryPythonPathProvider
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
class PythonInstallCommand(Command):
name = "python install"
arguments: ClassVar[list[Argument]] = [
argument("python", "The python version to install.")
]
options: ClassVar[list[Option]] = [
option("clean", "c", "Clean up installation if check fails.", flag=True),
option(
"free-threaded", "t", "Use free-threaded version if available.", flag=True
),
option(
"implementation",
"i",
"Python implementation to use. (cpython, pypy)",
flag=False,
default="cpython",
),
option(
"reinstall", "r", "Reinstall if installation already exists.", flag=True
),
]
description = (
"Install the specified Python version from the Python Standalone Builds project."
" (experimental feature)"
)
def handle(self) -> int:
request = self.argument("python")
impl = self.option("implementation").lower()
reinstall = self.option("reinstall")
free_threaded = self.option("free-threaded")
if request.endswith("t"):
free_threaded = True
request = request[:-1]
try:
version = Version.parse(request)
except (ValueError, InvalidVersionError):
self.io.write_error_line(
f"Invalid Python version requested {request}>"
)
return 1
if free_threaded and version < Version.parse("3.13.0"):
self.io.write_error_line("")
self.io.write_error_line(
"Free threading is not supported for Python versions prior to 3.13.0>.\n\n"
"See https://docs.python.org/3/howto/free-threading-python.html for more information."
)
self.io.write_error_line("")
return 1
installer = PythonInstaller(request, impl, free_threaded)
try:
if installer.exists() and not reinstall:
self.io.write_error_line(
"Python version already installed at "
f"{PoetryPythonPathProvider.installation_dir(version, impl, free_threaded)}>.\n"
)
self.io.write_error_line(
f"Use --reinstall> to install anyway, "
f"or use poetry python remove {version}> first."
)
return 1
except PythonDownloadNotFoundError:
self.io.write_error_line(
"No suitable standalone build found for the requested Python version."
)
return 1
request_title = get_request_title(request, impl, free_threaded)
try:
self.io.write(f"Downloading and installing {request_title} ... ")
installer.install()
except PythonInstallationError as e:
self.io.write("Failed>\n")
self.io.write_error_line("")
self.io.write_error_line(str(e))
self.io.write_error_line("")
return 1
self.io.write("Done>\n")
self.io.write(f"Testing {request_title} ... ")
try:
installer.exists()
except PoetryRuntimeError as e:
self.io.write("Failed>\n")
if installer.installation_directory.exists() and self.option("clean"):
PythonRemoveCommand.remove_python_installation(
str(installer.version), impl, free_threaded, self.io
)
raise e
self.io.write("Done>\n")
return 0
================================================
FILE: src/poetry/console/commands/python/list.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from cleo.helpers import option
from poetry.core.constraints.version import parse_constraint
from poetry.core.version.exceptions import InvalidVersionError
from poetry.config.config import Config
from poetry.console.commands.command import Command
from poetry.utils.env.python import Python
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
from poetry.utils.env.python.manager import PythonInfo
class PythonListCommand(Command):
name = "python list"
arguments: ClassVar[list[Argument]] = [
argument("version", "Python version to search for.", optional=True)
]
options: ClassVar[list[Option]] = [
option(
"all",
"a",
"List all versions, including those available for download.",
flag=True,
),
option(
"free-threaded", "t", "List only free-threaded Python versions.", flag=True
),
option(
"implementation", "i", "Python implementation to search for.", flag=False
),
option("managed", "m", "List only Poetry managed Python versions.", flag=True),
]
description = (
"Shows Python versions available for this environment."
" (experimental feature)"
)
def handle(self) -> int:
rows: list[PythonInfo] = []
constraint = None
if self.argument("version"):
request = self.argument("version")
version = f"~{request}" if request.count(".") < 2 else request
try:
constraint = parse_constraint(version)
except (ValueError, InvalidVersionError):
self.io.write_error_line(
f"Invalid Python version requested {request}>"
)
return 1
for info in Python.find_all_versions(
constraint=constraint,
implementation=self.option("implementation"),
free_threaded=self.option("free-threaded") or None,
):
rows.append(info)
if self.option("all"):
for info in Python.find_downloadable_versions(constraint):
rows.append(info)
rows.sort(
key=lambda x: (
x.major,
x.minor,
x.patch,
x.implementation,
x.free_threaded,
),
reverse=True,
)
table = self.table(style="compact")
table.set_headers(
[
"Version>",
"Implementation>",
"Manager>",
"Path>",
]
)
implementations = {"cpython": "CPython", "pypy": "PyPy"}
python_installation_path = Config.create().python_installation_dir
row_count = 0
for pv in rows:
version = f"{pv.major}.{pv.minor}.{pv.patch}"
if pv.free_threaded:
version += "t"
implementation = implementations.get(
pv.implementation.lower(), pv.implementation
)
is_poetry_managed = (
pv.executable is None
or pv.executable.resolve().is_relative_to(python_installation_path)
)
if self.option("managed") and not is_poetry_managed:
continue
manager = (
"Poetry>" if is_poetry_managed else "System>"
)
path = (
f"{pv.executable.as_posix()}>"
if pv.executable
else "Available for download"
)
table.add_row(
[
f"{version}>",
f"{implementation}>",
f"{manager}",
f"{path}",
]
)
row_count += 1
if row_count > 0:
table.render()
else:
self.io.write_line("No Python installations found.")
return 0
================================================
FILE: src/poetry/console/commands/python/remove.py
================================================
from __future__ import annotations
import shutil
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from cleo.helpers import option
from poetry.core.constraints.version.version import Version
from poetry.core.version.exceptions import InvalidVersionError
from poetry.console.commands.command import Command
from poetry.console.commands.python import get_request_title
from poetry.utils.env.python.providers import PoetryPythonPathProvider
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
from cleo.io.io import IO
class PythonRemoveCommand(Command):
name = "python remove"
arguments: ClassVar[list[Argument]] = [
argument("python", "The python version to remove.", multiple=True)
]
options: ClassVar[list[Option]] = [
option(
"free-threaded", "t", "Use free-threaded version if available.", flag=True
),
option(
"implementation",
"i",
"Python implementation to use. (cpython, pypy)",
flag=False,
default="cpython",
),
]
description = (
"Remove the specified Python version if managed by Poetry."
" (experimental feature)"
)
@staticmethod
def remove_python_installation(
request: str, implementation: str, free_threaded: bool, io: IO
) -> int:
if request.endswith("t"):
free_threaded = True
request = request[:-1]
try:
version = Version.parse(request)
except (ValueError, InvalidVersionError):
io.write_error_line(
f"Invalid Python version requested {request}>"
)
return 1
if version.minor is None or version.patch is None:
io.write_error_line(
f"Invalid Python version requested {request}>\n"
)
io.write_error_line(
"You need to provide an exact Python version in the format X.Y.Z> to be removed.\n\n"
"You can use poetry python list -m> to list installed Poetry managed Python versions."
)
return 1
request_title = get_request_title(request, implementation, free_threaded)
path = PoetryPythonPathProvider.installation_dir(
version, implementation, free_threaded
)
if path.exists():
if io.is_verbose():
io.write_line(f"Installation path: {path}")
io.write(f"Removing installation {request_title} ... ")
try:
shutil.rmtree(path)
except OSError as e:
io.write("Failed>\n")
if io.is_verbose():
io.write_line(f"Failed to remove directory: {e}")
io.write("Done>\n")
else:
io.write_line(f"No installation was found at {path}.")
return 0
def handle(self) -> int:
implementation = self.option("implementation").lower()
free_threaded = self.option("free-threaded")
result = 0
for request in self.argument("python"):
result += self.remove_python_installation(
request, implementation, free_threaded, self.io
)
return result
================================================
FILE: src/poetry/console/commands/remove.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from cleo.helpers import argument
from cleo.helpers import option
from packaging.utils import canonicalize_name
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.dependency_group import MAIN_GROUP
from tomlkit.toml_document import TOMLDocument
from poetry.console.commands.installer_command import InstallerCommand
if TYPE_CHECKING:
from collections.abc import Mapping
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
class RemoveCommand(InstallerCommand):
name = "remove"
description = "Removes a package from the project dependencies."
arguments: ClassVar[list[Argument]] = [
argument("packages", "The packages to remove.", multiple=True)
]
options: ClassVar[list[Option]] = [
option("group", "G", "The group to remove the dependency from.", flag=False),
option(
"dev",
"D",
"Remove a package from the development dependencies."
" (shortcut for '-G dev')",
),
option(
"dry-run",
None,
"Output the operations but do not execute anything "
"(implicitly enables --verbose).",
),
option("lock", None, "Do not perform operations (only update the lockfile)."),
]
help = """The remove command removes a package from the current
list of installed packages
poetry remove"""
loggers: ClassVar[list[str]] = [
"poetry.repositories.pypi_repository",
"poetry.inspection.info",
]
def handle(self) -> int:
packages = self.argument("packages")
if self.option("dev"):
group = "dev"
else:
group = self.option("group", self.default_group)
content: dict[str, Any] = self.poetry.file.read()
project_content = content.get("project", {})
groups_content = content.get("dependency-groups", {})
poetry_content = content.get("tool", {}).get("poetry", {})
poetry_groups_content = poetry_content.get("group", {})
if group is None:
# remove from all groups
removed = set()
group_sections = []
project_dependencies = project_content.get("dependencies", [])
poetry_dependencies = poetry_content.get("dependencies", {})
if project_dependencies or poetry_dependencies:
group_sections.append(
(MAIN_GROUP, project_dependencies, poetry_dependencies)
)
group_sections.extend(
(
group_name,
dependencies,
poetry_groups_content.get(group_name, {}).get("dependencies", {}),
)
for group_name, dependencies in groups_content.items()
)
group_sections.extend(
(group_name, [], group_section.get("dependencies", {}))
for group_name, group_section in poetry_groups_content.items()
if group_name not in groups_content and group_name != MAIN_GROUP
)
for group_name, standard_section, poetry_section in group_sections:
removed |= self._remove_packages(
packages=packages,
standard_section=standard_section,
poetry_section=poetry_section,
group_name=group_name,
)
if group_name != MAIN_GROUP:
if (
not poetry_section
and "dependencies" in poetry_groups_content.get(group_name, {})
):
del poetry_content["group"][group_name]["dependencies"]
if not poetry_content["group"][group_name]:
del poetry_content["group"][group_name]
if not standard_section and group_name in groups_content:
del groups_content[group_name]
if (
group_name not in groups_content
and group_name not in poetry_groups_content
):
self._remove_references_to_group(group_name, content)
elif group == "dev" and "dev-dependencies" in poetry_content:
# We need to account for the old `dev-dependencies` section
removed = self._remove_packages(
packages, [], poetry_content["dev-dependencies"], "dev"
)
if not poetry_content["dev-dependencies"]:
del poetry_content["dev-dependencies"]
else:
removed = set()
if group_content := poetry_groups_content.get(group):
poetry_section = group_content.get("dependencies", {})
removed.update(
self._remove_packages(
packages=packages,
standard_section=[],
poetry_section=poetry_section,
group_name=group,
)
)
if not poetry_section and "dependencies" in group_content:
del group_content["dependencies"]
if not group_content:
del poetry_content["group"][group]
if group in groups_content:
removed.update(
self._remove_packages(
packages=packages,
standard_section=groups_content[group],
poetry_section={},
group_name=group,
)
)
if not groups_content[group]:
del groups_content[group]
if group not in groups_content and group not in poetry_groups_content:
self._remove_references_to_group(group, content)
if "group" in poetry_content and not poetry_content["group"]:
del poetry_content["group"]
if "dependency-groups" in content and not content["dependency-groups"]:
del content["dependency-groups"]
not_found = set(packages).difference(removed)
if not_found:
raise ValueError(
"The following packages were not found: " + ", ".join(sorted(not_found))
)
# Refresh the locker
self.poetry.locker.set_pyproject_data(content)
self.installer.set_locker(self.poetry.locker)
self.installer.set_package(self.poetry.package)
self.installer.dry_run(self.option("dry-run", False))
self.installer.verbose(self.io.is_verbose())
self.installer.update(True)
self.installer.execute_operations(not self.option("lock"))
self.installer.whitelist(removed)
status = self.installer.run()
if not self.option("dry-run") and status == 0:
assert isinstance(content, TOMLDocument)
self.poetry.file.write(content)
return status
def _remove_packages(
self,
packages: list[str],
standard_section: list[str | Mapping[str, Any]],
poetry_section: dict[str, Any],
group_name: str,
) -> set[str]:
removed = set()
group = self.poetry.package.dependency_group(group_name)
for package in packages:
normalized_name = canonicalize_name(package)
for requirement in standard_section.copy():
if not isinstance(requirement, str):
continue
if Dependency.create_from_pep_508(requirement).name == normalized_name:
standard_section.remove(requirement)
removed.add(package)
for existing_package in list(poetry_section):
if canonicalize_name(existing_package) == normalized_name:
del poetry_section[existing_package]
removed.add(package)
for package in removed:
group.remove_dependency(package)
return removed
def _remove_references_to_group(
self, group_name: str, content: dict[str, Any]
) -> None:
"""
Removes references to the given group from other groups.
"""
# 1. PEP 735: [dependency-groups]
if "dependency-groups" in content:
groups_to_remove = []
for group_key, group_content in content["dependency-groups"].items():
if not isinstance(group_content, list):
continue
to_remove = []
for item in group_content:
if (
isinstance(item, dict)
and item.get("include-group") == group_name
):
to_remove.append(item)
for item in to_remove:
group_content.remove(item)
# Clean up now-empty lists (normalize with legacy behavior)
# Only remove groups that became empty due to include-group cleanup,
# not the target group itself (which is handled by the caller)
if not group_content and group_key != group_name:
groups_to_remove.append(group_key)
for group_key in groups_to_remove:
del content["dependency-groups"][group_key]
# 2. Legacy: [tool.poetry.group.] include-groups = [...]
poetry_content = content.get("tool", {}).get("poetry", {})
if "group" in poetry_content:
groups_to_remove = []
for group_key, group_content in poetry_content["group"].items():
if "include-groups" not in group_content:
continue
if group_name in group_content["include-groups"]:
group_content["include-groups"].remove(group_name)
if not group_content["include-groups"]:
del group_content["include-groups"]
if not group_content:
groups_to_remove.append(group_key)
for group_key in groups_to_remove:
del poetry_content["group"][group_key]
================================================
FILE: src/poetry/console/commands/run.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from poetry.console.commands.env_command import EnvCommand
from poetry.utils._compat import WINDOWS
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from poetry.core.masonry.utils.module import Module
class RunCommand(EnvCommand):
name = "run"
description = "Runs a command in the appropriate environment."
arguments: ClassVar[list[Argument]] = [
argument("args", "The command and arguments/options to run.", multiple=True)
]
def handle(self) -> int:
args = self.argument("args")
script = args[0]
scripts = self.poetry.local_config.get("scripts")
if scripts and script in scripts:
return self.run_script(scripts[script], args)
try:
return self.env.execute(*args)
except FileNotFoundError:
self.line_error(f"Command not found: {script}")
return 1
@property
def _module(self) -> Module:
from poetry.core.masonry.utils.module import Module
poetry = self.poetry
package = poetry.package
path = poetry.file.path.parent
module = Module(package.name, path.as_posix(), package.packages)
return module
def run_script(self, script: str | dict[str, str], args: list[str]) -> int:
"""Runs an entry point script defined in the section ``[tool.poetry.scripts]``.
When a script exists in the venv bin folder, i.e. after ``poetry install``,
then ``sys.argv[0]`` must be set to the full path of the executable, so
``poetry run foo`` and ``poetry shell``, ``foo`` have the same ``sys.argv[0]``
that points to the full path.
Otherwise (when an entry point script does not exist), ``sys.argv[0]`` is the
script name only, i.e. ``poetry run foo`` has ``sys.argv == ['foo']``.
"""
for script_dir in self.env.script_dirs:
script_path = script_dir / args[0]
if WINDOWS:
script_path = script_path.with_suffix(".cmd")
if script_path.exists():
args = [str(script_path), *args[1:]]
break
else:
# If we reach this point, the script is not installed
self._warning_not_installed_script(args[0])
if isinstance(script, dict):
script = script["callable"]
module, callable_ = script.split(":")
src_in_sys_path = "sys.path.append('src'); " if self._module.is_in_src() else ""
cmd = ["python", "-c"]
cmd += [
"import sys; "
"from importlib import import_module; "
f"sys.argv = {args!r}; {src_in_sys_path}"
f"sys.exit(import_module('{module}').{callable_}())"
]
return self.env.execute(*cmd)
def _warning_not_installed_script(self, script: str) -> None:
message = f"""\
Warning: '{script}' is an entry point defined in pyproject.toml, but it's not \
installed as a script. You may get improper `sys.argv[0]`.
The support to run uninstalled scripts will be removed in a future release.
Run `poetry install` to resolve and get rid of this message.
"""
self.line_error(message, style="warning")
================================================
FILE: src/poetry/console/commands/search.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from poetry.console.commands.command import Command
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
class SearchCommand(Command):
name = "search"
description = "Searches for packages on remote repositories."
arguments: ClassVar[list[Argument]] = [
argument("tokens", "The tokens to search for.", multiple=True)
]
def handle(self) -> int:
seen = set()
table = self.table(style="compact")
table.set_headers(
["Package>", "Version>", "Source>", "Description>"]
)
rows = []
for repository in self.poetry.pool.repositories:
for result in repository.search(self.argument("tokens")):
key = f"{repository.name}::{result.pretty_string}"
if key in seen:
continue
seen.add(key)
rows.append((result, repository.name))
if not rows:
self.line("No matching packages were found.>")
return 0
for package, source in sorted(
rows, key=lambda x: (x[0].name, x[0].version, x[1])
):
table.add_row(
[
f"{package.name}>",
f"{package.version}",
f"{source}>",
str(package.description),
]
)
table.render()
return 0
================================================
FILE: src/poetry/console/commands/self/__init__.py
================================================
================================================
FILE: src/poetry/console/commands/self/add.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from poetry.core.constraints.version import Version
from poetry.__version__ import __version__
from poetry.console.commands.add import AddCommand
from poetry.console.commands.self.self_command import SelfCommand
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
class SelfAddCommand(SelfCommand, AddCommand):
name = "self add"
description = "Add additional packages to Poetry's runtime environment."
options: ClassVar[list[Option]] = [
o
for o in AddCommand.options
if o.name in {"editable", "extras", "source", "dry-run", "allow-prereleases"}
]
help = f"""\
The self add command installs additional packages to Poetry's runtime \
environment.
This is managed in the {SelfCommand.get_default_system_pyproject_file()}> \
file.
{AddCommand.examples}
"""
@property
def _hint_update_packages(self) -> str:
version = Version.parse(__version__)
flags = ""
if not version.is_stable():
flags = " --preview"
return (
"\nIf you want to update it to the latest compatible version, you can use"
f" `poetry self update{flags}`.\nIf you prefer to upgrade it to the latest"
" available version, you can use `poetry self add package@latest`.\n"
)
================================================
FILE: src/poetry/console/commands/self/install.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from packaging.utils import NormalizedName
from packaging.utils import canonicalize_name
from poetry.core.packages.dependency_group import MAIN_GROUP
from poetry.console.commands.install import InstallCommand
from poetry.console.commands.self.self_command import SelfCommand
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
class SelfInstallCommand(SelfCommand, InstallCommand):
name = "self install"
description = (
"Install locked packages (incl. addons) required by this Poetry installation."
)
options: ClassVar[list[Option]] = [
o for o in InstallCommand.options if o.name in {"sync", "dry-run"}
]
help = f"""\
The self install command ensures all additional packages specified are \
installed in the current runtime environment.
This is managed in the {SelfCommand.get_default_system_pyproject_file()}> \
file.
You can add more packages using the self add command and remove them using \
the self remove command.
"""
@property
def activated_groups(self) -> set[NormalizedName]:
return {MAIN_GROUP, canonicalize_name(self.default_group)}
@property
def _alternative_sync_command(self) -> str:
return "poetry self sync"
================================================
FILE: src/poetry/console/commands/self/lock.py
================================================
from __future__ import annotations
from poetry.console.commands.lock import LockCommand
from poetry.console.commands.self.self_command import SelfCommand
class SelfLockCommand(SelfCommand, LockCommand):
name = "self lock"
description = "Lock the Poetry installation's system requirements."
help = f"""\
The self lock command reads this Poetry installation's system requirements as \
specified in the {SelfCommand.get_default_system_pyproject_file()}> file.
The system dependencies are locked in the \
{SelfCommand.get_default_system_pyproject_file().parent.joinpath("poetry.lock")}> \
file.
"""
================================================
FILE: src/poetry/console/commands/self/remove.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from poetry.console.commands.remove import RemoveCommand
from poetry.console.commands.self.self_command import SelfCommand
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
class SelfRemoveCommand(SelfCommand, RemoveCommand):
name = "self remove"
description = "Remove additional packages from Poetry's runtime environment."
options: ClassVar[list[Option]] = [
o for o in RemoveCommand.options if o.name in {"dry-run"}
]
help = f"""\
The self remove command removes additional package's to Poetry's runtime \
environment.
This is managed in the {SelfCommand.get_default_system_pyproject_file()}> \
file.
"""
================================================
FILE: src/poetry/console/commands/self/self_command.py
================================================
from __future__ import annotations
import typing
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from packaging.utils import canonicalize_name
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.project_package import ProjectPackage
from poetry.__version__ import __version__
from poetry.console.commands.installer_command import InstallerCommand
from poetry.factory import Factory
from poetry.pyproject.toml import PyProjectTOML
from poetry.utils.constants import POETRY_SYSTEM_PROJECT_NAME
from poetry.utils.env import EnvManager
from poetry.utils.env import SystemEnv
from poetry.utils.helpers import directory
if TYPE_CHECKING:
from packaging.utils import NormalizedName
from poetry.poetry import Poetry
from poetry.utils.env import Env
class SelfCommand(InstallerCommand):
ADDITIONAL_PACKAGE_GROUP = canonicalize_name("additional")
@staticmethod
def get_default_system_pyproject_file() -> Path:
# We separate this out to avoid unwanted side effect during testing while
# maintaining dynamic use in help text.
#
# This is not ideal, but is the simplest solution for now.
from poetry.locations import CONFIG_DIR
return Path(CONFIG_DIR).joinpath("pyproject.toml")
@property
def system_pyproject(self) -> Path:
file = self.get_default_system_pyproject_file()
file.parent.mkdir(parents=True, exist_ok=True)
return file
def reset_env(self) -> None:
self._env = EnvManager.get_system_env(naive=True)
@property
def env(self) -> Env:
if not isinstance(self._env, SystemEnv):
self.reset_env()
assert self._env is not None
return self._env
@property
def default_group(self) -> str:
return self.ADDITIONAL_PACKAGE_GROUP
@property
def activated_groups(self) -> set[NormalizedName]:
return {canonicalize_name(self.default_group)}
def generate_system_pyproject(self) -> None:
preserved = {}
preserved_groups: dict[str, Any] = {}
if self.system_pyproject.exists():
toml_file = PyProjectTOML(self.system_pyproject)
content = toml_file.data
for key in {"group", "source"}:
if key in toml_file.poetry_config:
preserved[key] = toml_file.poetry_config[key]
if "dependency-groups" in content:
preserved_groups = typing.cast(
"dict[str, Any]", content["dependency-groups"]
)
package = ProjectPackage(name=POETRY_SYSTEM_PROJECT_NAME, version=__version__)
package.add_dependency(Dependency(name="poetry", constraint=f"{__version__}"))
package.python_versions = ".".join(str(v) for v in self.env.version_info[:3])
content = Factory.create_legacy_pyproject_from_package(package=package)
content["tool"]["poetry"]["package-mode"] = False # type: ignore[index]
for key in preserved:
content["tool"]["poetry"][key] = preserved[key] # type: ignore[index]
if preserved_groups:
content["dependency-groups"] = preserved_groups
pyproject = PyProjectTOML(self.system_pyproject)
pyproject.file.write(content)
def reset_poetry(self) -> None:
with directory(self.system_pyproject.parent):
self.generate_system_pyproject()
self._poetry = Factory().create_poetry(
self.system_pyproject.parent, io=self.io, disable_plugins=True
)
@property
def poetry(self) -> Poetry:
if self._poetry is None:
self.reset_poetry()
assert self._poetry is not None
return self._poetry
def _system_project_handle(self) -> int:
"""
This is a helper method that by default calls the handle method implemented in
the child class's next MRO sibling. Override this if you want special handling
either before calling the handle() from the super class or have custom logic
to handle the command.
The default implementations handles cases where a `self` command delegates
handling to an existing command. Eg: `SelfAddCommand(SelfCommand, AddCommand)`.
"""
return_code: int = super().handle()
return return_code
def reset(self) -> None:
"""
Reset current command instance's environment and poetry instances to ensure
use of the system specific ones.
"""
self.reset_env()
self.reset_poetry()
def handle(self) -> int:
# We override the base class's handle() method to ensure that poetry and env
# are reset to work within the system project instead of current context.
# Further, during execution, the working directory is temporarily changed
# to parent directory of Poetry system pyproject.toml file.
#
# This method **should not** be overridden in child classes as it may have
# unexpected consequences.
self.reset()
with directory(self.system_pyproject.parent):
return self._system_project_handle()
================================================
FILE: src/poetry/console/commands/self/show/__init__.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import option
from poetry.console.commands.self.self_command import SelfCommand
from poetry.console.commands.show import ShowCommand
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
from packaging.utils import NormalizedName
class SelfShowCommand(SelfCommand, ShowCommand):
name = "self show"
options: ClassVar[list[Option]] = [
option("addons", None, "List only add-on packages installed."),
*[
o
for o in ShowCommand.options
if o.name in {"tree", "latest", "outdated", "format"}
],
]
description = "Show packages from Poetry's runtime environment."
help = f"""\
The self show command behaves similar to the show command, but
working within Poetry's runtime environment. This lists all packages installed within
the Poetry install environment.
To show only additional packages that have been added via self add and their
dependencies use self show --addons.
This is managed in the {SelfCommand.get_default_system_pyproject_file()}> \
file.
"""
@property
def activated_groups(self) -> set[NormalizedName]:
if self.option("addons", False):
return {SelfCommand.ADDITIONAL_PACKAGE_GROUP}
return super(ShowCommand, self).activated_groups
================================================
FILE: src/poetry/console/commands/self/show/plugins.py
================================================
from __future__ import annotations
import dataclasses
from typing import TYPE_CHECKING
from poetry.console.commands.self.self_command import SelfCommand
if TYPE_CHECKING:
from importlib import metadata
from poetry.core.packages.package import Package
@dataclasses.dataclass
class PluginPackage:
package: Package
plugins: list[metadata.EntryPoint] = dataclasses.field(default_factory=list)
application_plugins: list[metadata.EntryPoint] = dataclasses.field(
default_factory=list
)
def append(self, entry_point: metadata.EntryPoint) -> None:
from poetry.plugins.application_plugin import ApplicationPlugin
from poetry.plugins.plugin import Plugin
group = entry_point.group
if group == ApplicationPlugin.group:
self.application_plugins.append(entry_point)
elif group == Plugin.group:
self.plugins.append(entry_point)
else:
name = entry_point.name
raise ValueError(f"Unknown plugin group ({group}) for {name}")
class SelfShowPluginsCommand(SelfCommand):
name = "self show plugins"
description = "Shows information about the currently installed plugins."
help = """\
The self show plugins command lists all installed Poetry plugins.
Plugins can be added and removed using the self add and self remove \
commands respectively.
This command does not list packages that do not provide a Poetry plugin.>
"""
def _system_project_handle(self) -> int:
from packaging.utils import canonicalize_name
from poetry.plugins.application_plugin import ApplicationPlugin
from poetry.plugins.plugin import Plugin
from poetry.plugins.plugin_manager import PluginManager
from poetry.repositories.installed_repository import InstalledRepository
from poetry.utils.env import EnvManager
from poetry.utils.helpers import pluralize
plugins: dict[str, PluginPackage] = {}
system_env = EnvManager.get_system_env(naive=True)
installed_repository = InstalledRepository.load(
system_env, with_dependencies=True
)
packages_by_name: dict[str, Package] = {
pkg.name: pkg for pkg in installed_repository.packages
}
for group in [ApplicationPlugin.group, Plugin.group]:
for entry_point in PluginManager(group).get_plugin_entry_points():
assert entry_point.dist is not None
package = packages_by_name[canonicalize_name(entry_point.dist.name)]
name = package.pretty_name
info = plugins.get(name) or PluginPackage(package=package)
info.append(entry_point)
plugins[name] = info
for name, info in plugins.items():
package = info.package
description = " " + package.description if package.description else ""
self.line("")
self.line(f" - {name} ({package.version}){description}")
provide_line = " "
if info.plugins:
count = len(info.plugins)
provide_line += f" {count} plugin{pluralize(count)}"
if info.application_plugins:
if info.plugins:
provide_line += " and"
count = len(info.application_plugins)
provide_line += (
f" {count} application plugin{pluralize(count)}"
)
self.line(provide_line)
if package.requires:
self.line("")
self.line(" Dependencies")
for dependency in package.requires:
self.line(
f" - {dependency.pretty_name}"
f" ({dependency.pretty_constraint})"
)
return 0
================================================
FILE: src/poetry/console/commands/self/sync.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from poetry.console.commands.self.install import SelfInstallCommand
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
class SelfSyncCommand(SelfInstallCommand):
name = "self sync"
description = (
"Sync Poetry's own environment according to the locked packages (incl. addons)"
" required by this Poetry installation."
)
options: ClassVar[list[Option]] = [
opt for opt in SelfInstallCommand.options if opt.name != "sync"
]
help = f"""\
The self sync command ensures all additional (and no other) packages \
specified are installed in the current runtime environment.
This is managed in the \
{SelfInstallCommand.get_default_system_pyproject_file()}> file.
You can add more packages using the self add command and remove them using \
the self remove command.
"""
@property
def _with_synchronization(self) -> bool:
return True
================================================
FILE: src/poetry/console/commands/self/update.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from cleo.helpers import option
from cleo.io.inputs.string_input import StringInput
from cleo.io.io import IO
from poetry.console.commands.add import AddCommand
from poetry.console.commands.self.self_command import SelfCommand
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
class SelfUpdateCommand(SelfCommand):
name = "self update"
description = "Updates Poetry to the latest version."
arguments: ClassVar[list[Argument]] = [
argument(
"version", "The version to update to.", optional=True, default="latest"
)
]
options: ClassVar[list[Option]] = [
option("preview", None, "Allow the installation of pre-release versions."),
option(
"dry-run",
None,
"Output the operations but do not execute anything "
"(implicitly enables --verbose).",
),
]
help = """\
The self update command updates Poetry version in its current runtime \
environment.
"""
def _system_project_handle(self) -> int:
self.write("Updating Poetry version ...\n\n")
application = self.get_application()
add_command = application.find("add")
assert isinstance(add_command, AddCommand)
add_command.set_env(self.env)
add_command.set_poetry(self.poetry)
application.configure_installer_for_command(add_command, self.io)
argv = ["add", f"poetry@{self.argument('version')}"]
if self.option("dry-run"):
argv.append("--dry-run")
if self.option("preview"):
argv.append("--allow-prereleases")
exit_code: int = add_command.run(
IO(
StringInput(" ".join(argv)),
self.io.output,
self.io.error_output,
)
)
return exit_code
================================================
FILE: src/poetry/console/commands/show.py
================================================
from __future__ import annotations
import json
import sys
from enum import Enum
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from cleo.helpers import option
from packaging.utils import canonicalize_name
from poetry.console.commands.env_command import EnvCommand
from poetry.console.commands.group_command import GroupCommand
from poetry.utils.constants import POETRY_SYSTEM_PROJECT_NAME
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
from cleo.io.io import IO
from cleo.ui.table import Rows
from packaging.utils import NormalizedName
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package
from poetry.core.packages.project_package import ProjectPackage
from poetry.repositories.repository import Repository
def reverse_deps(pkg: Package, repo: Repository) -> dict[str, str]:
required_by = {}
for locked in repo.packages:
dependencies = {d.name: d.pretty_constraint for d in locked.requires}
if pkg.name in dependencies:
required_by[locked.pretty_name] = dependencies[pkg.name]
return required_by
class OutputFormats(str, Enum):
JSON = "json"
TEXT = "text"
class ShowCommand(GroupCommand, EnvCommand):
name = "show"
description = "Shows information about packages."
arguments: ClassVar[list[Argument]] = [
argument("package", "The package to inspect", optional=True)
]
options: ClassVar[list[Option]] = [
*GroupCommand._group_dependency_options(),
option("tree", "t", "List the dependencies as a tree."),
option(
"why",
None,
"When showing the full list, or a --tree for a single package,"
" display whether they are a direct dependency or required by other"
" packages",
),
option("latest", "l", "Show the latest version."),
option(
"outdated",
"o",
"Show the latest version but only for packages that are outdated.",
),
option(
"all",
"a",
"Show all packages (even those not compatible with current system).",
),
option("top-level", "T", "Show only top-level dependencies."),
option(
"no-truncate",
None,
"Do not truncate the output based on the terminal width.",
),
option(
"format",
"f",
"Specify the output format (`json` or `text`). Default is `text`. `json` cannot be combined with the --tree option.",
flag=False,
default="text",
),
]
help = """The show command displays detailed information about a package, or
lists all packages available."""
colors: ClassVar[list[str]] = ["cyan", "yellow", "green", "magenta", "blue"]
def handle(self) -> int:
package = self.argument("package")
if self.option("tree"):
self.init_styles(self.io)
if self.option("top-level"):
if self.option("tree"):
self.line_error(
"Error: Cannot use --tree and --top-level at the same"
" time."
)
return 1
if package is not None:
self.line_error(
"Error: Cannot use --top-level when displaying a single"
" package."
)
return 1
if self.option("why"):
if self.option("tree") and package is None:
self.line_error(
"Error: --why requires a package when combined with"
" --tree."
)
return 1
if not self.option("tree") and package:
self.line_error(
"Error: --why cannot be used without --tree when displaying"
" a single package."
)
return 1
if self.option("format") not in set(OutputFormats):
self.line_error(
f"Error: Invalid output format. Supported formats are: {', '.join(OutputFormats)}."
)
return 1
if self.option("format") != OutputFormats.TEXT and self.option("tree"):
self.line_error(
"Error: --tree option can only be used with the text output option."
)
return 1
if self.option("outdated"):
self.io.input.set_option("latest", True)
if not self.poetry.locker.is_locked():
self.line_error(
f"Error: poetry.lock not found. Run `{self._lock_create_command()}`"
" to create it."
)
return 1
locked_repo = self.poetry.locker.locked_repository()
if package:
return self._display_single_package_information(package, locked_repo)
root = self.project_with_activated_groups_only()
# Show tree view if requested
if self.option("tree"):
return self._display_packages_tree_information(locked_repo, root)
return self._display_packages_information(locked_repo, root)
def _lock_create_command(self) -> str:
if self.poetry.package.name == POETRY_SYSTEM_PROJECT_NAME:
return "poetry self lock"
return "poetry lock"
def _display_single_package_information(
self, package: str, locked_repository: Repository
) -> int:
locked_packages = locked_repository.packages
canonicalized_package = canonicalize_name(package)
pkg = None
for locked in locked_packages:
if locked.name == canonicalized_package:
pkg = locked
break
if not pkg:
raise ValueError(f"Package {package} not found")
required_by = reverse_deps(pkg, locked_repository)
if self.option("tree"):
if self.option("why"):
# The default case if there's no reverse dependencies is to query
# the subtree for pkg but if any rev-deps exist we'll query for each
# of them in turn
packages = [pkg]
if required_by:
packages = [
p for p in locked_packages for r in required_by if p.name == r
]
else:
# if no rev-deps exist we'll make this clear as it can otherwise
# look very odd for packages that also have no or few direct
# dependencies
self.io.write_line(f"Package {package} is a direct dependency.")
for p in packages:
self.display_package_tree(
self.io, p, locked_packages, why_package=pkg
)
else:
self.display_package_tree(self.io, pkg, locked_packages)
return 0
if self.option("format") == OutputFormats.JSON:
package_info: dict[str, str | dict[str, str]] = {
"name": pkg.pretty_name,
"version": pkg.pretty_version,
"description": pkg.description,
}
if pkg.requires:
package_info["dependencies"] = {
dependency.pretty_name: dependency.pretty_constraint
for dependency in pkg.requires
}
if required_by:
package_info["required_by"] = required_by
self.line(json.dumps(package_info))
return 0
rows: Rows = [
["name>", f" : {pkg.pretty_name}>"],
["version>", f" : {pkg.pretty_version}"],
["description>", f" : {pkg.description}"],
]
self.table(rows=rows, style="compact").render()
if pkg.requires:
self.line("")
self.line("dependencies")
for dependency in pkg.requires:
self.line(
f" - {dependency.pretty_name}"
f" {dependency.pretty_constraint}"
)
if required_by:
self.line("")
self.line("required by")
for parent, requires_version in required_by.items():
self.line(f" - {parent} requires {requires_version}")
return 0
def _display_packages_information(
self, locked_repository: Repository, root: ProjectPackage
) -> int:
import shutil
from cleo.io.null_io import NullIO
from poetry.puzzle.solver import Solver
from poetry.repositories.installed_repository import InstalledRepository
from poetry.repositories.repository_pool import RepositoryPool
from poetry.utils.helpers import get_package_version_display_string
locked_packages = locked_repository.packages
pool = RepositoryPool.from_packages(locked_packages, self.poetry.config)
solver = Solver(
root,
pool=pool,
installed=[],
locked=locked_packages,
io=NullIO(),
)
solver.provider.load_deferred(False)
with solver.use_environment(self.env):
ops = solver.solve().calculate_operations()
required_locked_packages = {op.package for op in ops if not op.skipped}
show_latest = self.option("latest")
show_all = self.option("all")
show_top_level = self.option("top-level")
show_why = self.option("why")
width = (
sys.maxsize
if self.option("no-truncate")
else shutil.get_terminal_size().columns
)
name_length = version_length = latest_length = required_by_length = 0
latest_packages = {}
latest_statuses = {}
installed_repo = InstalledRepository.load(self.env)
requires = root.all_requires
# Computing widths
for locked in locked_packages:
if locked not in required_locked_packages and not show_all:
continue
current_length = len(locked.pretty_name)
if not self.io.output.is_decorated():
installed_status = self.get_installed_status(
locked, installed_repo.packages
)
if installed_status == "not-installed":
current_length += 4
if show_latest:
latest = self.find_latest_package(locked, root)
if not latest:
latest = locked
latest_packages[locked.pretty_name] = latest
update_status = latest_statuses[locked.pretty_name] = (
self.get_update_status(latest, locked)
)
if not self.option("outdated") or update_status != "up-to-date":
name_length = max(name_length, current_length)
version_length = max(
version_length,
len(
get_package_version_display_string(
locked, root=self.poetry.file.path.parent
)
),
)
latest_length = max(
latest_length,
len(
get_package_version_display_string(
latest, root=self.poetry.file.path.parent
)
),
)
if show_why:
required_by = reverse_deps(locked, locked_repository)
required_by_length = max(
required_by_length,
len(" from " + ",".join(required_by.keys())),
)
else:
name_length = max(name_length, current_length)
version_length = max(
version_length,
len(
get_package_version_display_string(
locked, root=self.poetry.file.path.parent
)
),
)
if show_why:
required_by = reverse_deps(locked, locked_repository)
required_by_length = max(
required_by_length, len(" from " + ",".join(required_by.keys()))
)
if self.option("format") == OutputFormats.JSON:
packages = []
for locked in locked_packages:
if locked not in required_locked_packages and not show_all:
continue
if (
show_latest
and self.option("outdated")
and latest_statuses[locked.pretty_name] == "up-to-date"
):
continue
if show_top_level and not any(locked.satisfies(r) for r in requires):
continue
package: dict[str, str | list[str]] = {}
package["name"] = locked.pretty_name
package["installed_status"] = self.get_installed_status(
locked, installed_repo.packages
)
package["version"] = get_package_version_display_string(
locked, root=self.poetry.file.path.parent
)
if show_latest:
latest = latest_packages[locked.pretty_name]
package["latest_version"] = get_package_version_display_string(
latest, root=self.poetry.file.path.parent
)
if show_why:
required_by = reverse_deps(locked, locked_repository)
if required_by:
package["required_by"] = list(required_by.keys())
package["description"] = locked.description
packages.append(package)
self.line(json.dumps(packages))
return 0
write_version = name_length + version_length + 3 <= width
write_latest = name_length + version_length + latest_length + 3 <= width
why_end_column = (
name_length + version_length + latest_length + required_by_length
)
write_why = show_why and (why_end_column + 3) <= width
write_description = (why_end_column + 24) <= width
for locked in locked_packages:
color = "cyan"
name = locked.pretty_name
install_marker = ""
if show_top_level and not any(locked.satisfies(r) for r in requires):
continue
if locked not in required_locked_packages:
if not show_all:
continue
color = "black;options=bold"
else:
installed_status = self.get_installed_status(
locked, installed_repo.packages
)
if installed_status == "not-installed":
color = "red"
if not self.io.output.is_decorated():
# Non installed in non decorated mode
install_marker = " (!)"
if (
show_latest
and self.option("outdated")
and latest_statuses[locked.pretty_name] == "up-to-date"
):
continue
line = (
f""
f"{name:{name_length - len(install_marker)}}{install_marker}>"
)
if write_version:
version = get_package_version_display_string(
locked, root=self.poetry.file.path.parent
)
line += f" {version:{version_length}}"
if show_latest:
latest = latest_packages[locked.pretty_name]
update_status = latest_statuses[locked.pretty_name]
if write_latest:
color = "green"
if update_status == "semver-safe-update":
color = "red"
elif update_status == "update-possible":
color = "yellow"
version = get_package_version_display_string(
latest, root=self.poetry.file.path.parent
)
line += f" {version:{latest_length}}>"
if write_why:
required_by = reverse_deps(locked, locked_repository)
if required_by:
content = ",".join(required_by.keys())
# subtract 6 for ' from '
line += f" from {content:{required_by_length - 6}}"
else:
line += " " * required_by_length
if write_description:
description = locked.description
remaining = (
width - name_length - version_length - required_by_length - 4
)
if show_latest:
remaining -= latest_length
if len(locked.description) > remaining:
description = description[: remaining - 3] + "..."
line += " " + description
self.line(line)
return 0
def _display_packages_tree_information(
self, locked_repository: Repository, root: ProjectPackage
) -> int:
packages = locked_repository.packages
for p in packages:
for require in root.all_requires:
if p.name == require.name:
self.display_package_tree(self.io, p, packages)
break
return 0
def display_package_tree(
self,
io: IO,
package: Package,
installed_packages: list[Package],
why_package: Package | None = None,
) -> None:
io.write(f"{package.pretty_name}")
description = ""
if package.description:
description = " " + package.description
io.write_line(f" {package.pretty_version}{description}")
if why_package is not None:
dependencies = [p for p in package.requires if p.name == why_package.name]
else:
dependencies = package.requires
dependencies = sorted(
dependencies,
key=lambda x: x.name,
)
tree_bar = "├"
total = len(dependencies)
for i, dependency in enumerate(dependencies, 1):
if i == total:
tree_bar = "└"
level = 1
color = self.colors[level]
info = (
f"{tree_bar}── <{color}>{dependency.name}{color}>"
f" {dependency.pretty_constraint}"
)
self._write_tree_line(io, info)
tree_bar = tree_bar.replace("└", " ")
packages_in_tree = [package.name, dependency.name]
self._display_tree(
io,
dependency,
installed_packages,
packages_in_tree,
tree_bar,
level + 1,
)
def _display_tree(
self,
io: IO,
dependency: Dependency,
installed_packages: list[Package],
packages_in_tree: list[NormalizedName],
previous_tree_bar: str = "├",
level: int = 1,
) -> None:
previous_tree_bar = previous_tree_bar.replace("├", "│")
dependencies = []
for package in installed_packages:
if package.name == dependency.name:
dependencies = package.requires
break
dependencies = sorted(
dependencies,
key=lambda x: x.name,
)
tree_bar = previous_tree_bar + " ├"
total = len(dependencies)
for i, dependency in enumerate(dependencies, 1):
current_tree = packages_in_tree
if i == total:
tree_bar = previous_tree_bar + " └"
color_ident = level % len(self.colors)
color = self.colors[color_ident]
circular_warn = ""
if dependency.name in current_tree:
circular_warn = "(circular dependency aborted here)"
info = (
f"{tree_bar}── <{color}>{dependency.name}{color}>"
f" {dependency.pretty_constraint} {circular_warn}"
)
self._write_tree_line(io, info)
tree_bar = tree_bar.replace("└", " ")
if dependency.name not in current_tree:
current_tree.append(dependency.name)
self._display_tree(
io,
dependency,
installed_packages,
current_tree,
tree_bar,
level + 1,
)
def _write_tree_line(self, io: IO, line: str) -> None:
if not io.output.supports_utf8():
line = line.replace("└", "`-")
line = line.replace("├", "|-")
line = line.replace("──", "-")
line = line.replace("│", "|")
io.write_line(line)
def init_styles(self, io: IO) -> None:
from cleo.formatters.style import Style
for color in self.colors:
style = Style(color)
io.output.formatter.set_style(color, style)
io.error_output.formatter.set_style(color, style)
def find_latest_package(
self, package: Package, root: ProjectPackage
) -> Package | None:
from cleo.io.null_io import NullIO
from poetry.puzzle.provider import Provider
from poetry.version.version_selector import VersionSelector
# find the latest version allowed in this pool
requires = root.all_requires
if package.is_direct_origin():
for dep in requires:
if dep.name == package.name and dep.source_type == package.source_type:
provider = Provider(root, self.poetry.pool, NullIO())
return provider.search_for_direct_origin_dependency(dep)
allow_prereleases: bool | None = None
for dep in requires:
if dep.name == package.name:
allow_prereleases = dep.allows_prereleases()
break
name = package.name
selector = VersionSelector(self.poetry.pool)
return selector.find_best_candidate(
name, f">={package.pretty_version}", allow_prereleases
)
def get_update_status(self, latest: Package, package: Package) -> str:
from poetry.core.constraints.version import parse_constraint
if latest.full_pretty_version == package.full_pretty_version:
return "up-to-date"
constraint = parse_constraint("^" + package.pretty_version)
if constraint.allows(latest.version):
# It needs an immediate semver-compliant upgrade
return "semver-safe-update"
# it needs an upgrade but has potential BC breaks so is not urgent
return "update-possible"
def get_installed_status(
self, locked: Package, installed_packages: list[Package]
) -> str:
for package in installed_packages:
if locked.name == package.name:
return "installed"
return "not-installed"
================================================
FILE: src/poetry/console/commands/source/__init__.py
================================================
================================================
FILE: src/poetry/console/commands/source/add.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from cleo.helpers import argument
from cleo.helpers import option
from cleo.io.null_io import NullIO
from tomlkit import table
from tomlkit.items import AoT
from poetry.config.source import Source
from poetry.console.commands.command import Command
from poetry.repositories.repository_pool import Priority
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
class SourceAddCommand(Command):
name = "source add"
description = "Add source configuration for project."
arguments: ClassVar[list[Argument]] = [
argument(
"name",
"Source repository name.",
),
argument(
"url",
"Source repository URL."
" Required, except for PyPI, for which it is not allowed.",
optional=True,
),
]
options: ClassVar[list[Option]] = [
option(
"priority",
"p",
"Set the priority of this source. One of:"
f" {', '.join(p.name.lower() for p in Priority)}. Defaults to"
f" {Priority.PRIMARY.name.lower()}, but will switch to "
f"{Priority.SUPPLEMENTAL.name.lower()} in a later release.",
flag=False,
),
]
def handle(self) -> int:
from poetry.factory import Factory
name: str = self.argument("name")
lower_name = name.lower()
url: str = self.argument("url")
priority_str: str | None = self.option("priority", None)
if lower_name == "pypi":
name = "PyPI"
if url:
self.line_error(
"The URL of PyPI is fixed and cannot be set."
)
return 1
elif not url:
self.line_error(
"A custom source cannot be added without a URL."
)
return 1
if priority_str is None:
self.io.write_error_line(
f"The default priority will change to {Priority.SUPPLEMENTAL.name.lower()}> "
f"in a future release.>"
)
priority = Priority.PRIMARY
else:
priority = Priority[priority_str.upper()]
sources = AoT([])
new_source = Source(name=name, url=url, priority=priority)
is_new_source = True
for source in self.poetry.get_sources():
if source.name.lower() == lower_name:
source = new_source
is_new_source = False
sources.append(source.to_toml_table())
if is_new_source:
self.line(f"Adding source with name {name}.")
sources.append(new_source.to_toml_table())
else:
self.line(f"Source with name {name} already exists. Updating.")
# ensure new source is valid. eg: invalid name etc.
try:
pool = Factory.create_pool(self.poetry.config, sources, NullIO())
pool.repository(name)
except ValueError as e:
self.line_error(
f"Failed to validate addition of {name}: {e}"
)
return 1
# tomlkit types are awkward to work with, treat content as a mostly untyped
# dictionary.
content: dict[str, Any] = self.poetry.pyproject.data
if "tool" not in content:
content["tool"] = table()
if "poetry" not in content["tool"]:
content["tool"]["poetry"] = table()
self.poetry.pyproject.poetry_config["source"] = sources
self.poetry.pyproject.save()
return 0
================================================
FILE: src/poetry/console/commands/source/remove.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from tomlkit.items import AoT
from poetry.console.commands.command import Command
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
class SourceRemoveCommand(Command):
name = "source remove"
description = "Remove source configured for the project."
arguments: ClassVar[list[Argument]] = [
argument(
"name",
"Source repository name.",
),
]
def handle(self) -> int:
name = self.argument("name")
lower_name = name.lower()
sources = AoT([])
removed = False
for source in self.poetry.get_sources():
if source.name.lower() == lower_name:
self.line(f"Removing source with name {source.name}.")
removed = True
continue
sources.append(source.to_toml_table())
if not removed:
self.line_error(
f"Source with name {name} was not found."
)
return 1
self.poetry.pyproject.poetry_config["source"] = sources
self.poetry.pyproject.save()
return 0
================================================
FILE: src/poetry/console/commands/source/show.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from poetry.console.commands.command import Command
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from cleo.ui.table import Rows
class SourceShowCommand(Command):
name = "source show"
description = "Show information about sources configured for the project."
arguments: ClassVar[list[Argument]] = [
argument(
"source",
"Source(s) to show information for. Defaults to showing all sources.",
optional=True,
multiple=True,
),
]
def notify_implicit_pypi(self) -> None:
if not self.poetry.pool.has_repository("pypi"):
return
self.line(
"PyPI> is implicitly enabled as a primary> source. "
"If you wish to disable it, or alter its priority please refer to "
"https://python-poetry.org/docs/repositories/#package-sources>.>"
)
self.line("")
def handle(self) -> int:
sources = self.poetry.get_sources()
names = self.argument("source")
lower_names = [name.lower() for name in names]
if not sources:
self.line("No sources configured for this project.\n")
self.notify_implicit_pypi()
return 0
if names and not any(s.name.lower() in lower_names for s in sources):
self.line_error(
f"No source found with name(s): {', '.join(names)}",
style="error",
)
return 1
is_pypi_implicit = True
for source in sources:
if names and source.name.lower() not in lower_names:
continue
if source.name.lower() == "pypi":
is_pypi_implicit = False
table = self.table(style="compact")
rows: Rows = [["name>", f" : {source.name}>"]]
if source.url:
rows.append(["url>", f" : {source.url}"])
rows.append(["priority>", f" : {source.priority.name.lower()}"])
table.add_rows(rows)
table.render()
self.line("")
if not names and is_pypi_implicit:
self.notify_implicit_pypi()
return 0
================================================
FILE: src/poetry/console/commands/sync.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import ClassVar
from poetry.console.commands.install import InstallCommand
if TYPE_CHECKING:
from cleo.io.inputs.option import Option
class SyncCommand(InstallCommand):
name = "sync"
description = "Update the project's environment according to the lockfile."
options: ClassVar[list[Option]] = [
opt for opt in InstallCommand.options if opt.name != "sync"
]
help = """\
The sync command makes sure that the project's environment is in sync with
the poetry.lock> file.
It is equivalent to running poetry install --sync.
poetry sync
By default, the above command will also install the current project. To install only the
dependencies and not including the current project, run the command with the
--no-root option like below:
poetry sync --no-root
If you want to use Poetry only for dependency management but not for packaging,
you can set the "package-mode" to false in your pyproject.toml file.
"""
@property
def _with_synchronization(self) -> bool:
return True
================================================
FILE: src/poetry/console/commands/update.py
================================================
from __future__ import annotations
import re
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from cleo.helpers import option
from packaging.utils import canonicalize_name
from poetry.console.commands.installer_command import InstallerCommand
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
_VERSION_SPECIFIER_RE = re.compile(r"[><=!~]")
class UpdateCommand(InstallerCommand):
name = "update"
description = (
"Update the dependencies as according to the pyproject.toml> file."
)
arguments: ClassVar[list[Argument]] = [
argument("packages", "The packages to update", optional=True, multiple=True)
]
options: ClassVar[list[Option]] = [
*InstallerCommand._group_dependency_options(),
option(
"sync",
None,
"Synchronize the environment with the locked packages and the specified"
" groups.",
),
option(
"dry-run",
None,
"Output the operations but do not execute anything "
"(implicitly enables --verbose).",
),
option("lock", None, "Do not perform operations (only update the lockfile)."),
]
loggers: ClassVar[list[str]] = ["poetry.repositories.pypi_repository"]
def handle(self) -> int:
packages = self.argument("packages")
if packages:
# Detect version specifiers in package arguments — poetry update
# only accepts bare package names, not requirement strings.
packages_with_specifiers = [
p for p in packages if _VERSION_SPECIFIER_RE.search(p)
]
if packages_with_specifiers:
self.line_error(
"Version specifiers are not allowed in"
" poetry update."
)
for pkg in packages_with_specifiers:
self.line_error(f" - {pkg}")
self.line_error(
"Use poetry add to change version constraints."
)
return 1
# Validate that all specified packages are declared dependencies
all_dependencies = {dep.name for dep in self.poetry.package.all_requires}
invalid_packages = [
p for p in packages if canonicalize_name(p) not in all_dependencies
]
if invalid_packages:
self.line_error(
"The following packages are not dependencies"
f" of this project: {', '.join(invalid_packages)}"
)
return 1
self.installer.whitelist(dict.fromkeys(packages, "*"))
self.installer.only_groups(self.activated_groups)
self.installer.dry_run(self.option("dry-run"))
self.installer.requires_synchronization(self.option("sync"))
self.installer.execute_operations(not self.option("lock"))
# Force update
self.installer.update(True)
return self.installer.run()
================================================
FILE: src/poetry/console/commands/version.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from cleo.helpers import argument
from cleo.helpers import option
from poetry.core.version.exceptions import InvalidVersionError
from tomlkit.toml_document import TOMLDocument
from poetry.console.commands.command import Command
if TYPE_CHECKING:
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
from poetry.core.constraints.version import Version
class VersionCommand(Command):
name = "version"
description = (
"Shows the version of the project or bumps it when a valid "
"bump rule is provided."
)
arguments: ClassVar[list[Argument]] = [
argument(
"version",
"The version number or the rule to update the version.",
optional=True,
),
]
options: ClassVar[list[Option]] = [
option("short", "s", "Output the version number only"),
option(
"dry-run",
None,
"Do not update pyproject.toml file",
),
option("next-phase", None, "Increment the phase of the current version"),
]
help = """\
The version command shows the current version of the project or bumps the version of
the project and writes the new version back to pyproject.toml> if a valid
bump rule is provided.
The new version should ideally be a valid semver string or a valid bump rule:
patch, minor, major, prepatch, preminor, premajor, prerelease.
"""
def handle(self) -> int:
version = self.argument("version")
if version:
version = self.increment_version(
self.poetry.package.pretty_version, version, self.option("next-phase")
)
if self.option("short"):
self.line(version.to_string())
else:
self.line(
f"Bumping version from {self.poetry.package.pretty_version}>"
f" to {version}>"
)
if not self.option("dry-run"):
content: dict[str, Any] = self.poetry.file.read()
project_content = content.get("project", {})
if "version" in project_content:
project_content["version"] = version.text
poetry_content = content.get("tool", {}).get("poetry", {})
if "version" in poetry_content:
poetry_content["version"] = version.text
assert isinstance(content, TOMLDocument)
self.poetry.file.write(content)
else:
if self.option("short"):
self.line(self.poetry.package.pretty_version)
else:
self.line(
f"{self.poetry.package.pretty_name}>"
f" {self.poetry.package.pretty_version}>"
)
return 0
def increment_version(
self, version: str, rule: str, next_phase: bool = False
) -> Version:
from poetry.core.constraints.version import Version
try:
parsed = Version.parse(version)
except InvalidVersionError:
raise ValueError("The project's version doesn't seem to follow semver")
if rule in {"major", "premajor"}:
new = parsed.next_major()
if rule == "premajor":
new = new.first_prerelease()
elif rule in {"minor", "preminor"}:
new = parsed.next_minor()
if rule == "preminor":
new = new.first_prerelease()
elif rule in {"patch", "prepatch"}:
new = parsed.next_patch()
if rule == "prepatch":
new = new.first_prerelease()
elif rule == "prerelease":
if parsed.is_unstable():
pre = parsed.pre
assert pre is not None
pre = pre.next_phase() if next_phase else pre.next()
new = Version(parsed.epoch, parsed.release, pre)
else:
new = parsed.next_patch().first_prerelease()
else:
new = Version.parse(rule)
return new
================================================
FILE: src/poetry/console/events/__init__.py
================================================
================================================
FILE: src/poetry/console/events/console_events.py
================================================
================================================
FILE: src/poetry/console/exceptions.py
================================================
from __future__ import annotations
import dataclasses
import shlex
from dataclasses import InitVar
from subprocess import CalledProcessError
from typing import TYPE_CHECKING
from cleo.exceptions import CleoError
from poetry.utils._compat import decode
if TYPE_CHECKING:
from cleo.io.io import IO
class PoetryConsoleError(CleoError):
pass
class GroupNotFoundError(PoetryConsoleError):
pass
@dataclasses.dataclass
class ConsoleMessage:
"""
Representation of a console message, providing utilities for formatting text
with tags, indentation, and sections.
The ConsoleMessage class is designed to represent text messages that might be
displayed in a console or terminal output. It provides features for managing
formatted text, such as stripping tags, wrapping text with specific tags,
indenting, and creating structured message sections.
"""
text: str
debug: bool = False
@property
def stripped(self) -> str:
from cleo._utils import strip_tags
return strip_tags(self.text)
def wrap(self, tag: str) -> ConsoleMessage:
if self.text:
self.text = f"<{tag}>{self.text}>"
return self
def indent(self, indent: str) -> ConsoleMessage:
if self.text:
self.text = f"\n{indent}".join(self.text.splitlines()).strip()
self.text = f"{indent}{self.text}"
return self
def make_section(
self,
title: str,
indent: str = "",
) -> ConsoleMessage:
if not self.text:
return self
if self.text:
section = [f"{title}:>"] if title else []
section.extend(self.text.splitlines())
self.text = f"\n{indent}".join(section).strip()
return self
@dataclasses.dataclass
class PrettyCalledProcessError:
"""
Represents a formatted and decorated error object for a subprocess call.
This class is used to encapsulate information about a `CalledProcessError`,
providing additional context such as command output, errors, and helpful
debugging messages. It is particularly useful for wrapping and decorating
subprocess-related exceptions in a more user-friendly format.
Attributes:
message: A string representation of the exception.
output: A section formatted representation of the exception stdout.
errors: A section formatted representation of the exception stderr.
command_message: Formatted message including a hint on retrying the original command.
command: A `shelex` quoted string representation of the original command.
exception: The original `CalledProcessError` instance.
indent: Indent prefix to use for inner content per section.
"""
message: ConsoleMessage = dataclasses.field(init=False)
output: ConsoleMessage = dataclasses.field(init=False)
errors: ConsoleMessage = dataclasses.field(init=False)
command_message: ConsoleMessage = dataclasses.field(init=False)
command: str = dataclasses.field(init=False)
exception: InitVar[CalledProcessError] = dataclasses.field(init=True)
indent: InitVar[str] = dataclasses.field(default="")
def __post_init__(self, exception: CalledProcessError, indent: str) -> None:
self.message = ConsoleMessage(str(exception).strip(), debug=True).make_section(
"Exception", indent
)
self.output = ConsoleMessage(decode(exception.stdout), debug=True).make_section(
"Output", indent
)
self.errors = ConsoleMessage(decode(exception.stderr), debug=True).make_section(
"Errors", indent
)
self.command = (
shlex.join(exception.cmd)
if isinstance(exception.cmd, list)
else exception.cmd
)
self.command_message = ConsoleMessage(
f"You can test the failed command by executing:\n\n {self.command}",
debug=False,
)
class PoetryRuntimeError(PoetryConsoleError):
"""
Represents a runtime error in the Poetry console application.
"""
def __init__(
self,
reason: str,
messages: list[ConsoleMessage] | None = None,
exit_code: int = 1,
) -> None:
super().__init__(reason)
self.exit_code = exit_code
self._messages = messages or []
self._messages.insert(0, ConsoleMessage(reason))
def write(self, io: IO) -> None:
"""
Write the error text to the provided IO iff there is any text
to write.
"""
if text := self.get_text(debug=io.is_verbose(), strip=False):
io.write_error_line(text)
def get_text(
self, debug: bool = False, indent: str = "", strip: bool = False
) -> str:
"""
Convert the error messages to a formatted string. All empty messages
are ignored along with debug level messages if `debug` is `False`.
"""
text = ""
has_skipped_debug = False
for message in self._messages:
if message.debug and not debug:
has_skipped_debug = True
continue
message_text = message.stripped if strip else message.text
if not message_text:
continue
if indent:
message_text = f"\n{indent}".join(message_text.splitlines())
text += f"{indent}{message_text}\n{indent}\n"
if has_skipped_debug:
message = ConsoleMessage(
f"{indent}You can also run your poetry> command with -v> to see more information.\n{indent}\n"
)
text += message.stripped if strip else message.text
return text.rstrip(f"{indent}\n")
def __str__(self) -> str:
return self._messages[0].stripped.strip()
@classmethod
def create(
cls,
reason: str,
exception: CalledProcessError | Exception | None = None,
info: list[str] | str | None = None,
) -> PoetryRuntimeError:
"""
Create an instance of this class using the provided reason. If
an exception is provided, this is also injected as a debug
`ConsoleMessage`.
There is specific handling for known exception types. For example,
if exception is of type `subprocess.CalledProcessError`, the following
sections are additionally added when available - stdout, stderr and
command for testing.
"""
if isinstance(info, str):
info = [info]
messages: list[ConsoleMessage] = [
ConsoleMessage(
"\n".join(info or []),
debug=False,
).wrap("info"),
]
if isinstance(exception, CalledProcessError):
error = PrettyCalledProcessError(exception, indent=" | ")
messages = [
error.message.wrap("warning"),
error.output.wrap("warning"),
error.errors.wrap("warning"),
*messages,
error.command_message,
]
elif exception is not None and isinstance(exception, Exception):
messages.insert(
0,
ConsoleMessage(str(exception), debug=True).make_section(
"Exception", indent=" | "
),
)
return cls(reason, messages)
def append(self, message: str | ConsoleMessage) -> PoetryRuntimeError:
if isinstance(message, str):
message = ConsoleMessage(message)
self._messages.append(message)
return self
================================================
FILE: src/poetry/console/logging/__init__.py
================================================
================================================
FILE: src/poetry/console/logging/filters.py
================================================
from __future__ import annotations
import logging
POETRY_FILTER = logging.Filter(name="poetry")
================================================
FILE: src/poetry/console/logging/formatters/__init__.py
================================================
from __future__ import annotations
from poetry.console.logging.formatters.builder_formatter import BuilderLogFormatter
FORMATTERS = {
"poetry.core.masonry.builders.builder": BuilderLogFormatter(),
"poetry.core.masonry.builders.sdist": BuilderLogFormatter(),
"poetry.core.masonry.builders.wheel": BuilderLogFormatter(),
}
================================================
FILE: src/poetry/console/logging/formatters/builder_formatter.py
================================================
from __future__ import annotations
import re
from poetry.console.logging.formatters.formatter import Formatter
class BuilderLogFormatter(Formatter):
def format(self, msg: str) -> str:
if msg.startswith("Building "):
msg = re.sub("Building (.+)", " - Building \\1", msg)
elif msg.startswith("Built "):
msg = re.sub("Built (.+)", " - Built \\1", msg)
elif msg.startswith("Adding: "):
msg = re.sub("Adding: (.+)", " - Adding: \\1", msg)
elif msg.startswith("Executing build script: "):
msg = re.sub(
"Executing build script: (.+)",
" - Executing build script: \\1",
msg,
)
return msg
================================================
FILE: src/poetry/console/logging/formatters/formatter.py
================================================
from __future__ import annotations
from abc import ABC
from abc import abstractmethod
class Formatter(ABC):
@abstractmethod
def format(self, msg: str) -> str: ...
================================================
FILE: src/poetry/console/logging/io_formatter.py
================================================
from __future__ import annotations
import logging
import sys
import textwrap
from pathlib import Path
from typing import TYPE_CHECKING
from typing import ClassVar
from poetry.console.logging.filters import POETRY_FILTER
from poetry.console.logging.formatters import FORMATTERS
if TYPE_CHECKING:
from logging import LogRecord
class IOFormatter(logging.Formatter):
_colors: ClassVar[dict[str, str]] = {
"error": "fg=red",
"warning": "fg=yellow",
"debug": "debug",
"info": "fg=blue",
}
def format(self, record: LogRecord) -> str:
if not record.exc_info:
level = record.levelname.lower()
msg = record.msg
if record.name in FORMATTERS:
msg = FORMATTERS[record.name].format(msg)
elif level in self._colors:
msg = f"<{self._colors[level]}>{msg}>"
record.msg = msg
formatted = super().format(record)
if not POETRY_FILTER.filter(record):
# prefix all lines from third-party packages for easier debugging
formatted = textwrap.indent(
formatted, f"[{_log_prefix(record)}] ", lambda line: True
)
return formatted
def _log_prefix(record: LogRecord) -> str:
prefix = _path_to_package(Path(record.pathname)) or record.module
if record.name != "root":
prefix = ":".join([prefix, record.name])
return prefix
def _path_to_package(path: Path) -> str | None:
"""Return main package name from the LogRecord.pathname."""
prefix: Path | None = None
# Find the most specific prefix in sys.path.
# We have to search the entire sys.path because a subsequent path might be
# a sub path of the first match and thereby a better match.
for syspath in sys.path:
if (
prefix and prefix in (p := Path(syspath)).parents and p in path.parents
) or (not prefix and (p := Path(syspath)) in path.parents):
prefix = p
if not prefix:
# this is unexpected, but let's play it safe
return None
path = path.relative_to(prefix)
return path.parts[0] # main package name
================================================
FILE: src/poetry/console/logging/io_handler.py
================================================
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from logging import LogRecord
from cleo.io.io import IO
class IOHandler(logging.Handler):
def __init__(self, io: IO) -> None:
self._io = io
super().__init__()
def emit(self, record: LogRecord) -> None:
try:
msg = self.format(record)
level = record.levelname.lower()
err = level in ("warning", "error", "exception", "critical")
if err:
self._io.write_error_line(msg)
else:
self._io.write_line(msg)
except Exception:
self.handleError(record)
================================================
FILE: src/poetry/exceptions.py
================================================
from __future__ import annotations
class PoetryError(Exception):
pass
================================================
FILE: src/poetry/factory.py
================================================
from __future__ import annotations
import contextlib
import logging
import re
from typing import TYPE_CHECKING
from typing import Any
from typing import cast
from cleo.io.null_io import NullIO
from packaging.utils import NormalizedName
from packaging.utils import canonicalize_name
from poetry.core.constraints.version import Version
from poetry.core.constraints.version import parse_constraint
from poetry.core.factory import Factory as BaseFactory
from poetry.core.packages.dependency_group import MAIN_GROUP
from poetry.__version__ import __version__
from poetry.config.config import Config
from poetry.exceptions import PoetryError
from poetry.json import validate_object
from poetry.packages.locker import Locker
from poetry.plugins.plugin import Plugin
from poetry.plugins.plugin_manager import PluginManager
from poetry.poetry import Poetry
from poetry.pyproject.toml import PyProjectTOML
from poetry.toml.file import TOMLFile
from poetry.utils.isolated_build import CONSTRAINTS_GROUP_NAME
if TYPE_CHECKING:
from collections.abc import Iterable
from pathlib import Path
from cleo.io.io import IO
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package
from tomlkit.toml_document import TOMLDocument
from poetry.repositories import RepositoryPool
from poetry.repositories.http_repository import HTTPRepository
from poetry.utils.dependency_specification import DependencySpec
logger = logging.getLogger(__name__)
class Factory(BaseFactory):
"""
Factory class to create various elements needed by Poetry.
"""
def _ensure_valid_poetry_version(self, cwd: Path | None) -> None:
poetry_file = self.locate(cwd)
pyproject = PyProjectTOML(path=poetry_file)
poetry_config = pyproject.data.get("tool", {}).get("poetry", {})
if version_str := poetry_config.get("requires-poetry"):
version_constraint = parse_constraint(version_str)
version = Version.parse(__version__)
if not version_constraint.allows(version):
raise PoetryError(
f"This project requires Poetry {version_constraint},"
f" but you are using Poetry {version}"
)
def create_poetry(
self,
cwd: Path | None = None,
with_groups: bool = True,
io: IO | None = None,
disable_plugins: bool = False,
disable_cache: bool = False,
) -> Poetry:
if io is None:
io = NullIO()
self._ensure_valid_poetry_version(cwd)
base_poetry = super().create_poetry(cwd=cwd, with_groups=with_groups)
build_constraints: dict[NormalizedName, list[Dependency]] = {}
for name, constraints in base_poetry.local_config.get(
"build-constraints", {}
).items():
name = canonicalize_name(name)
build_constraints[name] = []
for dep_name, constraint in constraints.items():
_constraints = (
constraint if isinstance(constraint, list) else [constraint]
)
for _constraint in _constraints:
build_constraints[name].append(
Factory.create_dependency(
dep_name, _constraint, groups=[CONSTRAINTS_GROUP_NAME]
)
)
poetry_file = base_poetry.pyproject_path
locker = Locker(poetry_file.parent / "poetry.lock", base_poetry.pyproject.data)
# Loading global configuration
config = Config.create()
# Loading local configuration
local_config_file = TOMLFile(poetry_file.parent / "poetry.toml")
if local_config_file.exists():
if io.is_debug():
io.write_line(f"Loading configuration file {local_config_file.path}")
config.merge(local_config_file.read())
# Load local sources
repositories = {}
existing_repositories = config.get("repositories", {})
for source in base_poetry.local_config.get("source", []):
name = source.get("name")
url = source.get("url")
if name and url and name not in existing_repositories:
repositories[name] = {"url": url}
config.merge({"repositories": repositories})
poetry = Poetry(
poetry_file,
base_poetry.local_config,
base_poetry.package,
locker,
config,
disable_cache=disable_cache,
build_constraints=build_constraints,
)
poetry.set_pool(
self.create_pool(
config,
poetry.local_config.get("source", []),
io,
disable_cache=disable_cache,
)
)
if not disable_plugins:
plugin_manager = PluginManager(Plugin.group)
plugin_manager.load_plugins()
plugin_manager.activate(poetry, io)
return poetry
@classmethod
def create_pool(
cls,
config: Config,
sources: Iterable[dict[str, Any]] = (),
io: IO | None = None,
disable_cache: bool = False,
) -> RepositoryPool:
from poetry.repositories import RepositoryPool
from poetry.repositories.repository_pool import Priority
if io is None:
io = NullIO()
if disable_cache:
logger.debug("Disabling source caches")
pool = RepositoryPool(config=config)
explicit_pypi = False
for source in sources:
repository = cls.create_package_source(
source, config, disable_cache=disable_cache
)
priority = Priority[source.get("priority", Priority.PRIMARY.name).upper()]
if io.is_debug():
io.write_line(
f"Adding repository {repository.name} ({repository.url})"
f" and setting it as {priority.name.lower()}"
)
pool.add_repository(repository, priority=priority)
if repository.name.lower() == "pypi":
explicit_pypi = True
# Only add PyPI if no primary repository is configured
if not explicit_pypi:
if pool.has_primary_repositories():
if io.is_debug():
io.write_line("Deactivating the PyPI repository")
else:
pool.add_repository(
cls.create_package_source(
{"name": "pypi"}, config, disable_cache=disable_cache
),
priority=Priority.PRIMARY,
)
if not pool.repositories:
raise PoetryError(
"At least one source must not be configured as 'explicit'."
)
return pool
@classmethod
def create_package_source(
cls, source: dict[str, str], config: Config, disable_cache: bool = False
) -> HTTPRepository:
from poetry.repositories.exceptions import InvalidSourceError
from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.pypi_repository import PyPiRepository
from poetry.repositories.single_page_repository import SinglePageRepository
try:
name = source["name"]
except KeyError:
raise InvalidSourceError("Missing [name] in source.")
pool_size = config.installer_max_workers
if name.lower() == "pypi":
if "url" in source:
raise InvalidSourceError(
"The PyPI repository cannot be configured with a custom url."
)
return PyPiRepository(
config=config,
disable_cache=disable_cache,
pool_size=pool_size,
)
try:
url = source["url"]
except KeyError:
raise InvalidSourceError(f"Missing [url] in source {name!r}.")
repository_class = LegacyRepository
if re.match(r".*\.(htm|html)$", url):
repository_class = SinglePageRepository
return repository_class(
name,
url,
config=config,
disable_cache=disable_cache,
pool_size=pool_size,
)
@classmethod
def create_legacy_pyproject_from_package(cls, package: Package) -> TOMLDocument:
import tomlkit
from poetry.utils.dependency_specification import dependency_to_specification
pyproject: dict[str, Any] = tomlkit.document()
pyproject["tool"] = tomlkit.table(is_super_table=True)
content: dict[str, Any] = tomlkit.table()
pyproject["tool"]["poetry"] = content
content["name"] = package.name
content["version"] = package.version.text
content["description"] = package.description
content["authors"] = package.authors
content["license"] = package.license.id if package.license else ""
if package.classifiers:
content["classifiers"] = package.classifiers
if package.documentation_url:
content["documentation"] = package.documentation_url
if package.repository_url:
content["repository"] = package.repository_url
if package.homepage:
content["homepage"] = package.homepage
if package.maintainers:
content["maintainers"] = package.maintainers
if package.keywords:
content["keywords"] = package.keywords
readmes = []
for readme in package.readmes:
readme_posix_path = readme.as_posix()
with contextlib.suppress(ValueError):
if package.root_dir:
readme_posix_path = readme.relative_to(package.root_dir).as_posix()
readmes.append(readme_posix_path)
if readmes:
content["readme"] = readmes
optional_dependencies = set()
extras_section = None
if package.extras:
extras_section = tomlkit.table()
for extra in package.extras:
_dependencies = []
for dependency in package.extras[extra]:
_dependencies.append(dependency.name)
optional_dependencies.add(dependency.name)
extras_section[extra] = _dependencies
optional_dependencies = set(optional_dependencies)
dependency_section = content["dependencies"] = tomlkit.table()
dependency_section["python"] = package.python_versions
for dep in package.all_requires:
constraint: DependencySpec | str = dependency_to_specification(
dep, tomlkit.inline_table()
)
if not isinstance(constraint, str):
if dep.name in optional_dependencies:
constraint["optional"] = True
if len(constraint) == 1 and "version" in constraint:
assert isinstance(constraint["version"], str)
constraint = constraint["version"]
elif not constraint:
constraint = "*"
for group in dep.groups:
if group == MAIN_GROUP:
dependency_section[dep.name] = constraint
else:
if "group" not in content:
content["group"] = tomlkit.table(is_super_table=True)
if group not in content["group"]:
content["group"][group] = tomlkit.table(is_super_table=True)
if "dependencies" not in content["group"][group]:
content["group"][group]["dependencies"] = tomlkit.table()
content["group"][group]["dependencies"][dep.name] = constraint
if extras_section:
content["extras"] = extras_section
pyproject = cast("TOMLDocument", pyproject)
return pyproject
@classmethod
def validate(
cls, toml_data: dict[str, Any], strict: bool = False
) -> dict[str, list[str]]:
results = super().validate(toml_data, strict)
poetry_config = toml_data["tool"]["poetry"]
results["errors"].extend(
[e.replace("data.", "tool.poetry.") for e in validate_object(poetry_config)]
)
# A project should not depend on itself.
# TODO: consider [project.dependencies] and [project.optional-dependencies]
dependencies = set(poetry_config.get("dependencies", {}).keys())
dependencies.update(poetry_config.get("dev-dependencies", {}).keys())
groups = poetry_config.get("group", {}).values()
for group in groups:
dependencies.update(group.get("dependencies", {}).keys())
dependencies = {canonicalize_name(d) for d in dependencies}
project_name = toml_data.get("project", {}).get("name") or poetry_config.get(
"name"
)
if project_name is not None and canonicalize_name(project_name) in dependencies:
results["errors"].append(
f"Project name ({project_name}) is same as one of its dependencies"
)
return results
================================================
FILE: src/poetry/inspection/__init__.py
================================================
================================================
FILE: src/poetry/inspection/info.py
================================================
from __future__ import annotations
import contextlib
import functools
import glob
import logging
import tempfile
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import TYPE_CHECKING
from typing import Any
import pkginfo
from poetry.core.constraints.version import Version
from poetry.core.factory import Factory
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package
from poetry.core.pyproject.toml import PyProjectTOML
from poetry.core.utils.helpers import parse_requires
from poetry.core.version.markers import InvalidMarkerError
from poetry.core.version.requirements import InvalidRequirementError
from poetry.utils.helpers import extractall
from poetry.utils.isolated_build import IsolatedBuildBackendError
from poetry.utils.isolated_build import isolated_builder
if TYPE_CHECKING:
from collections.abc import Iterator
from collections.abc import Sequence
from packaging.metadata import RawMetadata
from packaging.utils import NormalizedName
from poetry.core.packages.package import PackageFile
from poetry.core.packages.project_package import ProjectPackage
logger = logging.getLogger(__name__)
DYNAMIC_METADATA_VERSION = Version.parse("2.2")
class PackageInfoError(ValueError):
def __init__(self, path: Path, *reasons: BaseException | str) -> None:
reasons = (f"Unable to determine package info for path: {path!s}", *reasons)
super().__init__("\n\n".join(str(msg).strip() for msg in reasons if msg))
class PackageInfo:
def __init__(
self,
*,
name: str | None = None,
version: str | None = None,
summary: str | None = None,
requires_dist: list[str] | None = None,
requires_python: str | None = None,
files: Sequence[PackageFile] | None = None,
yanked: str | bool = False,
cache_version: str | None = None,
) -> None:
self.name = name
self.version = version
self.summary = summary
self.requires_dist = requires_dist
self.requires_python = requires_python
self.files = files or []
self.yanked = yanked
self._cache_version = cache_version
self._source_type: str | None = None
self._source_url: str | None = None
self._source_reference: str | None = None
@property
def cache_version(self) -> str | None:
return self._cache_version
def update(self, other: PackageInfo) -> PackageInfo:
self.name = other.name or self.name
self.version = other.version or self.version
self.summary = other.summary or self.summary
self.requires_dist = other.requires_dist or self.requires_dist
self.requires_python = other.requires_python or self.requires_python
self.files = other.files or self.files
self._cache_version = other.cache_version or self._cache_version
return self
def asdict(self) -> dict[str, Any]:
"""
Helper method to convert package info into a dictionary used for caching.
"""
return {
"name": self.name,
"version": self.version,
"summary": self.summary,
"requires_dist": self.requires_dist,
"requires_python": self.requires_python,
"files": self.files,
"yanked": self.yanked,
"_cache_version": self._cache_version,
}
@classmethod
def load(cls, data: dict[str, Any]) -> PackageInfo:
"""
Helper method to load data from a dictionary produced by `PackageInfo.asdict()`.
:param data: Data to load. This is expected to be a `dict` object output by
`asdict()`.
"""
cache_version = data.pop("_cache_version", None)
return cls(cache_version=cache_version, **data)
def to_package(
self, name: str | None = None, root_dir: Path | None = None
) -> Package:
"""
Create a new `poetry.core.packages.package.Package` instance using metadata from
this instance.
:param name: Name to use for the package, if not specified name from this
instance is used.
:param extras: Extras to activate for this package.
:param root_dir: Optional root directory to use for the package. If set,
dependency strings will be parsed relative to this directory.
"""
name = name or self.name
if not name:
raise RuntimeError(f"Unable to create package with no name for {root_dir}")
if not self.version:
# The version could not be determined, so we raise an error since it is
# mandatory.
raise RuntimeError(f"Unable to retrieve the package version for {name}")
package = Package(
name=name,
version=self.version,
source_type=self._source_type,
source_url=self._source_url,
source_reference=self._source_reference,
yanked=self.yanked,
)
if self.summary is not None:
package.description = self.summary
package.root_dir = root_dir
package.python_versions = self.requires_python or "*"
package.files = self.files
# If this is a local poetry project, we can extract "richer" requirement
# information, eg: development requirements etc.
if root_dir is not None:
path = root_dir
elif self._source_type == "directory" and self._source_url is not None:
path = Path(self._source_url)
else:
path = None
if path is not None:
poetry_package = self._get_poetry_package(path=path)
if poetry_package:
package.extras = poetry_package.extras
for dependency in poetry_package.requires:
package.add_dependency(dependency)
return package
seen_requirements = set()
package_extras: dict[NormalizedName, list[Dependency]] = {}
for req in self.requires_dist or []:
try:
# Attempt to parse the PEP-508 requirement string
dependency = Dependency.create_from_pep_508(req, relative_to=root_dir)
except InvalidMarkerError:
# Invalid marker, We strip the markers hoping for the best
logger.warning(
"Stripping invalid marker (%s) found in %s-%s dependencies",
req,
package.name,
package.version,
)
req = req.split(";")[0]
dependency = Dependency.create_from_pep_508(req, relative_to=root_dir)
except InvalidRequirementError:
# Unable to parse requirement so we skip it
logger.warning(
"Invalid requirement (%s) found in %s-%s dependencies, skipping",
req,
package.name,
package.version,
)
continue
if dependency.in_extras:
# this dependency is required by an extra package
for extra in dependency.in_extras:
if extra not in package_extras:
# this is the first time we encounter this extra for this
# package
package_extras[extra] = []
package_extras[extra].append(dependency)
req = dependency.to_pep_508(with_extras=True)
if req not in seen_requirements:
package.add_dependency(dependency)
seen_requirements.add(req)
package.extras = package_extras
return package
@classmethod
def _requirements_from_distribution(
cls,
dist: pkginfo.BDist | pkginfo.SDist | pkginfo.Wheel,
) -> list[str] | None:
"""
Helper method to extract package requirements from a `pkginfo.Distribution`
instance.
:param dist: The distribution instance to extract requirements from.
"""
# If the distribution lists requirements, we use those.
#
# If the distribution does not list requirements, but the metadata is new enough
# to specify that this is because there definitely are none: then we return an
# empty list.
#
# If there is a requires.txt, we use that.
if dist.requires_dist:
return list(dist.requires_dist)
if dist.metadata_version is not None:
metadata_version = Version.parse(dist.metadata_version)
if (
metadata_version >= DYNAMIC_METADATA_VERSION
and "Requires-Dist" not in dist.dynamic
):
return []
requires = Path(dist.filename) / "requires.txt"
if requires.exists():
text = requires.read_text(encoding="utf-8")
requirements = parse_requires(text)
return requirements
return None
@classmethod
def _from_distribution(
cls, dist: pkginfo.BDist | pkginfo.SDist | pkginfo.Wheel
) -> PackageInfo:
"""
Helper method to parse package information from a `pkginfo.Distribution`
instance.
:param dist: The distribution instance to parse information from.
"""
# If the METADATA version is greater than the highest supported version,
# pkginfo prints a warning and tries to parse the fields from the highest
# known version. Assuming that METADATA versions adhere to semver,
# this should be safe for minor updates.
if not dist.metadata_version or dist.metadata_version.split(".")[0] not in {
v.split(".")[0] for v in pkginfo.distribution.HEADER_ATTRS
}:
raise ValueError(f"Unknown metadata version: {dist.metadata_version}")
requirements = cls._requirements_from_distribution(dist)
info = cls(
name=dist.name,
version=dist.version,
summary=dist.summary,
requires_dist=requirements,
requires_python=dist.requires_python,
)
info._source_type = "file"
info._source_url = Path(dist.filename).resolve().as_posix()
return info
@classmethod
def _from_sdist_file(cls, path: Path) -> PackageInfo:
"""
Helper method to parse package information from an sdist file. We attempt to
first inspect the file using `pkginfo.SDist`. If this does not provide us with
package requirements, we extract the source and handle it as a directory.
:param path: The sdist file to parse information from.
"""
info = None
with contextlib.suppress(ValueError):
sdist = pkginfo.SDist(str(path))
info = cls._from_distribution(sdist)
if info is not None and info.requires_dist is not None:
# we successfully retrieved dependencies from sdist metadata
return info
# Still not dependencies found
# So, we unpack and introspect
suffix = path.suffix
zip = suffix == ".zip"
if suffix == ".bz2":
suffixes = path.suffixes
if len(suffixes) > 1 and suffixes[-2] == ".tar":
suffix = ".tar.bz2"
elif not zip:
suffix = ".tar.gz"
with TemporaryDirectory(ignore_cleanup_errors=True) as tmp_str:
tmp = Path(tmp_str)
extractall(source=path, dest=tmp, zip=zip)
# a little bit of guess work to determine the directory we care about
elements = list(tmp.glob("*"))
if len(elements) == 1 and elements[0].is_dir():
sdist_dir = elements[0]
else:
sdist_dir = tmp / path.name.rstrip(suffix)
if not sdist_dir.is_dir():
sdist_dir = tmp
# now this is an unpacked directory we know how to deal with
new_info = cls.from_directory(path=sdist_dir)
new_info._source_type = "file"
new_info._source_url = path.resolve().as_posix()
if not info:
return new_info
return info.update(new_info)
@staticmethod
def _find_dist_info(path: Path) -> Iterator[Path]:
"""
Discover all `*.*-info` directories in a given path.
:param path: Path to search.
"""
pattern = "**/*.*-info"
# Sometimes pathlib will fail on recursive symbolic links, so we need to work
# around it and use the glob module instead. Note that this does not happen with
# pathlib2 so it's safe to use it for Python < 3.4.
directories = glob.iglob(path.joinpath(pattern).as_posix(), recursive=True)
for d in directories:
yield Path(d)
@classmethod
def from_metadata(cls, metadata: RawMetadata) -> PackageInfo:
"""
Create package information from core metadata.
:param metadata: raw metadata
"""
return cls(
name=metadata.get("name"),
version=metadata.get("version"),
summary=metadata.get("summary"),
requires_dist=metadata.get("requires_dist"),
requires_python=metadata.get("requires_python"),
)
@classmethod
def from_metadata_directory(cls, path: Path) -> PackageInfo | None:
"""
Helper method to parse package information from an unpacked metadata directory.
:param path: The metadata directory to parse information from.
"""
if path.suffix in {".dist-info", ".egg-info"}:
directories = [path]
else:
directories = list(cls._find_dist_info(path=path))
dist: pkginfo.BDist | pkginfo.SDist | pkginfo.Wheel
for directory in directories:
try:
if directory.suffix == ".egg-info":
dist = pkginfo.UnpackedSDist(directory.as_posix())
elif directory.suffix == ".dist-info":
dist = pkginfo.Wheel(directory.as_posix())
else:
continue
break
except ValueError:
continue
else:
try:
# handle PKG-INFO in unpacked sdist root
dist = pkginfo.UnpackedSDist(path.as_posix())
except ValueError:
return None
return cls._from_distribution(dist=dist)
@classmethod
def from_package(cls, package: Package) -> PackageInfo:
"""
Helper method to inspect a `Package` object, in order to generate package info.
:param package: This must be a poetry package instance.
"""
requires = {dependency.to_pep_508() for dependency in package.requires}
for extra_requires in package.extras.values():
for dependency in extra_requires:
requires.add(dependency.to_pep_508())
return cls(
name=package.name,
version=str(package.version),
summary=package.description,
requires_dist=list(requires),
requires_python=package.python_versions,
files=package.files,
yanked=package.yanked_reason if package.yanked else False,
)
@staticmethod
def _get_poetry_package(path: Path) -> ProjectPackage | None:
# Note: we ignore any setup.py file at this step
# TODO: add support for handling non-poetry PEP-517 builds
if PyProjectTOML(path.joinpath("pyproject.toml")).is_poetry_project():
with contextlib.suppress(RuntimeError):
return Factory().create_poetry(path).package
return None
@classmethod
def from_directory(cls, path: Path) -> PackageInfo:
"""
Generate package information from a package source directory. If introspection
of all available metadata fails, the package is attempted to be built in an
isolated environment so as to generate required metadata.
:param path: Path to generate package information from.
"""
project_package = cls._get_poetry_package(path)
info: PackageInfo | None
if project_package:
info = cls.from_package(project_package)
else:
info = cls.from_metadata_directory(path)
if not info or info.requires_dist is None:
try:
info = get_pep517_metadata(path)
except PackageInfoError:
if not info:
raise
# we discovered PkgInfo but no requirements were listed
info._source_type = "directory"
info._source_url = path.as_posix()
return info
@classmethod
def from_sdist(cls, path: Path) -> PackageInfo:
"""
Gather package information from an sdist file, packed or unpacked.
:param path: Path to an sdist file or unpacked directory.
"""
if path.is_file():
return cls._from_sdist_file(path=path)
# if we get here then it is neither an sdist instance nor a file
# so, we assume this is an directory
return cls.from_directory(path=path)
@classmethod
def from_wheel(cls, path: Path) -> PackageInfo:
"""
Gather package information from a wheel.
:param path: Path to wheel.
"""
try:
wheel = pkginfo.Wheel(str(path))
return cls._from_distribution(wheel)
except ValueError as e:
raise PackageInfoError(path, e)
@classmethod
def from_bdist(cls, path: Path) -> PackageInfo:
"""
Gather package information from a bdist (wheel etc.).
:param path: Path to bdist.
"""
if path.suffix == ".whl":
return cls.from_wheel(path=path)
try:
bdist = pkginfo.BDist(str(path))
return cls._from_distribution(bdist)
except ValueError as e:
raise PackageInfoError(path, e)
@classmethod
def from_path(cls, path: Path) -> PackageInfo:
"""
Gather package information from a given path (bdist, sdist, directory).
:param path: Path to inspect.
"""
try:
return cls.from_bdist(path=path)
except PackageInfoError:
return cls.from_sdist(path=path)
@functools.cache
def get_pep517_metadata(path: Path) -> PackageInfo:
"""
Helper method to use PEP-517 library to build and read package metadata.
:param path: Path to package source to build and read metadata for.
"""
info = None
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as dist:
try:
dest = Path(dist)
with isolated_builder(path, "wheel") as builder:
builder.metadata_path(dest)
info = PackageInfo.from_metadata_directory(dest)
except IsolatedBuildBackendError as e:
raise PackageInfoError(path, str(e)) from None
if info:
return info
# if we reach here, everything has failed and all hope is lost
raise PackageInfoError(path, "Exhausted all core metadata sources.")
================================================
FILE: src/poetry/inspection/lazy_wheel.py
================================================
"""Lazy ZIP over HTTP"""
from __future__ import annotations
import io
import logging
import re
from bisect import bisect_left
from bisect import bisect_right
from contextlib import contextmanager
from tempfile import NamedTemporaryFile
from typing import IO
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from urllib.parse import urlparse
from zipfile import BadZipFile
from zipfile import ZipFile
from packaging.metadata import parse_email
from requests.models import CONTENT_CHUNK_SIZE
from requests.models import HTTPError
from requests.models import Response
from requests.status_codes import codes
if TYPE_CHECKING:
from collections.abc import Iterable
from collections.abc import Iterator
from types import TracebackType
from packaging.metadata import RawMetadata
from requests import Session
from typing_extensions import Self
from poetry.utils.authenticator import Authenticator
logger = logging.getLogger(__name__)
class LazyWheelUnsupportedError(Exception):
"""Raised when a lazy wheel is unsupported."""
class HTTPRangeRequestUnsupportedError(LazyWheelUnsupportedError):
"""Raised when the remote server appears unable to support byte ranges."""
class HTTPRangeRequestNotRespectedError(LazyWheelUnsupportedError):
"""Raised when the remote server tells us that it supports byte ranges
but does not respect a respective request."""
class UnsupportedWheelError(LazyWheelUnsupportedError):
"""Unsupported wheel."""
class InvalidWheelError(LazyWheelUnsupportedError):
"""Invalid (e.g. corrupt) wheel."""
def __init__(self, location: str, name: str) -> None:
self.location = location
self.name = name
def __str__(self) -> str:
return f"Wheel {self.name} located at {self.location} is invalid."
def metadata_from_wheel_url(
name: str, url: str, session: Session | Authenticator
) -> RawMetadata:
"""Fetch metadata from the given wheel URL.
This uses HTTP range requests to only fetch the portion of the wheel
containing metadata, just enough for the object to be constructed.
:raises HTTPRangeRequestUnsupportedError: if range requests are unsupported for ``url``.
:raises InvalidWheelError: if the zip file contents could not be parsed.
"""
try:
# After context manager exit, wheel.name will point to a deleted file path.
# Add `delete_backing_file=False` to disable this for debugging.
with LazyWheelOverHTTP(url, session) as lazy_file:
metadata_bytes = lazy_file.read_metadata(name)
metadata, _ = parse_email(metadata_bytes)
return metadata
except (BadZipFile, UnsupportedWheelError):
# We assume that these errors have occurred because the wheel contents
# themselves are invalid, not because we've messed up our bookkeeping
# and produced an invalid file.
raise InvalidWheelError(url, name)
except Exception as e:
if isinstance(e, LazyWheelUnsupportedError):
# this is expected when the code handles issues with lazy wheel metadata retrieval correctly
raise e
logger.debug(
"There was an unexpected %s when handling lazy wheel metadata retrieval for %s from %s: %s",
type(e).__name__,
name,
url,
e,
)
# Catch all exception to handle any issues that may have occurred during
# attempts to use Lazy Wheel.
raise LazyWheelUnsupportedError(
f"Attempts to use lazy wheel metadata retrieval for {name} from {url} failed"
) from e
class MergeIntervals:
"""Stateful bookkeeping to merge interval graphs."""
def __init__(self, *, left: Iterable[int] = (), right: Iterable[int] = ()) -> None:
self._left = list(left)
self._right = list(right)
def __repr__(self) -> str:
return (
f"{type(self).__name__}"
f"(left={tuple(self._left)}, right={tuple(self._right)})"
)
def _merge(
self, start: int, end: int, left: int, right: int
) -> Iterator[tuple[int, int]]:
"""Return an iterator of intervals to be fetched.
Args:
start: Start of needed interval
end: End of needed interval
left: Index of first overlapping downloaded data
right: Index after last overlapping downloaded data
"""
lslice, rslice = self._left[left:right], self._right[left:right]
i = start = min([start, *lslice[:1]])
end = max([end, *rslice[-1:]])
for j, k in zip(lslice, rslice):
if j > i:
yield i, j - 1
i = k + 1
if i <= end:
yield i, end
self._left[left:right], self._right[left:right] = [start], [end]
def minimal_intervals_covering(
self, start: int, end: int
) -> Iterator[tuple[int, int]]:
"""Provide the intervals needed to cover from ``start <= x <= end``.
This method mutates internal state so that later calls only return intervals not
covered by prior calls. The first call to this method will always return exactly
one interval, which was exactly the one requested. Later requests for
intervals overlapping that first requested interval will yield only the ranges
not previously covered (which may be empty, e.g. if the same interval is
requested twice).
This may be used e.g. to download substrings of remote files on demand.
"""
left = bisect_left(self._right, start)
right = bisect_right(self._left, end)
yield from self._merge(start, end, left, right)
class ReadOnlyIOWrapper(IO[bytes]):
"""Implement read-side ``IO[bytes]`` methods wrapping an inner ``IO[bytes]``.
This wrapper is useful because Python currently does not distinguish read-only
streams at the type level.
"""
def __init__(self, inner: IO[bytes]) -> None:
self._file = inner
def __enter__(self) -> Self:
self._file.__enter__()
return self
def __exit__(
self,
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
traceback: TracebackType | None,
) -> None:
self._file.__exit__(exc_type, exc_value, traceback)
def __iter__(self) -> Iterator[bytes]:
raise NotImplementedError
def __next__(self) -> bytes:
raise NotImplementedError
@property
def mode(self) -> str:
"""Opening mode, which is always rb."""
return "rb"
@property
def name(self) -> str:
"""Path to the underlying file."""
return self._file.name
def seekable(self) -> bool:
"""Return whether random access is supported, which is True."""
return True
def close(self) -> None:
"""Close the file."""
self._file.close()
@property
def closed(self) -> bool:
"""Whether the file is closed."""
return self._file.closed
def fileno(self) -> int:
return self._file.fileno()
def flush(self) -> None:
self._file.flush()
def isatty(self) -> bool:
return False
def readable(self) -> bool:
"""Return whether the file is readable, which is True."""
return True
def read(self, size: int = -1) -> bytes:
"""Read up to size bytes from the object and return them.
As a convenience, if size is unspecified or -1,
all bytes until EOF are returned. Fewer than
size bytes may be returned if EOF is reached.
"""
return self._file.read(size)
def readline(self, limit: int = -1) -> bytes:
# Explicit impl needed to satisfy mypy.
raise NotImplementedError
def readlines(self, hint: int = -1) -> list[bytes]:
raise NotImplementedError
def seek(self, offset: int, whence: int = 0) -> int:
"""Change stream position and return the new absolute position.
Seek to offset relative position indicated by whence:
* 0: Start of stream (the default). pos should be >= 0;
* 1: Current position - pos may be negative;
* 2: End of stream - pos usually negative.
"""
return self._file.seek(offset, whence)
def tell(self) -> int:
"""Return the current position."""
return self._file.tell()
def truncate(self, size: int | None = None) -> int:
"""Resize the stream to the given size in bytes.
If size is unspecified resize to the current position.
The current stream position isn't changed.
Return the new file size.
"""
return self._file.truncate(size)
def writable(self) -> bool:
"""Return False."""
return False
def write(self, s: Any) -> int:
raise NotImplementedError
def writelines(self, lines: Iterable[Any]) -> None:
raise NotImplementedError
class LazyFileOverHTTP(ReadOnlyIOWrapper):
"""File-like object representing a fixed-length file over HTTP.
This uses HTTP range requests to lazily fetch the file's content into a temporary
file. If such requests are not supported by the server, raises
``HTTPRangeRequestUnsupportedError`` in the ``__enter__`` method."""
def __init__(
self,
url: str,
session: Session | Authenticator,
delete_backing_file: bool = True,
) -> None:
inner = NamedTemporaryFile(delete=delete_backing_file) # noqa: SIM115
super().__init__(inner)
self._merge_intervals: MergeIntervals | None = None
self._length: int | None = None
self._request_count = 0
self._session = session
self._url = url
def __enter__(self) -> Self:
super().__enter__()
self._setup_content()
return self
def __exit__(
self,
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
traceback: TracebackType | None,
) -> None:
self._reset_content()
super().__exit__(exc_type, exc_value, traceback)
def read(self, size: int = -1) -> bytes:
"""Read up to size bytes from the object and return them.
As a convenience, if size is unspecified or -1,
all bytes until EOF are returned. Fewer than
size bytes may be returned if EOF is reached.
:raises ValueError: if ``__enter__`` was not called beforehand.
"""
if self._length is None:
raise ValueError(".__enter__() must be called to set up content length")
cur = self.tell()
logger.debug("read size %d at %d from lazy file %s", size, cur, self.name)
if size < 0:
assert cur <= self._length
download_size = self._length - cur
elif size == 0:
return b""
else:
download_size = size
stop = min(cur + download_size, self._length)
self._ensure_downloaded(cur, stop)
return super().read(download_size)
@classmethod
def _uncached_headers(cls) -> dict[str, str]:
"""HTTP headers to bypass any HTTP caching.
The requests we perform in this file are intentionally small, and any caching
should be done at a higher level.
Further, caching partial requests might cause issues:
https://github.com/pypa/pip/pull/8716
"""
# "no-cache" is the correct value for "up to date every time", so this will also
# ensure we get the most recent value from the server:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#provide_up-to-date_content_every_time
return {"Accept-Encoding": "identity", "Cache-Control": "no-cache"}
def _setup_content(self) -> None:
"""Initialize the internal length field and other bookkeeping.
Ensure ``self._merge_intervals`` is initialized.
After parsing the remote file length with ``self._fetch_content_length()``,
this method will truncate the underlying file from parent abstract class
``ReadOnlyIOWrapper`` to that size in order to support seek operations against
``io.SEEK_END`` in ``self.read()``.
Called in ``__enter__``, and should make recursive invocations into a no-op.
Subclasses may override this method."""
if self._merge_intervals is None:
self._merge_intervals = MergeIntervals()
if self._length is None:
logger.debug("begin fetching content length")
self._length = self._fetch_content_length()
logger.debug("done fetching content length (is: %d)", self._length)
# Enable us to seek and write anywhere in the backing file up to this
# known length.
self.truncate(self._length)
else:
logger.debug("content length already fetched (is: %d)", self._length)
def _reset_content(self) -> None:
"""Unset the internal length field and merge intervals.
Called in ``__exit__``, and should make recursive invocations into a no-op.
Subclasses may override this method."""
if self._merge_intervals is not None:
logger.debug(
"unsetting merge intervals (were: %s)", repr(self._merge_intervals)
)
self._merge_intervals = None
if self._length is not None:
logger.debug("unsetting content length (was: %d)", self._length)
self._length = None
def _content_length_from_head(self) -> int:
"""Performs a HEAD request to extract the Content-Length.
:raises HTTPRangeRequestUnsupportedError: if the response fails to indicate support
for "bytes" ranges."""
self._request_count += 1
head = self._session.head(
self._url, headers=self._uncached_headers(), allow_redirects=True
)
head.raise_for_status()
assert head.status_code == codes.ok
accepted_range = head.headers.get("Accept-Ranges", None)
if accepted_range != "bytes":
raise HTTPRangeRequestUnsupportedError(
f"server does not support byte ranges: header was '{accepted_range}'"
)
return int(head.headers["Content-Length"])
def _fetch_content_length(self) -> int:
"""Get the remote file's length."""
# NB: This is currently dead code, as _fetch_content_length() is overridden
# again in LazyWheelOverHTTP.
return self._content_length_from_head()
def _stream_response(self, start: int, end: int) -> Response:
"""Return streaming HTTP response to a range request from start to end."""
headers = self._uncached_headers()
headers["Range"] = f"bytes={start}-{end}"
logger.debug("streamed bytes request: %s", headers["Range"])
self._request_count += 1
response = self._session.get(self._url, headers=headers, stream=True)
try:
response.raise_for_status()
if int(response.headers["Content-Length"]) != (end - start + 1):
raise HTTPRangeRequestNotRespectedError(
f"server did not respect byte range request: "
f"requested {end - start + 1} bytes, got "
f"{response.headers['Content-Length']} bytes"
)
return response
except BaseException:
response.close()
raise
def _fetch_content_range(self, start: int, end: int) -> Iterator[bytes]:
"""Perform a series of HTTP range requests to cover the specified byte range.
NB: For compatibility with HTTP range requests, the range provided to this
method must *include* the byte indexed at argument ``end`` (so e.g. ``0-1`` is 2
bytes long, and the range can never be empty).
"""
with self._stream_response(start, end) as response:
yield from response.iter_content(CONTENT_CHUNK_SIZE)
@contextmanager
def _stay(self) -> Iterator[None]:
"""Return a context manager keeping the position.
At the end of the block, seek back to original position.
"""
pos = self.tell()
try:
yield
finally:
self.seek(pos)
def _ensure_downloaded(self, start: int, end: int) -> None:
"""Ensures bytes start to end (inclusive) have been downloaded and written to
the backing file.
:raises ValueError: if ``__enter__`` was not called beforehand.
"""
if self._merge_intervals is None:
raise ValueError(".__enter__() must be called to set up merge intervals")
# Reducing by 1 to get an inclusive end range.
end -= 1
with self._stay():
for (
range_start,
range_end,
) in self._merge_intervals.minimal_intervals_covering(start, end):
self.seek(start)
for chunk in self._fetch_content_range(range_start, range_end):
self._file.write(chunk)
class LazyWheelOverHTTP(LazyFileOverHTTP):
"""File-like object mapped to a ZIP file over HTTP.
This uses HTTP range requests to lazily fetch the file's content, which should be
provided as the first argument to a ``ZipFile``.
"""
# Cache this on the type to avoid trying and failing our initial lazy wheel request
# multiple times in the same invocation against an index without this support.
_domains_without_negative_range: ClassVar[set[str]] = set()
_metadata_regex = re.compile(r"^[^/]*\.dist-info/METADATA$")
def read_metadata(self, name: str) -> bytes:
"""Download and read the METADATA file from the remote wheel."""
with ZipFile(self) as zf:
# prefetch metadata to reduce the number of range requests
filename = self._prefetch_metadata(name)
return zf.read(filename)
@classmethod
def _initial_chunk_length(cls) -> int:
"""Return the size of the chunk (in bytes) to download from the end of the file.
This method is called in ``self._fetch_content_length()``. As noted in that
method's docstring, this should be set high enough to cover the central
directory sizes of the *average* wheels you expect to see, in order to avoid
further requests before being able to process the zip file's contents at all.
If we choose a small number, we need one more range request for larger wheels.
If we choose a big number, we download unnecessary data from smaller wheels.
If the chunk size from this method is larger than the size of an entire wheel,
that may raise an HTTP error, but this is gracefully handled in
``self._fetch_content_length()`` with a small performance penalty.
"""
return 10_000
def _fetch_content_length(self) -> int:
"""Get the total remote file length, but also download a chunk from the end.
This method is called within ``__enter__``. In an attempt to reduce
the total number of requests needed to populate this lazy file's contents, this
method will also attempt to fetch a chunk of the file's actual content. This
chunk will be ``self._initial_chunk_length()`` bytes in size, or just the remote
file's length if that's smaller, and the chunk will come from the *end* of
the file.
This method will first attempt to download with a negative byte range request,
i.e. a GET with the headers ``Range: bytes=-N`` for ``N`` equal to
``self._initial_chunk_length()``. If negative offsets are unsupported, it will
instead fall back to making a HEAD request first to extract the length, followed
by a GET request with the double-ended range header ``Range: bytes=X-Y`` to
extract the final ``N`` bytes from the remote resource.
"""
initial_chunk_size = self._initial_chunk_length()
ret_length, tail = self._extract_content_length(initial_chunk_size)
# Need to explicitly truncate here in order to perform the write and seek
# operations below when we write the chunk of file contents to disk.
self.truncate(ret_length)
if tail is None:
# If we could not download any file contents yet (e.g. if negative byte
# ranges were not supported, or the requested range was larger than the file
# size), then download all of this at once, hopefully pulling in the entire
# central directory.
initial_start = max(0, ret_length - initial_chunk_size)
self._ensure_downloaded(initial_start, ret_length)
else:
# If we *could* download some file contents, then write them to the end of
# the file and set up our bisect boundaries by hand.
with self._stay(), tail:
response_length = int(tail.headers["Content-Length"])
assert response_length == min(initial_chunk_size, ret_length)
self.seek(-response_length, io.SEEK_END)
# Default initial chunk size is currently 1MB, but streaming content
# here allows it to be set arbitrarily large.
for chunk in tail.iter_content(CONTENT_CHUNK_SIZE):
self._file.write(chunk)
# We now need to update our bookkeeping to cover the interval we just
# wrote to file so we know not to do it in later read()s.
init_chunk_start = ret_length - response_length
# MergeIntervals uses inclusive boundaries i.e. start <= x <= end.
init_chunk_end = ret_length - 1
assert self._merge_intervals is not None
assert ((init_chunk_start, init_chunk_end),) == tuple(
# NB: We expect LazyRemoteResource to reset `self._merge_intervals`
# just before it calls the current method, so our assertion here
# checks that indeed no prior overlapping intervals have
# been covered.
self._merge_intervals.minimal_intervals_covering(
init_chunk_start, init_chunk_end
)
)
return ret_length
@staticmethod
def _parse_full_length_from_content_range(arg: str) -> int:
"""Parse the file's full underlying length from the Content-Range header.
This supports both * and numeric ranges, from success or error responses:
https://www.rfc-editor.org/rfc/rfc9110#field.content-range.
"""
m = re.match(r"bytes [^/]+/([0-9]+)", arg)
if m is None:
raise HTTPRangeRequestUnsupportedError(
f"could not parse Content-Range: '{arg}'"
)
return int(m.group(1))
def _try_initial_chunk_request(
self, initial_chunk_size: int
) -> tuple[int, Response]:
"""Attempt to fetch a chunk from the end of the file with a negative offset."""
headers = self._uncached_headers()
# Perform a negative range index, which is not supported by some servers.
headers["Range"] = f"bytes=-{initial_chunk_size}"
logger.debug("initial bytes request: %s", headers["Range"])
self._request_count += 1
tail = self._session.get(self._url, headers=headers, stream=True)
try:
tail.raise_for_status()
code = tail.status_code
if code != codes.partial_content:
# According to
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests,
# a 200 OK implies that range requests are not supported,
# regardless of the requested size.
# However, some servers that support negative range requests also return a
# 200 OK if the requested range from the end was larger than the file size.
if code == codes.ok:
accept_ranges = tail.headers.get("Accept-Ranges", None)
content_length = int(tail.headers["Content-Length"])
if (
accept_ranges == "bytes"
and content_length <= initial_chunk_size
):
return content_length, tail
raise HTTPRangeRequestUnsupportedError(
f"did not receive partial content: got code {code}"
)
if "Content-Range" not in tail.headers:
raise LazyWheelUnsupportedError(
f"file length cannot be determined for {self._url}, "
f"did not receive content range header from server"
)
file_length = self._parse_full_length_from_content_range(
tail.headers["Content-Range"]
)
return (file_length, tail)
except BaseException:
tail.close()
raise
def _extract_content_length(
self, initial_chunk_size: int
) -> tuple[int, Response | None]:
"""Get the Content-Length of the remote file, and possibly a chunk of it."""
domain = urlparse(self._url).netloc
if domain in self._domains_without_negative_range:
return (self._content_length_from_head(), None)
tail: Response | None
try:
# Initial range request for just the end of the file.
file_length, tail = self._try_initial_chunk_request(initial_chunk_size)
except HTTPError as e:
# Our initial request using a negative byte range was not supported.
resp = e.response
code = resp.status_code if resp is not None else None
# This indicates that the requested range from the end was larger than the
# actual file size: https://www.rfc-editor.org/rfc/rfc9110#status.416.
if (
code == codes.requested_range_not_satisfiable
and resp is not None
and "Content-Range" in resp.headers
):
# In this case, we don't have any file content yet, but we do know the
# size the file will be, so we can return that and exit here.
file_length = self._parse_full_length_from_content_range(
resp.headers["Content-Range"]
)
return file_length, None
# pypi notably does not support negative byte ranges: see
# https://github.com/pypi/warehouse/issues/12823.
logger.debug(
"Negative byte range not supported for domain '%s': "
"using HEAD request before lazy wheel from now on (code: %s)",
domain,
code,
)
# Avoid trying a negative byte range request against this domain for the
# rest of the resolve.
self._domains_without_negative_range.add(domain)
# Apply a HEAD request to get the real size, and nothing else for now.
return self._content_length_from_head(), None
# Some servers that do not support negative offsets,
# handle a negative offset like "-10" as "0-10"...
# ... or behave even more strangely, see
# https://github.com/python-poetry/poetry/issues/9056#issuecomment-1973273721
if int(tail.headers["Content-Length"]) > initial_chunk_size or tail.headers.get(
"Content-Range", ""
).startswith("bytes -"):
tail.close()
tail = None
self._domains_without_negative_range.add(domain)
return file_length, tail
def _prefetch_metadata(self, name: str) -> str:
"""Locate the *.dist-info/METADATA entry from a temporary ``ZipFile`` wrapper,
and download it.
This method assumes that the *.dist-info directory (containing e.g. METADATA) is
contained in a single contiguous section of the zip file in order to ensure it
can be downloaded in a single ranged GET request."""
logger.debug("begin prefetching METADATA for %s", name)
start: int | None = None
end: int | None = None
# This may perform further requests if __init__() did not pull in the entire
# central directory at the end of the file (although _initial_chunk_length()
# should be set large enough to avoid this).
zf = ZipFile(self)
filename = ""
for info in zf.infolist():
if start is None:
if self._metadata_regex.search(info.filename):
filename = info.filename
start = info.header_offset
continue
else:
# The last .dist-info/ entry may be before the end of the file if the
# wheel's entries are sorted lexicographically (which is unusual).
if not self._metadata_regex.search(info.filename):
end = info.header_offset
break
if start is None:
raise UnsupportedWheelError(
f"no {self._metadata_regex!r} found for {name} in {self.name}"
)
# If it is the last entry of the zip, then give us everything
# until the start of the central directory.
if end is None:
end = zf.start_dir
logger.debug(f"fetch {filename}")
self._ensure_downloaded(start, end)
logger.debug("done prefetching METADATA for %s", name)
return filename
================================================
FILE: src/poetry/installation/__init__.py
================================================
from __future__ import annotations
from poetry.installation.installer import Installer
__all__ = ["Installer"]
================================================
FILE: src/poetry/installation/chef.py
================================================
from __future__ import annotations
import tempfile
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import TYPE_CHECKING
from poetry.utils.helpers import extractall
from poetry.utils.isolated_build import isolated_builder
if TYPE_CHECKING:
from collections.abc import Mapping
from collections.abc import Sequence
from build import DistributionType
from poetry.core.packages.dependency import Dependency
from poetry.repositories import RepositoryPool
from poetry.utils.cache import ArtifactCache
from poetry.utils.env import Env
class ChefError(Exception): ...
class Chef:
def __init__(
self, artifact_cache: ArtifactCache, env: Env, pool: RepositoryPool
) -> None:
self._env = env
self._pool = pool
self._artifact_cache = artifact_cache
def prepare(
self,
archive: Path,
output_dir: Path | None = None,
*,
editable: bool = False,
config_settings: Mapping[str, str | Sequence[str]] | None = None,
build_constraints: list[Dependency] | None = None,
) -> Path:
if not self._should_prepare(archive):
return archive
if archive.is_dir():
destination = output_dir or Path(tempfile.mkdtemp(prefix="poetry-chef-"))
return self._prepare(
archive,
destination=destination,
editable=editable,
config_settings=config_settings,
build_constraints=build_constraints,
)
return self._prepare_sdist(
archive,
destination=output_dir,
config_settings=config_settings,
build_constraints=build_constraints,
)
def _prepare(
self,
directory: Path,
destination: Path,
*,
editable: bool = False,
config_settings: Mapping[str, str | Sequence[str]] | None = None,
build_constraints: list[Dependency] | None = None,
) -> Path:
distribution: DistributionType = "editable" if editable else "wheel"
with isolated_builder(
source=directory,
distribution=distribution,
python_executable=self._env.python,
pool=self._pool,
build_constraints=build_constraints,
) as builder:
return Path(
builder.build(
distribution,
destination.as_posix(),
config_settings=config_settings,
)
)
def _prepare_sdist(
self,
archive: Path,
destination: Path | None = None,
config_settings: Mapping[str, str | Sequence[str]] | None = None,
build_constraints: list[Dependency] | None = None,
) -> Path:
from poetry.core.packages.utils.link import Link
suffix = archive.suffix
zip = suffix == ".zip"
with TemporaryDirectory(ignore_cleanup_errors=True) as tmp_dir:
archive_dir = Path(tmp_dir)
extractall(source=archive, dest=archive_dir, zip=zip)
elements = list(archive_dir.glob("*"))
if len(elements) == 1 and elements[0].is_dir():
sdist_dir = elements[0]
else:
sdist_dir = archive_dir / archive.name.rstrip(suffix)
if not sdist_dir.is_dir():
sdist_dir = archive_dir
if destination is None:
destination = self._artifact_cache.get_cache_directory_for_link(
Link(archive.as_uri())
)
destination.mkdir(parents=True, exist_ok=True)
return self._prepare(
sdist_dir,
destination,
config_settings=config_settings,
build_constraints=build_constraints,
)
def _should_prepare(self, archive: Path) -> bool:
return archive.is_dir() or not self._is_wheel(archive)
@classmethod
def _is_wheel(cls, archive: Path) -> bool:
return archive.suffix == ".whl"
================================================
FILE: src/poetry/installation/chooser.py
================================================
from __future__ import annotations
import logging
import re
from typing import TYPE_CHECKING
from typing import Any
from poetry.config.config import Config
from poetry.config.config import PackageFilterPolicy
from poetry.console.exceptions import ConsoleMessage
from poetry.console.exceptions import PoetryRuntimeError
from poetry.repositories.http_repository import HTTPRepository
from poetry.utils.helpers import get_highest_priority_hash_type
from poetry.utils.wheel import Wheel
if TYPE_CHECKING:
from poetry.core.constraints.version import Version
from poetry.core.packages.package import Package
from poetry.core.packages.utils.link import Link
from poetry.repositories.repository_pool import RepositoryPool
from poetry.utils.env import Env
logger = logging.getLogger(__name__)
class Chooser:
"""
A Chooser chooses an appropriate release archive for packages.
"""
def __init__(
self, pool: RepositoryPool, env: Env, config: Config | None = None
) -> None:
self._pool = pool
self._env = env
self._config = config or Config.create()
self._no_binary_policy: PackageFilterPolicy = PackageFilterPolicy(
self._config.get("installer.no-binary", [])
)
self._only_binary_policy: PackageFilterPolicy = PackageFilterPolicy(
self._config.get("installer.only-binary", [])
)
def choose_for(self, package: Package) -> Link:
"""
Return the url of the selected archive for a given package.
"""
links = []
# these are used only for providing insightful errors to the user
unsupported_wheels = set()
links_seen = 0
wheels_skipped = 0
sdists_skipped = 0
for link in self._get_links(package):
links_seen += 1
if link.is_wheel:
if (
# exact package name must reject wheel, even if `only-binary` includes it
self._no_binary_policy.has_exact_package(package.name)
# `:all:` reject wheel only if `only-binary` does not include it
or (
not self._no_binary_policy.allows(package.name)
and not self._only_binary_policy.has_exact_package(package.name)
)
):
logger.debug(
"Skipping wheel for %s as requested in no binary policy for"
" package (%s)",
link.filename,
package.name,
)
wheels_skipped += 1
continue
if not Wheel(link.filename).is_supported_by_environment(self._env):
logger.debug(
"Skipping wheel %s as this is not supported by the current"
" environment",
link.filename,
)
unsupported_wheels.add(link.filename)
continue
if link.ext in {".egg", ".exe", ".msi", ".rpm", ".srpm"}:
logger.debug("Skipping unsupported distribution %s", link.filename)
continue
if link.is_sdist and (
# exact package name must reject sdist, even if `no-binary` includes it
self._only_binary_policy.has_exact_package(package.name)
# `:all:` reject sdist only if `no-binary` does not include it
or (
not self._only_binary_policy.allows(package.name)
and not self._no_binary_policy.has_exact_package(package.name)
)
):
logger.debug(
"Skipping source distribution for %s as requested in only binary policy for"
" package (%s)",
link.filename,
package.name,
)
sdists_skipped += 1
continue
links.append(link)
if not links:
raise self._no_links_found_error(
package, links_seen, wheels_skipped, sdists_skipped, unsupported_wheels
)
# Get the best link
chosen = max(links, key=lambda link: self._sort_key(package, link))
return chosen
def _no_links_found_error(
self,
package: Package,
links_seen: int,
wheels_skipped: int,
sdists_skipped: int,
unsupported_wheels: set[str],
) -> PoetryRuntimeError:
messages = []
info = (
f"This is likely not a Poetry issue.\n\n"
f" - {links_seen} candidate(s) were identified for the package\n"
)
if wheels_skipped > 0:
info += f" - {wheels_skipped} wheel(s) were skipped due to your installer.no-binary> policy\n"
if sdists_skipped > 0:
info += f" - {sdists_skipped} source distribution(s) were skipped due to your installer.only-binary> policy\n"
if unsupported_wheels:
info += (
f" - {len(unsupported_wheels)} wheel(s) were skipped as your project's environment does not support "
f"the identified abi tags\n"
)
messages.append(ConsoleMessage(info.strip()))
if unsupported_wheels:
messages += [
ConsoleMessage(
"The following wheel(s) were skipped as the current project environment does not support them "
"due to abi compatibility issues.",
debug=True,
),
ConsoleMessage("\n".join(unsupported_wheels), debug=True)
.indent(" - ")
.wrap("warning"),
ConsoleMessage(
"If you would like to see the supported tags in your project environment, you can execute "
"the following command:\n\n"
" poetry debug tags>",
debug=True,
),
]
source_hint = ""
if package.source_type and package.source_reference:
source_hint += f" ({package.source_reference})"
messages.append(
ConsoleMessage(
f"Make sure the lockfile is up-to-date. You can try one of the following;\n\n"
f" 1. Regenerate lockfile: >poetry lock --no-cache --regenerate>\n"
f" 2. Update package : >poetry update --no-cache {package.name}>\n\n"
# FIXME: In the future, it would be better to suggest a more targeted
# cache clear command for just the package in question. E.g.
# `poetry cache clear {package.source_reference}:{package.name}:{package.version}`
# but `package.source_reference` currently resolves to `None` because
# repository names are case sensitive at the moment (`PyPI` vs `pypi`).
f"If any of those solutions worked, you will have to clear your caches using (poetry cache clear --all>).\n\n"
f"If neither works, please first check to verify that the {package.name} has published wheels "
f"available from your configured source{source_hint} that are compatible with your environment"
f"- ie. operating system, architecture (x86_64, arm64 etc.), python interpreter."
)
.make_section("Solutions")
.wrap("info")
)
return PoetryRuntimeError(
reason=f"Unable to find installation candidates for {package}",
messages=messages,
)
def _get_links(self, package: Package) -> list[Link]:
if package.source_type:
assert package.source_reference is not None
repository = self._pool.repository(package.source_reference)
elif not self._pool.has_repository("pypi"):
repository = self._pool.repositories[0]
else:
repository = self._pool.repository("pypi")
links = repository.find_links_for_package(package)
locked_hashes = {f["hash"] for f in package.files}
if not locked_hashes:
return links
selected_links = []
skipped = []
locked_hash_names = {h.split(":")[0] for h in locked_hashes}
for link in links:
if not link.hashes:
selected_links.append(link)
continue
link_hash: str | None = None
if (candidates := locked_hash_names.intersection(link.hashes.keys())) and (
hash_name := get_highest_priority_hash_type(candidates, link.filename)
):
link_hash = f"{hash_name}:{link.hashes[hash_name]}"
elif isinstance(repository, HTTPRepository):
link_hash = repository.calculate_sha256(link)
if link_hash not in locked_hashes:
skipped.append((link.filename, link_hash))
logger.debug(
"Skipping %s as %s checksum does not match expected value",
link.filename,
link_hash,
)
continue
selected_links.append(link)
if links and not selected_links:
reason = f"Downloaded distributions for {package.pretty_name} ({package.pretty_version})> did not match any known checksums in your lock file."
link_hashes = "\n".join(f" - {link}({h})" for link, h in skipped)
known_hashes = "\n".join(f" - {h}" for h in locked_hashes)
messages = [
ConsoleMessage(
"Causes:>\n"
" - invalid or corrupt cache either during locking or installation\n"
" - network interruptions or errors causing corrupted downloads\n\n"
"Solutions:>\n"
" 1. Try running your command again using the --no-cache> global option enabled.\n"
" 2. Try regenerating your lock file using (poetry lock --no-cache --regenerate>).\n\n"
"If any of those solutions worked, you will have to clear your caches using (poetry cache clear --all CACHE_NAME>)."
),
ConsoleMessage(
f"Poetry retrieved the following links:\n"
f"{link_hashes}\n\n"
f"The lockfile contained only the following hashes:\n"
f"{known_hashes}",
debug=True,
),
]
raise PoetryRuntimeError(reason, messages)
return selected_links
def _sort_key(
self, package: Package, link: Link
) -> tuple[int, int, int, Version, tuple[Any, ...], int]:
"""
Function to pass as the `key` argument to a call to sorted() to sort
InstallationCandidates by preference.
Returns a tuple such that tuples sorting as greater using Python's
default comparison operator are more preferred.
The preference is as follows:
First and foremost, candidates with allowed (matching) hashes are
always preferred over candidates without matching hashes. This is
because e.g. if the only candidate with an allowed hash is yanked,
we still want to use that candidate.
Second, excepting hash considerations, candidates that have been
yanked (in the sense of PEP 592) are always less preferred than
candidates that haven't been yanked. Then:
If not finding wheels, they are sorted by version only.
If finding wheels, then the sort order is by version, then:
1. existing installs
2. wheels ordered via Wheel.support_index_min(self._supported_tags)
3. source archives
If prefer_binary was set, then all wheels are sorted above sources.
Note: it was considered to embed this logic into the Link
comparison operators, but then different sdist links
with the same version, would have to be considered equal
"""
build_tag: tuple[Any, ...] = ()
binary_preference = 0
if link.is_wheel:
wheel = Wheel(link.filename)
if not wheel.is_supported_by_environment(self._env):
raise RuntimeError(
f"{wheel.filename} is not a supported wheel for this platform. It "
"can't be sorted."
)
# TODO: Binary preference
pri = -(wheel.get_minimum_supported_index(self._env.supported_tags) or 0)
if wheel.build_tag is not None:
match = re.match(r"^(\d+)(.*)$", wheel.build_tag)
if not match:
raise ValueError(f"Unable to parse build tag: {wheel.build_tag}")
build_tag_groups = match.groups()
build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
else: # sdist
support_num = len(self._env.supported_tags)
pri = -support_num
has_allowed_hash = int(self._is_link_hash_allowed_for_package(link, package))
yank_value = int(not link.yanked)
return (
has_allowed_hash,
yank_value,
binary_preference,
package.version,
build_tag,
pri,
)
def _is_link_hash_allowed_for_package(self, link: Link, package: Package) -> bool:
if not link.hashes:
return True
link_hashes = {f"{name}:{h}" for name, h in link.hashes.items()}
locked_hashes = {f["hash"] for f in package.files}
return bool(link_hashes & locked_hashes)
================================================
FILE: src/poetry/installation/executor.py
================================================
from __future__ import annotations
import csv
import functools
import itertools
import json
import threading
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import wait
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from poetry.core.packages.utils.link import Link
from poetry.console.exceptions import PoetryRuntimeError
from poetry.installation.chef import Chef
from poetry.installation.chooser import Chooser
from poetry.installation.operations import Install
from poetry.installation.operations import Uninstall
from poetry.installation.operations import Update
from poetry.installation.wheel_installer import WheelInstaller
from poetry.puzzle.exceptions import SolverProblemError
from poetry.utils._compat import decode
from poetry.utils.authenticator import Authenticator
from poetry.utils.env import EnvCommandError
from poetry.utils.helpers import Downloader
from poetry.utils.helpers import get_file_hash
from poetry.utils.helpers import get_highest_priority_hash_type
from poetry.utils.helpers import pluralize
from poetry.utils.helpers import remove_directory
from poetry.utils.isolated_build import IsolatedBuildBackendError
from poetry.utils.isolated_build import IsolatedBuildInstallError
from poetry.utils.log_utils import format_build_wheel_log
from poetry.vcs.git import Git
if TYPE_CHECKING:
from collections.abc import Mapping
from collections.abc import Sequence
from cleo.io.io import IO
from cleo.io.outputs.section_output import SectionOutput
from packaging.utils import NormalizedName
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package
from poetry.config.config import Config
from poetry.installation.operations.operation import Operation
from poetry.repositories import RepositoryPool
from poetry.utils.env import Env
def _package_get_name(package: Package) -> str | None:
if url := package.repository_url:
return Git.get_name_from_source_url(url)
return None
class Executor:
def __init__(
self,
env: Env,
pool: RepositoryPool,
config: Config,
io: IO,
parallel: bool | None = None,
disable_cache: bool = False,
*,
build_constraints: Mapping[NormalizedName, list[Dependency]] | None = None,
) -> None:
self._env = env
self._io = io
self._dry_run = False
self._enabled = True
self._verbose = False
self._wheel_installer = WheelInstaller(self._env)
self._build_constraints = build_constraints or {}
if parallel is None:
parallel = config.get("installer.parallel", True)
if parallel:
self._max_workers = config.installer_max_workers
else:
self._max_workers = 1
self._artifact_cache = pool.artifact_cache
self._authenticator = Authenticator(
config, self._io, disable_cache=disable_cache, pool_size=self._max_workers
)
self._chef = Chef(self._artifact_cache, self._env, pool)
self._chooser = Chooser(pool, self._env, config)
self._executor = ThreadPoolExecutor(max_workers=self._max_workers)
self._executed = {"install": 0, "update": 0, "uninstall": 0}
self._skipped = {"install": 0, "update": 0, "uninstall": 0}
self._sections: dict[int, SectionOutput] = {}
self._yanked_warnings: list[str] = []
self._lock = threading.Lock()
self._shutdown = False
self._hashes: dict[str, str] = {}
# Cache whether decorated output is supported.
# https://github.com/python-poetry/cleo/issues/423
self._decorated_output: bool = self._io.output.is_decorated()
self._max_retries = config.get("requests.max-retries", 0)
# sdist build config settings
self._build_config_settings: Mapping[
NormalizedName, Mapping[str, str | Sequence[str]]
] = config.get("installer.build-config-settings")
@property
def installations_count(self) -> int:
return self._executed["install"]
@property
def updates_count(self) -> int:
return self._executed["update"]
@property
def removals_count(self) -> int:
return self._executed["uninstall"]
@property
def enabled(self) -> bool:
return self._enabled
def supports_fancy_output(self) -> bool:
return self._decorated_output and not self._dry_run
def disable(self) -> Executor:
self._enabled = False
return self
def dry_run(self, dry_run: bool = True) -> Executor:
self._dry_run = dry_run
return self
def verbose(self, verbose: bool = True) -> Executor:
self._verbose = verbose
return self
def enable_bytecode_compilation(self, enable: bool = True) -> None:
self._wheel_installer.enable_bytecode_compilation(enable)
def execute(self, operations: list[Operation]) -> int:
for job_type in self._executed:
self._executed[job_type] = 0
self._skipped[job_type] = 0
if operations and (self._enabled or self._dry_run):
self._display_summary(operations)
self._sections = {}
self._yanked_warnings = []
# pip has to be installed/updated first without parallelism
# because we still need it for uninstalls
for i, op in enumerate(operations):
if op.package.name == "pip":
wait([self._executor.submit(self._execute_operation, op)])
del operations[i]
break
# We group operations by priority
groups = itertools.groupby(operations, key=lambda o: -o.priority)
for _, group in groups:
tasks = []
serial_operations = []
serial_git_operations = defaultdict(list)
for operation in group:
if self._shutdown:
break
# Some operations are unsafe, we must execute them serially in a group
# https://github.com/python-poetry/poetry/issues/3086
# https://github.com/python-poetry/poetry/issues/2658
#
# We need to explicitly check source type here, see:
# https://github.com/python-poetry/poetry-core/pull/98
is_parallel_unsafe = operation.job_type == "uninstall" or (
operation.package.develop
and operation.package.source_type in {"directory", "git"}
)
# Skipped operations are safe to execute in parallel
if operation.skipped:
is_parallel_unsafe = False
if is_parallel_unsafe:
serial_operations.append(operation)
elif operation.package.source_type == "git":
# Serially execute git operations that get cloned to the same directory,
# to prevent multiple parallel git operations in the same repo.
serial_git_operations[_package_get_name(operation.package)].append(
operation
)
else:
tasks.append(
self._executor.submit(self._execute_operation, operation)
)
def _serialize(
repository_serial_operations: list[Operation],
) -> None:
for operation in repository_serial_operations:
self._execute_operation(operation)
# For each git repository, execute all operations serially
for repository_git_operations in serial_git_operations.values():
tasks.append(
self._executor.submit(
_serialize,
repository_serial_operations=repository_git_operations,
)
)
try:
wait(tasks)
for operation in serial_operations:
self._execute_operation(operation)
except KeyboardInterrupt:
self._shutdown = True
if self._shutdown:
self._executor.shutdown(wait=True, cancel_futures=True)
break
for warning in self._yanked_warnings:
self._io.write_error_line(f"Warning: {warning}")
for path, issues in self._wheel_installer.invalid_wheels.items():
formatted_issues = "\n".join(issues)
warning = (
f"Validation of the RECORD file of {path.name} failed."
" Please report to the maintainers of that package so they can fix"
f" their build process. Details:\n{formatted_issues}\n"
)
self._io.write_error_line(f"Warning: {warning}")
return 1 if self._shutdown else 0
def _write(self, operation: Operation, line: str) -> None:
if not self.supports_fancy_output() or not self._should_write_operation(
operation
):
return
if self._io.is_debug():
with self._lock:
section = self._sections[id(operation)]
section.write_line(line)
return
with self._lock:
section = self._sections[id(operation)]
section.clear()
section.write(line)
def _execute_operation(self, operation: Operation) -> None:
try:
op_message = self.get_operation_message(operation)
if self.supports_fancy_output():
if id(operation) not in self._sections and self._should_write_operation(
operation
):
with self._lock:
self._sections[id(operation)] = self._io.section()
self._sections[id(operation)].write_line(
f" -> {op_message}:"
" Pending...>"
)
else:
if self._should_write_operation(operation):
if not operation.skipped:
self._io.write_line(
f" -> {op_message}"
)
else:
self._io.write_line(
f" -> {op_message}: "
"Skipped> "
"for the following reason:> "
f"{operation.skip_reason}>"
)
try:
result = self._do_execute_operation(operation)
except EnvCommandError as e:
if e.e.returncode == -2:
result = -2
else:
raise
# If we have a result of -2 it means a KeyboardInterrupt
# in the any python subprocess, so we raise a KeyboardInterrupt
# error to be picked up by the error handler.
if result == -2:
raise KeyboardInterrupt
except Exception as e:
try:
from cleo.ui.exception_trace import ExceptionTrace
io: IO | SectionOutput
if not self.supports_fancy_output():
io = self._io
else:
message = (
" -"
f" {self.get_operation_message(operation, error=True)}:"
" Failed"
)
self._write(operation, message)
io = self._sections.get(id(operation), self._io)
with self._lock:
pkg = operation.package
with_trace = True
if isinstance(e, IsolatedBuildBackendError):
# TODO: Revisit once upstream fix is available https://github.com/python-poetry/cleo/issues/454
# we disable trace here explicitly to workaround incorrect context detection by crashtest
with_trace = False
pip_command = "pip wheel --no-cache-dir --use-pep517"
if pkg.develop:
if pkg.source_type == "git":
git_url_parts = (
pkg.to_dependency()
.to_pep_508()
.split(";", 1)[0]
.split("@", 1)[-1]
.strip()
).split("#", 1)
requirement = f"{git_url_parts[0]}#egg={pkg.name}"
if len(git_url_parts) > 1:
requirement += f"&{git_url_parts[1]}"
else:
assert pkg.source_url
requirement = pkg.source_url
pip_command += " --editable"
else:
requirement = (
pkg.to_dependency().to_pep_508().split(";")[0].strip()
)
if config_settings := self._build_config_settings.get(pkg.name):
for setting in config_settings:
for setting_value in config_settings[setting]:
pip_command += f" --config-settings='{setting}={setting_value}'"
message = e.generate_message(
source_string=f"{pkg.pretty_name} ({pkg.full_pretty_version})",
build_command=f'{pip_command} "{requirement}"',
)
elif isinstance(e, IsolatedBuildInstallError):
message = (
""
"Cannot install build-system.requires"
f" for {pkg.pretty_name}."
""
)
elif isinstance(e, SolverProblemError):
message = (
""
"Cannot resolve build-system.requires"
f" for {pkg.pretty_name}."
""
)
elif isinstance(e, PoetryRuntimeError):
message = e.get_text(io.is_verbose(), indent=" | ").rstrip()
message = f"{message}>"
with_trace = False
else:
message = f"Cannot install {pkg.pretty_name}."
if with_trace:
ExceptionTrace(e).render(io)
io.write_line("")
io.write_line(message)
io.write_line("")
finally:
with self._lock:
self._shutdown = True
except KeyboardInterrupt:
try:
message = (
" -"
f" {self.get_operation_message(operation, warning=True)}:"
" Cancelled"
)
if not self.supports_fancy_output():
self._io.write_line(message)
else:
self._write(operation, message)
finally:
with self._lock:
self._shutdown = True
def _do_execute_operation(self, operation: Operation) -> int:
method = operation.job_type
operation_message = self.get_operation_message(operation)
if operation.skipped:
if self.supports_fancy_output():
self._write(
operation,
f" -> {operation_message}: "
"Skipped> "
"for the following reason:> "
f"{operation.skip_reason}>",
)
self._skipped[operation.job_type] += 1
return 0
if not self._enabled or self._dry_run:
return 0
result: int = getattr(self, f"_execute_{method}")(operation)
if result != 0:
return result
operation_message = self.get_operation_message(operation, done=True)
message = f" -> {operation_message}"
self._write(operation, message)
self._increment_operations_count(operation, True)
return result
def _increment_operations_count(self, operation: Operation, executed: bool) -> None:
with self._lock:
if executed:
self._executed[operation.job_type] += 1
else:
self._skipped[operation.job_type] += 1
def run_pip(self, *args: Any, **kwargs: Any) -> int:
try:
self._env.run_pip(*args, **kwargs)
except EnvCommandError as e:
output = decode(e.e.output)
if (
"KeyboardInterrupt" in output
or "ERROR: Operation cancelled by user" in output
):
return -2
raise
return 0
def get_operation_message(
self,
operation: Operation,
done: bool = False,
error: bool = False,
warning: bool = False,
) -> str:
base_tag = "fg=default"
operation_color = "c2"
source_operation_color = "c2"
package_color = "c1"
if error:
operation_color = "error"
elif warning:
operation_color = "warning"
elif done:
operation_color = "success"
if operation.skipped:
base_tag = "fg=default;options=dark"
operation_color += "_dark"
source_operation_color += "_dark"
package_color += "_dark"
if isinstance(operation, Install):
return (
f"<{base_tag}>Installing"
f" <{package_color}>{operation.package.name}{package_color}>"
f" (<{operation_color}>{operation.package.full_pretty_version}>)>"
)
if isinstance(operation, Uninstall):
return (
f"<{base_tag}>Removing"
f" <{package_color}>{operation.package.name}{package_color}>"
f" (<{operation_color}>{operation.package.full_pretty_version}>)>"
)
if isinstance(operation, Update):
initial_version = (initial_pkg := operation.initial_package).version
target_version = (target_pkg := operation.target_package).version
update_kind = (
"Updating" if target_version >= initial_version else "Downgrading"
)
return (
f"<{base_tag}>{update_kind}"
f" <{package_color}>{initial_pkg.name}{package_color}> "
f"(<{source_operation_color}>"
f"{initial_pkg.full_pretty_version}"
f"{source_operation_color}> -> <{operation_color}>"
f"{target_pkg.full_pretty_version}>)>"
)
return ""
def _display_summary(self, operations: list[Operation]) -> None:
installs = 0
updates = 0
uninstalls = 0
skipped = 0
for op in operations:
if op.skipped:
skipped += 1
continue
if op.job_type == "install":
installs += 1
elif op.job_type == "update":
updates += 1
elif op.job_type == "uninstall":
uninstalls += 1
if not installs and not updates and not uninstalls and not self._verbose:
self._io.write_line("")
self._io.write_line("No dependencies to install or update")
return
self._io.write_line("")
self._io.write("Package operations: ")
self._io.write(f"{installs}> install{pluralize(installs)}, ")
self._io.write(f"{updates}> update{pluralize(updates)}, ")
self._io.write(f"{uninstalls}> removal{pluralize(uninstalls)}")
if skipped and self._verbose:
self._io.write(f", {skipped}> skipped")
self._io.write_line("")
self._io.write_line("")
def _execute_install(self, operation: Install | Update) -> int:
status_code = self._install(operation)
self._save_url_reference(operation)
return status_code
def _execute_update(self, operation: Install | Update) -> int:
status_code = self._update(operation)
self._save_url_reference(operation)
return status_code
def _execute_uninstall(self, operation: Uninstall) -> int:
op_msg = self.get_operation_message(operation)
message = f" -> {op_msg}: Removing..."
self._write(operation, message)
return self._remove(operation.package)
def _install(self, operation: Install | Update) -> int:
package = operation.package
cleanup_archive: bool = False
if package.source_type == "git":
archive = self._prepare_git_archive(operation)
cleanup_archive = operation.package.develop
elif package.source_type == "file":
archive = self._prepare_archive(operation)
elif package.source_type == "directory":
archive = self._prepare_archive(operation)
cleanup_archive = True
elif package.source_type == "url":
assert package.source_url is not None
archive = self._download_link(operation, Link(package.source_url))
else:
archive = self._download(operation)
operation_message = self.get_operation_message(operation)
message = (
f" -> {operation_message}:"
" Installing..."
)
self._write(operation, message)
try:
if operation.job_type == "update":
# Uninstall first
# TODO: Make an uninstaller and find a way to rollback in case
# the new package can't be installed
assert isinstance(operation, Update)
self._remove(operation.initial_package)
self._wheel_installer.install(archive)
finally:
if cleanup_archive:
archive.unlink()
return 0
def _update(self, operation: Install | Update) -> int:
return self._install(operation)
def _remove(self, package: Package) -> int:
# If we have a VCS package, remove its source directory
if package.source_type == "git":
src_dir = self._env.path / "src" / package.name
if src_dir.exists():
remove_directory(src_dir, force=True)
try:
return self.run_pip("uninstall", package.name, "-y")
except EnvCommandError as e:
if "not installed" in str(e):
return 0
raise
def _prepare_archive(
self, operation: Install | Update, *, output_dir: Path | None = None
) -> Path:
package = operation.package
operation_message = self.get_operation_message(operation)
message = (
f" -> {operation_message}:"
f"{format_build_wheel_log(package, self._env)}"
)
self._write(operation, message)
assert package.source_url is not None
archive = Path(package.source_url)
if package.source_subdirectory:
archive = archive / package.source_subdirectory
if not Path(package.source_url).is_absolute() and package.root_dir:
archive = package.root_dir / archive
self._populate_hashes_dict(archive, package)
name = operation.package.name
return self._chef.prepare(
archive,
editable=package.develop,
output_dir=output_dir,
config_settings=self._build_config_settings.get(name),
build_constraints=self._build_constraints.get(name),
)
def _prepare_git_archive(self, operation: Install | Update) -> Path:
package = operation.package
assert package.source_url is not None
if package.source_resolved_reference and not package.develop:
# Only cache git archives when we know precise reference hash,
# otherwise we might get stale archives
cached_archive = self._artifact_cache.get_cached_archive_for_git(
package.source_url,
package.source_resolved_reference,
package.source_subdirectory,
env=self._env,
)
if cached_archive is not None:
return cached_archive
operation_message = self.get_operation_message(operation)
message = (
f" -> {operation_message}: Cloning..."
)
self._write(operation, message)
source = Git.clone(
url=package.source_url,
source_root=self._env.path / "src",
revision=package.source_resolved_reference or package.source_reference,
)
# Now we just need to install from the source directory
original_url = package.source_url
package._source_url = str(source.path)
output_dir = None
if package.source_resolved_reference and not package.develop:
output_dir = self._artifact_cache.get_cache_directory_for_git(
original_url,
package.source_resolved_reference,
package.source_subdirectory,
)
try:
archive = self._prepare_archive(operation, output_dir=output_dir)
except Exception:
# always reset source_url in case of an error for correct output
package._source_url = original_url
raise
if not package.develop:
package._source_url = original_url
if output_dir is not None and output_dir.is_dir():
# Mark directories with cached git packages, to distinguish from
# "normal" cache
(output_dir / ".created_from_git_dependency").touch()
return archive
def _download(self, operation: Install | Update) -> Path:
link = self._chooser.choose_for(operation.package)
if link.yanked:
# Store yanked warnings in a list and print after installing, so they can't
# be overlooked. Further, printing them in the concerning section would have
# the risk of overwriting the warning, so it is only briefly visible.
message = (
f"The file chosen for install of {operation.package.pretty_name} "
f"{operation.package.pretty_version} ({link.show_url}) is yanked."
)
if link.yanked_reason:
message += f" Reason for being yanked: {link.yanked_reason}"
self._yanked_warnings.append(message)
return self._download_link(operation, link)
def _download_link(self, operation: Install | Update, link: Link) -> Path:
package = operation.package
# Get original package for the link provided
download_func = functools.partial(self._download_archive, operation)
original_archive = self._artifact_cache.get_cached_archive_for_link(
link, strict=True, download_func=download_func
)
# Get potential higher prioritized cached archive, otherwise it will fall back
# to the original archive.
archive = self._artifact_cache.get_cached_archive_for_link(
link,
strict=False,
env=self._env,
)
if archive is None:
# Since we previously downloaded an archive, we now should have
# something cached that we can use here. The only case in which
# archive is None is if the original archive is not valid for the
# current environment.
raise RuntimeError(
f"Package {link.url} cannot be installed in the current environment"
f" {self._env.marker_env}"
)
if archive.suffix != ".whl":
message = (
f" -> {self.get_operation_message(operation)}:"
f"{format_build_wheel_log(package, self._env)}"
)
self._write(operation, message)
name = operation.package.name
archive = self._chef.prepare(
archive,
output_dir=original_archive.parent,
config_settings=self._build_config_settings.get(name),
build_constraints=self._build_constraints.get(name),
)
# Use the original archive to provide the correct hash.
self._populate_hashes_dict(original_archive, package)
return archive
def _populate_hashes_dict(self, archive: Path, package: Package) -> None:
if package.files and archive.name in {f["file"] for f in package.files}:
archive_hash = self._validate_archive_hash(archive, package)
self._hashes[package.name] = archive_hash
@staticmethod
def _validate_archive_hash(archive: Path, package: Package) -> str:
known_hashes = {f["hash"] for f in package.files if f["file"] == archive.name}
hash_types = {t.split(":")[0] for t in known_hashes}
hash_type = get_highest_priority_hash_type(hash_types, archive.name)
if hash_type is None:
raise RuntimeError(
f"No usable hash type(s) for {package} from archive"
f" {archive.name} found (known hashes: {known_hashes!s})"
)
archive_hash = f"{hash_type}:{get_file_hash(archive, hash_type)}"
if archive_hash not in known_hashes:
raise RuntimeError(
f"Hash for {package} from archive {archive.name} not found in"
f" known hashes (was: {archive_hash})"
)
return archive_hash
def _download_archive(
self,
operation: Install | Update,
url: str,
dest: Path,
) -> None:
downloader = Downloader(
url, dest, self._authenticator, max_retries=self._max_retries
)
wheel_size = downloader.total_size
operation_message = self.get_operation_message(operation)
message = (
f" -> {operation_message}: Downloading...>"
)
progress = None
if self.supports_fancy_output():
if wheel_size is None:
self._write(operation, message)
else:
from cleo.ui.progress_bar import ProgressBar
progress = ProgressBar(
self._sections[id(operation)], max=int(wheel_size)
)
progress.set_format(message + " %percent%%")
if progress:
with self._lock:
self._sections[id(operation)].clear()
progress.start()
for fetched_size in downloader.download_with_progress(chunk_size=4096):
if progress:
with self._lock:
progress.set_progress(fetched_size)
if progress:
with self._lock:
progress.finish()
def _should_write_operation(self, operation: Operation) -> bool:
return (
not operation.skipped or self._dry_run or self._verbose or not self._enabled
)
def _save_url_reference(self, operation: Operation) -> None:
"""
Create and store a PEP-610 `direct_url.json` file, if needed.
"""
if operation.job_type not in {"install", "update"}:
return
package = operation.package
if not package.source_url or package.source_type == "legacy":
return
url_reference: dict[str, Any] | None = None
if package.source_type == "git" and not package.develop:
url_reference = self._create_git_url_reference(package)
elif package.source_type in ("directory", "git"):
url_reference = self._create_directory_url_reference(package)
elif package.source_type == "url":
url_reference = self._create_url_url_reference(package)
elif package.source_type == "file":
url_reference = self._create_file_url_reference(package)
if url_reference:
for dist in self._env.site_packages.distributions(
name=package.name, writable_only=True
):
dist_path = dist._path # type: ignore[attr-defined]
assert isinstance(dist_path, Path)
url = dist_path / "direct_url.json"
url.write_text(json.dumps(url_reference), encoding="utf-8")
record = dist_path / "RECORD"
if record.exists():
with record.open(mode="a", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
path = url.relative_to(record.parent.parent)
writer.writerow([str(path), "", ""])
def _create_git_url_reference(self, package: Package) -> dict[str, Any]:
reference = {
"url": package.source_url,
"vcs_info": {
"vcs": "git",
"requested_revision": package.source_reference,
"commit_id": package.source_resolved_reference,
},
}
if package.source_subdirectory:
reference["subdirectory"] = package.source_subdirectory
return reference
def _create_url_url_reference(self, package: Package) -> dict[str, Any]:
archive_info = self._get_archive_info(package)
return {"url": package.source_url, "archive_info": archive_info}
def _create_file_url_reference(self, package: Package) -> dict[str, Any]:
archive_info = self._get_archive_info(package)
assert package.source_url is not None
return {
"url": Path(package.source_url).as_uri(),
"archive_info": archive_info,
}
def _create_directory_url_reference(self, package: Package) -> dict[str, Any]:
dir_info = {}
if package.develop:
dir_info["editable"] = True
assert package.source_url is not None
return {
"url": Path(package.source_url).as_uri(),
"dir_info": dir_info,
}
def _get_archive_info(self, package: Package) -> dict[str, Any]:
"""
Create dictionary `archive_info` for file `direct_url.json`.
Specification: https://packaging.python.org/en/latest/specifications/direct-url
(it supersedes PEP 610)
:param package: This must be a poetry package instance.
"""
archive_info = {}
if package.name in self._hashes:
algorithm, value = self._hashes[package.name].split(":")
archive_info["hashes"] = {algorithm: value}
return archive_info
================================================
FILE: src/poetry/installation/installer.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import cast
from cleo.io.null_io import NullIO
from packaging.utils import canonicalize_name
from poetry.installation.executor import Executor
from poetry.puzzle.transaction import Transaction
from poetry.repositories import Repository
from poetry.repositories import RepositoryPool
from poetry.repositories.installed_repository import InstalledRepository
from poetry.repositories.lockfile_repository import LockfileRepository
from poetry.utils.constants import POETRY_SYSTEM_PROJECT_NAME
if TYPE_CHECKING:
from collections.abc import Iterable
from collections.abc import Mapping
from cleo.io.io import IO
from packaging.utils import NormalizedName
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package
from poetry.core.packages.path_dependency import PathDependency
from poetry.core.packages.project_package import ProjectPackage
from poetry.config.config import Config
from poetry.installation.operations.operation import Operation
from poetry.packages import Locker
from poetry.packages.transitive_package_info import TransitivePackageInfo
from poetry.utils.env import Env
class Installer:
def __init__(
self,
io: IO,
env: Env,
package: ProjectPackage,
locker: Locker,
pool: RepositoryPool,
config: Config,
installed: InstalledRepository | None = None,
executor: Executor | None = None,
disable_cache: bool = False,
*,
build_constraints: Mapping[NormalizedName, list[Dependency]] | None = None,
) -> None:
self._io = io
self._env = env
self._package = package
self._locker = locker
self._pool = pool
self._config = config
self._dry_run = False
self._requires_synchronization = False
self._update = False
self._verbose = False
self._groups: Iterable[NormalizedName] | None = None
self._skip_directory = False
self._lock = False
self._whitelist: list[NormalizedName] = []
self._extras: list[NormalizedName] = []
if executor is None:
executor = Executor(
self._env,
self._pool,
config,
self._io,
disable_cache=disable_cache,
build_constraints=build_constraints,
)
self._executor = executor
if installed is None:
installed = self._get_installed()
self._installed_repository = installed
@property
def executor(self) -> Executor:
return self._executor
def set_package(self, package: ProjectPackage) -> Installer:
self._package = package
return self
def set_locker(self, locker: Locker) -> Installer:
self._locker = locker
return self
def run(self) -> int:
# Check if refresh
if not self._update and self._lock and self._locker.is_locked():
return self._do_refresh()
# Force update if there is no lock file present
if not self._update and not self._locker.is_locked():
self._update = True
if self.is_dry_run():
self.verbose(True)
return self._do_install()
def dry_run(self, dry_run: bool = True) -> Installer:
self._dry_run = dry_run
self._executor.dry_run(dry_run)
return self
def is_dry_run(self) -> bool:
return self._dry_run
def requires_synchronization(
self, requires_synchronization: bool = True
) -> Installer:
self._requires_synchronization = requires_synchronization
return self
def verbose(self, verbose: bool = True) -> Installer:
self._verbose = verbose
self._executor.verbose(verbose)
return self
def is_verbose(self) -> bool:
return self._verbose
def only_groups(self, groups: Iterable[NormalizedName]) -> Installer:
self._groups = groups
return self
def update(self, update: bool = True) -> Installer:
self._update = update
return self
def skip_directory(self, skip_directory: bool = False) -> Installer:
self._skip_directory = skip_directory
return self
def lock(self, update: bool = True) -> Installer:
"""
Prepare the installer for locking only.
"""
self.update(update=update)
self.execute_operations(False)
self._lock = True
return self
def is_updating(self) -> bool:
return self._update
def execute_operations(self, execute: bool = True) -> Installer:
if not execute:
self._executor.disable()
return self
def whitelist(self, packages: Iterable[str]) -> Installer:
self._whitelist = [canonicalize_name(p) for p in packages]
return self
def extras(self, extras: list[str]) -> Installer:
self._extras = [canonicalize_name(extra) for extra in extras]
return self
def _do_refresh(self) -> int:
from poetry.puzzle.solver import Solver
# Checking extras
for extra in self._extras:
if extra not in self._package.extras:
raise ValueError(f"Extra [{extra}] is not specified.")
locked_repository = self._locker.locked_repository()
solver = Solver(
self._package,
self._pool,
locked_repository.packages,
locked_repository.packages,
self._io,
)
# Always re-solve directory dependencies, otherwise we can't determine
# if anything has changed (and the lock file contains an invalid version).
use_latest = [
p.name for p in locked_repository.packages if p.source_type == "directory"
]
with solver.provider.use_source_root(
source_root=self._env.path.joinpath("src")
):
solved_packages = solver.solve(use_latest=use_latest).get_solved_packages()
self._write_lock_file(solved_packages, force=True)
return 0
def _do_install(self) -> int:
from poetry.puzzle.solver import Solver
locked_repository = Repository("poetry-locked")
reresolve = self._config.get("installer.re-resolve", False)
solved_packages: dict[Package, TransitivePackageInfo] = {}
lockfile_repo = LockfileRepository()
if self._update:
if not self._lock and self._locker.is_locked():
locked_repository = self._locker.locked_repository()
# If no packages have been whitelisted (The ones we want to update),
# we whitelist every package in the lock file.
if not self._whitelist:
for pkg in locked_repository.packages:
self._whitelist.append(pkg.name)
# Checking extras
for extra in self._extras:
if extra not in self._package.extras:
raise ValueError(f"Extra [{extra}] is not specified.")
self._io.write_line("Updating dependencies>")
solver = Solver(
self._package,
self._pool,
self._installed_repository.packages,
locked_repository.packages,
self._io,
)
with solver.provider.use_source_root(
source_root=self._env.path.joinpath("src")
):
solved_packages = solver.solve(
use_latest=self._whitelist
).get_solved_packages()
if not self.executor.enabled:
# If we are only in lock mode, no need to go any further
self._write_lock_file(solved_packages)
return 0
for package in solved_packages:
if not lockfile_repo.has_package(package):
lockfile_repo.add_package(package)
else:
self._io.write_line("Installing dependencies from lock file>")
if not self._locker.is_fresh():
raise ValueError(
"pyproject.toml changed significantly since poetry.lock was last"
f" generated. Run `{self._lock_fix_command()}` to fix the lock file."
)
if not (reresolve or self._locker.is_locked_groups_and_markers()):
if self._io.is_verbose():
self._io.write_line(
"Cannot install without re-resolving"
" because the lock file is not at least version 2.1>"
)
reresolve = True
locker_extras = {
canonicalize_name(extra)
for extra in self._locker.lock_data.get("extras", {})
}
for extra in self._extras:
if extra not in locker_extras:
raise ValueError(f"Extra [{extra}] is not specified.")
locked_repository = self._locker.locked_repository()
if reresolve:
lockfile_repo = locked_repository
else:
solved_packages = self._locker.locked_packages()
if self._io.is_verbose():
self._io.write_line("")
self._io.write_line(
"Finding the necessary packages for the current system>"
)
if reresolve:
if self._groups is not None:
root = self._package.with_dependency_groups(
list(self._groups), only=True
)
else:
root = self._package.without_optional_dependency_groups()
# We resolve again by only using the lock file
packages = lockfile_repo.packages + locked_repository.packages
pool = RepositoryPool.from_packages(packages, self._config)
solver = Solver(
root,
pool,
self._installed_repository.packages,
locked_repository.packages,
NullIO(),
active_root_extras=self._extras,
)
# Everything is resolved at this point, so we no longer need
# to load deferred dependencies (i.e. VCS, URL and path dependencies)
solver.provider.load_deferred(False)
with solver.use_environment(self._env):
transaction = solver.solve(use_latest=self._whitelist)
else:
if self._groups is None:
groups = self._package.dependency_group_names()
else:
groups = set(self._groups)
transaction = Transaction(
locked_repository.packages,
solved_packages,
self._installed_repository.packages,
self._package,
self._env.marker_env,
groups,
)
ops = transaction.calculate_operations(
with_uninstalls=(
self._requires_synchronization or (self._update and not reresolve)
),
synchronize=self._requires_synchronization,
skip_directory=self._skip_directory,
extras=set(self._extras),
system_site_packages={
p.name for p in self._installed_repository.system_site_packages
},
)
if reresolve and not self._requires_synchronization:
# If no packages synchronisation has been requested we need
# to calculate the uninstall operations
transaction = Transaction(
locked_repository.packages,
lockfile_repo.packages,
installed_packages=self._installed_repository.packages,
root_package=root,
)
ops = [
op
for op in transaction.calculate_operations(with_uninstalls=True)
if op.job_type == "uninstall"
] + ops
# Validate the dependencies
for op in ops:
dep = op.package.to_dependency()
if dep.is_file() or dep.is_directory():
dep = cast("PathDependency", dep)
dep.validate(raise_error=not op.skipped)
# Execute operations
status = self._execute(ops)
if status == 0 and self._update:
# Only write lock file when installation is success
self._write_lock_file(solved_packages)
return status
def _lock_fix_command(self) -> str:
# `poetry self` commands operate on Poetry's own system project. When the lock
# file is outdated, users should run `poetry self lock` rather than `poetry lock`.
if self._package.name == POETRY_SYSTEM_PROJECT_NAME:
return "poetry self lock"
return "poetry lock"
def _write_lock_file(
self,
packages: dict[Package, TransitivePackageInfo],
force: bool = False,
) -> None:
if not self.is_dry_run() and (force or self._update):
updated_lock = self._locker.set_lock_data(self._package, packages)
if updated_lock:
self._io.write_line("")
self._io.write_line("Writing lock file>")
def _execute(self, operations: list[Operation]) -> int:
return self._executor.execute(operations)
def _get_installed(self) -> InstalledRepository:
return InstalledRepository.load(self._env)
================================================
FILE: src/poetry/installation/operations/__init__.py
================================================
from __future__ import annotations
from poetry.installation.operations.install import Install
from poetry.installation.operations.uninstall import Uninstall
from poetry.installation.operations.update import Update
__all__ = ["Install", "Uninstall", "Update"]
================================================
FILE: src/poetry/installation/operations/install.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from poetry.installation.operations.operation import Operation
if TYPE_CHECKING:
from poetry.core.packages.package import Package
class Install(Operation):
def __init__(
self, package: Package, reason: str | None = None, priority: int = 0
) -> None:
super().__init__(reason, priority=priority)
self._package = package
@property
def package(self) -> Package:
return self._package
@property
def job_type(self) -> str:
return "install"
def __str__(self) -> str:
return (
"Installing"
f" {self.package.pretty_name} ({self.format_version(self.package)})"
)
def __repr__(self) -> str:
return (
""
)
================================================
FILE: src/poetry/installation/operations/operation.py
================================================
from __future__ import annotations
from abc import ABC
from abc import abstractmethod
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from poetry.core.packages.package import Package
from typing_extensions import Self
class Operation(ABC):
def __init__(self, reason: str | None = None, priority: float = 0) -> None:
self._reason = reason
self._skipped = False
self._skip_reason: str | None = None
self._priority = priority
@property
@abstractmethod
def job_type(self) -> str: ...
@property
def reason(self) -> str | None:
return self._reason
@property
def skipped(self) -> bool:
return self._skipped
@property
def skip_reason(self) -> str | None:
return self._skip_reason
@property
def priority(self) -> float:
return self._priority
@property
@abstractmethod
def package(self) -> Package: ...
def format_version(self, package: Package) -> str:
version: str = package.full_pretty_version
return version
def skip(self, reason: str) -> Self:
self._skipped = True
self._skip_reason = reason
return self
================================================
FILE: src/poetry/installation/operations/uninstall.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from poetry.installation.operations.operation import Operation
if TYPE_CHECKING:
from poetry.core.packages.package import Package
class Uninstall(Operation):
def __init__(
self,
package: Package,
reason: str | None = None,
priority: float = float("inf"),
) -> None:
super().__init__(reason, priority=priority)
self._package = package
@property
def package(self) -> Package:
return self._package
@property
def job_type(self) -> str:
return "uninstall"
def __str__(self) -> str:
return (
"Uninstalling"
f" {self.package.pretty_name} ({self.format_version(self._package)})"
)
def __repr__(self) -> str:
return (
""
)
================================================
FILE: src/poetry/installation/operations/update.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from poetry.installation.operations.operation import Operation
if TYPE_CHECKING:
from poetry.core.packages.package import Package
class Update(Operation):
def __init__(
self,
initial: Package,
target: Package,
reason: str | None = None,
priority: int = 0,
) -> None:
self._initial_package = initial
self._target_package = target
super().__init__(reason, priority=priority)
@property
def initial_package(self) -> Package:
return self._initial_package
@property
def target_package(self) -> Package:
return self._target_package
@property
def package(self) -> Package:
return self._target_package
@property
def job_type(self) -> str:
return "update"
def __str__(self) -> str:
init_version = self.format_version(self.initial_package)
target_version = self.format_version(self.target_package)
return (
f"Updating {self.initial_package.pretty_name} ({init_version}) "
f"to {self.target_package.pretty_name} ({target_version})"
)
def __repr__(self) -> str:
init_version = self.format_version(self.initial_package)
target_version = self.format_version(self.target_package)
return (
f""
)
================================================
FILE: src/poetry/installation/wheel_installer.py
================================================
from __future__ import annotations
import logging
import platform
import sys
from pathlib import Path
from typing import TYPE_CHECKING
from installer import install
from installer.destinations import SchemeDictionaryDestination
from installer.sources import WheelFile
from installer.sources import _WheelFileValidationError
from poetry.__version__ import __version__
from poetry.utils._compat import WINDOWS
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from collections.abc import Collection
from typing import BinaryIO
from installer.records import RecordEntry
from installer.scripts import LauncherKind
from installer.utils import Scheme
from poetry.utils.env import Env
class WheelDestination(SchemeDictionaryDestination):
""" """
def write_to_fs(
self,
scheme: Scheme,
path: str,
stream: BinaryIO,
is_executable: bool,
) -> RecordEntry:
from installer.records import Hash
from installer.records import RecordEntry
from installer.utils import copyfileobj_with_hashing
from installer.utils import make_file_executable
target_path = Path(self.scheme_dict[scheme]) / path
if target_path.exists():
# Contrary to the base library we don't raise an error here since it can
# break pkgutil-style and pkg_resource-style namespace packages.
logger.warning(f"Installing {target_path} over existing file")
parent_folder = target_path.parent
if not parent_folder.exists():
# Due to the parallel installation it can happen
# that two threads try to create the directory.
parent_folder.mkdir(parents=True, exist_ok=True)
with target_path.open("wb") as f:
hash_, size = copyfileobj_with_hashing(stream, f, self.hash_algorithm)
if is_executable:
make_file_executable(target_path)
return RecordEntry(path, Hash(self.hash_algorithm, hash_), size)
class WheelInstaller:
def __init__(self, env: Env) -> None:
self._env = env
script_kind: LauncherKind
if not WINDOWS:
script_kind = "posix"
else:
if platform.uname()[4].startswith("arm"):
script_kind = "win-arm64" if sys.maxsize > 2**32 else "win-arm"
else:
script_kind = "win-amd64" if sys.maxsize > 2**32 else "win-ia32"
self._script_kind = script_kind
self._bytecode_optimization_levels: Collection[int] = ()
self.invalid_wheels: dict[Path, list[str]] = {}
def enable_bytecode_compilation(self, enable: bool = True) -> None:
self._bytecode_optimization_levels = (-1,) if enable else ()
def install(self, wheel: Path) -> None:
with WheelFile.open(wheel) as source:
try:
# Content validation is temporarily disabled because of
# pypa/installer's out of memory issues with big wheels. See
# https://github.com/python-poetry/poetry/issues/7983
source.validate_record(validate_contents=False)
except _WheelFileValidationError as e:
self.invalid_wheels[wheel] = e.issues
scheme_dict = self._env.scheme_dict.copy()
scheme_dict["headers"] = str(
Path(scheme_dict["include"]) / source.distribution
)
destination = WheelDestination(
scheme_dict,
interpreter=str(self._env.python),
script_kind=self._script_kind,
bytecode_optimization_levels=self._bytecode_optimization_levels,
)
install(
source=source,
destination=destination,
# Additional metadata that is generated by the installation tool.
additional_metadata={
"INSTALLER": f"Poetry {__version__}".encode(),
},
)
================================================
FILE: src/poetry/json/__init__.py
================================================
from __future__ import annotations
import json
from importlib.resources import files
from typing import Any
import fastjsonschema
from fastjsonschema.exceptions import JsonSchemaValueException
def validate_object(obj: dict[str, Any]) -> list[str]:
schema = json.loads(
(files(__package__) / "schemas" / "poetry.json").read_text(encoding="utf-8")
)
validate = fastjsonschema.compile(schema)
errors = []
try:
validate(obj)
except JsonSchemaValueException as e:
errors = [e.message]
core_schema = json.loads(
(files("poetry.core") / "json" / "schemas" / "poetry-schema.json").read_text(
encoding="utf-8"
)
)
properties = schema["properties"].keys() | core_schema["properties"].keys()
additional_properties = obj.keys() - properties
for key in additional_properties:
errors.append(f"Additional properties are not allowed ('{key}' was unexpected)")
return errors
================================================
FILE: src/poetry/json/schemas/poetry.json
================================================
{
"$schema": "http://json-schema.org/draft-04/schema#",
"additionalProperties": true,
"type": "object",
"required": [],
"properties": {
"requires-poetry": {
"type": "string",
"description": "The version constraint for Poetry itself.",
"$ref": "#/definitions/dependency"
},
"requires-plugins": {
"type": "object",
"description": "Poetry plugins that are required for this project.",
"$ref": "#/definitions/dependencies",
"additionalProperties": false
},
"source": {
"type": "array",
"description": "A set of additional repositories where packages can be found.",
"additionalProperties": {
"$ref": "#/definitions/repository"
},
"items": {
"$ref": "#/definitions/repository"
}
},
"build-constraints": {
"type": "object",
"description": "This is a dict of package name (keys) and version constraints (values) to restrict build requirements for a package.",
"patternProperties": {
"^[a-zA-Z-_.0-9]+$": {
"$ref": "#/definitions/dependencies"
}
}
}
},
"definitions": {
"repository": {
"type": "object",
"additionalProperties": false,
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "The name of the repository."
},
"url": {
"type": "string",
"description": "The url of the repository.",
"format": "uri"
},
"priority": {
"enum": [
"primary",
"supplemental",
"explicit"
],
"description": "Declare the priority of this repository."
},
"links": {
"type": "boolean",
"description": "Declare this as a link source. Links at uri/path can point to sdist or bdist archives."
},
"indexed": {
"type": "boolean",
"description": "For PEP 503 simple API repositories, pre-fetch and index the available packages. (experimental)"
}
}
},
"dependencies": {
"type": "object",
"patternProperties": {
"^[a-zA-Z-_.0-9]+$": {
"oneOf": [
{
"$ref": "#/definitions/dependency"
},
{
"$ref": "#/definitions/long-dependency"
},
{
"$ref": "#/definitions/git-dependency"
},
{
"$ref": "#/definitions/file-dependency"
},
{
"$ref": "#/definitions/path-dependency"
},
{
"$ref": "#/definitions/url-dependency"
},
{
"$ref": "#/definitions/multiple-constraints-dependency"
},
{
"$ref": "#/definitions/dependency-options"
}
]
}
}
},
"dependency": {
"type": "string",
"description": "The constraint of the dependency."
},
"long-dependency": {
"type": "object",
"required": [
"version"
],
"additionalProperties": false,
"properties": {
"version": {
"type": "string",
"description": "The constraint of the dependency."
},
"python": {
"type": "string",
"description": "The python versions for which the dependency should be installed."
},
"platform": {
"type": "string",
"description": "The platform(s) for which the dependency should be installed."
},
"markers": {
"type": "string",
"description": "The PEP 508 compliant environment markers for which the dependency should be installed."
},
"allow-prereleases": {
"type": "boolean",
"description": "Whether the dependency allows prereleases or not."
},
"allows-prereleases": {
"type": "boolean",
"description": "Whether the dependency allows prereleases or not."
},
"optional": {
"type": "boolean",
"description": "Whether the dependency is optional or not."
},
"extras": {
"type": "array",
"description": "The required extras for this dependency.",
"items": {
"type": "string"
}
},
"source": {
"type": "string",
"description": "The exclusive source used to search for this dependency."
}
}
},
"git-dependency": {
"type": "object",
"required": [
"git"
],
"additionalProperties": false,
"properties": {
"git": {
"type": "string",
"description": "The url of the git repository."
},
"branch": {
"type": "string",
"description": "The branch to checkout."
},
"tag": {
"type": "string",
"description": "The tag to checkout."
},
"rev": {
"type": "string",
"description": "The revision to checkout."
},
"subdirectory": {
"type": "string",
"description": "The relative path to the directory where the package is located."
},
"python": {
"type": "string",
"description": "The python versions for which the dependency should be installed."
},
"platform": {
"type": "string",
"description": "The platform(s) for which the dependency should be installed."
},
"markers": {
"type": "string",
"description": "The PEP 508 compliant environment markers for which the dependency should be installed."
},
"allow-prereleases": {
"type": "boolean",
"description": "Whether the dependency allows prereleases or not."
},
"allows-prereleases": {
"type": "boolean",
"description": "Whether the dependency allows prereleases or not."
},
"optional": {
"type": "boolean",
"description": "Whether the dependency is optional or not."
},
"extras": {
"type": "array",
"description": "The required extras for this dependency.",
"items": {
"type": "string"
}
},
"develop": {
"type": "boolean",
"description": "Whether to install the dependency in development mode."
}
}
},
"file-dependency": {
"type": "object",
"required": [
"file"
],
"additionalProperties": false,
"properties": {
"file": {
"type": "string",
"description": "The path to the file."
},
"subdirectory": {
"type": "string",
"description": "The relative path to the directory where the package is located."
},
"python": {
"type": "string",
"description": "The python versions for which the dependency should be installed."
},
"platform": {
"type": "string",
"description": "The platform(s) for which the dependency should be installed."
},
"markers": {
"type": "string",
"description": "The PEP 508 compliant environment markers for which the dependency should be installed."
},
"optional": {
"type": "boolean",
"description": "Whether the dependency is optional or not."
},
"extras": {
"type": "array",
"description": "The required extras for this dependency.",
"items": {
"type": "string"
}
}
}
},
"path-dependency": {
"type": "object",
"required": [
"path"
],
"additionalProperties": false,
"properties": {
"path": {
"type": "string",
"description": "The path to the dependency."
},
"subdirectory": {
"type": "string",
"description": "The relative path to the directory where the package is located."
},
"python": {
"type": "string",
"description": "The python versions for which the dependency should be installed."
},
"platform": {
"type": "string",
"description": "The platform(s) for which the dependency should be installed."
},
"markers": {
"type": "string",
"description": "The PEP 508 compliant environment markers for which the dependency should be installed."
},
"optional": {
"type": "boolean",
"description": "Whether the dependency is optional or not."
},
"extras": {
"type": "array",
"description": "The required extras for this dependency.",
"items": {
"type": "string"
}
},
"develop": {
"type": "boolean",
"description": "Whether to install the dependency in development mode."
}
}
},
"url-dependency": {
"type": "object",
"required": [
"url"
],
"additionalProperties": false,
"properties": {
"url": {
"type": "string",
"description": "The url to the file."
},
"subdirectory": {
"type": "string",
"description": "The relative path to the directory where the package is located."
},
"python": {
"type": "string",
"description": "The python versions for which the dependency should be installed."
},
"platform": {
"type": "string",
"description": "The platform(s) for which the dependency should be installed."
},
"markers": {
"type": "string",
"description": "The PEP 508 compliant environment markers for which the dependency should be installed."
},
"optional": {
"type": "boolean",
"description": "Whether the dependency is optional or not."
},
"extras": {
"type": "array",
"description": "The required extras for this dependency.",
"items": {
"type": "string"
}
}
}
},
"dependency-options": {
"type": "object",
"additionalProperties": false,
"properties": {
"python": {
"type": "string",
"description": "The python versions for which the dependency should be installed."
},
"platform": {
"type": "string",
"description": "The platform(s) for which the dependency should be installed."
},
"markers": {
"type": "string",
"description": "The PEP 508 compliant environment markers for which the dependency should be installed."
},
"allow-prereleases": {
"type": "boolean",
"description": "Whether the dependency allows prereleases or not."
},
"source": {
"type": "string",
"description": "The exclusive source used to search for this dependency."
},
"develop": {
"type": "boolean",
"description": "Whether to install the dependency in development mode."
}
}
},
"multiple-constraints-dependency": {
"type": "array",
"minItems": 1,
"items": {
"oneOf": [
{
"$ref": "#/definitions/dependency"
},
{
"$ref": "#/definitions/long-dependency"
},
{
"$ref": "#/definitions/git-dependency"
},
{
"$ref": "#/definitions/file-dependency"
},
{
"$ref": "#/definitions/path-dependency"
},
{
"$ref": "#/definitions/url-dependency"
},
{
"$ref": "#/definitions/dependency-options"
}
]
}
}
}
}
================================================
FILE: src/poetry/layouts/__init__.py
================================================
from __future__ import annotations
from poetry.layouts.layout import Layout
from poetry.layouts.src import SrcLayout
_LAYOUTS = {"src": SrcLayout, "standard": Layout}
def layout(name: str) -> type[Layout]:
if name not in _LAYOUTS:
raise ValueError("Invalid layout")
return _LAYOUTS[name]
================================================
FILE: src/poetry/layouts/layout.py
================================================
from __future__ import annotations
import importlib.metadata
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from packaging.utils import canonicalize_name
from poetry.core.constraints.version import Version
from poetry.core.utils.helpers import module_name
from poetry.core.utils.patterns import AUTHOR_REGEX
from tomlkit import inline_table
from tomlkit import loads
from tomlkit import table
from tomlkit.toml_document import TOMLDocument
from poetry.factory import Factory
from poetry.pyproject.toml import PyProjectTOML
if TYPE_CHECKING:
from collections.abc import Mapping
from tomlkit.items import InlineTable
POETRY_DEFAULT = """\
[project]
name = ""
version = ""
description = ""
authors = [
]
license = {}
readme = ""
requires-python = ""
dependencies = [
]
[dependency-groups]
dev = [
]
[tool.poetry]
packages = []
"""
poetry_core_version = Version.parse(importlib.metadata.version("poetry-core"))
BUILD_SYSTEM_MIN_VERSION: str | None = Version.from_parts(
major=poetry_core_version.major,
minor=poetry_core_version.minor if poetry_core_version.major == 0 else 0,
patch=poetry_core_version.patch
if (poetry_core_version.major, poetry_core_version.minor) == (0, 0)
else 0,
).to_string()
BUILD_SYSTEM_MAX_VERSION: str | None = poetry_core_version.next_breaking().to_string()
class Layout:
def __init__(
self,
project: str,
version: str = "0.1.0",
description: str = "",
readme_format: str = "md",
author: str | None = None,
license: str | None = None,
python: str | None = None,
dependencies: Mapping[str, str | Mapping[str, Any]] | None = None,
dev_dependencies: Mapping[str, str | Mapping[str, Any]] | None = None,
) -> None:
self._project = canonicalize_name(project)
self._package_path_relative = Path(
*(module_name(part) for part in project.split("."))
)
self._package_name = ".".join(self._package_path_relative.parts)
self._version = version
self._description = description
self._readme_format = readme_format.lower()
self._license = license
self._python = python
self._dependencies = dependencies or {}
self._dev_dependencies = dev_dependencies or {}
if not author:
author = "Your Name "
self._author = author
@property
def basedir(self) -> Path:
return Path()
@property
def package_path(self) -> Path:
return self.basedir / self._package_path_relative
def get_package_include(self) -> InlineTable | None:
package = inline_table()
# If a project is created in the root directory (this is reasonable inside a
# docker container, eg )
# then parts will be empty.
parts = self._package_path_relative.parts
if not parts:
return None
include = parts[0]
package.append("include", include)
if self.basedir != Path():
package.append("from", self.basedir.as_posix())
else:
if module_name(self._project) == include:
# package include and package name are the same,
# packages table is redundant here.
return None
return package
def create(
self, path: Path, with_tests: bool = True, with_pyproject: bool = True
) -> None:
path.mkdir(parents=True, exist_ok=True)
self._create_default(path)
self._create_readme(path)
if with_tests:
self._create_tests(path)
if with_pyproject:
self._write_poetry(path)
def generate_project_content(
self, project_path: Path | None = None
) -> TOMLDocument:
template = POETRY_DEFAULT
content: dict[str, Any] = loads(template)
project_content = content["project"]
project_content["name"] = self._project
project_content["version"] = self._version
project_content["description"] = self._description
m = AUTHOR_REGEX.match(self._author)
if m is None:
# This should not happen because author has been validated before.
raise ValueError(f"Invalid author: {self._author}")
else:
author = {"name": m.group("name")}
if email := m.group("email"):
author["email"] = email
project_content["authors"].append(author)
if self._license:
project_content["license"]["text"] = self._license
else:
project_content.remove("license")
if project_path:
project_dir = project_path / f"README.{self._readme_format}"
abs_path = project_dir.resolve()
if abs_path.exists():
project_content["readme"] = f"README.{self._readme_format}"
else:
project_content.remove("readme")
if self._python:
project_content["requires-python"] = self._python
else:
project_content.remove("requires-python")
for dep_name, dep_constraint in self._dependencies.items():
dependency = Factory.create_dependency(dep_name, dep_constraint)
project_content["dependencies"].append(dependency.to_pep_508())
poetry_content = content["tool"]["poetry"]
packages = self.get_package_include()
if packages:
poetry_content["packages"].append(packages)
else:
poetry_content.remove("packages")
if self._dev_dependencies:
for dep_name, dep_constraint in self._dev_dependencies.items():
dependency = Factory.create_dependency(dep_name, dep_constraint)
content["dependency-groups"]["dev"].append(dependency.to_pep_508())
else:
del content["dependency-groups"]
if not poetry_content:
del content["tool"]["poetry"]
# Add build system
build_system = table()
build_system_version = ""
if BUILD_SYSTEM_MIN_VERSION is not None:
build_system_version = ">=" + BUILD_SYSTEM_MIN_VERSION
if BUILD_SYSTEM_MAX_VERSION is not None:
if build_system_version:
build_system_version += ","
build_system_version += "<" + BUILD_SYSTEM_MAX_VERSION
build_system.add("requires", ["poetry-core" + build_system_version])
build_system.add("build-backend", "poetry.core.masonry.api")
assert isinstance(content, TOMLDocument)
content.add("build-system", build_system)
return content
def _create_default(self, path: Path, src: bool = True) -> None:
package_path = path / self.package_path
package_path.mkdir(parents=True)
package_init = package_path / "__init__.py"
package_init.touch()
def _create_readme(self, path: Path) -> Path:
readme_file = path.joinpath(f"README.{self._readme_format}")
readme_file.touch()
return readme_file
@staticmethod
def _create_tests(path: Path) -> None:
tests = path / "tests"
tests.mkdir()
tests_init = tests / "__init__.py"
tests_init.touch(exist_ok=False)
def _write_poetry(self, path: Path) -> None:
pyproject = PyProjectTOML(path / "pyproject.toml")
content = self.generate_project_content()
for section, item in content.items():
pyproject.data.append(section, item)
pyproject.save()
================================================
FILE: src/poetry/layouts/src.py
================================================
from __future__ import annotations
from pathlib import Path
from poetry.layouts.layout import Layout
class SrcLayout(Layout):
@property
def basedir(self) -> Path:
return Path("src")
================================================
FILE: src/poetry/locations.py
================================================
from __future__ import annotations
import os
from pathlib import Path
from platformdirs import user_cache_path
from platformdirs import user_config_path
from platformdirs import user_data_path
_APP_NAME = "pypoetry"
DEFAULT_CACHE_DIR = user_cache_path(_APP_NAME, appauthor=False)
CONFIG_DIR = Path(
os.getenv("POETRY_CONFIG_DIR")
or user_config_path(_APP_NAME, appauthor=False, roaming=True)
)
def data_dir() -> Path:
if poetry_home := os.getenv("POETRY_HOME"):
return Path(poetry_home).expanduser()
return user_data_path(_APP_NAME, appauthor=False, roaming=True)
================================================
FILE: src/poetry/masonry/__init__.py
================================================
================================================
FILE: src/poetry/masonry/api.py
================================================
from __future__ import annotations
from poetry.core.masonry.api import build_sdist
from poetry.core.masonry.api import build_wheel
from poetry.core.masonry.api import get_requires_for_build_sdist
from poetry.core.masonry.api import get_requires_for_build_wheel
from poetry.core.masonry.api import prepare_metadata_for_build_wheel
__all__ = [
"build_sdist",
"build_wheel",
"get_requires_for_build_sdist",
"get_requires_for_build_wheel",
"prepare_metadata_for_build_wheel",
]
================================================
FILE: src/poetry/masonry/builders/__init__.py
================================================
from __future__ import annotations
from poetry.core.masonry.builders.sdist import SdistBuilder
from poetry.core.masonry.builders.wheel import WheelBuilder
from poetry.masonry.builders.editable import EditableBuilder
__all__ = ["BUILD_FORMATS", "EditableBuilder"]
# might be extended by plugins
BUILD_FORMATS = {
"sdist": SdistBuilder,
"wheel": WheelBuilder,
}
================================================
FILE: src/poetry/masonry/builders/editable.py
================================================
from __future__ import annotations
import csv
import hashlib
import json
import os
from base64 import urlsafe_b64encode
from pathlib import Path
from typing import TYPE_CHECKING
from poetry.core.masonry.builders.builder import Builder
from poetry.core.masonry.builders.sdist import SdistBuilder
from poetry.core.masonry.utils.package_include import PackageInclude
from poetry.utils._compat import WINDOWS
from poetry.utils._compat import decode
from poetry.utils._compat import getencoding
from poetry.utils.env import build_environment
from poetry.utils.helpers import is_dir_writable
from poetry.utils.pip import pip_install
if TYPE_CHECKING:
from cleo.io.io import IO
from poetry.poetry import Poetry
from poetry.utils.env import Env
SCRIPT_TEMPLATE = """\
#!{python}
import sys
from {module} import {callable_holder}
if __name__ == '__main__':
sys.exit({callable_}())
"""
WINDOWS_CMD_TEMPLATE = """\
@echo off\r\n"{python}" "%~dp0\\{script}" %*\r\n
"""
class EditableBuilder(Builder):
def __init__(self, poetry: Poetry, env: Env, io: IO) -> None:
self._poetry: Poetry
super().__init__(poetry)
self._env = env
self._io = io
def build(self, target_dir: Path | None = None) -> Path:
self._debug(
f" - Building package {self._package.name} in"
" editable mode"
)
if self._package.build_script:
if self._package.build_should_generate_setup():
self._debug(
" - Falling back on using a setup.py"
)
self._setup_build()
return self._path
self._run_build_script(self._package.build_script)
for removed in self._env.site_packages.remove_distribution_files(
distribution_name=self._package.name
):
self._debug(
f" - Removed {removed.name} directory from"
f" {removed.parent}"
)
added_files = []
added_files += self._add_pth()
added_files += self._add_scripts()
self._add_dist_info(added_files)
return self._path
def _run_build_script(self, build_script: str) -> None:
with build_environment(poetry=self._poetry, env=self._env, io=self._io) as env:
self._debug(f" - Executing build script: {build_script}")
env.run("python", str(self._path.joinpath(build_script)), call=True)
def _setup_build(self) -> None:
builder = SdistBuilder(self._poetry)
setup = self._path / "setup.py"
has_setup = setup.exists()
if has_setup:
self._io.write_error_line(
"A setup.py file already exists. Using it."
)
else:
with setup.open("w", encoding="utf-8") as f:
f.write(decode(builder.build_setup()))
try:
pip_install(self._path, self._env, upgrade=True, editable=True)
finally:
if not has_setup:
os.remove(setup)
def _add_pth(self) -> list[Path]:
paths = {
include.base.resolve().as_posix()
for include in self._module.includes
if isinstance(include, PackageInclude)
and (include.is_module() or include.is_package())
}
content = "".join(decode(path + os.linesep) for path in paths)
pth_file = Path(self._module.name).with_suffix(".pth")
# remove any pre-existing pth files for this package
for file in self._env.site_packages.find(path=pth_file, writable_only=True):
self._debug(
f" - Removing existing {file.name} from {file.parent}"
f" for {self._poetry.file.path.parent}"
)
file.unlink(missing_ok=True)
try:
pth_file = self._env.site_packages.write_text(
pth_file, content, encoding=getencoding()
)
self._debug(
f" - Adding {pth_file.name} to {pth_file.parent} for"
f" {self._poetry.file.path.parent}"
)
return [pth_file]
except PermissionError:
self._io.write_error_line(
f" - Failed to create {pth_file.name} for"
f" {self._poetry.file.path.parent}"
)
return []
def _add_scripts(self) -> list[Path]:
added = []
entry_points = self.convert_entry_points()
for scripts_path in self._env.script_dirs:
if is_dir_writable(path=scripts_path, create=True):
break
else:
self._io.write_error_line(
" - Failed to find a suitable script installation directory for"
f" {self._poetry.file.path.parent}"
)
return []
scripts = entry_points.get("console_scripts", [])
for script in scripts:
name, script_with_extras = script.split(" = ")
script_without_extras = script_with_extras.split("[")[0]
try:
module, callable_ = script_without_extras.split(":")
except ValueError as exc:
msg = (
f"Bad script ({name}): script needs to specify a function within a"
" module like: module(.submodule):function\nInstead got:"
f" {script_with_extras}"
)
if "not enough values" in str(exc):
msg += (
"\nHint: If the script depends on module-level code, try"
" wrapping it in a main() function and modifying your script"
f' like:\n{name} = "{script_with_extras}:main"'
)
elif "too many values" in str(exc):
msg += '\nToo many ":" found!'
raise ValueError(msg)
callable_holder = callable_.split(".", 1)[0]
script_file = scripts_path.joinpath(name)
self._debug(
f" - Adding the {name} script to {scripts_path}"
)
with script_file.open("w", encoding="utf-8") as f:
f.write(
decode(
SCRIPT_TEMPLATE.format(
python=self._env.python,
module=module,
callable_holder=callable_holder,
callable_=callable_,
)
)
)
script_file.chmod(0o755)
added.append(script_file)
if WINDOWS:
cmd_script = script_file.with_suffix(".cmd")
cmd = WINDOWS_CMD_TEMPLATE.format(python=self._env.python, script=name)
self._debug(
f" - Adding the {cmd_script.name} script wrapper to"
f" {scripts_path}"
)
with cmd_script.open("w", encoding="utf-8") as f:
f.write(decode(cmd))
added.append(cmd_script)
return added
def _add_dist_info(self, added_files: list[Path]) -> None:
from poetry.core.masonry.builders.wheel import WheelBuilder
builder = WheelBuilder(self._poetry)
dist_info = self._env.site_packages.mkdir(Path(builder.dist_info))
self._debug(
f" - Adding the {dist_info.name} directory to"
f" {dist_info.parent}"
)
builder.prepare_metadata(dist_info.parent)
for path in sorted(f for f in dist_info.rglob("*") if f.is_file()):
added_files.append(path)
with dist_info.joinpath("INSTALLER").open("w", encoding="utf-8") as f:
f.write("poetry")
added_files.append(dist_info.joinpath("INSTALLER"))
# write PEP 610 metadata
direct_url_json = dist_info.joinpath("direct_url.json")
direct_url_json.write_text(
json.dumps(
{
"dir_info": {"editable": True},
"url": self._poetry.file.path.parent.absolute().as_uri(),
}
),
encoding="utf-8",
)
added_files.append(direct_url_json)
record = dist_info.joinpath("RECORD")
with record.open("w", encoding="utf-8", newline="") as f:
csv_writer = csv.writer(f)
for path in added_files:
hash = self._get_file_hash(path)
size = path.stat().st_size
csv_writer.writerow((path, f"sha256={hash}", size))
# RECORD itself is recorded with no hash or size
csv_writer.writerow((record, "", ""))
def _get_file_hash(self, filepath: Path) -> str:
hashsum = hashlib.sha256()
with filepath.open("rb") as src:
while True:
buf = src.read(1024 * 8)
if not buf:
break
hashsum.update(buf)
src.seek(0)
return urlsafe_b64encode(hashsum.digest()).decode("ascii").rstrip("=")
def _debug(self, msg: str) -> None:
if self._io.is_debug():
self._io.write_line(msg)
================================================
FILE: src/poetry/mixology/__init__.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from poetry.mixology.version_solver import VersionSolver
if TYPE_CHECKING:
from poetry.core.packages.project_package import ProjectPackage
from poetry.mixology.result import SolverResult
from poetry.puzzle.provider import Provider
def resolve_version(root: ProjectPackage, provider: Provider) -> SolverResult:
solver = VersionSolver(root, provider)
return solver.solve()
================================================
FILE: src/poetry/mixology/assignment.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from poetry.mixology.term import Term
if TYPE_CHECKING:
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.package import Package
from poetry.mixology.incompatibility import Incompatibility
class Assignment(Term):
"""
A term in a PartialSolution that tracks some additional metadata.
"""
def __init__(
self,
dependency: Dependency,
is_positive: bool,
decision_level: int,
index: int,
cause: Incompatibility | None = None,
) -> None:
super().__init__(dependency, is_positive)
self._decision_level = decision_level
self._index = index
self._cause = cause
@property
def decision_level(self) -> int:
return self._decision_level
@property
def index(self) -> int:
return self._index
@property
def cause(self) -> Incompatibility | None:
return self._cause
@classmethod
def decision(cls, package: Package, decision_level: int, index: int) -> Assignment:
return cls(package.to_dependency(), True, decision_level, index)
@classmethod
def derivation(
cls,
dependency: Dependency,
is_positive: bool,
cause: Incompatibility,
decision_level: int,
index: int,
) -> Assignment:
return cls(dependency, is_positive, decision_level, index, cause)
def is_decision(self) -> bool:
return self._cause is None
================================================
FILE: src/poetry/mixology/failure.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from poetry.core.constraints.version import parse_constraint
from poetry.mixology.incompatibility_cause import ConflictCauseError
from poetry.mixology.incompatibility_cause import PythonCauseError
if TYPE_CHECKING:
from poetry.mixology.incompatibility import Incompatibility
class SolveFailureError(Exception):
def __init__(self, incompatibility: Incompatibility) -> None:
self._incompatibility = incompatibility
@property
def message(self) -> str:
return str(self)
def __str__(self) -> str:
return _Writer(self._incompatibility).write()
class _Writer:
def __init__(self, root: Incompatibility) -> None:
self._root = root
self._derivations: dict[Incompatibility, int] = {}
self._lines: list[tuple[str, int | None]] = []
self._line_numbers: dict[Incompatibility, int] = {}
self._count_derivations(self._root)
def write(self) -> str:
buffer = []
version_solutions = []
required_python_version_notification = False
for incompatibility in self._root.external_incompatibilities:
if isinstance(incompatibility.cause, PythonCauseError):
root_constraint = parse_constraint(
incompatibility.cause.root_python_version
)
constraint = parse_constraint(incompatibility.cause.python_version)
version_solutions.append(
"For "
f"{incompatibility.terms[0].dependency.name}>,"
" a possible solution would be to set the"
" `python>` property to"
f' "{root_constraint.intersect(constraint)}">'
)
if not required_python_version_notification:
buffer.append(
"The current project's supported Python range"
f" ({incompatibility.cause.root_python_version}) is not"
" compatible with some of the required packages Python"
" requirement:"
)
required_python_version_notification = True
root_constraint = parse_constraint(
incompatibility.cause.root_python_version
)
constraint = parse_constraint(incompatibility.cause.python_version)
buffer.append(
f" - {incompatibility.terms[0].dependency.name} requires Python"
f" {incompatibility.cause.python_version}, so it will not be"
f" installable for Python {root_constraint.difference(constraint)}"
)
if required_python_version_notification:
buffer.append("")
if isinstance(self._root.cause, ConflictCauseError):
self._visit(self._root)
else:
self._write(self._root, f"Because {self._root}, version solving failed.")
padding = (
0
if not self._line_numbers
else len(f"({list(self._line_numbers.values())[-1]}) ")
)
last_was_empty = False
for line in self._lines:
message = line[0]
if not message:
if not last_was_empty:
buffer.append("")
last_was_empty = True
continue
last_was_empty = False
number = line[-1]
if number is not None:
message = f"({number})".ljust(padding) + message
else:
message = " " * padding + message
buffer.append(message)
if required_python_version_notification:
# Add suggested solution
links = ",".join(
f"\n https://python-poetry.org/docs/dependency-specification/#{section}>"
for section in [
"python-restricted-dependencies",
"using-environment-markers",
]
)
description = (
"The Python requirement can be specified via the"
" `python>` or"
" `markers>` properties"
)
if version_solutions:
description += "\n\n " + "\n".join(version_solutions)
description = description.strip(" ")
buffer.append(
f"\n * >"
f"Check your dependencies Python requirement>:"
f" {description}\n{links}\n",
)
return "\n".join(buffer)
def _write(
self, incompatibility: Incompatibility, message: str, numbered: bool = False
) -> None:
if numbered:
number = len(self._line_numbers) + 1
self._line_numbers[incompatibility] = number
self._lines.append((message, number))
else:
self._lines.append((message, None))
def _visit(
self,
incompatibility: Incompatibility,
conclusion: bool = False,
) -> None:
numbered = conclusion or self._derivations[incompatibility] > 1
conjunction = "So," if conclusion or incompatibility == self._root else "And"
incompatibility_string = str(incompatibility)
cause = incompatibility.cause
assert isinstance(cause, ConflictCauseError)
if isinstance(cause.conflict.cause, ConflictCauseError) and isinstance(
cause.other.cause, ConflictCauseError
):
conflict_line = self._line_numbers.get(cause.conflict)
other_line = self._line_numbers.get(cause.other)
if conflict_line is not None and other_line is not None:
reason = cause.conflict.and_to_string(
cause.other, conflict_line, other_line
)
self._write(
incompatibility,
f"Because {reason}, {incompatibility_string}.",
numbered=numbered,
)
elif conflict_line is not None or other_line is not None:
if conflict_line is not None:
with_line = cause.conflict
without_line = cause.other
line = conflict_line
elif other_line is not None:
with_line = cause.other
without_line = cause.conflict
line = other_line
self._visit(without_line)
self._write(
incompatibility,
f"{conjunction} because {with_line!s} ({line}),"
f" {incompatibility_string}.",
numbered=numbered,
)
else:
single_line_conflict = self._is_single_line(cause.conflict.cause)
single_line_other = self._is_single_line(cause.other.cause)
if single_line_other or single_line_conflict:
first = cause.conflict if single_line_other else cause.other
second = cause.other if single_line_other else cause.conflict
self._visit(first)
self._visit(second)
self._write(
incompatibility,
f"Thus, {incompatibility_string}.",
numbered=numbered,
)
else:
self._visit(cause.conflict, conclusion=True)
self._lines.append(("", None))
self._visit(cause.other)
self._write(
incompatibility,
f"{conjunction} because {cause.conflict!s}"
f" ({self._line_numbers[cause.conflict]}),"
f" {incompatibility_string}",
numbered=numbered,
)
elif isinstance(cause.conflict.cause, ConflictCauseError) or isinstance(
cause.other.cause, ConflictCauseError
):
derived = (
cause.conflict
if isinstance(cause.conflict.cause, ConflictCauseError)
else cause.other
)
ext = (
cause.other
if isinstance(cause.conflict.cause, ConflictCauseError)
else cause.conflict
)
derived_line = self._line_numbers.get(derived)
if derived_line is not None:
reason = ext.and_to_string(derived, None, derived_line)
self._write(
incompatibility,
f"Because {reason}, {incompatibility_string}.",
numbered=numbered,
)
elif self._is_collapsible(derived):
derived_cause = derived.cause
assert isinstance(derived_cause, ConflictCauseError)
if isinstance(derived_cause.conflict.cause, ConflictCauseError):
collapsed_derived = derived_cause.conflict
collapsed_ext = derived_cause.other
else:
collapsed_derived = derived_cause.other
collapsed_ext = derived_cause.conflict
self._visit(collapsed_derived)
reason = collapsed_ext.and_to_string(ext, None, None)
self._write(
incompatibility,
f"{conjunction} because {reason}, {incompatibility_string}.",
numbered=numbered,
)
else:
self._visit(derived)
self._write(
incompatibility,
f"{conjunction} because {ext!s}, {incompatibility_string}.",
numbered=numbered,
)
else:
reason = cause.conflict.and_to_string(cause.other, None, None)
self._write(
incompatibility,
f"Because {reason}, {incompatibility_string}.",
numbered=numbered,
)
def _is_collapsible(self, incompatibility: Incompatibility) -> bool:
if self._derivations[incompatibility] > 1:
return False
cause = incompatibility.cause
assert isinstance(cause, ConflictCauseError)
if isinstance(cause.conflict.cause, ConflictCauseError) and isinstance(
cause.other.cause, ConflictCauseError
):
return False
if not isinstance(cause.conflict.cause, ConflictCauseError) and not isinstance(
cause.other.cause, ConflictCauseError
):
return False
complex = (
cause.conflict
if isinstance(cause.conflict.cause, ConflictCauseError)
else cause.other
)
return complex not in self._line_numbers
def _is_single_line(self, cause: ConflictCauseError) -> bool:
return not isinstance(
cause.conflict.cause, ConflictCauseError
) and not isinstance(cause.other.cause, ConflictCauseError)
def _count_derivations(self, incompatibility: Incompatibility) -> None:
if incompatibility in self._derivations:
self._derivations[incompatibility] += 1
else:
self._derivations[incompatibility] = 1
cause = incompatibility.cause
if isinstance(cause, ConflictCauseError):
self._count_derivations(cause.conflict)
self._count_derivations(cause.other)
================================================
FILE: src/poetry/mixology/incompatibility.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from poetry.mixology.incompatibility_cause import ConflictCauseError
from poetry.mixology.incompatibility_cause import DependencyCauseError
from poetry.mixology.incompatibility_cause import NoVersionsCauseError
from poetry.mixology.incompatibility_cause import PlatformCauseError
from poetry.mixology.incompatibility_cause import PythonCauseError
from poetry.mixology.incompatibility_cause import RootCauseError
if TYPE_CHECKING:
from collections.abc import Callable
from collections.abc import Iterator
from poetry.mixology.incompatibility_cause import IncompatibilityCauseError
from poetry.mixology.term import Term
class Incompatibility:
def __init__(self, terms: list[Term], cause: IncompatibilityCauseError) -> None:
# Remove the root package from generated incompatibilities, since it will
# always be satisfied. This makes error reporting clearer, and may also
# make solving more efficient.
if (
len(terms) != 1
and isinstance(cause, ConflictCauseError)
and any(term.is_positive() and term.dependency.is_root for term in terms)
):
terms = [
term
for term in terms
if not term.is_positive() or not term.dependency.is_root
]
if len(terms) != 1 and (
# Short-circuit in the common case of a two-term incompatibility with
# two different packages (for example, a dependency).
len(terms) != 2
or terms[0].dependency.complete_name == terms[-1].dependency.complete_name
):
# Coalesce multiple terms about the same package if possible.
by_name: dict[str, dict[str, Term]] = {}
for term in terms:
by_ref = by_name.setdefault(term.dependency.complete_name, {})
ref = term.dependency.complete_name
if ref in by_ref:
value = by_ref[ref].intersect(term)
# If we have two terms that refer to the same package but have a
# null intersection, they're mutually exclusive, making this
# incompatibility irrelevant, since we already know that mutually
# exclusive version ranges are incompatible. We should never derive
# an irrelevant incompatibility.
err_msg = f"Package '{ref}' is listed as a dependency of itself."
assert value is not None, err_msg
by_ref[ref] = value
else:
by_ref[ref] = term
new_terms = []
for by_ref in by_name.values():
positive_terms = [
term for term in by_ref.values() if term.is_positive()
]
if positive_terms:
new_terms += positive_terms
continue
new_terms += list(by_ref.values())
terms = new_terms
self._terms = terms
self._cause = cause
@property
def terms(self) -> list[Term]:
return self._terms
@property
def cause(self) -> IncompatibilityCauseError:
return self._cause
@property
def external_incompatibilities(
self,
) -> Iterator[Incompatibility]:
"""
Returns all external incompatibilities in this incompatibility's
derivation graph.
"""
if isinstance(self._cause, ConflictCauseError):
cause: ConflictCauseError = self._cause
yield from cause.conflict.external_incompatibilities
yield from cause.other.external_incompatibilities
else:
yield self
def is_failure(self) -> bool:
return len(self._terms) == 0 or (
len(self._terms) == 1 and self._terms[0].dependency.is_root
)
def __str__(self) -> str:
if isinstance(self._cause, DependencyCauseError):
assert len(self._terms) == 2
depender = self._terms[0]
dependee = self._terms[1]
assert depender.is_positive()
assert not dependee.is_positive()
return (
f"{self._terse(depender, allow_every=True)} depends on"
f" {self._terse(dependee)}"
)
elif isinstance(self._cause, PythonCauseError):
assert len(self._terms) == 1
assert self._terms[0].is_positive()
text = f"{self._terse(self._terms[0], allow_every=True)} requires "
text += f"Python {self._cause.python_version}"
return text
elif isinstance(self._cause, PlatformCauseError):
assert len(self._terms) == 1
assert self._terms[0].is_positive()
text = f"{self._terse(self._terms[0], allow_every=True)} requires "
text += f"platform {self._cause.platform}"
return text
elif isinstance(self._cause, NoVersionsCauseError):
assert len(self._terms) == 1
assert self._terms[0].is_positive()
return (
f"no versions of {self._terms[0].dependency.name} match"
f" {self._terms[0].constraint}"
)
elif isinstance(self._cause, RootCauseError):
assert len(self._terms) == 1
assert not self._terms[0].is_positive()
assert self._terms[0].dependency.is_root
return (
f"{self._terms[0].dependency.name} is"
f" {self._terms[0].dependency.constraint}"
)
elif self.is_failure():
return "version solving failed"
if len(self._terms) == 1:
term = self._terms[0]
verb = "forbidden" if term.is_positive() else "required"
return f"{term.dependency.name} is {verb}"
if len(self._terms) == 2:
term1 = self._terms[0]
term2 = self._terms[1]
if term1.is_positive() == term2.is_positive():
if not term1.is_positive():
return f"either {self._terse(term1)} or {self._terse(term2)}"
package1 = (
term1.dependency.name
if term1.constraint.is_any()
else self._terse(term1)
)
package2 = (
term2.dependency.name
if term2.constraint.is_any()
else self._terse(term2)
)
return f"{package1} is incompatible with {package2}"
positive = []
negative = []
for term in self._terms:
if term.is_positive():
positive.append(self._terse(term))
else:
negative.append(self._terse(term))
if positive and negative:
if len(positive) != 1:
return f"if {' and '.join(positive)} then {' or '.join(negative)}"
positive_term = next(term for term in self._terms if term.is_positive())
return (
f"{self._terse(positive_term, allow_every=True)} requires"
f" {' or '.join(negative)}"
)
elif positive:
return f"one of {' or '.join(positive)} must be false"
else:
return f"one of {' or '.join(negative)} must be true"
def and_to_string(
self,
other: Incompatibility,
this_line: int | None,
other_line: int | None,
) -> str:
requires_both = self._try_requires_both(other, this_line, other_line)
if requires_both is not None:
return requires_both
requires_through = self._try_requires_through(other, this_line, other_line)
if requires_through is not None:
return requires_through
requires_forbidden = self._try_requires_forbidden(other, this_line, other_line)
if requires_forbidden is not None:
return requires_forbidden
buffer = [str(self)]
if this_line is not None:
buffer.append(f" {this_line!s}")
buffer.append(f" and {other!s}")
if other_line is not None:
buffer.append(f" {other_line!s}")
return "\n".join(buffer)
def _try_requires_both(
self,
other: Incompatibility,
this_line: int | None,
other_line: int | None,
) -> str | None:
if len(self._terms) == 1 or len(other.terms) == 1:
return None
this_positive = self._single_term_where(lambda term: term.is_positive())
if this_positive is None:
return None
other_positive = other._single_term_where(lambda term: term.is_positive())
if other_positive is None:
return None
if this_positive.dependency != other_positive.dependency:
return None
this_negatives = " or ".join(
[self._terse(term) for term in self._terms if not term.is_positive()]
)
other_negatives = " or ".join(
[self._terse(term) for term in other.terms if not term.is_positive()]
)
buffer = [self._terse(this_positive, allow_every=True) + " "]
is_dependency = isinstance(self.cause, DependencyCauseError) and isinstance(
other.cause, DependencyCauseError
)
if is_dependency:
buffer.append("depends on")
else:
buffer.append("requires")
buffer.append(f" both {this_negatives}")
if this_line is not None:
buffer.append(f" ({this_line})")
buffer.append(f" and {other_negatives}")
if other_line is not None:
buffer.append(f" ({other_line})")
return "".join(buffer)
def _try_requires_through(
self,
other: Incompatibility,
this_line: int | None,
other_line: int | None,
) -> str | None:
if len(self._terms) == 1 or len(other.terms) == 1:
return None
this_negative = self._single_term_where(lambda term: not term.is_positive())
other_negative = other._single_term_where(lambda term: not term.is_positive())
if this_negative is None and other_negative is None:
return None
this_positive = self._single_term_where(lambda term: term.is_positive())
other_positive = self._single_term_where(lambda term: term.is_positive())
if (
this_negative is not None
and other_positive is not None
and this_negative.dependency.name == other_positive.dependency.name
and this_negative.inverse.satisfies(other_positive)
):
prior = self
prior_negative = this_negative
prior_line = this_line
latter = other
latter_line = other_line
elif (
other_negative is not None
and this_positive is not None
and other_negative.dependency.name == this_positive.dependency.name
and other_negative.inverse.satisfies(this_positive)
):
prior = other
prior_negative = other_negative
prior_line = other_line
latter = self
latter_line = this_line
else:
return None
prior_positives = [term for term in prior.terms if term.is_positive()]
buffer = []
if len(prior_positives) > 1:
prior_string = " or ".join([self._terse(term) for term in prior_positives])
buffer.append(f"if {prior_string} then ")
else:
if isinstance(prior.cause, DependencyCauseError):
verb = "depends on"
else:
verb = "requires"
buffer.append(
f"{self._terse(prior_positives[0], allow_every=True)} {verb} "
)
buffer.append(self._terse(prior_negative))
if prior_line is not None:
buffer.append(f" ({prior_line})")
buffer.append(" which ")
if isinstance(latter.cause, DependencyCauseError):
buffer.append("depends on ")
else:
buffer.append("requires ")
buffer.append(
" or ".join(
[self._terse(term) for term in latter.terms if not term.is_positive()]
)
)
if latter_line is not None:
buffer.append(f" ({latter_line})")
return "".join(buffer)
def _try_requires_forbidden(
self,
other: Incompatibility,
this_line: int | None,
other_line: int | None,
) -> str | None:
if len(self._terms) != 1 and len(other.terms) != 1:
return None
if len(self.terms) == 1:
prior = other
latter = self
prior_line = other_line
latter_line = this_line
else:
prior = self
latter = other
prior_line = this_line
latter_line = other_line
negative = prior._single_term_where(lambda term: not term.is_positive())
if negative is None:
return None
if not negative.inverse.satisfies(latter.terms[0]):
return None
positives = [t for t in prior.terms if t.is_positive()]
buffer = []
if len(positives) > 1:
prior_string = " or ".join([self._terse(term) for term in positives])
buffer.append(f"if {prior_string} then ")
else:
buffer.append(self._terse(positives[0], allow_every=True))
if isinstance(prior.cause, DependencyCauseError):
buffer.append(" depends on ")
else:
buffer.append(" requires ")
buffer.append(self._terse(latter.terms[0]) + " ")
if prior_line is not None:
buffer.append(f"({prior_line}) ")
if isinstance(latter.cause, PythonCauseError):
cause: PythonCauseError = latter.cause
buffer.append(f"which requires Python {cause.python_version}")
elif isinstance(latter.cause, NoVersionsCauseError):
buffer.append("which doesn't match any versions")
else:
buffer.append("which is forbidden")
if latter_line is not None:
buffer.append(f" ({latter_line})")
return "".join(buffer)
def _terse(self, term: Term, allow_every: bool = False) -> str:
if allow_every and term.constraint.is_any():
return f"every version of {term.dependency.complete_name}"
if term.dependency.is_root:
pretty_name: str = term.dependency.pretty_name
return pretty_name
if term.dependency.source_type:
return str(term.dependency)
pretty_name = term.dependency.complete_pretty_name
return f"{pretty_name} ({term.dependency.pretty_constraint})"
def _single_term_where(self, callable: Callable[[Term], bool]) -> Term | None:
found = None
for term in self._terms:
if not callable(term):
continue
if found is not None:
return None
found = term
return found
def __repr__(self) -> str:
return f"