hyperpocket 0.1.10__py3-none-any.whl → 0.2.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. hyperpocket/__init__.py +4 -4
  2. hyperpocket/auth/__init__.py +12 -7
  3. hyperpocket/auth/calendly/oauth2_handler.py +24 -17
  4. hyperpocket/auth/calendly/oauth2_schema.py +3 -1
  5. hyperpocket/auth/context.py +2 -1
  6. hyperpocket/auth/github/oauth2_handler.py +13 -8
  7. hyperpocket/auth/github/token_handler.py +27 -21
  8. hyperpocket/auth/google/context.py +1 -3
  9. hyperpocket/auth/google/oauth2_context.py +1 -1
  10. hyperpocket/auth/google/oauth2_handler.py +22 -17
  11. hyperpocket/auth/gumloop/token_context.py +1 -4
  12. hyperpocket/auth/gumloop/token_handler.py +48 -20
  13. hyperpocket/auth/gumloop/token_schema.py +2 -1
  14. hyperpocket/auth/handler.py +21 -6
  15. hyperpocket/auth/linear/token_context.py +2 -5
  16. hyperpocket/auth/linear/token_handler.py +45 -21
  17. hyperpocket/auth/notion/context.py +2 -2
  18. hyperpocket/auth/notion/token_context.py +2 -4
  19. hyperpocket/auth/notion/token_handler.py +45 -21
  20. hyperpocket/auth/notion/token_schema.py +0 -1
  21. hyperpocket/auth/reddit/oauth2_handler.py +9 -10
  22. hyperpocket/auth/reddit/oauth2_schema.py +0 -2
  23. hyperpocket/auth/schema.py +4 -1
  24. hyperpocket/auth/slack/oauth2_context.py +3 -1
  25. hyperpocket/auth/slack/oauth2_handler.py +55 -35
  26. hyperpocket/auth/slack/token_context.py +2 -4
  27. hyperpocket/auth/slack/token_handler.py +42 -19
  28. hyperpocket/builtin.py +4 -2
  29. hyperpocket/cli/__main__.py +4 -2
  30. hyperpocket/cli/auth.py +59 -28
  31. hyperpocket/cli/codegen/auth/auth_context_template.py +3 -2
  32. hyperpocket/cli/codegen/auth/auth_token_context_template.py +3 -2
  33. hyperpocket/cli/codegen/auth/auth_token_handler_template.py +6 -5
  34. hyperpocket/cli/codegen/auth/auth_token_schema_template.py +3 -2
  35. hyperpocket/cli/codegen/auth/server_auth_template.py +3 -2
  36. hyperpocket/cli/pull.py +5 -5
  37. hyperpocket/config/__init__.py +3 -8
  38. hyperpocket/config/auth.py +3 -1
  39. hyperpocket/config/logger.py +20 -15
  40. hyperpocket/config/session.py +4 -2
  41. hyperpocket/config/settings.py +19 -2
  42. hyperpocket/futures/__init__.py +1 -1
  43. hyperpocket/futures/futurestore.py +3 -2
  44. hyperpocket/pocket_auth.py +171 -84
  45. hyperpocket/pocket_core.py +51 -33
  46. hyperpocket/pocket_main.py +122 -93
  47. hyperpocket/prompts.py +2 -2
  48. hyperpocket/repository/__init__.py +1 -1
  49. hyperpocket/repository/lock.py +47 -33
  50. hyperpocket/repository/lockfile.py +2 -2
  51. hyperpocket/repository/repository.py +1 -1
  52. hyperpocket/server/__init__.py +1 -1
  53. hyperpocket/server/auth/github.py +2 -1
  54. hyperpocket/server/auth/linear.py +1 -3
  55. hyperpocket/server/auth/notion.py +2 -5
  56. hyperpocket/server/auth/slack.py +1 -3
  57. hyperpocket/server/auth/token.py +17 -11
  58. hyperpocket/server/proxy.py +29 -13
  59. hyperpocket/server/server.py +75 -31
  60. hyperpocket/server/tool/dto/script.py +15 -10
  61. hyperpocket/server/tool/wasm.py +14 -11
  62. hyperpocket/session/__init__.py +6 -2
  63. hyperpocket/session/in_memory.py +44 -24
  64. hyperpocket/session/interface.py +42 -24
  65. hyperpocket/session/redis.py +48 -31
  66. hyperpocket/tool/__init__.py +10 -10
  67. hyperpocket/tool/function/__init__.py +1 -5
  68. hyperpocket/tool/function/annotation.py +11 -9
  69. hyperpocket/tool/function/tool.py +37 -27
  70. hyperpocket/tool/tool.py +59 -36
  71. hyperpocket/tool/wasm/__init__.py +1 -1
  72. hyperpocket/tool/wasm/browser.py +15 -10
  73. hyperpocket/tool/wasm/invoker.py +16 -16
  74. hyperpocket/tool/wasm/script.py +27 -14
  75. hyperpocket/tool/wasm/templates/__init__.py +22 -15
  76. hyperpocket/tool/wasm/templates/node.py +2 -2
  77. hyperpocket/tool/wasm/templates/python.py +2 -2
  78. hyperpocket/tool/wasm/tool.py +27 -14
  79. hyperpocket/tool_like.py +3 -3
  80. hyperpocket/util/__init__.py +1 -1
  81. hyperpocket/util/extract_func_param_desc_from_docstring.py +23 -7
  82. hyperpocket/util/find_all_leaf_class_in_package.py +4 -3
  83. hyperpocket/util/find_all_subclass_in_package.py +4 -2
  84. hyperpocket/util/flatten_json_schema.py +10 -6
  85. hyperpocket/util/function_to_model.py +33 -12
  86. hyperpocket/util/get_objects_from_subpackage.py +1 -1
  87. hyperpocket/util/json_schema_to_model.py +14 -5
  88. {hyperpocket-0.1.10.dist-info → hyperpocket-0.2.1.dist-info}/METADATA +11 -5
  89. hyperpocket-0.2.1.dist-info/RECORD +137 -0
  90. hyperpocket-0.1.10.dist-info/RECORD +0 -137
  91. {hyperpocket-0.1.10.dist-info → hyperpocket-0.2.1.dist-info}/WHEEL +0 -0
  92. {hyperpocket-0.1.10.dist-info → hyperpocket-0.2.1.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
- from typing import Any, Optional, List, Union
2
+ from concurrent.futures import ThreadPoolExecutor
3
+ from typing import Any, List, Optional, Union
3
4
 
4
5
  from hyperpocket.config import pocket_logger
5
6
  from hyperpocket.pocket_auth import PocketAuth
@@ -12,30 +13,38 @@ class Pocket(object):
12
13
  server: PocketServer
13
14
  core: PocketCore
14
15
 
15
- def __init__(self,
16
- tools: list[ToolLike],
17
- auth: PocketAuth = None,
18
- lockfile_path: Optional[str] = None,
19
- force_update: bool = False,
20
- use_profile: bool = False):
16
+ def __init__(
17
+ self,
18
+ tools: list[ToolLike],
19
+ auth: PocketAuth = None,
20
+ lockfile_path: Optional[str] = None,
21
+ force_update: bool = False,
22
+ use_profile: bool = False,
23
+ ):
21
24
  self.use_profile = use_profile
22
-
23
25
  self.core = PocketCore(
24
26
  tools=tools,
25
27
  auth=auth,
26
28
  lockfile_path=lockfile_path,
27
- force_update=force_update
29
+ force_update=force_update,
28
30
  )
29
-
30
31
  self.server = PocketServer()
31
32
  self.server.run(self.core)
32
-
33
- def invoke(self,
34
- tool_name: str,
35
- body: Any,
36
- thread_id: str = 'default',
37
- profile: str = 'default',
38
- *args, **kwargs) -> str:
33
+ try:
34
+ self.server.wait_initialized()
35
+ except Exception as e:
36
+ pocket_logger.error(f"Failed to initialize pocket server. error : {e}")
37
+ self._teardown_server()
38
+
39
+ def invoke(
40
+ self,
41
+ tool_name: str,
42
+ body: Any,
43
+ thread_id: str = "default",
44
+ profile: str = "default",
45
+ *args,
46
+ **kwargs,
47
+ ) -> str:
39
48
  """
40
49
  Invoke Tool synchronously
41
50
 
@@ -48,15 +57,19 @@ class Pocket(object):
48
57
  Returns:
49
58
  str: tool result
50
59
  """
51
- result = asyncio.run(self.ainvoke(tool_name, body, thread_id, profile, *args, **kwargs))
52
- return result
60
+ return asyncio.run(
61
+ self.ainvoke(tool_name, body, thread_id, profile, *args, **kwargs)
62
+ )
53
63
 
54
- async def ainvoke(self,
55
- tool_name: str,
56
- body: Any,
57
- thread_id: str = 'default',
58
- profile: str = 'default',
59
- *args, **kwargs) -> str:
64
+ async def ainvoke(
65
+ self,
66
+ tool_name: str,
67
+ body: Any,
68
+ thread_id: str = "default",
69
+ profile: str = "default",
70
+ *args,
71
+ **kwargs,
72
+ ) -> str:
60
73
  """
61
74
  Invoke Tool asynchronously
62
75
 
@@ -79,12 +92,15 @@ class Pocket(object):
79
92
  )
80
93
  return result
81
94
 
82
- def invoke_with_state(self,
83
- tool_name: str,
84
- body: Any,
85
- thread_id: str = 'default',
86
- profile: str = 'default',
87
- *args, **kwargs) -> tuple[str, bool]:
95
+ def invoke_with_state(
96
+ self,
97
+ tool_name: str,
98
+ body: Any,
99
+ thread_id: str = "default",
100
+ profile: str = "default",
101
+ *args,
102
+ **kwargs,
103
+ ) -> tuple[str, bool]:
88
104
  """
89
105
  Invoke Tool with state synchronously
90
106
  State indicates whether this tool is paused or not.
@@ -101,27 +117,33 @@ class Pocket(object):
101
117
  """
102
118
  try:
103
119
  loop = asyncio.new_event_loop()
104
- result = loop.run_until_complete(
105
- self.ainvoke_with_state(tool_name, body, thread_id, profile, *args, **kwargs))
106
-
107
- except RuntimeError as e:
108
- pocket_logger.warning("Can't execute sync def in event loop. use nest-asyncio")
120
+ except RuntimeError:
121
+ pocket_logger.warning(
122
+ "Can't execute sync def in event loop. use nest-asyncio"
123
+ )
109
124
 
110
125
  import nest_asyncio
126
+
111
127
  loop = asyncio.new_event_loop()
112
128
  nest_asyncio.apply(loop=loop)
113
129
 
114
- result = loop.run_until_complete(
115
- self.ainvoke_with_state(tool_name, body, thread_id, profile, *args, **kwargs))
130
+ result = loop.run_until_complete(
131
+ self.ainvoke_with_state(
132
+ tool_name, body, thread_id, profile, *args, **kwargs
133
+ )
134
+ )
116
135
 
117
136
  return result
118
137
 
119
- async def ainvoke_with_state(self,
120
- tool_name: str,
121
- body: Any,
122
- thread_id: str = 'default',
123
- profile: str = 'default',
124
- *args, **kwargs) -> tuple[str, bool]:
138
+ async def ainvoke_with_state(
139
+ self,
140
+ tool_name: str,
141
+ body: Any,
142
+ thread_id: str = "default",
143
+ profile: str = "default",
144
+ *args,
145
+ **kwargs,
146
+ ) -> tuple[str, bool]:
125
147
  """
126
148
  Invoke Tool with state synchronously
127
149
  State indicates whether this tool is paused or not.
@@ -140,10 +162,10 @@ class Pocket(object):
140
162
  PocketServerOperations.CALL,
141
163
  args,
142
164
  {
143
- 'tool_name': tool_name,
144
- 'body': body,
145
- 'thread_id': thread_id,
146
- 'profile': profile,
165
+ "tool_name": tool_name,
166
+ "body": body,
167
+ "thread_id": thread_id,
168
+ "profile": profile,
147
169
  **kwargs,
148
170
  },
149
171
  )
