omgkit 2.1.0 → 2.1.1

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.
@@ -1,63 +1,527 @@
1
1
  ---
2
2
  name: python
3
- description: Python development. Use when writing Python code, using pip, or working with Python frameworks.
3
+ description: Python development with type hints, async patterns, testing, and modern best practices
4
+ category: languages
5
+ triggers:
6
+ - python
7
+ - py
8
+ - pip
9
+ - pydantic
10
+ - fastapi
11
+ - django
4
12
  ---
5
13
 
6
- # Python Skill
14
+ # Python
7
15
 
8
- ## Patterns
16
+ Modern **Python development** following industry best practices. This skill covers type hints, async programming, data validation, testing, and production-ready patterns used by top engineering teams.
17
+
18
+ ## Purpose
19
+
20
+ Write clean, maintainable Python code:
21
+
22
+ - Use type hints for better code quality
23
+ - Implement async patterns for I/O operations
24
+ - Validate data with Pydantic
25
+ - Structure projects properly
26
+ - Write comprehensive tests
27
+ - Follow PEP standards
28
+
29
+ ## Features
30
+
31
+ ### 1. Type Hints and Annotations
9
32
 
10
- ### Type Hints
11
33
  ```python
34
+ from typing import Optional, List, Dict, Union, Callable, TypeVar, Generic
35
+ from dataclasses import dataclass
36
+ from datetime import datetime
37
+
38
+ # Basic type hints
39
+ def greet(name: str) -> str:
40
+ return f"Hello, {name}"
41
+
42
+ def process_items(items: List[str], limit: int = 10) -> Dict[str, int]:
43
+ return {item: len(item) for item in items[:limit]}
44
+
45
+ # Optional and Union
46
+ def find_user(user_id: str) -> Optional[User]:
47
+ return db.users.get(user_id)
48
+
49
+ def parse_input(value: Union[str, int]) -> str:
50
+ return str(value)
51
+
52
+ # Callable types
53
+ Handler = Callable[[str, int], bool]
54
+
55
+ def register_handler(name: str, handler: Handler) -> None:
56
+ handlers[name] = handler
57
+
58
+ # Generic types
59
+ T = TypeVar('T')
60
+
61
+ class Repository(Generic[T]):
62
+ def __init__(self, model: type[T]) -> None:
63
+ self.model = model
64
+
65
+ def find_by_id(self, id: str) -> Optional[T]:
66
+ ...
67
+
68
+ def find_all(self) -> List[T]:
69
+ ...
70
+
71
+ def create(self, data: Dict) -> T:
72
+ ...
73
+
74
+ # Type aliases
75
+ UserId = str
76
+ UserMap = Dict[UserId, User]
77
+ EventHandler = Callable[[Event], None]
78
+ ```
79
+
80
+ ### 2. Dataclasses and Pydantic
81
+
82
+ ```python
83
+ from dataclasses import dataclass, field
12
84
  from typing import Optional, List
85
+ from datetime import datetime
86
+ from pydantic import BaseModel, EmailStr, Field, validator
87
+ from enum import Enum
88
+
89
+ # Dataclasses for simple data structures
90
+ @dataclass
91
+ class User:
92
+ id: str
93
+ email: str
94
+ name: str
95
+ created_at: datetime = field(default_factory=datetime.now)
96
+ roles: List[str] = field(default_factory=list)
97
+
98
+ @dataclass(frozen=True)
99
+ class Point:
100
+ x: float
101
+ y: float
13
102
 
14
- def create_user(email: str, password: str) -> User:
15
- """Create a new user."""
16
- ...
103
+ def distance_to(self, other: 'Point') -> float:
104
+ return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
17
105
 
18
- def get_users(limit: int = 10) -> List[User]:
19
- ...
106
+ # Pydantic for validation
107
+ class UserRole(str, Enum):
108
+ ADMIN = "admin"
109
+ USER = "user"
110
+ GUEST = "guest"
111
+
112
+ class UserCreate(BaseModel):
113
+ email: EmailStr
114
+ password: str = Field(..., min_length=8)
115
+ name: str = Field(..., min_length=2, max_length=100)
116
+ role: UserRole = UserRole.USER
117
+
118
+ @validator('password')
119
+ def password_strength(cls, v: str) -> str:
120
+ if not any(c.isupper() for c in v):
121
+ raise ValueError('Password must contain uppercase letter')
122
+ if not any(c.isdigit() for c in v):
123
+ raise ValueError('Password must contain digit')
124
+ return v
125
+
126
+ class Config:
127
+ str_strip_whitespace = True
128
+
129
+ class UserResponse(BaseModel):
130
+ id: str
131
+ email: str
132
+ name: str
133
+ role: UserRole
134
+ created_at: datetime
135
+
136
+ class Config:
137
+ from_attributes = True # For ORM compatibility
138
+
139
+ class PaginatedResponse(BaseModel, Generic[T]):
140
+ data: List[T]
141
+ total: int
142
+ page: int
143
+ limit: int
144
+ has_more: bool
20
145
  ```
