Skip to content

Commit 16ee980

Browse files
authored
Merge pull request #120 from Integration-Automation/dev
Add diagram editor with Mermaid import and image support
2 parents 4638528 + 811ab60 commit 16ee980

14 files changed

Lines changed: 3325 additions & 2 deletions

CLAUDE.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# PyBreeze
2+
3+
Automation-first Python IDE built on PySide6 + JEditor, integrating Web/API/GUI/Load testing into a single environment.
4+
5+
## Architecture
6+
7+
**Layered architecture with Facade + Strategy patterns:**
8+
9+
```
10+
pybreeze/
11+
├── __init__.py # Public API facade (start_editor, plugin re-exports)
12+
├── pybreeze_ui/ # Presentation layer (PySide6 widgets)
13+
│ ├── editor_main/ # Main window (extends JEditor)
14+
│ ├── menu/ # Menu bar builders (automation, install, tools, plugins)
15+
│ ├── connect_gui/ssh/ # SSH client widgets
16+
│ ├── extend_ai_gui/ # LLM code review & prompt editors
17+
│ ├── jupyter_lab_gui/ # JupyterLab tab integration
18+
│ ├── syntax/ # Automation keyword highlighting definitions
19+
│ └── show_code_window/ # CodeWindow - output display widget
20+
├── extend/
21+
│ ├── process_executor/ # Process isolation layer (Strategy pattern)
22+
│ │ ├── python_task_process_manager.py # Core: TaskProcessManager (subprocess + thread + QTimer)
23+
│ │ ├── process_executor_utils.py # Factory functions: build_process / start_process
24+
│ │ ├── file_runner_process.py # FileRunnerProcess for plugin run configs
25+
│ │ ├── api_testka/ # Each module delegates to build_process with its package name
26+
│ │ ├── auto_control/
27+
│ │ ├── web_runner/
28+
│ │ ├── load_density/
29+
│ │ ├── file_automation/
30+
│ │ ├── mail_thunder/
31+
│ │ └── test_pioneer/ # TestPioneerProcess (custom variant)
32+
│ └── mail_thunder_extend/ # Post-test email report hook
33+
├── extend_multi_language/ # Built-in i18n (English, Traditional Chinese)
34+
└── utils/
35+
├── exception/ # Exception hierarchy (ITEException base)
36+
├── logging/ # pybreeze_logger
37+
├── file_process/ # File/directory utilities
38+
├── json_format/ # JSON processing
39+
└── manager/package_manager/ # PackageManager class
40+
```
41+
42+
**Key design patterns in use:**
43+
- **Facade**: `pybreeze/__init__.py` exposes `start_editor()`, `EDITOR_EXTEND_TAB`, and plugin APIs
44+
- **Strategy**: Each automation module (`api_testka`, `web_runner`, etc.) is a strategy that delegates to `TaskProcessManager` via `build_process()`
45+
- **Template Method**: `TaskProcessManager` defines the subprocess lifecycle (start -> read stdout/stderr threads -> QTimer poll -> drain -> exit)
46+
- **Observer**: QTimer-based polling bridges subprocess output to PySide6 UI thread via thread-safe Queues
47+
- **Plugin System**: Auto-discovery from `jeditor_plugins/` directory; plugins register via `register()` function
48+
49+
## Key types
50+
51+
- `PyBreezeMainWindow` — main window class (extends JEditor), holds `tab_widget` and `current_run_code_window`
52+
- `TaskProcessManager` — core process executor; manages subprocess, I/O threads, and QTimer UI updates
53+
- `CodeWindow` — output display widget passed to `TaskProcessManager`
54+
- `PackageManager` — pip wrapper for installing automation modules
55+
- `EDITOR_EXTEND_TAB: dict` — registry for custom tabs (key=name, value=QWidget subclass)
56+
57+
## Branching & CI
58+
59+
- `main` branch: stable releases, publishes `pybreeze` to PyPI
60+
- `dev` branch: development, publishes `pybreeze_dev` to PyPI
61+
- Version config: `pyproject.toml` (stable), `dev.toml` (dev) — keep both in sync when bumping
62+
- CI runs on GitHub Actions (Windows, Python 3.10/3.11/3.12)
63+
- CI steps: install deps -> pytest `test/test_utils/` -> start_automation_test -> extend_automation_test
64+
65+
## Development
66+
67+
```bash
68+
python -m pip install -r dev_requirements.txt
69+
python -m pytest test/test_utils/ -v --tb=short
70+
python -m pybreeze # launch the IDE
71+
```
72+
73+
**Testing:**
74+
- Unit tests: `test/test_utils/` (pure logic: exceptions, JSON, logger, file utils, package manager, venv path, jupyter helpers)
75+
- Integration tests: `test/unit_test/start_automation/` (launches IDE in debug_mode, verifies startup and extend tab)
76+
- Run all tests before submitting changes: `python -m pytest test/test_utils/ -v`
77+
78+
## Conventions
79+
80+
- Python 3.10+ — use `X | Y` union syntax, not `Union[X, Y]`
81+
- Use `from __future__ import annotations` for deferred type evaluation
82+
- Use `TYPE_CHECKING` guard for imports only needed by type hints (avoid circular imports)
83+
- PySide6 threading: never update UI from worker threads — use Queue + QTimer pattern (see `TaskProcessManager`)
84+
- Exception hierarchy: all custom exceptions inherit from `ITEException`
85+
- Logging: use `pybreeze_logger` from `pybreeze.utils.logging.logger`
86+
- Plugin API: `register_programming_language()` and `register_natural_language()` from `je_editor.plugins`
87+
- Delete all unused code — do not leave dead imports, unreachable functions, commented-out blocks, or unused variables. If code is not called by any execution path, remove it entirely. No `# TODO: remove later` or `_old_` prefixes — delete immediately.
88+
89+
## Security
90+
91+
All code must follow secure-by-default principles. Review every change against the checklist below before committing.
92+
93+
### General rules
94+
- Never use `eval()`, `exec()`, or `pickle.loads()` on untrusted data
95+
- Never use `subprocess.Popen(..., shell=True)` — always pass argument lists
96+
- Never log or display secrets, tokens, passwords, or API keys
97+
- Use `json.loads()` / `json.dumps()` for serialisation — never pickle
98+
- Validate all user input at system boundaries (file dialogs, URL inputs, network data)
99+
100+
### Network requests (SSRF prevention)
101+
- All outbound HTTP requests must go through `diagram_net_utils.safe_download_image()` or equivalent guards
102+
- Only `http://` and `https://` schemes are allowed — block `file://`, `ftp://`, `data:`, `gopher://`
103+
- Resolved IP addresses must be checked against private/loopback/link-local ranges (`ipaddress.is_private`, `is_loopback`, `is_link_local`, `is_reserved`)
104+
- Enforce download size limits (default: 20 MB) and connection timeouts (default: 15s)
105+
- Never pass user-supplied URLs directly to `urlopen()` without validation
106+
107+
### File I/O
108+
- File read/write paths from user dialogs (`QFileDialog`) are trusted (user-initiated)
109+
- File paths loaded from saved data (`.diagram.json`) must be validated before access:
110+
- Local paths: check `path.is_file()` and verify extension is in an allowlist
111+
- URLs: pass through the same SSRF validation as user-entered URLs
112+
- Never construct file paths by string concatenation with user input — use `pathlib.Path` with validation
113+
114+
### Qt / UI
115+
- `QGraphicsTextItem` with `TextEditorInteraction` must not be enabled by default — use double-click-to-edit pattern to prevent unintended text selection issues in themed environments
116+
- Plugin loading (`jeditor_plugins/`) uses auto-discovery — only load `.py` files, skip files starting with `_` or `.`
117+
118+
## Commit & PR rules
119+
120+
- Commit messages: short imperative sentence (e.g., "Update stable version", "Fix github actions")
121+
- Do not mention any AI tools, assistants, or co-authors in commit messages or PR descriptions
122+
- Do not add `Co-Authored-By` headers referencing any AI
123+
- PR target: `dev` for development work, `main` for stable releases

