cloudshellgpt 1.0.0__tar.gz

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.
Files changed (69) hide show
  1. cloudshellgpt-1.0.0/.github/workflows/ci.yml +29 -0
  2. cloudshellgpt-1.0.0/.gitignore +67 -0
  3. cloudshellgpt-1.0.0/.kiro/hooks/lint-on-save.json +13 -0
  4. cloudshellgpt-1.0.0/.kiro/hooks/test-mirror-reminder.json +13 -0
  5. cloudshellgpt-1.0.0/.kiro/settings/mcp.json +13 -0
  6. cloudshellgpt-1.0.0/.kiro/skills/pytest-unit-creator.md +336 -0
  7. cloudshellgpt-1.0.0/.kiro/skills/python-module-scaffold.md +138 -0
  8. cloudshellgpt-1.0.0/.kiro/specs/00-overview.md +104 -0
  9. cloudshellgpt-1.0.0/.kiro/specs/01-architecture.md +413 -0
  10. cloudshellgpt-1.0.0/.kiro/specs/02-acceptance-criteria.md +346 -0
  11. cloudshellgpt-1.0.0/.kiro/specs/03-implementation-plan.md +431 -0
  12. cloudshellgpt-1.0.0/.kiro/specs/03-implementation-plan.meta.json +60 -0
  13. cloudshellgpt-1.0.0/.kiro/steering/aws-conventions.md +77 -0
  14. cloudshellgpt-1.0.0/.kiro/steering/code-style.md +79 -0
  15. cloudshellgpt-1.0.0/.kiro/steering/commit-conventions.md +109 -0
  16. cloudshellgpt-1.0.0/.kiro/steering/language.md +20 -0
  17. cloudshellgpt-1.0.0/.kiro/steering/mcp-development.md +90 -0
  18. cloudshellgpt-1.0.0/.kiro/steering/project-context.md +145 -0
  19. cloudshellgpt-1.0.0/.kiro/steering/safety-patterns.md +137 -0
  20. cloudshellgpt-1.0.0/.kiro/steering/testing-guide.md +186 -0
  21. cloudshellgpt-1.0.0/.pre-commit-config.yaml +32 -0
  22. cloudshellgpt-1.0.0/LICENSE +198 -0
  23. cloudshellgpt-1.0.0/PKG-INFO +426 -0
  24. cloudshellgpt-1.0.0/README.md +379 -0
  25. cloudshellgpt-1.0.0/VIDEO_PITCH.md +385 -0
  26. cloudshellgpt-1.0.0/docs/DEMO_SCRIPT.md +531 -0
  27. cloudshellgpt-1.0.0/docs/IAM_PERMISSIONS.md +552 -0
  28. cloudshellgpt-1.0.0/infrastructure/app.py +27 -0
  29. cloudshellgpt-1.0.0/infrastructure/cdk.json +28 -0
  30. cloudshellgpt-1.0.0/infrastructure/lib/cloudshellgpt_stack.py +309 -0
  31. cloudshellgpt-1.0.0/prueba.pdf +0 -0
  32. cloudshellgpt-1.0.0/pyproject.toml +121 -0
  33. cloudshellgpt-1.0.0/src/cloudshellgpt/__init__.py +11 -0
  34. cloudshellgpt-1.0.0/src/cloudshellgpt/audit.py +175 -0
  35. cloudshellgpt-1.0.0/src/cloudshellgpt/bedrock_translator.py +525 -0
  36. cloudshellgpt-1.0.0/src/cloudshellgpt/cli.py +612 -0
  37. cloudshellgpt-1.0.0/src/cloudshellgpt/config.py +296 -0
  38. cloudshellgpt-1.0.0/src/cloudshellgpt/cost.py +443 -0
  39. cloudshellgpt-1.0.0/src/cloudshellgpt/executor.py +382 -0
  40. cloudshellgpt-1.0.0/src/cloudshellgpt/formatter.py +328 -0
  41. cloudshellgpt-1.0.0/src/cloudshellgpt/i18n.py +203 -0
  42. cloudshellgpt-1.0.0/src/cloudshellgpt/intent.py +1080 -0
  43. cloudshellgpt-1.0.0/src/cloudshellgpt/learning.py +969 -0
  44. cloudshellgpt-1.0.0/src/cloudshellgpt/mcp_server.py +264 -0
  45. cloudshellgpt-1.0.0/src/cloudshellgpt/safety.py +952 -0
  46. cloudshellgpt-1.0.0/tests/__init__.py +1 -0
  47. cloudshellgpt-1.0.0/tests/conftest.py +265 -0
  48. cloudshellgpt-1.0.0/tests/eval/__init__.py +1 -0
  49. cloudshellgpt-1.0.0/tests/eval/test_eval.py +401 -0
  50. cloudshellgpt-1.0.0/tests/eval/translation_eval.yaml +1086 -0
  51. cloudshellgpt-1.0.0/tests/eval/validate_distribution.py +275 -0
  52. cloudshellgpt-1.0.0/tests/integration/__init__.py +1 -0
  53. cloudshellgpt-1.0.0/tests/integration/test_e2e.py +476 -0
  54. cloudshellgpt-1.0.0/tests/unit/__init__.py +1 -0
  55. cloudshellgpt-1.0.0/tests/unit/test_audit.py +298 -0
  56. cloudshellgpt-1.0.0/tests/unit/test_bedrock.py +1216 -0
  57. cloudshellgpt-1.0.0/tests/unit/test_bedrock_error.py +543 -0
  58. cloudshellgpt-1.0.0/tests/unit/test_config.py +300 -0
  59. cloudshellgpt-1.0.0/tests/unit/test_cost.py +664 -0
  60. cloudshellgpt-1.0.0/tests/unit/test_executor.py +1243 -0
  61. cloudshellgpt-1.0.0/tests/unit/test_formatter.py +1081 -0
  62. cloudshellgpt-1.0.0/tests/unit/test_intent.py +977 -0
  63. cloudshellgpt-1.0.0/tests/unit/test_learning.py +537 -0
  64. cloudshellgpt-1.0.0/tests/unit/test_mcp_serve.py +239 -0
  65. cloudshellgpt-1.0.0/tests/unit/test_mcp_server.py +430 -0
  66. cloudshellgpt-1.0.0/tests/unit/test_mcp_tool_contracts.py +1197 -0
  67. cloudshellgpt-1.0.0/tests/unit/test_safety.py +1882 -0
  68. cloudshellgpt-1.0.0/tests/unit/test_smoke.py +27 -0
  69. cloudshellgpt-1.0.0/uv.lock +2363 -0
