metacoding 1.0.0 → 1.1.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 (46) hide show
  1. package/CHANGELOG.md +68 -43
  2. package/LICENSE +1 -1
  3. package/lib/services/template-manager.d.ts +3 -0
  4. package/lib/services/template-manager.d.ts.map +1 -1
  5. package/lib/services/template-manager.js +126 -9
  6. package/lib/services/template-manager.js.map +1 -1
  7. package/lib/services/vscode.js +1 -1
  8. package/lib/services/vscode.js.map +1 -1
  9. package/package.json +4 -4
  10. package/templates/general/code-review.instructions.md +265 -0
  11. package/templates/general/{files/copilot-instructions.md.template → copilot-instructions.md} +97 -140
  12. package/templates/{python/files → general}/docs-update.instructions.md +45 -32
  13. package/templates/general/release.instructions.md +242 -0
  14. package/templates/general/test-runner.instructions.md +188 -0
  15. package/templates/node/nodejs.coding.instructions.md +249 -0
  16. package/templates/node/nodejs.docs.instructions.md +234 -0
  17. package/templates/node/nodejs.testing.instructions.md +373 -0
  18. package/templates/python/python.coding.instructions.md +339 -0
  19. package/templates/python/python.docs.instructions.md +1147 -0
  20. package/templates/python/python.testing.instructions.md +1074 -0
  21. package/templates/react/react.coding.instructions.md +695 -0
  22. package/templates/react/react.docs.instructions.md +427 -0
  23. package/templates/react/react.testing.instructions.md +193 -0
  24. package/templates/react/test-runner.instructions.md +135 -0
  25. package/templates/typescript/template.json +16 -0
  26. package/templates/typescript/typescript.coding.instructions.md +368 -0
  27. package/templates/typescript/typescript.docs.instructions.md +734 -0
  28. package/templates/typescript/typescript.testing.instructions.md +740 -0
  29. package/templates/general/files/code-review.instructions.md +0 -111
  30. package/templates/general/files/docs-update.instructions.md +0 -203
  31. package/templates/general/files/release.instructions.md +0 -72
  32. package/templates/general/files/test-runner.instructions.md +0 -107
  33. package/templates/node/files/code-review.instructions.md +0 -222
  34. package/templates/node/files/copilot-instructions.md.template +0 -391
  35. package/templates/node/files/docs-update.instructions.md +0 -203
  36. package/templates/node/files/release.instructions.md +0 -72
  37. package/templates/node/files/test-runner.instructions.md +0 -108
  38. package/templates/python/files/code-review.instructions.md +0 -215
  39. package/templates/python/files/copilot-instructions.md.template +0 -418
  40. package/templates/python/files/release.instructions.md +0 -72
  41. package/templates/python/files/test-runner.instructions.md +0 -108
  42. package/templates/react/files/code-review.instructions.md +0 -160
  43. package/templates/react/files/copilot-instructions.md.template +0 -472
  44. package/templates/react/files/docs-update.instructions.md +0 -203
  45. package/templates/react/files/release.instructions.md +0 -72
  46. package/templates/react/files/test-runner.instructions.md +0 -108