@@ -152,10 +174,11 @@ class Pocket(object):
152
174
 
153
175
  return result, paused
154
176
 
155
- async def initialize_tool_auth(self,
156
- thread_id: str = 'default',
157
- profile: str = 'default',
158
- ) -> dict[str, str]:
177
+ async def initialize_tool_auth(
178
+ self,
179
+ thread_id: str = "default",
180
+ profile: str = "default",
181
+ ) -> dict[str, str]:
159
182
  """
160
183
  Initialize authentication for all tools.
161
184
 
@@ -177,19 +200,17 @@ class Pocket(object):
177
200
  for provider, tools in tool_by_provider.items():
178
201
  tool_name_list = [tool.name for tool in tools]
179
202
  prepare = await self.prepare_in_subprocess(
180
- tool_name=tool_name_list,
181
- thread_id=thread_id,
182
- profile=profile)
203
+ tool_name=tool_name_list, thread_id=thread_id, profile=profile
204
+ )
183
205
 
184
206
  if prepare is not None:
185
207
  prepare_list[provider] = prepare
186
208
 
187
209
  return prepare_list
188
210
 
189
- async def wait_tool_auth(self,
190
- thread_id: str = 'default',
191
- profile: str = 'default'
192
- ) -> bool:
211
+ async def wait_tool_auth(
212
+ self, thread_id: str = "default", profile: str = "default"
213
+ ) -> bool:
193
214
  """
