@booklib/skills 1.0.0 → 1.2.0
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.
- package/README.md +1 -0
- package/effective-python-skill/SKILL.md +199 -0
- package/effective-python-skill/ref-01-pythonic-thinking.md +202 -0
- package/effective-python-skill/ref-02-lists-and-dicts.md +146 -0
- package/effective-python-skill/ref-03-functions.md +186 -0
- package/effective-python-skill/ref-04-comprehensions-generators.md +211 -0
- package/effective-python-skill/ref-05-classes-interfaces.md +188 -0
- package/effective-python-skill/ref-06-metaclasses-attributes.md +209 -0
- package/effective-python-skill/ref-07-concurrency.md +213 -0
- package/effective-python-skill/ref-08-robustness-performance.md +248 -0
- package/effective-python-skill/ref-09-testing-debugging.md +253 -0
- package/effective-python-skill/ref-10-collaboration.md +175 -0
- package/package.json +8 -2
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Chapter 9: Testing and Debugging (Items 77-85)
|
|
2
|
+
|
|
3
|
+
## Item 77: Isolate Tests from Each Other via setUp, tearDown, setUpModule, etc.
|
|
4
|
+
```python
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
class DatabaseTestCase(unittest.TestCase):
|
|
8
|
+
@classmethod
|
|
9
|
+
def setUpClass(cls):
|
|
10
|
+
"""Run once before all tests in this class."""
|
|
11
|
+
cls.db = create_test_database()
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def tearDownClass(cls):
|
|
15
|
+
"""Run once after all tests in this class."""
|
|
16
|
+
cls.db.close()
|
|
17
|
+
|
|
18
|
+
def setUp(self):
|
|
19
|
+
"""Run before each test method."""
|
|
20
|
+
self.connection = self.db.connect()
|
|
21
|
+
self.transaction = self.connection.begin()
|
|
22
|
+
|
|
23
|
+
def tearDown(self):
|
|
24
|
+
"""Run after each test method."""
|
|
25
|
+
self.transaction.rollback()
|
|
26
|
+
self.connection.close()
|
|
27
|
+
|
|
28
|
+
def test_query(self):
|
|
29
|
+
result = self.connection.execute('SELECT 1')
|
|
30
|
+
self.assertEqual(result, 1)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- `setUp`/`tearDown` run for every test method (isolation)
|
|
34
|
+
- `setUpClass`/`tearDownClass` run once per class (expensive setup)
|
|
35
|
+
- `setUpModule`/`tearDownModule` run once per module
|
|
36
|
+
- Always clean up in tearDown (even if test fails)
|
|
37
|
+
|
|
38
|
+
## Item 78: Use Mocks to Test Code with Complex Dependencies
|
|
39
|
+
```python
|
|
40
|
+
from unittest.mock import patch, MagicMock, call
|
|
41
|
+
|
|
42
|
+
# Mock a function
|
|
43
|
+
@patch('mymodule.external_api_call')
|
|
44
|
+
def test_process(mock_api):
|
|
45
|
+
mock_api.return_value = {'status': 'ok'}
|
|
46
|
+
result = process_data()
|
|
47
|
+
mock_api.assert_called_once_with(expected_arg)
|
|
48
|
+
assert result == expected_result
|
|
49
|
+
|
|
50
|
+
# Mock an object's method
|
|
51
|
+
def test_with_mock():
|
|
52
|
+
mock_db = MagicMock()
|
|
53
|
+
mock_db.query.return_value = [{'id': 1}]
|
|
54
|
+
service = MyService(db=mock_db)
|
|
55
|
+
result = service.get_items()
|
|
56
|
+
mock_db.query.assert_called_once()
|
|
57
|
+
|
|
58
|
+
# Verify call order
|
|
59
|
+
mock_db.query.assert_has_calls([
|
|
60
|
+
call('SELECT * FROM users'),
|
|
61
|
+
call('SELECT * FROM orders'),
|
|
62
|
+
])
|
|
63
|
+
|
|
64
|
+
# Use spec for type checking
|
|
65
|
+
mock = MagicMock(spec=RealClass)
|
|
66
|
+
mock.nonexistent_method() # raises AttributeError
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- Mock external dependencies (APIs, databases, file systems)
|
|
70
|
+
- Use `@patch` to replace modules/objects during tests
|
|
71
|
+
- Use `spec=RealClass` to catch API mismatches
|
|
72
|
+
- Verify both return values and call patterns
|
|
73
|
+
- Use `side_effect` for exceptions or multiple return values:
|
|
74
|
+
```python
|
|
75
|
+
mock.side_effect = ValueError('error')
|
|
76
|
+
mock.side_effect = [1, 2, 3] # returns different values each call
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Item 79: Encapsulate Dependencies to Facilitate Mocking and Testing
|
|
80
|
+
```python
|
|
81
|
+
# BAD — hard-coded dependency
|
|
82
|
+
class DataProcessor:
|
|
83
|
+
def process(self):
|
|
84
|
+
data = requests.get('https://api.example.com/data').json()
|
|
85
|
+
return transform(data)
|
|
86
|
+
|
|
87
|
+
# GOOD — inject dependency
|
|
88
|
+
class DataProcessor:
|
|
89
|
+
def __init__(self, data_fetcher):
|
|
90
|
+
self._fetcher = data_fetcher
|
|
91
|
+
|
|
92
|
+
def process(self):
|
|
93
|
+
data = self._fetcher.get_data()
|
|
94
|
+
return transform(data)
|
|
95
|
+
|
|
96
|
+
# Easy to test
|
|
97
|
+
class FakeFetcher:
|
|
98
|
+
def get_data(self):
|
|
99
|
+
return {'test': 'data'}
|
|
100
|
+
|
|
101
|
+
processor = DataProcessor(FakeFetcher())
|
|
102
|
+
result = processor.process()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- Dependency injection makes code testable
|
|
106
|
+
- Accept dependencies as constructor or method parameters
|
|
107
|
+
- Use abstract base classes or protocols to define interfaces
|
|
108
|
+
- Fakes/stubs are often clearer than mocks for complex dependencies
|
|
109
|
+
|
|
110
|
+
## Item 80: Consider Interactive Debugging with pdb
|
|
111
|
+
```python
|
|
112
|
+
# Drop into debugger at a specific point
|
|
113
|
+
def complex_function(data):
|
|
114
|
+
result = step_one(data)
|
|
115
|
+
breakpoint() # Python 3.7+ (same as pdb.set_trace())
|
|
116
|
+
final = step_two(result)
|
|
117
|
+
return final
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Key pdb commands:**
|
|
121
|
+
- `n` (next) — execute next line
|
|
122
|
+
- `s` (step) — step into function call
|
|
123
|
+
- `c` (continue) — continue execution until next breakpoint
|
|
124
|
+
- `p expr` — print expression
|
|
125
|
+
- `pp expr` — pretty-print expression
|
|
126
|
+
- `l` (list) — show current code context
|
|
127
|
+
- `w` (where) — show call stack
|
|
128
|
+
- `b line` — set breakpoint at line
|
|
129
|
+
- `r` (return) — run until current function returns
|
|
130
|
+
- `q` (quit) — quit debugger
|
|
131
|
+
|
|
132
|
+
- Use `breakpoint()` (Python 3.7+) instead of `import pdb; pdb.set_trace()`
|
|
133
|
+
- Use `PYTHONBREAKPOINT=0` environment variable to disable all breakpoints
|
|
134
|
+
- Use `post_mortem()` to debug after an exception
|
|
135
|
+
|
|
136
|
+
## Item 81: Use tracemalloc to Understand Memory Usage and Leaks
|
|
137
|
+
```python
|
|
138
|
+
import tracemalloc
|
|
139
|
+
|
|
140
|
+
tracemalloc.start()
|
|
141
|
+
|
|
142
|
+
# ... run code that uses memory ...
|
|
143
|
+
|
|
144
|
+
snapshot = tracemalloc.take_snapshot()
|
|
145
|
+
top_stats = snapshot.statistics('lineno')
|
|
146
|
+
|
|
147
|
+
print('Top 10 memory allocations:')
|
|
148
|
+
for stat in top_stats[:10]:
|
|
149
|
+
print(stat)
|
|
150
|
+
|
|
151
|
+
# Compare snapshots to find leaks
|
|
152
|
+
snapshot1 = tracemalloc.take_snapshot()
|
|
153
|
+
# ... more code ...
|
|
154
|
+
snapshot2 = tracemalloc.take_snapshot()
|
|
155
|
+
top_stats = snapshot2.compare_to(snapshot1, 'lineno')
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- `tracemalloc` tracks where memory was allocated
|
|
159
|
+
- Use snapshot comparison to find memory leaks
|
|
160
|
+
- Shows file and line number of allocations
|
|
161
|
+
- Much more useful than `gc` module for debugging memory issues
|
|
162
|
+
|
|
163
|
+
## Item 82: Know Where to Find Community-Built Modules
|
|
164
|
+
- PyPI (Python Package Index) is the main repository
|
|
165
|
+
- Use `pip install` to install packages
|
|
166
|
+
- Check package health: last update, stars, downloads, issues
|
|
167
|
+
- Popular packages: requests, flask, django, pandas, numpy, pytest
|
|
168
|
+
|
|
169
|
+
## Item 83: Use Virtual Environments for Isolated and Reproducible Dependencies
|
|
170
|
+
```bash
|
|
171
|
+
# Create virtual environment
|
|
172
|
+
python3 -m venv myenv
|
|
173
|
+
|
|
174
|
+
# Activate
|
|
175
|
+
source myenv/bin/activate
|
|
176
|
+
|
|
177
|
+
# Install packages
|
|
178
|
+
pip install flask==2.0.1
|
|
179
|
+
|
|
180
|
+
# Freeze dependencies
|
|
181
|
+
pip freeze > requirements.txt
|
|
182
|
+
|
|
183
|
+
# Recreate environment
|
|
184
|
+
pip install -r requirements.txt
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
- Always use virtual environments for projects
|
|
188
|
+
- Never install packages globally with `pip`
|
|
189
|
+
- Use `requirements.txt` for reproducible environments
|
|
190
|
+
- Consider `pyproject.toml` and modern tools (poetry, pipenv)
|
|
191
|
+
|
|
192
|
+
## Item 84: Write Docstrings for Every Module, Class, and Function
|
|
193
|
+
```python
|
|
194
|
+
"""Module docstring: brief description of the module's purpose."""
|
|
195
|
+
|
|
196
|
+
class MyClass:
|
|
197
|
+
"""One-line summary of the class.
|
|
198
|
+
|
|
199
|
+
Extended description of the class if needed.
|
|
200
|
+
|
|
201
|
+
Attributes:
|
|
202
|
+
attr1: Description of attr1.
|
|
203
|
+
attr2: Description of attr2.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
def method(self, arg1: str, arg2: int = 0) -> bool:
|
|
207
|
+
"""One-line summary of method.
|
|
208
|
+
|
|
209
|
+
Extended description if needed.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
arg1: Description of arg1.
|
|
213
|
+
arg2: Description of arg2. Defaults to 0.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Description of return value.
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
ValueError: When arg1 is empty.
|
|
220
|
+
"""
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
- First line: one-line summary ending with period
|
|
224
|
+
- Blank line, then extended description if needed
|
|
225
|
+
- Document Args, Returns, Raises sections
|
|
226
|
+
- Use Google style or NumPy style consistently
|
|
227
|
+
- Type hints complement but don't replace docstrings
|
|
228
|
+
|
|
229
|
+
## Item 85: Use Packages to Organize Modules and Provide Stable APIs
|
|
230
|
+
```python
|
|
231
|
+
# mypackage/__init__.py
|
|
232
|
+
from mypackage.core import (
|
|
233
|
+
PublicClass,
|
|
234
|
+
public_function,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
__all__ = ['PublicClass', 'public_function']
|
|
238
|
+
|
|
239
|
+
# mypackage/core.py
|
|
240
|
+
class PublicClass:
|
|
241
|
+
...
|
|
242
|
+
|
|
243
|
+
def public_function():
|
|
244
|
+
...
|
|
245
|
+
|
|
246
|
+
def _private_helper(): # not exported
|
|
247
|
+
...
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
- Use `__init__.py` to define public API
|
|
251
|
+
- Use `__all__` to control `from package import *` behavior
|
|
252
|
+
- Keep internal modules private with `_` prefix
|
|
253
|
+
- Stable API = external code won't break when internals change
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Chapter 10: Collaboration (Items 86-90)
|
|
2
|
+
|
|
3
|
+
## Item 86: Consider Module-Scoped Code to Configure Deployment Environments
|
|
4
|
+
```python
|
|
5
|
+
# config.py
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
# Module-scoped code runs at import time
|
|
9
|
+
if sys.platform == 'win32':
|
|
10
|
+
DB_PATH = r'C:\data\mydb.sqlite'
|
|
11
|
+
else:
|
|
12
|
+
DB_PATH = '/data/mydb.sqlite'
|
|
13
|
+
|
|
14
|
+
# Use environment variables for deployment config
|
|
15
|
+
import os
|
|
16
|
+
ENVIRONMENT = os.environ.get('APP_ENV', 'development')
|
|
17
|
+
|
|
18
|
+
if ENVIRONMENT == 'production':
|
|
19
|
+
DEBUG = False
|
|
20
|
+
DATABASE_URL = os.environ['DATABASE_URL']
|
|
21
|
+
else:
|
|
22
|
+
DEBUG = True
|
|
23
|
+
DATABASE_URL = 'sqlite:///dev.db'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- Module-level code executes once at import time
|
|
27
|
+
- Use it for deployment configuration, feature flags
|
|
28
|
+
- Keep it minimal — complex logic at import time slows startup
|
|
29
|
+
- Prefer environment variables over hardcoded conditions
|
|
30
|
+
|
|
31
|
+
## Item 87: Define a Root Exception to Insulate Callers from APIs
|
|
32
|
+
```python
|
|
33
|
+
# mypackage/exceptions.py
|
|
34
|
+
class Error(Exception):
|
|
35
|
+
"""Base class for all exceptions in this package."""
|
|
36
|
+
|
|
37
|
+
class InvalidInputError(Error):
|
|
38
|
+
"""Raised when input validation fails."""
|
|
39
|
+
|
|
40
|
+
class AuthenticationError(Error):
|
|
41
|
+
"""Raised when authentication fails."""
|
|
42
|
+
|
|
43
|
+
class InternalError(Error):
|
|
44
|
+
"""Raised for unexpected internal errors."""
|
|
45
|
+
|
|
46
|
+
# Callers can catch all package errors
|
|
47
|
+
try:
|
|
48
|
+
result = mypackage.do_something()
|
|
49
|
+
except mypackage.Error:
|
|
50
|
+
# Catches any error from this package
|
|
51
|
+
logging.exception('Error from mypackage')
|
|
52
|
+
except Exception:
|
|
53
|
+
# Catches unexpected errors (bugs)
|
|
54
|
+
logging.exception('Unexpected error')
|
|
55
|
+
raise
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- Define a root `Error` class for your package/module
|
|
59
|
+
- All custom exceptions inherit from it
|
|
60
|
+
- Callers can catch the root to handle all your errors
|
|
61
|
+
- Three levels: root error (known API errors), specific errors, Exception (bugs)
|
|
62
|
+
- This insulates callers from internal changes to your exception hierarchy
|
|
63
|
+
|
|
64
|
+
## Item 88: Know How to Break Circular Dependencies
|
|
65
|
+
```python
|
|
66
|
+
# BAD — circular import
|
|
67
|
+
# module_a.py
|
|
68
|
+
from module_b import B
|
|
69
|
+
class A:
|
|
70
|
+
def use_b(self):
|
|
71
|
+
return B()
|
|
72
|
+
|
|
73
|
+
# module_b.py
|
|
74
|
+
from module_a import A # ImportError: circular!
|
|
75
|
+
class B:
|
|
76
|
+
def use_a(self):
|
|
77
|
+
return A()
|
|
78
|
+
|
|
79
|
+
# FIX 1 — import at function call time
|
|
80
|
+
# module_a.py
|
|
81
|
+
class A:
|
|
82
|
+
def use_b(self):
|
|
83
|
+
from module_b import B # lazy import
|
|
84
|
+
return B()
|
|
85
|
+
|
|
86
|
+
# FIX 2 — restructure to remove the cycle
|
|
87
|
+
# Move shared code to a third module
|
|
88
|
+
|
|
89
|
+
# FIX 3 — use import module, not from module import
|
|
90
|
+
import module_b
|
|
91
|
+
class A:
|
|
92
|
+
def use_b(self):
|
|
93
|
+
return module_b.B()
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Strategies (in order of preference):**
|
|
97
|
+
1. **Restructure** — move shared code to a common module
|
|
98
|
+
2. **Import at use time** — put import inside the function
|
|
99
|
+
3. **Import the module** — use `import module` instead of `from module import name`
|
|
100
|
+
|
|
101
|
+
## Item 89: Consider warnings to Refactor and Migrate Usage
|
|
102
|
+
```python
|
|
103
|
+
import warnings
|
|
104
|
+
|
|
105
|
+
def old_function():
|
|
106
|
+
"""Deprecated: use new_function instead."""
|
|
107
|
+
warnings.warn(
|
|
108
|
+
'old_function is deprecated, use new_function',
|
|
109
|
+
DeprecationWarning,
|
|
110
|
+
stacklevel=2 # point to caller, not this function
|
|
111
|
+
)
|
|
112
|
+
return new_function()
|
|
113
|
+
|
|
114
|
+
# Callers see:
|
|
115
|
+
# DeprecationWarning: old_function is deprecated, use new_function
|
|
116
|
+
# result = old_function() # <-- points to their code
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- Use `DeprecationWarning` for API migrations
|
|
120
|
+
- `stacklevel=2` makes the warning point to the caller's code
|
|
121
|
+
- Use `warnings.filterwarnings` to control warning behavior
|
|
122
|
+
- In tests, use `warnings.catch_warnings` to verify warnings are raised:
|
|
123
|
+
```python
|
|
124
|
+
with warnings.catch_warnings(record=True) as w:
|
|
125
|
+
warnings.simplefilter('always')
|
|
126
|
+
old_function()
|
|
127
|
+
assert len(w) == 1
|
|
128
|
+
assert issubclass(w[0].category, DeprecationWarning)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Item 90: Consider Static Analysis via typing to Obviate Bugs
|
|
132
|
+
```python
|
|
133
|
+
from typing import List, Dict, Optional, Tuple, Union
|
|
134
|
+
from typing import Protocol # Python 3.8+
|
|
135
|
+
|
|
136
|
+
# Basic type annotations
|
|
137
|
+
def greet(name: str) -> str:
|
|
138
|
+
return f'Hello, {name}'
|
|
139
|
+
|
|
140
|
+
# Collections
|
|
141
|
+
def process(items: List[int]) -> Dict[str, int]:
|
|
142
|
+
return {'total': sum(items)}
|
|
143
|
+
|
|
144
|
+
# Optional (can be None)
|
|
145
|
+
def find(name: str) -> Optional[str]:
|
|
146
|
+
...
|
|
147
|
+
|
|
148
|
+
# Protocol for structural typing (duck typing)
|
|
149
|
+
class Readable(Protocol):
|
|
150
|
+
def read(self) -> str:
|
|
151
|
+
...
|
|
152
|
+
|
|
153
|
+
def process_input(source: Readable) -> str:
|
|
154
|
+
return source.read()
|
|
155
|
+
|
|
156
|
+
# Generic classes
|
|
157
|
+
from typing import Generic, TypeVar
|
|
158
|
+
T = TypeVar('T')
|
|
159
|
+
|
|
160
|
+
class Stack(Generic[T]):
|
|
161
|
+
def __init__(self) -> None:
|
|
162
|
+
self._items: List[T] = []
|
|
163
|
+
|
|
164
|
+
def push(self, item: T) -> None:
|
|
165
|
+
self._items.append(item)
|
|
166
|
+
|
|
167
|
+
def pop(self) -> T:
|
|
168
|
+
return self._items.pop()
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
- Use type annotations for documentation and static analysis
|
|
172
|
+
- Run `mypy` for type checking: `mypy --strict mymodule.py`
|
|
173
|
+
- Use `Protocol` for structural typing (no inheritance needed)
|
|
174
|
+
- Type hints are not enforced at runtime (use `mypy` to check)
|
|
175
|
+
- Modern syntax (Python 3.10+): `list[int]` instead of `List[int]`, `X | Y` instead of `Union[X, Y]`
|
package/package.json
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@booklib/skills",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Book knowledge distilled into structured AI skills for Claude Code and other AI assistants",
|
|
5
5
|
"bin": {
|
|
6
6
|
"skills": "./bin/skills.js"
|
|
7
7
|
},
|
|
8
|
-
"keywords": [
|
|
8
|
+
"keywords": [
|
|
9
|
+
"claude",
|
|
10
|
+
"claude-code",
|
|
11
|
+
"ai",
|
|
12
|
+
"skills",
|
|
13
|
+
"agent-skills"
|
|
14
|
+
],
|
|
9
15
|
"license": "MIT",
|
|
10
16
|
"repository": {
|
|
11
17
|
"type": "git",
|