cdpify 0.1.0__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 (239) hide show
  1. cdpify-0.1.0/PKG-INFO +203 -0
  2. cdpify-0.1.0/README.md +192 -0
  3. cdpify-0.1.0/cdpify/__init__.py +17 -0
  4. cdpify-0.1.0/cdpify/client.py +243 -0
  5. cdpify-0.1.0/cdpify/domains/__init__.py +89 -0
  6. cdpify-0.1.0/cdpify/domains/accessibility/__init__.py +8 -0
  7. cdpify-0.1.0/cdpify/domains/accessibility/client.py +170 -0
  8. cdpify-0.1.0/cdpify/domains/accessibility/commands.py +103 -0
  9. cdpify-0.1.0/cdpify/domains/accessibility/events.py +31 -0
  10. cdpify-0.1.0/cdpify/domains/accessibility/types.py +189 -0
  11. cdpify-0.1.0/cdpify/domains/animation/__init__.py +8 -0
  12. cdpify-0.1.0/cdpify/domains/animation/client.py +172 -0
  13. cdpify-0.1.0/cdpify/domains/animation/commands.py +82 -0
  14. cdpify-0.1.0/cdpify/domains/animation/events.py +47 -0
  15. cdpify-0.1.0/cdpify/domains/animation/types.py +74 -0
  16. cdpify-0.1.0/cdpify/domains/audits/__init__.py +8 -0
  17. cdpify-0.1.0/cdpify/domains/audits/client.py +90 -0
  18. cdpify-0.1.0/cdpify/domains/audits/commands.py +41 -0
  19. cdpify-0.1.0/cdpify/domains/audits/events.py +16 -0
  20. cdpify-0.1.0/cdpify/domains/audits/types.py +769 -0
  21. cdpify-0.1.0/cdpify/domains/backgroundservice/__init__.py +8 -0
  22. cdpify-0.1.0/cdpify/domains/backgroundservice/client.py +87 -0
  23. cdpify-0.1.0/cdpify/domains/backgroundservice/commands.py +40 -0
  24. cdpify-0.1.0/cdpify/domains/backgroundservice/events.py +33 -0
  25. cdpify-0.1.0/cdpify/domains/backgroundservice/types.py +42 -0
  26. cdpify-0.1.0/cdpify/domains/base.py +13 -0
  27. cdpify-0.1.0/cdpify/domains/browser/__init__.py +8 -0
  28. cdpify-0.1.0/cdpify/domains/browser/client.py +367 -0
  29. cdpify-0.1.0/cdpify/domains/browser/commands.py +183 -0
  30. cdpify-0.1.0/cdpify/domains/browser/events.py +40 -0
  31. cdpify-0.1.0/cdpify/domains/browser/types.py +116 -0
  32. cdpify-0.1.0/cdpify/domains/cachestorage/__init__.py +8 -0
  33. cdpify-0.1.0/cdpify/domains/cachestorage/client.py +125 -0
  34. cdpify-0.1.0/cdpify/domains/cachestorage/commands.py +71 -0
  35. cdpify-0.1.0/cdpify/domains/cachestorage/events.py +8 -0
  36. cdpify-0.1.0/cdpify/domains/cachestorage/types.py +61 -0
  37. cdpify-0.1.0/cdpify/domains/cast/__init__.py +8 -0
  38. cdpify-0.1.0/cdpify/domains/cast/client.py +109 -0
  39. cdpify-0.1.0/cdpify/domains/cast/commands.py +51 -0
  40. cdpify-0.1.0/cdpify/domains/cast/events.py +31 -0
  41. cdpify-0.1.0/cdpify/domains/cast/types.py +11 -0
  42. cdpify-0.1.0/cdpify/domains/console/__init__.py +8 -0
  43. cdpify-0.1.0/cdpify/domains/console/client.py +48 -0
  44. cdpify-0.1.0/cdpify/domains/console/commands.py +7 -0
  45. cdpify-0.1.0/cdpify/domains/console/events.py +20 -0
  46. cdpify-0.1.0/cdpify/domains/console/types.py +31 -0
  47. cdpify-0.1.0/cdpify/domains/css/__init__.py +8 -0
  48. cdpify-0.1.0/cdpify/domains/css/client.py +664 -0
  49. cdpify-0.1.0/cdpify/domains/css/commands.py +423 -0
  50. cdpify-0.1.0/cdpify/domains/css/events.py +68 -0
  51. cdpify-0.1.0/cdpify/domains/css/types.py +505 -0
  52. cdpify-0.1.0/cdpify/domains/debugger/__init__.py +8 -0
  53. cdpify-0.1.0/cdpify/domains/debugger/client.py +629 -0
  54. cdpify-0.1.0/cdpify/domains/debugger/commands.py +389 -0
  55. cdpify-0.1.0/cdpify/domains/debugger/events.py +121 -0
  56. cdpify-0.1.0/cdpify/domains/debugger/types.py +128 -0
  57. cdpify-0.1.0/cdpify/domains/deviceorientation/__init__.py +8 -0
  58. cdpify-0.1.0/cdpify/domains/deviceorientation/client.py +47 -0
  59. cdpify-0.1.0/cdpify/domains/deviceorientation/commands.py +17 -0
  60. cdpify-0.1.0/cdpify/domains/deviceorientation/events.py +8 -0
  61. cdpify-0.1.0/cdpify/domains/deviceorientation/types.py +7 -0
  62. cdpify-0.1.0/cdpify/domains/dom/__init__.py +8 -0
  63. cdpify-0.1.0/cdpify/domains/dom/client.py +975 -0
  64. cdpify-0.1.0/cdpify/domains/dom/commands.py +579 -0
  65. cdpify-0.1.0/cdpify/domains/dom/events.py +195 -0
  66. cdpify-0.1.0/cdpify/domains/dom/types.py +217 -0
  67. cdpify-0.1.0/cdpify/domains/domdebugger/__init__.py +8 -0
  68. cdpify-0.1.0/cdpify/domains/domdebugger/client.py +194 -0
  69. cdpify-0.1.0/cdpify/domains/domdebugger/commands.py +101 -0
  70. cdpify-0.1.0/cdpify/domains/domdebugger/events.py +8 -0
  71. cdpify-0.1.0/cdpify/domains/domdebugger/types.py +36 -0
  72. cdpify-0.1.0/cdpify/domains/domsnapshot/__init__.py +8 -0
  73. cdpify-0.1.0/cdpify/domains/domsnapshot/client.py +92 -0
  74. cdpify-0.1.0/cdpify/domains/domsnapshot/commands.py +47 -0
  75. cdpify-0.1.0/cdpify/domains/domsnapshot/events.py +8 -0
  76. cdpify-0.1.0/cdpify/domains/domsnapshot/types.py +194 -0
  77. cdpify-0.1.0/cdpify/domains/domstorage/__init__.py +8 -0
  78. cdpify-0.1.0/cdpify/domains/domstorage/client.py +112 -0
  79. cdpify-0.1.0/cdpify/domains/domstorage/commands.py +30 -0
  80. cdpify-0.1.0/cdpify/domains/domstorage/events.py +37 -0
  81. cdpify-0.1.0/cdpify/domains/domstorage/types.py +24 -0
  82. cdpify-0.1.0/cdpify/domains/emulation/__init__.py +8 -0
  83. cdpify-0.1.0/cdpify/domains/emulation/client.py +853 -0
  84. cdpify-0.1.0/cdpify/domains/emulation/commands.py +395 -0
  85. cdpify-0.1.0/cdpify/domains/emulation/events.py +21 -0
  86. cdpify-0.1.0/cdpify/domains/emulation/types.py +176 -0
  87. cdpify-0.1.0/cdpify/domains/eventbreakpoints/__init__.py +8 -0
  88. cdpify-0.1.0/cdpify/domains/eventbreakpoints/client.py +61 -0
  89. cdpify-0.1.0/cdpify/domains/eventbreakpoints/commands.py +23 -0
  90. cdpify-0.1.0/cdpify/domains/eventbreakpoints/events.py +8 -0
  91. cdpify-0.1.0/cdpify/domains/eventbreakpoints/types.py +7 -0
  92. cdpify-0.1.0/cdpify/domains/fetch/__init__.py +8 -0
  93. cdpify-0.1.0/cdpify/domains/fetch/client.py +207 -0
  94. cdpify-0.1.0/cdpify/domains/fetch/commands.py +117 -0
  95. cdpify-0.1.0/cdpify/domains/fetch/events.py +56 -0
  96. cdpify-0.1.0/cdpify/domains/fetch/types.py +58 -0
  97. cdpify-0.1.0/cdpify/domains/heapprofiler/__init__.py +8 -0
  98. cdpify-0.1.0/cdpify/domains/heapprofiler/client.py +220 -0
  99. cdpify-0.1.0/cdpify/domains/heapprofiler/commands.py +69 -0
  100. cdpify-0.1.0/cdpify/domains/heapprofiler/events.py +51 -0
  101. cdpify-0.1.0/cdpify/domains/heapprofiler/types.py +45 -0
  102. cdpify-0.1.0/cdpify/domains/indexeddb/__init__.py +8 -0
  103. cdpify-0.1.0/cdpify/domains/indexeddb/client.py +232 -0
  104. cdpify-0.1.0/cdpify/domains/indexeddb/commands.py +113 -0
  105. cdpify-0.1.0/cdpify/domains/indexeddb/events.py +8 -0
  106. cdpify-0.1.0/cdpify/domains/indexeddb/types.py +84 -0
  107. cdpify-0.1.0/cdpify/domains/input/__init__.py +8 -0
  108. cdpify-0.1.0/cdpify/domains/input/client.py +377 -0
  109. cdpify-0.1.0/cdpify/domains/input/commands.py +177 -0
  110. cdpify-0.1.0/cdpify/domains/input/events.py +21 -0
  111. cdpify-0.1.0/cdpify/domains/input/types.py +43 -0
  112. cdpify-0.1.0/cdpify/domains/io/__init__.py +8 -0
  113. cdpify-0.1.0/cdpify/domains/io/client.py +74 -0
  114. cdpify-0.1.0/cdpify/domains/io/commands.py +46 -0
  115. cdpify-0.1.0/cdpify/domains/io/events.py +8 -0
  116. cdpify-0.1.0/cdpify/domains/io/types.py +11 -0
  117. cdpify-0.1.0/cdpify/domains/layertree/__init__.py +8 -0
  118. cdpify-0.1.0/cdpify/domains/layertree/client.py +177 -0
  119. cdpify-0.1.0/cdpify/domains/layertree/commands.py +93 -0
  120. cdpify-0.1.0/cdpify/domains/layertree/events.py +26 -0
  121. cdpify-0.1.0/cdpify/domains/layertree/types.py +78 -0
  122. cdpify-0.1.0/cdpify/domains/log/__init__.py +8 -0
  123. cdpify-0.1.0/cdpify/domains/log/client.py +82 -0
  124. cdpify-0.1.0/cdpify/domains/log/commands.py +15 -0
  125. cdpify-0.1.0/cdpify/domains/log/events.py +20 -0
  126. cdpify-0.1.0/cdpify/domains/log/types.py +58 -0
  127. cdpify-0.1.0/cdpify/domains/media/__init__.py +8 -0
  128. cdpify-0.1.0/cdpify/domains/media/client.py +37 -0
  129. cdpify-0.1.0/cdpify/domains/media/commands.py +7 -0
  130. cdpify-0.1.0/cdpify/domains/media/events.py +64 -0
  131. cdpify-0.1.0/cdpify/domains/media/types.py +70 -0
  132. cdpify-0.1.0/cdpify/domains/memory/__init__.py +8 -0
  133. cdpify-0.1.0/cdpify/domains/memory/client.py +166 -0
  134. cdpify-0.1.0/cdpify/domains/memory/commands.py +54 -0
  135. cdpify-0.1.0/cdpify/domains/memory/events.py +8 -0
  136. cdpify-0.1.0/cdpify/domains/memory/types.py +50 -0
  137. cdpify-0.1.0/cdpify/domains/network/__init__.py +8 -0
  138. cdpify-0.1.0/cdpify/domains/network/client.py +742 -0
  139. cdpify-0.1.0/cdpify/domains/network/commands.py +399 -0
  140. cdpify-0.1.0/cdpify/domains/network/events.py +534 -0
  141. cdpify-0.1.0/cdpify/domains/network/types.py +890 -0
  142. cdpify-0.1.0/cdpify/domains/overlay/__init__.py +8 -0
  143. cdpify-0.1.0/cdpify/domains/overlay/client.py +547 -0
  144. cdpify-0.1.0/cdpify/domains/overlay/commands.py +247 -0
  145. cdpify-0.1.0/cdpify/domains/overlay/events.py +53 -0
  146. cdpify-0.1.0/cdpify/domains/overlay/types.py +194 -0
  147. cdpify-0.1.0/cdpify/domains/page/__init__.py +8 -0
  148. cdpify-0.1.0/cdpify/domains/page/client.py +1084 -0
  149. cdpify-0.1.0/cdpify/domains/page/commands.py +538 -0
  150. cdpify-0.1.0/cdpify/domains/page/events.py +326 -0
  151. cdpify-0.1.0/cdpify/domains/page/types.py +793 -0
  152. cdpify-0.1.0/cdpify/domains/performance/__init__.py +8 -0
  153. cdpify-0.1.0/cdpify/domains/performance/client.py +73 -0
  154. cdpify-0.1.0/cdpify/domains/performance/commands.py +30 -0
  155. cdpify-0.1.0/cdpify/domains/performance/events.py +21 -0
  156. cdpify-0.1.0/cdpify/domains/performance/types.py +14 -0
  157. cdpify-0.1.0/cdpify/domains/profiler/__init__.py +8 -0
  158. cdpify-0.1.0/cdpify/domains/profiler/client.py +137 -0
  159. cdpify-0.1.0/cdpify/domains/profiler/commands.py +45 -0
  160. cdpify-0.1.0/cdpify/domains/profiler/events.py +48 -0
  161. cdpify-0.1.0/cdpify/domains/profiler/types.py +73 -0
  162. cdpify-0.1.0/cdpify/domains/runtime/__init__.py +8 -0
  163. cdpify-0.1.0/cdpify/domains/runtime/client.py +478 -0
  164. cdpify-0.1.0/cdpify/domains/runtime/commands.py +234 -0
  165. cdpify-0.1.0/cdpify/domains/runtime/events.py +116 -0
  166. cdpify-0.1.0/cdpify/domains/runtime/types.py +355 -0
  167. cdpify-0.1.0/cdpify/domains/schema/__init__.py +8 -0
  168. cdpify-0.1.0/cdpify/domains/schema/client.py +30 -0
  169. cdpify-0.1.0/cdpify/domains/schema/commands.py +11 -0
  170. cdpify-0.1.0/cdpify/domains/schema/events.py +8 -0
  171. cdpify-0.1.0/cdpify/domains/schema/types.py +14 -0
  172. cdpify-0.1.0/cdpify/domains/security/__init__.py +8 -0
  173. cdpify-0.1.0/cdpify/domains/security/client.py +93 -0
  174. cdpify-0.1.0/cdpify/domains/security/commands.py +34 -0
  175. cdpify-0.1.0/cdpify/domains/security/events.py +47 -0
  176. cdpify-0.1.0/cdpify/domains/security/types.py +106 -0
  177. cdpify-0.1.0/cdpify/domains/serviceworker/__init__.py +8 -0
  178. cdpify-0.1.0/cdpify/domains/serviceworker/client.py +217 -0
  179. cdpify-0.1.0/cdpify/domains/serviceworker/commands.py +50 -0
  180. cdpify-0.1.0/cdpify/domains/serviceworker/events.py +26 -0
  181. cdpify-0.1.0/cdpify/domains/serviceworker/types.py +60 -0
  182. cdpify-0.1.0/cdpify/domains/storage/__init__.py +8 -0
  183. cdpify-0.1.0/cdpify/domains/storage/client.py +649 -0
  184. cdpify-0.1.0/cdpify/domains/storage/commands.py +355 -0
  185. cdpify-0.1.0/cdpify/domains/storage/events.py +190 -0
  186. cdpify-0.1.0/cdpify/domains/storage/types.py +422 -0
  187. cdpify-0.1.0/cdpify/domains/systeminfo/__init__.py +8 -0
  188. cdpify-0.1.0/cdpify/domains/systeminfo/client.py +59 -0
  189. cdpify-0.1.0/cdpify/domains/systeminfo/commands.py +30 -0
  190. cdpify-0.1.0/cdpify/domains/systeminfo/events.py +8 -0
  191. cdpify-0.1.0/cdpify/domains/systeminfo/types.py +87 -0
  192. cdpify-0.1.0/cdpify/domains/target/__init__.py +8 -0
  193. cdpify-0.1.0/cdpify/domains/target/client.py +394 -0
  194. cdpify-0.1.0/cdpify/domains/target/commands.py +234 -0
  195. cdpify-0.1.0/cdpify/domains/target/events.py +85 -0
  196. cdpify-0.1.0/cdpify/domains/target/types.py +60 -0
  197. cdpify-0.1.0/cdpify/domains/tethering/__init__.py +8 -0
  198. cdpify-0.1.0/cdpify/domains/tethering/client.py +50 -0
  199. cdpify-0.1.0/cdpify/domains/tethering/commands.py +23 -0
  200. cdpify-0.1.0/cdpify/domains/tethering/events.py +21 -0
  201. cdpify-0.1.0/cdpify/domains/tethering/types.py +7 -0
  202. cdpify-0.1.0/cdpify/domains/tracing/__init__.py +8 -0
  203. cdpify-0.1.0/cdpify/domains/tracing/client.py +131 -0
  204. cdpify-0.1.0/cdpify/domains/tracing/commands.py +54 -0
  205. cdpify-0.1.0/cdpify/domains/tracing/events.py +46 -0
  206. cdpify-0.1.0/cdpify/domains/tracing/types.py +58 -0
  207. cdpify-0.1.0/cdpify/domains/webaudio/__init__.py +8 -0
  208. cdpify-0.1.0/cdpify/domains/webaudio/client.py +61 -0
  209. cdpify-0.1.0/cdpify/domains/webaudio/commands.py +19 -0
  210. cdpify-0.1.0/cdpify/domains/webaudio/events.py +148 -0
  211. cdpify-0.1.0/cdpify/domains/webaudio/types.py +110 -0
  212. cdpify-0.1.0/cdpify/domains/webauthn/__init__.py +8 -0
  213. cdpify-0.1.0/cdpify/domains/webauthn/client.py +261 -0
  214. cdpify-0.1.0/cdpify/domains/webauthn/commands.py +132 -0
  215. cdpify-0.1.0/cdpify/domains/webauthn/events.py +53 -0
  216. cdpify-0.1.0/cdpify/domains/webauthn/types.py +44 -0
  217. cdpify-0.1.0/cdpify/events.py +55 -0
  218. cdpify-0.1.0/cdpify/exceptions.py +18 -0
  219. cdpify-0.1.0/cdpify/generator/__main__.py +27 -0
  220. cdpify-0.1.0/cdpify/generator/config.py +104 -0
  221. cdpify-0.1.0/cdpify/generator/constants.py +2 -0
  222. cdpify-0.1.0/cdpify/generator/downloader.py +43 -0
  223. cdpify-0.1.0/cdpify/generator/generator.py +170 -0
  224. cdpify-0.1.0/cdpify/generator/generators/__init__.py +11 -0
  225. cdpify-0.1.0/cdpify/generator/generators/base.py +67 -0
  226. cdpify-0.1.0/cdpify/generator/generators/client.py +246 -0
  227. cdpify-0.1.0/cdpify/generator/generators/commands.py +107 -0
  228. cdpify-0.1.0/cdpify/generator/generators/events.py +116 -0
  229. cdpify-0.1.0/cdpify/generator/generators/types.py +132 -0
  230. cdpify-0.1.0/cdpify/generator/generators/utils.py +115 -0
  231. cdpify-0.1.0/cdpify/generator/models.py +81 -0
  232. cdpify-0.1.0/cdpify/generator/parser.py +23 -0
  233. cdpify-0.1.0/cdpify.egg-info/PKG-INFO +203 -0
  234. cdpify-0.1.0/cdpify.egg-info/SOURCES.txt +237 -0
  235. cdpify-0.1.0/cdpify.egg-info/dependency_links.txt +1 -0
  236. cdpify-0.1.0/cdpify.egg-info/requires.txt +4 -0
  237. cdpify-0.1.0/cdpify.egg-info/top_level.txt +1 -0
  238. cdpify-0.1.0/pyproject.toml +53 -0
  239. cdpify-0.1.0/setup.cfg +4 -0
