bricks-py 0.2.1__tar.gz → 0.2.2__tar.gz

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.
Files changed (100) hide show
  1. {bricks_py-0.2.1 → bricks_py-0.2.2}/PKG-INFO +1 -1
  2. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/core/events.py +108 -13
  3. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/core/genesis.py +71 -14
  4. {bricks_py-0.2.1 → bricks_py-0.2.2}/pyproject.toml +1 -1
  5. {bricks_py-0.2.1 → bricks_py-0.2.2}/.gitignore +0 -0
  6. {bricks_py-0.2.1 → bricks_py-0.2.2}/LICENSE +0 -0
  7. {bricks_py-0.2.1 → bricks_py-0.2.2}/README.md +0 -0
  8. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/__init__.py +0 -0
  9. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/__main__.py +0 -0
  10. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/client/__init__.py +0 -0
  11. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/client/cli.py +0 -0
  12. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/client/manage.py +0 -0
  13. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/client/runner.py +0 -0
  14. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/client/server/__init__.py +0 -0
  15. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/client/server/sanic_.py +0 -0
  16. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/client/server/starlette_.py +0 -0
  17. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/core/__init__.py +0 -0
  18. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/core/context.py +0 -0
  19. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/core/dispatch.py +0 -0
  20. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/core/signals.py +0 -0
  21. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/db/__init__.py +0 -0
  22. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/db/mongo.py +0 -0
  23. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/db/redis_.py +0 -0
  24. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/db/rocksdb.py +0 -0
  25. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/db/sqlite.py +0 -0
  26. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/downloader/__init__.py +0 -0
  27. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/downloader/cffi.py +0 -0
  28. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/downloader/curl.py +0 -0
  29. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/downloader/dp.py +0 -0
  30. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/downloader/go_requests.py +0 -0
  31. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/downloader/httpx_.py +0 -0
  32. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/downloader/niquests_.py +0 -0
  33. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/downloader/playwright_.py +0 -0
  34. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/downloader/primp_.py +0 -0
  35. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/downloader/pyhttpx_.py +0 -0
  36. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/downloader/requests_.py +0 -0
  37. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/downloader/tls_client_.py +0 -0
  38. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/__init__.py +0 -0
  39. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/cookies.py +0 -0
  40. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/counter.py +0 -0
  41. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/extractors.py +0 -0
  42. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/headers.py +0 -0
  43. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/items.py +0 -0
  44. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/nodes.py +0 -0
  45. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/proxies.py +0 -0
  46. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/queues/__init__.py +0 -0
  47. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/queues/cache.py +0 -0
  48. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/queues/local.py +0 -0
  49. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/queues/redis_.py +0 -0
  50. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/queues/rocksdb_.py +0 -0
  51. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/queues/smart.py +0 -0
  52. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/queues/sqlite_.py +0 -0
  53. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/request.py +0 -0
  54. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/response.py +0 -0
  55. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/lib/variable.py +0 -0
  56. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/plugins/__init__.py +0 -0
  57. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/plugins/make_seeds.py +0 -0
  58. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/plugins/on_request.py +0 -0
  59. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/plugins/scripts.py +0 -0
  60. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/plugins/storage.py +0 -0
  61. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/__init__.py +0 -0
  62. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/common.py +0 -0
  63. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/grpc_/__init__.py +0 -0
  64. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/grpc_/generic.proto +0 -0
  65. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/grpc_/generic_pb2.py +0 -0
  66. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/grpc_/generic_pb2.pyi +0 -0
  67. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/grpc_/generic_pb2_grpc.py +0 -0
  68. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/grpc_/service.py +0 -0
  69. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/http_/__init__.py +0 -0
  70. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/http_/service.py +0 -0
  71. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/redis_/__init__.py +0 -0
  72. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/redis_/service.py +0 -0
  73. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/socket_/__init__.py +0 -0
  74. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/socket_/service.py +0 -0
  75. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/websocket_/__init__.py +0 -0
  76. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/rpc/websocket_/service.py +0 -0
  77. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/spider/__init__.py +0 -0
  78. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/spider/addon.py +0 -0
  79. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/spider/air.py +0 -0
  80. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/spider/form.py +0 -0
  81. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/spider/template.py +0 -0
  82. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/state.py +0 -0
  83. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/tpls/__init__.py +0 -0
  84. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/tpls/spider/__init__.py +0 -0
  85. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/tpls/spider/air.tpl +0 -0
  86. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/tpls/spider/form.tpl +0 -0
  87. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/tpls/spider/template.tpl +0 -0
  88. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/utils/__init__.py +0 -0
  89. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/utils/arrow.py +0 -0
  90. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/utils/codes.py +0 -0
  91. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/utils/compress.py +0 -0
  92. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/utils/convert.py +0 -0
  93. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/utils/csv_.py +0 -0
  94. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/utils/fake/__init__.py +0 -0
  95. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/utils/fake/stochastic.py +0 -0
  96. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/utils/fake/tls.py +0 -0
  97. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/utils/fake/user_agent.py +0 -0
  98. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/utils/package.py +0 -0
  99. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/utils/pandora.py +0 -0
  100. {bricks_py-0.2.1 → bricks_py-0.2.2}/bricks/utils/scheduler.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bricks-py
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: quickly build your crawler
5
5
  Project-URL: Homepage, https://github.com/KKKKKKKEM/bricks
