|
19 | 19 | import argparse
|
20 | 20 |
|
21 | 21 | orgs = [
|
22 |
| - "binder-examples", |
23 |
| - "binderhub-ci-repos", |
| 22 | + # "binder-examples", |
| 23 | + # "binderhub-ci-repos", |
24 | 24 | "ipython",
|
25 |
| - "jupyter", |
26 |
| - "jupyter-book", |
27 |
| - "jupyter-governance", |
28 |
| - "jupyter-incubator", |
29 |
| - "jupyter-server", |
30 |
| - "jupyter-standards", |
31 |
| - "jupyter-widgets", |
32 |
| - "jupyterhub", |
33 |
| - "jupyterlab", |
34 |
| - "jupyter-xeus", |
35 |
| - "jupytercon", |
36 |
| - "voila-dashboards", |
37 |
| - "voila-gallery", |
| 25 | + # "jupyter", |
| 26 | + # "jupyter-book", |
| 27 | + # "jupyter-governance", |
| 28 | + # "jupyter-incubator", |
| 29 | + # "jupyter-server", |
| 30 | + # "jupyter-standards", |
| 31 | + # "jupyter-widgets", |
| 32 | + # "jupyterhub", |
| 33 | + # "jupyterlab", |
| 34 | + # "jupyter-xeus", |
| 35 | + # "jupytercon", |
| 36 | + # "voila-dashboards", |
| 37 | + # "voila-gallery", |
38 | 38 | ]
|
39 | 39 |
|
40 | 40 | token = os.getenv("GH_TOKEN")
|
@@ -76,95 +76,98 @@ async def get_org_members(session: aiohttp.ClientSession, org: str) -> List[Dict
|
76 | 76 | Pagination is handled automatically (100 items per page).
|
77 | 77 | """
|
78 | 78 | cache_key = f"org_members_{org}"
|
79 |
| - if cache_key in cache: |
80 |
| - return cache[cache_key] |
| 79 | + |
| 80 | + # Try to get from cache |
| 81 | + cached_data = cache.get(cache_key) |
| 82 | + if cached_data is not None: |
| 83 | + print(f"[cyan]Cache hit for {org} members[/cyan]") |
| 84 | + return cached_data |
81 | 85 |
|
| 86 | + print(f"[yellow]Cache miss for {org} members - fetching from API[/yellow]") |
82 | 87 | members = []
|
83 |
| - for page in count(1): |
84 |
| - url = f"https://api.github.com/orgs/{org}/members?page={page}&per_page=100" |
85 |
| - async with session.get(url, headers=headers) as response: |
86 |
| - if response.status != 200: |
87 |
| - print(f"[red]Error fetching members for {org}: {response.status}[/red]") |
88 |
| - break |
89 |
| - |
90 |
| - page_members = await response.json() |
91 |
| - if not page_members: |
92 |
| - break |
93 |
| - |
94 |
| - members.extend(page_members) |
95 | 88 |
|
96 |
| - cache.set(cache_key, members, expire=3600 * 24) |
97 |
| - return members |
| 89 | + try: |
| 90 | + for page in count(1): |
| 91 | + url = f"https://api.github.com/orgs/{org}/members?page={page}&per_page=100" |
| 92 | + async with session.get(url, headers=headers) as response: |
| 93 | + if response.status != 200: |
| 94 | + print(f"[red]Error fetching members for {org}: {response.status}[/red]") |
| 95 | + break |
| 96 | + |
| 97 | + page_members = await response.json() |
| 98 | + if not page_members: |
| 99 | + break |
| 100 | + |
| 101 | + members.extend(page_members) |
| 102 | + |
| 103 | + # Cache the results |
| 104 | + cache.set(cache_key, members, expire=3600 * 24) # 24 hours |
| 105 | + print(f"[green]Cached {len(members)} members for {org}[/green]") |
| 106 | + return members |
| 107 | + |
| 108 | + except Exception as e: |
| 109 | + print(f"[red]Error fetching members for {org}: {str(e)}[/red]") |
| 110 | + return [] |
98 | 111 |
|
99 | 112 | async def get_user_activity(session: aiohttp.ClientSession, username: str) -> Optional[datetime]:
|
100 |
| - """Fetch the last public activity date for a GitHub user. |
101 |
| -
|
102 |
| - Parameters |
103 |
| - ---------- |
104 |
| - session : aiohttp.ClientSession |
105 |
| - The HTTP session to use for requests |
106 |
| - username : str |
107 |
| - The GitHub username to check |
108 |
| -
|
109 |
| - Returns |
110 |
| - ------- |
111 |
| - Optional[datetime] |
112 |
| - The datetime of the user's last public activity, |
113 |
| - or None if no activity was found or an error occurred |
114 |
| -
|
115 |
| - Notes |
116 |
| - ----- |
117 |
| - Results are cached for 24 hours to minimize API requests. |
118 |
| - Only public events are considered for activity tracking. |
119 |
| - """ |
| 113 | + """Fetch the last public activity date for a GitHub user.""" |
120 | 114 | cache_key = f"user_activity_{username}"
|
121 |
| - if cache_key in cache: |
122 |
| - return cache[cache_key] |
123 |
| - |
124 |
| - url = f"https://api.github.com/users/{username}/events/public" |
125 |
| - async with session.get(url, headers=headers) as response: |
126 |
| - if response.status == 200: |
127 |
| - events = await response.json() |
128 |
| - if events: |
129 |
| - last_activity = datetime.fromisoformat(events[0]["created_at"].replace('Z', '+00:00')) |
130 |
| - cache.set(cache_key, last_activity, expire=3600 * 24) |
131 |
| - return last_activity |
132 |
| - return None |
| 115 | + |
| 116 | + # Try to get from cache |
| 117 | + cached_data = cache.get(cache_key) |
| 118 | + if cached_data is not None: |
| 119 | + print(f"[cyan]Cache hit for {username} activity[/cyan]") |
| 120 | + return cached_data |
133 | 121 |
|
134 |
| -def clear_cache() -> None: |
135 |
| - """Clear the disk cache. |
| 122 | + print(f"[yellow]Cache miss for {username} activity - fetching from API[/yellow]") |
136 | 123 |
|
137 |
| - Removes all cached data, forcing fresh API requests on next run. |
| 124 | + try: |
| 125 | + print(f"Getting activity for {username}") |
| 126 | + url = f"https://api.github.com/users/{username}/events/public" |
| 127 | + async with session.get(url, headers=headers) as response: |
| 128 | + if response.status == 200: |
| 129 | + print(f"Got activity for {username}") |
| 130 | + events = await response.json() |
| 131 | + if events: |
| 132 | + last_activity = datetime.fromisoformat(events[0]["created_at"].replace('Z', '+00:00')) |
| 133 | + # Cache the results |
| 134 | + cache.set(cache_key, last_activity, expire=3600 * 24) # 24 hours |
| 135 | + print(f"[green]Cached activity for {username}[/green]") |
| 136 | + return last_activity |
| 137 | + else: |
| 138 | + print(f"[yellow]No activity found for {username}[/yellow]") |
| 139 | + cache.set(cache_key, None, expire=3600 * 24) |
| 140 | + else: |
| 141 | + print(f"[red]Error fetching activity for {username}: {response.status}[/red]") |
| 142 | + except Exception as e: |
| 143 | + print(f"[red]Error fetching activity for {username}: {str(e)}[/red]") |
138 | 144 |
|
139 |
| - Notes |
140 |
| - ----- |
141 |
| - This is useful when you want to ensure you're getting the latest data |
142 |
| - or if the cache becomes corrupted. |
143 |
| - """ |
144 |
| - if pathlib.Path(CACHE_DIR).exists(): |
| 145 | + return None |
| 146 | + |
| 147 | +def get_cache_size() -> str: |
| 148 | + """Get the current cache size in a human-readable format.""" |
| 149 | + try: |
| 150 | + cache_path = pathlib.Path(CACHE_DIR) |
| 151 | + if cache_path.exists(): |
| 152 | + total_size = sum(f.stat().st_size for f in cache_path.rglob('*') if f.is_file()) |
| 153 | + return f"{total_size / 1024 / 1024:.1f} MB" |
| 154 | + except Exception: |
| 155 | + pass |
| 156 | + return "unknown size" |
| 157 | + |
| 158 | +def clear_cache() -> None: |
| 159 | + """Clear the disk cache.""" |
| 160 | + try: |
145 | 161 | cache.clear()
|
146 | 162 | print("[green]Cache cleared successfully[/green]")
|
147 |
| - else: |
148 |
| - print("[yellow]No cache directory found[/yellow]") |
| 163 | + except Exception as e: |
| 164 | + print(f"[red]Error clearing cache: {str(e)}[/red]") |
149 | 165 |
|
150 | 166 | async def main():
|
151 |
| - """Main execution function. |
152 |
| - |
153 |
| - Fetches and displays the last activity for all members across specified organizations. |
154 |
| - Uses disk caching to minimize API requests and handles GitHub API rate limits. |
155 |
| -
|
156 |
| - Notes |
157 |
| - ----- |
158 |
| - The results are displayed organization by organization, with members sorted |
159 |
| - by their last activity date (most recent first). |
160 |
| - """ |
161 |
| - # Add cache info at start |
162 |
| - cache_path = pathlib.Path(CACHE_DIR) |
163 |
| - if cache_path.exists(): |
164 |
| - cache_size = sum(f.stat().st_size for f in cache_path.rglob('*') if f.is_file()) |
165 |
| - print(f"[blue]Using cache directory: {CACHE_DIR} ({cache_size / 1024 / 1024:.1f} MB)[/blue]") |
166 |
| - else: |
167 |
| - print("[yellow]Creating new cache directory[/yellow]") |
| 167 | + """Main execution function.""" |
| 168 | + # Show cache status |
| 169 | + print(f"[blue]Cache directory: {CACHE_DIR} (size: {get_cache_size()})[/blue]") |
| 170 | + print(f"[blue]Cache contains {len(cache)} items[/blue]") |
168 | 171 |
|
169 | 172 | async with aiohttp.ClientSession() as session:
|
170 | 173 | # Check rate limit
|
@@ -213,6 +216,7 @@ async def main():
|
213 | 216 | if __name__ == "__main__":
|
214 | 217 | parser = argparse.ArgumentParser(description="GitHub Organization Activity Tracker")
|
215 | 218 | parser.add_argument('--clear-cache', action='store_true', help='Clear the cache before running')
|
| 219 | + parser.add_argument('--debug', action='store_true', help='Show debug information') |
216 | 220 | args = parser.parse_args()
|
217 | 221 |
|
218 | 222 | if args.clear_cache:
|
|
0 commit comments