@@ -0,0 +1,29 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, dev]
6
+ pull_request:
7
+ branches: [main, dev]
8
+
9
+ jobs:
10
+ lint-and-test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - uses: astral-sh/setup-uv@v3
16
+ with:
17
+ version: "latest"
18
+
19
+ - name: Install dependencies
20
+ run: uv sync --all-extras
21
+
22
+ - name: Lint (ruff check)
23
+ run: uv run ruff check .
24
+
25
+ - name: Format check (ruff format)
26
+ run: uv run ruff format --check .
27
+
28
+ - name: Unit tests
29
+ run: uv run pytest tests/unit/ --no-cov -q --ignore=tests/unit/test_executor.py -k "not TestLLMIndependence and not TestAssessIntegration and not test_llm_high_stays_high"
@@ -0,0 +1,67 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ *.egg
10
+ .eggs/
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ venv/
15
+ env/
16
+ .env
17
+
18
+ # IDE
19
+ .vscode/
20
+ .idea/
21
+ *.swp
22
+ *.swo
23
+ *~
24
+
25
+ # OS
26
+ .DS_Store
27
+ Thumbs.db
28
+ desktop.ini
29
+
30
+ # AWS & Secrets
31
+ .aws/
32
+ *.pem
33
+ *.key
34
+ credentials
35
+ .env*
36
+ !.env.example
37
+
38
+ # Testing
39
+ .coverage
40
+ htmlcov/
41
+ .pytest_cache/
42
+ .mypy_cache/
43
+
44
+ # CDK
45
+ cdk.out/
46
+ *.js
47
+ !infrastructure/cdk.json
48
+
49
+ # Node (if CDK uses node)
50
+ node_modules/
51
+
52
+ # Local config (user-specific)
53
+ ~/.csgpt/
54
+
55
+ # Logs
56
+ *.log
57
+
58
+ # Ruff cache
59
+ .ruff_cache/
60
+
61
+ # Temporary output files
62
+ output.txt
63
+ uv_output2.txt
64
+
65
+ # Distribution
66
+ *.tar.gz
67
+ *.whl
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "Lint on Save",
3
+ "version": "1.0.0",
4
+ "description": "Runs ruff check on saved Python files",
5
+ "when": {
6
+ "type": "fileEdited",
7
+ "patterns": ["*.py"]
8
+ },
9
+ "then": {
10
+ "type": "runCommand",
11
+ "command": "ruff check --fix {file}"
12
+ }
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "Test Mirror Reminder",
3
+ "version": "1.0.0",
4
+ "description": "Reminds to update tests when source files change",
5
+ "when": {
6
+ "type": "fileEdited",
7
+ "patterns": ["src/cloudshellgpt/*.py"]
8
+ },
9
+ "then": {
10
+ "type": "askAgent",
11
+ "prompt": "A source file was modified. Check if the corresponding test file in tests/unit/ needs to be updated to match the changes."
12
+ }
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "mcpServers": {
3
+ "cloudshellgpt": {
4
+ "command": "csgpt",
5
+ "args": ["mcp", "serve"],
6
+ "env": {
7
+ "AWS_REGION": "us-east-1"
8
+ },
9
+ "disabled": false,
10
+ "autoApprove": ["aws_translate", "aws_explain", "aws_cost_preview"]
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,336 @@
1
+ ---
2
+ inclusion: fileMatch
3
+ fileMatchPattern: "tests/**/*.py"
4
+ ---
5
+
6
+ # Skill: Pytest Unit Creator
7
+
8
+ Genera tests unitarios para módulos de CloudShellGPT siguiendo las convenciones del testing-guide.
9
+
10
+ ## Cuándo usar
11
+
12
+ Cuando necesites crear tests para un módulo en `src/cloudshellgpt/`. Cada módulo debe tener su test mirror en `tests/unit/`.
13
+
14
+ ## Mapeo de archivos
15
+
16
+ | Módulo | Test |
17
+ |--------|------|
18
+ | `src/cloudshellgpt/intent.py` | `tests/unit/test_intent.py` |
19
+ | `src/cloudshellgpt/bedrock_translator.py` | `tests/unit/test_bedrock.py` |
20
+ | `src/cloudshellgpt/safety.py` | `tests/unit/test_safety.py` |
21
+ | `src/cloudshellgpt/cost.py` | `tests/unit/test_cost.py` |
22
+ | `src/cloudshellgpt/executor.py` | `tests/unit/test_executor.py` |
23
+ | `src/cloudshellgpt/formatter.py` | `tests/unit/test_formatter.py` |
24
+ | `src/cloudshellgpt/audit.py` | `tests/unit/test_audit.py` |
25
+ | `src/cloudshellgpt/learning.py` | `tests/unit/test_learning.py` |
26
+ | `src/cloudshellgpt/config.py` | `tests/unit/test_config.py` |
27
+
28
+ ## Template base de archivo test
29
+
30
+ ```python
31
+ """Tests unitarios para <module_name>."""
32
+
33
+ from __future__ import annotations
34
+
35
+ from unittest.mock import MagicMock, patch
36
+
37
+ import pytest
38
+
39
+ from cloudshellgpt.<module> import <ClassUnderTest>
40
+
41
+
42
+ class TestClassName:
43
+ """Tests para ClassName."""
44
+
45
+ def test_action_condition_expected_result(self) -> None:
46
+ """Descripción clara de qué verifica este test."""
47
+ # Arrange
48
+ input_data = "..."
49
+
50
+ # Act
51
+ result = function_under_test(input_data)
52
+
53
+ # Assert
54
+ assert result.field == expected_value
55
+
56
+ def test_another_scenario(self) -> None:
57
+ """Otro escenario."""
58
+ ...
59
+ ```
60
+
61
+ ## Naming convention para tests
62
+
63
+ Formato: `test_<acción>_<condición>_<resultado_esperado>`
64
+
65
+ ```python
66
+ # Buenos nombres:
67
+ def test_parse_spanish_list_intent_returns_high_confidence(): ...
68
+ def test_destructive_command_upgrades_risk_to_critical(): ...
69
+ def test_executor_rejects_pipe_in_command(): ...
70
+ def test_cost_explorer_failure_returns_unknown_status(): ...
71
+
72
+
73
+ # Malos nombres:
74
+ def test_1(): ...
75
+ def test_intent(): ...
76
+ def test_it_works(): ...
77
+ ```
78
+
79
+ ## Fixtures — usar conftest.py
80
+
81
+ Fixtures compartidas van en `tests/conftest.py`:
82
+
83
+ ```python
84
+ """Fixtures compartidas para tests."""
85
+
86
+ from __future__ import annotations
87
+
88
+ from pathlib import Path
89
+
90
+ import pytest
91
+
92
+ from cloudshellgpt.config import Config
93
+ from cloudshellgpt.intent import Intent
94
+
95
+
96
+ @pytest.fixture
97
+ def tmp_config(tmp_path: Path) -> Config:
98
+ """Provides a temporary config for testing."""
99
+ from cloudshellgpt.config import ConfigManager
100
+
101
+ return ConfigManager(config_path=tmp_path / "config.yaml")
102
+
103
+
104
+ @pytest.fixture
105
+ def sample_intent() -> Intent:
106
+ """Provides a sample Intent for testing."""
107
+ return Intent(
108
+ action="list",
109
+ service="s3",
110
+ confidence=0.9,
111
+ raw_input="lista los buckets de S3",
112
+ detected_language="es",
113
+ )
114
+
115
+
116
+ @pytest.fixture
117
+ def sample_translation() -> Translation:
118
+ """Provides a sample Translation for testing."""
119
+ from cloudshellgpt.bedrock_translator import Translation
120
+
121
+ return Translation(
122
+ command="aws s3api list-buckets --output json",
123
+ explanation="Lista todos los buckets",
124
+ detailed_explanation="Usa la API ListBuckets de S3",
125
+ risk_level="low",
126
+ estimated_cost="$0.00",
127
+ requires_dry_run=False,
128
+ affected_resources=["s3:*"],
129
+ flags_used=["--output json"],
130
+ )
131
+ ```
132
+
133
+ ## Mocking AWS — patrones
134
+
135
+ ### Moto para servicios AWS completos
136
+
137
+ ```python
138
+ import pytest
139
+ from moto import mock_aws
140
+
141
+
142
+ @mock_aws
143
+ def test_cost_explorer_returns_estimate() -> None:
144
+ """Cost Explorer mock retorna estimación válida."""
145
+ # moto intercepta boto3 calls automáticamente
146
+ import boto3
147
+
148
+ client = boto3.client("ce", region_name="us-east-1")
149
+ # ... setup y assertions
150
+ ```
151
+
152
+ ### unittest.mock para subprocess (executor)
153
+
154
+ ```python
155
+ from unittest.mock import MagicMock, patch
156
+
157
+
158
+ def test_executor_runs_aws_command() -> None:
159
+ """Executor ejecuta comando y retorna resultado."""
160
+ with patch("subprocess.run") as mock_run:
161
+ mock_run.return_value = MagicMock(
162
+ stdout='{"Buckets": []}',
163
+ stderr="",
164
+ returncode=0,
165
+ )
166
+ executor = AWSExecutor()
167
+ result = executor.run("aws s3api list-buckets")
168
+
169
+ assert result.exit_code == 0
170
+ assert result.stdout == '{"Buckets": []}'
171
+ mock_run.assert_called_once()
172
+ ```
173
+
174
+ ### Mock de Bedrock (translator)
175
+
176
+ ```python
177
+ from unittest.mock import MagicMock, patch
178
+
179
+
180
+ def test_translator_calls_converse_api() -> None:
181
+ """Translator usa Converse API, no invoke_model."""
182
+ with patch("boto3.client") as mock_boto:
183
+ mock_client = MagicMock()
184
+ mock_boto.return_value = mock_client
185
+ mock_client.converse.return_value = {
186
+ "output": {
187
+ "message": {"content": [{"text": '{"command": "aws s3 ls", "explanation": "..."}'}]}
188
+ },
189
+ "usage": {"inputTokens": 100, "outputTokens": 50},
190
+ }
191
+
192
+ translator = BedrockTranslator()
193
+ result = translator.translate(sample_intent)
194
+
195
+ mock_client.converse.assert_called_once()
196
+ # Verificar que NO se usó invoke_model
197
+ mock_client.invoke_model.assert_not_called()
198
+ ```
199
+
200
+ ## Patrones por módulo
201
+
202
+ ### Tests de IntentParser (sin mocks — lógica pura)
203
+
204
+ ```python
205
+ class TestIntentParser:
206
+ """Tests para IntentParser — no requiere mocks AWS."""
207
+
208
+ def test_parse_spanish_s3_list(self) -> None:
209
+ parser = IntentParser()
210
+ intent = parser.parse("lista los buckets de S3")
211
+ assert intent.service == "s3"
212
+ assert intent.action == "list"
213
+ assert intent.detected_language == "es"
214
+ assert intent.confidence >= 0.85
215
+
216
+ def test_parse_english_ec2_create(self) -> None:
217
+ parser = IntentParser()
218
+ intent = parser.parse("create a t3.micro EC2 instance")
219
+ assert intent.service == "ec2"
220
+ assert intent.action == "create"
221
+ assert intent.detected_language == "en"
222
+
223
+ def test_ambiguous_input_low_confidence(self) -> None:
224
+ parser = IntentParser()
225
+ intent = parser.parse("muéstrame las cosas")
226
+ assert intent.confidence < 0.7
227
+ ```
228
+
229
+ ### Tests de SafetyLayer (critical path — > 90% coverage)
230
+
231
+ ```python
232
+ class TestSafetyLayer:
233
+ """Tests para SafetyLayer — critical path."""
234
+
235
+ def test_read_only_command_is_low(self) -> None:
236
+ safety = SafetyLayer()
237
+ check = safety.evaluate("aws s3api list-buckets")
238
+ assert check.risk_level == "low"
239
+ assert check.requires_confirmation is False
240
+
241
+ def test_delete_command_is_high(self) -> None:
242
+ safety = SafetyLayer()
243
+ check = safety.evaluate("aws s3api delete-bucket --bucket prod")
244
+ assert check.risk_level == "high"
245
+ assert check.requires_confirmation is True
246
+
247
+ def test_recursive_delete_is_critical(self) -> None:
248
+ safety = SafetyLayer()
249
+ check = safety.evaluate("aws s3 rm s3://prod --recursive")
250
+ assert check.risk_level == "critical"
251
+ assert check.requires_dry_run is True
252
+
253
+ def test_never_downgrades_below_llm_risk(self) -> None:
254
+ """Si LLM dice high, safety no puede poner medium."""
255
+ safety = SafetyLayer()
256
+ check = safety.evaluate("aws s3api list-buckets", llm_risk="high")
257
+ assert check.risk_level in ("high", "critical")
258
+ ```
259
+
260
+ ### Tests de Executor (critical path — > 90% coverage)
261
+
262
+ ```python
263
+ class TestAWSExecutor:
264
+ """Tests para AWSExecutor — shell injection prevention."""
265
+
266
+ @pytest.mark.parametrize("metachar", ["|", "&&", "||", ";", "`", "$(", ">", ">>", "<"])
267
+ def test_rejects_shell_metacharacter(self, metachar: str) -> None:
268
+ executor = AWSExecutor()
269
+ with pytest.raises(ExecutorError, match="metacharacter"):
270
+ executor.run(f"aws s3 ls {metachar} malicious")
271
+
272
+ def test_accepts_stdin_dash_argument(self) -> None:
273
+ """Argumento `-` es válido en posición de argumento."""
274
+ with patch("subprocess.run") as mock_run:
275
+ mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
276
+ executor = AWSExecutor()
277
+ result = executor.run("aws s3 cp - s3://bucket/file")
278
+ assert result.exit_code == 0
279
+
280
+ def test_rejects_non_aws_command(self) -> None:
281
+ executor = AWSExecutor()
282
+ with pytest.raises(ExecutorError):
283
+ executor.run("rm -rf /")
284
+
285
+ def test_timeout_kills_process(self) -> None:
286
+ with patch("subprocess.run", side_effect=subprocess.TimeoutExpired("aws", 30)):
287
+ executor = AWSExecutor(timeout=30)
288
+ with pytest.raises(ExecutorError, match="timeout"):
289
+ executor.run("aws s3 sync s3://bucket .")
290
+ ```
291
+
292
+ ## Reglas obligatorias
293
+
294
+ 1. **NUNCA** hit real AWS en unit tests — siempre moto o unittest.mock
295
+ 2. **Type annotations** en todos los tests (→ None para test methods)
296
+ 3. **Docstring** en cada test explicando qué verifica
297
+ 4. **Arrange → Act → Assert** como estructura interna
298
+ 5. **Un assert principal** por test (asserts auxiliares OK para setup validation)
299
+ 6. **`@pytest.mark.parametrize`** para variaciones del mismo escenario
300
+ 7. **`@pytest.mark.integration`** solo en `tests/integration/` (nunca en unit)
301
+ 8. **Fixtures** para setup compartido, nunca setup en el test body repetido
302
+ 9. **Nombres descriptivos** — el nombre del test debe explicar el escenario sin leer el body
303
+
304
+ ## Ejecución
305
+
306
+ ```bash
307
+ # Todos los unit tests
308
+ pytest tests/unit/ -v
309
+
310
+ # Un archivo específico
311
+ pytest tests/unit/test_safety.py -v
312
+
313
+ # Un test específico
314
+ pytest tests/unit/test_safety.py::TestSafetyLayer::test_recursive_delete_is_critical -v
315
+
316
+ # Con coverage
317
+ pytest --cov=cloudshellgpt --cov-report=html tests/unit/
318
+
319
+ # Solo tests que matchean nombre
320
+ pytest -k "test_parse_spanish" -v
321
+ ```
322
+
323
+ ## Coverage targets
324
+
325
+ | Módulo | Target |
326
+ |--------|--------|
327
+ | `safety.py` | > 90% |
328
+ | `executor.py` | > 90% |
329
+ | `intent.py` | > 80% |
330
+ | `bedrock_translator.py` | > 80% |
331
+ | `cost.py` | > 80% |
332
+ | `formatter.py` | > 70% |
333
+ | `learning.py` | > 70% |
334
+ | `audit.py` | > 80% |
335
+ | `config.py` | > 80% |
336
+ | **Global** | > 80% |
@@ -0,0 +1,138 @@
1
+ ---
2
+ inclusion: fileMatch
3
+ fileMatchPattern: "src/cloudshellgpt/**/*.py"
4
+ ---
5
+
6
+ # Skill: Python Module Scaffold
7
+
8
+ Genera un nuevo módulo Python para CloudShellGPT siguiendo la estructura y convenciones del proyecto.
9
+
10
+ ## Cuándo usar
11
+
12
+ Cuando necesites crear un nuevo archivo `.py` en `src/cloudshellgpt/` o agregar estructura base a un módulo existente.
13
+
14
+ ## Estructura obligatoria del módulo
15
+
16
+ Todo módulo DEBE seguir este orden exacto:
17
+
18
+ ```python
19
+ """<Descripción en una línea del propósito del módulo.>"""
20
+
21
+ from __future__ import annotations
22
+
23
+ # Standard library imports
24
+ import logging
25
+ from typing import Any
26
+
27
+ # Third-party imports
28
+ import boto3
29
+ from pydantic import BaseModel, Field
30
+
31
+ # Local imports
32
+ from cloudshellgpt.config import Config
33
+
34
+ # Constants (UPPER_SNAKE_CASE)
35
+ DEFAULT_TIMEOUT: int = 30
36
+ MODULE_NAME: str = "module_name"
37
+
38
+
39
+ # Pydantic models
40
+ class MyModel(BaseModel):
41
+ """Descripción del modelo.
42
+
43
+ Attributes:
44
+ field_name: Descripción del campo.
45
+ """
46
+
47
+ field_name: str = Field(..., description="Descripción del campo")
48
+ optional_field: str | None = Field(default=None, description="Campo opcional")
49
+
50
+
51
+ # Classes (PascalCase)
52
+ class MyClass:
53
+ """Descripción de la clase.
54
+
55
+ Args:
56
+ config: Configuración del módulo.
57
+ """
58
+
59
+ def __init__(self, config: Config | None = None) -> None:
60
+ """Initialize MyClass."""
61
+ self._config = config
62
+
63
+ def public_method(self, arg: str) -> str:
64
+ """Descripción del método público.
65
+
66
+ Args:
67
+ arg: Descripción del argumento.
68
+
69
+ Returns:
70
+ Descripción del retorno.
71
+
72
+ Raises:
73
+ MyModuleError: Si algo falla.
74
+ """
75
+ return self._helper(arg)
76
+
77
+ def _helper(self, arg: str) -> str:
78
+ """Helper privado."""
79
+ return arg
80
+
81
+
82
+ # Public functions (snake_case)
83
+ def public_function(param: str) -> str:
84
+ """Descripción de la función pública.
85
+
86
+ Args:
87
+ param: Descripción del parámetro.
88
+
89
+ Returns:
90
+ Descripción del retorno.
91
+ """
92
+ return _private_helper(param)
93
+
94
+
95
+ # Private helpers (_prefixed)
96
+ def _private_helper(param: str) -> str:
97
+ """Helper privado."""
98
+ return param
99
+ ```
100
+
101
+ ## Reglas obligatorias
102
+
103
+ 1. **`from __future__ import annotations`** en la primera línea después del docstring
104
+ 2. **Type annotations** en TODAS las funciones (parámetros + retorno)
105
+ 3. **Docstrings Google style** en todas las clases y métodos públicos (Args, Returns, Raises)
106
+ 4. **Pydantic BaseModel** para todo dato que cruza fronteras entre módulos
107
+ 5. **`Field(..., description="...")`** para campos no obvios
108
+ 6. **Excepciones custom** por módulo (ej: `BedrockError`, `ExecutorError`)
109
+ 7. **Nunca swallow exceptions** silently — log o re-raise
110
+ 8. **Line length:** máximo 100 caracteres
111
+ 9. **Naming:**
112
+ - Classes: PascalCase
113
+ - Functions/methods: snake_case
114
+ - Constants: UPPER_SNAKE_CASE
115
+ - Private: prefijo `_`
116
+ - Modules: snake_case
117
+ 10. **Tipos modernos:** `str | None`, `list[str]`, `dict[str, Any]` (no Optional, List, Dict)
118
+
119
+ ## Excepciones custom — patrón
120
+
121
+ ```python
122
+ class ModuleNameError(Exception):
123
+ """Error base del módulo."""
124
+
125
+ def __init__(self, message: str, details: str | None = None) -> None:
126
+ self.details = details
127
+ super().__init__(message)
128
+ ```
129
+
130
+ ## Boto3 — patrón
131
+
132
+ ```python
133
+ # Siempre especificar region_name explícitamente
134
+ client = boto3.client("service-name", region_name=self._region)
135
+
136
+ # Nunca usar boto3.resource() — usar boto3.client()
137
+ # Manejar botocore.exceptions.ClientError con mensajes claros
138
+ ```