hyperpocket 0.4.5__py3-none-any.whl → 0.5.1__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.
@@ -1,101 +1,91 @@
1
1
  import asyncio
2
- import enum
3
- import uuid
2
+ import threading
4
3
  from typing import Optional
5
4
 
6
- import multiprocess as mp
7
5
  from fastapi import FastAPI
8
6
  from uvicorn import Config, Server
9
7
 
10
8
  from hyperpocket.config import config, pocket_logger
11
- from hyperpocket.pocket_core import PocketCore
12
9
  from hyperpocket.server.auth import auth_router
13
10
 
14
11
 
15
- class PocketServerOperations(enum.Enum):
16
- CALL = "call"
17
- PREPARE_AUTH = "prepare_auth"
18
- AUTHENTICATE = "authenticate"
19
- TOOL_CALL = "tool_call"
20
- PLUG_CORE = "plug_core"
21
-
22
-
23
12
  class PocketServer(object):
13
+ _instance: "PocketServer" = None
14
+
24
15
  fastapi_app: Optional[FastAPI]
25
16
  main_server: Optional[Server]
26
17
  internal_server_port: int
27
18
  proxy_server: Optional[Server]
28
19
  proxy_port: int
29
- pipe: mp.Pipe
30
- process: mp.Process
31
- future_store: dict[str, asyncio.Future]
32
- torn_down: bool = False
33
- _uidset: set
34
- _cores: dict[str, PocketCore]
20
+
21
+ thread: threading.Thread
22
+ _initialized: bool
23
+ _initialization_event: threading.Event
24
+ _initialization_error: Optional[Exception]
35
25
 
36
26
  def __init__(self):
37
27
  self._initialized = False
28
+ self._initialization_event = threading.Event()
29
+ self._initialization_error = None
30
+
38
31
  self.internal_server_port = config().internal_server_port
39
32
  self.proxy_port = config().public_server_port
40
- self._uidset = set()
41
- self.future_store = dict()
42
33
  self.fastapi_app = None
43
34
  self.main_server = None
44
- self._cores = dict()
45
35
 
46
- # should be called in child process
47
- def _plug_core(self, pocket_uid: str, pocket_core: PocketCore, *_a, **_kw):
48
- # extend http routers from each docks
49
- dock_routes = set([str(r) for r in self.fastapi_app.routes])
50
-
51
- for dock in pocket_core.docks:
52
- # check duplicated api route
53
- dock_route = set([str(r) for r in dock.router.routes])
54
- if dock_route in dock_routes:
55
- continue
36
+ @classmethod
37
+ def get_instance(cls):
38
+ if cls._instance is None:
39
+ cls._instance = cls()
40
+ cls._instance.run()
41
+ return cls._instance
56
42
 
57
- dock_routes.update(dock_route)
58
- self.fastapi_app.include_router(dock.router)
43
+ def teardown(self):
44
+ try:
45
+ loop = asyncio.get_event_loop()
46
+ except RuntimeError:
47
+ loop = asyncio.new_event_loop()
48
+ asyncio.set_event_loop(loop)
49
+ loop.run_until_complete(self._teardown())
50
+ loop.close()
51
+
52
+ async def _teardown(self):
53
+ if self.thread.is_alive():
54
+ if self.main_server:
55
+ self.main_server.should_exit = True
56
+ if self.proxy_server:
57
+ self.proxy_server.should_exit = True
58
+
59
+ while self.thread.is_alive():
60
+ await asyncio.sleep(0)
59
61
 
60
- # keep pocket core
61
- self._cores[pocket_uid] = pocket_core
62
+ self.thread.join()
62
63
 
