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