hyperpocket 0.0.2__py3-none-any.whl → 0.1.8__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. hyperpocket/auth/README.md +3 -3
  2. hyperpocket/auth/__init__.py +0 -8
  3. hyperpocket/auth/gumloop/context.py +13 -0
  4. hyperpocket/auth/gumloop/token_context.py +15 -0
  5. hyperpocket/auth/gumloop/token_handler.py +66 -0
  6. hyperpocket/auth/gumloop/token_schema.py +8 -0
  7. hyperpocket/auth/linear/token_context.py +1 -1
  8. hyperpocket/auth/notion/README.md +28 -0
  9. hyperpocket/auth/notion/context.py +15 -0
  10. hyperpocket/auth/notion/token_context.py +14 -0
  11. hyperpocket/auth/notion/token_handler.py +65 -0
  12. hyperpocket/auth/notion/token_schema.py +10 -0
  13. hyperpocket/auth/provider.py +8 -5
  14. hyperpocket/auth/reddit/context.py +15 -0
  15. hyperpocket/auth/reddit/oauth2_context.py +32 -0
  16. hyperpocket/auth/reddit/oauth2_handler.py +151 -0
  17. hyperpocket/auth/reddit/oauth2_schema.py +18 -0
  18. hyperpocket/auth/slack/token_context.py +1 -1
  19. hyperpocket/builtin.py +63 -0
  20. hyperpocket/cli/__main__.py +12 -0
  21. hyperpocket/cli/auth.py +83 -0
  22. hyperpocket/cli/codegen/auth/__init__.py +13 -0
  23. hyperpocket/cli/codegen/auth/auth_context_template.py +16 -0
  24. hyperpocket/cli/codegen/auth/auth_token_context_template.py +16 -0
  25. hyperpocket/cli/codegen/auth/auth_token_handler_template.py +69 -0
  26. hyperpocket/cli/codegen/auth/auth_token_schema_template.py +12 -0
  27. hyperpocket/cli/codegen/auth/server_auth_template.py +18 -0
  28. hyperpocket/cli/eject.py +19 -0
  29. hyperpocket/cli/sync.py +5 -5
  30. hyperpocket/config/settings.py +2 -4
  31. hyperpocket/futures/futurestore.py +0 -1
  32. hyperpocket/pocket_auth.py +25 -5
  33. hyperpocket/pocket_core.py +262 -0
  34. hyperpocket/pocket_main.py +125 -171
  35. hyperpocket/prompts.py +6 -8
  36. hyperpocket/repository/__init__.py +2 -2
  37. hyperpocket/repository/lock.py +19 -0
  38. hyperpocket/repository/lockfile.py +19 -13
  39. hyperpocket/repository/repository.py +26 -1
  40. hyperpocket/server/auth/__init__.py +0 -6
  41. hyperpocket/server/auth/gumloop.py +16 -0
  42. hyperpocket/server/auth/notion.py +19 -0
  43. hyperpocket/server/auth/reddit.py +16 -0
  44. hyperpocket/server/server.py +52 -16
  45. hyperpocket/server/tool/dto/script.py +15 -2
  46. hyperpocket/server/tool/wasm.py +20 -8
  47. hyperpocket/session/README.md +2 -2
  48. hyperpocket/session/in_memory.py +18 -5
  49. hyperpocket/session/interface.py +14 -0
  50. hyperpocket/session/redis.py +29 -5
  51. hyperpocket/tool/README.md +16 -12
  52. hyperpocket/tool/__init__.py +4 -3
  53. hyperpocket/tool/function/README.md +39 -10
  54. hyperpocket/tool/function/__init__.py +2 -0
  55. hyperpocket/tool/function/annotation.py +2 -1
  56. hyperpocket/tool/function/tool.py +98 -13
  57. hyperpocket/tool/tests/test_function_tool.py +55 -0
  58. hyperpocket/tool/tests/test_wasm_tool.py +73 -0
  59. hyperpocket/tool/tool.py +65 -2
  60. hyperpocket/tool/wasm/README.md +27 -5
  61. hyperpocket/tool/wasm/script.py +40 -1
  62. hyperpocket/tool/wasm/templates/python.py +32 -14
  63. hyperpocket/tool/wasm/tool.py +21 -18
  64. hyperpocket/tool_like.py +5 -0
  65. hyperpocket/util/__init__.py +1 -1
  66. hyperpocket/util/extract_func_param_desc_from_docstring.py +4 -4
  67. hyperpocket/util/function_to_model.py +5 -2
  68. hyperpocket/util/json_schema_to_model.py +45 -26
  69. {hyperpocket-0.0.2.dist-info → hyperpocket-0.1.8.dist-info}/METADATA +101 -72
  70. hyperpocket-0.1.8.dist-info/RECORD +139 -0
  71. {hyperpocket-0.0.2.dist-info → hyperpocket-0.1.8.dist-info}/WHEEL +1 -1
  72. hyperpocket-0.1.8.dist-info/entry_points.txt +2 -0
  73. hyperpocket/auth/README.KR.md +0 -309
  74. hyperpocket/auth/slack/tests/test_oauth2_handler.py +0 -32
  75. hyperpocket/auth/slack/tests/test_token_handler.py +0 -23
  76. hyperpocket/auth/tests/test_google_oauth2_handler.py +0 -147
  77. hyperpocket/auth/tests/test_slack_oauth2_handler.py +0 -147
  78. hyperpocket/auth/tests/test_slack_token_handler.py +0 -66
  79. hyperpocket/external/__init__.py +0 -7
  80. hyperpocket/external/github_client.py +0 -19
  81. hyperpocket/session/README.KR.md +0 -62
  82. hyperpocket/session/tests/test_in_memory.py +0 -145
  83. hyperpocket/session/tests/test_redis.py +0 -151
  84. hyperpocket/tests/test_pocket.py +0 -116
  85. hyperpocket/tests/test_pocket_auth.py +0 -982
  86. hyperpocket/tool/README.KR.md +0 -68
  87. hyperpocket/tool/builtins/__init__.py +0 -0
  88. hyperpocket/tool/builtins/example/__init__.py +0 -0
  89. hyperpocket/tool/builtins/example/add_tool.py +0 -18
  90. hyperpocket/tool/function/README.KR.md +0 -159
  91. hyperpocket/tool/wasm/README.KR.md +0 -144
  92. hyperpocket-0.0.2.dist-info/RECORD +0 -130
  93. hyperpocket-0.0.2.dist-info/entry_points.txt +0 -3
  94. /hyperpocket/auth/{slack/tests → gumloop}/__init__.py +0 -0
  95. /hyperpocket/auth/{tests → notion}/__init__.py +0 -0
  96. /hyperpocket/{session/tests → auth/reddit}/__init__.py +0 -0
  97. /hyperpocket/{tests → cli/codegen}/__init__.py +0 -0
@@ -1,86 +1,32 @@
1
1
  import asyncio
2
- import pathlib
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.repository import Lockfile
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.tool import Tool, ToolRequest
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
- tools: dict[str, Tool]
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
19
  force_update: bool = False):
28
- if auth is None:
29
- auth = PocketAuth()
30
-
31
- if lockfile_path is None:
32
- lockfile_path = "./pocket.lock"
33
- lockfile_pathlib_path = pathlib.Path(lockfile_path)
34
- lockfile = Lockfile(lockfile_pathlib_path)
35
- tool_likes = []
36
- for tool_like in tools:
37
- if isinstance(tool_like, str):
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
20
+
21
+ self.core = PocketCore(
22
+ tools=tools,
23
+ auth=auth,
24
+ lockfile_path=lockfile_path,
25
+ force_update=force_update
26
+ )
27
+
68
28
  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
29
+ self.server.run(self.core)
84
30
 
85
31
  def invoke(self,
86
32
  tool_name: str,
@@ -204,131 +150,139 @@ class Pocket(object):
204
150
 
205
151
  return result, paused
206
152
 
207
- # DO NOT EVER THINK ABOUT USING SERVER PROPERTY BELOW HERE
208
- async def acall(self,
209
- tool_name: str,
210
- body: Any,
211
- thread_id: str = 'default',
212
- profile: str = 'default',
213
- *args, **kwargs) -> tuple[str, bool]:
153
+ async def initialize_tool_auth(self,
154
+ thread_id: str = 'default',
155
+ profile: str = 'default',
156
+ ) -> dict[str, str]:
214
157
  """
215
- Invoke tool asynchronously, not that different from `Pocket.invoke`
216
- But this method is called only in subprocess.
158
+ Initialize authentication for all tools.
217
159
 
218
- This function performs the following steps:
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.
160
+ This method prepares all tools that require authentication by retrieving
161
+ their respective authentication URIs.
162
+
163
+ If no tool requires authentication, an empty list is returned.
222
164
 
223
165
  Args:
224
- tool_name(str): tool name to invoke
225
- body(Any): tool arguments. should be json format
226
- thread_id(str): thread id
227
- profile(str): profile name
166
+ thread_id (str): The thread id. Defaults to 'default'.
167
+ profile (str): The profile to be used for authentication. Defaults to 'default'.
228
168
 
229
169
  Returns:
230
- tuple[str, bool]: tool result and state.
170
+ List[str]: A list of authentication URIs for the tools that require authentication.
231
171
  """
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]:
248
- """
249
- Prepares the authentication process for the tool if necessary.
250
- Returns callback URL and whether the tool requires authentication.
172
+ tool_by_provider = self.core.grouping_tool_by_auth_provider()
251
173
 
252
- Args:
253
- tool_name(str): tool name to invoke
254
- thread_id(str): thread id
255
- profile(str): profile name
174
+ prepare_list = {}
175
+ for provider, tools in tool_by_provider.items():
176
+ tool_name_list = [tool.name for tool in tools]
177
+ prepare = await self.prepare_in_subprocess(
178
+ tool_name=tool_name_list,
179
+ thread_id=thread_id,
180
+ profile=profile)
256
181
 
257
- Returns:
258
- Optional[str]: callback URI if necessary
259
- """
260
- tool = self._tool_instance(tool_name)
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
- )
182
+ if prepare is not None:
183
+ prepare_list[provider] = prepare
277
184
 
278
- async def authenticate(
279
- self,
280
- tool_name: str,
281
- thread_id: str = 'default',
282
- profile: str = 'default',
283
- **kwargs) -> dict[str, str]:
185
+ return prepare_list
186
+
187
+ async def wait_tool_auth(self,
188
+ thread_id: str = 'default',
189
+ profile: str = 'default'
190
+ ) -> bool:
284
191
  """
285
- Authenticates the handler included in the tool and returns credentials.
192
+ Wait until all tool authentications are completed.
193
+
194
+ This method waits until all tools associated with the given
195
+ `thread_id` and `profile` have completed their authentication process.
286
196
 
287
197
  Args:
288
- tool_name(str): tool name to invoke
289
- thread_id(str): thread id
290
- profile(str): profile name
198
+ thread_id (str): The thread id. Defaults to 'default'.
199
+ profile (str): The profile to be used for authentication. Defaults to 'default'.
291
200
 
292
201
  Returns:
293
- dict[str, str]: credentials
202
+ bool: Returns `True` if all tool authentications are successfully completed,
203
+ or `False` if the process was interrupted or failed.
294
204
  """
295
- tool = self._tool_instance(tool_name)
296
- if tool.auth is None:
297
- return {}
298
- auth_req = self.auth.make_request(
299
- auth_handler_name=tool.auth.auth_handler,
300
- auth_provider=tool.auth.auth_provider,
301
- auth_scopes=tool.auth.scopes)
302
- auth_ctx = await self.auth.authenticate_async(
303
- auth_req=auth_req,
304
- auth_handler_name=tool.auth.auth_handler,
305
- auth_provider=tool.auth.auth_provider,
306
- thread_id=thread_id,
307
- profile=profile,
308
- **kwargs,
205
+ try:
206
+ tool_by_provider = self.core.grouping_tool_by_auth_provider()
207
+
208
+ waiting_futures = []
209
+ for provider, tools in tool_by_provider.items():
210
+ if len(tools) == 0:
211
+ continue
212
+
213
+ waiting_futures.append(
214
+ self.authenticate_in_subprocess(
215
+ tool_name=tools[0].name,
216
+ thread_id=thread_id,
217
+ profile=profile
218
+ ))
219
+
220
+ await asyncio.gather(*waiting_futures)
221
+
222
+ return True
223
+
224
+ except asyncio.TimeoutError as e:
225
+ pocket_logger.error("authentication time out.")
226
+ raise e
227
+
228
+ async def prepare_in_subprocess(self,
229
+ tool_name: Union[str, List[str]],
230
+ thread_id: str = 'default',
231
+ profile: str = 'default',
232
+ *args, **kwargs):
233
+ prepare = await self.server.call_in_subprocess(
234
+ PocketServerOperations.PREPARE_AUTH,
235
+ args,
236
+ {
237
+ 'tool_name': tool_name,
238
+ 'thread_id': thread_id,
239
+ 'profile': profile,
240
+ **kwargs,
241
+ },
309
242
  )
310
- return auth_ctx.to_dict()
311
243
 
312
- async def tool_call(self, tool_name: str, **kwargs) -> str:
313
- """
314
- Executing tool actually
244
+ return prepare
315
245
 
316
- Args:
317
- tool_name(str): tool name to invoke
318
- kwargs(dict): keyword arguments. authentication information is passed through this.
246
+ async def authenticate_in_subprocess(self,
247
+ tool_name: str,
248
+ thread_id: str = 'default',
249
+ profile: str = 'default',
250
+ *args, **kwargs):
251
+ credentials = await self.server.call_in_subprocess(
252
+ PocketServerOperations.AUTHENTICATE,
253
+ args,
254
+ {
255
+ 'tool_name': tool_name,
256
+ 'thread_id': thread_id,
257
+ 'profile': profile,
258
+ **kwargs,
259
+ },
260
+ )
319
261
 
320
- Returns:
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"
262
+ return credentials
263
+
264
+ async def tool_call_in_subprocess(self,
265
+ tool_name: str,
266
+ body: Any,
267
+ thread_id: str = 'default',
268
+ profile: str = 'default',
269
+ *args, **kwargs):
270
+ result = await self.server.call_in_subprocess(
271
+ PocketServerOperations.TOOL_CALL,
272
+ args,
273
+ {
274
+ 'tool_name': tool_name,
275
+ 'body': body,
276
+ 'thread_id': thread_id,
277
+ 'profile': profile,
278
+ **kwargs,
279
+ },
280
+ )
281
+
282
+ return result
329
283
 
330
- def _tool_instance(self, tool_name: str) -> Tool:
331
- return self.tools[tool_name]
284
+ def _teardown_server(self):
285
+ self.server.teardown()
332
286
 
333
287
  def __enter__(self):
334
288
  return self
hyperpocket/prompts.py CHANGED
@@ -1,15 +1,13 @@
1
1
  def pocket_extended_tool_description(description: str):
2
2
  return f'''
3
- This tool performs as stated in the <tool-description></tool-description> XML tag.
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
- This tool requires arguments as follows:
9
- - 'thread_id': The ID of the chat thread where the tool is invoked. Omitted when unknown.
10
- - 'profile': The profile of the user invoking the tool. Inferred from user's messages.
11
- Users can request tools to be invoked in specific personas, which is called a profile.
12
- If the user's profile name can be inferred from the query, pass it as a string in the 'profile' JSON property.
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"]
@@ -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,18 @@ class GitLock(Lock):
154
158
  new_sha = sha
155
159
  break
156
160
  return new_sha
161
+
162
+ def eject_to_path(self, dest_path: pathlib.Path, src_sub_path: str = None):
163
+
164
+ # clone the git repository to the target path
165
+ pocket_logger.info(
166
+ f"Ejecting git: {self.repository_url} @ ref: {self.git_ref} source in path: {src_sub_path} to {dest_path} ..."
167
+ )
168
+ if dest_path.exists():
169
+ shutil.rmtree(dest_path)
170
+
171
+ if src_sub_path:
172
+ src_path = self.toolpkg_path() / src_sub_path
173
+ else:
174
+ src_path = self.toolpkg_path()
175
+ 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, 'r') as f:
17
+ with open(self.path, "r") as f:
18
18
  for line in f:
19
- split = line.strip().split('\t')
19
+ split = line.strip().split("\t")
20
20
  source = split[0]
21
- if source == 'local':
21
+ if source == "local":
22
22
  lock = LocalLock(tool_path=split[1])
23
- elif source == 'git':
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(max_workers=min(len(locks), 100), thread_name_prefix="repository_loader") as executor:
49
- executor.map(lambda l: l.sync(force_update=force_update), locks)
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, 'w') as f:
60
+ with open(self.path, "w") as f:
54
61
  for lock in self.locks.values():
55
- f.write(str(lock) + '\n')
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")