194
215
  Wait until all tool authentications are completed.
195
216
 
@@ -214,10 +235,9 @@ class Pocket(object):
214
235
 
215
236
  waiting_futures.append(
216
237
  self.authenticate_in_subprocess(
217
- tool_name=tools[0].name,
218
- thread_id=thread_id,
219
- profile=profile
220
- ))
238
+ tool_name=tools[0].name, thread_id=thread_id, profile=profile
239
+ )
240
+ )
221
241
 
222
242
  await asyncio.gather(*waiting_futures)
223
243
 
@@ -227,56 +247,65 @@ class Pocket(object):
227
247
  pocket_logger.error("authentication time out.")
228
248
  raise e
229
249
 
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):
250
+ async def prepare_in_subprocess(
251
+ self,
252
+ tool_name: Union[str, List[str]],
253
+ thread_id: str = "default",
254
+ profile: str = "default",
255
+ *args,
256
+ **kwargs,
257
+ ):
235
258
  prepare = await self.server.call_in_subprocess(
236
259
  PocketServerOperations.PREPARE_AUTH,
237
260
  args,
238
261
  {
239
- 'tool_name': tool_name,
240
- 'thread_id': thread_id,
241
- 'profile': profile,
262
+ "tool_name": tool_name,
263
+ "thread_id": thread_id,
264
+ "profile": profile,
242
265
  **kwargs,
243
266
  },
244
267
  )
