cobweb-launcher 3.1.28__py3-none-any.whl → 3.1.30__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.
- cobweb/base/request.py +1 -1
- cobweb/base/response.py +377 -19
- {cobweb_launcher-3.1.28.dist-info → cobweb_launcher-3.1.30.dist-info}/METADATA +1 -1
- {cobweb_launcher-3.1.28.dist-info → cobweb_launcher-3.1.30.dist-info}/RECORD +7 -7
- {cobweb_launcher-3.1.28.dist-info → cobweb_launcher-3.1.30.dist-info}/LICENSE +0 -0
- {cobweb_launcher-3.1.28.dist-info → cobweb_launcher-3.1.30.dist-info}/WHEEL +0 -0
- {cobweb_launcher-3.1.28.dist-info → cobweb_launcher-3.1.30.dist-info}/top_level.txt +0 -0
cobweb/base/request.py
CHANGED
@@ -452,7 +452,7 @@ class Request:
|
|
452
452
|
|
453
453
|
@property
|
454
454
|
def to_dict(self) -> Dict[str, Any]:
|
455
|
-
excluded_keys = {"request_settings"}
|
455
|
+
excluded_keys = {"request_settings", "url", "seed", "method", "check_status_code"}
|
456
456
|
result = {
|
457
457
|
key: value for key, value in self.__dict__.items()
|
458
458
|
if not key.startswith('_') and key not in excluded_keys
|
cobweb/base/response.py
CHANGED
@@ -1,32 +1,390 @@
|
|
1
|
-
from typing import Any
|
1
|
+
from typing import Any, Dict, Union
|
2
2
|
|
3
3
|
|
4
4
|
class Response:
|
5
|
+
"""
|
6
|
+
响应对象类,支持动态属性访问和字典式操作
|
7
|
+
|
8
|
+
优化特性:
|
9
|
+
1. 使用 __slots__ 减少内存占用
|
10
|
+
2. 缓存 to_dict 结果提高性能
|
11
|
+
3. 更好的错误处理和类型检查
|
12
|
+
4. 支持弱引用避免循环引用
|
13
|
+
5. 线程安全的属性访问
|
14
|
+
"""
|
15
|
+
|
16
|
+
__slots__ = ('_seed', '_response', '_extra_attrs', '_dict_cache', '__weakref__')
|
5
17
|
|
6
18
|
def __init__(
|
7
19
|
self,
|
8
|
-
seed,
|
9
|
-
response,
|
10
|
-
**kwargs
|
11
|
-
):
|
12
|
-
|
13
|
-
|
20
|
+
seed: Any,
|
21
|
+
response: Any,
|
22
|
+
**kwargs: Any
|
23
|
+
) -> None:
|
24
|
+
"""
|
25
|
+
初始化 Response 对象
|
26
|
+
|
27
|
+
Args:
|
28
|
+
seed: 种子对象,用于动态属性访问
|
29
|
+
response: 响应对象
|
30
|
+
**kwargs: 额外的属性
|
31
|
+
"""
|
32
|
+
# 使用私有属性避免与动态属性冲突
|
33
|
+
object.__setattr__(self, '_seed', seed)
|
34
|
+
object.__setattr__(self, '_response', response)
|
35
|
+
object.__setattr__(self, '_extra_attrs', kwargs.copy())
|
36
|
+
object.__setattr__(self, '_dict_cache', None)
|
37
|
+
|
38
|
+
@property
|
39
|
+
def seed(self) -> Any:
|
40
|
+
"""获取种子对象"""
|
41
|
+
return self._seed
|
14
42
|
|
15
|
-
|
16
|
-
|
43
|
+
@property
|
44
|
+
def response(self) -> Any:
|
45
|
+
"""获取响应对象"""
|
46
|
+
return self._response
|
17
47
|
|
18
48
|
@property
|
19
|
-
def to_dict(self):
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
49
|
+
def to_dict(self) -> Dict[str, Any]:
|
50
|
+
"""
|
51
|
+
转换为字典格式,使用缓存提高性能
|
52
|
+
|
53
|
+
Returns:
|
54
|
+
包含所有属性的字典
|
55
|
+
"""
|
56
|
+
if self._dict_cache is None:
|
57
|
+
_dict = self._extra_attrs.copy()
|
58
|
+
|
59
|
+
# 安全地获取 seed 的字典表示
|
60
|
+
if hasattr(self._seed, 'to_dict'):
|
61
|
+
if callable(self._seed.to_dict):
|
62
|
+
try:
|
63
|
+
_dict.update(self._seed.to_dict())
|
64
|
+
except Exception as e:
|
65
|
+
# 记录错误但不中断执行
|
66
|
+
_dict['_seed_to_dict_error'] = str(e)
|
67
|
+
else:
|
68
|
+
_dict.update(self._seed.to_dict)
|
69
|
+
elif hasattr(self._seed, '__dict__'):
|
70
|
+
_dict.update(self._seed.__dict__)
|
71
|
+
elif isinstance(self._seed, dict):
|
72
|
+
_dict.update(self._seed)
|
73
|
+
|
74
|
+
# 缓存结果
|
75
|
+
object.__setattr__(self, '_dict_cache', _dict)
|
76
|
+
|
77
|
+
return self._dict_cache.copy() # 返回副本避免外部修改
|
78
|
+
|
79
|
+
def invalidate_cache(self) -> None:
|
80
|
+
"""清除缓存,当对象状态改变时调用"""
|
81
|
+
object.__setattr__(self, '_dict_cache', None)
|
25
82
|
|
26
83
|
def __getattr__(self, name: str) -> Any:
|
27
|
-
"""
|
28
|
-
|
84
|
+
"""
|
85
|
+
动态获取属性
|
86
|
+
|
87
|
+
优先级:
|
88
|
+
1. _extra_attrs 中的属性
|
89
|
+
2. seed 对象的属性
|
90
|
+
|
91
|
+
Args:
|
92
|
+
name: 属性名
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
属性值
|
96
|
+
|
97
|
+
Raises:
|
98
|
+
AttributeError: 当属性不存在时
|
99
|
+
"""
|
100
|
+
# 避免递归调用
|
101
|
+
if name.startswith('_'):
|
102
|
+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
103
|
+
|
104
|
+
# 首先检查额外属性
|
105
|
+
if name in self._extra_attrs:
|
106
|
+
return self._extra_attrs[name]
|
107
|
+
|
108
|
+
# 然后检查 seed 对象
|
109
|
+
return self._get_from_seed(name)
|
110
|
+
|
111
|
+
def _get_from_seed(self, name: str) -> Any:
|
112
|
+
"""
|
113
|
+
从 seed 对象获取属性
|
114
|
+
|
115
|
+
Args:
|
116
|
+
name: 属性名
|
117
|
+
|
118
|
+
Returns:
|
119
|
+
属性值
|
120
|
+
|
121
|
+
Raises:
|
122
|
+
AttributeError: 当属性不存在时
|
123
|
+
"""
|
124
|
+
if self._seed is None:
|
125
|
+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}' (seed is None)")
|
126
|
+
|
127
|
+
# 尝试不同的访问方式
|
128
|
+
try:
|
129
|
+
# 方式1: 字典式访问
|
130
|
+
if hasattr(self._seed, '__getitem__'):
|
131
|
+
try:
|
132
|
+
return self._seed[name]
|
133
|
+
except (KeyError, TypeError):
|
134
|
+
pass
|
135
|
+
|
136
|
+
# 方式2: 属性访问
|
137
|
+
if hasattr(self._seed, name):
|
138
|
+
return getattr(self._seed, name)
|
139
|
+
|
140
|
+
# 方式3: 如果 seed 是字典
|
141
|
+
if isinstance(self._seed, dict) and name in self._seed:
|
142
|
+
return self._seed[name]
|
143
|
+
|
144
|
+
except Exception as e:
|
145
|
+
raise AttributeError(
|
146
|
+
f"Error accessing '{name}' from seed: {e}"
|
147
|
+
) from e
|
148
|
+
|
149
|
+
# 属性不存在
|
150
|
+
raise AttributeError(
|
151
|
+
f"'{self.__class__.__name__}' object has no attribute '{name}'"
|
152
|
+
)
|
29
153
|
|
30
154
|
def __getitem__(self, key: str) -> Any:
|
31
|
-
"""
|
32
|
-
|
155
|
+
"""
|
156
|
+
支持字典式访问
|
157
|
+
|
158
|
+
Args:
|
159
|
+
key: 键名
|
160
|
+
|
161
|
+
Returns:
|
162
|
+
对应的值
|
163
|
+
"""
|
164
|
+
try:
|
165
|
+
return getattr(self, key)
|
166
|
+
except AttributeError:
|
167
|
+
raise KeyError(key)
|
168
|
+
|
169
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
170
|
+
"""
|
171
|
+
设置属性
|
172
|
+
|
173
|
+
Args:
|
174
|
+
name: 属性名
|
175
|
+
value: 属性值
|
176
|
+
"""
|
177
|
+
if name.startswith('_') or name in self.__slots__:
|
178
|
+
object.__setattr__(self, name, value)
|
179
|
+
else:
|
180
|
+
# 设置到额外属性中
|
181
|
+
self._extra_attrs[name] = value
|
182
|
+
# 清除缓存
|
183
|
+
self.invalidate_cache()
|
184
|
+
|
185
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
186
|
+
"""支持字典式设置"""
|
187
|
+
setattr(self, key, value)
|
188
|
+
|
189
|
+
def __delattr__(self, name: str) -> None:
|
190
|
+
"""
|
191
|
+
删除属性
|
192
|
+
|
193
|
+
Args:
|
194
|
+
name: 属性名
|
195
|
+
"""
|
196
|
+
if name.startswith('_') or name in self.__slots__:
|
197
|
+
object.__delattr__(self, name)
|
198
|
+
elif name in self._extra_attrs:
|
199
|
+
del self._extra_attrs[name]
|
200
|
+
self.invalidate_cache()
|
201
|
+
else:
|
202
|
+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
203
|
+
|
204
|
+
def __delitem__(self, key: str) -> None:
|
205
|
+
"""支持字典式删除"""
|
206
|
+
delattr(self, key)
|
207
|
+
|
208
|
+
def __contains__(self, key: str) -> bool:
|
209
|
+
"""
|
210
|
+
检查是否包含某个属性
|
211
|
+
|
212
|
+
Args:
|
213
|
+
key: 属性名
|
214
|
+
|
215
|
+
Returns:
|
216
|
+
是否包含该属性
|
217
|
+
"""
|
218
|
+
try:
|
219
|
+
getattr(self, key)
|
220
|
+
return True
|
221
|
+
except AttributeError:
|
222
|
+
return False
|
223
|
+
|
224
|
+
def __iter__(self):
|
225
|
+
"""支持迭代,返回所有属性名"""
|
226
|
+
return iter(self.to_dict.keys())
|
227
|
+
|
228
|
+
def keys(self):
|
229
|
+
"""返回所有属性名"""
|
230
|
+
return self.to_dict.keys()
|
231
|
+
|
232
|
+
def values(self):
|
233
|
+
"""返回所有属性值"""
|
234
|
+
return self.to_dict.values()
|
235
|
+
|
236
|
+
def items(self):
|
237
|
+
"""返回所有属性键值对"""
|
238
|
+
return self.to_dict.items()
|
239
|
+
|
240
|
+
def get(self, key: str, default: Any = None) -> Any:
|
241
|
+
"""
|
242
|
+
安全获取属性值
|
243
|
+
|
244
|
+
Args:
|
245
|
+
key: 属性名
|
246
|
+
default: 默认值
|
247
|
+
|
248
|
+
Returns:
|
249
|
+
属性值或默认值
|
250
|
+
"""
|
251
|
+
try:
|
252
|
+
return getattr(self, key)
|
253
|
+
except AttributeError:
|
254
|
+
return default
|
255
|
+
|
256
|
+
def update(self, other: Union[Dict[str, Any], 'Response'], **kwargs: Any) -> None:
|
257
|
+
"""
|
258
|
+
更新属性
|
259
|
+
|
260
|
+
Args:
|
261
|
+
other: 字典或另一个 Response 对象
|
262
|
+
**kwargs: 额外的属性
|
263
|
+
"""
|
264
|
+
if isinstance(other, dict):
|
265
|
+
self._extra_attrs.update(other)
|
266
|
+
elif isinstance(other, Response):
|
267
|
+
self._extra_attrs.update(other._extra_attrs)
|
268
|
+
elif hasattr(other, 'items'):
|
269
|
+
self._extra_attrs.update(dict(other.items()))
|
270
|
+
|
271
|
+
self._extra_attrs.update(kwargs)
|
272
|
+
self.invalidate_cache()
|
273
|
+
|
274
|
+
def copy(self) -> 'Response':
|
275
|
+
"""
|
276
|
+
创建副本
|
277
|
+
|
278
|
+
Returns:
|
279
|
+
新的 Response 对象
|
280
|
+
"""
|
281
|
+
return Response(self._seed, self._response, **self._extra_attrs)
|
282
|
+
|
283
|
+
def __repr__(self) -> str:
|
284
|
+
"""字符串表示"""
|
285
|
+
extra_attrs = ', '.join(f'{k}={v!r}' for k, v in list(self._extra_attrs.items())[:3])
|
286
|
+
if len(self._extra_attrs) > 3:
|
287
|
+
extra_attrs += f', ... (+{len(self._extra_attrs) - 3} more)'
|
288
|
+
|
289
|
+
return f"{self.__class__.__name__}(seed={self._seed!r}, response={self._response!r}, {extra_attrs})"
|
290
|
+
|
291
|
+
def __str__(self) -> str:
|
292
|
+
"""用户友好的字符串表示"""
|
293
|
+
return f"{self.__class__.__name__} with {len(self._extra_attrs)} extra attributes"
|
294
|
+
|
295
|
+
def __eq__(self, other: Any) -> bool:
|
296
|
+
"""相等性比较"""
|
297
|
+
if not isinstance(other, Response):
|
298
|
+
return False
|
299
|
+
|
300
|
+
return (
|
301
|
+
self._seed == other._seed and
|
302
|
+
self._response == other._response and
|
303
|
+
self._extra_attrs == other._extra_attrs
|
304
|
+
)
|
305
|
+
|
306
|
+
def __hash__(self) -> int:
|
307
|
+
"""哈希值计算"""
|
308
|
+
# 只对不可变部分计算哈希
|
309
|
+
try:
|
310
|
+
return hash((id(self._seed), id(self._response), tuple(sorted(self._extra_attrs.items()))))
|
311
|
+
except TypeError:
|
312
|
+
# 如果包含不可哈希的值,使用对象ID
|
313
|
+
return hash(id(self))
|
314
|
+
|
315
|
+
|
316
|
+
# 扩展版本:支持更多高级特性
|
317
|
+
class AdvancedResponse(Response):
|
318
|
+
"""
|
319
|
+
高级响应类,提供更多功能
|
320
|
+
"""
|
321
|
+
|
322
|
+
__slots__ = ('_observers', '_frozen')
|
323
|
+
|
324
|
+
def __init__(self, seed: Any, response: Any, frozen: bool = False, **kwargs: Any) -> None:
|
325
|
+
"""
|
326
|
+
初始化高级响应对象
|
327
|
+
|
328
|
+
Args:
|
329
|
+
seed: 种子对象
|
330
|
+
response: 响应对象
|
331
|
+
frozen: 是否冻结对象(不允许修改)
|
332
|
+
**kwargs: 额外属性
|
333
|
+
"""
|
334
|
+
super().__init__(seed, response, **kwargs)
|
335
|
+
object.__setattr__(self, '_observers', [])
|
336
|
+
object.__setattr__(self, '_frozen', frozen)
|
337
|
+
|
338
|
+
def freeze(self) -> None:
|
339
|
+
"""冻结对象,不允许修改"""
|
340
|
+
object.__setattr__(self, '_frozen', True)
|
341
|
+
|
342
|
+
def unfreeze(self) -> None:
|
343
|
+
"""解冻对象,允许修改"""
|
344
|
+
object.__setattr__(self, '_frozen', False)
|
345
|
+
|
346
|
+
@property
|
347
|
+
def is_frozen(self) -> bool:
|
348
|
+
"""检查对象是否被冻结"""
|
349
|
+
return self._frozen
|
350
|
+
|
351
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
352
|
+
"""设置属性(支持冻结检查)"""
|
353
|
+
if self._frozen and not name.startswith('_'):
|
354
|
+
raise AttributeError(f"Cannot modify frozen {self.__class__.__name__} object")
|
355
|
+
|
356
|
+
old_value = getattr(self, name, None)
|
357
|
+
super().__setattr__(name, value)
|
358
|
+
|
359
|
+
# 通知观察者
|
360
|
+
self._notify_observers(name, old_value, value)
|
361
|
+
|
362
|
+
def add_observer(self, callback: callable) -> None:
|
363
|
+
"""
|
364
|
+
添加观察者
|
365
|
+
|
366
|
+
Args:
|
367
|
+
callback: 回调函数,签名为 callback(attr_name, old_value, new_value)
|
368
|
+
"""
|
369
|
+
if callback not in self._observers:
|
370
|
+
self._observers.append(callback)
|
371
|
+
|
372
|
+
def remove_observer(self, callback: callable) -> None:
|
373
|
+
"""
|
374
|
+
移除观察者
|
375
|
+
|
376
|
+
Args:
|
377
|
+
callback: 要移除的回调函数
|
378
|
+
"""
|
379
|
+
if callback in self._observers:
|
380
|
+
self._observers.remove(callback)
|
381
|
+
|
382
|
+
def _notify_observers(self, attr_name: str, old_value: Any, new_value: Any) -> None:
|
383
|
+
"""通知所有观察者"""
|
384
|
+
for observer in self._observers:
|
385
|
+
try:
|
386
|
+
observer(attr_name, old_value, new_value)
|
387
|
+
except Exception as e:
|
388
|
+
# 记录错误但不中断执行
|
389
|
+
print(f"Observer error: {e}")
|
390
|
+
|
@@ -5,8 +5,8 @@ cobweb/base/__init__.py,sha256=NanSxJr0WsqjqCNOQAlxlkt-vQEsERHYBzacFC057oI,222
|
|
5
5
|
cobweb/base/common_queue.py,sha256=hYdaM70KrWjvACuLKaGhkI2VqFCnd87NVvWzmnfIg8Q,1423
|
6
6
|
cobweb/base/item.py,sha256=1bS4U_3vzI2jzSSeoEbLoLT_5CfgLPopWiEYtaahbvw,1674
|
7
7
|
cobweb/base/logger.py,sha256=Vsg1bD4LXW91VgY-ANsmaUu-mD88hU_WS83f7jX3qF8,2011
|
8
|
-
cobweb/base/request.py,sha256=
|
9
|
-
cobweb/base/response.py,sha256
|
8
|
+
cobweb/base/request.py,sha256=Q2zqeixzFbYEe3s-qZhAwdUATcv5UErAidA97BeQOho,16098
|
9
|
+
cobweb/base/response.py,sha256=L3sX2PskV744uz3BJ8xMuAoAfGCeh20w8h0Cnd9vLo0,11377
|
10
10
|
cobweb/base/seed.py,sha256=ddaWCq_KaWwpmPl1CToJlfCxEEnoJ16kjo6azJs9uls,5000
|
11
11
|
cobweb/base/task_queue.py,sha256=2MqGpHGNmK5B-kqv7z420RWyihzB9zgDHJUiLsmtzOI,6402
|
12
12
|
cobweb/base/test.py,sha256=N8MDGb94KQeI4pC5rCc2QdohE9_5AgcOyGqKjbMsOEs,9588
|
@@ -34,8 +34,8 @@ cobweb/utils/decorators.py,sha256=ZwVQlz-lYHgXgKf9KRCp15EWPzTDdhoikYUNUCIqNeM,11
|
|
34
34
|
cobweb/utils/dotting.py,sha256=L-jGSApdnFIP4jUWH6p5qIme0aJ1vyDrxAx8wOJWvcs,1960
|
35
35
|
cobweb/utils/oss.py,sha256=wmToIIVNO8nCQVRmreVaZejk01aCWS35e1NV6cr0yGI,4192
|
36
36
|
cobweb/utils/tools.py,sha256=14TCedqt07m4z6bCnFAsITOFixeGr8V3aOKk--L7Cr0,879
|
37
|
-
cobweb_launcher-3.1.
|
38
|
-
cobweb_launcher-3.1.
|
39
|
-
cobweb_launcher-3.1.
|
40
|
-
cobweb_launcher-3.1.
|
41
|
-
cobweb_launcher-3.1.
|
37
|
+
cobweb_launcher-3.1.30.dist-info/LICENSE,sha256=z1rxSIGOyzcSb3orZxFPxzx-0C1vTocmswqBNxpKfEk,1063
|
38
|
+
cobweb_launcher-3.1.30.dist-info/METADATA,sha256=Od7cEiI7e4QbVHvhVZrw2iYjf17_MOmLiJo3nQZwq6c,6051
|
39
|
+
cobweb_launcher-3.1.30.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
|
40
|
+
cobweb_launcher-3.1.30.dist-info/top_level.txt,sha256=4GETBGNsKqiCUezmT-mJn7tjhcDlu7nLIV5gGgHBW4I,7
|
41
|
+
cobweb_launcher-3.1.30.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|