63
- # should be called in parent process
64
- async def plug_core(self, pocket_uid: str, pocket_core: PocketCore):
65
- await self.call_in_subprocess(
66
- PocketServerOperations.PLUG_CORE,
67
- pocket_uid,
68
- tuple(),
69
- {
70
- "pocket_uid": pocket_uid,
71
- "pocket_core": pocket_core,
72
- },
64
+ def run(self):
65
+ self.thread = threading.Thread(
66
+ target=self._run,
67
+ daemon=True
73
68
  )
69
+ self.thread.start()
70
+ self._wait_initialized()
74
71
 
75
- @classmethod
76
- def get_instance_and_refcnt_up(cls, uid: str):
77
- if cls.__dict__.get("_instance") is None:
78
- cls._instance = cls()
79
- cls._instance.run()
80
- cls._instance.refcnt_up(uid)
81
- return cls._instance
72
+ def _run(self):
73
+ try:
74
+ # init process
75
+ self.fastapi_app = self._create_fastapi_app()
76
+ self.main_server = self._create_main_server(self.fastapi_app)
77
+ self.proxy_server = self._create_https_proxy_server()
78
+ self._report_initialized()
82
79
 
83
- def refcnt_up(self, uid: str):
84
- self._uidset.add(uid)
80
+ loop = asyncio.new_event_loop()
81
+ error = loop.run_until_complete(self._run_async())
82
+ loop.close()
85
83
 
86
- def refcnt_down(self, uid: str):
87
- if uid in self._uidset:
88
- self._uidset.remove(uid)
89
- if len(self._uidset) == 0:
90
- self._instance.teardown()
84
+ if error:
85
+ raise error
91
86
 
92
- def teardown(self):
93
- # @XXX(seokju) is it ok to call this method both in __del__ and __exit__?
94
- if self.torn_down:
95
- return
96
- self.torn_down = True
97
- self.process.terminate()
98
- self.process.join()
87
+ except Exception as error:
88
+ self._report_initialized(error)
99
89
 
100
90
  async def _run_async(self):
101
91
  try:
@@ -104,175 +94,34 @@ class PocketServer(object):
104
94
  self.proxy_server.serve()
105
95
  if self.proxy_server is not None
106
96
  else asyncio.sleep(0),
107
- self.poll_in_child(),
97
+ return_exceptions=True
108
98
  )
99
+ return None
109
100
  except Exception as e:
110
101
  pocket_logger.warning(f"failed to start pocket server. error : {e}")
