hyperpocket 0.0.3__py3-none-any.whl → 0.1.9__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- hyperpocket/auth/README.md +3 -3
- hyperpocket/auth/__init__.py +0 -8
- hyperpocket/auth/gumloop/context.py +13 -0
- hyperpocket/auth/gumloop/token_context.py +15 -0
- hyperpocket/auth/gumloop/token_handler.py +66 -0
- hyperpocket/auth/gumloop/token_schema.py +8 -0
- hyperpocket/auth/linear/token_context.py +1 -1
- hyperpocket/auth/notion/README.md +28 -0
- hyperpocket/auth/notion/context.py +15 -0
- hyperpocket/auth/notion/token_context.py +14 -0
- hyperpocket/auth/notion/token_handler.py +65 -0
- hyperpocket/auth/notion/token_schema.py +10 -0
- hyperpocket/auth/provider.py +8 -5
- hyperpocket/auth/reddit/context.py +15 -0
- hyperpocket/auth/reddit/oauth2_context.py +32 -0
- hyperpocket/auth/reddit/oauth2_handler.py +151 -0
- hyperpocket/auth/reddit/oauth2_schema.py +18 -0
- hyperpocket/auth/slack/token_context.py +1 -1
- hyperpocket/builtin.py +63 -0
- hyperpocket/cli/__main__.py +12 -0
- hyperpocket/cli/auth.py +83 -0
- hyperpocket/cli/codegen/auth/__init__.py +13 -0
- hyperpocket/cli/codegen/auth/auth_context_template.py +16 -0
- hyperpocket/cli/codegen/auth/auth_token_context_template.py +16 -0
- hyperpocket/cli/codegen/auth/auth_token_handler_template.py +69 -0
- hyperpocket/cli/codegen/auth/auth_token_schema_template.py +12 -0
- hyperpocket/cli/codegen/auth/server_auth_template.py +18 -0
- hyperpocket/cli/eject.py +19 -0
- hyperpocket/cli/sync.py +5 -5
- hyperpocket/config/settings.py +14 -12
- hyperpocket/futures/futurestore.py +0 -1
- hyperpocket/pocket_auth.py +25 -5
- hyperpocket/pocket_core.py +264 -0
- hyperpocket/pocket_main.py +127 -174
- hyperpocket/prompts.py +6 -8
- hyperpocket/repository/__init__.py +2 -2
- hyperpocket/repository/lock.py +71 -1
- hyperpocket/repository/lockfile.py +19 -13
- hyperpocket/repository/repository.py +26 -1
- hyperpocket/server/auth/__init__.py +0 -6
- hyperpocket/server/auth/gumloop.py +16 -0
- hyperpocket/server/auth/notion.py +19 -0
- hyperpocket/server/auth/reddit.py +16 -0
- hyperpocket/server/server.py +56 -20
- hyperpocket/server/tool/dto/script.py +15 -2
- hyperpocket/server/tool/wasm.py +20 -8
- hyperpocket/session/README.md +2 -2
- hyperpocket/session/in_memory.py +18 -5
- hyperpocket/session/interface.py +14 -0
- hyperpocket/session/redis.py +29 -5
- hyperpocket/tool/README.md +16 -12
- hyperpocket/tool/__init__.py +4 -3
- hyperpocket/tool/function/README.md +39 -10
- hyperpocket/tool/function/__init__.py +2 -0
- hyperpocket/tool/function/annotation.py +2 -1
- hyperpocket/tool/function/tool.py +108 -29
- hyperpocket/tool/tool.py +100 -28
- hyperpocket/tool/wasm/README.md +27 -5
- hyperpocket/tool/wasm/browser.py +2 -7
- hyperpocket/tool/wasm/script.py +40 -1
- hyperpocket/tool/wasm/templates/python.py +32 -14
- hyperpocket/tool/wasm/tool.py +21 -18
- hyperpocket/tool_like.py +5 -0
- hyperpocket/util/__init__.py +1 -1
- hyperpocket/util/extract_func_param_desc_from_docstring.py +4 -4
- hyperpocket/util/function_to_model.py +5 -2
- hyperpocket/util/json_schema_to_model.py +47 -26
- {hyperpocket-0.0.3.dist-info → hyperpocket-0.1.9.dist-info}/METADATA +107 -88
- hyperpocket-0.1.9.dist-info/RECORD +137 -0
- {hyperpocket-0.0.3.dist-info → hyperpocket-0.1.9.dist-info}/WHEEL +1 -1
- hyperpocket-0.1.9.dist-info/entry_points.txt +2 -0
- hyperpocket/auth/README.KR.md +0 -309
- hyperpocket/auth/slack/tests/test_oauth2_handler.py +0 -32
- hyperpocket/auth/slack/tests/test_token_handler.py +0 -23
- hyperpocket/auth/tests/test_google_oauth2_handler.py +0 -147
- hyperpocket/auth/tests/test_slack_oauth2_handler.py +0 -147
- hyperpocket/auth/tests/test_slack_token_handler.py +0 -66
- hyperpocket/external/__init__.py +0 -7
- hyperpocket/external/github_client.py +0 -19
- hyperpocket/session/README.KR.md +0 -62
- hyperpocket/session/tests/test_in_memory.py +0 -145
- hyperpocket/session/tests/test_redis.py +0 -151
- hyperpocket/tests/test_pocket.py +0 -116
- hyperpocket/tests/test_pocket_auth.py +0 -982
- hyperpocket/tool/README.KR.md +0 -68
- hyperpocket/tool/builtins/__init__.py +0 -0
- hyperpocket/tool/builtins/example/__init__.py +0 -0
- hyperpocket/tool/builtins/example/add_tool.py +0 -18
- hyperpocket/tool/function/README.KR.md +0 -159
- hyperpocket/tool/tests/test_function_tool.py +0 -266
- hyperpocket/tool/wasm/README.KR.md +0 -144
- hyperpocket-0.0.3.dist-info/RECORD +0 -130
- hyperpocket-0.0.3.dist-info/entry_points.txt +0 -3
- /hyperpocket/auth/{slack/tests → gumloop}/__init__.py +0 -0
- /hyperpocket/auth/{tests → notion}/__init__.py +0 -0
- /hyperpocket/{session/tests → auth/reddit}/__init__.py +0 -0
- /hyperpocket/{tests → cli/codegen}/__init__.py +0 -0
hyperpocket/pocket_main.py
CHANGED
@@ -1,86 +1,34 @@
|
|
1
1
|
import asyncio
|
2
|
-
import
|
3
|
-
from typing import Union, Any, Optional, Callable
|
2
|
+
from typing import Any, Optional, List, Union
|
4
3
|
|
5
4
|
from hyperpocket.config import pocket_logger
|
6
5
|
from hyperpocket.pocket_auth import PocketAuth
|
7
|
-
from hyperpocket.
|
8
|
-
from hyperpocket.repository.lock import LocalLock, GitLock
|
6
|
+
from hyperpocket.pocket_core import PocketCore
|
9
7
|
from hyperpocket.server.server import PocketServer, PocketServerOperations
|
10
|
-
from hyperpocket.
|
11
|
-
from hyperpocket.tool.function.tool import FunctionTool
|
12
|
-
from hyperpocket.tool.wasm import WasmTool
|
13
|
-
from hyperpocket.tool.wasm.tool import WasmToolRequest
|
14
|
-
|
15
|
-
ToolLike = Union[Tool, str, Callable, ToolRequest]
|
8
|
+
from hyperpocket.tool_like import ToolLike
|
16
9
|
|
17
10
|
|
18
11
|
class Pocket(object):
|
19
|
-
auth: PocketAuth
|
20
12
|
server: PocketServer
|
21
|
-
|
13
|
+
core: PocketCore
|
22
14
|
|
23
15
|
def __init__(self,
|
24
16
|
tools: list[ToolLike],
|
25
17
|
auth: PocketAuth = None,
|
26
18
|
lockfile_path: Optional[str] = None,
|
27
|
-
force_update: bool = False
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
if pathlib.Path(tool_like).exists():
|
39
|
-
lock = LocalLock(tool_like)
|
40
|
-
req = WasmToolRequest(lock, "")
|
41
|
-
else:
|
42
|
-
lock = GitLock(repository_url=tool_like, git_ref='HEAD')
|
43
|
-
req = WasmToolRequest(lock, "")
|
44
|
-
lockfile.add_lock(lock)
|
45
|
-
tool_likes.append(req)
|
46
|
-
elif isinstance(tool_like, WasmToolRequest):
|
47
|
-
lockfile.add_lock(tool_like.lock)
|
48
|
-
tool_likes.append(tool_like)
|
49
|
-
elif isinstance(tool_like, ToolRequest):
|
50
|
-
raise ValueError(f"unreachable. tool_like:{tool_like}")
|
51
|
-
elif isinstance(tool_like, WasmTool):
|
52
|
-
raise ValueError("WasmTool should pass ToolRequest instance instead.")
|
53
|
-
else:
|
54
|
-
tool_likes.append(tool_like)
|
55
|
-
lockfile.sync(force_update=force_update, referenced_only=True)
|
56
|
-
|
57
|
-
self.tools = dict()
|
58
|
-
for tool_like in tool_likes:
|
59
|
-
tool = self._load_tool(tool_like, lockfile)
|
60
|
-
if tool.name in self.tools:
|
61
|
-
pocket_logger.error(f"Duplicate tool name: {tool.name}.")
|
62
|
-
raise ValueError(f"Duplicate tool name: {tool.name}")
|
63
|
-
self.tools[tool.name] = tool
|
64
|
-
|
65
|
-
pocket_logger.info(f"All Tools Loaded successfully. total registered tools : {len(self.tools)}")
|
66
|
-
|
67
|
-
self.auth = auth
|
19
|
+
force_update: bool = False,
|
20
|
+
use_profile: bool = False):
|
21
|
+
self.use_profile = use_profile
|
22
|
+
|
23
|
+
self.core = PocketCore(
|
24
|
+
tools=tools,
|
25
|
+
auth=auth,
|
26
|
+
lockfile_path=lockfile_path,
|
27
|
+
force_update=force_update
|
28
|
+
)
|
29
|
+
|
68
30
|
self.server = PocketServer()
|
69
|
-
self.server.run(self)
|
70
|
-
|
71
|
-
def _load_tool(self, tool_like: ToolLike, lockfile: Lockfile) -> Tool:
|
72
|
-
pocket_logger.info(f"Loading Tool {tool_like}")
|
73
|
-
if isinstance(tool_like, Tool):
|
74
|
-
tool = tool_like
|
75
|
-
elif isinstance(tool_like, ToolRequest):
|
76
|
-
tool = Tool.from_tool_request(tool_like, lockfile=lockfile)
|
77
|
-
elif isinstance(tool_like, Callable):
|
78
|
-
tool = FunctionTool.from_func(tool_like)
|
79
|
-
else:
|
80
|
-
raise ValueError(f"Invalid tool type: {type(tool_like)}")
|
81
|
-
|
82
|
-
pocket_logger.info(f"Complete Loading Tool {tool.name}")
|
83
|
-
return tool
|
31
|
+
self.server.run(self.core)
|
84
32
|
|
85
33
|
def invoke(self,
|
86
34
|
tool_name: str,
|
@@ -204,131 +152,136 @@ class Pocket(object):
|
|
204
152
|
|
205
153
|
return result, paused
|
206
154
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
thread_id: str = 'default',
|
212
|
-
profile: str = 'default',
|
213
|
-
*args, **kwargs) -> tuple[str, bool]:
|
155
|
+
async def initialize_tool_auth(self,
|
156
|
+
thread_id: str = 'default',
|
157
|
+
profile: str = 'default',
|
158
|
+
) -> dict[str, str]:
|
214
159
|
"""
|
215
|
-
|
216
|
-
|
160
|
+
Initialize authentication for all tools.
|
161
|
+
|
162
|
+
This method prepares all tools that require authentication by retrieving
|
163
|
+
their respective authentication URIs.
|
217
164
|
|
218
|
-
|
219
|
-
1. `prepare_auth` : preparing the authentication process for the tool if necessary.
|
220
|
-
2. `authenticate` : performing authentication that needs to invoke tool.
|
221
|
-
3. `tool_call` : Executing tool actually with authentication information.
|
165
|
+
If no tool requires authentication, an empty list is returned.
|
222
166
|
|
223
167
|
Args:
|
224
|
-
|
225
|
-
|
226
|
-
thread_id(str): thread id
|
227
|
-
profile(str): profile name
|
168
|
+
thread_id (str): The thread id. Defaults to 'default'.
|
169
|
+
profile (str): The profile to be used for authentication. Defaults to 'default'.
|
228
170
|
|
229
171
|
Returns:
|
230
|
-
|
231
|
-
"""
|
232
|
-
tool = self._tool_instance(tool_name)
|
233
|
-
if tool.auth is not None:
|
234
|
-
callback_info = self.prepare_auth(tool_name, thread_id, profile, **kwargs)
|
235
|
-
if callback_info:
|
236
|
-
return callback_info, True
|
237
|
-
# 02. authenticate
|
238
|
-
credentials = await self.authenticate(tool_name, thread_id, profile, **kwargs)
|
239
|
-
# 03. call tool
|
240
|
-
result = await self.tool_call(tool_name, body=body, envs=credentials, **kwargs)
|
241
|
-
return result, False
|
242
|
-
|
243
|
-
def prepare_auth(self,
|
244
|
-
tool_name: str,
|
245
|
-
thread_id: str = 'default',
|
246
|
-
profile: str = 'default',
|
247
|
-
**kwargs) -> Optional[str]:
|
172
|
+
List[str]: A list of authentication URIs for the tools that require authentication.
|
248
173
|
"""
|
249
|
-
|
250
|
-
Returns callback URL and whether the tool requires authentication.
|
174
|
+
tool_by_provider = self.core.grouping_tool_by_auth_provider()
|
251
175
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
176
|
+
prepare_list = {}
|
177
|
+
for provider, tools in tool_by_provider.items():
|
178
|
+
tool_name_list = [tool.name for tool in tools]
|
179
|
+
prepare = await self.prepare_in_subprocess(
|
180
|
+
tool_name=tool_name_list,
|
181
|
+
thread_id=thread_id,
|
182
|
+
profile=profile)
|
256
183
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
if tool.auth is None:
|
262
|
-
return None
|
263
|
-
|
264
|
-
auth_req = self.auth.make_request(
|
265
|
-
auth_handler_name=tool.auth.auth_handler,
|
266
|
-
auth_provider=tool.auth.auth_provider,
|
267
|
-
auth_scopes=tool.auth.scopes)
|
268
|
-
|
269
|
-
return self.auth.prepare(
|
270
|
-
auth_req=auth_req,
|
271
|
-
auth_handler_name=tool.auth.auth_handler,
|
272
|
-
auth_provider=tool.auth.auth_provider,
|
273
|
-
thread_id=thread_id,
|
274
|
-
profile=profile,
|
275
|
-
**kwargs
|
276
|
-
)
|
184
|
+
if prepare is not None:
|
185
|
+
prepare_list[provider] = prepare
|
186
|
+
|
187
|
+
return prepare_list
|
277
188
|
|
278
|
-
async def
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
profile: str = 'default',
|
283
|
-
**kwargs) -> dict[str, str]:
|
189
|
+
async def wait_tool_auth(self,
|
190
|
+
thread_id: str = 'default',
|
191
|
+
profile: str = 'default'
|
192
|
+
) -> bool:
|
284
193
|
"""
|
285
|
-
|
194
|
+
Wait until all tool authentications are completed.
|
195
|
+
|
196
|
+
This method waits until all tools associated with the given
|
197
|
+
`thread_id` and `profile` have completed their authentication process.
|
286
198
|
|
287
199
|
Args:
|
288
|
-
|
289
|
-
|
290
|
-
profile(str): profile name
|
200
|
+
thread_id (str): The thread id. Defaults to 'default'.
|
201
|
+
profile (str): The profile to be used for authentication. Defaults to 'default'.
|
291
202
|
|
292
203
|
Returns:
|
293
|
-
|
204
|
+
bool: Returns `True` if all tool authentications are successfully completed,
|
205
|
+
or `False` if the process was interrupted or failed.
|
294
206
|
"""
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
207
|
+
try:
|
208
|
+
tool_by_provider = self.core.grouping_tool_by_auth_provider()
|
209
|
+
|
210
|
+
waiting_futures = []
|
211
|
+
for provider, tools in tool_by_provider.items():
|
212
|
+
if len(tools) == 0:
|
213
|
+
continue
|
214
|
+
|
215
|
+
waiting_futures.append(
|
216
|
+
self.authenticate_in_subprocess(
|
217
|
+
tool_name=tools[0].name,
|
218
|
+
thread_id=thread_id,
|
219
|
+
profile=profile
|
220
|
+
))
|
221
|
+
|
222
|
+
await asyncio.gather(*waiting_futures)
|
223
|
+
|
224
|
+
return True
|
225
|
+
|
226
|
+
except asyncio.TimeoutError as e:
|
227
|
+
pocket_logger.error("authentication time out.")
|
228
|
+
raise e
|
229
|
+
|
230
|
+
async def prepare_in_subprocess(self,
|
231
|
+
tool_name: Union[str, List[str]],
|
232
|
+
thread_id: str = 'default',
|
233
|
+
profile: str = 'default',
|
234
|
+
*args, **kwargs):
|
235
|
+
prepare = await self.server.call_in_subprocess(
|
236
|
+
PocketServerOperations.PREPARE_AUTH,
|
237
|
+
args,
|
238
|
+
{
|
239
|
+
'tool_name': tool_name,
|
240
|
+
'thread_id': thread_id,
|
241
|
+
'profile': profile,
|
242
|
+
**kwargs,
|
243
|
+
},
|
309
244
|
)
|
310
|
-
return auth_ctx.to_dict()
|
311
245
|
|
312
|
-
|
313
|
-
"""
|
314
|
-
Executing tool actually
|
246
|
+
return prepare
|
315
247
|
|
316
|
-
|
317
|
-
|
318
|
-
|
248
|
+
async def authenticate_in_subprocess(self,
|
249
|
+
tool_name: str,
|
250
|
+
thread_id: str = 'default',
|
251
|
+
profile: str = 'default',
|
252
|
+
*args, **kwargs):
|
253
|
+
credentials = await self.server.call_in_subprocess(
|
254
|
+
PocketServerOperations.AUTHENTICATE,
|
255
|
+
args,
|
256
|
+
{
|
257
|
+
'tool_name': tool_name,
|
258
|
+
'thread_id': thread_id,
|
259
|
+
'profile': profile,
|
260
|
+
**kwargs,
|
261
|
+
},
|
262
|
+
)
|
319
263
|
|
320
|
-
|
321
|
-
str: tool result
|
322
|
-
"""
|
323
|
-
tool = self._tool_instance(tool_name)
|
324
|
-
try:
|
325
|
-
return await asyncio.wait_for(tool.ainvoke(**kwargs), timeout=180)
|
326
|
-
except asyncio.TimeoutError:
|
327
|
-
pocket_logger.warning("Timeout tool call.")
|
328
|
-
return "timeout tool call"
|
264
|
+
return credentials
|
329
265
|
|
330
|
-
def
|
331
|
-
|
266
|
+
async def tool_call_in_subprocess(self,
|
267
|
+
tool_name: str,
|
268
|
+
body: Any,
|
269
|
+
thread_id: str = 'default',
|
270
|
+
profile: str = 'default',
|
271
|
+
*args, **kwargs):
|
272
|
+
result = await self.server.call_in_subprocess(
|
273
|
+
PocketServerOperations.TOOL_CALL,
|
274
|
+
args,
|
275
|
+
{
|
276
|
+
'tool_name': tool_name,
|
277
|
+
'body': body,
|
278
|
+
'thread_id': thread_id,
|
279
|
+
'profile': profile,
|
280
|
+
**kwargs,
|
281
|
+
},
|
282
|
+
)
|
283
|
+
|
284
|
+
return result
|
332
285
|
|
333
286
|
def _teardown_server(self):
|
334
287
|
self.server.teardown()
|
@@ -338,11 +291,11 @@ class Pocket(object):
|
|
338
291
|
|
339
292
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
340
293
|
if self.__dict__.get('server'):
|
341
|
-
self.
|
294
|
+
self.server.teardown()
|
342
295
|
|
343
296
|
def __del__(self):
|
344
297
|
if self.__dict__.get('server'):
|
345
|
-
self.
|
298
|
+
self.server.teardown()
|
346
299
|
|
347
300
|
def __getstate__(self):
|
348
301
|
state = self.__dict__.copy()
|
hyperpocket/prompts.py
CHANGED
@@ -1,15 +1,13 @@
|
|
1
1
|
def pocket_extended_tool_description(description: str):
|
2
2
|
return f'''
|
3
|
-
This tool
|
3
|
+
This tool functions as described in the <tool-description> XML tag:
|
4
4
|
<tool-description>
|
5
5
|
{description}
|
6
6
|
</tool-description>
|
7
7
|
|
8
|
-
|
9
|
-
- 'thread_id':
|
10
|
-
- 'profile': The profile of the user invoking the tool.
|
11
|
-
Users can request tools to be invoked in specific personas
|
12
|
-
|
13
|
-
Omitted when unknown.
|
14
|
-
- 'body': The argument of the tool. The argument is passed as JSON property 'body' in the JSON schema.
|
8
|
+
Arguments required:
|
9
|
+
- 'thread_id': Chat thread ID (omit if unknown).
|
10
|
+
- 'profile': The profile of the user invoking the tool. (infer from messages; omit if unknown).
|
11
|
+
Users can request tools to be invoked in specific personas.
|
12
|
+
- 'body': Tool argument passed as a JSON 'body'.
|
15
13
|
'''
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from hyperpocket.repository.lock import Lock
|
2
2
|
from hyperpocket.repository.lockfile import Lockfile
|
3
|
-
from hyperpocket.repository.repository import pull, sync
|
3
|
+
from hyperpocket.repository.repository import pull, sync, eject
|
4
4
|
|
5
|
-
__all__ = ["Lock", "Lockfile", "pull", "sync"]
|
5
|
+
__all__ = ["Lock", "Lockfile", "pull", "sync", "eject"]
|
hyperpocket/repository/lock.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import abc
|
2
2
|
import pathlib
|
3
3
|
import shutil
|
4
|
-
from typing import Optional
|
4
|
+
from typing import Optional, Tuple
|
5
5
|
|
6
6
|
import git
|
7
7
|
from pydantic import BaseModel, Field
|
@@ -28,6 +28,10 @@ class Lock(BaseModel, abc.ABC):
|
|
28
28
|
def toolpkg_path(self) -> pathlib.Path:
|
29
29
|
raise NotImplementedError
|
30
30
|
|
31
|
+
def eject_to_path(self, dest_path: pathlib.Path, src_sub_path: str = None):
|
32
|
+
## local locks are already tracked by git
|
33
|
+
raise NotImplementedError
|
34
|
+
|
31
35
|
|
32
36
|
class LocalLock(Lock):
|
33
37
|
tool_source: str = Field(default='local')
|
@@ -154,3 +158,69 @@ class GitLock(Lock):
|
|
154
158
|
new_sha = sha
|
155
159
|
break
|
156
160
|
return new_sha
|
161
|
+
|
162
|
+
@classmethod
|
163
|
+
def get_git_branches(cls, repo_url):
|
164
|
+
ls_lists = git.cmd.Git().ls_remote(repo_url)
|
165
|
+
|
166
|
+
branches = {}
|
167
|
+
for line in ls_lists.split("\n"):
|
168
|
+
sha, ref = line.split("\t")
|
169
|
+
if ref.startswith("refs/heads/"):
|
170
|
+
branch_name = ref.replace("refs/heads/", "")
|
171
|
+
branches[branch_name] = sha
|
172
|
+
|
173
|
+
return branches
|
174
|
+
|
175
|
+
@classmethod
|
176
|
+
def parse_repo_url(cls, repo_url: str) -> Tuple[str, str, str]:
|
177
|
+
"""
|
178
|
+
Parses a GitHub repository URL with optional branch and path information.
|
179
|
+
|
180
|
+
Returns:
|
181
|
+
Tuple[str, str, str]: base_repo, branch_name, directory_path
|
182
|
+
"""
|
183
|
+
if not repo_url.startswith("https://github.com/"):
|
184
|
+
raise AttributeError("Only GitHub URLs are supported")
|
185
|
+
|
186
|
+
# Remove the base URL and split the path
|
187
|
+
repo_path = repo_url.removeprefix("https://github.com/")
|
188
|
+
repo_path_list = repo_path.split("/")
|
189
|
+
|
190
|
+
# Check if the URL contains 'tree' (indicating branch and sub-path information)
|
191
|
+
if "tree" not in repo_path_list:
|
192
|
+
# If no 'tree', return the full repository URL
|
193
|
+
return repo_url, "HEAD", ""
|
194
|
+
|
195
|
+
# Parse base repo URL and remaining path
|
196
|
+
tree_index = repo_path_list.index("tree")
|
197
|
+
base_repo = f"https://github.com/{'/'.join(repo_path_list[:tree_index])}"
|
198
|
+
sub_path = repo_path_list[tree_index + 1:]
|
199
|
+
|
200
|
+
# Fetch branch information
|
201
|
+
branches = cls.get_git_branches(base_repo)
|
202
|
+
|
203
|
+
# Find branch and sub-directory path
|
204
|
+
for idx in range(1, len(sub_path) + 1):
|
205
|
+
branch_name = "/".join(sub_path[:idx])
|
206
|
+
if branch_name in branches:
|
207
|
+
directory_path = "/".join(sub_path[idx:]) if idx < len(sub_path) else None
|
208
|
+
return base_repo, branch_name, directory_path
|
209
|
+
|
210
|
+
# If no valid branch is found, raise an error
|
211
|
+
raise ValueError("Branch not found in repository")
|
212
|
+
|
213
|
+
def eject_to_path(self, dest_path: pathlib.Path, src_sub_path: str = None):
|
214
|
+
|
215
|
+
# clone the git repository to the target path
|
216
|
+
pocket_logger.info(
|
217
|
+
f"Ejecting git: {self.repository_url} @ ref: {self.git_ref} source in path: {src_sub_path} to {dest_path} ..."
|
218
|
+
)
|
219
|
+
if dest_path.exists():
|
220
|
+
shutil.rmtree(dest_path)
|
221
|
+
|
222
|
+
if src_sub_path:
|
223
|
+
src_path = self.toolpkg_path() / src_sub_path
|
224
|
+
else:
|
225
|
+
src_path = self.toolpkg_path()
|
226
|
+
shutil.copytree(src_path, dest_path)
|
@@ -8,19 +8,19 @@ class Lockfile:
|
|
8
8
|
path: pathlib.Path = None
|
9
9
|
locks: dict[tuple, Lock] = None
|
10
10
|
referenced_locks: set[tuple] = None
|
11
|
-
|
11
|
+
|
12
12
|
def __init__(self, path: pathlib.Path):
|
13
13
|
self.path = path
|
14
14
|
self.locks = {}
|
15
15
|
self.referenced_locks = set()
|
16
16
|
if self.path.exists():
|
17
|
-
with open(self.path,
|
17
|
+
with open(self.path, "r") as f:
|
18
18
|
for line in f:
|
19
|
-
split = line.strip().split(
|
19
|
+
split = line.strip().split("\t")
|
20
20
|
source = split[0]
|
21
|
-
if source ==
|
21
|
+
if source == "local":
|
22
22
|
lock = LocalLock(tool_path=split[1])
|
23
|
-
elif source ==
|
23
|
+
elif source == "git":
|
24
24
|
lock = GitLock(
|
25
25
|
repository_url=split[1],
|
26
26
|
git_ref=split[2],
|
@@ -31,12 +31,17 @@ class Lockfile:
|
|
31
31
|
self.locks[lock.key()] = lock
|
32
32
|
else:
|
33
33
|
self.path.touch()
|
34
|
-
|
34
|
+
|
35
35
|
def add_lock(self, lock: Lock):
|
36
36
|
if lock.key() not in self.locks:
|
37
37
|
self.locks[lock.key()] = lock
|
38
38
|
self.referenced_locks.add(lock.key())
|
39
|
-
|
39
|
+
|
40
|
+
def remove_lock(self, key: tuple[str, ...]):
|
41
|
+
self.locks.pop(key)
|
42
|
+
if key in self.referenced_locks:
|
43
|
+
self.referenced_locks.remove(key)
|
44
|
+
|
40
45
|
def get_lock(self, key: tuple[str, ...]):
|
41
46
|
return self.locks[key]
|
42
47
|
|
@@ -45,12 +50,13 @@ class Lockfile:
|
|
45
50
|
locks = [self.get_lock(key) for key in self.referenced_locks]
|
46
51
|
else:
|
47
52
|
locks = list(self.locks.values())
|
48
|
-
with ThreadPoolExecutor(
|
49
|
-
|
53
|
+
with ThreadPoolExecutor(
|
54
|
+
max_workers=min(len(locks) + 1, 100), thread_name_prefix="repository_loader"
|
55
|
+
) as executor:
|
56
|
+
executor.map(lambda lock: lock.sync(force_update=force_update), locks)
|
50
57
|
self.write()
|
51
|
-
|
58
|
+
|
52
59
|
def write(self):
|
53
|
-
with open(self.path,
|
60
|
+
with open(self.path, "w") as f:
|
54
61
|
for lock in self.locks.values():
|
55
|
-
f.write(str(lock) +
|
56
|
-
|
62
|
+
f.write(str(lock) + "\n")
|
@@ -12,7 +12,32 @@ def pull(lockfile: Lockfile, urllike: str, git_ref: str):
|
|
12
12
|
lockfile.add_lock(GitLock(repository_url=urllike, git_ref=git_ref))
|
13
13
|
lockfile.sync(force_update=False)
|
14
14
|
lockfile.write()
|
15
|
-
|
15
|
+
|
16
|
+
|
16
17
|
def sync(lockfile: Lockfile, force_update: bool):
|
17
18
|
lockfile.sync(force_update=force_update)
|
18
19
|
lockfile.write()
|
20
|
+
|
21
|
+
|
22
|
+
def eject(url: str, ref: str, remote_path: str, lockfile: Lockfile):
|
23
|
+
path = pathlib.Path(url)
|
24
|
+
if path.exists():
|
25
|
+
raise ValueError("Local tools are already ejected")
|
26
|
+
else:
|
27
|
+
lock = lockfile.get_lock(GitLock(repository_url=url, git_ref=ref).key())
|
28
|
+
|
29
|
+
# first copy source to ejected directory
|
30
|
+
working_dir = pathlib.Path.cwd()
|
31
|
+
ejected_dir = working_dir / "ejected_tools"
|
32
|
+
if not ejected_dir.exists():
|
33
|
+
ejected_dir.mkdir()
|
34
|
+
|
35
|
+
tool_name = pathlib.Path(remote_path).name
|
36
|
+
eject_path = ejected_dir / tool_name
|
37
|
+
lock.eject_to_path(eject_path, remote_path)
|
38
|
+
|
39
|
+
# then update lockfile
|
40
|
+
lockfile.remove_lock(lock.key())
|
41
|
+
lockfile.add_lock(LocalLock(tool_path=str(eject_path)))
|
42
|
+
lockfile.sync(force_update=True)
|
43
|
+
lockfile.write()
|
@@ -1,11 +1,5 @@
|
|
1
1
|
from fastapi import APIRouter
|
2
2
|
|
3
|
-
from hyperpocket.server.auth.github import github_auth_router
|
4
|
-
from hyperpocket.server.auth.google import google_auth_router
|
5
|
-
from hyperpocket.server.auth.linear import linear_auth_router
|
6
|
-
from hyperpocket.server.auth.slack import slack_auth_router
|
7
|
-
from hyperpocket.server.auth.token import token_router
|
8
|
-
from hyperpocket.server.auth.calendly import calendly_auth_router
|
9
3
|
from hyperpocket.util.get_objects_from_subpackage import get_objects_from_subpackage
|
10
4
|
|
11
5
|
auth_router = APIRouter(prefix="/auth")
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from fastapi import APIRouter, Request
|
2
|
+
from starlette.responses import HTMLResponse
|
3
|
+
|
4
|
+
from hyperpocket.futures import FutureStore
|
5
|
+
|
6
|
+
gumloop_auth_router = APIRouter(prefix="/gumloop")
|
7
|
+
|
8
|
+
|
9
|
+
@gumloop_auth_router.get("/token/callback")
|
10
|
+
async def gumloop_oauth2_callback(request: Request, state: str, token: str):
|
11
|
+
try:
|
12
|
+
FutureStore.resolve_future(state, token)
|
13
|
+
except ValueError:
|
14
|
+
return HTMLResponse(content="failed")
|
15
|
+
|
16
|
+
return HTMLResponse(content="success")
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
from fastapi import APIRouter
|
3
|
+
from starlette.responses import HTMLResponse
|
4
|
+
|
5
|
+
from hyperpocket.futures import FutureStore
|
6
|
+
|
7
|
+
notion_auth_router = APIRouter(
|
8
|
+
prefix="/notion"
|
9
|
+
)
|
10
|
+
|
11
|
+
|
12
|
+
@notion_auth_router.get("/token/callback")
|
13
|
+
async def notion_token_callback(state: str, token: str):
|
14
|
+
try:
|
15
|
+
FutureStore.resolve_future(state, token)
|
16
|
+
except ValueError:
|
17
|
+
return HTMLResponse(content="failed")
|
18
|
+
|
19
|
+
return HTMLResponse(content="success")
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from fastapi import APIRouter
|
2
|
+
from starlette.responses import HTMLResponse
|
3
|
+
|
4
|
+
from hyperpocket.futures import FutureStore
|
5
|
+
|
6
|
+
reddit_auth_router = APIRouter(prefix="/reddit")
|
7
|
+
|
8
|
+
|
9
|
+
@reddit_auth_router.get("/oauth2/callback")
|
10
|
+
async def reddit_oauth2_callback(state: str, code: str):
|
11
|
+
try:
|
12
|
+
FutureStore.resolve_future(state, code)
|
13
|
+
except ValueError:
|
14
|
+
return HTMLResponse(content="failed")
|
15
|
+
|
16
|
+
return HTMLResponse(content="success")
|