python-development

$npx mdskill add Community-Access/accessibility-agents/python-development

Build robust Python desktop apps with wxPython patterns.

  • Resolve version conflicts and packaging errors instantly.
  • Access wxPython desktop APIs and cross-platform guides.
  • Apply type hints and linting rules from pyproject.toml.
  • Display structured tables and code skeletons for reference.
SKILL.md
.github/skills/python-developmentView on GitHub ↗
---
name: python-development
description: "Python and wxPython development reference patterns, common pitfalls, framework-specific guides, desktop accessibility APIs, and cross-platform considerations. Use when building, debugging, packaging, or reviewing Python desktop applications."
---

# Python Development Skill

Reference data for the Developer Hub, Python Specialist, and wxPython Specialist agents.

## Python Version Quick Reference

| Version | Key Features | EOL |
|---|---|---|
| 3.10 | `match/case`, `X \| Y` unions, `ParamSpec` | Oct 2026 |
| 3.11 | Exception groups, `Self` type, `tomllib`, faster CPython | Oct 2027 |
| 3.12 | Type parameter syntax `def f[T]()`, `@override`, f-string nesting | Oct 2028 |
| 3.13 | Experimental free-threaded mode, improved error messages | Oct 2029 |
| 3.14 | `async pdb.set_trace_async()`, template strings (PEP 750) | Oct 2030 |

## pyproject.toml Skeleton

```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "my-app"
version = "1.0.0"
requires-python = ">=3.10"
dependencies = []

[project.optional-dependencies]
dev = ["pytest>=8.0", "ruff>=0.6", "mypy>=1.11"]

[project.scripts]
myapp = "my_app.__main__:main"

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra --strict-markers"

[tool.ruff]
target-version = "py310"
line-length = 100

[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "B", "SIM", "TCH"]

[tool.mypy]
python_version = "3.10"
strict = true
```

## PyInstaller Quick Reference

### One-File Mode

```python
exe = EXE(pyz, a.scripts, a.binaries, a.zipfiles, a.datas,
          name='MyApp', console=False, icon='icon.ico')
```

### One-Folder Mode

```python
exe = EXE(pyz, a.scripts, exclude_binaries=True,
          name='MyApp', console=False, icon='icon.ico')
coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, name='MyApp')
```

### Common Hidden Imports

- `pkg_resources.extern`
- `accessible_output2` (for a11y desktop apps)
- `keyring.backends` (for credential storage)
- `platformdirs`
- `httpx._transports` / `httpcore._backends`
- `encodings` (always needed)

## wxPython Quick Reference

### Sizer Cheat Sheet

| Sizer | When to Use |
|---|---|
| `wx.BoxSizer(wx.VERTICAL)` | Stack items top-to-bottom |
| `wx.BoxSizer(wx.HORIZONTAL)` | Lay items left-to-right |
| `wx.GridBagSizer(vgap, hgap)` | Form layouts with labels + controls |
| `wx.FlexGridSizer(rows, cols, vgap, hgap)` | Even grid layouts |
| `wx.WrapSizer` | Flow layout that wraps |
| `wx.StaticBoxSizer(wx.VERTICAL, parent, "Label")` | Grouped controls with border |

### Thread-Safe GUI Updates

```python
# From worker thread:
wx.CallAfter(self.update_status, "Done")
wx.PostEvent(self, CustomEvent(data=result))

# NEVER do this from a worker thread:
self.status_bar.SetStatusText("Done")  # CRASH or CORRUPTION
```

### Standard IDs

| ID | Purpose |
|---|---|
| `wx.ID_OK` | OK button |
| `wx.ID_CANCEL` | Cancel button |
| `wx.ID_SAVE` | Save action |
| `wx.ID_OPEN` | Open action |
| `wx.ID_EXIT` | Exit / Quit |
| `wx.ID_HELP` | Help action |
| `wx.ID_NEW` | New document |
| `wx.ID_UNDO` / `wx.ID_REDO` | Undo / Redo |

### Event Types

| Event | Trigger |
|---|---|
| `wx.EVT_BUTTON` | Button click |
| `wx.EVT_MENU` | Menu item selected |
| `wx.EVT_CLOSE` | Window close requested |
| `wx.EVT_SIZE` | Window resized |
| `wx.EVT_TIMER` | Timer fired |
| `wx.EVT_TEXT` | Text control content changed |
| `wx.EVT_LIST_ITEM_SELECTED` | List item selected |
| `wx.EVT_TREE_SEL_CHANGED` | Tree selection changed |
| `wx.EVT_UPDATE_UI` | UI state update check |

## Common Pitfalls

### Python

- **Mutable default arguments:** `def f(items=[])` shares the list across calls. Use `None` and create inside.
- **Late binding closures:** `lambda: x` in a loop captures the variable, not the value. Use `lambda x=x: x`.
- **Circular imports:** Move imports inside functions, use `TYPE_CHECKING` block, or restructure modules.
- **`field()` outside dataclass:** `field()` is only valid inside `@dataclass` classes. Use plain type annotations elsewhere.
- **`is` vs `==`:** `is` checks identity, `==` checks equality. Use `is` only for `None`, `True`, `False`.
- **String concatenation in loops:** Use `"".join()` or `io.StringIO` instead.

### wxPython

- **GUI from worker thread:** Always use `wx.CallAfter()` or `wx.PostEvent()`.
- **Missing `event.Skip()`:** Other handlers won't fire. Call `event.Skip()` unless you intentionally consume the event.
- **Timer not stopped:** Stop timers in `EVT_CLOSE` handler to prevent callbacks after destruction.
- **AUI not uninitialized:** Call `_mgr.UnInit()` in close handler.
- **Dialog not destroyed:** Use context managers (`with MyDialog(...) as dlg:`) for automatic cleanup.
- **Wrong parent for sizer items:** All controls in a sizer must have the same parent panel.
- **Absolute positioning:** Never use `SetPosition()` or `SetSize()` for layout. Always use sizers.

## Cross-Platform Paths

```python
from platformdirs import user_config_dir, user_data_dir, user_cache_dir

config = user_config_dir("MyApp", "MyCompany")  # %APPDATA% / ~/Library/... / ~/.config/
data = user_data_dir("MyApp", "MyCompany")
cache = user_cache_dir("MyApp", "MyCompany")
```

## Testing Quick Reference

```bash
# Run all tests
pytest

# Run specific test file
pytest tests/test_queue.py

# Run specific test
pytest tests/test_queue.py::test_submit_job -v

# With coverage
pytest --cov=mypackage --cov-report=term-missing

# Stop on first failure
pytest -x

# Show locals on failure
pytest -l
```

## Logging Setup Template

```python
import logging

def setup_logging(level: int = logging.INFO) -> None:
    logging.basicConfig(
        level=level,
        format="%(asctime)s %(name)s %(levelname)s %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )
    # Quiet noisy libraries
    logging.getLogger("httpx").setLevel(logging.WARNING)
    logging.getLogger("httpcore").setLevel(logging.WARNING)
```

## Desktop Accessibility Quick Reference

### Platform API Summary

| Platform | API | Python Binding | Use For |
|---|---|---|---|
| Windows | UI Automation (UIA) | `comtypes`, `pywinauto` | Modern apps, NVDA/Narrator |
| Windows | MSAA / IAccessible2 | `comtypes`, `pywinauto` | Legacy apps, JAWS |
| macOS | NSAccessibility | `pyobjc` | VoiceOver |

### wxPython Accessibility Essentials

```python
# Name every control that lacks a visible label
# CORRECT: use StaticText immediately before the control in the sizer
label = wx.StaticText(panel, label="Scan progress:")
# ctrl = wx.Gauge(panel)  -- add label to sizer right before ctrl
# WRONG: SetName() is ignored by screen readers
# ctrl.SetName("Scan progress")  -- only affects FindWindowByName()

# Tab order follows sizer insertion order; override with:
ctrl2.MoveAfterInTabOrder(ctrl1)

# Keyboard shortcuts via accelerator table
accel = wx.AcceleratorTable([
    (wx.ACCEL_CTRL, ord('S'), wx.ID_SAVE),
    (wx.ACCEL_CTRL, ord('Q'), wx.ID_EXIT),
])
frame.SetAcceleratorTable(accel)

# Platform-correct button order in dialogs
sizer.Add(dialog.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL))
```

### Screen Reader Interaction Model

Screen readers expose controls as: **Name + Role + Value + State**

| Property | wxPython Source | Example |
|---|---|---|
| Name | Preceding `wx.StaticText`, `label=` parameter, or `SetToolTip()` | "Scan progress" |
| Role | Widget type (automatic) | button, text field, list |
| Value | Widget content | "75%", "Hello world" |
| State | Widget flags | focused, disabled, checked |

### Desktop A11y Checklist

1. Every control has a meaningful name (preceding `wx.StaticText` for inputs, `label=` for buttons, `SetToolTip()` for image-only controls)
2. Keyboard-only operation -- every action reachable via Tab/Enter/Space/arrows
3. Focus visible -- never suppress focus indicators
4. Tab order is logical (generally top-to-bottom, left-to-right)
5. Color is not the sole information carrier
6. High contrast mode supported (use system colors, not hardcoded)
7. Dialogs use `CreateStdDialogButtonSizer()` for platform-correct button order
8. AUI panes are keyboard-navigable

### Structured Audit Rule Sets

When audit mode is activated, agents use these structured detection rule sets:

| Rule Prefix | Agent | Scope | Count |
|---|---|---|---|
| WX-A11Y-001..012 | `wxpython-specialist` | wxPython-specific patterns (StaticText labels, AcceleratorTable, mouse-only events, dialogs) | 12 rules |
| DTK-A11Y-001..012 | `desktop-a11y-specialist` | Platform-level API patterns (Name/Role/State/Value, focus, UIA/NSAccessibility) | 12 rules |
| TST-A11Y-001..010 | `desktop-a11y-testing-coach` | Test coverage gaps (automated tests, SR testing, keyboard plans, CI integration) | 10 rules |

Rule sets don't overlap -- WX covers wxPython widget patterns, DTK covers platform APIs, TST covers testing process gaps.

### Agent Routing

For deeper expertise, the skill routes to these specialists:

- **`desktop-a11y-specialist`** -- Platform API implementation, wx.Accessible, custom widget patterns (DTK-A11Y-* audit rules)
- **`desktop-a11y-testing-coach`** -- NVDA/JAWS/Narrator testing, Accessibility Insights, automated UIA tests (TST-A11Y-* audit rules)
- **`a11y-tool-builder`** -- Rule engine architecture, document parsers, severity scoring, report generators
More from Community-Access/accessibility-agents