diff --git a/devel-common/pyproject.toml b/devel-common/pyproject.toml index e874dd286e585..674b2fc6516e6 100644 --- a/devel-common/pyproject.toml +++ b/devel-common/pyproject.toml @@ -118,8 +118,7 @@ dependencies = [ "types-certifi>=2021.10.8.3", "types-croniter>=2.0.0.20240423", "types-docutils>=0.21.0.20240704", - # TODO: Bump to >= 4.0.0 once https://github.com/apache/airflow/issues/54079 - "types-paramiko>=3.4.0.20240423,<4.0.0", + "types-paramiko>=4.0.0.20260402", "types-protobuf>=5.26.0.20240422", "types-python-dateutil>=2.9.0.20240316", "types-python-slugify>=8.0.2.20240310", diff --git a/providers/sftp/README.rst b/providers/sftp/README.rst index d22393492f22c..51fdcd472fc71 100644 --- a/providers/sftp/README.rst +++ b/providers/sftp/README.rst @@ -57,7 +57,7 @@ PIP package Version required ``apache-airflow`` ``>=2.11.0`` ``apache-airflow-providers-ssh`` ``>=4.0.0`` ``apache-airflow-providers-common-compat`` ``>=1.12.0`` -``paramiko`` ``>=3.5.1,<4.0.0`` +``paramiko`` ``>=4.0.0`` ``asyncssh`` ``>=2.12.0; python_version < "3.14"`` ``asyncssh`` ``>=2.22.0; python_version >= "3.14"`` ========================================== ====================================== diff --git a/providers/sftp/docs/changelog.rst b/providers/sftp/docs/changelog.rst index 44a1c54cbb377..466d221f03f6c 100644 --- a/providers/sftp/docs/changelog.rst +++ b/providers/sftp/docs/changelog.rst @@ -27,6 +27,13 @@ Changelog --------- +Breaking changes +~~~~~~~~~~~~~~~~ + +* ``Bump minimum paramiko to 4.0.0; DSA/DSS keys are no longer supported (#54079)`` + + This provider requires paramiko 4.0+, which dropped DSS/DSA keys; see `paramiko changelog `__. To migrate: create a non-DSA key pair (Ed25519 or RSA are typical, e.g. ``ssh-keygen -t ed25519``), add the public key to the SFTP server, then update your Airflow SFTP (or shared SSH) connection so ``key_file`` or the ``private_key`` extra uses the new key, and ensure any ``host_key`` extra is not in ``ssh-dss`` form. Host key pinning should use ``ssh-rsa``, ``ssh-ed25519``, or ``ecdsa-sha2-nistp*`` tokens as in ``ssh-keyscan`` output. If you are not ready to migrate keys, stay on a provider release that still pins ``paramiko<4`` until you can switch. + 5.8.2 ..... diff --git a/providers/sftp/docs/index.rst b/providers/sftp/docs/index.rst index 96013f5eb4642..54e75eca425e6 100644 --- a/providers/sftp/docs/index.rst +++ b/providers/sftp/docs/index.rst @@ -104,7 +104,7 @@ PIP package Version required ``apache-airflow`` ``>=2.11.0`` ``apache-airflow-providers-ssh`` ``>=4.0.0`` ``apache-airflow-providers-common-compat`` ``>=1.12.0`` -``paramiko`` ``>=3.5.1,<4.0.0`` +``paramiko`` ``>=4.0.0`` ``asyncssh`` ``>=2.12.0; python_version < "3.14"`` ``asyncssh`` ``>=2.22.0; python_version >= "3.14"`` ========================================== ====================================== diff --git a/providers/sftp/pyproject.toml b/providers/sftp/pyproject.toml index e56b35bbb6f6d..85665739ca76c 100644 --- a/providers/sftp/pyproject.toml +++ b/providers/sftp/pyproject.toml @@ -63,8 +63,7 @@ dependencies = [ "apache-airflow>=2.11.0", "apache-airflow-providers-ssh>=4.0.0", "apache-airflow-providers-common-compat>=1.12.0", - # TODO: Bump to >= 4.0.0 once https://github.com/apache/airflow/issues/54079 is handled - "paramiko>=3.5.1,<4.0.0", + "paramiko>=4.0.0", "asyncssh>=2.12.0; python_version < '3.14'", "asyncssh>=2.22.0; python_version >= '3.14'", ] diff --git a/providers/ssh/README.rst b/providers/ssh/README.rst index 799684e2f07e6..8654bcea8dfff 100644 --- a/providers/ssh/README.rst +++ b/providers/ssh/README.rst @@ -56,7 +56,7 @@ PIP package Version required ``apache-airflow`` ``>=2.11.0`` ``apache-airflow-providers-common-compat`` ``>=1.12.0`` ``asyncssh`` ``>=2.12.0`` -``paramiko`` ``>=3.5.1,<4.0.0`` +``paramiko`` ``>=4.0.0`` ========================================== ================== The changelog for the provider package can be found in the diff --git a/providers/ssh/docs/changelog.rst b/providers/ssh/docs/changelog.rst index afcac791f850e..47c5a6357be55 100644 --- a/providers/ssh/docs/changelog.rst +++ b/providers/ssh/docs/changelog.rst @@ -27,6 +27,13 @@ Changelog --------- +Breaking changes +~~~~~~~~~~~~~~~~ + +* ``Bump minimum paramiko to 4.0.0; DSA/DSS private keys and ssh-dss host keys are no longer supported (#54079)`` + + Paramiko 4.0 removed DSS/DSA support—see `paramiko changelog `__ for upstream details. If you use a DSA private key in an SSH connection, generate a new key (for example ``ssh-keygen -t ed25519`` or ``-t rsa``), install the public key on the server, and point your Airflow connection at the new key file or ``private_key`` extra. If you pin the remote host with a ``host_key`` extra in ``ssh-dss`` form, obtain the server's current RSA, ECDSA, or Ed25519 host key (for example via ``ssh-keyscan``) and replace the value. The same constraints apply to SFTP connections that rely on paramiko via the SSH provider. Until you can migrate, stay on a provider release that still pins ``paramiko<4``. + 5.0.3 ..... diff --git a/providers/ssh/docs/connections/ssh.rst b/providers/ssh/docs/connections/ssh.rst index a248a60477c4f..2ac847505580a 100644 --- a/providers/ssh/docs/connections/ssh.rst +++ b/providers/ssh/docs/connections/ssh.rst @@ -54,7 +54,7 @@ Extra (optional) * ``no_host_key_check`` - Set to ``false`` to restrict connecting to hosts with no entries in ``~/.ssh/known_hosts`` (Hosts file). This provides maximum protection against trojan horse attacks, but can be troublesome when the ``/etc/ssh/ssh_known_hosts`` file is poorly maintained or connections to new hosts are frequently made. This option forces the user to manually add all new hosts. Default is ``true``, ssh will automatically add new host keys to the user known hosts files. * ``allow_host_key_change`` - Set to ``true`` if you want to allow connecting to hosts that has host key changed or when you get 'REMOTE HOST IDENTIFICATION HAS CHANGED' error. This won't protect against Man-In-The-Middle attacks. Other possible solution is to remove the host entry from ``~/.ssh/known_hosts`` file. Default is ``false``. * ``look_for_keys`` - Set to ``false`` if you want to disable searching for discoverable private key files in ``~/.ssh/`` - * ``host_key`` - The base64 encoded ssh-rsa public key of the host or "ssh- " (as you would find in the ``known_hosts`` file). Specifying this allows making the connection if and only if the public key of the endpoint matches this value. + * ``host_key`` - The base64-encoded public key of the host, or ``" "`` as in an OpenSSH ``known_hosts`` / ``ssh-keyscan`` line (without the host field). Specifying this allows making the connection if and only if the public key of the endpoint matches this value. Supported key type strings include ``ssh-rsa``, ``ssh-ed25519``, ``ecdsa-sha2-nistp256``, ``ecdsa-sha2-nistp384``, and ``ecdsa-sha2-nistp521``. Examples: ``ssh-rsa AAAA...``, ``ssh-ed25519 AAAA...``, or ``ecdsa-sha2-nistp256 AAAA...``. A bare base64 value (no type prefix) is treated as ``ssh-rsa``. DSA/DSS (``ssh-dss``) host keys are not supported because `paramiko 4.0 removed DSS support `__; generate a new host key and update this field. * ``disabled_algorithms`` - A dictionary mapping algorithm type to an iterable of algorithm identifiers, which will be disabled for the lifetime of the transport. * ``ciphers`` - A list of ciphers to use in order of preference. diff --git a/providers/ssh/docs/index.rst b/providers/ssh/docs/index.rst index d2fbdb05361ad..21f55e6c6858f 100644 --- a/providers/ssh/docs/index.rst +++ b/providers/ssh/docs/index.rst @@ -95,7 +95,7 @@ PIP package Version required ``apache-airflow`` ``>=2.11.0`` ``apache-airflow-providers-common-compat`` ``>=1.12.0`` ``asyncssh`` ``>=2.12.0`` -``paramiko`` ``>=3.5.1,<4.0.0`` +``paramiko`` ``>=4.0.0`` ========================================== ================== Downloading official packages diff --git a/providers/ssh/pyproject.toml b/providers/ssh/pyproject.toml index 5a9d50fccf951..b0f3daf25be6d 100644 --- a/providers/ssh/pyproject.toml +++ b/providers/ssh/pyproject.toml @@ -62,8 +62,7 @@ dependencies = [ "apache-airflow>=2.11.0", "apache-airflow-providers-common-compat>=1.12.0", "asyncssh>=2.12.0", - # TODO: Bump to >= 4.0.0 once https://github.com/apache/airflow/issues/54079 is handled - "paramiko>=3.5.1,<4.0.0", + "paramiko>=4.0.0", ] diff --git a/providers/ssh/src/airflow/providers/ssh/hooks/ssh.py b/providers/ssh/src/airflow/providers/ssh/hooks/ssh.py index 54b7edf6008c7..674cbf2aab437 100644 --- a/providers/ssh/src/airflow/providers/ssh/hooks/ssh.py +++ b/providers/ssh/src/airflow/providers/ssh/hooks/ssh.py @@ -25,7 +25,7 @@ from functools import cached_property from io import StringIO from select import select -from typing import Any +from typing import Any, TypeAlias import paramiko from paramiko.config import SSH_PORT @@ -48,6 +48,10 @@ def is_arg_set(value): # type: ignore[misc,no-redef] return value is not NOTSET +# Concrete host-key classes accept bytes via ``data=``; base ``PKey`` stubs do not. +_HostKeyConstructor: TypeAlias = type[paramiko.RSAKey] | type[paramiko.ECDSAKey] | type[paramiko.Ed25519Key] + + CMD_TIMEOUT = 10 @@ -87,21 +91,34 @@ class SSHHook(BaseHook): once and some connections are transiently refused (e.g. ``sshd`` ``MaxStartups`` throttling). """ - # List of classes to try loading private keys as, ordered (roughly) by most common to least common + # List of classes to try loading private keys as, ordered (roughly) by most common to least common. + # DSA/DSS keys are not supported (removed in paramiko 4.0). _pkey_loaders: Sequence[type[paramiko.PKey]] = ( paramiko.RSAKey, paramiko.ECDSAKey, paramiko.Ed25519Key, - paramiko.DSSKey, ) - _host_key_mappings = { + # Map OpenSSH known_hosts key-type tokens (and legacy short names) to Paramiko key classes. + # DSA/DSS (ssh-dss) is intentionally absent — paramiko 4.0 removed DSS support. + _host_key_mappings: dict[str, _HostKeyConstructor] = { + "ssh-rsa": paramiko.RSAKey, "rsa": paramiko.RSAKey, - "dss": paramiko.DSSKey, - "ecdsa": paramiko.ECDSAKey, + "ssh-ed25519": paramiko.Ed25519Key, "ed25519": paramiko.Ed25519Key, + "ecdsa-sha2-nistp256": paramiko.ECDSAKey, + "ecdsa-sha2-nistp384": paramiko.ECDSAKey, + "ecdsa-sha2-nistp521": paramiko.ECDSAKey, + # Legacy short name from older Airflow host_key parsing (ssh-ecdsa -> ecdsa). + "ecdsa": paramiko.ECDSAKey, + "ssh-ecdsa": paramiko.ECDSAKey, } + _SUPPORTED_HOST_KEY_TYPES_MSG = ( + "ssh-rsa, ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521 " + "(bare base64 is treated as ssh-rsa)" + ) + conn_name_attr = "ssh_conn_id" default_conn_name = "ssh_default" conn_type = "ssh" @@ -227,13 +244,7 @@ def __init__( self.ciphers = extra_options.get("ciphers") if host_key is not None: - if host_key.startswith("ssh-"): - key_type, host_key = host_key.split(None)[:2] - key_constructor = self._host_key_mappings[key_type[4:]] - else: - key_constructor = paramiko.RSAKey - decoded_host_key = decodebytes(host_key.encode("utf-8")) - self.host_key = key_constructor(data=decoded_host_key) + self.host_key = self._pkey_from_host_key(host_key) self.no_host_key_check = False if self.cmd_timeout is NOTSET: @@ -402,6 +413,72 @@ def get_tunnel( logger=self.log, ) + @classmethod + def _pkey_from_host_key(cls, host_key: str) -> paramiko.PKey: + """ + Build a Paramiko public host key from a connection ``host_key`` extra. + + Accepts either bare base64 (treated as ``ssh-rsa`` for backward compatibility) or an + OpenSSH ``known_hosts``-style ``" "`` string. + + :param host_key: host key material from the connection extra + :return: Paramiko public key object + :raises ValueError: if the key type is unsupported (including DSA/DSS) or data is invalid + """ + key_constructor, key_data = cls._parse_host_key(host_key) + decoded_host_key = decodebytes(key_data.encode("utf-8")) + return key_constructor(data=decoded_host_key) + + @classmethod + def _parse_host_key(cls, host_key: str) -> tuple[_HostKeyConstructor, str]: + """ + Parse a ``host_key`` extra into ``(key class, base64 key data)``. + + Typed values use the first whitespace-separated token as the algorithm name. Supported + tokens include ``ssh-rsa``, ``ssh-ed25519``, and ``ecdsa-sha2-nistp*`` (as in OpenSSH + ``known_hosts`` / ``ssh-keyscan``). DSA/DSS (``ssh-dss``) is rejected. A single token of + base64 data defaults to RSA. + """ + parts = host_key.split(None) + if len(parts) >= 2: + key_type, key_data = parts[0], parts[1] + if key_type in {"ssh-dss", "dss"}: + raise ValueError( + "DSA/DSS host keys are not supported. Paramiko 4.0 removed DSS support; " + "use an RSA, ECDSA, or Ed25519 host key and update the connection `host_key`." + ) + # Typed line if it matches a known algorithm or looks like an OpenSSH key-type token. + if ( + key_type in cls._host_key_mappings + or key_type.startswith("ssh-") + or key_type.startswith("ecdsa-sha2-") + ): + key_constructor = cls._host_key_mappings.get(key_type) + if key_constructor is None: + raise ValueError( + f"Unsupported SSH host key algorithm {key_type!r}. " + f"Supported types are: {cls._SUPPORTED_HOST_KEY_TYPES_MSG}." + ) + return key_constructor, key_data + + # Bare base64 (or value without a recognized key-type token): historical default is RSA. + return paramiko.RSAKey, host_key + + @staticmethod + def _verify_pkey_usable(key: paramiko.PKey) -> None: + """ + Ensure a loaded private key can sign data (guards against wrong-type loads). + + Paramiko 5 removed SHA-1 ``ssh-rsa`` as a signing algorithm while keys still report + name ``ssh-rsa``. Prefer an algorithm from ``HASHES`` when the name is absent. + """ + hashes = getattr(key, "HASHES", None) or {} + key_name = key.get_name() + if hashes and key_name not in hashes: + key.sign_ssh_data(b"", algorithm=next(iter(hashes))) + else: + key.sign_ssh_data(b"") + def _pkey_from_private_key(self, private_key: str, passphrase: str | None = None) -> paramiko.PKey: """ Create an appropriate Paramiko key for a given private key. @@ -418,14 +495,16 @@ def _pkey_from_private_key(self, private_key: str, passphrase: str | None = None key = pkey_class.from_private_key(StringIO(private_key), password=passphrase) # Test it actually works. If Paramiko loads an openssh generated key, sometimes it will # happily load it as the wrong type, only to fail when actually used. - key.sign_ssh_data(b"") + # Paramiko 5+ no longer treats legacy key names (e.g. ssh-rsa) as signing algorithms; + # pass an explicit algorithm from the key's HASHES map when needed. + self._verify_pkey_usable(key) return key - except (paramiko.ssh_exception.SSHException, ValueError): + except (paramiko.ssh_exception.SSHException, ValueError, KeyError): continue raise AirflowException( - "Private key provided cannot be read by paramiko." - "Ensure key provided is valid for one of the following" - "key formats: RSA, DSS, ECDSA, or Ed25519" + "Private key provided cannot be read by paramiko. " + "Ensure key provided is valid for one of the following " + "key formats: RSA, ECDSA, or Ed25519." ) def exec_ssh_client_command( diff --git a/providers/ssh/tests/unit/ssh/hooks/test_ssh.py b/providers/ssh/tests/unit/ssh/hooks/test_ssh.py index e277c1dc3f654..3d65a1907b8fe 100644 --- a/providers/ssh/tests/unit/ssh/hooks/test_ssh.py +++ b/providers/ssh/tests/unit/ssh/hooks/test_ssh.py @@ -69,6 +69,8 @@ def generate_host_key(pkey: paramiko.PKey): TEST_PKEY_ECDSA = paramiko.ECDSAKey.generate() TEST_PRIVATE_KEY_ECDSA = generate_key_string(pkey=TEST_PKEY_ECDSA) +TEST_HOST_KEY_ECDSA = TEST_PKEY_ECDSA.get_base64() +TEST_HOST_KEY_ECDSA_TYPED = f"{TEST_PKEY_ECDSA.get_name()} {TEST_HOST_KEY_ECDSA}" TEST_TIMEOUT = 20 TEST_CONN_TIMEOUT = 30 @@ -574,6 +576,78 @@ def test_ssh_connection_with_host_key_extra_with_type(self, ssh_client): hook.remote_host, "ssh-rsa", hook.host_key ) + @mock.patch.object(SSHHook, "get_connection") + def test_dss_host_key_in_connection_extra_raises(self, mock_get_connection): + mock_get_connection.return_value = Connection( + conn_id="ssh_dss_host_key", + conn_type="ssh", + host="remote_host", + login="user", + extra=json.dumps({"host_key": "ssh-dss AAAAB3NzaC1kc3MAAA==", "no_host_key_check": False}), + ) + with pytest.raises(ValueError, match="DSA/DSS host keys"): + SSHHook(ssh_conn_id="ssh_dss_host_key") + + @mock.patch.object(SSHHook, "get_connection") + def test_unsupported_host_key_algorithm_raises(self, mock_get_connection): + mock_get_connection.return_value = Connection( + conn_id="ssh_fake_alg", + conn_type="ssh", + host="remote_host", + login="user", + extra=json.dumps( + {"host_key": "ssh-fake AAAAB3NzaC1yc2EAAAADAQABAA==", "no_host_key_check": False} + ), + ) + with pytest.raises(ValueError, match=r"Unsupported SSH host key algorithm 'ssh-fake'"): + SSHHook(ssh_conn_id="ssh_fake_alg") + + @pytest.mark.parametrize( + ("host_key_value", "expected_key_type"), + [ + (f"ssh-rsa {TEST_HOST_KEY}", paramiko.RSAKey), + (TEST_HOST_KEY_ECDSA_TYPED, paramiko.ECDSAKey), + # Bare base64 remains RSA for backward compatibility. + (TEST_HOST_KEY, paramiko.RSAKey), + ], + ) + @mock.patch.object(SSHHook, "get_connection") + def test_supported_host_key_types_are_parsed( + self, mock_get_connection, host_key_value, expected_key_type + ): + mock_get_connection.return_value = Connection( + conn_id="ssh_typed_host_key", + conn_type="ssh", + host="remote_host", + login="user", + extra=json.dumps({"host_key": host_key_value, "no_host_key_check": False}), + ) + hook = SSHHook(ssh_conn_id="ssh_typed_host_key") + assert isinstance(hook.host_key, expected_key_type) + + @pytest.mark.parametrize( + ("host_key_value", "expected_key_class", "expected_data"), + [ + (f"ssh-rsa {TEST_HOST_KEY}", paramiko.RSAKey, TEST_HOST_KEY), + (TEST_HOST_KEY_ECDSA_TYPED, paramiko.ECDSAKey, TEST_HOST_KEY_ECDSA), + ( + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY=", + paramiko.ECDSAKey, + "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY=", + ), + ( + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJustAPlaceholderBase64Value", + paramiko.Ed25519Key, + "AAAAC3NzaC1lZDI1NTE5AAAAIJustAPlaceholderBase64Value", + ), + (TEST_HOST_KEY, paramiko.RSAKey, TEST_HOST_KEY), + ], + ) + def test_parse_host_key_selects_constructor(self, host_key_value, expected_key_class, expected_data): + key_class, key_data = SSHHook._parse_host_key(host_key_value) + assert key_class is expected_key_class + assert key_data == expected_data + @mock.patch("airflow.providers.ssh.hooks.ssh.paramiko.SSHClient") def test_ssh_connection_with_no_host_key_where_no_host_key_check_is_false(self, ssh_client): hook = SSHHook(ssh_conn_id=self.CONN_SSH_WITH_NO_HOST_KEY_AND_NO_HOST_KEY_CHECK_FALSE) diff --git a/uv.lock b/uv.lock index 7176da8f80076..c63dca91b69a7 100644 --- a/uv.lock +++ b/uv.lock @@ -642,7 +642,7 @@ name = "aiohttp-cors" version = "0.8.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiohttp" }, + { name = "aiohttp", marker = "python_full_version < '3.15'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/d89e846a5444b3d5eb8985a6ddb0daef3774928e1bfbce8e84ec97b0ffa7/aiohttp_cors-0.8.1.tar.gz", hash = "sha256:ccacf9cb84b64939ea15f859a146af1f662a6b1d68175754a07315e305fb1403", size = 38626, upload-time = "2025-03-31T14:16:20.048Z" } wheels = [ @@ -2698,7 +2698,7 @@ requires-dist = [ { name = "types-deprecated", marker = "extra == 'mypy'", specifier = ">=1.2.9.20240311" }, { name = "types-docutils", marker = "extra == 'mypy'", specifier = ">=0.21.0.20240704" }, { name = "types-markdown", marker = "extra == 'mypy'", specifier = ">=3.6.0.20240316" }, - { name = "types-paramiko", marker = "extra == 'mypy'", specifier = ">=3.4.0.20240423,<4.0.0" }, + { name = "types-paramiko", marker = "extra == 'mypy'", specifier = ">=4.0.0.20260402" }, { name = "types-protobuf", marker = "extra == 'mypy'", specifier = ">=5.26.0.20240422" }, { name = "types-pymysql", marker = "extra == 'mypy'", specifier = ">=1.1.0.20240425" }, { name = "types-python-dateutil", marker = "extra == 'mypy'", specifier = ">=2.9.0.20240316" }, @@ -7508,7 +7508,7 @@ requires-dist = [ { name = "apache-airflow-providers-ssh", editable = "providers/ssh" }, { name = "asyncssh", marker = "python_full_version < '3.14'", specifier = ">=2.12.0" }, { name = "asyncssh", marker = "python_full_version >= '3.14'", specifier = ">=2.22.0" }, - { name = "paramiko", specifier = ">=3.5.1,<4.0.0" }, + { name = "paramiko", specifier = ">=4.0.0" }, { name = "sshfs", marker = "extra == 'sshfs'", specifier = ">=2023.1.0" }, ] provides-extras = ["openlineage", "sshfs"] @@ -7785,7 +7785,7 @@ requires-dist = [ { name = "apache-airflow", editable = "." }, { name = "apache-airflow-providers-common-compat", editable = "providers/common/compat" }, { name = "asyncssh", specifier = ">=2.12.0" }, - { name = "paramiko", specifier = ">=3.5.1,<4.0.0" }, + { name = "paramiko", specifier = ">=4.0.0" }, ] [package.metadata.requires-dev] @@ -10822,7 +10822,7 @@ name = "colorful" version = "0.5.8" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "colorama", marker = "python_full_version < '3.15' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/82/31/109ef4bedeb32b4202e02ddb133162457adc4eb890a9ed9c05c9dd126ed0/colorful-0.5.8.tar.gz", hash = "sha256:bb16502b198be2f1c42ba3c52c703d5f651d826076817185f0294c1a549a7445", size = 209361, upload-time = "2025-10-29T11:53:21.663Z" } wheels = [ @@ -14467,6 +14467,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/97/9c/1646ca469bc2dc299ac393c8d31136c6c22a35ca1e373fa462ac01100d37/inputimeout-1.0.4-py3-none-any.whl", hash = "sha256:f4e23d27753cfc25268eefc8d52a3edc46280ad831d226617c51882423475a43", size = 4639, upload-time = "2018-03-02T14:28:06.903Z" }, ] +[[package]] +name = "invoke" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/f6/227c48c5fe47fa178ccf1fda8f047d16c97ba926567b661e9ce2045c600c/invoke-3.0.3.tar.gz", hash = "sha256:437b6a622223824380bfb4e64f612711a6b648c795f565efc8625af66fb57f0c", size = 343419, upload-time = "2026-04-07T15:17:48.307Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/de/bbc12563bbf979618d17625a4e753ff7a078523e28d870d3626daa97261a/invoke-3.0.3-py3-none-any.whl", hash = "sha256:f11327165e5cbb89b2ad1d88d3292b5113332c43b8553b494da435d6ec6f5053", size = 160958, upload-time = "2026-04-07T15:17:46.875Z" }, +] + [[package]] name = "ipdb" version = "0.13.13" @@ -17348,9 +17357,9 @@ name = "opencensus" version = "0.11.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "google-api-core" }, - { name = "opencensus-context" }, - { name = "six" }, + { name = "google-api-core", marker = "python_full_version < '3.15'" }, + { name = "opencensus-context", marker = "python_full_version < '3.15'" }, + { name = "six", marker = "python_full_version < '3.15'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/15/a7/a46dcffa1b63084f9f17fe3c8cb20724c4c8f91009fd0b2cfdb27d5d2b35/opencensus-0.11.4.tar.gz", hash = "sha256:cbef87d8b8773064ab60e5c2a1ced58bbaa38a6d052c41aec224958ce544eff2", size = 64966, upload-time = "2024-01-03T18:04:07.085Z" } wheels = [ @@ -18039,16 +18048,17 @@ all = [ [[package]] name = "paramiko" -version = "3.5.1" +version = "5.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bcrypt" }, { name = "cryptography" }, + { name = "invoke" }, { name = "pynacl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/15/ad6ce226e8138315f2451c2aeea985bf35ee910afb477bae7477dc3a8f3b/paramiko-3.5.1.tar.gz", hash = "sha256:b2c665bc45b2b215bd7d7f039901b14b067da00f3a11e6640995fd58f2664822", size = 1566110, upload-time = "2025-02-04T02:37:59.783Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/93/dcc25d52f49022ae6175d15e6bd751f1acc99b98bc61fc55e5155a7be2e7/paramiko-5.0.0.tar.gz", hash = "sha256:36763b5b95c2a0dcfdf1abc48e48156ee425b21efe2f0e787c2dd5a95c0e5e79", size = 1548586, upload-time = "2026-05-09T18:28:52.256Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/f8/c7bd0ef12954a81a1d3cea60a13946bd9a49a0036a5927770c461eade7ae/paramiko-3.5.1-py3-none-any.whl", hash = "sha256:43b9a0501fc2b5e70680388d9346cf252cfb7d00b0667c39e80eb43a408b8f61", size = 227298, upload-time = "2025-02-04T02:37:57.672Z" }, + { url = "https://files.pythonhosted.org/packages/82/5b/eadf6d45de38d30ab603f49393b6cd2cbe7e233af8cf90197e32782b68a9/paramiko-5.0.0-py3-none-any.whl", hash = "sha256:b7044611c30140d9a75261653210e2002977b71a0497ff3ba0d98d7edbf62f7c", size = 208919, upload-time = "2026-05-09T18:28:50.295Z" }, ] [[package]] @@ -20692,14 +20702,14 @@ name = "ray" version = "2.55.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click" }, - { name = "filelock" }, - { name = "jsonschema" }, - { name = "msgpack" }, - { name = "packaging" }, - { name = "protobuf" }, - { name = "pyyaml" }, - { name = "requests" }, + { name = "click", marker = "python_full_version < '3.15'" }, + { name = "filelock", marker = "python_full_version < '3.15'" }, + { name = "jsonschema", marker = "python_full_version < '3.15'" }, + { name = "msgpack", marker = "python_full_version < '3.15'" }, + { name = "packaging", marker = "python_full_version < '3.15'" }, + { name = "protobuf", marker = "python_full_version < '3.15'" }, + { name = "pyyaml", marker = "python_full_version < '3.15'" }, + { name = "requests", marker = "python_full_version < '3.15'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/7e/d0/a85097dd53aaca1a44acc4dd0b3d2c0e9233179433e2ee326e4018ab3cf7/ray-2.55.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:2d5786661e192148719accc959def6cdcabd7a24cd9008005bf3d0e3c8cfd529", size = 65829601, upload-time = "2026-04-22T20:09:10.013Z" }, @@ -20724,20 +20734,20 @@ wheels = [ [package.optional-dependencies] default = [ - { name = "aiohttp" }, - { name = "aiohttp-cors" }, - { name = "colorful" }, - { name = "grpcio" }, - { name = "opencensus" }, - { name = "opentelemetry-exporter-prometheus" }, - { name = "opentelemetry-proto" }, - { name = "opentelemetry-sdk" }, - { name = "prometheus-client" }, - { name = "py-spy" }, - { name = "pydantic" }, - { name = "requests" }, - { name = "smart-open" }, - { name = "virtualenv" }, + { name = "aiohttp", marker = "python_full_version < '3.15'" }, + { name = "aiohttp-cors", marker = "python_full_version < '3.15'" }, + { name = "colorful", marker = "python_full_version < '3.15'" }, + { name = "grpcio", marker = "python_full_version < '3.15'" }, + { name = "opencensus", marker = "python_full_version < '3.15'" }, + { name = "opentelemetry-exporter-prometheus", marker = "python_full_version < '3.15'" }, + { name = "opentelemetry-proto", marker = "python_full_version < '3.15'" }, + { name = "opentelemetry-sdk", marker = "python_full_version < '3.15'" }, + { name = "prometheus-client", marker = "python_full_version < '3.15'" }, + { name = "py-spy", marker = "python_full_version < '3.15'" }, + { name = "pydantic", marker = "python_full_version < '3.15'" }, + { name = "requests", marker = "python_full_version < '3.15'" }, + { name = "smart-open", marker = "python_full_version < '3.15'" }, + { name = "virtualenv", marker = "python_full_version < '3.15'" }, ] [[package]] @@ -21849,8 +21859,8 @@ name = "secretstorage" version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cryptography", marker = "python_full_version >= '3.14' or platform_machine != 'arm64' or sys_platform != 'darwin'" }, - { name = "jeepney", marker = "python_full_version >= '3.14' or platform_machine != 'arm64' or sys_platform != 'darwin'" }, + { name = "cryptography", marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version < '3.15' and sys_platform == 'emscripten') or (python_full_version < '3.15' and sys_platform == 'win32') or (platform_machine != 'arm64' and sys_platform == 'darwin') or (sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "jeepney", marker = "(python_full_version >= '3.14' and sys_platform == 'darwin') or (python_full_version < '3.15' and sys_platform == 'emscripten') or (python_full_version < '3.15' and sys_platform == 'win32') or (platform_machine != 'arm64' and sys_platform == 'darwin') or (sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ @@ -22049,7 +22059,7 @@ name = "smart-open" version = "7.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "wrapt" }, + { name = "wrapt", marker = "python_full_version < '3.15'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/db/c6/22e7a2acd5d27941e85e0d7ede398da5abe2e4677d2265c924157247c32e/smart_open-7.7.1.tar.gz", hash = "sha256:9414ba5733e28309f29b28a303b0f1054ad23fe0275f1a1b600c80a724f4bd1a", size = 54952, upload-time = "2026-06-26T07:56:35.309Z" } wheels = [ @@ -23525,14 +23535,14 @@ wheels = [ [[package]] name = "types-paramiko" -version = "3.5.0.20250801" +version = "5.0.0.20260617" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/c4/4df427c835e9c795662241410732ab936c6f58be798ec25ce7015771c448/types_paramiko-3.5.0.20250801.tar.gz", hash = "sha256:e79ff84eaf44f2a5ad811743edef5d9c0cd6632a264a526f00405b87db1ec99b", size = 28838, upload-time = "2025-08-01T03:48:52.777Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/89/902652d7b62bb7cd2b73c7f08c8fd03945c223c3814022f0ad305e1018b9/types_paramiko-5.0.0.20260617.tar.gz", hash = "sha256:50a5b0dc68b39d30097cb7d93b4915dbbc97ed740ea633bd492be25ca1f25df4", size = 28474, upload-time = "2026-06-17T06:56:52.955Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/5b/0f0bb1e45f7547d081ae9d490a88c6f6031d0f2c97459236e0a2a8b27207/types_paramiko-3.5.0.20250801-py3-none-any.whl", hash = "sha256:3e02a0fcf2b7e7b213e0cd569f7223ff9af417052a4d149d84172ebaa6fd742e", size = 39705, upload-time = "2025-08-01T03:48:51.855Z" }, + { url = "https://files.pythonhosted.org/packages/50/4d/eab89decb4201dfff665a3b92088f384ff1e165bf6caa447509733bc1bc6/types_paramiko-5.0.0.20260617-py3-none-any.whl", hash = "sha256:83d87c396405666524441710ec59e693bca8b19021861456ac0a2401327812f8", size = 37123, upload-time = "2026-06-17T06:56:51.81Z" }, ] [[package]]