sunholo 0.59.2__py3-none-any.whl → 0.59.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,264 @@
1
+ # from https://github.com/ray-project/docu-mentor
2
+ import base64
3
+ import httpx
4
+ from dotenv import load_dotenv
5
+ import jwt
6
+ import os
7
+ import time
8
+
9
+ load_dotenv()
10
+
11
+
12
+
13
+ APP_ID = os.environ.get("APP_ID")
14
+ PRIVATE_KEY = os.environ.get("PRIVATE_KEY", "")
15
+
16
+ # with open('private-key.pem', 'r') as f:
17
+ # PRIVATE_KEY = f.read()
18
+
19
+ def generate_jwt():
20
+ payload = {
21
+ "iat": int(time.time()),
22
+ "exp": int(time.time()) + (10 * 60),
23
+ "iss": APP_ID,
24
+ }
25
+ if PRIVATE_KEY:
26
+ jwt_token = jwt.encode(payload, PRIVATE_KEY, algorithm="RS256")
27
+ return jwt_token
28
+ raise ValueError("PRIVATE_KEY not found.")
29
+
30
+
31
+ async def get_installation_access_token(jwt, installation_id):
32
+ url = f"https://api.github.com/app/installations/{installation_id}/access_tokens"
33
+ headers = {
34
+ "Authorization": f"Bearer {jwt}",
35
+ "Accept": "application/vnd.github.v3+json",
36
+ }
37
+ async with httpx.AsyncClient() as client:
38
+ response = await client.post(url, headers=headers)
39
+ return response.json()["token"]
40
+
41
+
42
+ def get_diff_url(pr):
43
+ """GitHub 302s to this URL."""
44
+ original_url = pr.get("url")
45
+ parts = original_url.split("/")
46
+ owner, repo, pr_number = parts[-4], parts[-3], parts[-1]
47
+ return f"https://patch-diff.githubusercontent.com/raw/{owner}/{repo}/pull/{pr_number}.diff"
48
+
49
+
50
+ async def get_branch_files(pr, branch, headers):
51
+ original_url = pr.get("url")
52
+ parts = original_url.split("/")
53
+ owner, repo = parts[-4], parts[-3]
54
+ url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/{branch}?recursive=1"
55
+ async with httpx.AsyncClient() as client:
56
+ response = await client.get(url, headers=headers)
57
+ tree = response.json().get('tree', [])
58
+ files = {}
59
+ for item in tree:
60
+ if item['type'] == 'blob':
61
+ file_url = item['url']
62
+ print(file_url)
63
+ file_response = await client.get(file_url, headers=headers)
64
+ content = file_response.json().get('content', '')
65
+ # Decode the base64 content
66
+ decoded_content = base64.b64decode(content).decode('utf-8')
67
+ files[item['path']] = decoded_content
68
+ return files
69
+
70
+
71
+ async def get_pr_head_branch(pr, headers):
72
+ original_url = pr.get("url")
73
+ parts = original_url.split("/")
74
+ owner, repo, pr_number = parts[-4], parts[-3], parts[-1]
75
+ url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}"
76
+
77
+ async with httpx.AsyncClient() as client:
78
+ response = await client.get(url, headers=headers)
79
+
80
+ # Check if the response is successful
81
+ if response.status_code != 200:
82
+ print(f"Error: Received status code {response.status_code}")
83
+ print("Response body:", response.text)
84
+ return ''
85
+
86
+ # Safely get the 'ref'
87
+ data = response.json()
88
+ head_data = data.get('head', {})
89
+ ref = head_data.get('ref', '')
90
+ return ref
91
+
92
+
93
+ def files_to_diff_dict(diff):
94
+ files_with_diff = {}
95
+ current_file = None
96
+ for line in diff.split("\n"):
97
+ if line.startswith("diff --git"):
98
+ current_file = line.split(" ")[2][2:]
99
+ files_with_diff[current_file] = {"text": []}
100
+ elif line.startswith("+") and not line.startswith("+++"):
101
+ files_with_diff[current_file]["text"].append(line[1:])
102
+ return files_with_diff
103
+
104
+
105
+ def parse_diff_to_line_numbers(diff):
106
+ files_with_line_numbers = {}
107
+ current_file = None
108
+ line_number = 0
109
+ for line in diff.split("\n"):
110
+ if line.startswith("diff --git"):
111
+ current_file = line.split(" ")[2][2:]
112
+ files_with_line_numbers[current_file] = []
113
+ line_number = 0
114
+ elif line.startswith("@@"):
115
+ line_number = int(line.split(" ")[2].split(",")[0][1:]) - 1
116
+ elif line.startswith("+") and not line.startswith("+++"):
117
+ files_with_line_numbers[current_file].append(line_number)
118
+ line_number += 1
119
+ elif not line.startswith("-"):
120
+ line_number += 1
121
+ return files_with_line_numbers
122
+
123
+
124
+ def get_context_from_files(files, files_with_line_numbers, context_lines=2):
125
+ context_data = {}
126
+ for file, lines in files_with_line_numbers.items():
127
+ file_content = files[file].split("\n")
128
+ context_data[file] = []
129
+ for line in lines:
130
+ start = max(line - context_lines, 0)
131
+ end = min(line + context_lines + 1, len(file_content))
132
+ context_data[file].append('\n'.join(file_content[start:end]))
133
+ return context_data
134
+
135
+ app = FastAPI()
136
+
137
+
138
+ async def handle_webhook(request: Request):
139
+ data = await request.json()
140
+
141
+ installation = data.get("installation")
142
+ if installation and installation.get("id"):
143
+ installation_id = installation.get("id")
144
+ logger.info(f"Installation ID: {installation_id}")
145
+
146
+ JWT_TOKEN = generate_jwt()
147
+
148
+ installation_access_token = await get_installation_access_token(
149
+ JWT_TOKEN, installation_id
150
+ )
151
+
152
+ headers = {
153
+ "Authorization": f"token {installation_access_token}",
154
+ "User-Agent": "docu-mentor-bot",
155
+ "Accept": "application/vnd.github.VERSION.diff",
156
+ }
157
+ else:
158
+ raise ValueError("No app installation found.")
159
+
160
+ # If PR exists and is opened
161
+ if "pull_request" in data.keys() and (
162
+ data["action"] in ["opened", "reopened"]
163
+ ): # use "synchronize" for tracking new commits
164
+ pr = data.get("pull_request")
165
+
166
+ # Greet the user and show instructions.
167
+ async with httpx.AsyncClient() as client:
168
+ await client.post(
169
+ f"{pr['issue_url']}/comments",
170
+ json={"body": GREETING},
171
+ headers=headers,
172
+ )
173
+ return JSONResponse(content={}, status_code=200)
174
+
175
+ # Check if the event is a new or modified issue comment
176
+ if "issue" in data.keys() and data.get("action") in ["created", "edited"]:
177
+ issue = data["issue"]
178
+
179
+ # Check if the issue is a pull request
180
+ if "/pull/" in issue["html_url"]:
181
+ pr = issue.get("pull_request")
182
+
183
+ # Get the comment body
184
+ comment = data.get("comment")
185
+ comment_body = comment.get("body")
186
+ # Remove all whitespace characters except for regular spaces
187
+ comment_body = comment_body.translate(
188
+ str.maketrans("", "", string.whitespace.replace(" ", ""))
189
+ )
190
+
191
+ # Skip if the bot talks about itself
192
+ author_handle = comment["user"]["login"]
193
+
194
+ # Check if the bot is mentioned in the comment
195
+ if (
196
+ author_handle != "docu-mentor[bot]"
197
+ and "@docu-mentor run" in comment_body
198
+ ):
199
+ async with httpx.AsyncClient() as client:
200
+ # Fetch diff from GitHub
201
+ files_to_keep = comment_body.replace(
202
+ "@docu-mentor run", ""
203
+ ).split(" ")
204
+ files_to_keep = [item for item in files_to_keep if item]
205
+
206
+ logger.info(files_to_keep)
207
+
208
+ url = get_diff_url(pr)
209
+ diff_response = await client.get(url, headers=headers)
210
+ diff = diff_response.text
211
+
212
+ files_with_lines = parse_diff_to_line_numbers(diff)
213
+
214
+ # Get head branch of the PR
215
+ headers["Accept"] = "application/vnd.github.full+json"
216
+ head_branch = await get_pr_head_branch(pr, headers)
217
+
218
+ # Get files from head branch
219
+ head_branch_files = await get_branch_files(pr, head_branch, headers)
220
+ print("HEAD FILES", head_branch_files)
221
+
222
+ # Enrich diff data with context from the head branch.
223
+ context_files = get_context_from_files(head_branch_files, files_with_lines)
224
+
225
+ # Filter the dictionary
226
+ if files_to_keep:
227
+ context_files = {
228
+ k: context_files[k]
229
+ for k in context_files
230
+ if any(sub in k for sub in files_to_keep)
231
+ }
232
+
233
+ # Get suggestions from Docu Mentor
234
+ content, model, prompt_tokens, completion_tokens = \
235
+ ray_mentor(context_files) if ray.is_initialized() else mentor(context_files)
236
+
237
+
238
+ # Let's comment on the PR
239
+ await client.post(
240
+ f"{comment['issue_url']}/comments",
241
+ json={
242
+ "body": f":rocket: Docu Mentor finished "
243
+ + "analysing your PR! :rocket:\n\n"
244
+ + "Take a look at your results:\n"
245
+ + f"{content}\n\n"
246
+ + "This bot is powered by "
247
+ + "[Sunholo Multivac](https://www.sunholo.com/).\n"
248
+ + f"It used the model {model}, used {prompt_tokens} prompt tokens, "
249
+ + f"and {completion_tokens} completion tokens in total."
250
+ },
251
+ headers=headers,
252
+ )
253
+
254
+ @serve.deployment(route_prefix="/")
255
+ @serve.ingress(app)
256
+ class ServeBot:
257
+ @app.get("/")
258
+ async def root(self):
259
+ return {"message": "Docu Mentor reporting for duty!"}
260
+
261
+ @app.post("/webhook/")
262
+ async def handle_webhook_route(self, request: Request):
263
+ return await handle_webhook(request)
264
+
@@ -52,7 +52,8 @@ VAC_SUBCONFIG_SCHEMA = {
52
52
  "cluster": {"type": "string"},
53
53
  "instance": {"type": "string"},
54
54
  "database": {"type": "string"}
55
- }
55
+ },
56
+ "required": ["project_id", "region", "cluster", "instance", "database"]
56
57
  },
