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
skills/stdlib/pathlib.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Stdlib: pathlib
|
|
2
|
+
|
|
3
|
+
**Purpose**: Modern, object-oriented filesystem paths.
|
|
4
|
+
|
|
5
|
+
**When to use**: All filesystem operations. Replaces `os.path`, `os.walk`, `glob`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Path Objects
|
|
12
|
+
```python
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
# Creation
|
|
16
|
+
Path("relative/path")
|
|
17
|
+
Path("/absolute/path")
|
|
18
|
+
Path.home() / "documents"
|
|
19
|
+
Path.cwd()
|
|
20
|
+
|
|
21
|
+
# From parts
|
|
22
|
+
Path("a", "b", "c") # a/b/c
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Properties
|
|
26
|
+
```python
|
|
27
|
+
p = Path("a/b/c.txt")
|
|
28
|
+
|
|
29
|
+
p.name # "c.txt"
|
|
30
|
+
p.stem # "c"
|
|
31
|
+
p.suffix # ".txt"
|
|
32
|
+
p.suffixes # [".txt"]
|
|
33
|
+
p.parent # Path("a/b")
|
|
34
|
+
p.parents # [Path("a/b"), Path("a"), Path(".")]
|
|
35
|
+
p.anchor # "" (or "/" on Unix, "C:\\" on Windows)
|
|
36
|
+
p.root # "/" or "C:\\"
|
|
37
|
+
p.drive # "" or "C:"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Navigation
|
|
41
|
+
```python
|
|
42
|
+
p / "subdir" / "file.txt" # Join
|
|
43
|
+
p.joinpath("sub", "file.txt") # Join (multiple args)
|
|
44
|
+
p.resolve() # Absolute, resolve symlinks
|
|
45
|
+
p.absolute() # Absolute (no symlink resolve)
|
|
46
|
+
p.relative_to(Path("a")) # Relative path (raises if not subpath)
|
|
47
|
+
p.is_relative_to(Path("a")) # Boolean check (Python 3.9+)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Filesystem Operations
|
|
51
|
+
```python
|
|
52
|
+
p.exists() # Exists (file, dir, symlink)
|
|
53
|
+
p.is_file() # Regular file
|
|
54
|
+
p.is_dir() # Directory
|
|
55
|
+
p.is_symlink() # Symlink
|
|
56
|
+
p.is_absolute() # Absolute path
|
|
57
|
+
p.is_relative() # Relative path (Python 3.13+)
|
|
58
|
+
|
|
59
|
+
p.stat() # os.stat_result
|
|
60
|
+
p.lstat() # Don't follow symlinks
|
|
61
|
+
p.owner() # Owner name
|
|
62
|
+
p.group() # Group name
|
|
63
|
+
|
|
64
|
+
# Times (float seconds since epoch)
|
|
65
|
+
p.stat().st_mtime # Modified
|
|
66
|
+
p.stat().st_atime # Accessed
|
|
67
|
+
p.stat().st_ctime # Created (Unix: metadata change)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Reading/Writing
|
|
71
|
+
```python
|
|
72
|
+
# Text
|
|
73
|
+
text = p.read_text(encoding="utf-8")
|
|
74
|
+
p.write_text("content", encoding="utf-8")
|
|
75
|
+
|
|
76
|
+
# Binary
|
|
77
|
+
data = p.read_bytes()
|
|
78
|
+
p.write_bytes(b"content")
|
|
79
|
+
|
|
80
|
+
# Open (returns file object)
|
|
81
|
+
with p.open("r", encoding="utf-8") as f:
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
# Append
|
|
85
|
+
p.write_text("more", encoding="utf-8", mode="a")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Directory Operations
|
|
89
|
+
```python
|
|
90
|
+
p.mkdir(parents=True, exist_ok=True) # Create dir (mkdir -p)
|
|
91
|
+
p.rmdir() # Remove empty dir
|
|
92
|
+
p.unlink(missing_ok=True) # Remove file (missing_ok Python 3.8+)
|
|
93
|
+
p.replace(target) # Atomic replace
|
|
94
|
+
p.rename(target) # Rename (not atomic across fs)
|
|
95
|
+
p.symlink_to(target) # Create symlink
|
|
96
|
+
p.hardlink_to(target) # Create hardlink
|
|
97
|
+
p.touch(exist_ok=True) # Create empty file / update mtime
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Iteration and Globbing
|
|
101
|
+
```python
|
|
102
|
+
# Iterate directory
|
|
103
|
+
for child in p.iterdir():
|
|
104
|
+
...
|
|
105
|
+
|
|
106
|
+
# Glob patterns
|
|
107
|
+
for match in p.glob("*.py"): # Non-recursive
|
|
108
|
+
...
|
|
109
|
+
for match in p.rglob("*.py"): # Recursive
|
|
110
|
+
...
|
|
111
|
+
|
|
112
|
+
# Walk (like os.walk)
|
|
113
|
+
for root, dirs, files in p.walk():
|
|
114
|
+
...
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Path Matching
|
|
118
|
+
```python
|
|
119
|
+
p.match("*.py") # Match final component
|
|
120
|
+
p.match("*/*.py") # Match relative pattern
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Temporary Files
|
|
124
|
+
```python
|
|
125
|
+
import tempfile
|
|
126
|
+
|
|
127
|
+
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
|
|
128
|
+
path = Path(f.name)
|
|
129
|
+
# Use path
|
|
130
|
+
# Cleanup manual or use context manager
|
|
131
|
+
|
|
132
|
+
# Better: tempfile.TemporaryDirectory
|
|
133
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
134
|
+
path = Path(tmp) / "file.txt"
|
|
135
|
+
...
|
|
136
|
+
# Auto cleanup
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Decision Rules
|
|
142
|
+
|
|
143
|
+
| Operation | pathlib Method |
|
|
144
|
+
|-----------|----------------|
|
|
145
|
+
| Join paths | `p / "name"` or `p.joinpath()` |
|
|
146
|
+
| Get parent | `p.parent` |
|
|
147
|
+
| Get filename | `p.name` |
|
|
148
|
+
| Get extension | `p.suffix` / `p.suffixes` |
|
|
149
|
+
| Read text file | `p.read_text(encoding="utf-8")` |
|
|
150
|
+
| Write text file | `p.write_text(content, encoding="utf-8")` |
|
|
151
|
+
| Read binary | `p.read_bytes()` |
|
|
152
|
+
| Write binary | `p.write_bytes(data)` |
|
|
153
|
+
| Create dirs | `p.mkdir(parents=True, exist_ok=True)` |
|
|
154
|
+
| Delete file | `p.unlink(missing_ok=True)` |
|
|
155
|
+
| Delete empty dir | `p.rmdir()` |
|
|
156
|
+
| List directory | `p.iterdir()` |
|
|
157
|
+
| Find files | `p.glob()` / `p.rglob()` |
|
|
158
|
+
| Walk tree | `p.walk()` |
|
|
159
|
+
| Atomic write | Write to temp, `p.replace(target)` |
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Preferred Patterns
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
# Atomic write (prevents partial reads)
|
|
167
|
+
def atomic_write(path: Path, content: str, encoding: str = "utf-8") -> None:
|
|
168
|
+
tmp = path.with_suffix(path.suffix + ".tmp")
|
|
169
|
+
tmp.write_text(content, encoding=encoding)
|
|
170
|
+
tmp.replace(path)
|
|
171
|
+
|
|
172
|
+
# Safe config loading
|
|
173
|
+
def load_config(path: Path) -> dict:
|
|
174
|
+
if not path.is_file():
|
|
175
|
+
return {}
|
|
176
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
177
|
+
|
|
178
|
+
# Recursive find with filter
|
|
179
|
+
py_files = list(project_root.rglob("*.py"))
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Avoid
|
|
185
|
+
|
|
186
|
+
- `os.path` functions (use pathlib equivalents)
|
|
187
|
+
- String manipulation for paths (`+` or `os.path.join`)
|
|
188
|
+
- `glob.glob()` (use `Path.glob()` / `rglob()`)
|
|
189
|
+
- `os.walk()` (use `Path.walk()`)
|
|
190
|
+
- Not specifying `encoding` in text operations
|
|
191
|
+
- `missing_ok=False` (default) — raises on missing file
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Windows Notes
|
|
196
|
+
|
|
197
|
+
- `Path` handles `\` and `/` correctly
|
|
198
|
+
- `p.drive` returns `"C:"` on Windows
|
|
199
|
+
- `p.anchor` returns `"C:\\"` on Windows
|
|
200
|
+
- Symlinks require admin or Developer Mode
|
|
201
|
+
- Case-insensitive filesystem
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Validation Considerations
|
|
206
|
+
|
|
207
|
+
- Type checkers understand `Path` methods
|
|
208
|
+
- `Path` implements `os.PathLike` — works with stdlib APIs
|
|
209
|
+
- `resolve()` may raise on broken symlinks
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Related Skills
|
|
214
|
+
|
|
215
|
+
- `stdlib/os_sys.md`
|
|
216
|
+
- `engineering/configuration.md`
|
|
217
|
+
- `security/path_traversal.md`
|
|
218
|
+
- `security/file_handling.md`
|
skills/stdlib/re.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Stdlib: re (Regular Expressions)
|
|
2
|
+
|
|
3
|
+
**Purpose**: Pattern matching and text processing.
|
|
4
|
+
|
|
5
|
+
**When to use**: String validation, extraction, transformation. Not for parsing structured formats (HTML, JSON, etc.).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Basic Usage
|
|
12
|
+
```python
|
|
13
|
+
import re
|
|
14
|
+
|
|
15
|
+
# Compile once, use many (performance)
|
|
16
|
+
PATTERN = re.compile(r"\d{4}-\d{2}-\d{2}")
|
|
17
|
+
|
|
18
|
+
# Match at start
|
|
19
|
+
PATTERN.match("2024-01-15") # Match object or None
|
|
20
|
+
PATTERN.match("2024-01-15 text") # Matches!
|
|
21
|
+
|
|
22
|
+
# Search anywhere
|
|
23
|
+
PATTERN.search("Date: 2024-01-15") # Match object or None
|
|
24
|
+
|
|
25
|
+
# Full match (entire string)
|
|
26
|
+
PATTERN.fullmatch("2024-01-15") # Match object or None
|
|
27
|
+
|
|
28
|
+
# Find all
|
|
29
|
+
PATTERN.findall("2024-01-15 and 2024-02-20") # List of strings/groups
|
|
30
|
+
PATTERN.finditer("...") # Iterator of Match objects
|
|
31
|
+
|
|
32
|
+
# Substitute
|
|
33
|
+
PATTERN.sub("REDACTED", text) # Replace all
|
|
34
|
+
PATTERN.subn("REDACTED", text) # (new_string, count)
|
|
35
|
+
|
|
36
|
+
# Split
|
|
37
|
+
PATTERN.split("a2024-01-15b") # ["a", "b"]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Match Object
|
|
41
|
+
```python
|
|
42
|
+
m = PATTERN.search("Date: 2024-01-15")
|
|
43
|
+
if m:
|
|
44
|
+
m.group() # Entire match
|
|
45
|
+
m.group(1) # Group 1
|
|
46
|
+
m.groups() # All groups tuple
|
|
47
|
+
m.groupdict() # Named groups dict
|
|
48
|
+
m.start(), m.end() # Span
|
|
49
|
+
m.span(1) # Span of group 1
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Common Patterns
|
|
53
|
+
```python
|
|
54
|
+
# Email (simplified)
|
|
55
|
+
r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
|
|
56
|
+
|
|
57
|
+
# URL
|
|
58
|
+
r"https?://[^\s/$.?#].[^\s]*"
|
|
59
|
+
|
|
60
|
+
# IP address
|
|
61
|
+
r"\b(?:\d{1,3}\.){3}\d{1,3}\b"
|
|
62
|
+
|
|
63
|
+
# UUID
|
|
64
|
+
r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
|
65
|
+
|
|
66
|
+
# ISO datetime
|
|
67
|
+
r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Flags
|
|
71
|
+
```python
|
|
72
|
+
re.IGNORECASE / re.I # Case insensitive
|
|
73
|
+
re.MULTILINE / re.M # ^ $ match line start/end
|
|
74
|
+
re.DOTALL / re.S # . matches newline
|
|
75
|
+
re.VERBOSE / re.X # Verbose mode (whitespace ignored, comments)
|
|
76
|
+
re.ASCII / re.A # \w, \d, \s ASCII only
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Verbose Mode (Readable)
|
|
80
|
+
```python
|
|
81
|
+
EMAIL_PATTERN = re.compile(r"""
|
|
82
|
+
[a-zA-Z0-9._%+-]+ # Local part
|
|
83
|
+
@ # Separator
|
|
84
|
+
[a-zA-Z0-9.-]+ # Domain
|
|
85
|
+
\.[a-zA-Z]{2,} # TLD
|
|
86
|
+
""", re.VERBOSE)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Raw Strings (Critical)
|
|
90
|
+
```python
|
|
91
|
+
# ALWAYS use raw strings for regex
|
|
92
|
+
r"\d+" # Correct: backslash preserved
|
|
93
|
+
"\d+" # Wrong: "\d" = "d" in string literal
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Decision Rules
|
|
99
|
+
|
|
100
|
+
| Need | Method |
|
|
101
|
+
|------|--------|
|
|
102
|
+
| Validate entire string | `fullmatch` |
|
|
103
|
+
| Find first occurrence | `search` |
|
|
104
|
+
| Check prefix | `match` |
|
|
105
|
+
| Extract all occurrences | `findall` / `finditer` |
|
|
106
|
+
| Replace all | `sub` |
|
|
107
|
+
| Replace with function | `sub(lambda m: ...)` |
|
|
108
|
+
| Split on pattern | `split` |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Preferred Patterns
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
# Compile at module level (not in function)
|
|
116
|
+
ISO_DATE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
|
117
|
+
|
|
118
|
+
def is_valid_date(s: str) -> bool:
|
|
119
|
+
return ISO_DATE.fullmatch(s) is not None
|
|
120
|
+
|
|
121
|
+
# Named groups for clarity
|
|
122
|
+
LOG_PATTERN = re.compile(
|
|
123
|
+
r"(?P<timestamp>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})"
|
|
124
|
+
r"\s+"
|
|
125
|
+
r"(?P<level>\w+)"
|
|
126
|
+
r"\s+"
|
|
127
|
+
r"(?P<message>.*)"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
for match in LOG_PATTERN.finditer(log_text):
|
|
131
|
+
print(match.groupdict())
|
|
132
|
+
|
|
133
|
+
# Substitution with function
|
|
134
|
+
def redact_emails(text: str) -> str:
|
|
135
|
+
return EMAIL_PATTERN.sub(lambda m: f"{m.group(1)}@[REDACTED]", text)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Avoid
|
|
141
|
+
|
|
142
|
+
- Regex for parsing HTML/XML/JSON (use proper parsers)
|
|
143
|
+
- Complex regex without `re.VERBOSE` and comments
|
|
144
|
+
- `.*` greedy matching when `.*?` non-greedy needed
|
|
145
|
+
- Not using raw strings (`r"..."`)
|
|
146
|
+
- Compiling regex inside hot loops
|
|
147
|
+
- Catastrophic backtracking (nested quantifiers: `(a+)+`)
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Performance
|
|
152
|
+
|
|
153
|
+
- Compile once, reuse
|
|
154
|
+
- `fullmatch` > `match` + `$` > `search` + `^...$`
|
|
155
|
+
- `finditer` > `findall` for large data (memory)
|
|
156
|
+
- Specific patterns faster than generic
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Validation Considerations
|
|
161
|
+
|
|
162
|
+
- `re.compile` validates pattern syntax at compile time
|
|
163
|
+
- Type checkers don't validate regex patterns
|
|
164
|
+
- Test edge cases: empty string, no match, multiple matches
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Related Skills
|
|
169
|
+
|
|
170
|
+
- `security/input_validation.md`
|
|
171
|
+
- `generation/type_hints.md` (Pattern type)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Stdlib: statistics
|
|
2
|
+
|
|
3
|
+
**Purpose**: Basic statistical operations on numeric data.
|
|
4
|
+
|
|
5
|
+
**When to use**: Simple statistics without NumPy/pandas dependency.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Measures of Central Tendency
|
|
12
|
+
```python
|
|
13
|
+
import statistics
|
|
14
|
+
|
|
15
|
+
data = [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
16
|
+
|
|
17
|
+
statistics.mean(data) # Arithmetic mean: 5
|
|
18
|
+
statistics.fmean(data) # Fast float mean (3.8+): 5.0
|
|
19
|
+
statistics.median(data) # Median: 5
|
|
20
|
+
statistics.median_low(data) # Low median: 5
|
|
21
|
+
statistics.median_high(data) # High median: 5
|
|
22
|
+
statistics.median_grouped(data, interval=1) # Grouped median
|
|
23
|
+
statistics.mode(data) # Single mode (raises if multimodal)
|
|
24
|
+
statistics.multimode(data) # List of modes (3.8+)
|
|
25
|
+
statistics.harmonic_mean(data) # Harmonic mean
|
|
26
|
+
statistics.geometric_mean(data) # Geometric mean (3.8+)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Measures of Spread
|
|
30
|
+
```python
|
|
31
|
+
statistics.stdev(data) # Sample standard deviation
|
|
32
|
+
statistics.pstdev(data) # Population standard deviation
|
|
33
|
+
statistics.variance(data) # Sample variance
|
|
34
|
+
statistics.pvariance(data) # Population variance
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Requirements
|
|
38
|
+
- Input: iterable of numeric (int, float, Decimal, Fraction)
|
|
39
|
+
- At least 2 data points for stdev/variance
|
|
40
|
+
- At least 1 for mean/median
|
|
41
|
+
- Raises `StatisticsError` for insufficient data
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Decision Rules
|
|
46
|
+
|
|
47
|
+
| Need | Function |
|
|
48
|
+
|------|----------|
|
|
49
|
+
| Average | `mean` / `fmean` |
|
|
50
|
+
| Middle value | `median` |
|
|
51
|
+
| Most common | `mode` / `multimode` |
|
|
52
|
+
| Spread (sample) | `stdev` / `variance` |
|
|
53
|
+
| Spread (population) | `pstdev` / `pvariance` |
|
|
54
|
+
| Weighted average | Manual or NumPy |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Preferred Patterns
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
def summarize(values: list[float]) -> dict:
|
|
62
|
+
if not values:
|
|
63
|
+
return {"count": 0}
|
|
64
|
+
if len(values) == 1:
|
|
65
|
+
return {"count": 1, "mean": values[0], "median": values[0]}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
"count": len(values),
|
|
69
|
+
"mean": statistics.fmean(values),
|
|
70
|
+
"median": statistics.median(values),
|
|
71
|
+
"stdev": statistics.stdev(values),
|
|
72
|
+
"min": min(values),
|
|
73
|
+
"max": max(values),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# Streaming (large data) — use running calculation
|
|
77
|
+
def running_stats():
|
|
78
|
+
n = 0
|
|
79
|
+
mean = 0.0
|
|
80
|
+
m2 = 0.0 # Sum of squares of differences
|
|
81
|
+
for x in data_stream:
|
|
82
|
+
n += 1
|
|
83
|
+
delta = x - mean
|
|
84
|
+
mean += delta / n
|
|
85
|
+
m2 += delta * (x - mean)
|
|
86
|
+
if n > 1:
|
|
87
|
+
yield {"mean": mean, "stdev": (m2 / (n - 1)) ** 0.5}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Avoid
|
|
93
|
+
|
|
94
|
+
- Using for large datasets (use NumPy/pandas)
|
|
95
|
+
- Calling on empty iterables (raises)
|
|
96
|
+
- Expecting weighted statistics (not supported)
|
|
97
|
+
- Using `mode` on multimodal data (raises; use `multimode`)
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Validation Considerations
|
|
102
|
+
|
|
103
|
+
- Test with edge cases: empty, single element, two elements
|
|
104
|
+
- Verify numeric precision for large values
|
|
105
|
+
- Compare with NumPy for correctness if available
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Related Skills
|
|
110
|
+
|
|
111
|
+
- `generation/type_hints.md`
|
|
112
|
+
- `engineering/dependency_management.md` (when to add NumPy)
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Stdlib: subprocess
|
|
2
|
+
|
|
3
|
+
**Purpose**: Safe subprocess execution and process management.
|
|
4
|
+
|
|
5
|
+
**When to use**: Running external commands. Prefer stdlib APIs when possible.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Rules
|
|
10
|
+
|
|
11
|
+
### Basic Usage (Python 3.5+)
|
|
12
|
+
```python
|
|
13
|
+
import subprocess
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
# Simple run (recommended)
|
|
17
|
+
result = subprocess.run(
|
|
18
|
+
["cmd", "arg1", "arg2"],
|
|
19
|
+
capture_output=True, # stdout, stderr as bytes
|
|
20
|
+
text=True, # Decode as str (encoding=locale)
|
|
21
|
+
encoding="utf-8", # Explicit encoding
|
|
22
|
+
timeout=30, # Seconds
|
|
23
|
+
check=False, # Don't raise on non-zero
|
|
24
|
+
cwd=Path("/work"),
|
|
25
|
+
env={"VAR": "value"}, # Or None to inherit
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
result.returncode
|
|
29
|
+
result.stdout
|
|
30
|
+
result.stderr
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Safe Patterns
|
|
34
|
+
```python
|
|
35
|
+
# ALWAYS use list form (not shell=True)
|
|
36
|
+
subprocess.run(["git", "status"]) # Safe
|
|
37
|
+
subprocess.run("git status", shell=True) # UNSAFE - shell injection!
|
|
38
|
+
|
|
39
|
+
# With input
|
|
40
|
+
result = subprocess.run(
|
|
41
|
+
["grep", "pattern"],
|
|
42
|
+
input="data\nto\nsearch",
|
|
43
|
+
capture_output=True,
|
|
44
|
+
text=True,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Streaming (large output)
|
|
48
|
+
proc = subprocess.Popen(
|
|
49
|
+
["cmd", "arg"],
|
|
50
|
+
stdout=subprocess.PIPE,
|
|
51
|
+
stderr=subprocess.PIPE,
|
|
52
|
+
text=True,
|
|
53
|
+
)
|
|
54
|
+
for line in proc.stdout:
|
|
55
|
+
process(line)
|
|
56
|
+
proc.wait()
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Error Handling
|
|
60
|
+
```python
|
|
61
|
+
try:
|
|
62
|
+
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
|
|
63
|
+
except subprocess.CalledProcessError as e:
|
|
64
|
+
# e.returncode, e.stdout, e.stderr, e.cmd
|
|
65
|
+
logger.error("Command failed", extra={"cmd": e.cmd, "stderr": e.stderr})
|
|
66
|
+
raise
|
|
67
|
+
except subprocess.TimeoutExpired as e:
|
|
68
|
+
# e.stdout, e.stderr (partial)
|
|
69
|
+
logger.error("Command timed out")
|
|
70
|
+
raise
|
|
71
|
+
except FileNotFoundError:
|
|
72
|
+
logger.error("Command not found", extra={"cmd": cmd[0]})
|
|
73
|
+
raise
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Pipes and Chaining
|
|
77
|
+
```python
|
|
78
|
+
# Safe pipeline without shell
|
|
79
|
+
p1 = subprocess.Popen(["cmd1"], stdout=subprocess.PIPE)
|
|
80
|
+
p2 = subprocess.Popen(["cmd2"], stdin=p1.stdout, stdout=subprocess.PIPE)
|
|
81
|
+
p1.stdout.close() # Allow p1 to receive SIGPIPE
|
|
82
|
+
output = p2.communicate()[0]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Environment
|
|
86
|
+
```python
|
|
87
|
+
# Inherit + modify
|
|
88
|
+
env = os.environ.copy()
|
|
89
|
+
env["CUSTOM_VAR"] = "value"
|
|
90
|
+
subprocess.run(cmd, env=env)
|
|
91
|
+
|
|
92
|
+
# Clean environment
|
|
93
|
+
subprocess.run(cmd, env={"PATH": "/usr/bin", "HOME": "/tmp"})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Security Rules (Critical)
|
|
99
|
+
|
|
100
|
+
### NEVER use `shell=True` with untrusted input
|
|
101
|
+
```python
|
|
102
|
+
# DANGEROUS
|
|
103
|
+
subprocess.run(f"echo {user_input}", shell=True)
|
|
104
|
+
# User input: "; rm -rf /"
|
|
105
|
+
|
|
106
|
+
# SAFE
|
|
107
|
+
subprocess.run(["echo", user_input])
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Validate/Allowlist Commands
|
|
111
|
+
```python
|
|
112
|
+
ALLOWED_COMMANDS = {"git", "docker", "kubectl"}
|
|
113
|
+
|
|
114
|
+
def safe_run(cmd: list[str], **kwargs):
|
|
115
|
+
if cmd[0] not in ALLOWED_COMMANDS:
|
|
116
|
+
raise ValueError(f"Command not allowed: {cmd[0]}")
|
|
117
|
+
return subprocess.run(cmd, **kwargs)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Path Safety
|
|
121
|
+
```python
|
|
122
|
+
# Resolve paths, prevent traversal
|
|
123
|
+
safe_path = Path(user_input).resolve()
|
|
124
|
+
if not safe_path.is_relative_to(ALLOWED_ROOT):
|
|
125
|
+
raise ValueError("Path traversal attempt")
|
|
126
|
+
subprocess.run(["process", str(safe_path)])
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Decision Rules
|
|
132
|
+
|
|
133
|
+
| Situation | Approach |
|
|
134
|
+
|-----------|----------|
|
|
135
|
+
| Simple command, wait for result | `subprocess.run()` |
|
|
136
|
+
| Need streaming/large output | `subprocess.Popen` |
|
|
137
|
+
| Pipeline | `Popen` chain (no shell) |
|
|
138
|
+
| Fire and forget | `Popen` + `detach()` or `start_new_session=True` |
|
|
139
|
+
| Need shell features (glob, vars) | Avoid; reimplement in Python |
|
|
140
|
+
| Untrusted input in command | List form, validate/allowlist |
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Preferred Patterns
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
def run_command(
|
|
148
|
+
cmd: list[str],
|
|
149
|
+
*,
|
|
150
|
+
cwd: Path | None = None,
|
|
151
|
+
timeout: float = 30.0,
|
|
152
|
+
env: dict[str, str] | None = None,
|
|
153
|
+
input_data: str | None = None,
|
|
154
|
+
) -> subprocess.CompletedProcess:
|
|
155
|
+
"""Safe subprocess wrapper."""
|
|
156
|
+
# Validate command
|
|
157
|
+
if not cmd or not isinstance(cmd, list):
|
|
158
|
+
raise ValueError("Command must be non-empty list")
|
|
159
|
+
|
|
160
|
+
# Allowlist check (optional but recommended)
|
|
161
|
+
# if cmd[0] not in ALLOWED_COMMANDS: ...
|
|
162
|
+
|
|
163
|
+
return subprocess.run(
|
|
164
|
+
cmd,
|
|
165
|
+
capture_output=True,
|
|
166
|
+
text=True,
|
|
167
|
+
encoding="utf-8",
|
|
168
|
+
errors="replace", # Handle encoding issues
|
|
169
|
+
timeout=timeout,
|
|
170
|
+
check=False, # Handle returncode manually
|
|
171
|
+
cwd=cwd,
|
|
172
|
+
env=env,
|
|
173
|
+
input=input_data,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Usage
|
|
177
|
+
result = run_command(["git", "diff", "--name-only"])
|
|
178
|
+
if result.returncode != 0:
|
|
179
|
+
logger.warning("Git diff failed", stderr=result.stderr)
|
|
180
|
+
files = result.stdout.strip().splitlines()
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Avoid
|
|
186
|
+
|
|
187
|
+
- `shell=True` (except trusted, static commands)
|
|
188
|
+
- String commands (always use list)
|
|
189
|
+
- Unvalidated user input in command args
|
|
190
|
+
- `os.system()`, `os.popen()` (legacy, unsafe)
|
|
191
|
+
- Ignoring `returncode`
|
|
192
|
+
- No timeout (hangs indefinitely)
|
|
193
|
+
- Inheriting env without consideration (leaks secrets)
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Validation Considerations
|
|
198
|
+
|
|
199
|
+
- Test with malicious input (injection attempts)
|
|
200
|
+
- Verify timeout behavior
|
|
201
|
+
- Check encoding handling
|
|
202
|
+
- Audit command allowlists
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Related Skills
|
|
207
|
+
|
|
208
|
+
- `security/command_injection.md`
|
|
209
|
+
- `security/path_traversal.md`
|
|
210
|
+
- `stdlib/os_sys.md`
|
|
211
|
+
- `engineering/configuration.md`
|