autoworkflow 3.1.4 → 3.5.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.
Files changed (123) hide show
  1. package/.claude/commands/analyze.md +19 -0
  2. package/.claude/commands/audit.md +174 -11
  3. package/.claude/commands/build.md +39 -0
  4. package/.claude/commands/commit.md +25 -0
  5. package/.claude/commands/fix.md +23 -0
  6. package/.claude/commands/plan.md +18 -0
  7. package/.claude/commands/suggest.md +23 -0
  8. package/.claude/commands/verify.md +18 -0
  9. package/.claude/hooks/post-bash-router.sh +20 -0
  10. package/.claude/hooks/post-commit.sh +140 -0
  11. package/.claude/hooks/pre-edit.sh +129 -0
  12. package/.claude/hooks/session-check.sh +79 -0
  13. package/.claude/settings.json +40 -6
  14. package/.claude/settings.local.json +3 -1
  15. package/.claude/skills/actix.md +337 -0
  16. package/.claude/skills/alembic.md +504 -0
  17. package/.claude/skills/angular.md +237 -0
  18. package/.claude/skills/api-design.md +187 -0
  19. package/.claude/skills/aspnet-core.md +377 -0
  20. package/.claude/skills/astro.md +245 -0
  21. package/.claude/skills/auth-clerk.md +327 -0
  22. package/.claude/skills/auth-firebase.md +367 -0
  23. package/.claude/skills/auth-nextauth.md +359 -0
  24. package/.claude/skills/auth-supabase.md +368 -0
  25. package/.claude/skills/axum.md +386 -0
  26. package/.claude/skills/blazor.md +456 -0
  27. package/.claude/skills/chi.md +348 -0
  28. package/.claude/skills/code-review.md +133 -0
  29. package/.claude/skills/csharp.md +296 -0
  30. package/.claude/skills/css-modules.md +325 -0
  31. package/.claude/skills/cypress.md +343 -0
  32. package/.claude/skills/debugging.md +133 -0
  33. package/.claude/skills/diesel.md +392 -0
  34. package/.claude/skills/django.md +301 -0
  35. package/.claude/skills/docker.md +319 -0
  36. package/.claude/skills/doctrine.md +473 -0
  37. package/.claude/skills/documentation.md +182 -0
  38. package/.claude/skills/dotnet.md +409 -0
  39. package/.claude/skills/drizzle.md +293 -0
  40. package/.claude/skills/echo.md +321 -0
  41. package/.claude/skills/eloquent.md +256 -0
  42. package/.claude/skills/emotion.md +426 -0
  43. package/.claude/skills/entity-framework.md +370 -0
  44. package/.claude/skills/express.md +316 -0
  45. package/.claude/skills/fastapi.md +329 -0
  46. package/.claude/skills/fastify.md +299 -0
  47. package/.claude/skills/fiber.md +315 -0
  48. package/.claude/skills/flask.md +322 -0
  49. package/.claude/skills/gin.md +342 -0
  50. package/.claude/skills/git.md +116 -0
  51. package/.claude/skills/github-actions.md +353 -0
  52. package/.claude/skills/go.md +377 -0
  53. package/.claude/skills/gorm.md +409 -0
  54. package/.claude/skills/graphql.md +478 -0
  55. package/.claude/skills/hibernate.md +379 -0
  56. package/.claude/skills/hono.md +306 -0
  57. package/.claude/skills/java.md +400 -0
  58. package/.claude/skills/jest.md +313 -0
  59. package/.claude/skills/jpa.md +282 -0
  60. package/.claude/skills/kotlin.md +347 -0
  61. package/.claude/skills/kubernetes.md +363 -0
  62. package/.claude/skills/laravel.md +414 -0
  63. package/.claude/skills/mcp-browser.md +320 -0
  64. package/.claude/skills/mcp-database.md +219 -0
  65. package/.claude/skills/mcp-fetch.md +241 -0
  66. package/.claude/skills/mcp-filesystem.md +204 -0
  67. package/.claude/skills/mcp-github.md +217 -0
  68. package/.claude/skills/mcp-memory.md +240 -0
  69. package/.claude/skills/mcp-search.md +218 -0
  70. package/.claude/skills/mcp-slack.md +262 -0
  71. package/.claude/skills/micronaut.md +388 -0
  72. package/.claude/skills/mongodb.md +319 -0
  73. package/.claude/skills/mongoose.md +355 -0
  74. package/.claude/skills/mysql.md +281 -0
  75. package/.claude/skills/nestjs.md +335 -0
  76. package/.claude/skills/nextjs-app-router.md +260 -0
  77. package/.claude/skills/nextjs-pages.md +172 -0
  78. package/.claude/skills/nuxt.md +202 -0
  79. package/.claude/skills/openapi.md +489 -0
  80. package/.claude/skills/performance.md +199 -0
  81. package/.claude/skills/php.md +398 -0
  82. package/.claude/skills/playwright.md +371 -0
  83. package/.claude/skills/postgresql.md +257 -0
  84. package/.claude/skills/prisma.md +293 -0
  85. package/.claude/skills/pydantic.md +304 -0
  86. package/.claude/skills/pytest.md +313 -0
  87. package/.claude/skills/python.md +272 -0
  88. package/.claude/skills/quarkus.md +377 -0
  89. package/.claude/skills/react.md +230 -0
  90. package/.claude/skills/redis.md +391 -0
  91. package/.claude/skills/refactoring.md +143 -0
  92. package/.claude/skills/remix.md +246 -0
  93. package/.claude/skills/rest-api.md +490 -0
  94. package/.claude/skills/rocket.md +366 -0
  95. package/.claude/skills/rust.md +341 -0
  96. package/.claude/skills/sass.md +380 -0
  97. package/.claude/skills/sea-orm.md +382 -0
  98. package/.claude/skills/security.md +167 -0
  99. package/.claude/skills/sequelize.md +395 -0
  100. package/.claude/skills/spring-boot.md +416 -0
  101. package/.claude/skills/sqlalchemy.md +269 -0
  102. package/.claude/skills/sqlx-rust.md +408 -0
  103. package/.claude/skills/state-jotai.md +346 -0
  104. package/.claude/skills/state-mobx.md +353 -0
  105. package/.claude/skills/state-pinia.md +431 -0
  106. package/.claude/skills/state-redux.md +337 -0
  107. package/.claude/skills/state-tanstack-query.md +434 -0
  108. package/.claude/skills/state-zustand.md +340 -0
  109. package/.claude/skills/styled-components.md +403 -0
  110. package/.claude/skills/svelte.md +238 -0
  111. package/.claude/skills/sveltekit.md +207 -0
  112. package/.claude/skills/symfony.md +437 -0
  113. package/.claude/skills/tailwind.md +279 -0
  114. package/.claude/skills/terraform.md +394 -0
  115. package/.claude/skills/testing-library.md +371 -0
  116. package/.claude/skills/trpc.md +426 -0
  117. package/.claude/skills/typeorm.md +368 -0
  118. package/.claude/skills/vitest.md +330 -0
  119. package/.claude/skills/vue.md +202 -0
  120. package/.claude/skills/warp.md +365 -0
  121. package/README.md +135 -52
  122. package/package.json +1 -1
  123. package/system/triggers.md +152 -11
