ghoshell-common 0.4.0__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.
- ghoshell_common/__init__.py +0 -0
- ghoshell_common/cli/__init__.py +0 -0
- ghoshell_common/cli/main.py +34 -0
- ghoshell_common/contracts/__init__.py +41 -0
- ghoshell_common/contracts/assets.py +162 -0
- ghoshell_common/contracts/configs.py +200 -0
- ghoshell_common/contracts/logger.py +239 -0
- ghoshell_common/contracts/storage.py +209 -0
- ghoshell_common/contracts/workspace.py +91 -0
- ghoshell_common/entity.py +233 -0
- ghoshell_common/helpers/__init__.py +55 -0
- ghoshell_common/helpers/code_analyser.py +287 -0
- ghoshell_common/helpers/coding.py +21 -0
- ghoshell_common/helpers/dictionary.py +20 -0
- ghoshell_common/helpers/files.py +163 -0
- ghoshell_common/helpers/hashes.py +29 -0
- ghoshell_common/helpers/io.py +19 -0
- ghoshell_common/helpers/modules.py +245 -0
- ghoshell_common/helpers/openai.py +3 -0
- ghoshell_common/helpers/string.py +24 -0
- ghoshell_common/helpers/timeutils.py +35 -0
- ghoshell_common/helpers/toml.py +139 -0
- ghoshell_common/helpers/trans.py +13 -0
- ghoshell_common/helpers/tree_sitter.py +421 -0
- ghoshell_common/helpers/yaml.py +24 -0
- ghoshell_common/identifier.py +171 -0
- ghoshell_common/prompter.py +385 -0
- ghoshell_common-0.4.0.dist-info/METADATA +20 -0
- ghoshell_common-0.4.0.dist-info/RECORD +32 -0
- ghoshell_common-0.4.0.dist-info/WHEEL +4 -0
- ghoshell_common-0.4.0.dist-info/entry_points.txt +4 -0
- ghoshell_common-0.4.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from typing import Optional, Iterable, Protocol, Dict
|
|
3
|
+
import os
|
|
4
|
+
from ghoshell_container import Container, Provider
|
|
5
|
+
import fnmatch
|
|
6
|
+
|
|
7
|
+
__all__ = ['Storage', 'FileStorage', 'DefaultFileStorage', 'FileStorageProvider', 'MemoryStorage']
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Storage(Protocol):
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def sub_storage(self, relative_path: str) -> "Storage":
|
|
14
|
+
"""
|
|
15
|
+
生成一个次级目录下的 storage.
|
|
16
|
+
:param relative_path:
|
|
17
|
+
:return:
|
|
18
|
+
"""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def get(self, file_path: str) -> bytes:
|
|
23
|
+
"""
|
|
24
|
+
获取一个 Storage 路径下一个文件的内容.
|
|
25
|
+
:param file_path: storage 下的一个相对路径.
|
|
26
|
+
"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def remove(self, file_path: str) -> None:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def exists(self, file_path: str) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
if the object exists
|
|
37
|
+
:param file_path: file_path or directory path
|
|
38
|
+
"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def put(self, file_path: str, content: bytes) -> None:
|
|
43
|
+
"""
|
|
44
|
+
保存一个文件的内容到 file_path .
|
|
45
|
+
:param file_path: storage 下的一个相对路径.
|
|
46
|
+
:param content: 文件的内容.
|
|
47
|
+
"""
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def dir(self, prefix_dir: str, recursive: bool, patten: Optional[str] = None) -> Iterable[str]:
|
|
52
|
+
"""
|
|
53
|
+
遍历一个路径下的文件, 返回相对的文件名.
|
|
54
|
+
:param prefix_dir: 目录的相对路径位置.
|
|
55
|
+
:param recursive: 是否递归查找.
|
|
56
|
+
:param patten: 文件的正则规范.
|
|
57
|
+
:return: 多个文件路径名.
|
|
58
|
+
"""
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class FileStorage(Storage, Protocol):
|
|
63
|
+
"""
|
|
64
|
+
Storage Based on FileSystem.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def abspath(self) -> str:
|
|
69
|
+
"""
|
|
70
|
+
storage root directory's absolute path
|
|
71
|
+
"""
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def sub_storage(self, relative_path: str) -> "FileStorage":
|
|
76
|
+
"""
|
|
77
|
+
FileStorage's sub storage is still FileStorage
|
|
78
|
+
"""
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class DefaultFileStorage(FileStorage):
|
|
83
|
+
"""
|
|
84
|
+
FileStorage implementation based on python filesystem.
|
|
85
|
+
Simplest implementation.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(self, dir_: str):
|
|
89
|
+
self._dir: str = os.path.abspath(dir_)
|
|
90
|
+
|
|
91
|
+
def abspath(self) -> str:
|
|
92
|
+
return self._dir
|
|
93
|
+
|
|
94
|
+
def get(self, file_path: str) -> bytes:
|
|
95
|
+
file_path = self._join_file_path(file_path)
|
|
96
|
+
with open(file_path, 'rb') as f:
|
|
97
|
+
return f.read()
|
|
98
|
+
|
|
99
|
+
def remove(self, file_path: str) -> None:
|
|
100
|
+
file_path = self._join_file_path(file_path)
|
|
101
|
+
os.remove(file_path)
|
|
102
|
+
|
|
103
|
+
def exists(self, file_path: str) -> bool:
|
|
104
|
+
file_path = self._join_file_path(file_path)
|
|
105
|
+
return os.path.exists(file_path)
|
|
106
|
+
|
|
107
|
+
def _join_file_path(self, path: str) -> str:
|
|
108
|
+
file_path = os.path.join(self._dir, path)
|
|
109
|
+
file_path = os.path.abspath(file_path)
|
|
110
|
+
if not file_path.startswith(self._dir):
|
|
111
|
+
raise FileNotFoundError(f"file path {path} is not allowed")
|
|
112
|
+
return file_path
|
|
113
|
+
|
|
114
|
+
def put(self, file_path: str, content: bytes) -> None:
|
|
115
|
+
file_path = self._join_file_path(file_path)
|
|
116
|
+
if not file_path.startswith(self._dir):
|
|
117
|
+
raise FileNotFoundError(f"file path {file_path} is not allowed")
|
|
118
|
+
file_dir = os.path.dirname(file_path)
|
|
119
|
+
if not os.path.exists(file_dir):
|
|
120
|
+
os.makedirs(file_dir)
|
|
121
|
+
with open(file_path, 'wb') as f:
|
|
122
|
+
f.write(content)
|
|
123
|
+
|
|
124
|
+
def sub_storage(self, relative_path: str) -> "FileStorage":
|
|
125
|
+
if not relative_path:
|
|
126
|
+
return self
|
|
127
|
+
dir_path = self._join_file_path(relative_path)
|
|
128
|
+
return DefaultFileStorage(dir_path)
|
|
129
|
+
|
|
130
|
+
def dir(self, prefix_dir: str, recursive: bool, patten: Optional[str] = None) -> Iterable[str]:
|
|
131
|
+
dir_path = self._join_file_path(prefix_dir)
|
|
132
|
+
for root, ds, fs in os.walk(dir_path):
|
|
133
|
+
# 遍历单层的文件名.
|
|
134
|
+
for filename in fs:
|
|
135
|
+
if self._match_file_pattern(filename, patten):
|
|
136
|
+
yield filename
|
|
137
|
+
|
|
138
|
+
# 深入遍历目录.
|
|
139
|
+
if recursive and ds:
|
|
140
|
+
for _dir in ds:
|
|
141
|
+
sub_dir_iterator = self.dir(_dir, recursive, patten)
|
|
142
|
+
for filename in sub_dir_iterator:
|
|
143
|
+
yield filename
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def _match_file_pattern(filename: str, pattern: Optional[str]) -> bool:
|
|
147
|
+
if pattern is None:
|
|
148
|
+
return True
|
|
149
|
+
matched = True
|
|
150
|
+
if pattern.startswith('!'):
|
|
151
|
+
matched = False
|
|
152
|
+
pattern = pattern[1:]
|
|
153
|
+
if pattern and fnmatch.fnmatch(filename, pattern):
|
|
154
|
+
return matched
|
|
155
|
+
return not matched
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class MemoryStorage(Storage):
|
|
159
|
+
|
|
160
|
+
def __init__(self, dir_: str):
|
|
161
|
+
self._dir: str = dir_
|
|
162
|
+
self._children: Dict[str, Storage] = {}
|
|
163
|
+
self._data: Dict[str, bytes] = {}
|
|
164
|
+
|
|
165
|
+
def sub_storage(self, relative_path: str) -> "Storage":
|
|
166
|
+
dir_ = os.path.join(self._dir, relative_path)
|
|
167
|
+
if dir_ in self._children:
|
|
168
|
+
return self._children[dir_]
|
|
169
|
+
sub = MemoryStorage(dir_)
|
|
170
|
+
self._children[dir_] = sub
|
|
171
|
+
return sub
|
|
172
|
+
|
|
173
|
+
def get(self, file_path: str) -> bytes:
|
|
174
|
+
_path = os.path.join(self._dir, file_path)
|
|
175
|
+
if _path not in self._data:
|
|
176
|
+
raise FileNotFoundError(f"file {file_path} is not found")
|
|
177
|
+
return self._data[_path]
|
|
178
|
+
|
|
179
|
+
def remove(self, file_path: str) -> None:
|
|
180
|
+
_path = os.path.join(self._dir, file_path)
|
|
181
|
+
if _path in self._data:
|
|
182
|
+
del self._data[_path]
|
|
183
|
+
|
|
184
|
+
def exists(self, file_path: str) -> bool:
|
|
185
|
+
_path = os.path.join(self._dir, file_path)
|
|
186
|
+
return _path in self._data
|
|
187
|
+
|
|
188
|
+
def put(self, file_path: str, content: bytes) -> None:
|
|
189
|
+
_path = os.path.join(self._dir, file_path)
|
|
190
|
+
self._data[_path] = content
|
|
191
|
+
|
|
192
|
+
def dir(self, prefix_dir: str, recursive: bool, patten: Optional[str] = None) -> Iterable[str]:
|
|
193
|
+
for dir_ in self._children.keys():
|
|
194
|
+
yield dir_[len(self._dir):]
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class FileStorageProvider(Provider[FileStorage]):
|
|
198
|
+
|
|
199
|
+
def __init__(self, dir_: str):
|
|
200
|
+
self._dir: str = dir_
|
|
201
|
+
|
|
202
|
+
def singleton(self) -> bool:
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
def aliases(self) -> Iterable[Storage]:
|
|
206
|
+
yield Storage
|
|
207
|
+
|
|
208
|
+
def factory(self, con: Container) -> Optional[Storage]:
|
|
209
|
+
return DefaultFileStorage(self._dir)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from ghoshell_common.contracts.storage import Storage, DefaultFileStorage, MemoryStorage
|
|
6
|
+
from ghoshell_container import Container, Provider
|
|
7
|
+
from os.path import abspath
|
|
8
|
+
import shutil
|
|
9
|
+
|
|
10
|
+
__all__ = ['Workspace', 'LocalWorkspace', 'LocalWorkspaceProvider']
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Workspace(ABC):
|
|
14
|
+
"""
|
|
15
|
+
workspace 目录文件管理.
|
|
16
|
+
用于管理一个项目的本地文件存储.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def root(self) -> Storage:
|
|
21
|
+
"""
|
|
22
|
+
workspace 根 storage.
|
|
23
|
+
"""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def configs(self) -> Storage:
|
|
28
|
+
"""
|
|
29
|
+
配置文件存储路径.
|
|
30
|
+
"""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def runtime(self) -> Storage:
|
|
35
|
+
"""
|
|
36
|
+
运行时数据存储路径.
|
|
37
|
+
"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def assets(self) -> Storage:
|
|
42
|
+
"""
|
|
43
|
+
数据资产存储路径.
|
|
44
|
+
"""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class LocalWorkspace(Workspace):
|
|
49
|
+
|
|
50
|
+
def __init__(self, workspace_dir: str):
|
|
51
|
+
workspace_dir = abspath(workspace_dir)
|
|
52
|
+
self._ws_root_dir = workspace_dir
|
|
53
|
+
if not self._ws_root_dir:
|
|
54
|
+
self._root = MemoryStorage(self._ws_root_dir)
|
|
55
|
+
else:
|
|
56
|
+
self._root = DefaultFileStorage(workspace_dir)
|
|
57
|
+
|
|
58
|
+
def root(self) -> Storage:
|
|
59
|
+
return self._root
|
|
60
|
+
|
|
61
|
+
def configs(self) -> Storage:
|
|
62
|
+
return self._root.sub_storage("configs")
|
|
63
|
+
|
|
64
|
+
def runtime(self) -> Storage:
|
|
65
|
+
return self._root.sub_storage("runtime")
|
|
66
|
+
|
|
67
|
+
def assets(self) -> Storage:
|
|
68
|
+
return self._root.sub_storage("assets")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class LocalWorkspaceProvider(Provider[Workspace]):
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
workspace_dir: str = "",
|
|
76
|
+
stub_dir: Optional[str] = None,
|
|
77
|
+
):
|
|
78
|
+
if workspace_dir == "":
|
|
79
|
+
# 使用脚本运行的路径作为 workspace.
|
|
80
|
+
workspace_dir = abspath(workspace_dir)
|
|
81
|
+
self._ws_dir = workspace_dir
|
|
82
|
+
self._stub_dir = abspath(stub_dir) if stub_dir else None
|
|
83
|
+
|
|
84
|
+
def singleton(self) -> bool:
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
def factory(self, con: Container) -> Optional[Workspace]:
|
|
88
|
+
if self._ws_dir and self._stub_dir and not os.path.exists(self._stub_dir):
|
|
89
|
+
os.makedirs(self._stub_dir)
|
|
90
|
+
shutil.copytree(self._stub_dir, self._ws_dir)
|
|
91
|
+
return LocalWorkspace(self._ws_dir)
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Union, Any, TypeVar, Type, Optional, Protocol
|
|
6
|
+
from typing_extensions import Required, Self, TypedDict
|
|
7
|
+
from types import ModuleType
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
from ghoshell_common.helpers import generate_import_path, import_from_path, parse_import_path_module_and_attr_name
|
|
10
|
+
import inspect
|
|
11
|
+
import pickle
|
|
12
|
+
import base64
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
|
|
17
|
+
'to_entity_meta', 'from_entity_meta', 'get_entity',
|
|
18
|
+
'is_entity_type',
|
|
19
|
+
'EntityMeta',
|
|
20
|
+
'Entity', 'EntityType',
|
|
21
|
+
'EntityClass', 'ModelEntity',
|
|
22
|
+
|
|
23
|
+
'ModelEntityMeta',
|
|
24
|
+
'to_entity_model_meta',
|
|
25
|
+
'from_entity_model_meta',
|
|
26
|
+
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
Experimental Feature
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Entity(Protocol):
|
|
35
|
+
"""
|
|
36
|
+
Experimental interface
|
|
37
|
+
|
|
38
|
+
Protocol to define a class that generate transportable data (EntityMeta)
|
|
39
|
+
Instead of pickle, I want Entity is a light-weighted language independent protocol,
|
|
40
|
+
suite for JSON RPC or JSON API e.t.c.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def __to_entity_meta__(self) -> EntityMeta:
|
|
45
|
+
"""
|
|
46
|
+
self-defined method to generate EntityMeta
|
|
47
|
+
"""
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def __from_entity_meta__(cls, meta: EntityMeta) -> Self:
|
|
53
|
+
"""
|
|
54
|
+
self-defined method to factory instance from EntityMeta
|
|
55
|
+
"""
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class EntityClass(ABC):
|
|
60
|
+
"""
|
|
61
|
+
Abstract Class for Entity
|
|
62
|
+
|
|
63
|
+
Experimental interface
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def __to_entity_meta__(self) -> EntityMeta:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
@abstractmethod
|
|
72
|
+
def __from_entity_meta__(cls, meta: EntityMeta) -> Self:
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ModelEntity(BaseModel, EntityClass, ABC):
|
|
77
|
+
"""
|
|
78
|
+
pydantic Model that implements Entity interface
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __to_entity_meta__(self) -> EntityMeta:
|
|
82
|
+
return EntityMeta(
|
|
83
|
+
type=generate_import_path(self.__class__),
|
|
84
|
+
content=self.model_dump_json(exclude_defaults=True),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def __from_entity_meta__(cls, meta: EntityMeta) -> Self:
|
|
89
|
+
data = json.loads(meta['content'])
|
|
90
|
+
return cls(**data)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class EntityMeta(TypedDict):
|
|
94
|
+
"""
|
|
95
|
+
Experimental feature
|
|
96
|
+
|
|
97
|
+
I want python has a light-weight way to marshal and unmarshal any instance and make it readable if allowed.
|
|
98
|
+
I found so many package-level implements like various kinds of Serializable etc.
|
|
99
|
+
|
|
100
|
+
So, I develop EntityMeta as a wrapper for any kind.
|
|
101
|
+
The EntityType will grow bigger with more marshaller, but do not affect who (me) is using the EntityMeta.
|
|
102
|
+
One day I can replace it with any better way inside the functions (but in-compatible)
|
|
103
|
+
"""
|
|
104
|
+
type: Required[str]
|
|
105
|
+
content: Required[str]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ModelEntityMeta(TypedDict):
|
|
109
|
+
"""
|
|
110
|
+
Experimental feature
|
|
111
|
+
|
|
112
|
+
Data is dict, for pydantic.BaseModel
|
|
113
|
+
"""
|
|
114
|
+
type: Required[str]
|
|
115
|
+
data: Required[dict]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
EntityType = Union[Entity, EntityMeta, BaseModel]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def is_entity_type(value: Any) -> bool:
|
|
122
|
+
return hasattr(value, '__to_entity_meta__')
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def to_entity_model_meta(value: BaseModel) -> ModelEntityMeta:
|
|
126
|
+
type_ = generate_import_path(type(value))
|
|
127
|
+
data = value.model_dump(exclude_defaults=True)
|
|
128
|
+
return ModelEntityMeta(type=type_, data=data)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def from_entity_model_meta(value: ModelEntityMeta) -> BaseModel:
|
|
132
|
+
cls = import_from_path(value['type'])
|
|
133
|
+
return cls(**value['data'])
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def to_entity_meta(value: Union[EntityType, Any]) -> EntityMeta:
|
|
137
|
+
if value is None:
|
|
138
|
+
return EntityMeta(
|
|
139
|
+
type="None",
|
|
140
|
+
content="",
|
|
141
|
+
)
|
|
142
|
+
elif value is True or value is False:
|
|
143
|
+
return EntityMeta(type="bool", content=str(value))
|
|
144
|
+
elif isinstance(value, int):
|
|
145
|
+
return EntityMeta(type="int", content=str(value))
|
|
146
|
+
elif isinstance(value, str):
|
|
147
|
+
return EntityMeta(type="str", content=str(value))
|
|
148
|
+
elif isinstance(value, float):
|
|
149
|
+
return EntityMeta(type="float", content=str(value))
|
|
150
|
+
elif isinstance(value, list):
|
|
151
|
+
content = yaml.safe_dump(value)
|
|
152
|
+
return EntityMeta(type="list", content=content)
|
|
153
|
+
elif isinstance(value, dict):
|
|
154
|
+
content = yaml.safe_dump(value)
|
|
155
|
+
return EntityMeta(type="dict", content=content)
|
|
156
|
+
elif hasattr(value, '__to_entity_meta__'):
|
|
157
|
+
return getattr(value, '__to_entity_meta__')()
|
|
158
|
+
elif isinstance(value, BaseModel):
|
|
159
|
+
return EntityMeta(
|
|
160
|
+
type=generate_import_path(value.__class__),
|
|
161
|
+
content=value.model_dump_json(exclude_defaults=True),
|
|
162
|
+
)
|
|
163
|
+
elif inspect.isfunction(value):
|
|
164
|
+
return EntityMeta(
|
|
165
|
+
type=generate_import_path(value),
|
|
166
|
+
content="",
|
|
167
|
+
)
|
|
168
|
+
elif isinstance(value, BaseModel):
|
|
169
|
+
type_ = generate_import_path(value.__class__)
|
|
170
|
+
content = value.model_dump_json(exclude_defaults=True)
|
|
171
|
+
return EntityMeta(type=type_, content=content)
|
|
172
|
+
else:
|
|
173
|
+
content_bytes = pickle.dumps(value)
|
|
174
|
+
content = base64.encodebytes(content_bytes)
|
|
175
|
+
return EntityMeta(
|
|
176
|
+
type="pickle",
|
|
177
|
+
content=content.decode(),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
T = TypeVar("T")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def get_entity(meta: EntityMeta, expect: Type[T]) -> T:
|
|
185
|
+
if meta is None:
|
|
186
|
+
raise ValueError("EntityMeta cannot be None")
|
|
187
|
+
entity = from_entity_meta(meta)
|
|
188
|
+
if not isinstance(entity, expect):
|
|
189
|
+
raise TypeError(f"Expected entity type {expect} but got {type(entity)}")
|
|
190
|
+
return entity
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def from_entity_meta(meta: EntityMeta, module: Optional[ModuleType] = None) -> Any:
|
|
194
|
+
if meta is None:
|
|
195
|
+
return None
|
|
196
|
+
unmarshal_type = meta['type']
|
|
197
|
+
if unmarshal_type == "None":
|
|
198
|
+
return None
|
|
199
|
+
elif unmarshal_type == "int":
|
|
200
|
+
return int(meta['content'])
|
|
201
|
+
elif unmarshal_type == "str":
|
|
202
|
+
return str(meta['content'])
|
|
203
|
+
elif unmarshal_type == "bool":
|
|
204
|
+
return meta['content'] == "True"
|
|
205
|
+
elif unmarshal_type == "float":
|
|
206
|
+
return float(meta['content'])
|
|
207
|
+
elif unmarshal_type == "list" or unmarshal_type == "dict":
|
|
208
|
+
return yaml.safe_load(meta['content'])
|
|
209
|
+
elif unmarshal_type == 'pickle':
|
|
210
|
+
content = meta['content']
|
|
211
|
+
content_bytes = base64.decodebytes(content.encode())
|
|
212
|
+
return pickle.loads(content_bytes)
|
|
213
|
+
|
|
214
|
+
# raise if import error
|
|
215
|
+
cls = None
|
|
216
|
+
if module:
|
|
217
|
+
module_name, local_name = parse_import_path_module_and_attr_name(unmarshal_type)
|
|
218
|
+
if module_name == module.__name__:
|
|
219
|
+
cls = module.__dict__[local_name]
|
|
220
|
+
if cls is None:
|
|
221
|
+
cls = import_from_path(unmarshal_type)
|
|
222
|
+
|
|
223
|
+
if inspect.isfunction(cls):
|
|
224
|
+
return cls
|
|
225
|
+
# method is prior
|
|
226
|
+
elif hasattr(cls, "__from_entity_meta__"):
|
|
227
|
+
return getattr(cls, "__from_entity_meta__")(meta)
|
|
228
|
+
|
|
229
|
+
elif issubclass(cls, BaseModel):
|
|
230
|
+
data = json.loads(meta["content"])
|
|
231
|
+
return cls(**data)
|
|
232
|
+
|
|
233
|
+
raise TypeError(f"unsupported entity meta type: {unmarshal_type}")
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
from ghoshell_common.helpers.dictionary import (dict_without_none, dict_without_zero)
|
|
3
|
+
from ghoshell_common.helpers.string import camel_to_snake
|
|
4
|
+
from ghoshell_common.helpers.yaml import yaml_pretty_dump, yaml_multiline_string_pipe
|
|
5
|
+
from ghoshell_common.helpers.modules import (
|
|
6
|
+
import_from_path,
|
|
7
|
+
import_class_from_path,
|
|
8
|
+
import_instance_from_path,
|
|
9
|
+
parse_import_path_module_and_attr_name,
|
|
10
|
+
join_import_module_and_spec,
|
|
11
|
+
get_module_attr,
|
|
12
|
+
generate_module_and_attr_name,
|
|
13
|
+
generate_import_path,
|
|
14
|
+
get_module_fullname_from_path,
|
|
15
|
+
Importer,
|
|
16
|
+
is_method_belongs_to_class,
|
|
17
|
+
get_calling_modulename,
|
|
18
|
+
rewrite_module,
|
|
19
|
+
rewrite_module_by_path,
|
|
20
|
+
create_module,
|
|
21
|
+
create_and_bind_module,
|
|
22
|
+
)
|
|
23
|
+
from ghoshell_common.helpers.io import BufferPrint
|
|
24
|
+
from ghoshell_common.helpers.timeutils import Timeleft, timestamp_datetime, timestamp, timestamp_ms
|
|
25
|
+
from ghoshell_common.helpers.hashes import md5, sha1, sha256
|
|
26
|
+
from ghoshell_common.helpers.trans import gettext, ngettext
|
|
27
|
+
|
|
28
|
+
from ghoshell_common.helpers.coding import reflect_module_code, unwrap
|
|
29
|
+
from ghoshell_common.helpers.openai import get_openai_key
|
|
30
|
+
from ghoshell_common.helpers.tree_sitter import tree_sitter_parse, code_syntax_check
|
|
31
|
+
from ghoshell_common.helpers.code_analyser import (
|
|
32
|
+
get_code_interface, get_code_interface_str,
|
|
33
|
+
get_attr_source_from_code, get_attr_interface_from_code,
|
|
34
|
+
)
|
|
35
|
+
from ghoshell_common.helpers.files import generate_directory_tree, list_dir, is_pathname_ignored
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from typing import Callable
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# --- private methods --- #
|
|
42
|
+
def __uuid() -> str:
|
|
43
|
+
from uuid import uuid4
|
|
44
|
+
# keep uuid in 32 chars
|
|
45
|
+
return str(uuid4())
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# --- facade --- #
|
|
49
|
+
|
|
50
|
+
uuid: "Callable[[], str]" = __uuid
|
|
51
|
+
""" patch this method to change global uuid generator"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def uuid_md5() -> str:
|
|
55
|
+
return md5(uuid())
|