Skip to content

Commit

Permalink
Make keyring optional on ppc64le and s390x
Browse files Browse the repository at this point in the history
Twine can now be installed on Linux pp64le and s390x without C and Rust
compiler. The `keyring` package has an indirect dependency on `cryptography`
package. Upstream PyCA does not provide wheels for ppc64le and s390x
architecture, because the team has no means to run CI in reasonable
time.

Fixes: 1158
Signed-off-by: Christian Heimes <[email protected]>
  • Loading branch information
tiran authored and sigmavirus24 committed Oct 6, 2024
1 parent 4bb4001 commit 38e0c7a
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@ exclude_lines =
# Don't complain if non-runnable code isn't run
if __name__ == .__main__.:

# exclude typing.TYPE_CHECKING
if TYPE_CHECKING:

[html]
show_contexts = True
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ dependencies = [
"requests-toolbelt >= 0.8.0, != 0.9.0",
"urllib3 >= 1.26.0",
"importlib-metadata >= 3.6",
"keyring >= 15.1",
# workaround for missing binaries on these platforms, see #1158
"keyring >= 15.1; platform_machine != 'ppc64le' and platform_machine != 's390x'",
"rfc3986 >= 1.4.0",
"rich >= 12.0.0",

Expand All @@ -60,6 +61,9 @@ check = "twine.commands.check:main"
upload = "twine.commands.upload:main"
register = "twine.commands.register:main"

[project.optional-dependencies]
keyring = ["keyring >= 15.1"]

[project.scripts]
twine = "twine.__main__:main"

Expand Down
27 changes: 27 additions & 0 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import getpass
import logging
import platform
import re

import pytest
Expand All @@ -26,6 +27,15 @@ def get_credential(system, user):
assert username == "entered user"


def test_get_username_keyring_not_installed_defers_to_prompt(
monkeypatch, entered_username, config
):
monkeypatch.setattr(auth, "keyring", None)

username = auth.Resolver(config, auth.CredentialInput()).username
assert username == "entered user"


def test_get_password_keyring_defers_to_prompt(monkeypatch, entered_password, config):
class MockKeyring:
@staticmethod
Expand All @@ -38,6 +48,15 @@ def get_password(system, user):
assert pw == "entered pw"


def test_get_password_keyring_not_installed_defers_to_prompt(
monkeypatch, entered_password, config
):
monkeypatch.setattr(auth, "keyring", None)

pw = auth.Resolver(config, auth.CredentialInput("user")).password
assert pw == "entered pw"


def test_no_password_defers_to_prompt(monkeypatch, entered_password, config):
config.update(password=None)
pw = auth.Resolver(config, auth.CredentialInput("user")).password
Expand Down Expand Up @@ -272,3 +291,11 @@ def test_warns_for_empty_password(
assert auth.Resolver(config, auth.CredentialInput()).password == password

assert caplog.messages[0].startswith(warning)


@pytest.mark.skipif(
platform.machine() in {"ppc64le", "s390x"},
reason="keyring module is optional on ppc64le and s390x",
)
def test_keyring_module():
assert auth.keyring is not None
20 changes: 17 additions & 3 deletions twine/auth.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import functools
import getpass
import logging
from typing import Callable, Optional, Type, cast

import keyring
from typing import TYPE_CHECKING, Callable, Optional, Type, cast

# keyring has an indirect dependency on PyCA cryptography, which has no
# pre-built wheels for ppc64le and s390x, see #1158.
if TYPE_CHECKING:
import keyring
else:
try:
import keyring
except ModuleNotFoundError: # pragma: no cover
keyring = None

from twine import exceptions
from twine import utils
Expand Down Expand Up @@ -60,6 +68,9 @@ def system(self) -> Optional[str]:
return self.config["repository"]

def get_username_from_keyring(self) -> Optional[str]:
if keyring is None:
logger.info("keyring module is not available")
return None
try:
system = cast(str, self.system)
logger.info("Querying keyring for username")
Expand All @@ -74,6 +85,9 @@ def get_username_from_keyring(self) -> Optional[str]:
return None

def get_password_from_keyring(self) -> Optional[str]:
if keyring is None:
logger.info("keyring module is not available")
return None
try:
system = cast(str, self.system)
username = cast(str, self.username)
Expand Down

0 comments on commit 38e0c7a

Please sign in to comment.