21
146
 
22
- ### Async
147
+ ### 3. Async Programming
148
+
23
149
  ```python
24
150
  import asyncio
151
+ from typing import List, Dict, Any
25
152
  import aiohttp
153
+ import asyncpg
26
154
 
27
- async def fetch_data(url: str) -> dict:
155
+ # Basic async functions
156
+ async def fetch_data(url: str) -> Dict[str, Any]:
28
157
  async with aiohttp.ClientSession() as session:
29
158
  async with session.get(url) as response:
30
159
  return await response.json()
160
+
161
+ # Concurrent requests
162
+ async def fetch_all(urls: List[str]) -> List[Dict[str, Any]]:
163
+ async with aiohttp.ClientSession() as session:
164
+ tasks = [fetch_url(session, url) for url in urls]
165
+ return await asyncio.gather(*tasks)
166
+
167
+ async def fetch_url(session: aiohttp.ClientSession, url: str) -> Dict[str, Any]:
168
+ async with session.get(url) as response:
169
+ return await response.json()
170
+
171
+ # Database operations
172
+ class Database:
173
+ def __init__(self, dsn: str):
174
+ self.dsn = dsn
175
+ self.pool: Optional[asyncpg.Pool] = None
176
+
177
+ async def connect(self) -> None:
178
+ self.pool = await asyncpg.create_pool(
179
+ self.dsn,
180
+ min_size=5,
181
+ max_size=20,
182
+ )
183
+
184
+ async def disconnect(self) -> None:
185
+ if self.pool:
186
+ await self.pool.close()
187
+
188
+ async def fetch_user(self, user_id: str) -> Optional[Dict]:
189
+ async with self.pool.acquire() as conn:
190
+ row = await conn.fetchrow(
191
+ "SELECT * FROM users WHERE id = $1",
192
+ user_id
193
+ )
194
+ return dict(row) if row else None
195
+
196
+ async def create_user(self, email: str, name: str) -> Dict:
197
+ async with self.pool.acquire() as conn:
198
+ row = await conn.fetchrow(
199
+ """
200
+ INSERT INTO users (email, name)
201
+ VALUES ($1, $2)
202
+ RETURNING *
203
+ """,
204
+ email, name
205
+ )
206
+ return dict(row)
207
+
208
+ # Context managers
209
+ class AsyncResource:
210
+ async def __aenter__(self) -> 'AsyncResource':
211
+ await self.setup()
212
+ return self
213
+
214
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
215
+ await self.cleanup()
216
+
217
+ # Rate limiting
218
+ class RateLimiter:
219
+ def __init__(self, rate: int, per: float):
220
+ self.rate = rate
221
+ self.per = per
222
+ self.semaphore = asyncio.Semaphore(rate)
223
+
224
+ async def acquire(self) -> None:
225
+ await self.semaphore.acquire()
226
+ asyncio.create_task(self._release())
227
+
228
+ async def _release(self) -> None:
229
+ await asyncio.sleep(self.per)
230
+ self.semaphore.release()
31
231
  ```
32
232
 