6
6
  Project-URL: Repository, https://github.com/KKKKKKKEM/bricks.git
@@ -78,6 +78,70 @@ class Task:
78
78
  match_code: Optional[object] = None # lazily-compiled eval code object for str matches
79
79
 
80
80
 
81
+ @dataclass
82
+ class EventSpec:
83
+ """声明式钩子元数据,由 @events.on() 附加到装饰的方法上。
84
+
85
+ 与 Task 不同,EventSpec 不引用实际的可调用对象;它只保存 *声明* 参数。
86
+ 可调用对象在实例创建时由 Pangu._install_events() 解析和绑定。
87
+ """
88
+ form: str
89
+ index: Optional[int] = None
90
+ disposable: bool = False
91
+ args: Optional[list] = None
92
+ kwargs: Optional[dict] = None
93
+ match: Optional[Union[Callable, str]] = None
94
+
95
+
96
+ # ------------------------------------------------------------------ #
97
+ # EventSpec helpers - attach / read metadata on functions & descriptors
98
+ # ------------------------------------------------------------------ #
99
+
100
+ _EVENT_SPEC_ATTR = "__event_spec__" # Backward-compatible single-spec view.
101
+ _EVENT_SPECS_ATTR = "__event_specs__"
102
+
103
+
104
+ def _set_event_spec(func, spec: EventSpec):
105
+ """将 EventSpec 附加到普通函数上,允许一个方法声明多个 hook。"""
106
+ specs = list(getattr(func, _EVENT_SPECS_ATTR, ()))
107
+ legacy_spec = getattr(func, _EVENT_SPEC_ATTR, None)
108
+ if legacy_spec is not None and legacy_spec not in specs:
109
+ specs.insert(0, legacy_spec)
110
+ specs.append(spec)
111
+ setattr(func, _EVENT_SPECS_ATTR, specs)
112
+ setattr(func, _EVENT_SPEC_ATTR, spec)
113
+
114
+
115
+ def _get_event_spec(obj) -> Optional[EventSpec]:
116
+ """从函数或描述符 (staticmethod / classmethod) 上读取最后声明的 EventSpec。"""
117
+ if isinstance(obj, (staticmethod, classmethod)):
118
+ return getattr(obj.__func__, _EVENT_SPEC_ATTR, None)
119
+ return getattr(obj, _EVENT_SPEC_ATTR, None)
120
+
121
+
122
+ def _get_event_specs(obj) -> List[EventSpec]:
123
+ """从函数或描述符上读取全部 EventSpec 元数据。"""
124
+ if isinstance(obj, (staticmethod, classmethod)):
125
+ obj = obj.__func__
126
+ specs = getattr(obj, _EVENT_SPECS_ATTR, None)
127
+ if specs:
128
+ return list(specs)
129
+ spec = getattr(obj, _EVENT_SPEC_ATTR, None)
130
+ return [spec] if spec is not None else []
131
+
132
+
133
+ def _has_event_spec(obj) -> bool:
134
+ """检查函数或描述符是否携带 EventSpec 元数据。"""
135
+ return bool(_get_event_specs(obj))
136
+
137
+
138
+ def _get_event_legacy(obj):
139
+ """从函数或描述符上读取旧版 __event__ 元数据,用于向后兼容。"""
140
+ if isinstance(obj, (staticmethod, classmethod)):
141
+ return getattr(obj.__func__, "__event__", None)
142
+ return getattr(obj, "__event__", None)
143
+
144
+
81
145
  @dataclass
