tianruci commited on
Commit
3afe350
·
verified ·
1 Parent(s): f1f84ee

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +111 -35
app.py CHANGED
@@ -1,19 +1,21 @@
1
  import os
2
- import requests
3
- from fastapi import FastAPI
4
  from datetime import datetime, timedelta
5
  from fastapi.middleware.cors import CORSMiddleware
6
  from fastmcp.server import FastMCP
7
  from contextlib import asynccontextmanager
 
8
 
9
  @asynccontextmanager
10
  async def lifespan(app: FastAPI):
 
11
  await fetch_github_trending()
12
  yield
13
 
14
  app = FastAPI(lifespan=lifespan)
15
 
16
- # 更宽松的 CORS 配置
17
  app.add_middleware(
18
  CORSMiddleware,
19
  allow_origins=["*"],
@@ -24,14 +26,15 @@ app.add_middleware(
24
  )
25
 
26
  # 缓存设置
27
- cached_trending = []
28
- last_updated = None
 
 
29
 
30
- async def fetch_github_trending():
31
  """获取 GitHub 热门项目"""
32
  global cached_trending, last_updated
33
 
34
- url = "https://api.github.com/search/repositories"
35
  params = {
36
  "q": "stars:>1000",
37
  "sort": "stars",
@@ -39,57 +42,130 @@ async def fetch_github_trending():
39
  "per_page": 10
40
  }
41
 
42
- headers = {}
 
 
 
 
43
  if github_token := os.getenv("GITHUB_TOKEN"):
44
  headers["Authorization"] = f"token {github_token}"
45
 
46
  try:
47
- response = requests.get(url, params=params, headers=headers)
48
- response.raise_for_status()
49
- cached_trending = [
50
- {
51
- "name": item["full_name"],
52
- "url": item["html_url"],
53
- "stars": item["stargazers_count"],
54
- "description": item["description"],
55
- "language": item["language"]
56
- }
57
- for item in response.json().get("items", [])
58
- ]
59
- last_updated = datetime.now()
60
- return cached_trending
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  except Exception as e:
62
  print(f"Error fetching GitHub trending: {e}")
63
- return cached_trending if cached_trending else []
 
 
 
 
 
64
 
65
  # 初始化 MCP 服务器
66
  mcp_server = FastMCP(
67
- name="GithubTrending"
 
68
  )
69
 
70
  @mcp_server.tool()
71
- def get_trending_repos(num: int = 10) -> dict:
72
  """获取 GitHub 热门仓库"""
73
- if not last_updated or (datetime.now() - last_updated) > timedelta(minutes=5):
74
- fetch_github_trending()
75
  return {"trending": cached_trending[:num]}
76
 
77
- # 关键修改:确保正确挂载 MCP 服务
78
  app.mount("/mcp", mcp_server.http_app())
79
 
80
  @app.get("/")
81
  async def root():
82
- return {"message": "GitHub Trending API is running"}
 
 
 
 
 
83
 
84
  @app.get("/trending")
85
- async def get_trending():
86
  """公开的 HTTP 端点"""
87
- if not cached_trending or (datetime.now() - last_updated) > timedelta(minutes=5):
88
  await fetch_github_trending()
89
 
90
  return {
91
- "trending": cached_trending,
92
  "last_updated": last_updated.isoformat() if last_updated else None,
93
- "cache_expires_in": 300,
94
- "suggested_polling_interval": 60
95
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import httpx
3
+ from fastapi import FastAPI, HTTPException
4
  from datetime import datetime, timedelta
5
  from fastapi.middleware.cors import CORSMiddleware
6
  from fastmcp.server import FastMCP
7
  from contextlib import asynccontextmanager
8
+ from typing import List, Dict, Optional
9
 
10
  @asynccontextmanager
11
  async def lifespan(app: FastAPI):
12
+ # 初始化时获取数据
13
  await fetch_github_trending()
14
  yield
15
 
16
  app = FastAPI(lifespan=lifespan)
17
 
18
+ # CORS 配置
19
  app.add_middleware(
20
  CORSMiddleware,
21
  allow_origins=["*"],
 
26
  )
27
 
28
  # 缓存设置
29
+ cached_trending: List[Dict] = []
30
+ last_updated: Optional[datetime] = None
31
+ GITHUB_API_URL = "https://api.github.com/search/repositories"
32
+ CACHE_EXPIRY = timedelta(minutes=30)
33
 
34
+ async def fetch_github_trending() -> List[Dict]:
35
  """获取 GitHub 热门项目"""
36
  global cached_trending, last_updated
37
 
 
38
  params = {
39
  "q": "stars:>1000",
40
  "sort": "stars",
 
42
  "per_page": 10
43
  }
44
 
45
+ headers = {
46
+ "Accept": "application/vnd.github.v3+json",
47
+ "User-Agent": "GitHub-Trending-API"
48
+ }
49
+
50
  if github_token := os.getenv("GITHUB_TOKEN"):
51
  headers["Authorization"] = f"token {github_token}"
52
 
53
  try:
54
+ async with httpx.AsyncClient() as client:
55
+ response = await client.get(
56
+ GITHUB_API_URL,
57
+ params=params,
58
+ headers=headers,
59
+ timeout=10.0
60
+ )
61
+ response.raise_for_status()
62
+
63
+ cached_trending = [
64
+ {
65
+ "name": item["full_name"],
66
+ "url": item["html_url"],
67
+ "stars": item["stargazers_count"],
68
+ "description": item["description"],
69
+ "language": item["language"]
70
+ }
71
+ for item in response.json().get("items", [])
72
+ ]
73
+ last_updated = datetime.now()
74
+ return cached_trending
75
+
76
+ except httpx.HTTPStatusError as e:
77
+ print(f"GitHub API error: {e.response.status_code} - {e.response.text}")
78
+ if cached_trending:
79
+ return cached_trending
80
+ raise HTTPException(
81
+ status_code=429,
82
+ detail="GitHub API rate limit exceeded. Please try again later."
83
+ )
84
  except Exception as e:
85
  print(f"Error fetching GitHub trending: {e}")
86
+ if cached_trending:
87
+ return cached_trending
88
+ raise HTTPException(
89
+ status_code=500,
90
+ detail="Failed to fetch trending repositories"
91
+ )
92
 
93
  # 初始化 MCP 服务器
94
  mcp_server = FastMCP(
95
+ name="GitHubTrendingAPI",
96
+ description="Access GitHub trending repositories"
97
  )
98
 
99
  @mcp_server.tool()
100
+ async def get_trending_repos(num: int = 10) -> dict:
101
  """获取 GitHub 热门仓库"""
102
+ if not last_updated or (datetime.now() - last_updated) > CACHE_EXPIRY:
103
+ await fetch_github_trending()
104
  return {"trending": cached_trending[:num]}
105
 
106
+ # 挂载 MCP 服务
107
  app.mount("/mcp", mcp_server.http_app())
108
 
109
  @app.get("/")
110
  async def root():
111
+ return {
112
+ "message": "GitHub Trending API is running",
113
+ "documentation": "/docs",
114
+ "mcp_endpoint": "/mcp",
115
+ "trending_endpoint": "/trending"
116
+ }
117
 
118
  @app.get("/trending")
119
+ async def get_trending(num: int = 10):
120
  """公开的 HTTP 端点"""
121
+ if not cached_trending or (datetime.now() - last_updated) > CACHE_EXPIRY:
122
  await fetch_github_trending()
123
 
124
  return {
125
+ "trending": cached_trending[:num],
126
  "last_updated": last_updated.isoformat() if last_updated else None,
127
+ "cache_expires_in": CACHE_EXPIRY.total_seconds(),
128
+ "rate_limit_info": "Consider adding a GitHub token for higher rate limits"
129
+ }
130
+
131
+
132
+ @app.get("/trending/stream")
133
+ async def stream_trending(interval: int = 60, client_id: str = None):
134
+ """增强版 SSE 端点"""
135
+ async def event_generator():
136
+ try:
137
+ while True:
138
+ # 发送心跳
139
+ yield {"event": "heartbeat", "data": ""}
140
+
141
+ # 获取数据
142
+ if not last_updated or (datetime.now() - last_updated) > timedelta(seconds=interval):
143
+ await fetch_github_trending()
144
+
145
+ yield {
146
+ "event": "update",
147
+ "data": json.dumps({
148
+ "trending": cached_trending[:10],
149
+ "last_updated": last_updated.isoformat(),
150
+ "client_id": client_id,
151
+ "next_update": (datetime.now() + timedelta(seconds=interval)).isoformat()
152
+ })
153
+ }
154
+
155
+ await asyncio.sleep(interval)
156
+
157
+ except asyncio.CancelledError:
158
+ print(f"客户端断开连接: {client_id}")
159
+ except Exception as e:
160
+ print(f"SSE 错误: {e}")
161
+
162
+ response = StreamingResponse(
163
+ event_generator(),
164
+ media_type="text/event-stream",
165
+ headers={
166
+ "Cache-Control": "no-cache",
167
+ "Connection": "keep-alive",
168
+ "X-Accel-Buffering": "no" # 针对 Nginx 的优化
169
+ }
170
+ )
171
+ return response