33
- ### Dataclasses
233
+ ### 4. Error Handling
234
+
34
235
  ```python
236
+ from typing import TypeVar, Generic
35
237
  from dataclasses import dataclass
36
- from datetime import datetime
238
+
239
+ # Custom exceptions
240
+ class AppError(Exception):
241
+ def __init__(self, message: str, code: str, status_code: int = 500):
242
+ self.message = message
243
+ self.code = code
244
+ self.status_code = status_code
245
+ super().__init__(message)
246
+
247
+ class NotFoundError(AppError):
248
+ def __init__(self, resource: str, id: str):
249
+ super().__init__(
250
+ f"{resource} with id {id} not found",
251
+ "NOT_FOUND",
252
+ 404
253
+ )
254
+
255
+ class ValidationError(AppError):
256
+ def __init__(self, field: str, message: str):
257
+ super().__init__(
258
+ f"Validation error on {field}: {message}",
259
+ "VALIDATION_ERROR",
260
+ 400
261
+ )
262
+
263
+ # Result type pattern
264
+ T = TypeVar('T')
265
+ E = TypeVar('E', bound=Exception)
37
266
 
38
267
  @dataclass
39
- class User:
40
- id: str
41
- email: str
42
- created_at: datetime = field(default_factory=datetime.now)
268
+ class Ok(Generic[T]):
269
+ value: T
270
+
271
+ def is_ok(self) -> bool:
272
+ return True
273
+
274
+ def is_err(self) -> bool:
275
+ return False
276
+
277
+ @dataclass
278
+ class Err(Generic[E]):
279
+ error: E
280
+
281
+ def is_ok(self) -> bool:
282
+ return False
283
+
284
+ def is_err(self) -> bool:
285
+ return True
286
+
287
+ Result = Ok[T] | Err[E]
288
+
289
+ def divide(a: float, b: float) -> Result[float, ValueError]:
290
+ if b == 0:
291
+ return Err(ValueError("Division by zero"))
292
+ return Ok(a / b)
293
+
294
+ # Usage
295
+ result = divide(10, 2)
296
+ if result.is_ok():
297
+ print(f"Result: {result.value}")
298
+ else:
299
+ print(f"Error: {result.error}")
43
300
  ```
44
301
 