82
146
  class Register:
83
147
  """事件注册信息
@@ -417,9 +481,25 @@ def on(
417
481
  match: Optional[Union[Callable, str]] = None,
418
482
  ):
419
483
  """
420
- 使用装饰器的方式注册事件
484
+ 使用装饰器的方式注册事件 / 声明事件元数据
485
+
486
+ 行为取决于被装饰对象的位置:
487
+
488
+ * **模块级 / 局部函数** — 立即以 ``target=None`` 全局注册(行为与旧版一致)。
489
+ * **类方法 / 静态方法 / 类方法描述符** — 仅附加 ``EventSpec`` 元数据,
490
+ 由 ``Pangu._install_events()`` 在实例化时绑定并注册。
491
+
492
+ 支持两种装饰器顺序::
421
493
 
422
- 注意:如果使用 @staticmethod 等装饰器,@on 应该放在最内层(紧贴函数定义)
494
+ # 推荐:@events.on 在最内层
495
+ @staticmethod
496
+ @events.on("my_event")
497
+ def handler(context): ...
498
+
499
+ # 也支持:@events.on 在最外层
500
+ @events.on("my_event")
501
+ @staticmethod
502
+ def handler(context): ...
423
503
 
424
504
  :param form: 事件类型,可以传入常量属性
425
505
  :param index: 事件排序索引,默认按代码顺序递增,索引越大越靠后执行
