red64-cli 0.1.0 → 0.2.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.
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +5 -0
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/components/init/CompleteStep.d.ts.map +1 -1
- package/dist/components/init/CompleteStep.js +2 -2
- package/dist/components/init/CompleteStep.js.map +1 -1
- package/dist/components/init/TestCheckStep.d.ts +16 -0
- package/dist/components/init/TestCheckStep.d.ts.map +1 -0
- package/dist/components/init/TestCheckStep.js +120 -0
- package/dist/components/init/TestCheckStep.js.map +1 -0
- package/dist/components/init/index.d.ts +1 -0
- package/dist/components/init/index.d.ts.map +1 -1
- package/dist/components/init/index.js +1 -0
- package/dist/components/init/index.js.map +1 -1
- package/dist/components/init/types.d.ts +9 -0
- package/dist/components/init/types.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.js +69 -6
- package/dist/components/screens/InitScreen.js.map +1 -1
- package/dist/components/screens/StartScreen.d.ts.map +1 -1
- package/dist/components/screens/StartScreen.js +89 -3
- package/dist/components/screens/StartScreen.js.map +1 -1
- package/dist/services/ConfigService.d.ts +1 -0
- package/dist/services/ConfigService.d.ts.map +1 -1
- package/dist/services/ConfigService.js.map +1 -1
- package/dist/services/ProjectDetector.d.ts +28 -0
- package/dist/services/ProjectDetector.d.ts.map +1 -0
- package/dist/services/ProjectDetector.js +236 -0
- package/dist/services/ProjectDetector.js.map +1 -0
- package/dist/services/TestRunner.d.ts +46 -0
- package/dist/services/TestRunner.d.ts.map +1 -0
- package/dist/services/TestRunner.js +85 -0
- package/dist/services/TestRunner.js.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
- package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
- package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
- package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
- package/framework/stacks/generic/feedback.md +80 -0
- package/framework/stacks/nextjs/accessibility.md +437 -0
- package/framework/stacks/nextjs/api.md +431 -0
- package/framework/stacks/nextjs/coding-style.md +282 -0
- package/framework/stacks/nextjs/commenting.md +226 -0
- package/framework/stacks/nextjs/components.md +411 -0
- package/framework/stacks/nextjs/conventions.md +333 -0
- package/framework/stacks/nextjs/css.md +310 -0
- package/framework/stacks/nextjs/error-handling.md +442 -0
- package/framework/stacks/nextjs/feedback.md +124 -0
- package/framework/stacks/nextjs/migrations.md +332 -0
- package/framework/stacks/nextjs/models.md +362 -0
- package/framework/stacks/nextjs/queries.md +410 -0
- package/framework/stacks/nextjs/responsive.md +338 -0
- package/framework/stacks/nextjs/tech-stack.md +177 -0
- package/framework/stacks/nextjs/test-writing.md +475 -0
- package/framework/stacks/nextjs/validation.md +467 -0
- package/framework/stacks/python/api.md +468 -0
- package/framework/stacks/python/authentication.md +342 -0
- package/framework/stacks/python/code-quality.md +283 -0
- package/framework/stacks/python/code-refactoring.md +315 -0
- package/framework/stacks/python/coding-style.md +462 -0
- package/framework/stacks/python/conventions.md +399 -0
- package/framework/stacks/python/error-handling.md +512 -0
- package/framework/stacks/python/feedback.md +92 -0
- package/framework/stacks/python/implement-ai-llm.md +468 -0
- package/framework/stacks/python/migrations.md +388 -0
- package/framework/stacks/python/models.md +399 -0
- package/framework/stacks/python/python.md +232 -0
- package/framework/stacks/python/queries.md +451 -0
- package/framework/stacks/python/structure.md +245 -58
- package/framework/stacks/python/tech.md +92 -35
- package/framework/stacks/python/testing.md +380 -0
- package/framework/stacks/python/validation.md +471 -0
- package/framework/stacks/rails/authentication.md +176 -0
- package/framework/stacks/rails/code-quality.md +287 -0
- package/framework/stacks/rails/code-refactoring.md +299 -0
- package/framework/stacks/rails/feedback.md +130 -0
- package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
- package/framework/stacks/rails/rails.md +301 -0
- package/framework/stacks/rails/rails8-best-practices.md +498 -0
- package/framework/stacks/rails/rails8-css.md +573 -0
- package/framework/stacks/rails/structure.md +140 -0
- package/framework/stacks/rails/tech.md +108 -0
- package/framework/stacks/react/code-quality.md +521 -0
- package/framework/stacks/react/components.md +625 -0
- package/framework/stacks/react/data-fetching.md +586 -0
- package/framework/stacks/react/feedback.md +110 -0
- package/framework/stacks/react/forms.md +694 -0
- package/framework/stacks/react/performance.md +640 -0
- package/framework/stacks/react/product.md +22 -9
- package/framework/stacks/react/state-management.md +472 -0
- package/framework/stacks/react/structure.md +351 -44
- package/framework/stacks/react/tech.md +219 -30
- package/framework/stacks/react/testing.md +690 -0
- package/package.json +1 -1
- package/framework/stacks/node/product.md +0 -27
- package/framework/stacks/node/structure.md +0 -82
- package/framework/stacks/node/tech.md +0 -63
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
# Python Coding Style
|
|
2
|
+
|
|
3
|
+
Coding style conventions beyond what ruff enforces automatically. Opinionated patterns for readable, maintainable Python.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Philosophy
|
|
8
|
+
|
|
9
|
+
- **Readability counts**: Code is read far more than written
|
|
10
|
+
- **Explicit over implicit**: No magic, no hidden behavior
|
|
11
|
+
- **Consistency over preference**: Follow the project convention, not personal style
|
|
12
|
+
- **Automate what you can**: Let ruff handle formatting; this doc covers judgment calls
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Naming Conventions
|
|
17
|
+
|
|
18
|
+
### Standard Python Naming
|
|
19
|
+
|
|
20
|
+
| Element | Convention | Example |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| Variables, functions | `snake_case` | `user_count`, `get_user` |
|
|
23
|
+
| Classes | `PascalCase` | `UserService`, `CreateUserRequest` |
|
|
24
|
+
| Constants | `UPPER_SNAKE_CASE` | `MAX_RETRIES`, `DEFAULT_PAGE_SIZE` |
|
|
25
|
+
| Modules, packages | `snake_case` | `user_service.py`, `auth_utils` |
|
|
26
|
+
| Private members | `_leading_underscore` | `_validate_email`, `_cache` |
|
|
27
|
+
| Type variables | `PascalCase` or `T` | `T`, `ModelT`, `ResponseT` |
|
|
28
|
+
| Enums | `PascalCase` class, `UPPER_SNAKE_CASE` values | `Role.ADMIN` |
|
|
29
|
+
|
|
30
|
+
### Naming Rules
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
# GOOD: Descriptive, reveals intent
|
|
34
|
+
user_count = len(active_users)
|
|
35
|
+
is_authenticated = token is not None
|
|
36
|
+
max_retry_attempts = 3
|
|
37
|
+
|
|
38
|
+
async def get_active_users(db: AsyncSession) -> list[User]:
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
class PaymentProcessingError(AppError):
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
# BAD: Abbreviated, unclear
|
|
45
|
+
uc = len(au)
|
|
46
|
+
auth = token is not None
|
|
47
|
+
n = 3
|
|
48
|
+
|
|
49
|
+
async def get_au(db):
|
|
50
|
+
...
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Boolean Naming
|
|
54
|
+
|
|
55
|
+
Prefix with `is_`, `has_`, `can_`, `should_`:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
is_active: bool
|
|
59
|
+
has_permission: bool
|
|
60
|
+
can_publish: bool
|
|
61
|
+
should_notify: bool
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Function Design
|
|
67
|
+
|
|
68
|
+
### Size Limits
|
|
69
|
+
|
|
70
|
+
- **Target**: Under 20 lines of logic
|
|
71
|
+
- **Maximum**: 40 lines (extract if longer)
|
|
72
|
+
- **Parameters**: Maximum 5; use a Pydantic model for more
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
# GOOD: Small, focused
|
|
76
|
+
async def create_user(self, data: CreateUserRequest) -> User:
|
|
77
|
+
await self._check_email_available(data.email)
|
|
78
|
+
user = User(
|
|
79
|
+
email=data.email,
|
|
80
|
+
name=data.name,
|
|
81
|
+
hashed_password=hash_password(data.password),
|
|
82
|
+
)
|
|
83
|
+
return await self.repo.save(user)
|
|
84
|
+
|
|
85
|
+
async def _check_email_available(self, email: str) -> None:
|
|
86
|
+
existing = await self.repo.get_by_email(email)
|
|
87
|
+
if existing:
|
|
88
|
+
raise ConflictError("Email already registered")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
# BAD: Too many responsibilities
|
|
93
|
+
async def create_user(self, email, name, password, role, bio, avatar_url, ...):
|
|
94
|
+
# 60+ lines doing validation, hashing, saving, emailing, logging...
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Return Types
|
|
98
|
+
|
|
99
|
+
Always annotate return types for public functions:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
# GOOD
|
|
103
|
+
async def get_user(self, user_id: int) -> User:
|
|
104
|
+
...
|
|
105
|
+
|
|
106
|
+
async def find_user(self, email: str) -> User | None:
|
|
107
|
+
...
|
|
108
|
+
|
|
109
|
+
async def list_users(self) -> list[User]:
|
|
110
|
+
...
|
|
111
|
+
|
|
112
|
+
# BAD: Missing return type
|
|
113
|
+
async def get_user(self, user_id):
|
|
114
|
+
...
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Early Returns
|
|
118
|
+
|
|
119
|
+
Prefer guard clauses over nested conditionals:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
# GOOD: Guard clauses
|
|
123
|
+
async def publish(self, post: Post, user: User) -> Post:
|
|
124
|
+
if post.user_id != user.id:
|
|
125
|
+
raise AuthorizationError()
|
|
126
|
+
if post.status == "published":
|
|
127
|
+
raise ConflictError("Already published")
|
|
128
|
+
if not post.body:
|
|
129
|
+
raise ValidationError("Body required")
|
|
130
|
+
|
|
131
|
+
post.status = "published"
|
|
132
|
+
return await self.repo.save(post)
|
|
133
|
+
|
|
134
|
+
# BAD: Deeply nested
|
|
135
|
+
async def publish(self, post: Post, user: User) -> Post:
|
|
136
|
+
if post.user_id == user.id:
|
|
137
|
+
if post.status != "published":
|
|
138
|
+
if post.body:
|
|
139
|
+
post.status = "published"
|
|
140
|
+
return await self.repo.save(post)
|
|
141
|
+
else:
|
|
142
|
+
raise ValidationError("Body required")
|
|
143
|
+
else:
|
|
144
|
+
raise ConflictError("Already published")
|
|
145
|
+
else:
|
|
146
|
+
raise AuthorizationError()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Module Organization
|
|
152
|
+
|
|
153
|
+
### Standard Module Layout
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
"""Module docstring describing purpose."""
|
|
157
|
+
|
|
158
|
+
# 1. Future imports
|
|
159
|
+
from __future__ import annotations
|
|
160
|
+
|
|
161
|
+
# 2. Standard library
|
|
162
|
+
import asyncio
|
|
163
|
+
from datetime import datetime, timezone
|
|
164
|
+
from typing import TYPE_CHECKING
|
|
165
|
+
|
|
166
|
+
# 3. Third-party
|
|
167
|
+
from sqlalchemy import select
|
|
168
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
169
|
+
|
|
170
|
+
# 4. Local imports
|
|
171
|
+
from app.exceptions import NotFoundError
|
|
172
|
+
from app.models.user import User
|
|
173
|
+
|
|
174
|
+
# 5. Type-checking only imports
|
|
175
|
+
if TYPE_CHECKING:
|
|
176
|
+
from app.services.email import EmailService
|
|
177
|
+
|
|
178
|
+
# 6. Module-level constants
|
|
179
|
+
MAX_RETRIES = 3
|
|
180
|
+
DEFAULT_PAGE_SIZE = 20
|
|
181
|
+
|
|
182
|
+
# 7. Classes and functions
|
|
183
|
+
class UserService:
|
|
184
|
+
...
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Import Rules
|
|
188
|
+
|
|
189
|
+
- One import per line for `from` imports with multiple names (ruff handles this)
|
|
190
|
+
- Use `TYPE_CHECKING` block for imports used only in annotations
|
|
191
|
+
- Never use wildcard imports (`from module import *`)
|
|
192
|
+
- Group: stdlib, third-party, local (ruff `isort` enforces this)
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Type Annotations
|
|
197
|
+
|
|
198
|
+
### Modern Syntax (Python 3.12+)
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
# GOOD: Modern syntax
|
|
202
|
+
def get_users() -> list[User]:
|
|
203
|
+
...
|
|
204
|
+
|
|
205
|
+
def find_user(email: str) -> User | None:
|
|
206
|
+
...
|
|
207
|
+
|
|
208
|
+
def process_items(items: dict[str, list[int]]) -> None:
|
|
209
|
+
...
|
|
210
|
+
|
|
211
|
+
# Also acceptable: use `from __future__ import annotations` for older Python
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Common Patterns
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from collections.abc import AsyncIterator, Callable, Sequence
|
|
218
|
+
from typing import Any, TypeVar
|
|
219
|
+
|
|
220
|
+
T = TypeVar("T")
|
|
221
|
+
|
|
222
|
+
# Generic function
|
|
223
|
+
async def get_or_404(model: type[T], id: int, db: AsyncSession) -> T:
|
|
224
|
+
...
|
|
225
|
+
|
|
226
|
+
# Callable type
|
|
227
|
+
Handler = Callable[[Request], Awaitable[Response]]
|
|
228
|
+
|
|
229
|
+
# Optional with default
|
|
230
|
+
def paginate(page: int = 1, per_page: int = 20) -> tuple[int, int]:
|
|
231
|
+
...
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### When to Use `Any`
|
|
235
|
+
|
|
236
|
+
Almost never. Use `Any` only for:
|
|
237
|
+
- Interfacing with untyped third-party libraries
|
|
238
|
+
- Generic JSON data (prefer `dict[str, Any]` over bare `Any`)
|
|
239
|
+
- Temporary during migration to typed code
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Docstring Style (Google)
|
|
244
|
+
|
|
245
|
+
### Functions
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
async def create_user(self, data: CreateUserRequest) -> User:
|
|
249
|
+
"""Create a new user account.
|
|
250
|
+
|
|
251
|
+
Validates email uniqueness and hashes the password before
|
|
252
|
+
persisting to the database.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
data: Validated user creation request containing email,
|
|
256
|
+
name, and plain-text password.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
The created User instance with generated ID and timestamps.
|
|
260
|
+
|
|
261
|
+
Raises:
|
|
262
|
+
ConflictError: If the email is already registered.
|
|
263
|
+
"""
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Classes
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
class UserService:
|
|
270
|
+
"""Service for user account management.
|
|
271
|
+
|
|
272
|
+
Handles registration, authentication, and profile operations.
|
|
273
|
+
Delegates data access to UserRepo.
|
|
274
|
+
|
|
275
|
+
Attributes:
|
|
276
|
+
repo: Repository for user data persistence.
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
def __init__(self, repo: UserRepo) -> None:
|
|
280
|
+
self.repo = repo
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### When NOT to Docstring
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
# Self-documenting: no docstring needed
|
|
287
|
+
def hash_password(password: str) -> str:
|
|
288
|
+
return pwd_context.hash(password)
|
|
289
|
+
|
|
290
|
+
# Tests: name IS the documentation
|
|
291
|
+
async def test_create_user_with_duplicate_email_raises_conflict():
|
|
292
|
+
...
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Common Anti-Patterns
|
|
298
|
+
|
|
299
|
+
### Mutable Default Arguments
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
# BAD: Shared mutable default
|
|
303
|
+
def add_item(item: str, items: list[str] = []) -> list[str]:
|
|
304
|
+
items.append(item) # Mutates the default!
|
|
305
|
+
return items
|
|
306
|
+
|
|
307
|
+
# GOOD: Use None sentinel
|
|
308
|
+
def add_item(item: str, items: list[str] | None = None) -> list[str]:
|
|
309
|
+
if items is None:
|
|
310
|
+
items = []
|
|
311
|
+
items.append(item)
|
|
312
|
+
return items
|
|
313
|
+
|
|
314
|
+
# GOOD: Pydantic uses Field(default_factory=...)
|
|
315
|
+
class Config(BaseModel):
|
|
316
|
+
tags: list[str] = Field(default_factory=list)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Bare Except
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
# BAD: Catches everything including SystemExit, KeyboardInterrupt
|
|
323
|
+
try:
|
|
324
|
+
result = await risky_operation()
|
|
325
|
+
except:
|
|
326
|
+
pass
|
|
327
|
+
|
|
328
|
+
# BAD: Too broad
|
|
329
|
+
try:
|
|
330
|
+
result = await risky_operation()
|
|
331
|
+
except Exception:
|
|
332
|
+
pass # Silently swallowed
|
|
333
|
+
|
|
334
|
+
# GOOD: Specific exceptions, proper handling
|
|
335
|
+
try:
|
|
336
|
+
result = await risky_operation()
|
|
337
|
+
except (ConnectionError, TimeoutError) as exc:
|
|
338
|
+
logger.warning("operation_failed", error=str(exc))
|
|
339
|
+
raise ExternalServiceError("service", str(exc)) from exc
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Global Mutable State
|
|
343
|
+
|
|
344
|
+
```python
|
|
345
|
+
# BAD: Module-level mutable state
|
|
346
|
+
_cache = {}
|
|
347
|
+
|
|
348
|
+
def get_cached(key: str) -> str | None:
|
|
349
|
+
return _cache.get(key)
|
|
350
|
+
|
|
351
|
+
def set_cached(key: str, value: str) -> None:
|
|
352
|
+
_cache[key] = value
|
|
353
|
+
|
|
354
|
+
# GOOD: Encapsulate state in a class or use proper caching
|
|
355
|
+
class Cache:
|
|
356
|
+
def __init__(self) -> None:
|
|
357
|
+
self._store: dict[str, str] = {}
|
|
358
|
+
|
|
359
|
+
def get(self, key: str) -> str | None:
|
|
360
|
+
return self._store.get(key)
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### String Concatenation in Loops
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
# BAD: O(n^2) string building
|
|
367
|
+
result = ""
|
|
368
|
+
for item in items:
|
|
369
|
+
result += f"{item.name}, "
|
|
370
|
+
|
|
371
|
+
# GOOD: Join
|
|
372
|
+
result = ", ".join(item.name for item in items)
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Overusing Inheritance
|
|
376
|
+
|
|
377
|
+
```python
|
|
378
|
+
# BAD: Deep inheritance for code reuse
|
|
379
|
+
class BaseRepo:
|
|
380
|
+
...
|
|
381
|
+
class CachedRepo(BaseRepo):
|
|
382
|
+
...
|
|
383
|
+
class AuditedCachedRepo(CachedRepo):
|
|
384
|
+
...
|
|
385
|
+
class UserRepo(AuditedCachedRepo):
|
|
386
|
+
...
|
|
387
|
+
|
|
388
|
+
# GOOD: Composition and mixins
|
|
389
|
+
class UserRepo:
|
|
390
|
+
def __init__(self, db: AsyncSession, cache: Cache) -> None:
|
|
391
|
+
self.db = db
|
|
392
|
+
self.cache = cache
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Async Patterns
|
|
398
|
+
|
|
399
|
+
### Prefer `async` Throughout
|
|
400
|
+
|
|
401
|
+
```python
|
|
402
|
+
# GOOD: Async all the way down
|
|
403
|
+
async def get_user_with_posts(user_id: int, db: AsyncSession) -> User:
|
|
404
|
+
stmt = select(User).options(selectinload(User.posts)).where(User.id == user_id)
|
|
405
|
+
result = await db.execute(stmt)
|
|
406
|
+
return result.scalar_one_or_none()
|
|
407
|
+
|
|
408
|
+
# BAD: Mixing sync and async
|
|
409
|
+
def get_user_sync(user_id: int) -> User:
|
|
410
|
+
# Blocks the event loop if called from async context
|
|
411
|
+
with Session() as db:
|
|
412
|
+
return db.get(User, user_id)
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Concurrent Operations
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
import asyncio
|
|
419
|
+
|
|
420
|
+
# Run independent async operations concurrently
|
|
421
|
+
user, posts, notifications = await asyncio.gather(
|
|
422
|
+
user_service.get_user(user_id),
|
|
423
|
+
post_service.get_user_posts(user_id),
|
|
424
|
+
notification_service.get_unread(user_id),
|
|
425
|
+
)
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## File and Class Size
|
|
431
|
+
|
|
432
|
+
### Guidelines
|
|
433
|
+
|
|
434
|
+
| Element | Guideline |
|
|
435
|
+
|---|---|
|
|
436
|
+
| Function | Under 20 lines of logic, max 40 |
|
|
437
|
+
| Class | Under 200 lines, max 300 |
|
|
438
|
+
| Module | Under 300 lines, max 500 |
|
|
439
|
+
| Parameters | Max 5 per function; use model for more |
|
|
440
|
+
|
|
441
|
+
When a file exceeds limits, extract:
|
|
442
|
+
- Utility functions into a `utils` module
|
|
443
|
+
- Related classes into their own module
|
|
444
|
+
- Constants into a `constants` module
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Formatting (Handled by Ruff)
|
|
449
|
+
|
|
450
|
+
These are automated -- do not worry about them manually:
|
|
451
|
+
|
|
452
|
+
- Line length: 99 characters
|
|
453
|
+
- Indentation: 4 spaces
|
|
454
|
+
- Import sorting: stdlib, third-party, local
|
|
455
|
+
- Trailing commas in multi-line constructs
|
|
456
|
+
- Quote style: double quotes
|
|
457
|
+
|
|
458
|
+
Run `uv run ruff format .` and move on.
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
_Style is a tool for communication. Write code that your future self and teammates will thank you for._
|