webtap-tool 0.5.2__py3-none-any.whl → 0.6.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.
Potentially problematic release.
This version of webtap-tool might be problematic. Click here for more details.
- webtap/app.py +1 -0
- webtap/commands/TIPS.md +19 -0
- webtap/commands/to_model.py +129 -0
- {webtap_tool-0.5.2.dist-info → webtap_tool-0.6.0.dist-info}/METADATA +2 -1
- {webtap_tool-0.5.2.dist-info → webtap_tool-0.6.0.dist-info}/RECORD +7 -6
- {webtap_tool-0.5.2.dist-info → webtap_tool-0.6.0.dist-info}/WHEEL +0 -0
- {webtap_tool-0.5.2.dist-info → webtap_tool-0.6.0.dist-info}/entry_points.txt +0 -0
webtap/app.py
CHANGED
|
@@ -89,6 +89,7 @@ else:
|
|
|
89
89
|
from webtap.commands import inspect # noqa: E402, F401
|
|
90
90
|
from webtap.commands import fetch # noqa: E402, F401
|
|
91
91
|
from webtap.commands import body # noqa: E402, F401
|
|
92
|
+
from webtap.commands import to_model # noqa: E402, F401
|
|
92
93
|
from webtap.commands import selections # noqa: E402, F401
|
|
93
94
|
from webtap.commands import server # noqa: E402, F401
|
|
94
95
|
from webtap.commands import setup # noqa: E402, F401
|
webtap/commands/TIPS.md
CHANGED
|
@@ -26,12 +26,30 @@ body(123, "msgpack.unpackb(body)") # Binary formats
|
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
#### Tips
|
|
29
|
+
- **Generate models:** `to_model({id}, "models/model.py")` - create Pydantic models from JSON
|
|
29
30
|
- **Chain requests:** `body({id}, "httpx.get(json.loads(body)['next_url']).text[:100]")`
|
|
30
31
|
- **Parse XML:** `body({id}, "ElementTree.fromstring(body).find('.//title').text")`
|
|
31
32
|
- **Extract forms:** `body({id}, "[f['action'] for f in bs4(body, 'html.parser').find_all('form')]")`
|
|
32
33
|
- **Decode protobuf:** `body({id}, "protobuf_json.Parse(body, YourMessage())")`
|
|
33
34
|
- **Find related:** `events({'requestId': request_id})` - related events
|
|
34
35
|
|
|
36
|
+
### to_model
|
|
37
|
+
Generate Pydantic v2 models from JSON response bodies for reverse engineering APIs.
|
|
38
|
+
|
|
39
|
+
#### Examples
|
|
40
|
+
```python
|
|
41
|
+
to_model(123, "models/product.py") # Generate from full response
|
|
42
|
+
to_model(123, "models/customer.py", json_path="Data[0]") # Extract nested object
|
|
43
|
+
to_model(123, "/tmp/model.py", json_path="items") # Extract array items
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
#### Tips
|
|
47
|
+
- **Check structure first:** `body({id})` - preview JSON before generating
|
|
48
|
+
- **Extract nested data:** Use `json_path="Data[0]"` to extract specific objects
|
|
49
|
+
- **Array items:** Extract first item with `json_path="items[0]"` for model generation
|
|
50
|
+
- **Auto-cleanup:** Generated models use snake_case fields and modern type hints (list, dict, | None)
|
|
51
|
+
- **Edit after:** Add `Field(alias="...")` manually for API field mapping
|
|
52
|
+
|
|
35
53
|
### inspect
|
|
36
54
|
Inspect CDP events with full Python debugging.
|
|
37
55
|
|
|
@@ -62,6 +80,7 @@ Show network requests with full data.
|
|
|
62
80
|
|
|
63
81
|
#### Tips
|
|
64
82
|
- **Analyze responses:** `body({id})` - fetch response body
|
|
83
|
+
- **Generate models:** `to_model({id}, "models/model.py")` - create Pydantic models from JSON
|
|
65
84
|
- **Parse HTML:** `body({id}, "bs4(body, 'html.parser').find('title').text")`
|
|
66
85
|
- **Extract JSON:** `body({id}, "json.loads(body)['data']")`
|
|
67
86
|
- **Find patterns:** `body({id}, "re.findall(r'/api/\\w+', body)")`
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Generate Pydantic models from HTTP response bodies."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from datamodel_code_generator import generate, InputFileType, DataModelType
|
|
6
|
+
from webtap.app import app
|
|
7
|
+
from webtap.commands._builders import check_connection, success_response, error_response
|
|
8
|
+
from webtap.commands._tips import get_mcp_description
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
mcp_desc = get_mcp_description("to_model")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command(display="markdown", fastmcp={"type": "tool", "description": mcp_desc} if mcp_desc else {"type": "tool"})
|
|
15
|
+
def to_model(state, response: int, output: str, json_path: str = None) -> dict: # pyright: ignore[reportArgumentType]
|
|
16
|
+
"""Generate Pydantic model from response body using datamodel-codegen.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
response: Response row ID from network() table
|
|
20
|
+
output: Output file path for generated model (e.g., "models/product.py")
|
|
21
|
+
json_path: Optional JSON path to extract nested data (e.g., "Data[0]")
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Success message with generation details
|
|
25
|
+
"""
|
|
26
|
+
if error := check_connection(state):
|
|
27
|
+
return error
|
|
28
|
+
|
|
29
|
+
# Get response body from service
|
|
30
|
+
body_service = state.service.body
|
|
31
|
+
result = body_service.get_response_body(response, use_cache=True)
|
|
32
|
+
|
|
33
|
+
if "error" in result:
|
|
34
|
+
return error_response(result["error"])
|
|
35
|
+
|
|
36
|
+
body_content = result.get("body", "")
|
|
37
|
+
is_base64 = result.get("base64Encoded", False)
|
|
38
|
+
|
|
39
|
+
# Decode if needed
|
|
40
|
+
if is_base64:
|
|
41
|
+
decoded = body_service.decode_body(body_content, is_base64)
|
|
42
|
+
if isinstance(decoded, bytes):
|
|
43
|
+
return error_response(
|
|
44
|
+
"Response body is binary",
|
|
45
|
+
suggestions=["Only JSON responses can be converted to models", "Try a different response"],
|
|
46
|
+
)
|
|
47
|
+
body_content = decoded
|
|
48
|
+
|
|
49
|
+
# Parse JSON
|
|
50
|
+
try:
|
|
51
|
+
data = json.loads(body_content)
|
|
52
|
+
except json.JSONDecodeError as e:
|
|
53
|
+
return error_response(
|
|
54
|
+
f"Invalid JSON: {e}",
|
|
55
|
+
suggestions=[
|
|
56
|
+
"Response must be valid JSON",
|
|
57
|
+
"Check the response with body() first",
|
|
58
|
+
"Try a different response",
|
|
59
|
+
],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Extract JSON path if specified
|
|
63
|
+
if json_path:
|
|
64
|
+
try:
|
|
65
|
+
# Support simple bracket notation like "Data[0]"
|
|
66
|
+
parts = json_path.replace("[", ".").replace("]", "").split(".")
|
|
67
|
+
for part in parts:
|
|
68
|
+
if part:
|
|
69
|
+
if part.isdigit():
|
|
70
|
+
data = data[int(part)]
|
|
71
|
+
else:
|
|
72
|
+
data = data[part]
|
|
73
|
+
except (KeyError, IndexError, TypeError) as e:
|
|
74
|
+
return error_response(
|
|
75
|
+
f"JSON path extraction failed: {e}",
|
|
76
|
+
suggestions=[
|
|
77
|
+
f"Path '{json_path}' not found in response",
|
|
78
|
+
"Check the response structure with body()",
|
|
79
|
+
'Try a simpler path like "Data" or "Data[0]"',
|
|
80
|
+
],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Ensure data is dict or list for model generation
|
|
84
|
+
if not isinstance(data, (dict, list)):
|
|
85
|
+
return error_response(
|
|
86
|
+
f"Extracted data is {type(data).__name__}, not dict or list",
|
|
87
|
+
suggestions=[
|
|
88
|
+
"Model generation requires dict or list structure",
|
|
89
|
+
"Adjust json_path to extract a complex object",
|
|
90
|
+
],
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Create output directory if needed
|
|
94
|
+
output_path = Path(output).expanduser().resolve()
|
|
95
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
96
|
+
|
|
97
|
+
# Generate model using datamodel-codegen Python API
|
|
98
|
+
try:
|
|
99
|
+
generate(
|
|
100
|
+
json.dumps(data),
|
|
101
|
+
input_file_type=InputFileType.Json,
|
|
102
|
+
input_filename="response.json",
|
|
103
|
+
output=output_path,
|
|
104
|
+
output_model_type=DataModelType.PydanticV2BaseModel,
|
|
105
|
+
snake_case_field=True, # Convert to snake_case
|
|
106
|
+
use_standard_collections=True, # Use list instead of List
|
|
107
|
+
use_union_operator=True, # Use | instead of Union
|
|
108
|
+
)
|
|
109
|
+
except Exception as e:
|
|
110
|
+
return error_response(
|
|
111
|
+
f"Model generation failed: {e}",
|
|
112
|
+
suggestions=[
|
|
113
|
+
"Check that the JSON structure is valid",
|
|
114
|
+
"Try simplifying the JSON path",
|
|
115
|
+
"Ensure output directory is writable",
|
|
116
|
+
],
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Count fields in generated model
|
|
120
|
+
try:
|
|
121
|
+
model_content = output_path.read_text()
|
|
122
|
+
field_count = model_content.count(": ") # Count field definitions
|
|
123
|
+
except Exception:
|
|
124
|
+
field_count = "unknown"
|
|
125
|
+
|
|
126
|
+
return success_response(
|
|
127
|
+
"Model generated successfully",
|
|
128
|
+
details={"Output": str(output_path), "Fields": field_count, "Size": f"{output_path.stat().st_size} bytes"},
|
|
129
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: webtap-tool
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Terminal-based web page inspector for AI debugging sessions
|
|
5
5
|
Author-email: Fredrik Angelsen <fredrikangelsen@gmail.com>
|
|
6
6
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -12,6 +12,7 @@ Classifier: Topic :: Software Development :: Debuggers
|
|
|
12
12
|
Requires-Python: >=3.12
|
|
13
13
|
Requires-Dist: beautifulsoup4>=4.13.5
|
|
14
14
|
Requires-Dist: cryptography>=45.0.6
|
|
15
|
+
Requires-Dist: datamodel-code-generator>=0.35.0
|
|
15
16
|
Requires-Dist: duckdb>=1.3.2
|
|
16
17
|
Requires-Dist: fastapi>=0.116.1
|
|
17
18
|
Requires-Dist: httpx>=0.28.1
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
webtap/VISION.md,sha256=kfoJfEPVc4chOrD9tNMDmYBY9rX9KB-286oZj70ALCE,7681
|
|
2
2
|
webtap/__init__.py,sha256=DFWJGmqZfX8_h4csLA5pKPR4SkaHBMlUgU-WQIE96Gw,1092
|
|
3
3
|
webtap/api.py,sha256=twDO_aA861yjjvrKquZ0phm2SIL07Wq37DmFjtVbTw4,17990
|
|
4
|
-
webtap/app.py,sha256=
|
|
4
|
+
webtap/app.py,sha256=75x_MQ2sjt6-HJi-YYRdFoKc3ss6SQfgFVArE1QKs3M,3851
|
|
5
5
|
webtap/filters.py,sha256=kRCicGMSV3R_zSvwzqZqksnry6jxJNdXRcgWvpoBLfc,13323
|
|
6
6
|
webtap/cdp/README.md,sha256=0TS0V_dRgRAzBqhddpXWD4S0YVi5wI4JgFJSll_KUBE,5660
|
|
7
7
|
webtap/cdp/__init__.py,sha256=c6NFG0XJnAa5GTe9MLr9mDZcLZqoTQN7A1cvvOfLcgY,453
|
|
@@ -11,7 +11,7 @@ webtap/cdp/schema/README.md,sha256=hnWCzbXYcYtWaZb_SgjVaFBiG81S9b9Y3x-euQFwQDo,1
|
|
|
11
11
|
webtap/cdp/schema/cdp_protocol.json,sha256=dp9_OLYLuVsQb1oV5r6MZfMzURscBLyAXUckdaPWyv4,1488452
|
|
12
12
|
webtap/cdp/schema/cdp_version.json,sha256=OhGy1qpfQjSe3Z7OqL6KynBFlDFBXxKGPZCY-ZN_lVU,399
|
|
13
13
|
webtap/commands/DEVELOPER_GUIDE.md,sha256=LYOhycZ3k5EHx5nREfkjvLz7vOs8pXCRLlcDm-keWao,11973
|
|
14
|
-
webtap/commands/TIPS.md,sha256=
|
|
14
|
+
webtap/commands/TIPS.md,sha256=9zXJM6VjP23vdODpT6bjWd69uWAnhfs12KapK3Dd8H4,9126
|
|
15
15
|
webtap/commands/__init__.py,sha256=rr3xM_bY0BgxkDOjsnsI8UBhjlz7nqiYlgJ8fjiJ1jQ,270
|
|
16
16
|
webtap/commands/_builders.py,sha256=SYacZmZTdkolQ7OOf3rFtFPCjkukY8z020WFA-i_O_A,7902
|
|
17
17
|
webtap/commands/_tips.py,sha256=SleMpwdghrHNqdzR60Cu8T0NZqJfWfcfrgIcyWI6GIQ,4793
|
|
@@ -30,6 +30,7 @@ webtap/commands/network.py,sha256=gEOg_u7VF9A5aKv5myzLCuvfAUkF1OPxsuj4UAgbS44,31
|
|
|
30
30
|
webtap/commands/selections.py,sha256=M001d_Gc51aSTuVeXGa19LDh2ZGR_qBJEjVGKpcGGFM,4895
|
|
31
31
|
webtap/commands/server.py,sha256=DOcIgYuKp0ydwrK9EA3hGwqOwfwM9DABhdPu3hk_jjo,6948
|
|
32
32
|
webtap/commands/setup.py,sha256=dov1LaN50nAEMNIuBLSK7mcnwhfn9rtqdTopBm1-PhA,9648
|
|
33
|
+
webtap/commands/to_model.py,sha256=_KVGgptMkYSWgCTA9ekfAneIpwVeCcqIl6f9gwarYUQ,4719
|
|
33
34
|
webtap/services/README.md,sha256=rala_jtnNgSiQ1lFLM7x_UQ4SJZDceAm7dpkQMRTYaI,2346
|
|
34
35
|
webtap/services/__init__.py,sha256=IjFqu0Ak6D-r18aokcQMtenDV3fbelvfjTCejGv6CZ0,570
|
|
35
36
|
webtap/services/body.py,sha256=XQPa19y5eUc3XJ2TuwVK6kffO1VQoKqNs33MBBz7hzU,3913
|
|
@@ -44,7 +45,7 @@ webtap/services/setup/desktop.py,sha256=fXwQa201W-s2mengm_dJZ9BigJopVrO9YFUQcW_T
|
|
|
44
45
|
webtap/services/setup/extension.py,sha256=iJY43JlQO6Vicgd9Mz6Mw0LQfbBNUGhnwI8n-LnvHBY,3602
|
|
45
46
|
webtap/services/setup/filters.py,sha256=lAPSLMH_KZQO-7bRkmURwzforx7C3SDrKEw2ZogN-Lo,3220
|
|
46
47
|
webtap/services/setup/platform.py,sha256=7yn-7LQFffgerWzWRtOG-yNEsR36ICThYUAu_N2FAso,4532
|
|
47
|
-
webtap_tool-0.
|
|
48
|
-
webtap_tool-0.
|
|
49
|
-
webtap_tool-0.
|
|
50
|
-
webtap_tool-0.
|
|
48
|
+
webtap_tool-0.6.0.dist-info/METADATA,sha256=9a8cGzDHRhYnS-aamgu_IW5C3zCtY4pOVmbC-YDZucQ,17636
|
|
49
|
+
webtap_tool-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
50
|
+
webtap_tool-0.6.0.dist-info/entry_points.txt,sha256=iFe575I0CIb1MbfPt0oX2VYyY5gSU_dA551PKVR83TU,39
|
|
51
|
+
webtap_tool-0.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|