Coverage for src / gh_secrets_and_vars_async / common.py: 77%

65 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-16 17:56 +0000

1import os 

2import subprocess 

3import sys 

4 

5from dotenv import dotenv_values 

6from github import Auth, Github 

7from github.GithubException import UnknownObjectException 

8from github.Repository import Repository 

9from loguru import logger 

10 

11 

12def configure_logging(verbose: bool) -> None: 

13 """Configure loguru: silent by default, compact format with --verbose.""" 

14 logger.remove() 

15 if verbose: 

16 logger.add(sys.stderr, level="DEBUG", format=" {message}") 

17 

18 

19def _load_dotenv_values(filename: str = ".env") -> dict[str, str]: 

20 """Read key/value pairs from ``filename`` without mutating the process environment.""" 

21 values = dotenv_values(filename) 

22 return {key: value for key, value in values.items() if value is not None} 

23 

24 

25def load_env_config(filename: str = ".env") -> tuple[str, str, str]: 

26 """Return GH_* configuration with explicit environment variables taking precedence.""" 

27 env_values = _load_dotenv_values(filename) 

28 gh_repo = os.environ.get("GH_REPO", env_values.get("GH_REPO", "")) 

29 gh_account = os.environ.get("GH_ACCOUNT", env_values.get("GH_ACCOUNT", "")) 

30 gh_token = os.environ.get("GH_TOKEN", env_values.get("GH_TOKEN", "")) 

31 return gh_repo, gh_account, gh_token 

32 

33 

34def _get_gh_cli_token() -> str: 

35 """Return the token from ``gh auth token`` or an empty string if unavailable.""" 

36 try: 

37 result = subprocess.run( 

38 ["gh", "auth", "token"], 

39 capture_output=True, 

40 text=True, 

41 check=True, 

42 ) 

43 except (subprocess.CalledProcessError, FileNotFoundError): 

44 return "" 

45 return result.stdout.strip() 

46 

47 

48def _resolve_token(filename: str = ".env", auth_source: str = "auto") -> str: 

49 """Return a GitHub token from the configured auth source.""" 

50 dotenv_token = _load_dotenv_values(filename).get("GH_TOKEN", "").strip() 

51 

52 if auth_source == "dotenv": 

53 if dotenv_token: 

54 logger.debug("Using GitHub token from GH_TOKEN in .env (--env-auth).") 

55 return dotenv_token 

56 raise RuntimeError( 

57 "No GitHub token found in .env. Remove --env-auth or add GH_TOKEN to .env." 

58 ) 

59 

60 if auth_source != "auto": 

61 raise ValueError(f"Unsupported auth_source '{auth_source}'.") 

62 

63 token = os.environ.get("GH_TOKEN", "").strip() 

64 if token: 

65 logger.debug("Using GitHub token from GH_TOKEN environment variable.") 

66 return token 

67 

68 gh_token = _get_gh_cli_token() 

69 if gh_token: 

70 if dotenv_token: 

71 logger.debug( 

72 "Using GitHub token from gh auth token. Ignoring GH_TOKEN from .env; " 

73 "export GH_TOKEN in the current shell to force it." 

74 ) 

75 else: 

76 logger.debug("Using GitHub token from gh auth token.") 

77 return gh_token 

78 

79 if dotenv_token: 

80 logger.debug("Using GitHub token from GH_TOKEN in .env.") 

81 return dotenv_token 

82 

83 raise RuntimeError( 

84 "No GitHub token found. Set GH_TOKEN in .env / environment, " 

85 "or authenticate with: gh auth login" 

86 ) 

87 

88 

89def get_github_repo( 

90 github_account: str, 

91 github_repo_name: str, 

92 auth_source: str = "auto", 

93) -> Repository: 

94 """Get the GitHub repository object. 

95 

96 Tries user lookup first, falls back to organization. 

97 """ 

98 token = _resolve_token(auth_source=auth_source) 

99 auth = Auth.Token(token) 

100 g = Github(auth=auth) 

101 try: 

102 repo = g.get_user(github_account).get_repo(github_repo_name) 

103 except UnknownObjectException as e: 

104 logger.critical(e) 

105 repo = g.get_organization(github_account).get_repo(github_repo_name) 

106 logger.critical("You must add GH_USER to your env file.") 

107 

108 return repo 

109 

110 

111def get_github_client(auth_source: str = "auto") -> Github: 

112 """Create an authenticated Github client from env, ``gh auth token``, or ``.env``.""" 

113 token = _resolve_token(auth_source=auth_source) 

114 auth = Auth.Token(token) 

115 return Github(auth=auth)