ghoshell-common 0.4.0.dev0__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.
File without changes
@@ -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())
@@ -0,0 +1,287 @@
1
+ from typing import List, Union, Dict
2
+ from tree_sitter import (
3
+ Node as TreeSitterNode,
4
+ )
5
+ from ghoshell_common.helpers.tree_sitter import tree_sitter_parse
6
+ from functools import lru_cache
7
+
8
+ __all__ = ['get_code_interface', 'get_code_interface_str', 'get_attr_source_from_code', 'get_attr_interface_from_code']
9
+
10
+
11
+ @lru_cache(maxsize=256)
12
+ def get_code_interface_str(code: str) -> str:
13
+ return "\n\n".join(get_code_interface(code))
14
+
15
+
16
+ def get_code_interface(code: str) -> List[str]:
17
+ try:
18
+ # 解析代码,获取语法树
19
+ tree = tree_sitter_parse(code)
20
+ root_node = tree.root_node
21
+
22
+ # 用于存储解析后的抽象描述
23
+ interfaces = []
24
+ for child in root_node.children:
25
+ child_interface = _get_child_interface_in_depth(code, child, depth=0)
26
+ if child_interface:
27
+ interfaces.append(child_interface)
28
+
29
+ return interfaces
30
+
31
+ except Exception as e:
32
+ # 异常处理
33
+ raise
34
+
35
+
36
+ @lru_cache(maxsize=256)
37
+ def get_attr_source_from_code(code: str) -> Dict[str, str]:
38
+ """
39
+ get function or class attribute code.
40
+ """
41
+ try:
42
+ data = {}
43
+ # 解析代码,获取语法树
44
+ tree = tree_sitter_parse(code)
45
+ root_node = tree.root_node
46
+
47
+ for child in root_node.children:
48
+ name = _get_func_or_class_name(child)
49
+ if name is not None:
50
+ data[name] = child.text.decode()
51
+ return data
52
+ except Exception as e:
53
+ # 异常处理
54
+ raise
55
+
56
+
57
+ def get_attr_interface_from_code(code: str) -> Dict[str, str]:
58
+ """
59
+ get function or class interface map from the code.
60
+ """
61
+ data = {}
62
+ # 解析代码,获取语法树
63
+ tree = tree_sitter_parse(code)
64
+ root_node = tree.root_node
65
+
66
+ for child in root_node.children:
67
+ name = _get_func_or_class_name(child)
68
+ if name is not None:
69
+ interface = _get_child_interface(code, child)
70
+ if interface:
71
+ data[name] = interface
72
+ return data
73
+
74
+
75
+ def _get_func_or_class_name(child: TreeSitterNode) -> Union[str, None]:
76
+ if child.type == 'function_definition':
77
+ # 处理函数定义
78
+ func_name = child.child_by_field_name('name').text.decode()
79
+ return func_name
80
+ elif child.type == 'class_definition':
81
+ class_name = child.child_by_field_name('name').text.decode()
82
+ return class_name
83
+
84
+ elif child.type == 'decorated_definition':
85
+ # 处理装饰器
86
+ return _get_decorator_target_name(child)
87
+ return None
88
+
89
+
90
+ def _get_decorator_target_name(child: TreeSitterNode) -> str:
91
+ definition = child.children[1]
92
+ return _get_func_or_class_name(definition)
93
+
94
+
95
+ def _get_child_interface_in_depth(code: str, node: TreeSitterNode, depth: int) -> str:
96
+ interface = _get_child_interface(code, node)
97
+ if interface and depth > 0:
98
+ intend = ' ' * 4
99
+ splits = [intend + content for content in interface.split('\n')]
100
+ return '\n'.join(splits)
101
+ return interface
102
+
103
+
104
+ def _get_child_interface(code: str, child: TreeSitterNode) -> str:
105
+ if child.type == 'import_statement' or child.type == 'import_from_statement':
106
+ # 如果是引用语句,保持原样
107
+ return child.text.decode()
108
+
109
+ elif child.type == 'expression_statement':
110
+ # 处理表达式语句
111
+ return _get_assignment_interface(code, child)
112
+
113
+ elif child.type == 'function_definition':
114
+ # 处理函数定义
115
+ func_name = child.child_by_field_name('name').text.decode()
116
+ if func_name and not func_name.startswith('_'):
117
+ definition = _get_function_interface(child)
118
+ return definition
119
+ return ""
120
+
121
+ elif child.type == 'class_definition':
122
+ return _get_class_interface(code, child)
123
+
124
+ elif child.type == 'decorated_definition':
125
+ # 处理装饰器
126
+ return _get_decorator_interface(code, child)
127
+ else:
128
+ return ""
129
+
130
+
131
+ def _get_decorator_interface(code: str, child: TreeSitterNode) -> str:
132
+ decorator = child.children[0]
133
+ definition = child.children[1]
134
+ body = _get_child_interface_in_depth(code, definition, 0)
135
+ if body:
136
+ decorator_interfaces = [f"{decorator.text.decode()}", body]
137
+ return "\n".join(decorator_interfaces)
138
+ return ""
139
+
140
+
141
+ def _get_full_definition(node: TreeSitterNode, docstring: bool = True) -> str:
142
+ definition = node.children[0].text.decode() + ' '
143
+ for child in node.children[1:]:
144
+ if child.type != "block":
145
+ text = child.text.decode()
146
+ if child.type == 'identifier':
147
+ definition = definition.rstrip() + ' '
148
+
149
+ definition += text
150
+ else:
151
+ if len(child.children) > 0 and docstring:
152
+ doc_node = child.children[0]
153
+ if doc_node.type == 'expression_statement' and doc_node.children[0].type == "string":
154
+ doc_node = doc_node.children[0]
155
+ doc = "\n".join([line.lstrip() for line in doc_node.text.decode().splitlines()])
156
+ doc = insert_depth_to_code(doc, 1)
157
+ definition += "\n" + doc
158
+ break
159
+ return definition.lstrip()
160
+
161
+
162
+ def _get_assignment_interface(code: str, node: TreeSitterNode) -> str:
163
+ expression = node.children[0]
164
+ if expression.type == 'string':
165
+ return node.text.decode()
166
+
167
+ elif expression.type != 'assignment':
168
+ return ""
169
+
170
+ # 处理赋值语句
171
+ left = expression.children[0]
172
+ right = expression.children[2]
173
+ if left.type == 'identifier':
174
+ var_name = left.text.decode()
175
+ if var_name.startswith('__') and var_name.endswith('__'):
176
+ # 保持魔术变量和类型变量赋值原样
177
+ return node.text.decode()
178
+ elif var_name.startswith('_') or var_name.startswith('__'):
179
+ # 忽略私有变量赋值
180
+ return ""
181
+ else:
182
+ # 如果赋值语句长度小于50个字符,保留原样
183
+ assignment_code = node.text.decode()
184
+ if len(assignment_code) < 500:
185
+ return assignment_code
186
+ else:
187
+ # 折叠其它赋值
188
+ return f"{var_name} = ..."
189
+
190
+
191
+ def _get_class_interface(code, class_node: TreeSitterNode) -> str:
192
+ # 处理类定义
193
+ class_name = class_node.child_by_field_name('name').text.decode()
194
+ if class_name.startswith('_'):
195
+ return ""
196
+ class_definition = _get_full_definition(class_node, docstring=False)
197
+
198
+ class_body_interface = []
199
+ # 处理类中的公共方法和__init__中的公共属性赋值
200
+ property_count = 0
201
+ class_body = class_node.children[-1]
202
+ for class_child in class_body.children:
203
+ if class_child.type == 'decorated_definition':
204
+ # 处理装饰器
205
+ decorator_interface = _get_decorator_interface(code, class_child)
206
+ class_body_interface.append(decorator_interface)
207
+
208
+ elif class_child.type == 'function_definition':
209
+ method_name = class_child.child_by_field_name('name').text.decode()
210
+ if method_name == '__init__':
211
+ init_method_interface = _get_class_init_body_interface(code, class_child)
212
+ class_body_interface.append(init_method_interface)
213
+ elif not method_name.startswith('_'):
214
+ method_interface = _get_function_interface(class_child)
215
+ class_body_interface.append(method_interface)
216
+ property_count += 1
217
+ else:
218
+ interface = _get_child_interface_in_depth(code, class_child, 0)
219
+ if interface:
220
+ class_body_interface.append(interface)
221
+ property_count += 1
222
+
223
+ if property_count == 0:
224
+ class_body_interface.append(" pass")
225
+ class_body = "\n\n".join([itf for itf in class_body_interface if itf])
226
+ class_body = insert_depth_to_code(class_body, 1)
227
+ return class_definition + "\n" + class_body
228
+
229
+
230
+ def insert_depth_to_code(code: str, depth: int) -> str:
231
+ if depth < 1:
232
+ return code
233
+ indent = ' ' * 4 * depth
234
+ lines = []
235
+ for line in code.splitlines():
236
+ if line:
237
+ line = indent + line
238
+ lines.append(line)
239
+ return '\n'.join(lines)
240
+
241
+
242
+ def _get_function_interface(method_node: TreeSitterNode) -> str:
243
+ definition = _get_full_definition(method_node)
244
+ method_interface = [definition, " pass"]
245
+ return "\n".join(method_interface)
246
+
247
+
248
+ def _get_class_init_body_interface(code: str, method_node: TreeSitterNode) -> str:
249
+ # 处理__init__方法中的公共属性赋值
250
+ definition = _get_full_definition(method_node)
251
+ init_method_interface = [definition]
252
+ body_interface = []
253
+ for init_child in method_node.children:
254
+ if init_child.type == 'block':
255
+ for block_child in init_child.children:
256
+ if block_child.type == 'expression_statement':
257
+ expr = block_child.children[0]
258
+ if expr.type == 'assignment':
259
+ left = expr.children[0]
260
+ if left.type == 'attribute':
261
+ attr_name = left.text.decode()
262
+ if not (attr_name.startswith('_') or attr_name.startswith('self._')):
263
+ expr_line = expr.text.decode()
264
+ if len(expr_line) > 50:
265
+ expr_line = expr_line[:50] + "..."
266
+ body_interface.append(f" {expr_line}")
267
+ body = "\n".join(body_interface)
268
+ if body:
269
+ init_method_interface.append(body)
270
+ else:
271
+ init_method_interface.append(" pass")
272
+
273
+ return "\n".join(init_method_interface)
274
+
275
+
276
+ def _get_node_docstring(node: TreeSitterNode) -> str:
277
+ """
278
+ 获取函数或类的 docstring.
279
+ """
280
+ for child in node.children:
281
+ if child.type == 'block':
282
+ for block_child in child.children:
283
+ if block_child.type == 'expression_statement':
284
+ expr = block_child.children[0]
285
+ if expr.type == 'string':
286
+ return expr.text.decode().strip()
287
+ return ""
@@ -0,0 +1,21 @@
1
+ from typing import TypeVar, Union, Callable
2
+ from types import ModuleType
3
+
4
+ T = TypeVar("T")
5
+
6
+
7
+ def unwrap(value: Union[T, Callable[[], T]]) -> T:
8
+ """
9
+ unwrap T from value or a callable
10
+ """
11
+ if isinstance(value, Callable):
12
+ return value()
13
+ return value
14
+
15
+
16
+ def reflect_module_code(module: ModuleType) -> str:
17
+ """
18
+ get the module's code from file
19
+ """
20
+ with open(module.__file__) as f:
21
+ return f.read()
@@ -0,0 +1,20 @@
1
+ def dict_without_none(data: dict) -> dict:
2
+ """
3
+ Removes None values from a dictionary.
4
+ """
5
+ result = {}
6
+ for key, value in data.items():
7
+ if value is not None:
8
+ result[key] = value
9
+ return result
10
+
11
+
12
+ def dict_without_zero(data: dict) -> dict:
13
+ """
14
+ Removes zero values from a dictionary.
15
+ """
16
+ result = {}
17
+ for key, value in data.items():
18
+ if not value:
19
+ result[key] = value
20
+ return result