45
- ### Pydantic
302
+ ### 5. Testing with Pytest
303
+
46
304
  ```python
47
- from pydantic import BaseModel, EmailStr
305
+ import pytest
306
+ from unittest.mock import Mock, patch, AsyncMock
307
+ from datetime import datetime
48
308
 
49
- class UserCreate(BaseModel):
50
- email: EmailStr
51
- password: str
309
+ # Basic tests
310
+ def test_create_user():
311
+ user = create_user("test@example.com", "Test User")
312
+ assert user.email == "test@example.com"
313
+ assert user.name == "Test User"
314
+
315
+ # Parametrized tests
316
+ @pytest.mark.parametrize("email,expected", [
317
+ ("valid@example.com", True),
318
+ ("invalid-email", False),
319
+ ("", False),
320
+ ])
321
+ def test_validate_email(email: str, expected: bool):
322
+ assert validate_email(email) == expected
323
+
324
+ # Fixtures
325
+ @pytest.fixture
326
+ def user():
327
+ return User(
328
+ id="test-id",
329
+ email="test@example.com",
330
+ name="Test User"
331
+ )
332
+
333
+ @pytest.fixture
334
+ def db():
335
+ db = Database(":memory:")
336
+ db.connect()
337
+ yield db
338
+ db.disconnect()
339
+
340
+ def test_user_creation(user: User):
341
+ assert user.id == "test-id"
342
+
343
+ # Async tests
344
+ @pytest.mark.asyncio
345
+ async def test_fetch_user():
346
+ db = AsyncMock()
347
+ db.fetch_user.return_value = {"id": "1", "name": "Test"}
348
+
349
+ result = await db.fetch_user("1")
350
+ assert result["name"] == "Test"
351
+
352
+ # Mocking
353
+ def test_external_api_call():
354
+ with patch('module.requests.get') as mock_get:
355
+ mock_get.return_value.json.return_value = {"data": "value"}
356
+
357
+ result = fetch_external_data()
358
+
359
+ assert result == {"data": "value"}
360
+ mock_get.assert_called_once()
361
+
362
+ # Exception testing
363
+ def test_not_found_raises():
364
+ with pytest.raises(NotFoundError) as exc_info:
365
+ find_user("nonexistent")
366
+
367
+ assert "not found" in str(exc_info.value)
368
+
369
+ # Test class organization
370
+ class TestUserService:
371
+ @pytest.fixture(autouse=True)
372
+ def setup(self):
373
+ self.service = UserService()
374
+ self.mock_repo = Mock()
375
+ self.service.repo = self.mock_repo
376
+
377
+ def test_create_user_success(self):
378
+ self.mock_repo.create.return_value = User(id="1", email="test@example.com")
379
+
380
+ user = self.service.create_user("test@example.com", "password")
381
+
382
+ assert user.email == "test@example.com"
383
+
384
+ def test_create_user_duplicate_email(self):
385
+ self.mock_repo.find_by_email.return_value = User(id="1", email="test@example.com")
386
+
387
+ with pytest.raises(ValidationError):
388
+ self.service.create_user("test@example.com", "password")
389
+ ```
390
+
391
+ ### 6. Project Structure
392
+
393
+ ```
394
+ project/
395
+ ├── src/
396
+ │ └── myapp/
397
+ │ ├── __init__.py
398
+ │ ├── main.py
399
+ │ ├── config.py
400
+ │ ├── models/
401
+ │ │ ├── __init__.py
402
+ │ │ └── user.py
403
+ │ ├── services/
404
+ │ │ ├── __init__.py
405
+ │ │ └── user_service.py
406
+ │ ├── repositories/
407
+ │ │ ├── __init__.py
408
+ │ │ └── user_repository.py
409
+ │ └── api/
410
+ │ ├── __init__.py
411
+ │ ├── routes.py
412
+ │ └── dependencies.py
413
+ ├── tests/
414
+ │ ├── __init__.py
415
+ │ ├── conftest.py
416
+ │ ├── unit/
417
+ │ │ └── test_user_service.py
418
+ │ └── integration/
419
+ │ └── test_api.py
420
+ ├── pyproject.toml
421
+ ├── requirements.txt
422
+ └── README.md
423
+ ```
424
+
425
+ ### 7. Configuration Management
426
+
427
+ ```python
428
+ from pydantic_settings import BaseSettings
429
+ from functools import lru_cache
430
+ from typing import Optional
431
+
432
+ class Settings(BaseSettings):
433
+ app_name: str = "MyApp"
434
+ debug: bool = False
435
+
436
+ database_url: str
437
+ redis_url: Optional[str] = None
438
+
439
+ secret_key: str
440
+ jwt_algorithm: str = "HS256"
441
+ jwt_expire_minutes: int = 30
442
+
443
+ cors_origins: list[str] = ["http://localhost:3000"]
52
444
 
53
445
  class Config:
54
- str_strip_whitespace = True
446
+ env_file = ".env"
447
+ env_file_encoding = "utf-8"
448
+
449
+ @lru_cache
450
+ def get_settings() -> Settings:
451
+ return Settings()
452
+
453
+ # Usage
454
+ settings = get_settings()
455
+ print(settings.database_url)
456
+ ```
457
+
458
+ ## Use Cases
459
+
460
+ ### FastAPI Application
461
+ ```python
462
+ from fastapi import FastAPI, Depends, HTTPException
463
+ from sqlalchemy.orm import Session
464
+
465
+ app = FastAPI()
466
+
467
+ @app.get("/users/{user_id}", response_model=UserResponse)
468
+ async def get_user(user_id: str, db: Session = Depends(get_db)):
469
+ user = await db.users.find_by_id(user_id)
470
+ if not user:
471
+ raise HTTPException(status_code=404, detail="User not found")
472
+ return user
473
+
474
+ @app.post("/users", response_model=UserResponse, status_code=201)
475
+ async def create_user(data: UserCreate, db: Session = Depends(get_db)):
476
+ existing = await db.users.find_by_email(data.email)
477
+ if existing:
478
+ raise HTTPException(status_code=400, detail="Email already exists")
479
+ return await db.users.create(data.dict())
480
+ ```
481
+
482
+ ### CLI Application
483
+ ```python
484
+ import click
485
+
486
+ @click.group()
487
+ def cli():
488
+ """My CLI application."""
489
+ pass
490
+
491
+ @cli.command()
492
+ @click.option('--name', prompt='Your name', help='User name')
493
+ @click.option('--count', default=1, help='Number of greetings')
494
+ def hello(name: str, count: int):
495
+ """Greet the user."""
496
+ for _ in range(count):
497
+ click.echo(f'Hello, {name}!')
498
+
499
+ if __name__ == '__main__':
500
+ cli()
55
501
  ```
56
502
 
57
503
  ## Best Practices
504
+
505
+ ### Do's
58
506
  - Use type hints everywhere
59
507
  - Use dataclasses or Pydantic for data
60
508
  - Use async for I/O operations
61
- - Follow PEP 8
509
+ - Follow PEP 8 and PEP 257
62
510
  - Use virtual environments
63
- - Write docstrings
511
+ - Write comprehensive docstrings
512
+ - Use pytest for testing
513
+
514
+ ### Don'ts
515
+ - Don't use mutable default arguments
516
+ - Don't catch bare exceptions
517
+ - Don't ignore type errors
518
+ - Don't use global state
519
+ - Don't skip error handling
520
+ - Don't write untestable code
521
+
522
+ ## References
523
+
524
+ - [Python Documentation](https://docs.python.org/3/)
525
+ - [PEP 8 Style Guide](https://peps.python.org/pep-0008/)
526
+ - [Pydantic Documentation](https://docs.pydantic.dev/)
527
+ - [Pytest Documentation](https://docs.pytest.org/)