@@ -0,0 +1,1147 @@
1
+ ---
2
+ description: 'Python-specific documentation standards and docstring patterns'
3
+ applyTo: '**/*.py'
4
+ language: 'python'
5
+ ---
6
+
7
+ # Python Documentation Standards
8
+
9
+ ## Docstring Conventions
10
+
11
+ ### Google Style Docstrings
12
+
13
+ Python projects should use Google-style docstrings for consistency and compatibility with documentation generation tools.
14
+
15
+ #### Function Documentation
16
+
17
+ ```python
18
+ def get_user_by_id(user_id: str, include_inactive: bool = False) -> Optional[User]:
19
+ """Retrieve a user by their unique identifier.
20
+
21
+ This function queries the database for a user with the specified ID.
22
+ By default, only active users are returned unless explicitly requested.
23
+
24
+ Args:
25
+ user_id: The unique identifier for the user (UUID format).
26
+ include_inactive: Whether to include inactive users in the search.
27
+ Defaults to False.
28
+
29
+ Returns:
30
+ User object if found, None otherwise. The user object contains
31
+ all standard user fields including id, name, email, and status.
32
+
33
+ Raises:
34
+ ValueError: If user_id is not a valid UUID format.
35
+ DatabaseConnectionError: If database connection fails.
36
+ PermissionError: If caller lacks permission to access user data.
37
+
38
+ Example:
39
+ Basic usage:
40
+
41
+ >>> user = get_user_by_id("123e4567-e89b-12d3-a456-426614174000")
42
+ >>> print(user.name)
43
+ "John Doe"
44
+
45
+ Including inactive users:
46
+
47
+ >>> inactive_user = get_user_by_id("456e7890-e89b-12d3-a456-426614174000",
48
+ ... include_inactive=True)
49
+ >>> print(inactive_user.status)
50
+ "inactive"
51
+
52
+ Note:
53
+ This function implements caching for frequently accessed users.
54
+ Cache invalidation occurs automatically when user data is modified.
55
+
56
+ See Also:
57
+ get_users_by_email: For searching users by email address.
58
+ create_user: For creating new user records.
59
+ """
60
+ # Implementation here
61
+ pass
62
+ ```
63
+
64
+ #### Class Documentation
65
+
66
+ ```python
67
+ class UserService:
68
+ """Service for managing user data with validation and caching.
69
+
70
+ The UserService provides a high-level interface for user operations,
71
+ abstracting database interactions and implementing business logic.
72
+ It includes automatic validation, caching for performance, and
73
+ comprehensive error handling.
74
+
75
+ Attributes:
76
+ repository: The user repository for database operations.
77
+ cache: Cache service for storing frequently accessed data.
78
+ validator: User data validation service.
79
+
80
+ Example:
81
+ Basic initialization and usage:
82
+
83
+ >>> from src.repositories import UserRepository
84
+ >>> from src.services import CacheService, ValidationService
85
+ >>>
86
+ >>> service = UserService(
87
+ ... repository=UserRepository(database),
88
+ ... cache=CacheService(),
89
+ ... validator=ValidationService()
90
+ ... )
91
+ >>>
92
+ >>> user = service.create_user({
93
+ ... 'name': 'John Doe',
94
+ ... 'email': 'john@example.com'
95
+ ... })
96
+ >>> print(user.id)
97
+ "123e4567-e89b-12d3-a456-426614174000"
98
+ """
99
+
100
+ def __init__(
101
+ self,
102
+ repository: UserRepository,
103
+ cache: Optional[CacheService] = None,
104
+ validator: Optional[ValidationService] = None
105
+ ) -> None:
106
+ """Initialize the UserService.
107
+
108
+ Args:
109
+ repository: Repository for user data persistence.
110
+ cache: Optional cache service for performance optimization.
111
+ If not provided, no caching will be used.
112
+ validator: Optional validation service for user data.
113
+ If not provided, default validation rules will be applied.
114
+
115
+ Raises:
116
+ TypeError: If repository is not an instance of UserRepository.
117
+ ValueError: If repository is not properly configured.
118
+ """
119
+ if not isinstance(repository, UserRepository):
120
+ raise TypeError("repository must be an instance of UserRepository")
121
+
122
+ self.repository = repository
123
+ self.cache = cache or NoOpCache()
124
+ self.validator = validator or DefaultValidator()
125
+
126
+ def create_user(self, user_data: Dict[str, Any]) -> User:
127
+ """Create a new user with validation and duplicate checking.
128
+
129
+ This method validates the provided user data, checks for existing
130
+ users with the same email, and creates a new user record in the
131
+ database. The created user is automatically cached for future access.
132
+
133
+ Args:
134
+ user_data: Dictionary containing user information. Must include
135
+ 'name' and 'email' fields. Optional fields include 'age',
136
+ 'preferences', and custom metadata.
137
+
138
+ Returns:
139
+ Newly created User object with generated ID and timestamps.
140
+
141
+ Raises:
142
+ ValidationError: If user data fails validation rules.
143
+ DuplicateEmailError: If a user with the email already exists.
144
+ DatabaseError: If database operation fails.
145
+
146
+ Example:
147
+ Creating a basic user:
148
+
149
+ >>> user_data = {
150
+ ... 'name': 'Jane Doe',
151
+ ... 'email': 'jane@example.com',
152
+ ... 'age': 30
153
+ ... }
154
+ >>> user = service.create_user(user_data)
155
+ >>> print(f"Created user: {user.name} ({user.id})")
156
+ "Created user: Jane Doe (456e7890-e89b-12d3-a456-426614174000)"
157
+
158
+ Creating a user with preferences:
159
+
160
+ >>> user_data = {
161
+ ... 'name': 'Bob Smith',
162
+ ... 'email': 'bob@example.com',
163
+ ... 'preferences': {
164
+ ... 'theme': 'dark',
165
+ ... 'notifications': True
166
+ ... }
167
+ ... }
168
+ >>> user = service.create_user(user_data)
169
+ """
170
+ # Implementation here
171
+ pass
172
+ ```
173
+
174
+ #### Exception Class Documentation
175
+
176
+ ```python
177
+ class ValidationError(Exception):
178
+ """Exception raised when user data validation fails.
179
+
180
+ This exception is raised when user input data does not meet the
181
+ required validation criteria. It includes detailed information
182
+ about which specific validation rules failed.
183
+
184
+ Attributes:
185
+ message: A human-readable error message.
186
+ field_errors: Dictionary mapping field names to specific error messages.
187
+ error_code: Machine-readable error code for programmatic handling.
188
+
189
+ Example:
190
+ Catching and handling validation errors:
191
+
192
+ >>> try:
193
+ ... user = service.create_user({'name': '', 'email': 'invalid'})
194
+ ... except ValidationError as e:
195
+ ... print(f"Validation failed: {e.message}")
196
+ ... for field, error in e.field_errors.items():
197
+ ... print(f" {field}: {error}")
198
+ "Validation failed: User data is invalid"
199
+ " name: Name cannot be empty"
200
+ " email: Invalid email format"
201
+ """
202
+
203
+ def __init__(
204
+ self,
205
+ message: str,
206
+ field_errors: Optional[Dict[str, str]] = None,
207
+ error_code: str = "VALIDATION_ERROR"
208
+ ) -> None:
209
+ """Initialize ValidationError.
210
+
211
+ Args:
212
+ message: High-level description of the validation failure.
213
+ field_errors: Optional dictionary mapping field names to
214
+ specific error messages. Defaults to empty dict.
215
+ error_code: Machine-readable error code. Defaults to
216
+ "VALIDATION_ERROR".
217
+ """
218
+ super().__init__(message)
219
+ self.message = message
220
+ self.field_errors = field_errors or {}
221
+ self.error_code = error_code
222
+
223
+ def get_field_error(self, field_name: str) -> Optional[str]:
224
+ """Get error message for a specific field.
225
+
226
+ Args:
227
+ field_name: Name of the field to get error for.
228
+
229
+ Returns:
230
+ Error message for the field, or None if no error exists.
231
+
232
+ Example:
233
+ >>> error = ValidationError("Invalid data", {"email": "Invalid format"})
234
+ >>> email_error = error.get_field_error("email")
235
+ >>> print(email_error)
236
+ "Invalid format"
237
+ """
238
+ return self.field_errors.get(field_name)
239
+ ```
240
+
241
+ ### Module and Package Documentation
242
+
243
+ ```python
244
+ """User management module for handling user data and operations.
245
+
246
+ This module provides comprehensive user management functionality including
247
+ user creation, retrieval, updating, and deletion. It implements proper
248
+ validation, caching, and error handling for all user operations.
249
+
250
+ The module follows the repository pattern for data access and provides
251
+ both synchronous and asynchronous interfaces for different use cases.
252
+
253
+ Typical usage example:
254
+
255
+ from src.user_module import UserService, UserRepository
256
+ from src.database import get_database_connection
257
+
258
+ # Initialize dependencies
259
+ db = get_database_connection()
260
+ repository = UserRepository(db)
261
+ service = UserService(repository)
262
+
263
+ # Create a new user
264
+ user_data = {
265
+ 'name': 'John Doe',
266
+ 'email': 'john@example.com'
267
+ }
268
+ user = service.create_user(user_data)
269
+
270
+ # Retrieve user by ID
271
+ found_user = service.get_user_by_id(user.id)
272
+
273
+ Classes:
274
+ UserService: High-level service for user operations.
275
+ UserRepository: Database access layer for user data.
276
+ User: Data model representing a user.
277
+ ValidationError: Exception for validation failures.
278
+
279
+ Functions:
280
+ validate_email: Utility function for email validation.
281
+ generate_user_id: Utility function for generating unique user IDs.
282
+
283
+ Constants:
284
+ MAX_NAME_LENGTH: Maximum allowed length for user names.
285
+ MIN_AGE: Minimum allowed age for users.
286
+ RESERVED_EMAILS: List of email addresses that cannot be used.
287
+ """
288
+
289
+ from typing import Optional, Dict, List, Any
290
+ from .models import User
291
+ from .services import UserService
292
+ from .repositories import UserRepository
293
+ from .exceptions import ValidationError, UserNotFoundError
294
+
295
+ # Module version
296
+ __version__ = "2.1.0"
297
+
298
+ # Public API
299
+ __all__ = [
300
+ "User",
301
+ "UserService",
302
+ "UserRepository",
303
+ "ValidationError",
304
+ "UserNotFoundError",
305
+ "validate_email",
306
+ "generate_user_id"
307
+ ]
308
+
309
+ # Module constants
310
+ MAX_NAME_LENGTH: int = 100
311
+ MIN_AGE: int = 13
312
+ RESERVED_EMAILS: List[str] = ["admin@example.com", "noreply@example.com"]
313
+ ```
314
+
315
+ ### Data Class and Model Documentation
316
+
317
+ ```python
318
+ from dataclasses import dataclass, field
319
+ from datetime import datetime
320
+ from typing import Optional, Dict, Any
321
+
322
+ @dataclass
323
+ class User:
324
+ """Data model representing a user in the system.
325
+
326
+ This class represents a user with all necessary fields for user
327
+ management operations. It includes automatic timestamp generation
328
+ and validation for core fields.
329
+
330
+ Attributes:
331
+ id: Unique identifier for the user (UUID format).
332
+ name: Full name of the user.
333
+ email: Email address (must be unique across all users).
334
+ age: User's age in years (must be >= 13).
335
+ is_active: Whether the user account is active.
336
+ created_at: Timestamp when the user was created.
337
+ updated_at: Timestamp when the user was last modified.
338
+ preferences: Optional user preferences dictionary.
339
+ metadata: Optional additional metadata dictionary.
340
+
341
+ Example:
342
+ Creating a user instance:
343
+
344
+ >>> from datetime import datetime
345
+ >>> user = User(
346
+ ... id="123e4567-e89b-12d3-a456-426614174000",
347
+ ... name="John Doe",
348
+ ... email="john@example.com",
349
+ ... age=30
350
+ ... )
351
+ >>> print(f"{user.name} ({user.email})")
352
+ "John Doe (john@example.com)"
353
+
354
+ User with preferences:
355
+
356
+ >>> user_with_prefs = User(
357
+ ... id="456e7890-e89b-12d3-a456-426614174000",
358
+ ... name="Jane Doe",
359
+ ... email="jane@example.com",
360
+ ... age=25,
361
+ ... preferences={"theme": "dark", "notifications": True}
362
+ ... )
363
+ """
364
+
365
+ id: str
366
+ name: str
367
+ email: str
368
+ age: int
369
+ is_active: bool = True
370
+ created_at: datetime = field(default_factory=datetime.utcnow)
371
+ updated_at: datetime = field(default_factory=datetime.utcnow)
372
+ preferences: Optional[Dict[str, Any]] = field(default_factory=dict)
373
+ metadata: Optional[Dict[str, Any]] = field(default_factory=dict)
374
+
375
+ def __post_init__(self) -> None:
376
+ """Validate user data after initialization.
377
+
378
+ Raises:
379
+ ValueError: If any field contains invalid data.
380
+ """
381
+ if not self.name.strip():
382
+ raise ValueError("Name cannot be empty")
383
+ if self.age < 13:
384
+ raise ValueError("Age must be at least 13")
385
+ if "@" not in self.email:
386
+ raise ValueError("Invalid email format")
387
+
388
+ def get_display_name(self) -> str:
389
+ """Get formatted display name for the user.
390
+
391
+ Returns:
392
+ Formatted display name, typically the full name.
393
+ Falls back to email username if name is not available.
394
+
395
+ Example:
396
+ >>> user = User(id="123", name="John Doe", email="john@example.com", age=30)
397
+ >>> print(user.get_display_name())
398
+ "John Doe"
399
+
400
+ >>> user_no_name = User(id="456", name="", email="jane@example.com", age=25)
401
+ >>> print(user_no_name.get_display_name())
402
+ "jane"
403
+ """
404
+ if self.name.strip():
405
+ return self.name
406
+ return self.email.split("@")[0]
407
+
408
+ def is_adult(self) -> bool:
409
+ """Check if user is an adult (18 or older).
410
+
411
+ Returns:
412
+ True if user is 18 or older, False otherwise.
413
+
414
+ Example:
415
+ >>> adult_user = User(id="123", name="Adult", email="adult@example.com", age=25)
416
+ >>> minor_user = User(id="456", name="Minor", email="minor@example.com", age=16)
417
+ >>> print(adult_user.is_adult())
418
+ True
419
+ >>> print(minor_user.is_adult())
420
+ False
421
+ """
422
+ return self.age >= 18
423
+
424
+ def update_preferences(self, preferences: Dict[str, Any]) -> None:
425
+ """Update user preferences with new values.
426
+
427
+ This method merges new preferences with existing ones,
428
+ preserving existing values unless explicitly overridden.
429
+
430
+ Args:
431
+ preferences: Dictionary of preference keys and values to update.
432
+
433
+ Example:
434
+ >>> user = User(id="123", name="John", email="john@example.com", age=30)
435
+ >>> user.update_preferences({"theme": "dark", "language": "en"})
436
+ >>> user.update_preferences({"theme": "light"}) # Only updates theme
437
+ >>> print(user.preferences)
438
+ {"theme": "light", "language": "en"}
439
+ """
440
+ if self.preferences is None:
441
+ self.preferences = {}
442
+ self.preferences.update(preferences)
443
+ self.updated_at = datetime.utcnow()
444
+ ```
445
+
446
+ ### Django Model Documentation
447
+
448
+ ```python
449
+ from django.db import models
450
+ from django.core.validators import MinValueValidator, EmailValidator
451
+ from django.contrib.auth.models import AbstractUser
452
+
453
+ class User(AbstractUser):
454
+ """Extended user model with additional fields and functionality.
455
+
456
+ This model extends Django's built-in User model to include additional
457
+ fields required for the application. It maintains compatibility with
458
+ Django's authentication system while adding business-specific data.
459
+
460
+ Attributes:
461
+ age: User's age in years (minimum 13).
462
+ phone_number: Optional phone number for the user.
463
+ date_of_birth: User's date of birth.
464
+ is_verified: Whether the user's email has been verified.
465
+ preferences: JSON field storing user preferences.
466
+ created_at: Timestamp when the user was created (auto-generated).
467
+ updated_at: Timestamp when the user was last modified (auto-updated).
468
+
469
+ Example:
470
+ Creating a user through Django ORM:
471
+
472
+ >>> user = User.objects.create_user(
473
+ ... username='johndoe',
474
+ ... email='john@example.com',
475
+ ... password='secure_password',
476
+ ... age=30,
477
+ ... first_name='John',
478
+ ... last_name='Doe'
479
+ ... )
480
+ >>> print(f"Created user: {user.get_full_name()}")
481
+ "Created user: John Doe"
482
+
483
+ Querying users:
484
+
485
+ >>> adult_users = User.objects.filter(age__gte=18)
486
+ >>> verified_users = User.objects.filter(is_verified=True)
487
+ """
488
+
489
+ age = models.PositiveIntegerField(
490
+ validators=[MinValueValidator(13)],
491
+ help_text="User's age in years (minimum 13)"
492
+ )
493
+
494
+ phone_number = models.CharField(
495
+ max_length=20,
496
+ blank=True,
497
+ null=True,
498
+ help_text="User's phone number in international format"
499
+ )
500
+
501
+ date_of_birth = models.DateField(
502
+ null=True,
503
+ blank=True,
504
+ help_text="User's date of birth"
505
+ )
506
+
507
+ is_verified = models.BooleanField(
508
+ default=False,
509
+ help_text="Whether the user's email address has been verified"
510
+ )
511
+
512
+ preferences = models.JSONField(
513
+ default=dict,
514
+ blank=True,
515
+ help_text="User preferences stored as JSON"
516
+ )
517
+
518
+ created_at = models.DateTimeField(
519
+ auto_now_add=True,
520
+ help_text="Timestamp when the user was created"
521
+ )
522
+
523
+ updated_at = models.DateTimeField(
524
+ auto_now=True,
525
+ help_text="Timestamp when the user was last modified"
526
+ )
527
+
528
+ class Meta:
529
+ """Meta configuration for the User model."""
530
+ db_table = 'users'
531
+ ordering = ['-created_at']
532
+ indexes = [
533
+ models.Index(fields=['email']),
534
+ models.Index(fields=['is_verified']),
535
+ models.Index(fields=['created_at']),
536
+ ]
537
+
538
+ def __str__(self) -> str:
539
+ """Return string representation of the user.
540
+
541
+ Returns:
542
+ User's full name if available, otherwise username.
543
+
544
+ Example:
545
+ >>> user = User(first_name="John", last_name="Doe", username="johndoe")
546
+ >>> print(str(user))
547
+ "John Doe"
548
+
549
+ >>> user_no_name = User(username="janedoe")
550
+ >>> print(str(user_no_name))
551
+ "janedoe"
552
+ """
553
+ full_name = self.get_full_name()
554
+ return full_name if full_name else self.username
555
+
556
+ def is_adult(self) -> bool:
557
+ """Check if user is an adult based on age.
558
+
559
+ Returns:
560
+ True if user is 18 or older, False otherwise.
561
+
562
+ Example:
563
+ >>> adult_user = User(age=25)
564
+ >>> minor_user = User(age=16)
565
+ >>> print(adult_user.is_adult())
566
+ True
567
+ >>> print(minor_user.is_adult())
568
+ False
569
+ """
570
+ return self.age >= 18
571
+
572
+ def get_preference(self, key: str, default: Any = None) -> Any:
573
+ """Get a specific preference value.
574
+
575
+ Args:
576
+ key: The preference key to retrieve.
577
+ default: Default value to return if key is not found.
578
+
579
+ Returns:
580
+ The preference value if found, otherwise the default value.
581
+
582
+ Example:
583
+ >>> user = User(preferences={"theme": "dark", "language": "en"})
584
+ >>> theme = user.get_preference("theme")
585
+ >>> print(theme)
586
+ "dark"
587
+ >>> missing = user.get_preference("missing_key", "default_value")
588
+ >>> print(missing)
589
+ "default_value"
590
+ """
591
+ return self.preferences.get(key, default)
592
+
593
+ def set_preference(self, key: str, value: Any) -> None:
594
+ """Set a preference value and save the model.
595
+
596
+ Args:
597
+ key: The preference key to set.
598
+ value: The value to set for the preference.
599
+
600
+ Example:
601
+ >>> user = User.objects.get(id=1)
602
+ >>> user.set_preference("theme", "light")
603
+ >>> user.set_preference("notifications", True)
604
+ >>> print(user.preferences)
605
+ {"theme": "light", "notifications": True}
606
+ """
607
+ self.preferences[key] = value
608
+ self.save(update_fields=['preferences', 'updated_at'])
609
+ ```
610
+
611
+ ### FastAPI Model Documentation
612
+
613
+ ```python
614
+ from pydantic import BaseModel, EmailStr, Field, validator
615
+ from typing import Optional, Dict, Any
616
+ from datetime import datetime
617
+ from enum import Enum
618
+
619
+ class UserRole(str, Enum):
620
+ """Enumeration of possible user roles.
621
+
622
+ Attributes:
623
+ ADMIN: Administrator with full system access.
624
+ MEMBER: Regular member with standard permissions.
625
+ GUEST: Guest user with limited access.
626
+ """
627
+ ADMIN = "admin"
628
+ MEMBER = "member"
629
+ GUEST = "guest"
630
+
631
+ class UserBase(BaseModel):
632
+ """Base user model with common fields.
633
+
634
+ This base model contains fields that are common across different
635
+ user-related models (create, update, response). It provides consistent
636
+ validation and documentation for user data.
637
+
638
+ Attributes:
639
+ name: Full name of the user (2-100 characters).
640
+ email: Valid email address.
641
+ age: User's age (must be at least 13).
642
+ role: User's role in the system.
643
+ """
644
+
645
+ name: str = Field(
646
+ ...,
647
+ min_length=2,
648
+ max_length=100,
649
+ description="User's full name",
650
+ example="John Doe"
651
+ )
652
+
653
+ email: EmailStr = Field(
654
+ ...,
655
+ description="User's email address",
656
+ example="john@example.com"
657
+ )
658
+
659
+ age: int = Field(
660
+ ...,
661
+ ge=13,
662
+ le=120,
663
+ description="User's age in years",
664
+ example=30
665
+ )
666
+
667
+ role: UserRole = Field(
668
+ default=UserRole.MEMBER,
669
+ description="User's role in the system",
670
+ example=UserRole.MEMBER
671
+ )
672
+
673
+ @validator('name')
674
+ def validate_name(cls, v: str) -> str:
675
+ """Validate that name contains only allowed characters.
676
+
677
+ Args:
678
+ v: The name value to validate.
679
+
680
+ Returns:
681
+ The validated name.
682
+
683
+ Raises:
684
+ ValueError: If name contains invalid characters.
685
+ """
686
+ if not v.replace(' ', '').replace('-', '').replace("'", '').isalpha():
687
+ raise ValueError('Name must contain only letters, spaces, hyphens, and apostrophes')
688
+ return v.strip()
689
+
690
+ class UserCreate(UserBase):
691
+ """Model for creating a new user.
692
+
693
+ This model extends UserBase with fields specific to user creation,
694
+ including password and optional preferences.
695
+
696
+ Example:
697
+ Creating a user creation request:
698
+
699
+ >>> user_data = UserCreate(
700
+ ... name="Jane Doe",
701
+ ... email="jane@example.com",
702
+ ... age=25,
703
+ ... password="secure_password123",
704
+ ... preferences={"theme": "dark"}
705
+ ... )
706
+ """
707
+
708
+ password: str = Field(
709
+ ...,
710
+ min_length=8,
711
+ max_length=128,
712
+ description="User's password (minimum 8 characters)",
713
+ example="secure_password123"
714
+ )
715
+
716
+ preferences: Optional[Dict[str, Any]] = Field(
717
+ default_factory=dict,
718
+ description="User preferences as key-value pairs",
719
+ example={"theme": "dark", "notifications": True}
720
+ )
721
+
722
+ @validator('password')
723
+ def validate_password(cls, v: str) -> str:
724
+ """Validate password complexity.
725
+
726
+ Args:
727
+ v: The password to validate.
728
+
729
+ Returns:
730
+ The validated password.
731
+
732
+ Raises:
733
+ ValueError: If password doesn't meet complexity requirements.
734
+ """
735
+ if not any(c.isupper() for c in v):
736
+ raise ValueError('Password must contain at least one uppercase letter')
737
+ if not any(c.islower() for c in v):
738
+ raise ValueError('Password must contain at least one lowercase letter')
739
+ if not any(c.isdigit() for c in v):
740
+ raise ValueError('Password must contain at least one digit')
741
+ return v
742
+
743
+ class UserResponse(UserBase):
744
+ """Model for user data in API responses.
745
+
746
+ This model extends UserBase with additional fields that are included
747
+ in API responses but not in creation requests, such as ID and timestamps.
748
+
749
+ Attributes:
750
+ id: Unique identifier for the user.
751
+ is_active: Whether the user account is active.
752
+ created_at: Timestamp when the user was created.
753
+ updated_at: Timestamp when the user was last modified.
754
+ preferences: User preferences dictionary.
755
+
756
+ Example:
757
+ Response model usage:
758
+
759
+ >>> user_response = UserResponse(
760
+ ... id="123e4567-e89b-12d3-a456-426614174000",
761
+ ... name="John Doe",
762
+ ... email="john@example.com",
763
+ ... age=30,
764
+ ... role=UserRole.MEMBER,
765
+ ... is_active=True,
766
+ ... created_at=datetime.utcnow(),
767
+ ... updated_at=datetime.utcnow(),
768
+ ... preferences={"theme": "light"}
769
+ ... )
770
+ """
771
+
772
+ id: str = Field(
773
+ ...,
774
+ description="Unique identifier for the user",
775
+ example="123e4567-e89b-12d3-a456-426614174000"
776
+ )
777
+
778
+ is_active: bool = Field(
779
+ default=True,
780
+ description="Whether the user account is active",
781
+ example=True
782
+ )
783
+
784
+ created_at: datetime = Field(
785
+ ...,
786
+ description="Timestamp when the user was created",
787
+ example="2023-01-01T12:00:00Z"
788
+ )
789
+
790
+ updated_at: datetime = Field(
791
+ ...,
792
+ description="Timestamp when the user was last modified",
793
+ example="2023-06-01T14:30:00Z"
794
+ )
795
+
796
+ preferences: Dict[str, Any] = Field(
797
+ default_factory=dict,
798
+ description="User preferences as key-value pairs",
799
+ example={"theme": "light", "notifications": True}
800
+ )
801
+
802
+ class Config:
803
+ """Pydantic model configuration."""
804
+ from_attributes = True
805
+ json_encoders = {
806
+ datetime: lambda v: v.isoformat()
807
+ }
808
+ schema_extra = {
809
+ "example": {
810
+ "id": "123e4567-e89b-12d3-a456-426614174000",
811
+ "name": "John Doe",
812
+ "email": "john@example.com",
813
+ "age": 30,
814
+ "role": "member",
815
+ "is_active": True,
816
+ "created_at": "2023-01-01T12:00:00Z",
817
+ "updated_at": "2023-06-01T14:30:00Z",
818
+ "preferences": {
819
+ "theme": "light",
820
+ "notifications": True
821
+ }
822
+ }
823
+ }
824
+ ```
825
+
826
+ ### Utility Function Documentation
827
+
828
+ ```python
829
+ import re
830
+ import hashlib
831
+ from typing import List, Optional, Union
832
+ from uuid import uuid4
833
+
834
+ def validate_email(email: str) -> bool:
835
+ """Validate email address format using RFC 5322 standard.
836
+
837
+ This function performs comprehensive email validation including
838
+ format checking, length validation, and basic domain validation.
839
+
840
+ Args:
841
+ email: Email address string to validate.
842
+
843
+ Returns:
844
+ True if email format is valid, False otherwise.
845
+
846
+ Example:
847
+ >>> validate_email("user@example.com")
848
+ True
849
+ >>> validate_email("invalid.email")
850
+ False
851
+ >>> validate_email("user+tag@subdomain.example.com")
852
+ True
853
+
854
+ Note:
855
+ This function validates format only and does not check if the
856
+ email address actually exists or is deliverable.
857
+ """
858
+ if not email or len(email) > 254:
859
+ return False
860
+
861
+ pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
862
+ return bool(re.match(pattern, email))
863
+
864
+ def generate_user_id() -> str:
865
+ """Generate a unique user identifier.
866
+
867
+ Creates a UUID4-based unique identifier suitable for use as
868
+ a primary key in user records.
869
+
870
+ Returns:
871
+ String representation of UUID4.
872
+
873
+ Example:
874
+ >>> user_id = generate_user_id()
875
+ >>> print(len(user_id))
876
+ 36
877
+ >>> print(user_id)
878
+ "123e4567-e89b-12d3-a456-426614174000"
879
+
880
+ Note:
881
+ This function uses UUID4 which provides good uniqueness
882
+ guarantees but is not cryptographically secure for
883
+ sensitive applications.
884
+ """
885
+ return str(uuid4())
886
+
887
+ def hash_password(password: str, salt: Optional[str] = None) -> tuple[str, str]:
888
+ """Hash a password using PBKDF2 with SHA-256.
889
+
890
+ This function creates a secure hash of the provided password using
891
+ PBKDF2 (Password-Based Key Derivation Function 2) with SHA-256.
892
+
893
+ Args:
894
+ password: Plain text password to hash.
895
+ salt: Optional salt value. If not provided, a random salt is generated.
896
+
897
+ Returns:
898
+ Tuple containing (hashed_password, salt_used).
899
+
900
+ Example:
901
+ Hashing a new password:
902
+
903
+ >>> hashed, salt = hash_password("my_secure_password")
904
+ >>> print(len(hashed))
905
+ 64
906
+
907
+ Verifying a password:
908
+
909
+ >>> stored_hash = "existing_hash_from_database"
910
+ >>> stored_salt = "existing_salt_from_database"
911
+ >>> user_input = "user_entered_password"
912
+ >>> new_hash, _ = hash_password(user_input, stored_salt)
913
+ >>> is_valid = new_hash == stored_hash
914
+
915
+ Security:
916
+ - Uses PBKDF2 with 100,000 iterations for resistance against
917
+ brute force attacks
918
+ - SHA-256 provides good security for password hashing
919
+ - Random salt prevents rainbow table attacks
920
+ """
921
+ if salt is None:
922
+ salt = hashlib.pbkdf2_hmac('sha256', uuid4().bytes, b'salt', 1000).hex()[:16]
923
+
924
+ hashed = hashlib.pbkdf2_hmac(
925
+ 'sha256',
926
+ password.encode('utf-8'),
927
+ salt.encode('utf-8'),
928
+ 100000
929
+ ).hex()
930
+
931
+ return hashed, salt
932
+
933
+ def sanitize_user_input(input_string: str, max_length: int = 1000) -> str:
934
+ """Sanitize user input by removing potentially dangerous characters.
935
+
936
+ This function removes or escapes characters that could be used in
937
+ injection attacks or cause display issues.
938
+
939
+ Args:
940
+ input_string: Raw user input to sanitize.
941
+ max_length: Maximum allowed length for the input.
942
+
943
+ Returns:
944
+ Sanitized string safe for storage and display.
945
+
946
+ Raises:
947
+ ValueError: If input exceeds maximum length after sanitization.
948
+
949
+ Example:
950
+ >>> sanitize_user_input("Hello <script>alert('xss')</script>")
951
+ "Hello alert('xss')"
952
+ >>> sanitize_user_input("Normal text with symbols: @#$%")
953
+ "Normal text with symbols: @#$%"
954
+
955
+ Security:
956
+ This function provides basic XSS protection but should not be
957
+ the only security measure. Always use proper templating and
958
+ context-aware escaping in your presentation layer.
959
+ """
960
+ if not input_string:
961
+ return ""
962
+
963
+ # Remove HTML tags and dangerous characters
964
+ dangerous_chars = ['<', '>', '"', "'", '&', '`']
965
+ sanitized = input_string
966
+
967
+ for char in dangerous_chars:
968
+ sanitized = sanitized.replace(char, '')
969
+
970
+ # Limit length
971
+ sanitized = sanitized[:max_length]
972
+
973
+ # Remove excessive whitespace
974
+ sanitized = ' '.join(sanitized.split())
975
+
976
+ return sanitized.strip()
977
+ ```
978
+
979
+ ### Configuration and Constants Documentation
980
+
981
+ ```python
982
+ """Configuration settings and constants for the user management system.
983
+
984
+ This module contains all configuration values, constants, and settings
985
+ used throughout the application. Values are loaded from environment
986
+ variables with appropriate defaults for development environments.
987
+
988
+ Environment Variables:
989
+ DATABASE_URL: Database connection URL
990
+ CACHE_TTL: Cache time-to-live in seconds
991
+ MAX_LOGIN_ATTEMPTS: Maximum login attempts before lockout
992
+ PASSWORD_MIN_LENGTH: Minimum required password length
993
+ EMAIL_VERIFICATION_TIMEOUT: Email verification timeout in hours
994
+
995
+ Example:
996
+ Loading configuration:
997
+
998
+ >>> from src.config import settings
999
+ >>> print(settings.DATABASE_URL)
1000
+ "postgresql://localhost:5432/myapp"
1001
+ >>> print(settings.MAX_LOGIN_ATTEMPTS)
1002
+ 5
1003
+ """
1004
+
1005
+ import os
1006
+ from typing import List, Optional
1007
+ from dataclasses import dataclass
1008
+
1009
+ @dataclass
1010
+ class Settings:
1011
+ """Application settings loaded from environment variables.
1012
+
1013
+ This class centralizes all configuration settings and provides
1014
+ type safety and validation for configuration values.
1015
+
1016
+ Attributes:
1017
+ DATABASE_URL: Database connection string.
1018
+ DEBUG: Whether debug mode is enabled.
1019
+ SECRET_KEY: Secret key for cryptographic operations.
1020
+ CACHE_TTL: Default cache time-to-live in seconds.
1021
+ MAX_LOGIN_ATTEMPTS: Maximum login attempts before account lockout.
1022
+ PASSWORD_MIN_LENGTH: Minimum required password length.
1023
+ EMAIL_VERIFICATION_TIMEOUT: Email verification timeout in hours.
1024
+ ALLOWED_EMAIL_DOMAINS: List of allowed email domains (empty = all allowed).
1025
+
1026
+ Example:
1027
+ Accessing settings:
1028
+
1029
+ >>> settings = Settings()
1030
+ >>> if settings.DEBUG:
1031
+ ... print("Debug mode is enabled")
1032
+ >>> db_url = settings.DATABASE_URL
1033
+ """
1034
+
1035
+ DATABASE_URL: str = os.getenv(
1036
+ 'DATABASE_URL',
1037
+ 'postgresql://localhost:5432/myapp_dev'
1038
+ )
1039
+
1040
+ DEBUG: bool = os.getenv('DEBUG', 'False').lower() == 'true'
1041
+
1042
+ SECRET_KEY: str = os.getenv(
1043
+ 'SECRET_KEY',
1044
+ 'dev-secret-key-change-in-production'
1045
+ )
1046
+
1047
+ CACHE_TTL: int = int(os.getenv('CACHE_TTL', '3600')) # 1 hour
1048
+
1049
+ MAX_LOGIN_ATTEMPTS: int = int(os.getenv('MAX_LOGIN_ATTEMPTS', '5'))
1050
+
1051
+ PASSWORD_MIN_LENGTH: int = int(os.getenv('PASSWORD_MIN_LENGTH', '8'))
1052
+
1053
+ EMAIL_VERIFICATION_TIMEOUT: int = int(os.getenv('EMAIL_VERIFICATION_TIMEOUT', '24'))
1054
+
1055
+ ALLOWED_EMAIL_DOMAINS: List[str] = os.getenv(
1056
+ 'ALLOWED_EMAIL_DOMAINS', ''
1057
+ ).split(',') if os.getenv('ALLOWED_EMAIL_DOMAINS') else []
1058
+
1059
+ def __post_init__(self) -> None:
1060
+ """Validate configuration after initialization.
1061
+
1062
+ Raises:
1063
+ ValueError: If any configuration value is invalid.
1064
+ """
1065
+ if self.SECRET_KEY == 'dev-secret-key-change-in-production' and not self.DEBUG:
1066
+ raise ValueError("SECRET_KEY must be set in production environment")
1067
+
1068
+ if self.PASSWORD_MIN_LENGTH < 6:
1069
+ raise ValueError("PASSWORD_MIN_LENGTH must be at least 6")
1070
+
1071
+ if self.MAX_LOGIN_ATTEMPTS < 1:
1072
+ raise ValueError("MAX_LOGIN_ATTEMPTS must be at least 1")
1073
+
1074
+ # Global settings instance
1075
+ settings = Settings()
1076
+
1077
+ # User validation constants
1078
+ class UserConstants:
1079
+ """Constants for user validation and constraints.
1080
+
1081
+ This class contains all constants related to user data validation,
1082
+ business rules, and system limits.
1083
+ """
1084
+
1085
+ # Name validation
1086
+ NAME_MIN_LENGTH: int = 2
1087
+ NAME_MAX_LENGTH: int = 100
1088
+
1089
+ # Age validation
1090
+ MIN_AGE: int = 13
1091
+ MAX_AGE: int = 120
1092
+
1093
+ # Email validation
1094
+ EMAIL_MAX_LENGTH: int = 254
1095
+ EMAIL_PATTERN: str = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
1096
+
1097
+ # Reserved usernames that cannot be used
1098
+ RESERVED_USERNAMES: List[str] = [
1099
+ 'admin', 'administrator', 'root', 'system', 'api', 'www',
1100
+ 'mail', 'email', 'support', 'help', 'info', 'noreply'
1101
+ ]
1102
+
1103
+ # Default user preferences
1104
+ DEFAULT_PREFERENCES: dict = {
1105
+ 'theme': 'light',
1106
+ 'language': 'en',
1107
+ 'notifications': True,
1108
+ 'email_notifications': True
1109
+ }
1110
+
1111
+ # Cache keys
1112
+ CACHE_KEY_USER_BY_ID: str = "user:id:{user_id}"
1113
+ CACHE_KEY_USER_BY_EMAIL: str = "user:email:{email}"
1114
+ CACHE_KEY_USER_PREFERENCES: str = "user:preferences:{user_id}"
1115
+
1116
+ # Error messages
1117
+ class ErrorMessages:
1118
+ """Standardized error messages for user operations.
1119
+
1120
+ This class provides consistent error messages throughout the
1121
+ application for better user experience and easier maintenance.
1122
+ """
1123
+
1124
+ # Validation errors
1125
+ INVALID_EMAIL_FORMAT: str = "Invalid email address format"
1126
+ EMAIL_ALREADY_EXISTS: str = "A user with this email address already exists"
1127
+ USERNAME_ALREADY_EXISTS: str = "This username is already taken"
1128
+ RESERVED_USERNAME: str = "This username is reserved and cannot be used"
1129
+
1130
+ # Password errors
1131
+ PASSWORD_TOO_SHORT: str = f"Password must be at least {settings.PASSWORD_MIN_LENGTH} characters"
1132
+ PASSWORD_TOO_WEAK: str = "Password must contain uppercase, lowercase, and numeric characters"
1133
+
1134
+ # User not found errors
1135
+ USER_NOT_FOUND: str = "User not found"
1136
+ USER_NOT_FOUND_BY_EMAIL: str = "No user found with this email address"
1137
+ USER_NOT_FOUND_BY_ID: str = "No user found with this ID"
1138
+
1139
+ # Authentication errors
1140
+ INVALID_CREDENTIALS: str = "Invalid email or password"
1141
+ ACCOUNT_LOCKED: str = f"Account locked due to {settings.MAX_LOGIN_ATTEMPTS} failed login attempts"
1142
+ ACCOUNT_INACTIVE: str = "This account has been deactivated"
1143
+
1144
+ # Authorization errors
1145
+ INSUFFICIENT_PERMISSIONS: str = "You do not have permission to perform this action"
1146
+ ACCESS_DENIED: str = "Access denied"
1147
+ ```