exe/auto_py_to_exe_setting.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@
6363
},
6464
{
6565
"optionDest": "collect_all",
66-
"value": "ipython"
66+
"value": "debugpy"
6767
},
6868
{
6969
"optionDest": "collect_all",
70-
"value": "debugpy"
70+
"value": "ipython"
7171
}
7272
],
7373
"nonPyinstallerOptions": {

pybreeze/extend_multi_language/extend_english.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,103 @@
296296
"file_tree_ctx_already_exists": "'{name}' already exists.",
297297
"file_tree_ctx_confirm_delete": "Confirm Delete",
298298
"file_tree_ctx_confirm_delete_message": "Are you sure you want to delete '{name}'?",
299+
# Diagram Editor — Menu
300+
"extend_tools_menu_diagram_editor_tab_action": "Diagram Editor Tab",
301+
"extend_tools_menu_diagram_editor_tab_label": "Diagram Editor",
302+
"extend_tools_menu_diagram_editor_dock_action": "Diagram Editor Dock",
303+
"extend_tools_menu_diagram_editor_dock_title": "Diagram Editor",
304+
# Diagram Editor — Tools
305+
"diagram_editor_tool_select": "Select",
306+
"diagram_editor_tool_rect": "Rect",
307+
"diagram_editor_tool_rounded_rect": "Rounded",
308+
"diagram_editor_tool_ellipse": "Ellipse",
309+
"diagram_editor_tool_diamond": "Diamond",
310+
"diagram_editor_tool_connection": "Connect",
311+
"diagram_editor_tool_text": "Text",
312+
# Diagram Editor — Actions
313+
"diagram_editor_action_new": "New",
314+
"diagram_editor_action_open": "Open",
315+
"diagram_editor_action_save": "Save",
316+
"diagram_editor_action_save_as": "Save As",
317+
"diagram_editor_action_import": "Import",
318+
"diagram_editor_action_export_png": "PNG",
319+
"diagram_editor_action_export_svg": "SVG",
320+
"diagram_editor_action_zoom_fit": "Fit",
321+
"diagram_editor_action_undo": "Undo",
322+
"diagram_editor_action_redo": "Redo",
323+
"diagram_editor_action_grid": "Grid",
324+
"diagram_editor_action_snap": "Snap",
325+
# Diagram Editor — Align
326+
"diagram_editor_align_menu": "Align",
327+
"diagram_editor_align_left": "Align Left",
328+
"diagram_editor_align_right": "Align Right",
329+
"diagram_editor_align_top": "Align Top",
330+
"diagram_editor_align_bottom": "Align Bottom",
331+
"diagram_editor_align_center_h": "Center Horizontal",
332+
"diagram_editor_align_center_v": "Center Vertical",
333+
"diagram_editor_distribute_h": "Distribute Horizontal",
334+
"diagram_editor_distribute_v": "Distribute Vertical",
335+
# Diagram Editor — Dialogs
336+
"diagram_editor_confirm_title": "Confirm",
337+
"diagram_editor_confirm_new": "Discard current diagram?",
338+
"diagram_editor_dialog_open": "Open Diagram",
339+
"diagram_editor_dialog_save": "Save Diagram",
340+
"diagram_editor_dialog_export_png": "Export PNG",
341+
"diagram_editor_dialog_export_svg": "Export SVG",
342+
"diagram_editor_error_title": "Error",
343+
# Diagram Editor — Property Panel
344+
"diagram_editor_prop_title": "Properties",
345+
"diagram_editor_prop_no_selection": "No selection",
346+
"diagram_editor_prop_node_group": "Node",
347+
"diagram_editor_prop_conn_group": "Connection",
348+
"diagram_editor_prop_text": "Text",
349+
"diagram_editor_prop_width": "Width",
350+
"diagram_editor_prop_height": "Height",
351+
"diagram_editor_prop_shape": "Shape",
352+
"diagram_editor_prop_fill_color": "Fill",
353+
"diagram_editor_prop_border_color": "Border",
354+
"diagram_editor_prop_font_size": "Font Size",
355+
"diagram_editor_prop_label": "Label",
356+
"diagram_editor_prop_line_style": "Style",
357+
"diagram_editor_prop_line_color": "Color",
358+
"diagram_editor_prop_line_width": "Width",
359+
# Diagram Editor — Shape / Style names
360+
"diagram_editor_shape_rectangle": "Rectangle",
361+
"diagram_editor_shape_rounded_rect": "Rounded Rect",
362+
"diagram_editor_shape_ellipse": "Ellipse",
363+
"diagram_editor_shape_diamond": "Diamond",
364+
"diagram_editor_style_solid": "Solid",
365+
"diagram_editor_style_dashed": "Dashed",
366+
"diagram_editor_style_dotted": "Dotted",
367+
# Diagram Editor — Context Menu
368+
"diagram_editor_ctx_delete": "Delete",
369+
"diagram_editor_ctx_duplicate": "Duplicate",
370+
"diagram_editor_ctx_bring_front": "Bring to Front",
371+
"diagram_editor_ctx_send_back": "Send to Back",
372+
"diagram_editor_ctx_select_all": "Select All",
373+
"diagram_editor_ctx_paste": "Paste",
374+
# Diagram Editor — Image
375+
"diagram_editor_tool_image_file": "Image",
376+
"diagram_editor_tool_image_url": "URL Image",
377+
"diagram_editor_dialog_image_file": "Open Image",
378+
"diagram_editor_dialog_image_url": "Image URL",
379+
"diagram_editor_dialog_image_url_hint": "Enter image URL:",
380+
"diagram_editor_image_load_failed": "Failed to load image.",
381+
"diagram_editor_prop_img_group": "Image",
382+
"diagram_editor_prop_caption": "Caption",
383+
"diagram_editor_prop_source": "Source",
384+
# Diagram Editor — Mermaid Import
385+
"diagram_editor_import_title": "Import Mermaid Diagram",
386+
"diagram_editor_import_hint": "Paste Mermaid flowchart code below:",
387+
"diagram_editor_import_convert": "Convert",
388+
"diagram_editor_import_cancel": "Cancel",
389+
"diagram_editor_import_error": "Parse Error",
390+
"diagram_editor_import_empty": "No nodes found in the input.",
391+
# Diagram Editor — Status Bar
392+
"diagram_editor_status_select": "Click to select, drag to move. Right-click to pan.",
393+
"diagram_editor_status_add_node": "Click canvas to place a node.",
394+
"diagram_editor_status_connection": "Click source node, then click target node.",
395+
"diagram_editor_status_text": "Click canvas to place a text node.",
299396
# Plugin Browser
300397
"plugin_browser_tab_name": "Plugin Browser",
301398
"plugin_browser_repo_label": "Repository URL:",

pybreeze/extend_multi_language/extend_traditional_chinese.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,103 @@
274274
"jupyterlab_loading": "載入中...",
275275
"jupyterlab_timeout": "JupyterLab 啟動超時",
276276
"jupyterlab_init_failed": "JupyterLab 啟動失敗",
277+
# Diagram Editor — 選單
278+
"extend_tools_menu_diagram_editor_tab_action": "架構圖編輯器分頁",
279+
"extend_tools_menu_diagram_editor_tab_label": "架構圖編輯器",
280+
"extend_tools_menu_diagram_editor_dock_action": "架構圖編輯器停駐窗格",
281+
"extend_tools_menu_diagram_editor_dock_title": "架構圖編輯器",
282+
# Diagram Editor — 工具
283+
"diagram_editor_tool_select": "選取",
284+
"diagram_editor_tool_rect": "矩形",
285+
"diagram_editor_tool_rounded_rect": "圓角",
286+
"diagram_editor_tool_ellipse": "橢圓",
287+
"diagram_editor_tool_diamond": "菱形",
288+
"diagram_editor_tool_connection": "連線",
289+
"diagram_editor_tool_text": "文字",
290+
# Diagram Editor — 動作
291+
"diagram_editor_action_new": "新增",
292+
"diagram_editor_action_open": "開啟",
293+
"diagram_editor_action_save": "儲存",
294+
"diagram_editor_action_save_as": "另存新檔",
295+
"diagram_editor_action_import": "匯入",
296+
"diagram_editor_action_export_png": "PNG",
297+
"diagram_editor_action_export_svg": "SVG",
298+
"diagram_editor_action_zoom_fit": "全覽",
299+
"diagram_editor_action_undo": "復原",
300+
"diagram_editor_action_redo": "重做",
301+
"diagram_editor_action_grid": "格線",
302+
"diagram_editor_action_snap": "對齊",
303+
# Diagram Editor — 對齊
304+
"diagram_editor_align_menu": "對齊",
305+
"diagram_editor_align_left": "靠左對齊",
306+
"diagram_editor_align_right": "靠右對齊",
307+
"diagram_editor_align_top": "靠上對齊",
308+
"diagram_editor_align_bottom": "靠下對齊",
309+
"diagram_editor_align_center_h": "水平置中",
310+
"diagram_editor_align_center_v": "垂直置中",
311+
"diagram_editor_distribute_h": "水平均分",
312+
"diagram_editor_distribute_v": "垂直均分",
313+
# Diagram Editor — 對話框
314+
"diagram_editor_confirm_title": "確認",
315+
"diagram_editor_confirm_new": "是否捨棄目前的架構圖?",
316+
"diagram_editor_dialog_open": "開啟架構圖",
317+
"diagram_editor_dialog_save": "儲存架構圖",
318+
"diagram_editor_dialog_export_png": "匯出 PNG",
319+
"diagram_editor_dialog_export_svg": "匯出 SVG",
320+
"diagram_editor_error_title": "錯誤",
321+
# Diagram Editor — 屬性面板
322+
"diagram_editor_prop_title": "屬性",
323+
"diagram_editor_prop_no_selection": "未選取",
324+
"diagram_editor_prop_node_group": "節點",
325+
"diagram_editor_prop_conn_group": "連線",
326+
"diagram_editor_prop_text": "文字",
327+
"diagram_editor_prop_width": "寬度",
328+
"diagram_editor_prop_height": "高度",
329+
"diagram_editor_prop_shape": "形狀",
330+
"diagram_editor_prop_fill_color": "填充",
331+
"diagram_editor_prop_border_color": "邊框",
332+
"diagram_editor_prop_font_size": "字體大小",
333+
"diagram_editor_prop_label": "標籤",
334+
"diagram_editor_prop_line_style": "樣式",
335+
"diagram_editor_prop_line_color": "顏色",
336+
"diagram_editor_prop_line_width": "寬度",
337+
# Diagram Editor — 形狀 / 樣式名稱
338+
"diagram_editor_shape_rectangle": "矩形",
339+
"diagram_editor_shape_rounded_rect": "圓角矩形",
340+
"diagram_editor_shape_ellipse": "橢圓",
341+
"diagram_editor_shape_diamond": "菱形",
342+
"diagram_editor_style_solid": "實線",
343+
"diagram_editor_style_dashed": "虛線",
344+
"diagram_editor_style_dotted": "點線",
345+
# Diagram Editor — 右鍵選單
346+
"diagram_editor_ctx_delete": "刪除",
347+
"diagram_editor_ctx_duplicate": "複製",
348+
"diagram_editor_ctx_bring_front": "移到最前",
349+
"diagram_editor_ctx_send_back": "移到最後",
350+
"diagram_editor_ctx_select_all": "全選",
351+
"diagram_editor_ctx_paste": "貼上",
352+
# Diagram Editor — 圖片
353+
"diagram_editor_tool_image_file": "圖片",
354+
"diagram_editor_tool_image_url": "網路圖片",
355+
"diagram_editor_dialog_image_file": "開啟圖片",
356+
"diagram_editor_dialog_image_url": "圖片網址",
357+
"diagram_editor_dialog_image_url_hint": "輸入圖片網址:",
358+
"diagram_editor_image_load_failed": "無法載入圖片。",
359+
"diagram_editor_prop_img_group": "圖片",
360+
"diagram_editor_prop_caption": "標題",
361+
"diagram_editor_prop_source": "來源",
362+
# Diagram Editor — Mermaid 匯入
363+
"diagram_editor_import_title": "匯入 Mermaid 架構圖",
364+
"diagram_editor_import_hint": "在下方貼上 Mermaid 流程圖程式碼:",
365+
"diagram_editor_import_convert": "轉換",
366+
"diagram_editor_import_cancel": "取消",
367+
"diagram_editor_import_error": "解析錯誤",
368+
"diagram_editor_import_empty": "輸入中找不到任何節點。",
369+
# Diagram Editor — 狀態列
370+
"diagram_editor_status_select": "點擊選取,拖曳移動。右鍵平移畫布。",
371+
"diagram_editor_status_add_node": "點擊畫布放置節點。",
372+
"diagram_editor_status_connection": "點擊來源節點,再點擊目標節點。",
373+
"diagram_editor_status_text": "點擊畫布放置文字節點。",
277374
# File Tree Context Menu
278375
"file_tree_ctx_new_file": "新增檔案",
279376
"file_tree_ctx_new_folder": "新增資料夾",

pybreeze/pybreeze_ui/diagram_editor/__init__.py

Whitespace-only changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
from PySide6.QtGui import QUndoCommand
6+
7+
if TYPE_CHECKING:
8+
from pybreeze.pybreeze_ui.diagram_editor.diagram_scene import DiagramScene
9+
10+
11+
class DiagramSnapshotCommand(QUndoCommand):
12+
"""Snapshot-based undo/redo: stores full scene state before and after a change."""
13+
14+
def __init__(self, scene: DiagramScene, description: str, old_data: dict, new_data: dict):
15+
super().__init__(description)
16+
self._scene = scene
17+
self._old_data = old_data
18+
self._new_data = new_data
19+
self._first_redo = True
20+
21+
def redo(self) -> None:
22+
if self._first_redo:
23+
self._first_redo = False
24+
return
25+
self._scene._restore_from_dict(self._new_data)
26+
27+
def undo(self) -> None:
28+
self._scene._restore_from_dict(self._old_data)

0 commit comments

Comments
 (0)