diff --git a/modules/github_fetcher.py b/modules/github_fetcher.py index 4e62418e..5ebacc1e 100644 --- a/modules/github_fetcher.py +++ b/modules/github_fetcher.py @@ -1,8 +1,6 @@ from collections import Counter from datetime import datetime, timedelta - import requests - from config.settings import Settings from modules.github_projects import GitHubProjectRanker @@ -21,120 +19,118 @@ def fetch_user_profile(username): Returns: dict: Comprehensive user profile data """ + try: + one_year_ago = (datetime.now() - timedelta(days=365)).isoformat() + 'Z' - one_year_ago = (datetime.now() - timedelta(days=365)).isoformat() + 'Z' - - graphql_query = { - "query": f""" - query {{ - user(login: "{username}") {{ - # Basic user information - name - bio - location - avatarUrl - url - followers {{ - totalCount - }} - following {{ - totalCount - }} - repositories(first: 100, orderBy: {{field: UPDATED_AT, direction: DESC}}) {{ - totalCount - nodes {{ + graphql_query = { + "query": f""" + query {{ + user(login: "{username}") {{ name - description - stargazerCount - primaryLanguage {{ - name - }} + bio + location + avatarUrl url - updatedAt - }} - }} - - # Contributions and achievements - contributionsCollection(from: "{one_year_ago}") {{ - contributionCalendar {{ - totalContributions - }} - # These fields will help filter issues and repositories - pullRequestContributionsByRepository {{ - repository {{ - name + followers {{ + totalCount }} - contributions(first: 100) {{ + following {{ totalCount }} - }} - issueContributionsByRepository {{ - repository {{ - name + repositories(first: 100, orderBy: {{field: UPDATED_AT, direction: DESC}}) {{ + totalCount + nodes {{ + name + description + stargazerCount + primaryLanguage {{ + name + }} + url + updatedAt + }} }} - contributions(first: 100) {{ + contributionsCollection(from: "{one_year_ago}") {{ + contributionCalendar {{ + totalContributions + }} + pullRequestContributionsByRepository {{ + repository {{ + name + }} + contributions(first: 100) {{ + totalCount + }} + }} + issueContributionsByRepository {{ + repository {{ + name + }} + contributions(first: 100) {{ + totalCount + }} + }} + }} + pullRequests(first: 0, states: MERGED, orderBy: {{field: UPDATED_AT, direction: DESC}}) {{ + totalCount + }} + issues(first: 0, states: CLOSED) {{ totalCount }} + repositoriesContributedTo(first: 100, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {{ + totalCount + nodes {{ + name + }} + }} }} }} + """ + } - # Pull Requests and Issues - pullRequests(first: 0, states: MERGED, orderBy: {{field: UPDATED_AT, direction: DESC}}) {{ - totalCount - }} + graphql_url = "https://api.github.com/graphql" + graphql_response = requests.post( + graphql_url, + headers={ + "Authorization": f"Bearer {Settings.get_github_token()}", + "Content-Type": "application/json" + }, + json=graphql_query + ) + graphql_response.raise_for_status() + + graphql_data = graphql_response.json().get('data', {}).get('user', {}) + if not graphql_data: + raise ValueError(f"User '{username}' not found or query returned no data.") + + featured = GitHubProjectRanker().get_featured(username) + return { + 'username': username, + 'name': graphql_data.get('name', username), + 'bio': graphql_data.get('bio', ''), + 'location': graphql_data.get('location', ''), + 'avatar_url': graphql_data.get('avatarUrl', ''), + 'profile_url': graphql_data.get('url', ''), + 'top_languages': featured['top_languages'], + 'top_projects': featured['top_projects'], + 'followers': graphql_data['followers']['totalCount'], + 'following': graphql_data['following']['totalCount'], + 'public_repos': graphql_data['repositories']['totalCount'], + 'pull_requests_merged': graphql_data['pullRequests']['totalCount'], + 'issues_closed': graphql_data['issues']['totalCount'], + 'achievements': { + 'total_contributions': graphql_data['contributionsCollection']['contributionCalendar']['totalContributions'], + 'repositories_contributed_to': graphql_data['repositoriesContributedTo']['totalCount'], + }, + 'social_accounts': GitHubProfileFetcher.social_accounts(username) + } - issues(first: 0, states: CLOSED) {{ - totalCount - }} - - repositoriesContributedTo(first: 100, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {{ - totalCount - nodes {{ - name - }} - }} - }} - }} - """ - } - - # Single GraphQL request to fetch most of the data - graphql_url = "https://api.github.com/graphql" - graphql_response = requests.post( - graphql_url, - headers={ - "Authorization": f"Bearer {Settings.get_github_token()}", - "Content-Type": "application/json" - }, - json=graphql_query - ) - - graphql_response.raise_for_status() - graphql_data = graphql_response.json()['data']['user'] - featured = GitHubProjectRanker().get_featured(username) - return { - 'username': username, - 'name': graphql_data.get('name', username) or username, - 'bio': graphql_data.get('bio', ''), - 'location': graphql_data.get('location', ''), - 'avatar_url': graphql_data.get('avatarUrl', ''), - 'profile_url': graphql_data.get('url', ''), - 'top_languages': featured['top_languages'], - 'top_projects': featured['top_projects'], - 'followers': graphql_data['followers']['totalCount'], - 'following': graphql_data['following']['totalCount'], - 'public_repos': graphql_data['repositories']['totalCount'], - - # Metrics from GraphQL data - 'pull_requests_merged': graphql_data['pullRequests']['totalCount'], - 'issues_closed': graphql_data['issues']['totalCount'], - 'achievements': { - 'total_contributions': graphql_data['contributionsCollection']['contributionCalendar'][ - 'totalContributions'], - 'repositories_contributed_to': graphql_data['repositoriesContributedTo']['totalCount'], - }, - 'social_accounts': GitHubProfileFetcher.social_accounts(username) - } + except requests.exceptions.HTTPError as e: + return {"error": f"HTTP Error: {e.response.status_code} - {e.response.reason}"} + except requests.exceptions.RequestException as e: + return {"error": f"Request failed: {str(e)}"} + except Exception as e: + return {"error": f"An unexpected error occurred: {str(e)}"} @staticmethod def fetch_user_profile_rest(username): @@ -147,60 +143,64 @@ def fetch_user_profile_rest(username): Returns: dict: Comprehensive user profile data """ - base_url = f"https://api.github.com/users/{username}" - - # Fetch user details - user_response = requests.get( - base_url, - headers={ - "Accept": "application/vnd.github.v3+json", - "Authorization": f"token {Settings.get_github_token()}", - } - ) - user_response.raise_for_status() - user_data = user_response.json() - - # Fetch repositories - repos_response = requests.get( - user_data['repos_url'], - headers={ - "Accept": "application/vnd.github.v3+json", - "Authorization": f"token {Settings.get_github_token()}", + try: + base_url = f"https://api.github.com/users/{username}" + + user_response = requests.get( + base_url, + headers={ + "Accept": "application/vnd.github.v3+json", + "Authorization": f"token {Settings.get_github_token()}", + } + ) + user_response.raise_for_status() + user_data = user_response.json() + + repos_response = requests.get( + user_data['repos_url'], + headers={ + "Accept": "application/vnd.github.v3+json", + "Authorization": f"token {Settings.get_github_token()}", + } + ) + repos_response.raise_for_status() + repos_data = repos_response.json() + + languages = Counter() + recent_projects = [] + for repo in repos_data: + if repo.get('language'): + languages[repo['language']] += 1 + recent_projects.append({ + 'name': repo['name'], + 'description': repo.get('description', ''), + 'stars': repo['stargazers_count'], + 'url': repo['html_url'], + 'updated_at': repo['updated_at'] + }) + + recent_projects.sort(key=lambda x: datetime.strptime(x['updated_at'], '%Y-%m-%dT%H:%M:%SZ'), reverse=True) + + return { + 'username': username, + 'name': user_data.get('name', username), + 'bio': user_data.get('bio', ''), + 'location': user_data.get('location', ''), + 'avatar_url': user_data.get('avatar_url', ''), + 'profile_url': user_data.get('html_url', ''), + 'top_languages': languages.most_common(3), + 'recent_projects': recent_projects[:5], + 'followers': user_data.get('followers', 0), + 'following': user_data.get('following', 0), + 'public_repos': user_data.get('public_repos', 0) } - ) - repos_response.raise_for_status() - repos_data = repos_response.json() - - # Process languages and projects - languages = Counter() - recent_projects = [] - for repo in repos_data: - if repo.get('language'): - languages[repo['language']] += 1 - recent_projects.append({ - 'name': repo['name'], - 'description': repo.get('description', ''), - 'stars': repo['stargazers_count'], - 'url': repo['html_url'], - 'updated_at': repo['updated_at'] - }) - - # Sort projects date - recent_projects.sort(key=lambda x: datetime.strptime(x['updated_at'], '%Y-%m-%dT%H:%M:%SZ'), reverse=True) - - return { - 'username': username, - 'name': user_data.get('name', username), - 'bio': user_data.get('bio', ''), - 'location': user_data.get('location', ''), - 'avatar_url': user_data.get('avatar_url', ''), - 'profile_url': user_data.get('html_url', ''), - 'top_languages': languages.most_common(3), - 'recent_projects': recent_projects[:5], - 'followers': user_data.get('followers', 0), - 'following': user_data.get('following', 0), - 'public_repos': user_data.get('public_repos', 0) - } + + except requests.exceptions.HTTPError as e: + return {"error": f"HTTP Error: {e.response.status_code} - {e.response.reason}"} + except requests.exceptions.RequestException as e: + return {"error": f"Request failed: {str(e)}"} + except Exception as e: + return {"error": f"An unexpected error occurred: {str(e)}"} @staticmethod def social_accounts(username): @@ -213,17 +213,22 @@ def social_accounts(username): Returns: dict: Social accounts of the user """ - base_url = f"https://api.github.com/users/{username}/social_accounts" - - # Fetch user details - user_response = requests.get( - base_url, - headers={ - "Accept": "application/vnd.github.v3+json", - "Authorization": f"token {Settings.get_github_token()}", - } - ) - user_response.raise_for_status() - user_data = user_response.json() - - return user_data + try: + base_url = f"https://api.github.com/users/{username}/social_accounts" + + user_response = requests.get( + base_url, + headers={ + "Accept": "application/vnd.github.v3+json", + "Authorization": f"token {Settings.get_github_token()}", + } + ) + user_response.raise_for_status() + return user_response.json() + + except requests.exceptions.HTTPError as e: + return {"error": f"HTTP Error: {e.response.status_code} - {e.response.reason}"} + except requests.exceptions.RequestException as e: + return {"error": f"Request failed: {str(e)}"} + except Exception as e: + return {"error": f"An unexpected error occurred: {str(e)}"}