-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from AF-Warsame/copilot/fix-f985b0bd-ab02-43f9-…
…8c87-310fa6d06ab9
- Loading branch information
Showing
27 changed files
with
732 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Python | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
*.so | ||
.Python | ||
build/ | ||
develop-eggs/ | ||
dist/ | ||
downloads/ | ||
eggs/ | ||
.eggs/ | ||
lib/ | ||
lib64/ | ||
parts/ | ||
sdist/ | ||
var/ | ||
wheels/ | ||
*.egg-info/ | ||
.installed.cfg | ||
*.egg | ||
MANIFEST | ||
|
||
# Testing | ||
.coverage | ||
.pytest_cache/ | ||
.tox/ | ||
htmlcov/ | ||
|
||
# Virtual environments | ||
venv/ | ||
env/ | ||
ENV/ | ||
|
||
# IDE | ||
.vscode/ | ||
.idea/ | ||
*.swp | ||
*.swo | ||
|
||
# OS | ||
.DS_Store | ||
Thumbs.db | ||
|
||
# Logs | ||
*.log | ||
|
||
# Temporary files | ||
tmp/ | ||
temp/ |
Binary file not shown.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from typing import Dict, Optional | ||
|
||
|
||
class InMemoryCache: | ||
"""Simple in-memory cache implementation.""" | ||
|
||
def __init__(self): | ||
self._cache: Dict[str, bytes] = {} | ||
self._etags: Dict[str, str] = {} | ||
|
||
def get(self, key: str) -> bytes | None: | ||
"""Get cached data by key.""" | ||
return self._cache.get(key) | ||
|
||
def set(self, key: str, data: bytes, etag: str | None = None) -> None: | ||
"""Set cached data with optional etag.""" | ||
self._cache[key] = data | ||
if etag: | ||
self._etags[key] = etag | ||
|
||
def get_etag(self, key: str) -> str | None: | ||
"""Get etag for cached data.""" | ||
return self._etags.get(key) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import requests | ||
import logging | ||
from typing import Dict, Any | ||
|
||
|
||
class GitHubHandler: | ||
"""Handler for GitHub repository metadata fetching.""" | ||
|
||
def __init__(self): | ||
self.session = requests.Session() | ||
# Set user agent for GitHub API | ||
self.session.headers.update({ | ||
'User-Agent': 'ACME-CLI/1.0', | ||
'Accept': 'application/vnd.github.v3+json' | ||
}) | ||
|
||
def fetch_meta(self, url: str) -> Dict[str, Any]: | ||
"""Fetch repository metadata from GitHub API.""" | ||
try: | ||
# Parse GitHub URL: https://github.com/owner/repo | ||
parts = url.rstrip('/').split('/') | ||
if len(parts) < 5 or 'github.com' not in parts[2]: | ||
logging.error(f"Invalid GitHub URL format: {url}") | ||
return {} | ||
|
||
owner, repo = parts[3], parts[4] | ||
api_url = f"https://api.github.com/repos/{owner}/{repo}" | ||
|
||
response = self.session.get(api_url) | ||
response.raise_for_status() | ||
|
||
repo_data = response.json() | ||
|
||
# Fetch additional metadata | ||
meta = { | ||
'name': repo_data.get('name', ''), | ||
'full_name': repo_data.get('full_name', ''), | ||
'description': repo_data.get('description', ''), | ||
'stars': repo_data.get('stargazers_count', 0), | ||
'forks': repo_data.get('forks_count', 0), | ||
'watchers': repo_data.get('watchers_count', 0), | ||
'size': repo_data.get('size', 0), # in KB | ||
'language': repo_data.get('language', ''), | ||
'topics': repo_data.get('topics', []), | ||
'license': repo_data.get('license', {}).get('spdx_id', '') if repo_data.get('license') else '', | ||
'created_at': repo_data.get('created_at', ''), | ||
'updated_at': repo_data.get('updated_at', ''), | ||
'pushed_at': repo_data.get('pushed_at', ''), | ||
'default_branch': repo_data.get('default_branch', 'main'), | ||
'open_issues_count': repo_data.get('open_issues_count', 0), | ||
'has_wiki': repo_data.get('has_wiki', False), | ||
'has_pages': repo_data.get('has_pages', False), | ||
'archived': repo_data.get('archived', False), | ||
'disabled': repo_data.get('disabled', False), | ||
} | ||
|
||
# Try to fetch contributors data | ||
try: | ||
contributors_url = f"https://api.github.com/repos/{owner}/{repo}/contributors" | ||
contrib_response = self.session.get(contributors_url) | ||
if contrib_response.status_code == 200: | ||
contributors = contrib_response.json() | ||
meta['contributors'] = { | ||
contrib.get('login', 'unknown'): contrib.get('contributions', 0) | ||
for contrib in contributors[:10] # Limit to top 10 | ||
} | ||
else: | ||
meta['contributors'] = {} | ||
except Exception as e: | ||
logging.warning(f"Failed to fetch contributors for {url}: {e}") | ||
meta['contributors'] = {} | ||
|
||
# Try to fetch README | ||
try: | ||
readme_url = f"https://api.github.com/repos/{owner}/{repo}/readme" | ||
readme_response = self.session.get(readme_url) | ||
if readme_response.status_code == 200: | ||
readme_data = readme_response.json() | ||
import base64 | ||
readme_content = base64.b64decode(readme_data.get('content', '')).decode('utf-8') | ||
meta['readme_text'] = readme_content | ||
else: | ||
meta['readme_text'] = '' | ||
except Exception as e: | ||
logging.warning(f"Failed to fetch README for {url}: {e}") | ||
meta['readme_text'] = '' | ||
|
||
return meta | ||
|
||
except requests.RequestException as e: | ||
logging.error(f"HTTP error fetching metadata for {url}: {e}") | ||
return {} | ||
except Exception as e: | ||
logging.error(f"Failed to fetch metadata for {url}: {e}") | ||
return {} |
Binary file not shown.
Binary file not shown.
Binary file modified
BIN
+1.54 KB
(220%)
src/acmecli/metrics/__pycache__/license_metric.cpython-312.pyc
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import time | ||
from ..types import MetricValue | ||
from .base import register | ||
|
||
|
||
class BusFactorMetric: | ||
"""Metric to assess bus factor - higher score means less risk from key person dependency.""" | ||
name = "bus_factor" | ||
|
||
def score(self, meta: dict) -> MetricValue: | ||
t0 = time.perf_counter() | ||
|
||
# Heuristics for bus factor (higher = safer, more distributed) | ||
score = 0.0 | ||
|
||
contributors = meta.get('contributors', {}) | ||
if contributors: | ||
total_contributions = sum(contributors.values()) | ||
contributor_count = len(contributors) | ||
|
||
if contributor_count >= 10: | ||
score += 0.4 | ||
elif contributor_count >= 5: | ||
score += 0.3 | ||
elif contributor_count >= 3: | ||
score += 0.2 | ||
elif contributor_count >= 2: | ||
score += 0.1 | ||
|
||
# Check contribution distribution | ||
if total_contributions > 0: | ||
# Find the top contributor's share | ||
max_contributions = max(contributors.values()) if contributors else 0 | ||
top_contributor_share = max_contributions / total_contributions | ||
|
||
# Lower share of top contributor = better bus factor | ||
if top_contributor_share < 0.3: | ||
score += 0.3 | ||
elif top_contributor_share < 0.5: | ||
score += 0.2 | ||
elif top_contributor_share < 0.7: | ||
score += 0.1 | ||
|
||
# Organization/company backing (GitHub org vs individual) | ||
full_name = meta.get('full_name', '') | ||
if '/' in full_name: | ||
owner = full_name.split('/')[0] | ||
# Heuristic: longer names often indicate organizations | ||
if len(owner) > 3 and not owner.islower(): | ||
score += 0.1 | ||
|
||
# Forks indicate community involvement | ||
forks = meta.get('forks', 0) | ||
if forks > 50: | ||
score += 0.2 | ||
elif forks > 10: | ||
score += 0.1 | ||
|
||
value = min(1.0, score) | ||
latency_ms = int((time.perf_counter() - t0) * 1000) | ||
return MetricValue(self.name, value, latency_ms) | ||
|
||
|
||
register(BusFactorMetric()) |
Oops, something went wrong.