pyqt — правило для Cursor: что делает и как установить
Правило для Cursor задаёт стандарты разработки PyQt-приложений на Python: запрещает менять сгенерированный интерфейс вручную и требует выносить тяжёлые вычисления из потока интерфейса.
.cursor/rules/pyqt.mdcЧто делает pyqt
Правило активируется для файлов с маской **/*.py и направляет ИИ на чистую архитектуру PyQt6. Главное требование к визуальной части — собирать интерфейс в Qt Designer и загружать .ui динамически через loadUi либо наследовать сгенерированный класс. Править код pyuic напрямую запрещено, иначе вёрстка сломается при первой перегенерации.
Для списков и таблиц агент обязан строить Model-View архитектуру через QAbstractListModel, не сохраняя данные внутри виджетов. Фоновые расчёты выносятся в QThreadPool через QRunnable или QThread. Если запустить тяжёлую задачу в основном потоке интерфейса, окно намертво зависнет, поэтому передача статусов и результатов идёт исключительно через сигналы pyqtSignal. Связи между логикой и кнопками оформляются через слоты.
Какие задачи решает
- предотвратить ручные правки в файлах, сгенерированных pyuic
- разделить данные и интерфейс через архитектуру Model-View
- вынести блокирующие задачи из основного UI-потока в QThreadPool
- связать компоненты интерфейса с бизнес-логикой через сигналы и слоты
Примеры запросов агенту
- Создай окно со списком задач на PyQt6, загрузи разметку из mainwindow.ui и подключи модель на базе QAbstractListModel
- Напиши воркер для длительной загрузки данных в QThreadPool, чтобы интерфейс окна не зависал
Как установить pyqt
Вручную: положить файлы
.cursor/rules/pyqt.mdcВо frontmatter: description, globs, alwaysApply
Вручную: положить файлы
.claude/rules/pyqt.mdПоле paths во frontmatter ограничивает файлы: globs из источника → paths
AGENTS.md
Codex читает AGENTS.md: вставьте текст правила туда
Вручную: положить файлы
.github/instructions/pyqt.instructions.mdПоле applyTo задаёт, к каким файлам применять: globs из источника → applyTo
Вручную: положить файлы
.devin/rules/pyqt.mdВручную: положить файлы
.clinerules/pyqt.mdПо шагам, с командами для каждой платформы: Cursor rules: файлы .mdc, четыре режима и замена .cursorrules
Вопросы о pyqt
Как установить правило pyqt в Cursor?
Создайте файл .cursor/rules/pyqt.mdc в корне проекта и добавьте в него содержимое правила. Cursor начнёт применять инструкции ко всем файлам по маске **/*.py.
Что требует правило pyqt при создании интерфейса?
Оно предписывает проектировать экраны в Qt Designer и загружать файлы .ui на лету через loadUi либо одиночное наследование. Ручное редактирование файлов, созданных компилятором pyuic, запрещено.
Как правило pyqt организует фоновые вычисления?
Фоновая работа передаётся в пул потоков через QThreadPool и QRunnable. Обновление элементов интерфейса разрешено только через сигналы pyqtSignal, чтобы избежать блокировки главного цикла событий.
Исходный текст
pyqt.mdc · CC0-1.0
---
description: Enforce modern, maintainable, and performant PyQt application development standards by leveraging Qt Designer, Model-View architecture, and responsive threading.
globs: **/*.py
---
# PyQt Best Practices
This guide outlines essential practices for building robust, scalable, and maintainable PyQt applications. Adhere to these rules for consistent, high-quality code.
## 1. UI Design: Qt Designer & Dynamic Loading
Always design your UI visually with **Qt Designer** (or Qt Design Studio). Keep the generated `.ui` files pristine. Load them dynamically at runtime or integrate the `pyuic`-generated code via inheritance without modification. This separates design from logic and simplifies UI updates.
❌ **BAD: Hand-editing `pyuic`-generated files**
```python
# ui_mainwindow.py (DO NOT EDIT THIS FILE DIRECTLY)
# This file was generated by pyuic6...
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
# ... generated code ...
self.my_custom_button.clicked.connect(self.my_logic_function) # Added here
```
✅ **GOOD: Dynamic loading with `QUiLoader` (PyQt6)**
```python
from PyQt6.QtWidgets import QApplication, QWidget
from PyQt6.uic import loadUi
class MyMainWindow(QWidget):
def __init__(self):
super().__init__()
loadUi("mainwindow.ui", self) # Load UI dynamically
self.my_button.clicked.connect(self._on_button_clicked)
def _on_button_clicked(self):
print("Button clicked!")
if __name__ == "__main__":
app = QApplication([])
window = MyMainWindow()
window.show()
app.exec()
```
✅ **GOOD: Single inheritance with `pyuic`-generated code**
```python
# ui_mainwindow.py (Generated by `pyuic6 -o ui_mainwindow.py mainwindow.ui`)
# DO NOT EDIT THIS FILE.
from PyQt6 import QtWidgets # ... other imports ...
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
# ... generated UI setup code ...
pass # No signal connections here
# main.py (Your application logic)
from PyQt6.QtWidgets import QApplication, QMainWindow
from ui_mainwindow import Ui_MainWindow # Import generated UI
class MyMainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self) # Set up the UI on this window
self.ui.my_button.clicked.connect(self._on_button_clicked)
def _on_button_clicked(self):
print("Button clicked from inherited UI!")
if __name__ == "__main__":
app = QApplication([])
window = MyMainWindow()
window.show()
app.exec()
```
## 2. Component Architecture: Model-View Separation
For any data-driven UI (lists, tables, trees), adopt the **Model-View architecture**. This decouples data management from UI presentation, leading to cleaner, more scalable code.
❌ **BAD: Storing data directly in widgets**
```python
from PyQt6.QtWidgets import QListWidget, QListWidgetItem
# ...
self.my_list_widget = QListWidget()
self.my_list_widget.addItem("Item 1")
self.my_list_widget.addItem("Item 2")
# To update: self.my_list_widget.item(0).setText("New Item 1") # Tedious
```
✅ **GOOD: Custom `QAbstractListModel` for data**
```python
from PyQt6.QtCore import QAbstractListModel, QModelIndex, Qt
from PyQt6.QtWidgets import QListView, QApplication
class MyListModel(QAbstractListModel):
def __init__(self, data=None):
super().__init__()
self._data = data or []
def data(self, index: QModelIndex, role: int):
if role == Qt.ItemDataRole.DisplayRole:
return self._data[index.row()]
return None
def rowCount(self, parent: QModelIndex):
return len(self._data)
def add_item(self, item: str):
self.beginInsertRows(QModelIndex(), len(self._data), len(self._data))
self._data.append(item)
self.endInsertRows()
# ... in your QMainWindow or QWidget
# self.my_list_view = QListView()
# self.model = MyListModel(["Initial Item 1", "Initial Item 2"])
# self.my_list_view.setModel(self.model)
# self.model.add_item("New Item from Logic") # Model updates view automatically
```
## 3. Asynchronous Operations: Keep UI Responsive
Never block the UI thread with long-running tasks. Use `QThreadPool` with `QRunnable` or `QThread` for background processing. Emit signals from worker threads to update the UI on the main thread.
❌ **BAD: Blocking UI thread**
```python
import time
from PyQt6.QtWidgets import QPushButton, QLabel
# ...
def on_heavy_button_clicked(self):
self.status_label.setText("Processing...")
time.sleep(5) # UI freezes for 5 seconds
self.status_label.setText("Done!")
```
✅ **GOOD: Offload to `QThreadPool`**
```python
from PyQt6.QtCore import QRunnable, QThreadPool, pyqtSignal, QObject
from PyQt6.QtWidgets import QPushButton, QLabel, QApplication, QWidget
import time
class WorkerSignals(QObject):
finished = pyqtSignal()
result = pyqtSignal(str)
class Worker(QRunnable):
def __init__(self, task_id):
super().__init__()
self.signals = WorkerSignals()
self.task_id = task_id
def run(self):
time.sleep(2) # Simulate heavy work
self.signals.result.emit(f"Task {self.task_id} completed.")
self.signals.finished.emit()
class MyWindow(QWidget):
def __init__(self):
super().__init__()
self.threadpool = QThreadPool()
self.button = QPushButton("Start Heavy Task", self)
self.label = QLabel("Ready", self)
self.button.clicked.connect(self._start_task)
# ... layout setup ...
def _start_task(self):
self.label.setText("Processing...")
worker = Worker(time.time()) # Unique ID for task
worker.signals.result.connect(self.label.setText)
worker.signals.finished.connect(lambda: print("Worker finished"))
self.threadpool.start(worker)
if __name__ == "__main__":
app = QApplication([])
window = MyWindow()
window.show()
app.exec()
```
## 4. Communication: Signals & Slots
Leverage Qt's powerful signals and slots mechanism for decoupled communication between UI components and business logic.
❌ **BAD: Direct method calls for UI events**
```python
# In MainWindow
self.login_button.clicked.connect(self.login_manager.authenticate) # login_manager is tightly coupled
```
✅ **GOOD: Decoupled signals and slots**
```python
from PyQt6.QtCore import pyqtSignal, QObject
# ...
class LoginManager(QObject):
login_successful = pyqtSignal(str)
login_failed = pyqtSignal(str)
def authenticate(self, username, password):
if username == "user" and password == "pass":
self.login_successful.emit("Welcome!")
else:
self.login_failed.emit("Invalid credentials.")
# In MainWindow
# self.login_manager = LoginManager()
# self.login_manager.login_successful.connect(self.show_dashboard)
# self.login_manager.login_failed.connect(self.display_error)
# self.login_button.clicked.connect(lambda: self.login_manager.authenticate(self.user_input.text(), self.pass_input.text()))
```
## 5. Code Organization & Naming
Follow Pythonic naming conventions (`CamelCase` for Qt classes, `snake_case` for methods/variables). Organize code into logical modules (e.g., `ui/`, `models/`, `workers/`). Use Qt resource files (`.qrc`) for icons, images, and stylesheets.
❌ **BAD: Inconsistent naming, monolithic files**
```python
# myapp.py
class my_main_window(QtWidgets.QMainWindow): # Incorrect class name
def __init__(self):
self.MyButton = QtWidgets.QPushButton() # Incorrect variable name
# ... all logic here ...
```
✅ **GOOD: Consistent naming, modular structure**
```python
# main.py
from PyQt6.QtWidgets import QApplication
from my_app.ui.main_window import MainWindow # Separate UI module
if __name__ == "__main__":
app = QApplication([])
main_window = MainWindow()
main_window.show()
app.exec()
# my_app/ui/main_window.py
from PyQt6.QtWidgets import QMainWindow
# ...
class MainWindow(QMainWindow): # CamelCase for Qt classes
def __init__(self):
super().__init__()
self._setup_ui() # Private helper method
self._connect_signals()
def _on_button_clicked(self): # snake_case for methods
pass
# my_app/resources.qrc (compiled to my_app/resources_rc.py)
# <RCC>
# <qresource prefix="/icons">
# <file>icon.png</file>
# </qresource>
# </RCC>
# Use: self.setWindowIcon(QIcon(":/icons/icon.png"))
```
## 6. Styling & Theming
Use Qt's stylesheet system (CSS-like `.qss` files) for styling. This allows for easy theming (e.g., dark mode) without modifying Python code.
❌ **BAD: Hardcoding styles in Python**
```python
self.my_button.setStyleSheet("background-color: red; color: white;") # Hardcoded
```
✅ **GOOD: External stylesheets**
```python
# styles.qss
QPushButton {
background-color: #2196F3; /* Blue */
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
}
QPushButton:hover {
background-color: #1976D2; /* Darker Blue */
}
# In main.py or MainWindow __init__
with open("styles.qss", "r") as f:
_stylesheet = f.read()
app.setStyleSheet(_stylesheet) # Apply globally
# Or self.my_widget.setStyleSheet(_stylesheet) # Apply locally
```
## 7. Type Hints
Always use Python type hints for improved readability, maintainability, and static analysis.
❌ **BAD: Untyped code**
```python
def process_data(data):
# ...
```
✅ **GOOD: Type-hinted code**
```python
from typing import List, Tuple
from PyQt6.QtCore import QModelIndex
def process_data(data: List[Tuple[bool, str]]) -> List[str]:
# ...
return [item[1] for item in data]
def data(self, index: QModelIndex, role: int) -> str | None:
# ...
```
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc