python-skills 1.0.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.
- python_skills/__init__.py +10 -0
- python_skills/__main__.py +6 -0
- python_skills/adapters/__init__.py +48 -0
- python_skills/adapters/agent_skills.py +415 -0
- python_skills/adapters/aider_adapter.py +226 -0
- python_skills/adapters/base.py +153 -0
- python_skills/adapters/claude.py +474 -0
- python_skills/adapters/cline.py +332 -0
- python_skills/adapters/codex.py +24 -0
- python_skills/adapters/continue_adapter.py +198 -0
- python_skills/adapters/cursor.py +327 -0
- python_skills/adapters/gemini.py +26 -0
- python_skills/adapters/goose.py +26 -0
- python_skills/adapters/junie.py +25 -0
- python_skills/adapters/kiro.py +382 -0
- python_skills/adapters/opencode.py +27 -0
- python_skills/adapters/roo.py +25 -0
- python_skills/adapters/universal.py +203 -0
- python_skills/adapters/vscode.py +27 -0
- python_skills/adapters/windsurf.py +26 -0
- python_skills/adapters/zed.py +27 -0
- python_skills/cli.py +326 -0
- python_skills/config.py +160 -0
- python_skills/detector.py +152 -0
- python_skills/installer.py +163 -0
- python_skills/markers.py +115 -0
- python_skills/skills/__init__.py +14 -0
- python_skills/skills/loader.py +171 -0
- python_skills/skills/metadata.py +152 -0
- python_skills/skills/registry.py +101 -0
- python_skills/state.py +204 -0
- python_skills-1.0.0.dist-info/METADATA +99 -0
- python_skills-1.0.0.dist-info/RECORD +105 -0
- python_skills-1.0.0.dist-info/WHEEL +4 -0
- python_skills-1.0.0.dist-info/entry_points.txt +2 -0
- python_skills-1.0.0.dist-info/licenses/LICENSE +21 -0
- skills/advanced_python.md +239 -0
- skills/anti_patterns/index.md +406 -0
- skills/comprehensions.md +167 -0
- skills/control_flow.md +175 -0
- skills/data_structures.md +243 -0
- skills/debugging/common_bugs.md +222 -0
- skills/debugging/inspection_techniques.md +249 -0
- skills/debugging/root_cause.md +203 -0
- skills/engineering/application_logging.md +195 -0
- skills/engineering/cli_apps.md +207 -0
- skills/engineering/configuration.md +218 -0
- skills/engineering/database.md +240 -0
- skills/engineering/dependency_management.md +205 -0
- skills/engineering/http_clients.md +267 -0
- skills/engineering/modules_packages.md +211 -0
- skills/engineering/packaging.md +197 -0
- skills/engineering/project_structure.md +155 -0
- skills/engineering/pyproject_toml.md +302 -0
- skills/engineering/virtual_environments.md +206 -0
- skills/functions.md +244 -0
- skills/generation/async_concurrency.md +291 -0
- skills/generation/error_handling.md +276 -0
- skills/generation/protocols_generics.md +243 -0
- skills/generation/type_hints.md +290 -0
- skills/generation/validation_pipeline.md +274 -0
- skills/generation/workflow.md +190 -0
- skills/oop.md +228 -0
- skills/quality/abstractions.md +154 -0
- skills/quality/comments.md +177 -0
- skills/quality/documentation.md +176 -0
- skills/quality/duplication.md +137 -0
- skills/quality/maintainability.md +142 -0
- skills/quality/naming.md +171 -0
- skills/quality/quality_functions.md +245 -0
- skills/quality/readability.md +239 -0
- skills/quality/type_annotations.md +192 -0
- skills/refactoring/behavior_preservation.md +157 -0
- skills/refactoring/incremental.md +187 -0
- skills/refactoring/interface_stability.md +199 -0
- skills/refactoring/safe_refactoring.md +206 -0
- skills/security/auth_boundaries.md +200 -0
- skills/security/command_injection.md +207 -0
- skills/security/dependency_risks.md +282 -0
- skills/security/file_handling.md +156 -0
- skills/security/input_validation.md +190 -0
- skills/security/path_traversal.md +172 -0
- skills/security/secrets.md +171 -0
- skills/security/sql_injection.md +188 -0
- skills/security/unsafe_deserialization.md +164 -0
- skills/stdlib/argparse.md +178 -0
- skills/stdlib/collections.md +212 -0
- skills/stdlib/datetime.md +187 -0
- skills/stdlib/functools.md +238 -0
- skills/stdlib/itertools.md +183 -0
- skills/stdlib/json.md +162 -0
- skills/stdlib/logging.md +185 -0
- skills/stdlib/os_sys.md +184 -0
- skills/stdlib/pathlib.md +218 -0
- skills/stdlib/re.md +171 -0
- skills/stdlib/statistics.md +112 -0
- skills/stdlib/subprocess.md +211 -0
- skills/testing/async_tests.md +249 -0
- skills/testing/coverage.md +168 -0
- skills/testing/edge_cases.md +197 -0
- skills/testing/fixtures_mocks.md +203 -0
- skills/testing/organization.md +205 -0
- skills/testing/parameterized.md +174 -0
- skills/testing/regression_tests.md +165 -0
- skills/variables_types.md +107 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Debugging: Common Bugs
|
|
2
|
+
|
|
3
|
+
**Purpose**: Quick reference for frequent Python bug patterns.
|
|
4
|
+
|
|
5
|
+
**When to use**: Debugging, code review, preventing known issues.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Mutable Default Arguments
|
|
12
|
+
```python
|
|
13
|
+
# BUG
|
|
14
|
+
def append_item(item, items=[]):
|
|
15
|
+
items.append(item)
|
|
16
|
+
return items
|
|
17
|
+
|
|
18
|
+
append_item(1) # [1]
|
|
19
|
+
append_item(2) # [1, 2] — BUG: shared list!
|
|
20
|
+
|
|
21
|
+
# FIX
|
|
22
|
+
def append_item(item, items=None):
|
|
23
|
+
if items is None:
|
|
24
|
+
items = []
|
|
25
|
+
items.append(item)
|
|
26
|
+
return items
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Late Binding in Closures
|
|
30
|
+
```python
|
|
31
|
+
# BUG
|
|
32
|
+
funcs = []
|
|
33
|
+
for i in range(3):
|
|
34
|
+
funcs.append(lambda: i)
|
|
35
|
+
|
|
36
|
+
[f() for f in funcs] # [2, 2, 2] — all capture same i!
|
|
37
|
+
|
|
38
|
+
# FIX — bind early
|
|
39
|
+
funcs = []
|
|
40
|
+
for i in range(3):
|
|
41
|
+
funcs.append(lambda i=i: i) # Default binds current value
|
|
42
|
+
|
|
43
|
+
# OR use functools.partial
|
|
44
|
+
from functools import partial
|
|
45
|
+
funcs = [partial(lambda x: x, i) for i in range(3)]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Integer Division (Python 2 vs 3)
|
|
49
|
+
```python
|
|
50
|
+
# Python 3: / is float division, // is integer
|
|
51
|
+
3 / 2 # 1.5
|
|
52
|
+
3 // 2 # 1
|
|
53
|
+
|
|
54
|
+
# In Python 2: / was integer division for ints
|
|
55
|
+
# Not an issue in Python 3+
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Floating Point Precision
|
|
59
|
+
```python
|
|
60
|
+
# BUG
|
|
61
|
+
0.1 + 0.2 == 0.3 # False! 0.30000000000000004
|
|
62
|
+
|
|
63
|
+
# FIX — use Decimal for money/precision
|
|
64
|
+
from decimal import Decimal
|
|
65
|
+
Decimal("0.1") + Decimal("0.2") == Decimal("0.3") # True
|
|
66
|
+
|
|
67
|
+
# Or use tolerance
|
|
68
|
+
abs(0.1 + 0.2 - 0.3) < 1e-9 # True
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Variable Shadowing
|
|
72
|
+
```python
|
|
73
|
+
# BUG
|
|
74
|
+
def process(items):
|
|
75
|
+
list = [] # Shadows built-in list!
|
|
76
|
+
for item in items:
|
|
77
|
+
list.append(transform(item))
|
|
78
|
+
return list
|
|
79
|
+
|
|
80
|
+
# FIX — never shadow builtins
|
|
81
|
+
def process(items):
|
|
82
|
+
result = []
|
|
83
|
+
for item in items:
|
|
84
|
+
result.append(transform(item))
|
|
85
|
+
return result
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Iterable Exhaustion
|
|
89
|
+
```python
|
|
90
|
+
# BUG
|
|
91
|
+
gen = (x for x in range(3))
|
|
92
|
+
list(gen) # [0, 1, 2]
|
|
93
|
+
list(gen) # [] — exhausted!
|
|
94
|
+
|
|
95
|
+
# FIX — convert to list if reused
|
|
96
|
+
gen = list(x for x in range(3))
|
|
97
|
+
list(gen) # [0, 1, 2]
|
|
98
|
+
list(gen) # [0, 1, 2]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Default Dict Mutation
|
|
102
|
+
```python
|
|
103
|
+
# BUG
|
|
104
|
+
from collections import defaultdict
|
|
105
|
+
|
|
106
|
+
def add_item(key, value, d=defaultdict(list)):
|
|
107
|
+
d[key].append(value)
|
|
108
|
+
return d
|
|
109
|
+
|
|
110
|
+
add_item("a", 1) # {"a": [1]}
|
|
111
|
+
add_item("b", 2) # {"a": [1], "b": [2]} — SHARED!
|
|
112
|
+
|
|
113
|
+
# FIX
|
|
114
|
+
def add_item(key, value, d=None):
|
|
115
|
+
if d is None:
|
|
116
|
+
d = defaultdict(list)
|
|
117
|
+
d[key].append(value)
|
|
118
|
+
return d
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Exception Swallowing
|
|
122
|
+
```python
|
|
123
|
+
# BUG
|
|
124
|
+
try:
|
|
125
|
+
risky()
|
|
126
|
+
except:
|
|
127
|
+
pass # Swallows everything including KeyboardInterrupt!
|
|
128
|
+
|
|
129
|
+
# FIX — specific exceptions
|
|
130
|
+
try:
|
|
131
|
+
risky()
|
|
132
|
+
except SpecificError:
|
|
133
|
+
handle()
|
|
134
|
+
except Exception:
|
|
135
|
+
logger.exception("Unexpected error")
|
|
136
|
+
raise
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Modifying During Iteration
|
|
140
|
+
```python
|
|
141
|
+
# BUG
|
|
142
|
+
items = [1, 2, 3, 4, 5]
|
|
143
|
+
for item in items:
|
|
144
|
+
if item % 2 == 0:
|
|
145
|
+
items.remove(item) # Skips elements!
|
|
146
|
+
|
|
147
|
+
# FIX — iterate over copy
|
|
148
|
+
for item in items[:]: # or list(items)
|
|
149
|
+
if item % 2 == 0:
|
|
150
|
+
items.remove(item)
|
|
151
|
+
|
|
152
|
+
# OR filter
|
|
153
|
+
items = [x for x in items if x % 2 != 0]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### String/Bytes Confusion
|
|
157
|
+
```python
|
|
158
|
+
# BUG
|
|
159
|
+
data = b"hello"
|
|
160
|
+
data + " world" # TypeError: can't concat bytes to str
|
|
161
|
+
|
|
162
|
+
# FIX — explicit encode/decode
|
|
163
|
+
data.decode() + " world" # "hello world"
|
|
164
|
+
data + b" world" # b"hello world"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Path Traversal (Security)
|
|
168
|
+
```python
|
|
169
|
+
# BUG
|
|
170
|
+
def read_file(filename):
|
|
171
|
+
with open(filename) as f: # User controls path!
|
|
172
|
+
return f.read()
|
|
173
|
+
|
|
174
|
+
# FIX — validate path
|
|
175
|
+
def read_file(filename, base_dir):
|
|
176
|
+
path = (Path(base_dir) / filename).resolve()
|
|
177
|
+
if not path.is_relative_to(base_dir):
|
|
178
|
+
raise SecurityError("Path traversal")
|
|
179
|
+
return path.read_text()
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Decision Rules
|
|
185
|
+
|
|
186
|
+
| Symptom | Likely Cause |
|
|
187
|
+
|---------|--------------|
|
|
188
|
+
| State persists across calls | Mutable default argument |
|
|
189
|
+
| Loop variable wrong in closure | Late binding |
|
|
190
|
+
| Money calculations wrong | Float precision |
|
|
191
|
+
| First/last element wrong | Off-by-one |
|
|
192
|
+
| Intermittent failures | Race condition / shared state |
|
|
193
|
+
| Unicode errors | Bytes/str confusion |
|
|
194
|
+
| Second iteration empty | Iterator exhaustion |
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Avoid
|
|
199
|
+
|
|
200
|
+
- `except:` without exception type
|
|
201
|
+
- `list`, `dict`, `str` as variable names
|
|
202
|
+
- `float` for money
|
|
203
|
+
- Implicit iterator reuse
|
|
204
|
+
- User input in file paths without validation
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Validation Considerations
|
|
209
|
+
|
|
210
|
+
- `ruff` catches most of these (B006, B007, B008, etc.)
|
|
211
|
+
- `mypy` catches type issues
|
|
212
|
+
- Unit tests for edge cases
|
|
213
|
+
- Property-based testing finds edge cases
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Related Skills
|
|
218
|
+
|
|
219
|
+
- `debugging/root_cause.md`
|
|
220
|
+
- `anti_patterns/index.md`
|
|
221
|
+
- `security/input_validation.md`
|
|
222
|
+
- `generation/type_hints.md`
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Debugging: Inspection Techniques
|
|
2
|
+
|
|
3
|
+
**Purpose**: Tools and techniques for runtime inspection and debugging.
|
|
4
|
+
|
|
5
|
+
**When to use**: Investigating bugs, understanding code behavior, profiling.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Runtime Inspection
|
|
12
|
+
|
|
13
|
+
#### Object Inspection
|
|
14
|
+
```python
|
|
15
|
+
# Type and attributes
|
|
16
|
+
type(obj)
|
|
17
|
+
dir(obj)
|
|
18
|
+
vars(obj) # __dict__
|
|
19
|
+
hasattr(obj, 'attr')
|
|
20
|
+
getattr(obj, 'attr', default)
|
|
21
|
+
isinstance(obj, Type)
|
|
22
|
+
issubclass(cls, Type)
|
|
23
|
+
|
|
24
|
+
# Function inspection
|
|
25
|
+
import inspect
|
|
26
|
+
inspect.signature(func)
|
|
27
|
+
inspect.getsource(func)
|
|
28
|
+
inspect.getfile(func)
|
|
29
|
+
inspect.getmodule(func)
|
|
30
|
+
inspect.iscoroutinefunction(func)
|
|
31
|
+
inspect.isgeneratorfunction(func)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Frame Inspection
|
|
35
|
+
```python
|
|
36
|
+
import sys
|
|
37
|
+
|
|
38
|
+
# Current frame
|
|
39
|
+
frame = sys._getframe()
|
|
40
|
+
frame.f_locals # Local variables
|
|
41
|
+
frame.f_globals # Global variables
|
|
42
|
+
frame.f_code # Code object
|
|
43
|
+
frame.f_lineno # Line number
|
|
44
|
+
|
|
45
|
+
# Call stack
|
|
46
|
+
traceback.print_stack()
|
|
47
|
+
traceback.extract_stack()
|
|
48
|
+
|
|
49
|
+
# In exception handler
|
|
50
|
+
import traceback
|
|
51
|
+
traceback.print_exc()
|
|
52
|
+
traceback.format_exc()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### Object Graph
|
|
56
|
+
```python
|
|
57
|
+
import gc
|
|
58
|
+
|
|
59
|
+
# Find referrers (what references this object)
|
|
60
|
+
gc.get_referrers(obj)
|
|
61
|
+
|
|
62
|
+
# Find referents (what this object references)
|
|
63
|
+
gc.get_referents(obj)
|
|
64
|
+
|
|
65
|
+
# All objects of type
|
|
66
|
+
[obj for obj in gc.get_objects() if isinstance(obj, MyClass)]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Profiling
|
|
70
|
+
|
|
71
|
+
#### Time Profiling
|
|
72
|
+
```bash
|
|
73
|
+
# cProfile
|
|
74
|
+
python -m cProfile -o profile.stats script.py
|
|
75
|
+
# Analyze
|
|
76
|
+
python -m pstats profile.stats
|
|
77
|
+
# Sort by cumulative time, show top 20
|
|
78
|
+
# pstats> sort cumulative
|
|
79
|
+
# pstats> stats 20
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
# In code
|
|
84
|
+
import cProfile
|
|
85
|
+
import pstats
|
|
86
|
+
|
|
87
|
+
profiler = cProfile.Profile()
|
|
88
|
+
profiler.enable()
|
|
89
|
+
|
|
90
|
+
# ... code to profile ...
|
|
91
|
+
|
|
92
|
+
profiler.disable()
|
|
93
|
+
stats = pstats.Stats(profiler).sort_stats('cumulative')
|
|
94
|
+
stats.print_stats(20)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### Memory Profiling
|
|
98
|
+
```bash
|
|
99
|
+
# memray (modern, fast)
|
|
100
|
+
pip install memray
|
|
101
|
+
memray run script.py
|
|
102
|
+
memray flamegraph memray-results.bin
|
|
103
|
+
|
|
104
|
+
# objgraph (object counts)
|
|
105
|
+
pip install objgraph
|
|
106
|
+
python -c "import objgraph; objgraph.show_most_common_types()"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### Line Profiling
|
|
110
|
+
```bash
|
|
111
|
+
# kernprof
|
|
112
|
+
pip install line_profiler
|
|
113
|
+
kernprof -l -v script.py
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Logging for Debugging
|
|
117
|
+
```python
|
|
118
|
+
import logging
|
|
119
|
+
|
|
120
|
+
# Structured debug logging
|
|
121
|
+
logging.basicConfig(
|
|
122
|
+
level=logging.DEBUG,
|
|
123
|
+
format="%(asctime)s [%(levelname)s] %(name)s:%(lineno)d: %(message)s"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Context-specific logger
|
|
127
|
+
logger = logging.getLogger("myapp.debug")
|
|
128
|
+
|
|
129
|
+
# Conditional debug
|
|
130
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
131
|
+
logger.debug("Expensive debug: %s", expensive_computation())
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### REPL Debugging
|
|
135
|
+
```python
|
|
136
|
+
# In code
|
|
137
|
+
breakpoint()
|
|
138
|
+
# Python 3.7+ opens pdb at this line
|
|
139
|
+
# In pdb: p var, pp var, n, s, c, l, where, up, down
|
|
140
|
+
|
|
141
|
+
# Or embed IPython
|
|
142
|
+
import IPython
|
|
143
|
+
IPython.embed() # Rich REPL with syntax highlighting
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Async Debugging
|
|
147
|
+
```python
|
|
148
|
+
# Check running tasks
|
|
149
|
+
async def debug_tasks():
|
|
150
|
+
for task in asyncio.all_tasks():
|
|
151
|
+
print(task.get_name(), task.get_coro())
|
|
152
|
+
|
|
153
|
+
# Current task
|
|
154
|
+
current = asyncio.current_task()
|
|
155
|
+
print(current.get_stack())
|
|
156
|
+
|
|
157
|
+
# Trace async calls
|
|
158
|
+
import asyncio
|
|
159
|
+
asyncio.set_debug(True)
|
|
160
|
+
# Or
|
|
161
|
+
loop = asyncio.get_event_loop()
|
|
162
|
+
loop.set_debug(True)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Network Debugging
|
|
166
|
+
```bash
|
|
167
|
+
# HTTP traffic
|
|
168
|
+
mitmproxy # Interactive
|
|
169
|
+
mitmdump # Scriptable
|
|
170
|
+
|
|
171
|
+
# Or in Python
|
|
172
|
+
import http.client
|
|
173
|
+
http.client.HTTPConnection.debuglevel = 1
|
|
174
|
+
|
|
175
|
+
# requests
|
|
176
|
+
import logging
|
|
177
|
+
logging.getLogger("urllib3").setLevel(logging.DEBUG)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Database Debugging
|
|
181
|
+
```python
|
|
182
|
+
# SQLAlchemy
|
|
183
|
+
import logging
|
|
184
|
+
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
|
|
185
|
+
# Shows all SQL with parameters
|
|
186
|
+
|
|
187
|
+
# Or echo
|
|
188
|
+
engine = create_engine("postgresql://...", echo=True)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Decision Rules
|
|
194
|
+
|
|
195
|
+
| Need | Tool |
|
|
196
|
+
|------|------|
|
|
197
|
+
| Quick variable check | `breakpoint()` / `print()` |
|
|
198
|
+
| Performance bottleneck | `cProfile` / `memray` |
|
|
199
|
+
| Memory leak | `memray` / `objgraph` |
|
|
200
|
+
| Async deadlock | `asyncio` debug + task inspection |
|
|
201
|
+
| SQL query issues | SQLAlchemy echo |
|
|
202
|
+
| HTTP issues | `mitmproxy` / request logging |
|
|
203
|
+
| Object lifecycle | `gc` / `weakref` |
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Preferred Patterns
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
# Debug helper
|
|
211
|
+
def debug_obj(obj, name="obj"):
|
|
212
|
+
print(f"=== {name} ===")
|
|
213
|
+
print(f"Type: {type(obj)}")
|
|
214
|
+
print(f"Dir: {[a for a in dir(obj) if not a.startswith('_')]}")
|
|
215
|
+
if hasattr(obj, '__dict__'):
|
|
216
|
+
print(f"Dict: {vars(obj)}")
|
|
217
|
+
print(f"=== end {name} ===")
|
|
218
|
+
|
|
219
|
+
# Usage
|
|
220
|
+
debug_obj(user, "user")
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Avoid
|
|
226
|
+
|
|
227
|
+
- `print()` in production code
|
|
228
|
+
- Leaving `breakpoint()` in committed code
|
|
229
|
+
- Profiling in production without sampling
|
|
230
|
+
- `gc.get_objects()` in hot paths (slow)
|
|
231
|
+
- Modifying `sys.path` at runtime
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Validation Considerations
|
|
236
|
+
|
|
237
|
+
- Debug code removed before commit
|
|
238
|
+
- Profiling overhead acceptable
|
|
239
|
+
- Logs don't contain secrets
|
|
240
|
+
- Async debug doesn't change timing significantly
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Related Skills
|
|
245
|
+
|
|
246
|
+
- `debugging/root_cause.md`
|
|
247
|
+
- `debugging/common_bugs.md`
|
|
248
|
+
- `stdlib/logging.md`
|
|
249
|
+
- `generation/async_concurrency.md`
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Debugging: Root Cause Analysis
|
|
2
|
+
|
|
3
|
+
**Purpose**: Systematic approach to finding and fixing bugs.
|
|
4
|
+
|
|
5
|
+
**When to use**: Any bug investigation.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Debugging Process
|
|
12
|
+
```
|
|
13
|
+
1. REPRODUCE — Create minimal failing case
|
|
14
|
+
2. ISOLATE — Narrow down location
|
|
15
|
+
3. HYPOTHESIZE — Form theory of cause
|
|
16
|
+
4. TEST HYPOTHESIS — Verify with experiment
|
|
17
|
+
5. FIX — Minimal change addressing root cause
|
|
18
|
+
6. REGRESSION TEST — Prevent recurrence
|
|
19
|
+
7. DOCUMENT — Record for future
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Reproduction
|
|
23
|
+
```python
|
|
24
|
+
# Minimal reproduction script
|
|
25
|
+
# reproduce_issue.py
|
|
26
|
+
import sys
|
|
27
|
+
sys.path.insert(0, "src")
|
|
28
|
+
|
|
29
|
+
from mypackage import process
|
|
30
|
+
|
|
31
|
+
# Exact input that fails
|
|
32
|
+
input_data = load_failing_case()
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
result = process(input_data)
|
|
36
|
+
print("UNEXPECTED SUCCESS:", result)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print(f"REPRODUCED: {type(e).__name__}: {e}")
|
|
39
|
+
import traceback
|
|
40
|
+
traceback.print_exc()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Isolation Techniques
|
|
44
|
+
|
|
45
|
+
#### Binary Search (Git Bisect)
|
|
46
|
+
```bash
|
|
47
|
+
# Find commit that introduced bug
|
|
48
|
+
git bisect start
|
|
49
|
+
git bisect bad HEAD
|
|
50
|
+
git bisect good v1.0.0
|
|
51
|
+
# Git checks out middle commit
|
|
52
|
+
# Run tests: pytest test_failing.py
|
|
53
|
+
# git bisect good/bad
|
|
54
|
+
# Repeats until found
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### Print Debugging (Structured)
|
|
58
|
+
```python
|
|
59
|
+
import logging
|
|
60
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
61
|
+
|
|
62
|
+
# Or structured
|
|
63
|
+
import structlog
|
|
64
|
+
log = structlog.get_logger()
|
|
65
|
+
|
|
66
|
+
def problematic_function(data):
|
|
67
|
+
log.debug("enter", data_keys=list(data.keys()))
|
|
68
|
+
result = step1(data)
|
|
69
|
+
log.debug("after_step1", result=result)
|
|
70
|
+
result = step2(result)
|
|
71
|
+
log.debug("after_step2", result=result)
|
|
72
|
+
return result
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### Interactive Debugger
|
|
76
|
+
```python
|
|
77
|
+
# In code
|
|
78
|
+
breakpoint() # Python 3.7+
|
|
79
|
+
|
|
80
|
+
# Or conditional
|
|
81
|
+
if condition:
|
|
82
|
+
breakpoint()
|
|
83
|
+
|
|
84
|
+
# Run: python -m pdb script.py
|
|
85
|
+
# Commands: n (next), s (step), c (continue), p (print), l (list)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Common Bug Patterns
|
|
89
|
+
|
|
90
|
+
| Pattern | Symptoms | Investigation |
|
|
91
|
+
|---------|----------|---------------|
|
|
92
|
+
| Off-by-one | First/last element wrong | Check loop bounds, slice indices |
|
|
93
|
+
| Mutable default | State leaks between calls | Check function defaults |
|
|
94
|
+
| Race condition | Intermittent, timing-dependent | Add logging, check thread safety |
|
|
95
|
+
| Null reference | AttributeError/TypeError | Trace None propagation |
|
|
96
|
+
| Type mismatch | Unexpected type at runtime | Add type checks, check boundaries |
|
|
97
|
+
| Resource leak | Slow degradation, OOM | Check cleanup in finally/with |
|
|
98
|
+
| Encoding issue | Unicode errors, corrupt data | Check encode/decode boundaries |
|
|
99
|
+
|
|
100
|
+
### Hypothesis Testing
|
|
101
|
+
```python
|
|
102
|
+
# Hypothesis: "The bug is in validate_email()"
|
|
103
|
+
# Test: Call validate_email() directly with failing input
|
|
104
|
+
|
|
105
|
+
def test_hypothesis():
|
|
106
|
+
failing_email = "USER@EXAMPLE.COM"
|
|
107
|
+
result = validate_email(failing_email)
|
|
108
|
+
# If this fails → hypothesis confirmed
|
|
109
|
+
# If this passes → bug is elsewhere
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Fixing Root Cause
|
|
113
|
+
```python
|
|
114
|
+
# BAD — symptom fix
|
|
115
|
+
def process(data):
|
|
116
|
+
if data is None: # Handles symptom
|
|
117
|
+
return default()
|
|
118
|
+
return real_process(data)
|
|
119
|
+
|
|
120
|
+
# GOOD — root cause fix
|
|
121
|
+
def get_data() -> Data:
|
|
122
|
+
# Fix upstream to never return None
|
|
123
|
+
data = fetch()
|
|
124
|
+
if data is None:
|
|
125
|
+
raise DataNotFoundError() # Explicit failure
|
|
126
|
+
return data
|
|
127
|
+
|
|
128
|
+
def process(data: Data) -> Result: # Type hint documents non-None
|
|
129
|
+
return real_process(data)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Regression Test
|
|
133
|
+
```python
|
|
134
|
+
# Always add test for the exact bug
|
|
135
|
+
def test_issue_123_uppercase_email_normalized():
|
|
136
|
+
"""Regression: Issue #123 - uppercase email caused duplicate"""
|
|
137
|
+
user = create_user("USER@EXAMPLE.COM", "Test")
|
|
138
|
+
assert user.email == "user@example.com"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Decision Rules
|
|
144
|
+
|
|
145
|
+
| Bug Type | First Step |
|
|
146
|
+
|----------|------------|
|
|
147
|
+
| Crash | Get traceback, reproduce |
|
|
148
|
+
| Wrong output | Create minimal input |
|
|
149
|
+
| Performance | Profile, find bottleneck |
|
|
150
|
+
| Intermittent | Add extensive logging |
|
|
151
|
+
| Security | Isolate, assess impact |
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Preferred Patterns
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
# Debug context manager
|
|
159
|
+
from contextlib import contextmanager
|
|
160
|
+
|
|
161
|
+
@contextmanager
|
|
162
|
+
def debug_context(name: str):
|
|
163
|
+
log.debug(f"{name}: start")
|
|
164
|
+
try:
|
|
165
|
+
yield
|
|
166
|
+
except Exception as e:
|
|
167
|
+
log.debug(f"{name}: error", error=str(e))
|
|
168
|
+
raise
|
|
169
|
+
else:
|
|
170
|
+
log.debug(f"{name}: success")
|
|
171
|
+
|
|
172
|
+
with debug_context("process_order"):
|
|
173
|
+
result = process_order(order)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Avoid
|
|
179
|
+
|
|
180
|
+
- Fixing without reproducing
|
|
181
|
+
- Guessing instead of isolating
|
|
182
|
+
- Fixing symptoms, not root cause
|
|
183
|
+
- No regression test
|
|
184
|
+
- Large changes to fix small bug
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Validation Considerations
|
|
189
|
+
|
|
190
|
+
- Bug reproduced before fix
|
|
191
|
+
- Fix verified with reproduction case
|
|
192
|
+
- Regression test added
|
|
193
|
+
- Related tests still pass
|
|
194
|
+
- No similar bugs elsewhere (grep for pattern)
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Related Skills
|
|
199
|
+
|
|
200
|
+
- `debugging/common_bugs.md`
|
|
201
|
+
- `debugging/inspection_techniques.md`
|
|
202
|
+
- `testing/regression_tests.md`
|
|
203
|
+
- `generation/error_handling.md`
|