111
-
112
- async def poll_in_child(self):
113
- loop = asyncio.get_running_loop()
114
- _, conn = self.pipe
115
-
116
- async def _acall(_conn, _future_uid, _core_uid, _args, _kwargs):
117
- try:
118
- core = self._cores[_core_uid]
119
- result = await core.acall(*_args, **_kwargs)
120
- error = None
121
- except Exception as e:
122
- pocket_logger.error(f"failed in pocket subprocess. error: {e}")
123
- result = None
124
- error = e
125
- _conn.send((_future_uid, result, error))
126
-
127
- async def _prepare(_conn, _future_uid, _core_uid, a, kw):
128
- try:
129
- core = self._cores[_core_uid]
130
- result = core.prepare_auth(*a, **kw)
131
- error = None
132
- except Exception as e:
133
- pocket_logger.error(
134
- f"failed to prepare in pocket subprocess. error: {e}"
135
- )
136
- result = None
137
- error = e
138
-
139
- _conn.send((_future_uid, result, error))
140
-
141
- async def _authenticate(_conn, _future_uid, _core_uid, a, kw):
142
- try:
143
- core = self._cores[_core_uid]
144
- result = await core.authenticate(*a, **kw)
145
- error = None
146
- except Exception as e:
147
- pocket_logger.error(
148
- f"failed to authenticate in pocket subprocess. error: {e}"
149
- )
150
- result = None
151
- error = e
152
-
153
- _conn.send((_future_uid, result, error))
154
-
155
- async def _tool_call(_conn, _future_uid, _core_uid, a, kw):
156
- try:
157
- core = self._cores[_core_uid]
158
- result = await core.tool_call(*a, **kw)
159
- error = None
160
- except Exception as e:
161
- pocket_logger.error(
162
- f"failed to tool_call in pocket subprocess. error: {e}"
163
- )
164
- result = None
165
- error = e
166
-
167
- _conn.send((_future_uid, result, error))
168
-
169
- async def _plug_core(_conn, _future_uid, _core_uid, a, kw):
170
- try:
171
- self._plug_core(*a, **kw)
172
- _conn.send((_future_uid, None, None))
173
- except Exception as e:
174
- pocket_logger.error(
175
- f"failed to plug_core in pocket subprocess. error: {e}"
176
- )
177
- _conn.send((_future_uid, None, e))
178
-
179
- while True:
180
- if conn.poll():
181
- op, future_uid, core_uid, args, kwargs = conn.recv()
182
- if op == PocketServerOperations.CALL:
183
- loop.create_task(_acall(conn, future_uid, core_uid, args, kwargs))
184
- elif op == PocketServerOperations.PREPARE_AUTH:
185
- loop.create_task(_prepare(conn, future_uid, core_uid, args, kwargs))
186
- elif op == PocketServerOperations.AUTHENTICATE:
187
- loop.create_task(_authenticate(conn, future_uid, core_uid, args, kwargs))
188
- elif op == PocketServerOperations.TOOL_CALL:
189
- loop.create_task(_tool_call(conn, future_uid, core_uid, args, kwargs))
190
- elif op == PocketServerOperations.PLUG_CORE:
191
- loop.create_task(_plug_core(conn, future_uid, core_uid, args, kwargs))
192
- else:
193
- raise ValueError(f"Unknown operation: {op}")
194
- else:
195
- await asyncio.sleep(0)
196
-
197
- def send_in_parent(self, op: PocketServerOperations, pocket_uid: str, args: tuple, kwargs: dict):
198
- conn, _ = self.pipe
199
- future_uid = str(uuid.uuid4())
200
- message = (op, future_uid, pocket_uid, args, kwargs)
201
- future = asyncio.Future()
202
- self.future_store[future_uid] = future
203
- conn.send(message)
204
- return future_uid
205
-
206
- async def poll_in_parent(self):
207
- conn, _ = self.pipe
208
- while True:
209
- if conn.poll():
210
- uid, result, error = conn.recv()
211
- future = self.future_store[uid]
212
- if error:
213
- future.set_exception(error)
214
- else:
215
- future.set_result(result)
216
- break
217
- else:
218
- await asyncio.sleep(0)
219
-
220
- async def call_in_subprocess(
221
- self, op: PocketServerOperations, pocket_uid: str, args: tuple, kwargs: dict
222
- ):
223
- uid = self.send_in_parent(op, pocket_uid, args, kwargs)
224
- loop = asyncio.get_running_loop()
225
- loop.create_task(self.poll_in_parent())
226
- return await self.future_store[uid]
227
-
228
- def run(self):
229
- self._set_mp_start_method()
230
-
231
- error_queue = mp.Queue()
232
- self.pipe = mp.Pipe()
233
- self.process = mp.Process(
234
- target=self._run
235
- )
236
- self.process.start() # process start
237
-
238
- if not error_queue.empty():
239
- error_message = error_queue.get()
240
- raise error_message
102
+ return e
241
103
 
242
104
  def _report_initialized(self, error: Optional[Exception] = None):
243
- _, conn = self.pipe
244
- conn.send(('server-initialization', error,))
105
+ if error:
106
+ pocket_logger.warning(f"Server initialization failed: {error}")
107
+ self._initialization_error = error
108
+ self._initialization_event.set()
245
109
 
246
- def wait_initialized(self):
110
+ def _wait_initialized(self):
247
111
  if self._initialized:
248
112
  return
249
- conn, _ = self.pipe
250
- while True:
251
- if conn.poll():
252
- _, error = conn.recv()
253
- if error:
254
- raise error
255
- break
256
- self._initialized = True
257
113
 
258
- def _run(self):
259
- try:
260
- # init process
261
- self.fastapi_app = self._create_fastapi_app()
262
- self.main_server = self._create_main_server(self.fastapi_app)
263
- self.proxy_server = self._create_https_proxy_server()
264
- self._report_initialized()
114
+ self._initialization_event.wait()
115
+ if self._initialization_error:
116
+ raise self._initialization_error
265
117
 
266
- loop = asyncio.new_event_loop()
267
- loop.run_until_complete(self._run_async())
268
- loop.close()
269
- except Exception as error:
270
- self._report_initialized(error)
118
+ self._initialization_event.clear()
119
+ self._initialized = True
271
120
 
272
121
  def _create_fastapi_app(self) -> FastAPI:
273
122
  app = FastAPI()
274
- app.include_router(auth_router)
275
123
  app.add_api_route("/health", lambda: {"status": "ok"}, methods=["GET"])
124
+ app.include_router(auth_router)
276
125
  return app
277
126
 
278
127
  def _create_main_server(self, app: FastAPI) -> Server:
@@ -307,21 +156,3 @@ class PocketServer(object):
307
156
  )
308
157
  proxy_server = Server(_config)
309
158
  return proxy_server
310
-
311
- def _set_mp_start_method(self):
312
- import platform
313
-
314
- os_name = platform.system()
315
- if os_name == "Windows":
316
- mp.set_start_method("spawn", force=True)
317
- pocket_logger.debug("Process start method set to 'spawn' for Windows.")
318
- elif os_name == "Darwin": # macOS
319
- mp.set_start_method("spawn", force=True)
320
- pocket_logger.debug("Process start method set to 'spawn' for macOS.")
321
- elif os_name == "Linux":
322
- mp.set_start_method("fork", force=True)
323
- pocket_logger.debug("Process start method set to 'fork' for Linux.")
324
- else:
325
- pocket_logger.debug(
326
- f"Unrecognized OS: {os_name}. Default start method will be used."
327
- )
@@ -19,49 +19,42 @@ InMemorySessionValue = BaseSessionValue
19
19
  class InMemorySessionStorage(
20
20
  SessionStorageInterface[InMemorySessionKey, InMemorySessionValue]
21
21
  ):
22
- _instance = None
23
- _is_initialized = False
24
-
25
- def __new__(cls, *args, **kwargs):
26
- if cls._instance is None:
27
- cls._instance = super(InMemorySessionStorage, cls).__new__(cls)
28
- cls._instance._is_initialized = False
29
- return cls._instance
22
+ storage = {}
30
23
 
31
24
  def __init__(self, session_config: SessionConfigInMemory):
32
- if not self._is_initialized:
33
- super().__init__()
34
- self.storage: Dict[InMemorySessionKey, InMemorySessionValue] = {}
35
- self._is_initialized = True
25
+ super().__init__()
36
26
 
37
27
  @classmethod
38
28
  def session_storage_type(cls) -> SessionType:
39
29
  return SessionType.IN_MEMORY
40
30
 
31
+ @classmethod
41
32
  def get(
42
- self, auth_provider: AuthProvider, thread_id: str, profile: str, **kwargs
33
+ cls, auth_provider: AuthProvider, thread_id: str, profile: str, **kwargs
43
34
  ) -> Optional[V]:
44
- key = self._make_session_key(auth_provider.name, thread_id, profile)
45
- return self.storage.get(key, None)
35
+ key = cls._make_session_key(auth_provider.name, thread_id, profile)
36
+ return cls.storage.get(key, None)
46
37
 
38
+ @classmethod
47
39
  def get_by_thread_id(
48
- self, thread_id: str, auth_provider: Optional[AuthProvider] = None, **kwargs
40
+ cls, thread_id: str, auth_provider: Optional[AuthProvider] = None, **kwargs
49
41
  ) -> List[V]:
50
42
  if auth_provider is None:
51
43
  auth_provider_name = ".*"
52
44
  else:
53
45
  auth_provider_name = auth_provider.name
54
46
 
55
- pattern = rf"{self._make_session_key(auth_provider_name, thread_id, '.*')}"
47
+ pattern = rf"{cls._make_session_key(auth_provider_name, thread_id, '.*')}"
56
48
  compiled = re.compile(pattern)
57
49
 
58
50
  session_list = [
59
- value for key, value in self.storage.items() if compiled.match(key)
51
+ value for key, value in cls.storage.items() if compiled.match(key)
60
52
  ]
61
53
  return session_list
62
54
 
55
+ @classmethod
63
56
  def set(
64
- self,
57
+ cls,
65
58
  auth_provider: AuthProvider,
66
59
  thread_id: str,
67
60
  profile: str,
@@ -71,8 +64,8 @@ class InMemorySessionStorage(
71
64
  is_auth_scope_universal: bool,
72
65
  **kwargs,
73
66
  ) -> V:
74
- key = self._make_session_key(auth_provider.name, thread_id, profile)
75
- session = self._make_session(
67
+ key = cls._make_session_key(auth_provider.name, thread_id, profile)
68
+ session = cls._make_session(
76
69
  auth_provider_name=auth_provider.name,
77
70
  auth_scopes=auth_scopes,
78
71
  auth_resolve_uid=auth_resolve_uid,
@@ -80,15 +73,16 @@ class InMemorySessionStorage(
80
73
  is_auth_scope_universal=is_auth_scope_universal,
81
74
  )
82
75
 
83
- self.storage[key] = session
76
+ cls.storage[key] = session
84
77
  return session
85
78
 
79
+ @classmethod
86
80
  def delete(
87
- self, auth_provider: AuthProvider, thread_id: str, profile: str, **kwargs
81
+ cls, auth_provider: AuthProvider, thread_id: str, profile: str, **kwargs
88
82
  ) -> bool:
89
- key = self._make_session_key(auth_provider.name, thread_id, profile)
90
- if key in self.storage:
91
- self.storage.pop(key)
83
+ key = cls._make_session_key(auth_provider.name, thread_id, profile)
84
+ if key in cls.storage:
85
+ cls.storage.pop(key)
92
86
  return True
93
87
 
94
88
  return False
@@ -1,10 +1,9 @@
1
1
  from hyperpocket.tool.function import from_dock, from_func, function_tool
2
- from hyperpocket.tool.tool import Tool, ToolAuth, ToolRequest
2
+ from hyperpocket.tool.tool import Tool, ToolAuth
3
3
 
4
4
  __all__ = [
5
5
  "Tool",
6
6
  "ToolAuth",
7
- "ToolRequest",
8
7
  "from_dock",
9
8
  "from_func",
10
9
  "function_tool",
@@ -1,34 +1,15 @@
1
1
  import abc
2
- from typing import Any
2
+ from typing import TypeVar, Generic
3
3
 
4
- from fastapi import APIRouter
5
-
6
- from hyperpocket.tool import ToolRequest
7
4
  from hyperpocket.tool.function import FunctionTool
8
5
 
6
+ DockToolLike = TypeVar("DockToolLike")
9
7
 
10
- class Dock(abc.ABC):
11
- _tool_requests: list[ToolRequest]
12
- _dock_http_router: APIRouter
13
- _dock_vars: dict[str, str]
14
-
15
- def __init__(self, dock_vars: dict[str, str] = None):
16
- self._dock_http_router = APIRouter()
17
- self._tool_requests = []
18
- self._dock_vars = dock_vars if dock_vars is not None else {}
19
-
20
- @property
21
- def router(self):
22
- return self._dock_http_router
23
-
24
- @abc.abstractmethod
25
- def plug(self, req_like: Any, **kwargs):
26
- raise NotImplementedError
27
8
 
9
+ class Dock(Generic[DockToolLike], abc.ABC):
28
10
  @abc.abstractmethod
29
- def tools(self) -> list[FunctionTool]:
11
+ def dock(self, tool_like: DockToolLike, *args, **kwargs) -> FunctionTool:
30
12
  raise NotImplementedError
31
13
 
32
- @abc.abstractmethod
33
- async def teardown(self):
34
- raise NotImplementedError
14
+ def __call__(self, *args, **kwargs):
15
+ return self.dock(*args, **kwargs)
@@ -121,6 +121,9 @@ class FunctionTool(Tool):
121
121
  if tool_vars is None:
122
122
  tool_vars = dict()
123
123
 
124
+ if afunc is None and inspect.iscoroutinefunction(func):
125
+ afunc = func
126
+
124
127
  if isinstance(func, FunctionTool):
125
128
  if tool_vars is not None:
126
129
  func.override_tool_variables(tool_vars)
@@ -133,7 +136,7 @@ class FunctionTool(Tool):
133
136
  raise ValueError("FunctionTool can only be created from a callable")
134
137
 
135
138
  name = name or (func and func.__name__) or (afunc and afunc.__name__)
136
- description = description or (func and func.__doc__) or (afunc and func.__doc__)
139
+ description = description or (func and func.__doc__) or (afunc and func.__doc__) or name
137
140
  schema = json_schema or \
138
141
  (func and function_to_model(func).model_json_schema()) or \
139
142
  (afunc and function_to_model(afunc).model_json_schema())
hyperpocket/tool/tool.py CHANGED
@@ -17,52 +17,20 @@ class ToolAuth(BaseModel):
17
17
  scopes: list[str] = Field(
18
18
  default=None,
19
19
  description="Indicates which authentication provider’s credentials are required to invoke the tool. "
20
- "If auth_provider is not specified, the tool is considered to require no authentication.",
20
+ "If auth_provider is not specified, the tool is considered to require no authentication.",
21
21
  )
22
22
  auth_provider: Optional[AuthProvider] = Field(
23
23
  default=None,
24
24
  description="Specifies which authentication handler should be used when invoking the tool. "
25
- "If auth_handler is not specified, the default handler of the authentication provider will be used.",
25
+ "If auth_handler is not specified, the default handler of the authentication provider will be used.",
26
26
  )
27
27
  auth_handler: Optional[str] = Field(
28
28
  default=None,
29
29
  description="Indicates the authentication scopes required to invoke the tool. "
30
- "If authentication is not performed or the authentication handler is non-scoped, the value should be None.",
30
+ "If authentication is not performed or the authentication handler is non-scoped, the value should be None.",
31
31
  )
32
32
 
33
33
 
34
- class ToolRequest(abc.ABC):
35
- postprocessings: Optional[list[Callable]] = None
36
- overridden_tool_vars: dict[str, str] = Field(
37
- default_factory=dict, description="overridden tool variables"
38
- )
39
-
40
- @abc.abstractmethod
41
- def __str__(self):
42
- raise NotImplementedError
43
-
44
- def add_postprocessing(self, postprocessing: Callable):
45
- if self.postprocessings is None:
46
- self.postprocessings = [postprocessing]
47
- else:
48
- self.postprocessings.append(postprocessing)
49
-
50
- def __or__(self, other: Callable):
51
- self.add_postprocessing(other)
52
- return self
53
-
54
- def with_postprocessings(self, postprocessings: list[Callable]):
55
- if self.postprocessings is None:
56
- self.postprocessings = postprocessings
57
- else:
58
- self.postprocessings.extend(postprocessings)
59
- return self
60
-
61
- def override_tool_variables(self, override_vars: dict[str, str]) -> "ToolRequest":
62
- self.overridden_tool_vars = override_vars
63
- return self
64
-
65
-
66
34
  class Tool(BaseModel, abc.ABC):
67
35
  """
68
36
  Pocket Tool Interface
@@ -191,3 +159,6 @@ class Tool(BaseModel, abc.ABC):
191
159
  else:
192
160
  self.postprocessings.extend(postprocessings)
193
161
  return self
162
+
163
+ def __str__(self) -> str:
164
+ return self.name
hyperpocket/tool_like.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from typing import Callable, Union
2
2
 
3
- from hyperpocket.tool import Tool, ToolRequest
4
- from hyperpocket.tool.dock import Dock
3
+ from hyperpocket.tool import Tool
5
4
 
6
- ToolLike = Union[Tool, str, tuple, Callable, ToolRequest, Dock]
5
+ ToolLike = Union[Tool, str, tuple, Callable]
@@ -0,0 +1,63 @@
1
+ import git
2
+
3
+
4
+ class GitParser:
5
+ git_branches_cache: dict[str, dict[str, str]] = {}
6
+
7
+ @classmethod
8
+ def get_git_branches(cls, repo_url) -> dict[str, str]:
9
+ if cls.git_branches_cache.get(repo_url) is None:
10
+ ls_lists = git.cmd.Git().ls_remote(repo_url)
11
+
12
+ branches = {}
13
+ for line in ls_lists.split("\n"):
14
+ sha, ref = line.split("\t")
15
+ if ref == "HEAD":
16
+ branches["HEAD"] = sha
17
+ elif ref.startswith("refs/heads/"):
18
+ branch_name = ref.replace("refs/heads/", "")
19
+ branches[branch_name] = sha
20
+ cls.git_branches_cache[repo_url] = branches
21
+ return cls.git_branches_cache[repo_url]
22
+
23
+ @classmethod
24
+ def parse_repo_url(cls, repo_url: str) -> tuple[str, str, str, str]:
25
+ """
26
+ Parses a GitHub repository URL with optional branch and path information.
27
+
28
+ Returns:
29
+ Tuple[str, str, str, str]: base_repo, branch_name, directory_path, git_sha
30
+ """
31
+ if not repo_url.startswith("https://github.com/"):
32
+ raise AttributeError("Only GitHub URLs are supported")
33
+
34
+ # Remove the base URL and split the path
35
+ repo_path = repo_url.removeprefix("https://github.com/")
36
+ repo_path_list = repo_path.split("/")
37
+
38
+ # Check if the URL contains 'tree' (indicating branch and sub-path information)
39
+ if "tree" not in repo_path_list:
40
+ # If no 'tree', return the full repository URL
41
+ git_sha = cls.get_git_branches(repo_url)["HEAD"]
42
+ return repo_url, "HEAD", "", git_sha
43
+
44
+ # Parse base repo URL and remaining path
45
+ tree_index = repo_path_list.index("tree")
46
+ base_repo = f"https://github.com/{'/'.join(repo_path_list[:tree_index])}"
47
+ sub_path = repo_path_list[tree_index + 1:]
48
+
49
+ # Fetch branch information
50
+ branches = cls.get_git_branches(base_repo)
51
+
52
+ # Find branch and sub-directory path
53
+ for idx in range(1, len(sub_path) + 1):
54
+ branch_name = "/".join(sub_path[:idx])
55
+ if branch_name in branches:
56
+ git_sha = branches[branch_name]
57
+ directory_path = (
58
+ "/".join(sub_path[idx:]) if idx < len(sub_path) else None
59
+ )
60
+ return base_repo, branch_name, directory_path, git_sha
61
+
62
+ # If no valid branch is found, raise an error
63
+ raise ValueError("Branch not found in repository")
@@ -0,0 +1,5 @@
1
+ import hashlib
2
+
3
+
4
+ def short_hashing_str(text, length=10):
5
+ return hashlib.sha256(text.encode()).hexdigest()[:length]