Automation-first Python IDE built on PySide6 + JEditor, integrating Web/API/GUI/Load testing into a single environment.
Layered architecture with Facade + Strategy patterns:
pybreeze/
├── __init__.py # Public API facade (start_editor, plugin re-exports)
├── pybreeze_ui/ # Presentation layer (PySide6 widgets)
│ ├── editor_main/ # Main window (extends JEditor)
│ ├── menu/ # Menu bar builders (automation, install, tools, plugins)
│ ├── connect_gui/ssh/ # SSH client widgets
│ ├── extend_ai_gui/ # LLM code review & prompt editors
│ ├── jupyter_lab_gui/ # JupyterLab tab integration
│ ├── syntax/ # Automation keyword highlighting definitions
│ └── show_code_window/ # CodeWindow - output display widget
├── extend/
│ ├── process_executor/ # Process isolation layer (Strategy pattern)
│ │ ├── python_task_process_manager.py # Core: TaskProcessManager (subprocess + thread + QTimer)
│ │ ├── process_executor_utils.py # Factory functions: build_process / start_process
│ │ ├── file_runner_process.py # FileRunnerProcess for plugin run configs
│ │ ├── api_testka/ # Each module delegates to build_process with its package name
│ │ ├── auto_control/
│ │ ├── web_runner/
│ │ ├── load_density/
│ │ ├── file_automation/
│ │ ├── mail_thunder/
│ │ └── test_pioneer/ # TestPioneerProcess (custom variant)
│ └── mail_thunder_extend/ # Post-test email report hook
├── extend_multi_language/ # Built-in i18n (English, Traditional Chinese)
└── utils/
├── exception/ # Exception hierarchy (ITEException base)
├── logging/ # pybreeze_logger
├── file_process/ # File/directory utilities
├── json_format/ # JSON processing
├── network/ # URL validation (SSRF prevention)
└── manager/package_manager/ # PackageManager class
Key design patterns in use:
- Facade:
pybreeze/__init__.pyexposesstart_editor(),EDITOR_EXTEND_TAB, and plugin APIs - Strategy: Each automation module (
api_testka,web_runner, etc.) is a strategy that delegates toTaskProcessManagerviabuild_process() - Template Method:
TaskProcessManagerdefines the subprocess lifecycle (start -> read stdout/stderr threads -> QTimer poll -> drain -> exit) - Observer: QTimer-based polling bridges subprocess output to PySide6 UI thread via thread-safe Queues
- Plugin System: Auto-discovery from
jeditor_plugins/directory; plugins register viaregister()function
PyBreezeMainWindow— main window class (extends JEditor), holdstab_widgetandcurrent_run_code_windowTaskProcessManager— core process executor; manages subprocess, I/O threads, and QTimer UI updatesCodeWindow— output display widget passed toTaskProcessManagerPackageManager— pip wrapper for installing automation modulesEDITOR_EXTEND_TAB: dict— registry for custom tabs (key=name, value=QWidget subclass)
mainbranch: stable releases, publishespybreezeto PyPIdevbranch: development, publishespybreeze_devto PyPI- Version config:
pyproject.toml(stable),dev.toml(dev) — keep both in sync when bumping - CI runs on GitHub Actions (Windows, Python 3.10/3.11/3.12)
- CI steps: install deps -> pytest
test/test_utils/-> start_automation_test -> extend_automation_test
python -m pip install -r dev_requirements.txt
python -m pytest test/test_utils/ -v --tb=short
python -m pybreeze # launch the IDETesting:
- Unit tests:
test/test_utils/(pure logic: exceptions, JSON, logger, file utils, package manager, venv path, jupyter helpers) - Integration tests:
test/unit_test/start_automation/(launches IDE in debug_mode, verifies startup and extend tab) - Run all tests before submitting changes:
python -m pytest test/test_utils/ -v
- Python 3.10+ — use
X | Yunion syntax, notUnion[X, Y] - Use
from __future__ import annotationsfor deferred type evaluation - Use
TYPE_CHECKINGguard for imports only needed by type hints (avoid circular imports) - PySide6 threading: never update UI from worker threads — use Queue + QTimer pattern (see
TaskProcessManager) - Exception hierarchy: all custom exceptions inherit from
ITEException - Logging: use
pybreeze_loggerfrompybreeze.utils.logging.logger - Plugin API:
register_programming_language()andregister_natural_language()fromje_editor.plugins - 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 lateror_old_prefixes — delete immediately.
All code must follow secure-by-default principles. Review every change against the checklist below before committing.
- Never use
eval(),exec(), orpickle.loads()on untrusted data - Never use
subprocess.Popen(..., shell=True)— always pass argument lists - Never log or display secrets, tokens, passwords, or API keys
- Use
json.loads()/json.dumps()for serialisation — never pickle - Never use
yaml.load()— always useyaml.safe_load() - Validate all user input at system boundaries (file dialogs, URL inputs, network data)
- Handle exceptions without leaking stack traces, file paths, or internal state to the user
- All outbound HTTP requests to user-specified URLs must validate the target before connecting:
- Only
http://andhttps://schemes — blockfile://,ftp://,data:,gopher:// - Resolve the hostname and check IPs against private/loopback/link-local/reserved ranges (
ipaddress.is_private,is_loopback,is_link_local,is_reserved) - Enforce connection timeouts (default: 15 s for downloads, 30 s for API calls)
- Enforce response size limits where applicable (default: 20 MB for binary downloads)
- Only
- Reference implementation:
diagram_net_utils._validate_url()andsafe_download_image() - For API-style requests (
requests.get/post): create or reuse a URL validation helper that performs scheme + IP checks, then call it before everyrequests.*call - Disable automatic redirect following (
allow_redirects=False) or re-validate the redirect target to prevent redirect-based SSRF - Never pass user-supplied URLs directly to
urlopen()orrequests.*without validation
- All HTTPS requests must use default TLS verification — never set
verify=False - SSH connections: never use
paramiko.AutoAddPolicy()orparamiko.WarningPolicy()— both silently accept unknown host keys and are vulnerable to MITM. UseInteractiveHostKeyPolicyfrompybreeze.pybreeze_ui.connect_gui.ssh.ssh_host_key_policy(viaapply_host_key_policy(client, parent_widget)), which prompts the user with the SHA256 fingerprint on first connection and persists confirmed keys to~/.pybreeze/ssh_known_hosts
- Always pass argument lists to
subprocess.Popen/subprocess.run— nevershell=True - Explicitly set
shell=Falsefor clarity in new code - Never interpolate user input into command strings — pass as separate list elements
- Set
timeouton allsubprocess.run()calls to prevent hangs - The IDE intentionally runs user-authored scripts; this is trusted local execution, not arbitrary remote code. Subprocess hardening protects against accidental shell injection, not against malicious local files
- The embedded JupyterLab server binds to
localhostonly and is intended for local development --ServerApp.token=and--ServerApp.password=are deliberately empty to enable seamless embedding — this is safe only because the server is localhost-only- Do not change
--ServerApp.ipto0.0.0.0or any externally-reachable address --ServerApp.disable_check_xsrf=Trueis required for the embedded QWebEngineView; do not expose the server externally with XSRF disabled
- File read/write paths from user dialogs (
QFileDialog) are trusted (user-initiated) - File paths loaded from saved data (
.diagram.json) must be validated before access:- Local paths: check
path.is_file()and verify extension is in an allowlist - URLs: pass through the same SSRF validation as user-entered URLs
- Local paths: check
- Never construct file paths by string concatenation with user input — use
pathlib.Pathwith validation - When writing to data directories (
.pybreeze/), create the directory withos.makedirs(exist_ok=True)and always useencoding="utf-8" - Never follow symlinks from untrusted sources — use
Path.resolve(strict=True)and verify the resolved path is still within expected boundaries
QGraphicsTextItemwithTextEditorInteractionmust not be enabled by default — use double-click-to-edit pattern to prevent unintended text selection issues in themed environments- Plugin loading (
jeditor_plugins/) uses auto-discovery — only load.pyfiles, skip files starting with_or. QWebEngineView.setUrl()must only load trusted URLs (localhost or user-confirmed external URLs) — never load untrusted HTML or URLs without user consent- Never call
QWebEngineView.setHtml()with unsanitised content — this enables XSS within the embedded browser
- SSH passwords and private key passphrases are held in memory only during the session — never persist to disk or logs
- Password fields must use
QLineEdit.EchoMode.Password - API endpoint URLs may contain embedded tokens — treat URL strings with the same care as credentials (do not log full URLs)
- Environment variables (
PYBREEZE_LOG_MAX_BYTES, etc.) must never contain secrets; use dedicated secure stores for credentials
- Pin dependencies to exact versions in
requirements.txt/dev_requirements.txt - Do not add new dependencies without reviewing their security posture (maintained? known CVEs?)
- Avoid transitive dependency bloat — prefer stdlib solutions when the alternative is a single-function dependency
All code must satisfy common static-analysis rules enforced by SonarQube and Codacy. Review each change against the checklist below.
- Cyclomatic complexity per function: ≤ 15 (hard cap 20). Break large branches into helpers
- Cognitive complexity per function: ≤ 15. Flatten nested
if/for/trychains with early returns or guard clauses - Function length: ≤ 75 lines of code (excluding docstring / blank lines). Extract helpers past that
- Parameter count: ≤ 7 per function/method. Use a dataclass or typed dict when more are needed
- Nesting depth: ≤ 4 levels of
if/for/while/try. Refactor with early returns instead of pyramids - File length: ≤ 1000 lines — split modules past that
- Class
__init__: keep attribute count reasonable; if a class has > 15 instance attributes, split responsibilities
- Never use bare
except:— always specify exception types - Avoid catching
ExceptionorBaseExceptionunless immediately re-raising or logging and re-raising with context - Never
passsilently insideexcept— log the error viapybreeze_logger(at minimum.debug()) with context - Do not
return/break/continueinside afinallyblock — it swallows exceptions - Custom exceptions must inherit from
ITEException; neverraise Exception(...)directly - Use
raise ... from err(orraise ... from None) when re-raising to preserve / suppress the chain explicitly
- Compare with
Noneusingis/is not, never==/!= - Type checks use
isinstance(obj, T), nevertype(obj) == T - Never use mutable default arguments (
def f(x=[])) — useNoneand initialise inside - Prefer f-strings over
%formatting orstr.format() - Use context managers (
with open(...) as f:) for every file / socket / lock — never leave resources to GC - Use
enumerate()instead ofrange(len(...))when the index is needed alongside the item - Use
dict.get(key, default)instead ofkey in dict and dict[key]patterns - Use set / dict comprehensions when clearer than manual loops; avoid comprehensions with side effects
snake_casefor functions, methods, variables, module namesPascalCasefor classesUPPER_SNAKE_CASEfor module-level constants_leading_underscorefor protected / internal members; never use__dunder__for custom attributes- No single-letter names except loop indices (
i,j) or conventional math (x,y) - Do not shadow built-ins (
id,type,list,dict,input,file,open, etc.) — rename the local variable
- String literal used 3+ times in the same module → extract a module-level constant
- Identical 6+ line blocks in 2+ places → extract a helper function
- Remove unused imports, unused parameters, unused local variables, unreachable code after
return/raise - No commented-out code blocks — delete them (git history is the archive)
- No
TODO/FIXME/XXXwithout an accompanying issue reference (# TODO(#123): ...)
- Never use
print()for diagnostics in library / runtime code — usepybreeze_logger - Use lazy logging (
logger.debug("x=%s", x)) — avoid eager f-string formatting inside log calls on hot paths - Never use
assertfor runtime validation (Python strips assertions with-O). Use explicitif … raise …instead;assertis only for test code
- No hardcoded passwords, tokens, API keys, or secrets — use env vars or a config file excluded from VCS
- No hardcoded IP addresses or hostnames outside of
localhost/ documented loopback — use config - Magic numbers (except 0, 1, -1) should be named constants when repeated or non-obvious
- Replace
if cond: return True else: return Falsewithreturn bool(cond)orreturn cond - Replace
if x == True/if x == Falsewithif x/if not x - A function should have a consistent return type — never mix
return valueand barereturn(returnsNone) on meaningful paths unless explicitly documented - Do not return inside a generator function (
yield+return valueis a syntax pitfall)
- One import per line for
importstatements; groupedfrom x import a, bis fine - Order: stdlib → third-party → first-party (
pybreeze.*) — separated by blank lines - No wildcard imports (
from x import *) outside of__init__.pyre-exports - No relative imports beyond one level (
from ..pkg import xOK,from ...pkg import xavoid)
- Before committing any non-trivial change, run
ruff check pybreeze/locally to catch these rules —ruffcovers the majority of SonarQube/Codacy Python rules - When adding a new rule exception, justify it in a
# noqa: RULEcomment with a short reason — never blanket-disable
- Commit messages: short imperative sentence (e.g., "Update stable version", "Fix github actions")
- Do not mention any AI tools, assistants, or co-authors in commit messages or PR descriptions
- Do not add
Co-Authored-Byheaders referencing any AI - PR target:
devfor development work,mainfor stable releases