57
58
  "secrets": {
58
59
  "type": "array",
@@ -87,7 +88,7 @@ VAC_CONFIG_SCHEMA = {
87
88
  }
88
89
  }
89
90
  },
90
- "required": ["kind", "apiVersion", "gcp_config", "vac"]
91
+ "required": ["kind", "apiVersion", "vac"]
91
92
  }
92
93
 
93
94
  PROMPT_CONFIG_SCHEMA = {
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.59.2
3
+ Version: 0.59.3
4
4
  Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
5
5
  Home-page: https://github.com/sunholo-data/sunholo-py
6
- Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.59.2.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.59.3.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -20,6 +20,7 @@ sunholo/auth/__init__.py,sha256=4owDjSaWYkbTlPK47UHTOC0gCWbZsqn4ZIEw5NWZTlg,28
20
20
  sunholo/auth/run.py,sha256=4fhNhDgtMtBHc08AywDmtazQPQ2560at6EMLQbMwTIo,2846
21
21
  sunholo/bots/__init__.py,sha256=EMFd7e2z68l6pzYOnkzHbLd2xJRvxTKFRNCTuhZ8hIw,130
22
22
  sunholo/bots/discord.py,sha256=cCFae5K1BCa6JVkWGLh_iZ9qFO1JpXb6K4eJrlDfEro,2442
23
+ sunholo/bots/github_webhook.py,sha256=5pQPRLM_wxxcILVaIzUDV8Kt7Arcm2dL1r1kMMHA524,9629
23
24
  sunholo/bots/webapp.py,sha256=EIMxdAJ_xtufwJmvnn7N_Fb_1hZ9DjhJ0Kf_hp02vEU,1926
24
25
  sunholo/chunker/__init__.py,sha256=UhQBZTKwDfBXm0TPv4LvsGc5pdUGCbYzi3qPTOkU4gw,55
25
26
  sunholo/chunker/data_to_embed_pubsub.py,sha256=t-pWNYv2mnwVAkMcIOK2CrIb3yr2aS9iAdtryk7hT8o,2931
@@ -85,14 +86,14 @@ sunholo/summarise/summarise.py,sha256=C3HhjepTjUhUC8FLk4jMQIBvq1BcORniwuTFHjPVhV
85
86
  sunholo/utils/__init__.py,sha256=G11nN_6ATjxpuMfG_BvcUr9UU8onPIgkpTK6CjOcbr8,48
86
87
  sunholo/utils/big_context.py,sha256=qHYtds4Ecf9eZRHVqXho4_q8Je7HD44-vS6RJ6s9Z0Q,5387
87
88
  sunholo/utils/config.py,sha256=Ve1sb68Av9_SPGqXs33g5FAJSIQ3GODoeuUCW3MNCwU,8802
88
- sunholo/utils/config_schema.py,sha256=bx3SHHuZ3SCOOXNRU91Mk-b4pHXt7D-EElv7Q85gAdw,3758
89
+ sunholo/utils/config_schema.py,sha256=Rkw5nVHcCtIQH_sH5ZUiDaxW1TOjUmzsDwHhrfiWeqQ,3829
89
90
  sunholo/utils/gcp.py,sha256=B2G1YKjeD7X9dqO86Jrp2vPuFwZ223Xl5Tg09Ndw-oc,5760
90
91
  sunholo/utils/parsers.py,sha256=OrHmASqIbI45atVOhiGodgLvnfrzkvVzyHnSvAXD89I,3841
91
92
  sunholo/vertex/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
93
  sunholo/vertex/init_vertex.py,sha256=JDMUaBRdednzbKF-5p33qqLit2LMsvgvWW-NRz0AqO0,1801
93
- sunholo-0.59.2.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
94
- sunholo-0.59.2.dist-info/METADATA,sha256=YcmQZ5MGbmH8xVGiJJEqVuK0DEr8ak2BM3nsQl3gfWc,7903
95
- sunholo-0.59.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
96
- sunholo-0.59.2.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
97
- sunholo-0.59.2.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
98
- sunholo-0.59.2.dist-info/RECORD,,
94
+ sunholo-0.59.3.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
95
+ sunholo-0.59.3.dist-info/METADATA,sha256=XAF0rooghlKcIPwDYbNNHt6dv5k06WF_PyV1t6GKl9E,7903
96
+ sunholo-0.59.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
97
+ sunholo-0.59.3.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
98
+ sunholo-0.59.3.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
99
+ sunholo-0.59.3.dist-info/RECORD,,