@@ -0,0 +1,313 @@
1
+ # Pytest Skill
2
+
3
+ ## Project Structure
4
+ \`\`\`
5
+ tests/
6
+ ├── conftest.py # Shared fixtures
7
+ ├── unit/
8
+ │ ├── test_models.py
9
+ │ └── test_services.py
10
+ ├── integration/
11
+ │ └── test_api.py
12
+ └── e2e/
13
+ └── test_flows.py
14
+ \`\`\`
15
+
16
+ ## Fixtures
17
+ \`\`\`python
18
+ # conftest.py
19
+ import pytest
20
+ from sqlalchemy import create_engine
21
+ from sqlalchemy.orm import sessionmaker
22
+
23
+ @pytest.fixture(scope="session")
24
+ def engine():
25
+ """Create test database engine once per test session."""
26
+ engine = create_engine("sqlite:///:memory:")
27
+ Base.metadata.create_all(engine)
28
+ yield engine
29
+ engine.dispose()
30
+
31
+ @pytest.fixture(scope="function")
32
+ def db_session(engine):
33
+ """Create a new session for each test."""
34
+ Session = sessionmaker(bind=engine)
35
+ session = Session()
36
+ yield session
37
+ session.rollback()
38
+ session.close()
39
+
40
+ @pytest.fixture
41
+ def user(db_session):
42
+ """Create a test user."""
43
+ user = User(email="test@example.com", name="Test User")
44
+ db_session.add(user)
45
+ db_session.commit()
46
+ return user
47
+
48
+ @pytest.fixture
49
+ def client(app):
50
+ """Flask test client."""
51
+ return app.test_client()
52
+
53
+ @pytest.fixture
54
+ def auth_headers(client, user):
55
+ """Get auth headers for authenticated requests."""
56
+ response = client.post('/auth/login', json={
57
+ 'email': user.email,
58
+ 'password': 'password',
59
+ })
60
+ token = response.json['access_token']
61
+ return {'Authorization': f'Bearer {token}'}
62
+ \`\`\`
63
+
64
+ ## Parametrize
65
+ \`\`\`python
66
+ import pytest
67
+
68
+ # Multiple test cases
69
+ @pytest.mark.parametrize("email,expected", [
70
+ ("valid@example.com", True),
71
+ ("invalid-email", False),
72
+ ("", False),
73
+ ("a@b.c", True),
74
+ ])
75
+ def test_validate_email(email, expected):
76
+ assert validate_email(email) == expected
77
+
78
+ # Multiple parameters
79
+ @pytest.mark.parametrize("a,b,expected", [
80
+ (1, 2, 3),
81
+ (0, 0, 0),
82
+ (-1, 1, 0),
83
+ ])
84
+ def test_add(a, b, expected):
85
+ assert add(a, b) == expected
86
+
87
+ # Combining parametrize decorators (cartesian product)
88
+ @pytest.mark.parametrize("x", [1, 2])
89
+ @pytest.mark.parametrize("y", [10, 20])
90
+ def test_multiply(x, y):
91
+ assert multiply(x, y) == x * y
92
+
93
+ # With IDs for better test names
94
+ @pytest.mark.parametrize(
95
+ "status_code,expected_error",
96
+ [
97
+ pytest.param(400, "Bad Request", id="bad_request"),
98
+ pytest.param(401, "Unauthorized", id="unauthorized"),
99
+ pytest.param(404, "Not Found", id="not_found"),
100
+ ]
101
+ )
102
+ def test_error_messages(status_code, expected_error):
103
+ assert get_error_message(status_code) == expected_error
104
+ \`\`\`
105
+
106
+ ## Mocking
107
+ \`\`\`python
108
+ from unittest.mock import Mock, patch, MagicMock, AsyncMock
109
+
110
+ # Patch a module function
111
+ @patch('app.services.user.send_email')
112
+ def test_create_user_sends_email(mock_send_email, db_session):
113
+ user = create_user(db_session, "test@example.com", "Test")
114
+ mock_send_email.assert_called_once_with("test@example.com", "Welcome!")
115
+
116
+ # Patch a method
117
+ @patch.object(UserService, 'get_user')
118
+ def test_get_user_not_found(mock_get_user):
119
+ mock_get_user.return_value = None
120
+ result = UserService().get_user(999)
121
+ assert result is None
122
+
123
+ # Mock with return values
124
+ def test_external_api(mocker):
125
+ mock_response = Mock()
126
+ mock_response.json.return_value = {'data': 'value'}
127
+ mock_response.status_code = 200
128
+
129
+ mocker.patch('requests.get', return_value=mock_response)
130
+
131
+ result = fetch_data()
132
+ assert result == {'data': 'value'}
133
+
134
+ # Mock async functions
135
+ @pytest.mark.asyncio
136
+ async def test_async_function(mocker):
137
+ mock_fetch = AsyncMock(return_value={'id': 1})
138
+ mocker.patch('app.services.fetch_user', mock_fetch)
139
+
140
+ result = await get_user_data(1)
141
+ assert result['id'] == 1
142
+
143
+ # Using pytest-mock's mocker fixture
144
+ def test_with_mocker(mocker):
145
+ mocker.patch('time.sleep') # Speed up tests
146
+ mocker.patch('os.environ.get', return_value='test_value')
147
+ \`\`\`
148
+
149
+ ## Async Testing
150
+ \`\`\`python
151
+ import pytest
152
+ import asyncio
153
+
154
+ # Mark async tests
155
+ @pytest.mark.asyncio
156
+ async def test_async_function():
157
+ result = await async_fetch_data()
158
+ assert result is not None
159
+
160
+ # Async fixture
161
+ @pytest.fixture
162
+ async def async_client():
163
+ async with AsyncClient(app, base_url="http://test") as client:
164
+ yield client
165
+
166
+ @pytest.mark.asyncio
167
+ async def test_async_api(async_client):
168
+ response = await async_client.get("/api/users")
169
+ assert response.status_code == 200
170
+
171
+ # Test async generators
172
+ @pytest.mark.asyncio
173
+ async def test_async_generator():
174
+ results = []
175
+ async for item in async_stream():
176
+ results.append(item)
177
+ assert len(results) == 10
178
+ \`\`\`
179
+
180
+ ## Testing Exceptions
181
+ \`\`\`python
182
+ import pytest
183
+
184
+ def test_raises_exception():
185
+ with pytest.raises(ValueError) as exc_info:
186
+ validate_age(-1)
187
+ assert "Age must be positive" in str(exc_info.value)
188
+
189
+ def test_raises_specific_exception():
190
+ with pytest.raises(ValidationError, match="Invalid email"):
191
+ validate_email("not-an-email")
192
+
193
+ # Test exception attributes
194
+ def test_custom_exception():
195
+ with pytest.raises(AppError) as exc_info:
196
+ do_something_risky()
197
+
198
+ assert exc_info.value.code == "RISK_ERROR"
199
+ assert exc_info.value.status_code == 400
200
+ \`\`\`
201
+
202
+ ## Markers
203
+ \`\`\`python
204
+ # Custom markers in pytest.ini or pyproject.toml
205
+ # [tool.pytest.ini_options]
206
+ # markers = [
207
+ # "slow: marks tests as slow",
208
+ # "integration: marks integration tests",
209
+ # ]
210
+
211
+ @pytest.mark.slow
212
+ def test_slow_operation():
213
+ result = process_large_dataset()
214
+ assert result is not None
215
+
216
+ @pytest.mark.integration
217
+ def test_database_connection(db_session):
218
+ assert db_session.execute("SELECT 1").scalar() == 1
219
+
220
+ @pytest.mark.skip(reason="Not implemented yet")
221
+ def test_future_feature():
222
+ pass
223
+
224
+ @pytest.mark.skipif(sys.platform == "win32", reason="Unix only")
225
+ def test_unix_specific():
226
+ pass
227
+
228
+ @pytest.mark.xfail(reason="Known bug #123")
229
+ def test_known_bug():
230
+ assert broken_function() == "expected"
231
+
232
+ # Run specific markers
233
+ # pytest -m slow
234
+ # pytest -m "not slow"
235
+ # pytest -m "integration and not slow"
236
+ \`\`\`
237
+
238
+ ## Coverage Configuration
239
+ \`\`\`toml
240
+ # pyproject.toml
241
+ [tool.pytest.ini_options]
242
+ testpaths = ["tests"]
243
+ python_files = ["test_*.py"]
244
+ python_functions = ["test_*"]
245
+ asyncio_mode = "auto"
246
+ addopts = [
247
+ "-v",
248
+ "--tb=short",
249
+ "--cov=app",
250
+ "--cov-report=term-missing",
251
+ "--cov-report=html",
252
+ "--cov-fail-under=80",
253
+ ]
254
+
255
+ [tool.coverage.run]
256
+ source = ["app"]
257
+ omit = ["*/tests/*", "*/__init__.py"]
258
+ branch = true
259
+
260
+ [tool.coverage.report]
261
+ exclude_lines = [
262
+ "pragma: no cover",
263
+ "if TYPE_CHECKING:",
264
+ "raise NotImplementedError",
265
+ ]
266
+ \`\`\`
267
+
268
+ ## Test Utilities
269
+ \`\`\`python
270
+ # Freeze time
271
+ from freezegun import freeze_time
272
+
273
+ @freeze_time("2024-01-15 12:00:00")
274
+ def test_timestamp():
275
+ assert get_current_time() == datetime(2024, 1, 15, 12, 0, 0)
276
+
277
+ # Factory Boy for test data
278
+ from factory import Factory, Faker, SubFactory
279
+
280
+ class UserFactory(Factory):
281
+ class Meta:
282
+ model = User
283
+
284
+ email = Faker('email')
285
+ name = Faker('name')
286
+
287
+ class PostFactory(Factory):
288
+ class Meta:
289
+ model = Post
290
+
291
+ title = Faker('sentence')
292
+ author = SubFactory(UserFactory)
293
+
294
+ def test_with_factory():
295
+ user = UserFactory()
296
+ posts = PostFactory.create_batch(5, author=user)
297
+ assert len(posts) == 5
298
+ \`\`\`
299
+
300
+ ## ❌ DON'T
301
+ - Use global state between tests
302
+ - Skip cleanup in fixtures
303
+ - Write tests that depend on order
304
+ - Mock too much (test real behavior)
305
+
306
+ ## ✅ DO
307
+ - Use fixtures for setup/teardown
308
+ - Use parametrize for multiple inputs
309
+ - Use markers to categorize tests
310
+ - Mock external dependencies only
311
+ - Test edge cases and errors
312
+ - Run with coverage reports
313
+ - Use factory patterns for test data
@@ -0,0 +1,272 @@
1
+ # Python Skill
2
+
3
+ ## Project Structure
4
+ \`\`\`
5
+ my_project/
6
+ ├── pyproject.toml # Project config (Poetry/PDM)
7
+ ├── .python-version # Python version
8
+ ├── .env # Environment variables
9
+ ├── src/
10
+ │ └── my_project/
11
+ │ ├── __init__.py
12
+ │ ├── main.py
13
+ │ ├── config.py
14
+ │ ├── models/
15
+ │ ├── services/
16
+ │ └── utils/
17
+ └── tests/
18
+ ├── conftest.py
19
+ └── test_*.py
20
+ \`\`\`
21
+
22
+ ## Type Hints
23
+ \`\`\`python
24
+ from typing import Optional, List, Dict, TypeVar, Generic
25
+ from collections.abc import Callable, Awaitable
26
+
27
+ # Basic types
28
+ def get_user(user_id: str) -> User | None:
29
+ return db.query(User).filter(User.id == user_id).first()
30
+
31
+ # Collections
32
+ def get_users(ids: list[str]) -> list[User]:
33
+ return db.query(User).filter(User.id.in_(ids)).all()
34
+
35
+ # Dictionaries
36
+ def process_data(data: dict[str, int]) -> dict[str, str]:
37
+ return {k: str(v) for k, v in data.items()}
38
+
39
+ # Callable types
40
+ Handler = Callable[[Request], Awaitable[Response]]
41
+
42
+ # Generics
43
+ T = TypeVar('T')
44
+
45
+ class Repository(Generic[T]):
46
+ def find(self, id: str) -> T | None: ...
47
+ def save(self, entity: T) -> T: ...
48
+ \`\`\`
49
+
50
+ ## Dataclasses & Pydantic
51
+ \`\`\`python
52
+ from dataclasses import dataclass, field
53
+ from pydantic import BaseModel, EmailStr, Field
54
+
55
+ # Dataclasses for simple data containers
56
+ @dataclass
57
+ class User:
58
+ id: str
59
+ email: str
60
+ name: str | None = None
61
+ tags: list[str] = field(default_factory=list)
62
+
63
+ # Pydantic for validation
64
+ class UserCreate(BaseModel):
65
+ email: EmailStr
66
+ name: str = Field(min_length=1, max_length=100)
67
+ password: str = Field(min_length=8)
68
+
69
+ class Config:
70
+ str_strip_whitespace = True
71
+
72
+ # Usage
73
+ user = UserCreate(email="test@example.com", name="John", password="secret123")
74
+ user_dict = user.model_dump()
75
+ \`\`\`
76
+
77
+ ## Async/Await
78
+ \`\`\`python
79
+ import asyncio
80
+ from typing import AsyncIterator
81
+
82
+ # Async function
83
+ async def fetch_user(user_id: str) -> User:
84
+ async with httpx.AsyncClient() as client:
85
+ response = await client.get(f"/users/{user_id}")
86
+ return User(**response.json())
87
+
88
+ # Concurrent execution
89
+ async def fetch_all_users(ids: list[str]) -> list[User]:
90
+ tasks = [fetch_user(id) for id in ids]
91
+ return await asyncio.gather(*tasks)
92
+
93
+ # Async context manager
94
+ class DatabaseConnection:
95
+ async def __aenter__(self) -> "DatabaseConnection":
96
+ await self.connect()
97
+ return self
98
+
99
+ async def __aexit__(self, *args) -> None:
100
+ await self.disconnect()
101
+
102
+ # Async generator
103
+ async def stream_users() -> AsyncIterator[User]:
104
+ async for row in db.stream_query("SELECT * FROM users"):
105
+ yield User(**row)
106
+
107
+ # Usage
108
+ async for user in stream_users():
109
+ print(user.name)
110
+ \`\`\`
111
+
112
+ ## Error Handling
113
+ \`\`\`python
114
+ from typing import NoReturn
115
+
116
+ # Custom exceptions
117
+ class UserNotFoundError(Exception):
118
+ def __init__(self, user_id: str):
119
+ self.user_id = user_id
120
+ super().__init__(f"User {user_id} not found")
121
+
122
+ class ValidationError(Exception):
123
+ def __init__(self, errors: list[str]):
124
+ self.errors = errors
125
+ super().__init__(f"Validation failed: {errors}")
126
+
127
+ # Try/except patterns
128
+ def get_user_or_raise(user_id: str) -> User:
129
+ try:
130
+ user = db.get_user(user_id)
131
+ if not user:
132
+ raise UserNotFoundError(user_id)
133
+ return user
134
+ except DatabaseError as e:
135
+ logger.error(f"Database error: {e}")
136
+ raise
137
+ finally:
138
+ db.close()
139
+
140
+ # Result pattern (no exceptions)
141
+ from dataclasses import dataclass
142
+
143
+ @dataclass
144
+ class Result[T]:
145
+ value: T | None = None
146
+ error: str | None = None
147
+
148
+ @property
149
+ def is_ok(self) -> bool:
150
+ return self.error is None
151
+
152
+ def safe_get_user(user_id: str) -> Result[User]:
153
+ try:
154
+ user = db.get_user(user_id)
155
+ return Result(value=user)
156
+ except Exception as e:
157
+ return Result(error=str(e))
158
+ \`\`\`
159
+
160
+ ## Logging
161
+ \`\`\`python
162
+ import logging
163
+ from logging.handlers import RotatingFileHandler
164
+
165
+ # Configure logging
166
+ logging.basicConfig(
167
+ level=logging.INFO,
168
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
169
+ )
170
+
171
+ # Module logger
172
+ logger = logging.getLogger(__name__)
173
+
174
+ # Structured logging
175
+ logger.info("User created", extra={"user_id": user.id, "email": user.email})
176
+
177
+ # Production setup
178
+ def setup_logging():
179
+ handler = RotatingFileHandler(
180
+ "app.log",
181
+ maxBytes=10_000_000, # 10MB
182
+ backupCount=5,
183
+ )
184
+ handler.setFormatter(logging.Formatter(
185
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
186
+ ))
187
+ logging.getLogger().addHandler(handler)
188
+ \`\`\`
189
+
190
+ ## Context Managers
191
+ \`\`\`python
192
+ from contextlib import contextmanager, asynccontextmanager
193
+
194
+ # Sync context manager
195
+ @contextmanager
196
+ def transaction():
197
+ db.begin()
198
+ try:
199
+ yield db
200
+ db.commit()
201
+ except Exception:
202
+ db.rollback()
203
+ raise
204
+
205
+ # Async context manager
206
+ @asynccontextmanager
207
+ async def get_connection():
208
+ conn = await pool.acquire()
209
+ try:
210
+ yield conn
211
+ finally:
212
+ await pool.release(conn)
213
+
214
+ # Usage
215
+ async with get_connection() as conn:
216
+ await conn.execute("SELECT * FROM users")
217
+ \`\`\`
218
+
219
+ ## Environment & Config
220
+ \`\`\`python
221
+ from pydantic_settings import BaseSettings
222
+ from functools import lru_cache
223
+
224
+ class Settings(BaseSettings):
225
+ database_url: str
226
+ redis_url: str
227
+ jwt_secret: str
228
+ debug: bool = False
229
+
230
+ class Config:
231
+ env_file = ".env"
232
+
233
+ @lru_cache
234
+ def get_settings() -> Settings:
235
+ return Settings()
236
+
237
+ # Usage
238
+ settings = get_settings()
239
+ print(settings.database_url)
240
+ \`\`\`
241
+
242
+ ## Virtual Environments
243
+ \`\`\`bash
244
+ # Create venv
245
+ python -m venv .venv
246
+
247
+ # Activate
248
+ source .venv/bin/activate # Unix
249
+ .venv\\Scripts\\activate # Windows
250
+
251
+ # Modern: use Poetry or PDM
252
+ poetry init
253
+ poetry add fastapi uvicorn
254
+ poetry install
255
+ \`\`\`
256
+
257
+ ## ❌ DON'T
258
+ - Use mutable default arguments (use None or field())
259
+ - Catch bare Exception without re-raising
260
+ - Use global state without proper management
261
+ - Mix sync and async code incorrectly
262
+ - Ignore type hints
263
+
264
+ ## ✅ DO
265
+ - Follow PEP 8 (use ruff/black for formatting)
266
+ - Use type hints everywhere
267
+ - Prefer f-strings over .format()
268
+ - Use context managers for resources
269
+ - Use dataclasses/pydantic for data
270
+ - Use async for I/O-bound operations
271
+ - Use logging instead of print
272
+ - Use virtual environments