yasserrmd commited on
Commit
754e3fa
·
verified ·
1 Parent(s): b3895d7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +95 -59
app.py CHANGED
@@ -5,7 +5,57 @@ from datetime import datetime, timedelta
5
  import time
6
  import os
7
 
8
- def fetch_github_users(github_token=None, max_users=200, min_followers=10):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  """
10
  Fetch GitHub users from UAE directly using GitHub API
11
  Sorted by contributions (primary) and followers (secondary)
@@ -13,14 +63,15 @@ def fetch_github_users(github_token=None, max_users=200, min_followers=10):
13
  # Read token from environment variable if not provided
14
  if not github_token:
15
  github_token = os.getenv('GITHUB_TOKEN')
 
 
 
16
 
17
  headers = {
18
  'Accept': 'application/vnd.github.v3+json',
 
19
  }
20
 
21
- if github_token:
22
- headers['Authorization'] = f'token {github_token}'
23
-
24
  all_users = []
25
 
26
  # Search locations in UAE
@@ -54,30 +105,8 @@ def fetch_github_users(github_token=None, max_users=200, min_followers=10):
54
  if user_response.status_code == 200:
55
  user_data = user_response.json()
56
 
57
- # Fetch user's contribution data for last 30 days
58
- events_url = f"https://api.github.com/users/{user['login']}/events/public?per_page=100"
59
- events_response = requests.get(events_url, headers=headers)
60
-
61
- contributions = 0
62
- thirty_days_ago = datetime.now() - timedelta(days=30)
63
-
64
- if events_response.status_code == 200:
65
- events = events_response.json()
66
- # Count various contribution events from last 30 days
67
- for event in events:
68
- event_date = datetime.strptime(event.get('created_at', ''), '%Y-%m-%dT%H:%M:%SZ')
69
- if event_date >= thirty_days_ago:
70
- event_type = event.get('type', '')
71
- # Count different types of contributions
72
- if event_type == 'PushEvent':
73
- # Count commits in push event
74
- commits = event.get('payload', {}).get('commits', [])
75
- contributions += len(commits) if commits else 1
76
- elif event_type in ['PullRequestEvent', 'IssuesEvent', 'CreateEvent']:
77
- contributions += 1
78
-
79
- # Total contributions is based on last 30 days activity
80
- total_contributions = contributions
81
 
82
  all_users.append({
83
  'login': user_data.get('login', ''),
@@ -85,7 +114,7 @@ def fetch_github_users(github_token=None, max_users=200, min_followers=10):
85
  'avatar': user_data.get('avatar_url', ''),
86
  'followers': user_data.get('followers', 0),
87
  'public_repos': user_data.get('public_repos', 0),
88
- 'contributions': total_contributions,
89
  'location': user_data.get('location', ''),
90
  'bio': user_data.get('bio', ''),
91
  'company': user_data.get('company', ''),
@@ -100,7 +129,7 @@ def fetch_github_users(github_token=None, max_users=200, min_followers=10):
100
  time.sleep(1) # Rate limiting between searches
101
 
102
  elif response.status_code == 403:
103
- status_updates.append(f"⚠️ Rate limit reached. Please add a GitHub token.")
104
  break
105
  else:
106
  status_updates.append(f"❌ Error searching {location}: {response.status_code}")
@@ -126,7 +155,7 @@ def fetch_github_users(github_token=None, max_users=200, min_followers=10):
126
  display_df.columns = ['Rank', 'Name', 'Username', 'Contributions', 'Followers', 'Public Repos', 'Location']
127
  display_df['GitHub Profile'] = df['login'].apply(lambda x: f"https://github.com/{x}")
128
 
129
- status_message = f"✅ Successfully fetched top {len(df)} contributors (sorted by last 30 days contributions)\n" + "\n".join(status_updates[-5:])
130
  return display_df, status_message
131
  else:
132
  return pd.DataFrame(), "⚠️ No users found\n" + "\n".join(status_updates)
@@ -153,17 +182,17 @@ def search_users(df, search_term):
153
  with gr.Blocks(theme=gr.themes.Soft(), title="GitHub UAE Top Contributors") as app:
154
 
155
  gr.Markdown("""
156
- # 🏆 Top 200 GitHub Contributors in UAE (Last 30 Days)
157
- ### Ranked by Contributions in the Last 30 Days
158
 
