Coverage for src / taipanstack / config / generators.py: 100%
51 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-23 14:54 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-23 14:54 +0000
1"""
2Configuration file generators.
4This module generates configuration files (pyproject.toml, pre-commit, etc.)
5with proper validation and templating.
6"""
8from pathlib import Path
10from taipanstack.config.models import StackConfig
13def _generate_ruff_config(target_version: str) -> str:
14 """Generate Ruff configuration.
16 Args:
17 target_version: The target Python version.
19 Returns:
20 Ruff configuration string.
22 """
23 return f"""[tool.ruff]
24line-length = 88
25target-version = "{target_version}"
27[tool.ruff.lint]
28select = [
29 "F", # Pyflakes
30 "E", # pycodestyle errors
31 "W", # pycodestyle warnings
32 "I", # isort
33 "N", # pep8-naming
34 "D", # pydocstyle
35 "Q", # flake8-quotes
36 "S", # flake8-bandit
37 "B", # flake8-bugbear
38 "A", # flake8-builtins
39 "C4", # flake8-comprehensions
40 "T20", # flake8-print
41 "SIM", # flake8-simplify
42 "PTH", # flake8-use-pathlib
43 "TID", # flake8-tidy-imports
44 "ARG", # flake8-unused-arguments
45 "PIE", # flake8-pie
46 "PLC", # Pylint Convention
47 "PLE", # Pylint Error
48 "PLR", # Pylint Refactor
49 "PLW", # Pylint Warning
50 "RUF", # Ruff-specific
51 "UP", # pyupgrade
52 "ERA", # eradicate
53 "TRY", # tryceratops
54]
55ignore = ["D203", "D212", "D213", "D416", "D417"]
57[tool.ruff.lint.mccabe]
58max-complexity = 10
60[tool.ruff.lint.per-file-ignores]
61"tests/**/*.py" = ["S101", "D"]
63[tool.ruff.format]
64quote-style = "double"
65indent-style = "space"
66"""
69def _generate_mypy_config(python_version: str) -> str:
70 """Generate Mypy configuration.
72 Args:
73 python_version: The target Python version.
75 Returns:
76 Mypy configuration string.
78 """
79 return f"""[tool.mypy]
80python_version = "{python_version}"
81warn_return_any = true
82warn_unused_configs = true
83disallow_untyped_defs = true
84disallow_any_unimported = false
85no_implicit_optional = true
86check_untyped_defs = true
87strict_optional = true
88strict_equality = true
89ignore_missing_imports = true
90show_error_codes = true
91enable_error_code = ["ignore-without-code", "redundant-cast", "truthy-bool"]
92"""
95def _generate_pytest_config() -> str:
96 """Generate Pytest configuration.
98 Returns:
99 Pytest configuration string.
101 """
102 return """[tool.pytest.ini_options]
103testpaths = ["tests"]
104addopts = "-v --cov=src --cov-report=html --cov-report=term-missing --cov-fail-under=80 --strict-markers"
105markers = [
106 "slow: marks tests as slow (deselect with '-m \"not slow\"')",
107 "security: marks tests as security-related",
108]
109"""
112def _generate_coverage_config() -> str:
113 """Generate Coverage configuration.
115 Returns:
116 Coverage configuration string.
118 """
119 return """[tool.coverage.run]
120branch = true
121source = ["src"]
122omit = ["*/tests/*", "*/__pycache__/*"]
124[tool.coverage.report]
125exclude_lines = [
126 "pragma: no cover",
127 "def __repr__",
128 "raise NotImplementedError",
129 "if TYPE_CHECKING:",
130 "if __name__ == .__main__.:",
131]
132"""
135def generate_pyproject_config(config: StackConfig) -> str:
136 """Generate Ruff, Mypy, and Pytest configuration for pyproject.toml.
138 Args:
139 config: The Stack configuration.
141 Returns:
142 Configuration string to append to pyproject.toml.
144 """
145 target_version = config.to_target_version()
146 python_version = config.python_version
148 return f"""
149# --- Stack v2.0 Quality Configuration ---
150{_generate_ruff_config(target_version)}
151{_generate_mypy_config(python_version)}
152{_generate_pytest_config()}
153{_generate_coverage_config()}"""
156def _generate_bandit_hook(severity: str) -> str:
157 """Generate Bandit pre-commit hook.
159 Args:
160 severity: Bandit severity level.
162 Returns:
163 Bandit hook YAML string.
165 """
166 sev_char = severity[0].upper()
167 return f"""
168 - repo: https://github.com/PyCQA/bandit
169 rev: '1.8.0'
170 hooks:
171 - id: bandit
172 args: ["-r", ".", "-l{sev_char}"]
173"""
176def _generate_safety_hook() -> str:
177 """Generate Safety pre-commit hook.
179 Returns:
180 Safety hook YAML string.
182 """
183 return """
184 - repo: https://github.com/pyupio/safety
185 rev: '3.2.11'
186 hooks:
187 - id: safety
188 args: ["check", "--json"]
189"""
192def _generate_semgrep_hook() -> str:
193 """Generate Semgrep pre-commit hook.
195 Returns:
196 Semgrep hook YAML string.
198 """
199 return """
200 - repo: https://github.com/semgrep/pre-commit
201 rev: 'v1.99.0'
202 hooks:
203 - id: semgrep
204 args: ['--config=auto']
205"""
208def _generate_detect_secrets_hook() -> str:
209 """Generate detect-secrets pre-commit hook.
211 Returns:
212 Detect-secrets hook YAML string.
214 """
215 return """
216 - repo: https://github.com/Yelp/detect-secrets
217 rev: 'v1.5.0'
218 hooks:
219 - id: detect-secrets
220 args: ['--baseline', '.secrets.baseline']
221"""
224def _generate_paranoid_hooks() -> str:
225 """Generate extra security hooks for paranoid mode.
227 Returns:
228 Paranoid hooks YAML string.
230 """
231 return """
232 - repo: https://github.com/trailofbits/pip-audit
233 rev: 'v2.7.3'
234 hooks:
235 - id: pip-audit
237 - repo: https://github.com/jendrikseipp/vulture
238 rev: 'v2.11'
239 hooks:
240 - id: vulture
242 - repo: https://github.com/guilatrova/tryceratops
243 rev: 'v2.3.3'
244 hooks:
245 - id: tryceratops
246"""
249def generate_pre_commit_config(config: StackConfig) -> str:
250 """Generate .pre-commit-config.yaml content.
252 Args:
253 config: The Stack configuration.
255 Returns:
256 Pre-commit configuration YAML string.
258 """
259 security_hooks: list[str] = []
261 if config.security.enable_bandit:
262 security_hooks.append(_generate_bandit_hook(config.security.bandit_severity))
264 if config.security.enable_safety:
265 security_hooks.append(_generate_safety_hook())
267 if config.security.enable_semgrep:
268 security_hooks.append(_generate_semgrep_hook())
270 if config.security.enable_detect_secrets:
271 security_hooks.append(_generate_detect_secrets_hook())
273 # Add extra hooks for paranoid mode
274 if config.security.level == "paranoid":
275 security_hooks.append(_generate_paranoid_hooks())
277 return f"""# Stack v2.0 Pre-commit Configuration
278# Security Level: {config.security.level}
279repos:
280 - repo: https://github.com/pre-commit/pre-commit-hooks
281 rev: v5.0.0
282 hooks:
283 - id: trailing-whitespace
284 - id: end-of-file-fixer
285 - id: check-yaml
286 - id: check-added-large-files
287 - id: check-merge-conflict
288 - id: check-case-conflict
289 - id: detect-private-key
291 - repo: https://github.com/astral-sh/ruff-pre-commit
292 rev: 'v0.8.4'
293 hooks:
294 - id: ruff
295 args: [--fix, --exit-non-zero-on-fix]
296 - id: ruff-format
298 - repo: https://github.com/pre-commit/mirrors-mypy
299 rev: 'v1.13.0'
300 hooks:
301 - id: mypy
302 additional_dependencies: [types-all, pydantic]
303{"".join(security_hooks)}"""
306def generate_dependabot_config() -> str:
307 """Generate .github/dependabot.yml content.
309 Returns:
310 Dependabot configuration YAML string.
312 """
313 return """# Stack v2.0 Dependabot Configuration
314version: 2
315updates:
316 - package-ecosystem: "pip"
317 directory: "/"
318 schedule:
319 interval: "daily"
320 open-pull-requests-limit: 10
321 groups:
322 dev-dependencies:
323 patterns:
324 - "ruff"
325 - "mypy"
326 - "bandit"
327 - "safety"
328 - "pytest*"
329 - "pre-commit"
330 - "semgrep"
331 - "py-spy"
332 security-tools:
333 patterns:
334 - "bandit"
335 - "safety"
336 - "semgrep"
337 - "pip-audit"
338 reviewers:
339 - "gabrielima7"
341 - package-ecosystem: "github-actions"
342 directory: "/"
343 schedule:
344 interval: "weekly"
345 groups:
346 actions:
347 patterns:
348 - "*"
349"""
352def generate_security_policy() -> str:
353 """Generate SECURITY.md content.
355 Returns:
356 Security policy markdown string.
358 """
359 return """# Security Policy
361## Supported Versions
363We prioritize security fixes for the latest version (Rolling Release).
365| Version | Supported |
366| ------- | ------------------ |
367| Latest | :white_check_mark: |
368| Older | :x: |
370## Security Features
372This project includes multiple layers of security:
374- **SAST**: Bandit for static security analysis
375- **SCA**: Safety/pip-audit for dependency vulnerabilities
376- **Secrets**: detect-secrets for preventing credential leaks
377- **Type Safety**: Mypy + Pydantic for runtime validation
378- **Runtime Guards**: Protection against path traversal and injection
380## Reporting a Vulnerability
3821. **DO NOT** create a public issue for security vulnerabilities
3832. Report via the [Security tab](../../security/advisories/new)
3843. Or email the maintainer directly
3854. Include:
386 - Description of the vulnerability
387 - Steps to reproduce
388 - Potential impact
389 - Suggested fix (if any)
391## Response Timeline
393- **Acknowledgment**: Within 48 hours
394- **Initial Assessment**: Within 1 week
395- **Fix Release**: Depends on severity (critical: ASAP, others: next release)
396"""
399def generate_editorconfig() -> str:
400 """Generate .editorconfig content.
402 Returns:
403 EditorConfig content string.
405 """
406 return """# Stack v2.0 EditorConfig
407root = true
409[*]
410end_of_line = lf
411insert_final_newline = true
412trim_trailing_whitespace = true
413charset = utf-8
414indent_style = space
415indent_size = 4
417[*.py]
418max_line_length = 88
420[*.{yml,yaml,toml,json}]
421indent_size = 2
423[Makefile]
424indent_style = tab
425"""
428def write_config_file(
429 path: Path,
430 content: str,
431 config: StackConfig,
432) -> bool:
433 """Write configuration file with backup support.
435 Args:
436 path: Path to write the file.
437 content: Content to write.
438 config: Stack configuration.
440 Returns:
441 True if file was written, False if in dry-run mode.
443 """
444 if config.dry_run:
445 return False
447 if path.exists() and not config.force:
448 backup_path = path.with_suffix(f"{path.suffix}.bak")
449 path.rename(backup_path)
451 path.write_text(content, encoding="utf-8")
452 return True