245
268
 
246
269
  return prepare
247
270
 
248
- async def authenticate_in_subprocess(self,
249
- tool_name: str,
250
- thread_id: str = 'default',
251
- profile: str = 'default',
252
- *args, **kwargs):
271
+ async def authenticate_in_subprocess(
272
+ self,
273
+ tool_name: str,
274
+ thread_id: str = "default",
275
+ profile: str = "default",
276
+ *args,
277
+ **kwargs,
278
+ ):
253
279
  credentials = await self.server.call_in_subprocess(
254
280
  PocketServerOperations.AUTHENTICATE,
255
281
  args,
256
282
  {
257
- 'tool_name': tool_name,
258
- 'thread_id': thread_id,
259
- 'profile': profile,
283
+ "tool_name": tool_name,
284
+ "thread_id": thread_id,
285
+ "profile": profile,
260
286
  **kwargs,
261
287
  },
262
288
  )
263
289
 
264
290
  return credentials
265
291
 
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):
292
+ async def tool_call_in_subprocess(
293
+ self,
294
+ tool_name: str,
295
+ body: Any,
296
+ thread_id: str = "default",
297
+ profile: str = "default",
298
+ *args,
299
+ **kwargs,
300
+ ):
272
301
  result = await self.server.call_in_subprocess(
273
302
  PocketServerOperations.TOOL_CALL,
274
303
  args,
275
304
  {
276
- 'tool_name': tool_name,
277
- 'body': body,
278
- 'thread_id': thread_id,
279
- 'profile': profile,
305
+ "tool_name": tool_name,
306
+ "body": body,
307
+ "thread_id": thread_id,
308
+ "profile": profile,
280
309
  **kwargs,
281
310
  },
282
311
  )
@@ -290,17 +319,17 @@ class Pocket(object):
290
319
  return self
291
320
 
292
321
  def __exit__(self, exc_type, exc_val, exc_tb):
293
- if self.__dict__.get('server'):
322
+ if self.__dict__.get("server"):
294
323
  self.server.teardown()
295
324
 
296
325
  def __del__(self):
297
- if self.__dict__.get('server'):
326
+ if self.__dict__.get("server"):
298
327
  self.server.teardown()
299
328
 
300
329
  def __getstate__(self):
301
330
  state = self.__dict__.copy()
302
- if 'server' in state:
303
- del state['server']
331
+ if "server" in state:
332
+ del state["server"]
304
333
  return state
305
334
 