159
- **Note:** The app will automatically use `GITHUB_TOKEN` environment variable if set.
160
- Without a token, you're limited to 60 requests/hour. With a token: 5000 requests/hour.
161
  """)
162
 
163
  with gr.Row():
164
  token_input = gr.Textbox(
165
- label="GitHub Personal Access Token (Optional - overrides env variable)",
166
- placeholder="ghp_xxxxxxxxxxxx or leave empty to use GITHUB_TOKEN env var",
167
  type="password",
168
  scale=3
169
  )
@@ -184,6 +213,13 @@ with gr.Blocks(theme=gr.themes.Soft(), title="GitHub UAE Top Contributors") as a
184
  value=10,
185
  step=1
186
  )
 
 
 
 
 
 
 
187
 
188
  with gr.Row():
189
  fetch_btn = gr.Button("🚀 Fetch Top Contributors from GitHub", variant="primary", size="lg")
@@ -212,31 +248,31 @@ with gr.Blocks(theme=gr.themes.Soft(), title="GitHub UAE Top Contributors") as a
212
  gr.Markdown("""
213
  ---
214
  **Ranking Criteria:**
215
- - **Primary Sort:** Contributions in Last 30 Days (Commits, Pull Requests, Issues, Creates)
216
  - **Secondary Sort:** Followers count