@@ -434,21 +514,36 @@ def on(
434
514
  def my_handler(context):
435
515
  print("Request starting")
436
516
  """
517
+ spec = EventSpec(
518
+ form=form,
519
+ index=index,
520
+ disposable=disposable,
521
+ args=args,
522
+ kwargs=kwargs,
523
+ match=match,
524
+ )
437
525
 
438
526
  def inner(func):
439
- e = Task(
440
- func=func,
441
- index=index,
442
- disposable=disposable,
443
- args=args,
444
- kwargs=kwargs,
445
- match=match,
446
- )
447
-
448
- if "." not in func.__qualname__ or "<locals>" in func.__qualname__:
527
+ # 提取底层函数以检查 qualname(兼容 staticmethod / classmethod 描述符)
528
+ if isinstance(func, (staticmethod, classmethod)):
529
+ actual_func = func.__func__
530
+ else:
531
+ actual_func = func
532
+
533
+ # 模块级函数或局部函数:立即全局注册
534
+ if "." not in actual_func.__qualname__ or "<locals>" in actual_func.__qualname__:
535
+ e = Task(
536
+ func=actual_func,
537
+ index=index,
538
+ disposable=disposable,
539
+ args=args,
540
+ kwargs=kwargs,
541
+ match=match,
542
+ )
449
543
  EventManager.register(Context(form=form, target=None), e)
450
544
  else:
451
- setattr(func, "__event__", (form, e))
545
+ # 类方法:仅声明元数据,延迟到实例化时绑定注册
546
+ _set_event_spec(actual_func, spec)
452
547
 
453
548
  return func
454
549
 
@@ -10,7 +10,15 @@ from loguru import logger
10
10
 
11
11
  from bricks.core import dispatch, signals
12
12
  from bricks.core.context import Context, Error, Flow
13
- from bricks.core.events import REGISTERED_EVENTS, EventManager, Register, Task
13
+ from bricks.core.events import (
14
+ REGISTERED_EVENTS,
15
+ EventManager,
16
+ EventSpec,
17
+ Register,
18
+ Task,
19
+ _get_event_legacy,
20
+ _get_event_specs,
21
+ )
14
22
  from bricks.state import const
15
23
  from bricks.utils import pandora
16
24
  from bricks.utils.scheduler import BaseTrigger, Scheduler
@@ -20,8 +28,6 @@ class MetaClass(type):
20
28
  def __call__(cls, *args, **kwargs):
21
29
  instance = type.__call__(cls, *args, **kwargs)
22
30
 
23
- registed_evets = []
24
-
25
31
  # 加载拦截器
26
32
  for method in dir(instance):
27
33
  # 修改被拦截的方法
@@ -35,19 +41,14 @@ class MetaClass(type):
35
41
  raw_method and setattr(
36
42
  instance, raw_method_name, method_wrapper(raw_method)
37
43
  ) # type: ignore
38
- else:
39
- func = getattr(instance, method, None)
40
- if not func or not callable(func):
41
- continue
42
-
43
- if hasattr(func, "__event__"):
44
- registed_evets.append(func.__event__)
45
44
 
46
- else:
47
- hasattr(instance, "install") and instance.install() # type: ignore
45
+ # 安装 @events.on 装饰的事件钩子(仅事件能力实例,如 Pangu)
46
+ if hasattr(instance, "_install_events"):
47
+ instance._install_events()
48
48
 
49
- for event in registed_evets:
50
- instance.use(*event)
49
+ # 运行生命周期 install
50
+ if hasattr(instance, "install"):
51
+ instance.install()
51
52
 
52
53
  return instance
53
54
 
@@ -210,6 +211,62 @@ class Pangu(Chaos):
210
211
  options=self.get("dispatcher.options", default={}),
211
212
  )
212
213
 
214
+ def _install_events(self):
215
+ """扫描 MRO 类层次中 @events.on 装饰的方法并注册为事件钩子。
216
+
217
+ 处理实例方法、staticmethod 和 classmethod 描述符。
218
+ 继承的钩子只注册一次;如果子类重写了同名方法(无论是否装饰),
219
+ 只使用子类的版本。
220
+
221
+ 支持单个方法声明多个 @events.on() 装饰器,每个 form 均会独立注册。
222
+ """
223
+ seen = set()
224
+
225
+ for klass in type(self).__mro__:
226
+ for name, value in klass.__dict__.items():
227
+ if name in seen:
228
+ continue
229
+ seen.add(name)
230
+
231
+ specs = _get_event_specs(value)
232
+
233
+ # 向后兼容:检查旧版 __event__ 格式
234
+ if not specs:
235
+ event_val = _get_event_legacy(value)
236
+ if event_val is not None:
237
+ try:
238
+ old_form, old_task = event_val
239
+ specs = [EventSpec(
240
+ form=old_form,
241
+ index=getattr(old_task, "index", None),
242
+ disposable=getattr(old_task, "disposable", False),
243
+ args=getattr(old_task, "args", None),
244
+ kwargs=getattr(old_task, "kwargs", None),
245
+ match=getattr(old_task, "match", None),
246
+ )]
247
+ except (ValueError, TypeError):
248
+ pass
249
+
250
+ if not specs:
251
+ continue
252
+
253
+ # 通过 getattr 获取绑定的可调用对象(自动处理所有描述符类型)
254
+ try:
255
+ bound = getattr(self, name)
256
+ except AttributeError:
257
+ continue
258
+
259
+ for spec in specs:
260
+ task = Task(
261
+ func=bound,
262
+ index=spec.index,
263
+ disposable=spec.disposable,
264
+ args=spec.args,
265
+ kwargs=spec.kwargs,
266
+ match=spec.match,
267
+ )
268
+ self.use(spec.form, task)
269
+
213
270
  @property
214
271
  def plugins(self) -> List[Register]:
215
272
  return REGISTERED_EVENTS.registered[self] # type: ignore
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bricks-py"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "quickly build your crawler"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes