11"""App Module"""
22import os
3- import xml .etree .ElementTree as ET
43import logging
54from concurrent .futures import ThreadPoolExecutor
6- import requests as requests
5+
6+ import xml .etree .ElementTree as ET
7+ import requests
78from flask import Flask , request , make_response
89
10+ logging .basicConfig (level = logging .INFO )
11+ logger = logging .getLogger (__name__ )
12+
913app = Flask ('github-cctray' )
1014
11- # Configure logging
12- logging .basicConfig (level = logging .INFO )
13- logger = app .logger
15+ API_BASE_URL = "https://api.github.com"
16+ MAX_WORKERS = 10
17+ TIMEOUT = 10
18+
19+
20+ def get_workflows (owner , repo , headers ):
21+ """Get the workflows for a given owner and repo from the GitHub API.
22+
23+ Args:
24+ owner (str): The owner of the repository.
25+ repo (str): The repository name.
26+ headers (dict): HTTP headers to be sent with the request.
27+
28+ Returns:
29+ list: A list of workflows for the given repository.
30+
31+ Raises:
32+ requests.HTTPError: If the request to the GitHub API fails.
33+ """
34+ endpoint = f"{ API_BASE_URL } /repos/{ owner } /{ repo } /actions/workflows"
35+ response = requests .get (endpoint , headers = headers , timeout = TIMEOUT )
36+ response .raise_for_status ()
37+ return response .json ()["workflows" ]
38+
39+
40+ def get_workflow_runs (workflow , headers ):
41+ """Get the workflow runs for a specific workflow from the GitHub API.
42+
43+ Args:
44+ workflow (dict): The workflow information.
45+ headers (dict): HTTP headers to be sent with the request.
1446
47+ Returns:
48+ list: A list of workflow runs for the given workflow.
1549
16- def get_workflow_runs (owner , repo , token ):
17- endpoint = f"https://api.github.com/repos/{ owner } /{ repo } /actions/workflows"
50+ Raises:
51+ requests.HTTPError: If the request to the GitHub API fails.
52+ """
53+ url = f"{ workflow ['url' ]} /runs"
54+ response = requests .get (url , headers = headers , timeout = TIMEOUT )
55+ response .raise_for_status ()
56+ return response .json ()["workflow_runs" ]
57+
58+
59+ def get_all_workflow_runs (owner , repo , token ):
60+ """Get all workflow runs for a given owner, repo, and token.
61+
62+ Args:
63+ owner (str): The owner of the repository.
64+ repo (str): The repository name.
65+ token (str): The GitHub token for authentication.
66+
67+ Returns:
68+ list: A list of all workflow runs for the given repository.
69+
70+ Raises:
71+ requests.HTTPError: If the request to the GitHub API fails.
72+ """
1873 headers = {
1974 "Authorization" : f"Bearer { token } " ,
2075 "Accept" : "application/vnd.github.v3+json"
2176 }
22- results = []
2377
24- response = requests .get (endpoint , headers = headers , timeout = 10 )
25- if response .status_code != 200 :
26- logger .error ("GitHub API returned status code %d" , response .status_code )
27- else :
28- workflows = response .json ()["workflows" ]
29- with ThreadPoolExecutor (max_workers = 10 ) as executor :
30- futures = []
31- for workflow in workflows :
32- future = executor .submit (requests .get , f"{ workflow ['url' ]} /runs" , headers = headers , timeout = 10 )
33- futures .append (future )
34- for future in futures :
35- response = future .result ()
36- if response is None :
37- logger .error ("Failed to get response from GitHub API" )
38- elif response .status_code != 200 :
39- logger .error ("GitHub API returned status code %d" , response .status_code )
40- else :
41- data = response .json ()
42- results += data ["workflow_runs" ]
78+ workflows = get_workflows (owner , repo , headers )
79+ with ThreadPoolExecutor (max_workers = MAX_WORKERS ) as executor :
80+ futures = [executor .submit (get_workflow_runs , workflow , headers ) for workflow in workflows ]
81+
82+ results = []
83+ for future in futures :
84+ data = future .result ()
85+ results += data
4386
4487 return results
4588
4689
4790@app .route ('/' )
4891def index ():
92+ """Endpoint for generating the CCTray XML.
93+
94+ Returns:
95+ flask.Response: The XML response containing the project information.
96+ """
4997 owner = request .args .get ("owner" ) or request .form .get ('owner' )
5098 repo = request .args .get ("repo" ) or request .form .get ('repo' )
5199 token = os .environ .get ("GITHUB_TOKEN" )
@@ -54,9 +102,8 @@ def index():
54102 logger .warning ("Missing parameter(s) or Environment Variable" )
55103 return make_response ("Missing parameter(s)" , 400 )
56104
57- data = get_workflow_runs (owner , repo , token )
105+ data = get_all_workflow_runs (owner , repo , token )
58106
59- # Sort workflow runs by 'updated_at' timestamp in descending order
60107 workflow_runs = sorted (data , key = lambda run : run ["updated_at" ], reverse = True )
61108
62109 root = ET .Element ("Projects" )
@@ -69,7 +116,7 @@ def index():
69116 project = ET .SubElement (root , "Project" )
70117 project .set ("name" , project_name )
71118
72- # Map 'status' field to 'activity '
119+ # Map 'Github' attributes to 'CCTray '
73120 if run ["status" ] == "completed" :
74121 if run ["conclusion" ] == "success" :
75122 project .set ("activity" , "Sleeping" )
@@ -89,7 +136,6 @@ def index():
89136 response = make_response (ET .tostring (root ).decode ())
90137 response .headers ['Content-Type' ] = 'application/xml'
91138
92- # Log request URI and response code
93139 logger .info ("Request URI: %s Response Code: %d" , request .path , response .status_code )
94140
95141
@@ -98,12 +144,19 @@ def index():
98144
99145@app .errorhandler (Exception )
100146def handle_error (exception ):
101- # Log the error
102- logger .error ("An error occurred: %s" , str (exception ))
147+ """Error handler for handling exceptions raised in the application.
103148
149+ Args:
150+ exception (Exception): The exception object.
151+
152+ Returns:
153+ str: The error message response.
154+ """
155+ logger .error ("An error occurred: %s" , str (exception ))
104156 return "An error occurred." , 500
105157
106158
107159if __name__ == '__main__' :
108160 from waitress import serve
161+
109162 serve (app , host = '0.0.0.0' , port = 8000 )
0 commit comments