306
335
  def __setstate__(self, state):
hyperpocket/prompts.py CHANGED
@@ -1,5 +1,5 @@
1
1
  def pocket_extended_tool_description(description: str):
2
- return f'''
2
+ return f"""
3
3
  This tool functions as described in the <tool-description> XML tag:
4
4
  <tool-description>
5
5
  {description}
@@ -10,4 +10,4 @@ Arguments required:
10
10
  - 'profile': The profile of the user invoking the tool. (infer from messages; omit if unknown).
11
11
  Users can request tools to be invoked in specific personas.
12
12
  - 'body': Tool argument passed as a JSON 'body'.
13
- '''
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, eject
3
+ from hyperpocket.repository.repository import eject, pull, sync
4
4
 
5
5
  __all__ = ["Lock", "Lockfile", "pull", "sync", "eject"]
@@ -1,12 +1,13 @@
1
1
  import abc
2
2
  import pathlib
3
3
  import shutil
4
- from typing import Optional, Tuple
4
+ from typing import Optional, Tuple, ClassVar
5
5
 
6
6
  import git
7
7
  from pydantic import BaseModel, Field
8
+ from pydantic.fields import ModelPrivateAttr
8
9
 
9
- from hyperpocket.config import settings, pocket_logger
10
+ from hyperpocket.config import pocket_logger, settings
10
11
 
11
12
 
12
13
  class Lock(BaseModel, abc.ABC):
@@ -34,17 +35,19 @@ class Lock(BaseModel, abc.ABC):
34
35
 
35
36
 
36
37
  class LocalLock(Lock):
37
- tool_source: str = Field(default='local')
38
+ tool_source: str = Field(default="local")
38
39
  tool_path: str
39
40
 
40
41
  def __init__(self, tool_path: str):
41
- super().__init__(tool_source="local", tool_path=str(pathlib.Path(tool_path).resolve()))
42
+ super().__init__(
43
+ tool_source="local", tool_path=str(pathlib.Path(tool_path).resolve())
44
+ )
42
45
 
43
46
  def __str__(self):
44
47
  return f"local\t{self.tool_path}"
45
48
 
46
49
  def key(self):
47
- return self.tool_source, self.tool_path.rstrip('/')
50
+ return self.tool_source, self.tool_path.rstrip("/")
48
51
 
49
52
  def sync(self, **kwargs):
50
53
  pocket_logger.info(f"Syncing path: {self.tool_path} ...")
@@ -55,11 +58,12 @@ class LocalLock(Lock):
55
58
 
56
59
  def toolpkg_path(self) -> pathlib.Path:
57
60
  pocket_pkgs = settings.toolpkg_path
58
- return pocket_pkgs / 'local' / self.tool_path[1:]
61
+ return pocket_pkgs / "local" / self.tool_path[1:]
59
62
 
60
63
 
61
64
  class GitLock(Lock):
62
- tool_source: str = 'git'
65
+ _remote_cache: ClassVar[dict[str, dict[str, str]]]
66
+ tool_source: str = "git"
63
67
  repository_url: str
64
68
  git_ref: str
65
69
  ref_sha: Optional[str] = None
@@ -68,17 +72,17 @@ class GitLock(Lock):
68
72
  return f"git\t{self.repository_url}\t{self.git_ref}\t{self.ref_sha}"
69
73
 
70
74
  def key(self):
71
- return self.tool_source, self.repository_url.rstrip('/'), self.git_ref
75
+ return self.tool_source, self.repository_url.rstrip("/"), self.git_ref
72
76
 
73
77
  def toolpkg_path(self) -> pathlib.Path:
74
78
  if not self.ref_sha:
75
79
  raise ValueError("ref_sha is not set")
76
80
  cleansed_url = self.repository_url
77
- if self.repository_url.startswith('http://'):
81
+ if self.repository_url.startswith("http://"):
78
82
  cleansed_url = self.repository_url[7:]
79
- elif self.repository_url.startswith('https://'):
83
+ elif self.repository_url.startswith("https://"):
80
84
  cleansed_url = self.repository_url[8:]
81
- elif self.repository_url.startswith('git@'):
85
+ elif self.repository_url.startswith("git@"):
82
86
  cleansed_url = self.repository_url[4:]
83
87
  return settings.toolpkg_path / cleansed_url / self.ref_sha
84
88
 
@@ -91,12 +95,16 @@ class GitLock(Lock):
91
95
  to align the local repository with the remote version.