217
- - Only users from UAE locations are included
218
- - Contributions counted: Commits (from PushEvents), Pull Requests, Issues, and Create Events
219
-
220
- **How to set up GitHub Token:**
221
-
222
- **Option 1: Environment Variable (Recommended)**
223
- ```bash
224
- export GITHUB_TOKEN="ghp_your_token_here"
225
- python app.py
226
- ```
227
-
228
- **Option 2: Manual Input**
229
- 1. Go to [GitHub Settings > Developer settings > Personal access tokens](https://github.com/settings/tokens)
230
- 2. Generate new token (classic)
231
- 3. No special scopes needed for public data
232
- 4. Copy and paste the token in the field above
233
-
234
- **Data fetched directly from:** GitHub API
235
  """)
236
 
237
  # Event handlers
238
- def fetch_and_display(token, max_users, min_followers):
239
- df, msg = fetch_github_users(token if token else None, int(max_users), int(min_followers))
240
  return df, df, msg
241
 
242
  def filter_data(df, search):
@@ -249,7 +285,7 @@ with gr.Blocks(theme=gr.themes.Soft(), title="GitHub UAE Top Contributors") as a
249
 
250
  fetch_btn.click(
251
  fn=fetch_and_display,
252
- inputs=[token_input, max_users_input, min_followers_input],
253
  outputs=[full_data, data_display, status_msg]
254
  )
255
 
 
5
  import time
6
  import os
7
 
8
+ def get_contribution_count(username, github_token, days=30):
9
+ """
10
+ Get accurate contribution count using GitHub GraphQL API
11
+ """
12
+ if not github_token:
13
+ return 0
14
+
15
+ # Calculate date range
16
+ end_date = datetime.now()
17
+ start_date = end_date - timedelta(days=days)
18
+
19
+ query = """
20
+ query($username: String!, $from: DateTime!, $to: DateTime!) {
21
+ user(login: $username) {
22
+ contributionsCollection(from: $from, to: $to) {
23
+ contributionCalendar {
24
+ totalContributions
25
+ }
26
+ }
27
+ }
28
+ }
29
+ """
30
+
31
+ variables = {
32
+ "username": username,
33
+ "from": start_date.isoformat(),
34
+ "to": end_date.isoformat()
35
+ }
36
+
37
+ headers = {
38
+ 'Authorization': f'bearer {github_token}',
39
+ 'Content-Type': 'application/json',
40
+ }
41
+
42
+ try:
43
+ response = requests.post(
44
+ 'https://api.github.com/graphql',
45
+ json={'query': query, 'variables': variables},
46
+ headers=headers
47
+ )
48
+
49
+ if response.status_code == 200:
50
+ data = response.json()
51
+ if 'data' in data and data['data']['user']:
52
+ return data['data']['user']['contributionsCollection']['contributionCalendar']['totalContributions']
53
+ except Exception as e:
54
+ print(f"Error fetching contributions for {username}: {e}")
55
+
56
+ return 0
57
+
58
+ def fetch_github_users(github_token=None, max_users=200, min_followers=10, days=30):
59
  """
60
  Fetch GitHub users from UAE directly using GitHub API
61
  Sorted by contributions (primary) and followers (secondary)
 
63
  # Read token from environment variable if not provided
64
  if not github_token:
65
  github_token = os.getenv('GITHUB_TOKEN')
66
+
67
+ if not github_token:
68
+ return pd.DataFrame(), "❌ GitHub token is required for accurate contribution counts. Please provide a token."
69
 
70
  headers = {
71
  'Accept': 'application/vnd.github.v3+json',
72
+ 'Authorization': f'token {github_token}'
73
  }
74
 
 
 
 
75
  all_users = []
76
 
77
  # Search locations in UAE
 
105
  if user_response.status_code == 200:
106
  user_data = user_response.json()
107
 
108
+ # Get accurate contribution count using GraphQL
109
+ contributions = get_contribution_count(user['login'], github_token, days)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
  all_users.append({
112
  'login': user_data.get('login', ''),
 
114
  'avatar': user_data.get('avatar_url', ''),
115
  'followers': user_data.get('followers', 0),
116
  'public_repos': user_data.get('public_repos', 0),
117
+ 'contributions': contributions,
118
  'location': user_data.get('location', ''),
119
  'bio': user_data.get('bio', ''),
120
  'company': user_data.get('company', ''),
 
129
  time.sleep(1) # Rate limiting between searches
130
 
131
  elif response.status_code == 403:
132
+ status_updates.append(f"⚠️ Rate limit reached.")
133
  break
134
  else:
135
  status_updates.append(f"❌ Error searching {location}: {response.status_code}")
 
155
  display_df.columns = ['Rank', 'Name', 'Username', 'Contributions', 'Followers', 'Public Repos', 'Location']
156
  display_df['GitHub Profile'] = df['login'].apply(lambda x: f"https://github.com/{x}")
157
 
158
+ status_message = f"✅ Successfully fetched top {len(df)} contributors (last {days} days)\n" + "\n".join(status_updates[-5:])
159
  return display_df, status_message
160
  else:
161
  return pd.DataFrame(), "⚠️ No users found\n" + "\n".join(status_updates)
 
182
  with gr.Blocks(theme=gr.themes.Soft(), title="GitHub UAE Top Contributors") as app:
183
 
184
  gr.Markdown("""
185
+ # 🏆 Top 200 GitHub Contributors in UAE
186
+ ### Ranked by Accurate Contribution Counts
187
 
188
+ **IMPORTANT:** This app requires a GitHub Personal Access Token for accurate contribution counts.
189
+ The token needs the `read:user` scope for GraphQL API access.
190
  """)
191
 
192
  with gr.Row():
193
  token_input = gr.Textbox(
194
+ label="GitHub Personal Access Token (Required)",
195
+ placeholder="ghp_xxxxxxxxxxxx or set GITHUB_TOKEN env var",
196
  type="password",
197
  scale=3
198
  )
 
213
  value=10,
214
  step=1
215
  )
216
+ days_input = gr.Slider(
217
+ label="Days to Count (30 days = last 30 days, not calendar month)",
218
+ minimum=7,
219
+ maximum=90,
220
+ value=30,
221
+ step=1
222
+ )
223
 
224
  with gr.Row():
225
  fetch_btn = gr.Button("🚀 Fetch Top Contributors from GitHub", variant="primary", size="lg")
 
248
  gr.Markdown("""
249
  ---
250
  **Ranking Criteria:**
251
+ - **Primary Sort:** Total contributions in selected time period (accurate count from GitHub)
252
  - **Secondary Sort:** Followers count
253
+ - Uses GitHub's GraphQL API for accurate contribution counts
254
+ - Counts ALL contribution types (commits, PRs, issues, reviews, etc.)
255
+
256
+ **How to set up GitHub Token with correct permissions:**
257
+
258
+ 1. Go to [GitHub Settings > Developer settings > Personal access tokens > Tokens (classic)](https://github.com/settings/tokens)
259
+ 2. Click "Generate new token (classic)"
260
+ 3. Give it a name like "UAE Contributors Tracker"
261
+ 4. Select scopes: **read:user** (this is required for GraphQL API)
262
+ 5. Generate and copy the token
263
+ 6. Either:
264
+ - Paste it in the field above, OR
265
+ - Set environment variable: `export GITHUB_TOKEN="ghp_your_token_here"`
266
+
267
+ **Why the change?**
268
+ - The Events API only returns ~100 events and may miss many contributions
269
+ - GraphQL API gives accurate counts of ALL contribution types
270
+ - This matches what you see on your GitHub profile
271
  """)
272
 
273
  # Event handlers
274
+ def fetch_and_display(token, max_users, min_followers, days):
275
+ df, msg = fetch_github_users(token if token else None, int(max_users), int(min_followers), int(days))
276
  return df, df, msg
277
 
278
  def filter_data(df, search):
 
285
 
286
  fetch_btn.click(
287
  fn=fetch_and_display,
288
+ inputs=[token_input, max_users_input, min_followers_input, days_input],
289
  outputs=[full_data, data_display, status_msg]
290
  )
291