cdpify-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.4
2
+ Name: cdpify
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ Requires-Python: >=3.14
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: httpx>=0.28.1
8
+ Requires-Dist: pre-commit>=4.5.1
9
+ Requires-Dist: pydantic>=2.12.5
10
+ Requires-Dist: websockets>=15.0.1
11
+
12
+ # Pydantic CDP
13
+
14
+ Type-safe Python client for the Chrome DevTools Protocol (CDP) with Pydantic validation.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pip install cdpify
20
+ ```
21
+
22
+ ## What it does
23
+
24
+ This library provides Python bindings for the Chrome DevTools Protocol with full type safety through Pydantic models. All CDP domains, commands, events, and types are automatically generated from the official Chrome DevTools Protocol specifications.
25
+
26
+ ## Usage
27
+
28
+ ### Basic Connection
29
+
30
+ ```python
31
+ import asyncio
32
+ from cdpify import CDPClient
33
+
34
+ async def main():
35
+ async with CDPClient("ws://localhost:9222/devtools/browser/...") as client:
36
+ # Send CDP commands
37
+ result = await client.send_raw(
38
+ method="Target.getTargets",
39
+ params=None
40
+ )
41
+ print(f"Targets: {result}")
42
+
43
+ asyncio.run(main())
44
+ ```
45
+
46
+ ### Using Domain Clients
47
+
48
+ Domain-specific clients provide typed methods for all CDP commands:
49
+
50
+ ```python
51
+ from cdpify import CDPClient
52
+ from cdpify.domains import PageClient, RuntimeClient
53
+
54
+ async def main():
55
+ async with CDPClient("ws://localhost:9222/devtools/page/...") as client:
56
+ # Initialize domain clients
57
+ page = PageClient(client)
58
+ runtime = RuntimeClient(client)
59
+
60
+ # Navigate to a page
61
+ await page.navigate(url="https://example.com")
62
+
63
+ # Evaluate JavaScript
64
+ result = await runtime.evaluate(
65
+ expression="document.title",
66
+ return_by_value=True
67
+ )
68
+ print(f"Page title: {result.result.value}")
69
+
70
+ asyncio.run(main())
71
+ ```
72
+
73
+ ### Event Handling
74
+
75
+ Register handlers for CDP events:
76
+
77
+ ```python
78
+ from cdpify import CDPClient
79
+ from cdpify.domains import PageClient
80
+
81
+ async def main():
82
+ client = CDPClient("ws://localhost:9222/devtools/page/...")
83
+ await client.connect()
84
+
85
+ # Register event handler
86
+ @client.on("Page.loadEventFired")
87
+ async def on_load(params, session_id):
88
+ print(f"Page loaded at timestamp: {params['timestamp']}")
89
+
90
+ # Wildcard handler for all events
91
+ @client.on()
92
+ async def on_any_event(params, session_id):
93
+ print(f"Event received: {params}")
94
+
95
+ page = PageClient(client)
96
+ await page.enable()
97
+ await page.navigate(url="https://example.com")
98
+
99
+ await asyncio.sleep(5)
100
+ await client.disconnect()
101
+
102
+ asyncio.run(main())
103
+ ```
104
+
105
+ ### Configuration
106
+
107
+ ```python
108
+ client = CDPClient(
109
+ url="ws://localhost:9222/devtools/browser/...",
110
+ additional_headers={"Authorization": "Bearer token"},
111
+ max_frame_size=100 * 1024 * 1024, # 100MB
112
+ default_timeout=30.0 # seconds
113
+ )
114
+ ```
115
+
116
+ ## Available Domain Clients
117
+
118
+ All CDP domains are available as typed clients:
119
+
120
+ - `PageClient` - Page lifecycle, navigation, screenshots
121
+ - `RuntimeClient` - JavaScript execution, console, objects
122
+ - `NetworkClient` - Network monitoring, request interception
123
+ - `DOMClient` - DOM tree access and manipulation
124
+ - `DebuggerClient` - JavaScript debugging
125
+ - `EmulationClient` - Device emulation, geolocation
126
+ - `PerformanceClient` - Performance metrics
127
+ - `SecurityClient` - Security state, certificates
128
+ - And 40+ more domains...
129
+
130
+ Import them from the root package:
131
+
132
+ ```python
133
+ from cdpify import (
134
+ CDPClient,
135
+ PageClient,
136
+ NetworkClient,
137
+ RuntimeClient,
138
+ # ... all other domain clients
139
+ )
140
+ ```
141
+
142
+ ## Type Safety
143
+
144
+ All commands and events use Pydantic models for validation:
145
+
146
+ ```python
147
+ # Parameters are validated
148
+ await page.navigate(
149
+ url="https://example.com",
150
+ referrer="https://google.com", # Optional parameter
151
+ transition_type="link" # Validated against allowed values
152
+ )
153
+
154
+ # Return values are typed
155
+ result = await runtime.evaluate(expression="1 + 1")
156
+ print(result.result.value) # Pydantic model with full IDE support
157
+ ```
158
+
159
+ ## Code Generation
160
+
161
+ The CDP bindings are generated from the official Chrome DevTools Protocol specifications. To regenerate:
162
+
163
+ ```bash
164
+ uv run python -m pydantic_cpd.generator
165
+ ```
166
+
167
+ This downloads the latest protocol definitions and generates:
168
+ - `pydantic_cpd/domains/*/types.py` - Type definitions
169
+ - `pydantic_cpd/domains/*/commands.py` - Command parameters and results
170
+ - `pydantic_cpd/domains/*/events.py` - Event definitions
171
+ - `pydantic_cpd/domains/*/library.py` - Domain client classes
172
+
173
+ ## Project Structure
174
+
175
+ ```
176
+ pydantic_cpd/
177
+ ├── client.py # Core CDP WebSocket client
178
+ ├── events.py # Event dispatcher
179
+ ├── exceptions.py # CDP exceptions
180
+ ├── domains/ # Generated CDP bindings
181
+ │ ├── page/
182
+ │ ├── runtime/
183
+ │ ├── network/
184
+ │ └── ... (42 domains)
185
+ └── generator/ # Code generation tools
186
+ ```
187
+
188
+ ## Requirements
189
+
190
+ - Python 3.14+
191
+ - pydantic >= 2.12
192
+ - websockets >= 15.0
193
+ - httpx >= 0.28
194
+
195
+
196
+ ## Inspiration
197
+
198
+ The idea for automatic code generation and the overall approach is inspired by [cdp-use](https://github.com/browser-use/cdp-use). This project adapts that concept for Python, with a slightly different API design and type system using Pydantic models.
199
+
200
+ ## Links
201
+
202
+ - [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)
203
+ - [Protocol Repository](https://github.com/ChromeDevTools/devtools-protocol)
cdpify-0.1.0/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # Pydantic CDP
2
+
3
+ Type-safe Python client for the Chrome DevTools Protocol (CDP) with Pydantic validation.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install cdpify
9
+ ```
10
+
11
+ ## What it does
12
+
13
+ This library provides Python bindings for the Chrome DevTools Protocol with full type safety through Pydantic models. All CDP domains, commands, events, and types are automatically generated from the official Chrome DevTools Protocol specifications.
14
+
15
+ ## Usage
16
+
17
+ ### Basic Connection
18
+
19
+ ```python
20
+ import asyncio
21
+ from cdpify import CDPClient
22
+
23
+ async def main():
24
+ async with CDPClient("ws://localhost:9222/devtools/browser/...") as client:
25
+ # Send CDP commands
26
+ result = await client.send_raw(
27
+ method="Target.getTargets",
28
+ params=None
29
+ )
30
+ print(f"Targets: {result}")
31
+
32
+ asyncio.run(main())
33
+ ```
34
+
35
+ ### Using Domain Clients
36
+
37
+ Domain-specific clients provide typed methods for all CDP commands:
38
+
39
+ ```python
40
+ from cdpify import CDPClient
41
+ from cdpify.domains import PageClient, RuntimeClient
42
+
43
+ async def main():
44
+ async with CDPClient("ws://localhost:9222/devtools/page/...") as client:
45
+ # Initialize domain clients
46
+ page = PageClient(client)
47
+ runtime = RuntimeClient(client)
48
+
49
+ # Navigate to a page
50
+ await page.navigate(url="https://example.com")
51
+
52
+ # Evaluate JavaScript
53
+ result = await runtime.evaluate(
54
+ expression="document.title",
55
+ return_by_value=True
56
+ )
57
+ print(f"Page title: {result.result.value}")
58
+
59
+ asyncio.run(main())
60
+ ```
61
+
62
+ ### Event Handling
63
+
64
+ Register handlers for CDP events:
65
+
66
+ ```python
67
+ from cdpify import CDPClient
68
+ from cdpify.domains import PageClient
69
+
70
+ async def main():
71
+ client = CDPClient("ws://localhost:9222/devtools/page/...")
72
+ await client.connect()
73
+
74
+ # Register event handler
75
+ @client.on("Page.loadEventFired")
76
+ async def on_load(params, session_id):
77
+ print(f"Page loaded at timestamp: {params['timestamp']}")
78
+
79
+ # Wildcard handler for all events
80
+ @client.on()
81
+ async def on_any_event(params, session_id):
82
+ print(f"Event received: {params}")
83
+
84
+ page = PageClient(client)
85
+ await page.enable()
86
+ await page.navigate(url="https://example.com")
87
+
88
+ await asyncio.sleep(5)
89
+ await client.disconnect()
90
+
91
+ asyncio.run(main())
92
+ ```
93
+
94
+ ### Configuration
95
+
96
+ ```python
97
+ client = CDPClient(
98
+ url="ws://localhost:9222/devtools/browser/...",
99
+ additional_headers={"Authorization": "Bearer token"},
100
+ max_frame_size=100 * 1024 * 1024, # 100MB
101
+ default_timeout=30.0 # seconds
102
+ )
103
+ ```
104
+
105
+ ## Available Domain Clients
106
+
107
+ All CDP domains are available as typed clients:
108
+
109
+ - `PageClient` - Page lifecycle, navigation, screenshots
110
+ - `RuntimeClient` - JavaScript execution, console, objects
111
+ - `NetworkClient` - Network monitoring, request interception
112
+ - `DOMClient` - DOM tree access and manipulation
113
+ - `DebuggerClient` - JavaScript debugging
114
+ - `EmulationClient` - Device emulation, geolocation
115
+ - `PerformanceClient` - Performance metrics
116
+ - `SecurityClient` - Security state, certificates
117
+ - And 40+ more domains...
118
+
119
+ Import them from the root package:
120
+
121
+ ```python
122
+ from cdpify import (
123
+ CDPClient,
124
+ PageClient,
125
+ NetworkClient,
126
+ RuntimeClient,
127
+ # ... all other domain clients
128
+ )
129
+ ```
130
+
131
+ ## Type Safety
132
+
133
+ All commands and events use Pydantic models for validation:
134
+
135
+ ```python
136
+ # Parameters are validated
137
+ await page.navigate(
138
+ url="https://example.com",
139
+ referrer="https://google.com", # Optional parameter
140
+ transition_type="link" # Validated against allowed values
141
+ )
142
+
143
+ # Return values are typed
144
+ result = await runtime.evaluate(expression="1 + 1")
145
+ print(result.result.value) # Pydantic model with full IDE support
146
+ ```
147
+
148
+ ## Code Generation
149
+
150
+ The CDP bindings are generated from the official Chrome DevTools Protocol specifications. To regenerate:
151
+
152
+ ```bash
153
+ uv run python -m pydantic_cpd.generator
154
+ ```
155
+
156
+ This downloads the latest protocol definitions and generates:
157
+ - `pydantic_cpd/domains/*/types.py` - Type definitions
158
+ - `pydantic_cpd/domains/*/commands.py` - Command parameters and results
159
+ - `pydantic_cpd/domains/*/events.py` - Event definitions
160
+ - `pydantic_cpd/domains/*/library.py` - Domain client classes
161
+
162
+ ## Project Structure
163
+
164
+ ```
165
+ pydantic_cpd/
166
+ ├── client.py # Core CDP WebSocket client
167
+ ├── events.py # Event dispatcher
168
+ ├── exceptions.py # CDP exceptions
169
+ ├── domains/ # Generated CDP bindings
170
+ │ ├── page/
171
+ │ ├── runtime/
172
+ │ ├── network/
173
+ │ └── ... (42 domains)
174
+ └── generator/ # Code generation tools
175
+ ```
176
+
177
+ ## Requirements
178
+
179
+ - Python 3.14+
180
+ - pydantic >= 2.12
181
+ - websockets >= 15.0
182
+ - httpx >= 0.28
183
+
184
+
185
+ ## Inspiration
186
+
187
+ The idea for automatic code generation and the overall approach is inspired by [cdp-use](https://github.com/browser-use/cdp-use). This project adapts that concept for Python, with a slightly different API design and type system using Pydantic models.
188
+
189
+ ## Links
190
+
191
+ - [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)
192
+ - [Protocol Repository](https://github.com/ChromeDevTools/devtools-protocol)
@@ -0,0 +1,17 @@
1
+ from .client import CDPClient
2
+ from .exceptions import (
3
+ CDPCommandException,
4
+ CDPConnectionException,
5
+ CDPException,
6
+ CDPTimeoutException,
7
+ )
8
+
9
+ __all__ = [
10
+ # CDPClient
11
+ "CDPClient",
12
+ # Exceptions
13
+ "CDPException",
14
+ "CDPConnectionException",
15
+ "CDPCommandException",
16
+ "CDPTimeoutException",
17
+ ]
@@ -0,0 +1,243 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ from typing import Any
5
+
6
+ import websockets
7
+ from websockets.asyncio.client import ClientConnection, connect
8
+
9
+ from cdpify.events import EventDispatcher, EventHandler
10
+ from cdpify.exceptions import (
11
+ CDPCommandException,
12
+ CDPConnectionException,
13
+ CDPTimeoutException,
14
+ )
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class CDPClient:
20
+ def __init__(
21
+ self,
22
+ url: str,
23
+ *,
24
+ additional_headers: dict[str, str] | None = None,
25
+ max_frame_size: int = 100 * 1024 * 1024,
26
+ default_timeout: float = 30.0,
27
+ ) -> None:
28
+ self.url: str = url
29
+ self.additional_headers: dict[str, str] | None = additional_headers
30
+ self.max_frame_size: int = max_frame_size
31
+ self.default_timeout: float = default_timeout
32
+
33
+ self._ws: ClientConnection | None = None
34
+ self._next_message_id: int = 0
35
+ self._pending_requests: dict[int, asyncio.Future[dict[str, Any]]] = {}
36
+ self._message_loop_task: asyncio.Task[None] | None = None
37
+ self._events: EventDispatcher = EventDispatcher()
38
+ self._is_shutting_down: bool = False
39
+
40
+ async def __aenter__(self) -> CDPClient:
41
+ await self.connect()
42
+ return self
43
+
44
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
45
+ await self.disconnect()
46
+
47
+ @property
48
+ def is_connected(self) -> bool:
49
+ return self._ws is not None
50
+
51
+ def on(self, event_name: str | None = None):
52
+ """
53
+ Register event handler.
54
+
55
+ Usage:
56
+ @client.on("Page.loadEventFired")
57
+ async def on_load(params, session_id):
58
+ print("Page loaded!")
59
+
60
+ @client.on() # Wildcard - all events
61
+ async def on_any(params, session_id):
62
+ print(f"Event: {params}")
63
+ """
64
+
65
+ def decorator(func: EventHandler) -> EventHandler:
66
+ self._events.add_handler(event_name, func)
67
+ return func
68
+
69
+ return decorator
70
+
71
+ def add_event_listener(self, event_name: str | None, handler: EventHandler) -> None:
72
+ self._events.add_handler(event_name, handler)
73
+
74
+ def remove_event_listener(
75
+ self, event_name: str | None, handler: EventHandler
76
+ ) -> None:
77
+ self._events.remove_handler(event_name, handler)
78
+
79
+ async def connect(self) -> None:
80
+ if self._ws is not None:
81
+ raise CDPConnectionException("Already connected")
82
+
83
+ logger.info(f"Connecting to {self.url}")
84
+
85
+ try:
86
+ self._ws = await connect(
87
+ self.url,
88
+ max_size=self.max_frame_size,
89
+ additional_headers=self.additional_headers,
90
+ )
91
+ self._is_shutting_down = False
92
+ self._message_loop_task = asyncio.create_task(self._run_message_loop())
93
+ logger.info("Connected")
94
+ except Exception as e:
95
+ raise CDPConnectionException(f"Connection failed: {e}") from e
96
+
97
+ async def disconnect(self) -> None:
98
+ if self._is_shutting_down:
99
+ return
100
+
101
+ self._is_shutting_down = True
102
+ logger.info("Disconnecting...")
103
+
104
+ await self._stop_message_loop()
105
+ self._cancel_pending_requests()
106
+ await self._close_websocket()
107
+
108
+ logger.info("Disconnected")
109
+
110
+ async def send_raw(
111
+ self,
112
+ method: str,
113
+ params: dict[str, Any] | None = None,
114
+ session_id: str | None = None,
115
+ timeout: float | None = None,
116
+ ) -> dict[str, Any]:
117
+ if not self.is_connected:
118
+ raise CDPConnectionException("Not connected")
119
+
120
+ timeout = timeout or self.default_timeout
121
+ msg_id = self._next_message_id
122
+ self._next_message_id += 1
123
+
124
+ message = self._build_message(msg_id, method, params, session_id)
125
+ future = self._create_pending_request(msg_id)
126
+
127
+ try:
128
+ await self._send(msg_id, method, message)
129
+ return await self._await_response(msg_id, method, future, timeout)
130
+ finally:
131
+ self._pending_requests.pop(msg_id, None)
132
+
133
+ def _build_message(
134
+ self,
135
+ msg_id: int,
136
+ method: str,
137
+ params: dict[str, Any] | None,
138
+ session_id: str | None,
139
+ ) -> dict[str, Any]:
140
+ message = {"id": msg_id, "method": method, "params": params or {}}
141
+ if session_id:
142
+ message["sessionId"] = session_id
143
+ return message
144
+
145
+ def _create_pending_request(self, msg_id: int) -> asyncio.Future[dict[str, Any]]:
146
+ future: asyncio.Future[dict[str, Any]] = asyncio.Future()
147
+ self._pending_requests[msg_id] = future
148
+ return future
149
+
150
+ async def _send(self, msg_id: int, method: str, message: dict[str, Any]) -> None:
151
+ logger.debug(f"→ #{msg_id}: {method}")
152
+ await self._ws.send(json.dumps(message))
153
+
154
+ async def _await_response(
155
+ self, msg_id: int, method: str, future: asyncio.Future, timeout: float
156
+ ) -> dict[str, Any]:
157
+ try:
158
+ result = await asyncio.wait_for(future, timeout=timeout)
159
+ logger.debug(f"← #{msg_id}: OK")
160
+ return result
161
+ except asyncio.TimeoutError:
162
+ raise CDPTimeoutException(f"{method} timed out after {timeout}s") from None
163
+ except (CDPCommandException, CDPConnectionException):
164
+ raise
165
+ except Exception as e:
166
+ raise CDPConnectionException(f"Command failed: {e}") from e
167
+
168
+ async def _run_message_loop(self) -> None:
169
+ try:
170
+ async for raw_message in self._ws:
171
+ if self._is_shutting_down:
172
+ break
173
+ await self._process_message(raw_message)
174
+ except websockets.exceptions.ConnectionClosed:
175
+ logger.info("Connection closed")
176
+ except asyncio.CancelledError:
177
+ logger.debug("Message loop cancelled")
178
+ raise
179
+ except Exception as e:
180
+ logger.exception(f"Message loop error: {e}")
181
+ finally:
182
+ if not self._is_shutting_down:
183
+ await self.disconnect()
184
+
185
+ async def _process_message(self, raw: str) -> None:
186
+ try:
187
+ msg = json.loads(raw)
188
+
189
+ if "id" in msg:
190
+ await self._handle_response(msg)
191
+ elif "method" in msg:
192
+ await self._handle_event(msg)
193
+ else:
194
+ logger.warning(f"Unknown message format: {msg}")
195
+ except json.JSONDecodeError as e:
196
+ logger.error(f"Invalid JSON: {e}")
197
+
198
+ async def _handle_response(self, msg: dict[str, Any]) -> None:
199
+ msg_id = msg["id"]
200
+ future = self._pending_requests.get(msg_id)
201
+
202
+ if not future or future.done():
203
+ return
204
+
205
+ if "error" in msg:
206
+ future.set_exception(CDPCommandException(msg["error"]))
207
+ else:
208
+ future.set_result(msg.get("result", {}))
209
+
210
+ async def _handle_event(self, msg: dict[str, Any]) -> None:
211
+ method = msg["method"]
212
+ params = msg.get("params", {})
213
+ session_id = msg.get("sessionId")
214
+
215
+ logger.debug(f"Event: {method}")
216
+ handled = await self._events.dispatch(method, params, session_id)
217
+
218
+ if not handled:
219
+ logger.debug(f"Unhandled event: {method}")
220
+
221
+ async def _stop_message_loop(self) -> None:
222
+ if self._message_loop_task and not self._message_loop_task.done():
223
+ self._message_loop_task.cancel()
224
+ try:
225
+ await self._message_loop_task
226
+ except asyncio.CancelledError:
227
+ pass
228
+
229
+ def _cancel_pending_requests(self) -> None:
230
+ error = CDPConnectionException("Disconnected")
231
+ for future in self._pending_requests.values():
232
+ if not future.done():
233
+ future.set_exception(error)
234
+ self._pending_requests.clear()
235
+
236
+ async def _close_websocket(self) -> None:
237
+ if self._ws:
238
+ try:
239
+ await self._ws.close()
240
+ except Exception as e:
241
+ logger.debug(f"Websocket close error: {e}")
242
+ finally:
243
+ self._ws = None