92
96
  """
93
97
  try:
94
- pocket_logger.info(f"Syncing git: {self.repository_url} @ ref: {self.git_ref} ...")
98
+ pocket_logger.info(
99
+ f"Syncing git: {self.repository_url} @ ref: {self.git_ref} ..."
100
+ )
95
101
 
96
102
  # get new sha from refs
97
103
  new_sha = self._get_new_sha_if_exists_in_remote()
98
104
  if new_sha is None:
99
- raise ValueError(f"Could not find ref {self.git_ref} in {self.repository_url}")
105
+ raise ValueError(
106
+ f"Could not find ref {self.git_ref} in {self.repository_url}"
107
+ )
100
108
 
101
109
  # check self.ref_sha should be updated
102
110
  if self.ref_sha != new_sha:
@@ -111,10 +119,10 @@ class GitLock(Lock):
111
119
  # init git repo in local and set origin url
112
120
  repo = git.Repo.init(pkg_version_path)
113
121
  try:
114
- remote = repo.remote('origin')
122
+ remote = repo.remote("origin")
115
123
  remote.set_url(self.repository_url)
116
124
  except ValueError:
117
- remote = repo.create_remote('origin', self.repository_url)
125
+ remote = repo.create_remote("origin", self.repository_url)
118
126
 
119
127
  # check current local commit include new_sha
120
128
  # if not included, fetch and do hard reset
@@ -126,10 +134,12 @@ class GitLock(Lock):
126
134
  if exist_sha is None or exist_sha != self.ref_sha:
127
135
  remote.fetch(depth=1, refspec=self.ref_sha)
128
136
  repo.git.checkout(new_sha)
129
- repo.git.reset('--hard', new_sha)
130
- repo.git.clean('-fd')
137
+ repo.git.reset("--hard", new_sha)
138
+ repo.git.clean("-fd")
131
139
  except Exception as e:
132
- pocket_logger.error(f"failed to sync git: {self.repository_url} @ ref: {self.git_ref}. reason : {e}")
140
+ pocket_logger.error(
141
+ f"failed to sync git: {self.repository_url} @ ref: {self.git_ref}. reason : {e}"
142
+ )
133
143
  raise e
134
144
 
135
145
  def _get_new_sha_if_exists_in_remote(self):
@@ -143,8 +153,8 @@ class GitLock(Lock):
143
153
  refs = git.cmd.Git().ls_remote(self.repository_url)
144
154
 
145
155
  new_sha = None
146
- for r in refs.split('\n'):
147
- sha, ref = r.split('\t')
156
+ for r in refs.split("\n"):
157
+ sha, ref = r.split("\t")
148
158
  if sha == self.ref_sha:
149
159
  new_sha = sha
150
160
  break
@@ -161,16 +171,19 @@ class GitLock(Lock):
161
171
 
162
172
  @classmethod
163
173
  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
+ if not hasattr(cls, "_remote_cache"):
175
+ cls._remote_cache = {}
176
+ if cls._remote_cache.get(repo_url) is None:
177
+ ls_lists = git.cmd.Git().ls_remote(repo_url)
178
+
179
+ branches = {}
180
+ for line in ls_lists.split("\n"):
181
+ sha, ref = line.split("\t")
182
+ if ref.startswith("refs/heads/"):
183
+ branch_name = ref.replace("refs/heads/", "")
184
+ branches[branch_name] = sha
185
+ cls._remote_cache[repo_url] = branches
186
+ return cls._remote_cache[repo_url]
174
187
 
175
188
  @classmethod
176
189
  def parse_repo_url(cls, repo_url: str) -> Tuple[str, str, str]:
@@ -195,7 +208,7 @@ class GitLock(Lock):
195
208
  # Parse base repo URL and remaining path
196
209
  tree_index = repo_path_list.index("tree")
197
210
  base_repo = f"https://github.com/{'/'.join(repo_path_list[:tree_index])}"
198
- sub_path = repo_path_list[tree_index + 1:]
211
+ sub_path = repo_path_list[tree_index + 1 :]
199
212
 
200
213
  # Fetch branch information
201
214
  branches = cls.get_git_branches(base_repo)
@@ -204,14 +217,15 @@ class GitLock(Lock):
204
217
  for idx in range(1, len(sub_path) + 1):
205
218
  branch_name = "/".join(sub_path[:idx])
206
219
  if branch_name in branches:
207
- directory_path = "/".join(sub_path[idx:]) if idx < len(sub_path) else None
220
+ directory_path = (
221
+ "/".join(sub_path[idx:]) if idx < len(sub_path) else None
222
+ )
208
223
  return base_repo, branch_name, directory_path
209
224
 
210
225
  # If no valid branch is found, raise an error
211
226
  raise ValueError("Branch not found in repository")
212
227
 
213
228
  def eject_to_path(self, dest_path: pathlib.Path, src_sub_path: str = None):
214
-
215
229
  # clone the git repository to the target path
216
230
  pocket_logger.info(
217
231
  f"Ejecting git: {self.repository_url} @ ref: {self.git_ref} source in path: {src_sub_path} to {dest_path} ..."
@@ -1,7 +1,7 @@
1
1
  import pathlib
2
2
  from concurrent.futures.thread import ThreadPoolExecutor
3
3
 
4
- from hyperpocket.repository.lock import Lock, LocalLock, GitLock
4
+ from hyperpocket.repository.lock import GitLock, LocalLock, Lock
5
5
 
6
6
 
7
7
  class Lockfile:
@@ -51,7 +51,7 @@ class Lockfile:
51
51
  else:
52
52
  locks = list(self.locks.values())
53
53
  with ThreadPoolExecutor(
54
- max_workers=min(len(locks) + 1, 100), thread_name_prefix="repository_loader"
54
+ max_workers=min(len(locks) + 1, 100), thread_name_prefix="repository_loader"
55
55
  ) as executor:
56
56
  executor.map(lambda lock: lock.sync(force_update=force_update), locks)
57
57
  self.write()
@@ -1,6 +1,6 @@
1
1
  import pathlib
2
2
 
3
- from hyperpocket.repository.lock import LocalLock, GitLock
3
+ from hyperpocket.repository.lock import GitLock, LocalLock
4
4
  from hyperpocket.repository.lockfile import Lockfile
5
5
 
6
6
 
@@ -1,3 +1,3 @@
1
1
  from hyperpocket.server.proxy import add_callback_proxy
2
2
 
3
- __all__ = ["add_callback_proxy"]
3
+ __all__ = ["add_callback_proxy"]
@@ -15,6 +15,7 @@ async def github_oauth2_callback(request: Request, state: str, code: str):
15
15
 
16
16
  return HTMLResponse(content="success")
17
17
 
18
+
18
19
  @github_auth_router.get("/token/callback")
19
20
  async def github_token_callback(request: Request, state: str, token: str):
20
21
  try:
@@ -22,4 +23,4 @@ async def github_token_callback(request: Request, state: str, token: str):
22
23
  except ValueError:
23
24
  return HTMLResponse(content="failed")
24
25
 
25
- return HTMLResponse(content="success")
26
+ return HTMLResponse(content="success")
@@ -3,9 +3,7 @@ from starlette.responses import HTMLResponse
3
3
 
4
4
  from hyperpocket.futures import FutureStore
5
5
 
6
- linear_auth_router = APIRouter(
7
- prefix="/linear"
8
- )
6
+ linear_auth_router = APIRouter(prefix="/linear")
9
7
 
10
8
 
11
9
  @linear_auth_router.get("/token/callback")
@@ -1,12 +1,9 @@
1
-
2
1
  from fastapi import APIRouter
3
2
  from starlette.responses import HTMLResponse
4
3
 
5
4
  from hyperpocket.futures import FutureStore
6
5
 
7
- notion_auth_router = APIRouter(
8
- prefix="/notion"
9
- )
6
+ notion_auth_router = APIRouter(prefix="/notion")
10
7
 
11
8
 
12
9
  @notion_auth_router.get("/token/callback")
@@ -16,4 +13,4 @@ async def notion_token_callback(state: str, token: str):
16
13
  except ValueError:
17
14
  return HTMLResponse(content="failed")
18
15
 
19
- return HTMLResponse(content="success")
16
+ return HTMLResponse(content="success")
@@ -3,9 +3,7 @@ from starlette.responses import HTMLResponse
3
3
 
4
4
  from hyperpocket.futures import FutureStore
5
5
 
6
- slack_auth_router = APIRouter(
7
- prefix="/slack"
8
- )
6
+ slack_auth_router = APIRouter(prefix="/slack")
9
7
 
10
8
 
11
9
  @slack_auth_router.get("/oauth2/callback")