microsoft/hve-core

Public

mirrored fromhttps://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
hve-core-v3.3.27

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

.github/instructions/coding-standards/python-script.instructions.md

263lines · modecode

1---
2applyTo: '**/*.py'
3description: 'Instructions for Python scripting implementation - Brought to you by microsoft/hve-core'
4---
5
6# Python Script Instructions
7
8Conventions for Python 3.11+ scripts used in automation, tooling, and CLI applications.
9
10## Entry Points and Exit Codes
11
12```python
13import sys
14
15EXIT_SUCCESS = 0 # Successful execution
16EXIT_FAILURE = 1 # General failure
17EXIT_ERROR = 2 # Arguments or configuration error
18
19
20def main() -> int:
21 """Main entry point for the script."""
22 return EXIT_SUCCESS
23
24
25if __name__ == "__main__":
26 sys.exit(main())
27```
28
29Standard exit codes: 0 success, 1 failure, 2 configuration error, 130 user interrupt (SIGINT).
30
31## CLI Argument Parsing
32
33### argparse
34
35Extract parser creation into a separate function for testability.
36
37```python
38import argparse
39from pathlib import Path
40
41
42def create_parser() -> argparse.ArgumentParser:
43 """Create and configure argument parser."""
44 parser = argparse.ArgumentParser(description="Process files")
45 parser.add_argument("-v", "--verbose", action="store_true")
46 parser.add_argument("-o", "--output", type=Path, default=Path("output.txt"))
47 parser.add_argument("input_file", type=Path)
48 return parser
49```
50
51Use `type=Path` for file arguments and `action="store_true"` for boolean flags.
52
53### click
54
55For complex CLIs with subcommands or interactive prompts, use the *click* framework.
56
57```python
58import click
59
60
61@click.command()
62@click.option("-v", "--verbose", is_flag=True)
63@click.argument("input_file", type=click.Path(exists=True))
64@click.pass_context
65def main(ctx: click.Context, verbose: bool, input_file: str) -> None:
66 """Process input files."""
67 ctx.exit(0) # Explicit exit code
68```
69
70Use `@click.group()` for subcommands, `ctx.exit(code)` for exit codes, and `ctx.fail(message)` for errors.
71
72## Logging Configuration
73
74```python
75import logging
76
77logger = logging.getLogger(__name__)
78
79
80def configure_logging(verbose: bool = False) -> None:
81 """Configure logging based on verbosity level."""
82 level = logging.DEBUG if verbose else logging.INFO
83 logging.basicConfig(level=level, format="%(levelname)s: %(message)s")
84```
85
86Create module-level logger, configure early in main. For file logging, add *FileHandler* to the root logger.
87
88## Path Handling
89
90Use *pathlib.Path* exclusively; avoid *os.path*.
91
92```python
93from pathlib import Path
94
95
96def process_file(path: Path) -> None:
97 """Read, process, and write file content."""
98 content = path.read_text(encoding="utf-8")
99 processed = transform_content(content)
100 output_path = path.with_suffix(".out")
101 output_path.parent.mkdir(parents=True, exist_ok=True)
102 output_path.write_text(processed, encoding="utf-8")
103```
104
105Common patterns: `cwd()`, `resolve()`, `exists()`, `is_dir()`, `is_file()`, `iterdir()`, `glob()`, `rglob()`, `read_text()`, `write_text()`, `mkdir(parents=True, exist_ok=True)`, `parent`, `name`, `stem`, `suffix`.
106
107## Subprocess Execution
108
109Use *subprocess.run()* with error handling.
110
111```python
112import subprocess
113import os
114from pathlib import Path
115
116
117def run_command(cmd: list[str], cwd: Path | None = None, extra_env: dict[str, str] | None = None) -> str:
118 """Run command and return stdout, raising on failure."""
119 env = os.environ.copy()
120 if extra_env:
121 env.update(extra_env)
122 try:
123 result = subprocess.run(cmd, capture_output=True, text=True, check=True, cwd=cwd, env=env)
124 return result.stdout
125 except subprocess.CalledProcessError as e:
126 logger.error("Command failed: %s\nstderr: %s", e.returncode, e.stderr)
127 raise
128 except FileNotFoundError:
129 logger.error("Command not found: %s", cmd[0])
130 raise
131```
132
133Use `capture_output=True` and `text=True` for string output. Use `check=True` to raise on non-zero exit.
134
135## Type Hints
136
137Use Python 3.11+ syntax with built-in generics.
138
139```python
140from pathlib import Path
141from typing import Literal, Self
142
143
144def process_items(items: list[str]) -> dict[str, int]: # Built-in generics
145 return {item: len(item) for item in items}
146
147
148def read_file(path: str | Path) -> str: # Union with pipe
149 return Path(path).read_text(encoding="utf-8")
150
151
152def find_config(name: str) -> Path | None: # Optional with pipe
153 config = Path(name)
154 return config if config.exists() else None
155
156
157def set_level(level: Literal["debug", "info", "warning"]) -> None: # Constrained values
158 pass
159
160
161class Builder:
162 def add(self, item: str) -> Self: # Fluent interface
163 self.items.append(item)
164 return self
165```
166
167Use `list[str]` not `typing.List[str]`, `str | None` not `Optional[str]`, `Literal` for constrained values, `Self` for chained methods.
168
169## Error Handling
170
171Handle interrupts and pipe errors at the top level.
172
173```python
174import sys
175
176
177def main() -> int:
178 """Main entry point with error handling."""
179 try:
180 return run()
181 except KeyboardInterrupt:
182 print("\nInterrupted by user", file=sys.stderr)
183 return 130
184 except BrokenPipeError:
185 sys.stderr.close()
186 return 1
187 except Exception as e:
188 print(f"Error: {e}", file=sys.stderr)
189 return 1
190```
191
192Custom exceptions can carry exit codes:
193
194```python
195class ScriptError(Exception):
196 def __init__(self, message: str, exit_code: int = 1) -> None:
197 super().__init__(message)
198 self.exit_code = exit_code
199```
200
201## Documentation
202
203Use Google-style docstrings with Args, Returns, Raises, and Example sections.
204
205```python
206def process_data(data: list[str], *, normalize: bool = False) -> dict[str, int]:
207 """Process input data and return statistics.
208
209 Args:
210 data: List of strings to process.
211 normalize: If True, normalize values before processing.
212
213 Returns:
214 Dictionary mapping processed items to their counts.
215
216 Raises:
217 ValueError: If data is empty.
218
219 Example:
220 >>> process_data(["a", "b", "a"])
221 {'a': 2, 'b': 1}
222 """
223```
224
225Include module docstrings with description, usage, and examples.
226
227## Script Organization
228
229Organize scripts in this order:
230
2311. Shebang: `#!/usr/bin/env python3`
2322. Copyright header: `# Copyright (c) Microsoft Corporation.`
2333. SPDX license identifier: `# SPDX-License-Identifier: MIT`
2344. PEP 723 inline script metadata (if applicable)
2355. Future imports: `from __future__ import annotations`
2366. Imports: standard library, third-party, local (separated by blank lines)
2377. Constants and exit codes
2388. Module-level logger
2399. Helper functions
24010. Parser creation function
24111. Logging configuration function
24212. Run logic function
24313. Main entry point
24414. Module guard: `if __name__ == "__main__": sys.exit(main())`
245
246## Inline Script Metadata
247
248PEP 723 inline metadata enables automatic dependency installation with *uv*.
249
250```python
251#!/usr/bin/env python3
252# Copyright (c) Microsoft Corporation.
253# SPDX-License-Identifier: MIT
254# /// script
255# requires-python = ">=3.11"
256# dependencies = [
257# "click>=8.0",
258# "rich>=13.0",
259# ]
260# ///
261```
262
263Place after copyright and SPDX headers, before module docstring. Run with `uv run script.py`.