webtap-tool 0.5.2__py3-none-any.whl → 0.7.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 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,31 @@ body(123, "msgpack.unpackb(body)") # Binary formats
26
26
  ```
27
27
 
28
28
  #### Tips
29
+ - **Generate models:** `to_model({id}, "models/model.py", "Model")` - 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", "Product") # Generate from full response
42
+ to_model(123, "models/customers/group.py", "CustomerGroup", "Data[0]") # Extract nested + domain structure
43
+ to_model(123, "/tmp/item.py", "Item", "items[0]") # Extract array items
44
+ ```
45
+
46
+ #### Tips
47
+ - **Check structure first:** `body({id})` - preview JSON before generating
48
+ - **Domain organization:** Use paths like `"models/customers/group.py"` for structure
49
+ - **Extract nested data:** Use `json_path="Data[0]"` to extract specific objects
50
+ - **Array items:** Extract first item with `json_path="items[0]"` for model generation
51
+ - **Auto-cleanup:** Generated models use snake_case fields and modern type hints (list, dict, | None)
52
+ - **Edit after:** Add `Field(alias="...")` manually for API field mapping
53
+
35
54
  ### inspect
36
55
  Inspect CDP events with full Python debugging.
37
56
 
@@ -62,6 +81,7 @@ Show network requests with full data.
62
81
 
63
82
  #### Tips
64
83
  - **Analyze responses:** `body({id})` - fetch response body
84
+ - **Generate models:** `to_model({id}, "models/model.py", "Model")` - create Pydantic models from JSON
65
85
  - **Parse HTML:** `body({id}, "bs4(body, 'html.parser').find('title').text")`
66
86
  - **Extract JSON:** `body({id}, "json.loads(body)['data']")`
67
87
  - **Find patterns:** `body({id}, "re.findall(r'/api/\\w+', body)")`
@@ -0,0 +1,136 @@
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, model_name: 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/customers/group.py")
21
+ model_name: Class name for generated model (e.g., "CustomerGroup")
22
+ json_path: Optional JSON path to extract nested data (e.g., "Data[0]")
23
+
24
+ Returns:
25
+ Success message with generation details
26
+ """
27
+ if error := check_connection(state):
28
+ return error
29
+
30
+ # Get response body from service
31
+ body_service = state.service.body
32
+ result = body_service.get_response_body(response, use_cache=True)
33
+
34
+ if "error" in result:
35
+ return error_response(result["error"])
36
+
37
+ body_content = result.get("body", "")
38
+ is_base64 = result.get("base64Encoded", False)
39
+
40
+ # Decode if needed
41
+ if is_base64:
42
+ decoded = body_service.decode_body(body_content, is_base64)
43
+ if isinstance(decoded, bytes):
44
+ return error_response(
45
+ "Response body is binary",
46
+ suggestions=["Only JSON responses can be converted to models", "Try a different response"],
47
+ )
48
+ body_content = decoded
49
+
50
+ # Parse JSON
51
+ try:
52
+ data = json.loads(body_content)
53
+ except json.JSONDecodeError as e:
54
+ return error_response(
55
+ f"Invalid JSON: {e}",
56
+ suggestions=[
57
+ "Response must be valid JSON",
58
+ "Check the response with body() first",
59
+ "Try a different response",
60
+ ],
61
+ )
62
+
63
+ # Extract JSON path if specified
64
+ if json_path:
65
+ try:
66
+ # Support simple bracket notation like "Data[0]"
67
+ parts = json_path.replace("[", ".").replace("]", "").split(".")
68
+ for part in parts:
69
+ if part:
70
+ if part.isdigit():
71
+ data = data[int(part)]
72
+ else:
73
+ data = data[part]
74
+ except (KeyError, IndexError, TypeError) as e:
75
+ return error_response(
76
+ f"JSON path extraction failed: {e}",
77
+ suggestions=[
78
+ f"Path '{json_path}' not found in response",
79
+ "Check the response structure with body()",
80
+ 'Try a simpler path like "Data" or "Data[0]"',
81
+ ],
82
+ )
83
+
84
+ # Ensure data is dict or list for model generation
85
+ if not isinstance(data, (dict, list)):
86
+ return error_response(
87
+ f"Extracted data is {type(data).__name__}, not dict or list",
88
+ suggestions=[
89
+ "Model generation requires dict or list structure",
90
+ "Adjust json_path to extract a complex object",
91
+ ],
92
+ )
93
+
94
+ # Create output directory if needed
95
+ output_path = Path(output).expanduser().resolve()
96
+ output_path.parent.mkdir(parents=True, exist_ok=True)
97
+
98
+ # Generate model using datamodel-codegen Python API
99
+ try:
100
+ generate(
101
+ json.dumps(data),
102
+ input_file_type=InputFileType.Json,
103
+ input_filename="response.json",
104
+ output=output_path,
105
+ output_model_type=DataModelType.PydanticV2BaseModel,
106
+ class_name=model_name, # Set generated class name
107
+ snake_case_field=True, # Convert to snake_case
108
+ use_standard_collections=True, # Use list instead of List
109
+ use_union_operator=True, # Use | instead of Union
110
+ )
111
+ except Exception as e:
112
+ return error_response(
113
+ f"Model generation failed: {e}",
114
+ suggestions=[
115
+ "Check that the JSON structure is valid",
116
+ "Try simplifying the JSON path",
117
+ "Ensure output directory is writable",
118
+ ],
119
+ )
120
+
121
+ # Count fields in generated model
122
+ try:
123
+ model_content = output_path.read_text()
124
+ field_count = model_content.count(": ") # Count field definitions
125
+ except Exception:
126
+ field_count = "unknown"
127
+
128
+ return success_response(
129
+ "Model generated successfully",
130
+ details={
131
+ "Class": model_name,
132
+ "Output": str(output_path),
133
+ "Fields": field_count,
134
+ "Size": f"{output_path.stat().st_size} bytes",
135
+ },
136
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webtap-tool
3
- Version: 0.5.2
3
+ Version: 0.7.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=UipzpRGS6riZxU7CgyW_eeWcB-6817P3egEO0pIdcp4,3790
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=LnzNHb6fhoeqEyNIhx5IOKd05nY-xCiDLCrJ5zBOmNY,8131
14
+ webtap/commands/TIPS.md,sha256=XwnPKY5AgLsxNw3q0aF4Amr0P891Pt4QIkw0_3AF58g,9293
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=jOb93t616m5weT75VyF506J6nydDXUWENF7cpscbe9Q,4962
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.5.2.dist-info/METADATA,sha256=Tcq1QufQPMu8KhNGAOnV0UIc6_y1jiXag6Oe0yuipgE,17588
48
- webtap_tool-0.5.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
49
- webtap_tool-0.5.2.dist-info/entry_points.txt,sha256=iFe575I0CIb1MbfPt0oX2VYyY5gSU_dA551PKVR83TU,39
50
- webtap_tool-0.5.2.dist-info/RECORD,,
48
+ webtap_tool-0.7.0.dist-info/METADATA,sha256=JAfV9YTu10f5ss15yatHtYFx8W8HJewrPthMBS0TwN4,17636
49
+ webtap_tool-0.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
50
+ webtap_tool-0.7.0.dist-info/entry_points.txt,sha256=iFe575I0CIb1MbfPt0oX2VYyY5gSU_dA551PKVR83TU,39
51
+ webtap_tool-0.7.0.dist-info/RECORD,,