metacoding 1.0.0 → 1.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.
- package/CHANGELOG.md +79 -42
- package/LICENSE +1 -1
- package/lib/commands/init.d.ts.map +1 -1
- package/lib/commands/init.js +1 -1
- package/lib/commands/init.js.map +1 -1
- package/lib/services/template-manager.d.ts +4 -1
- package/lib/services/template-manager.d.ts.map +1 -1
- package/lib/services/template-manager.js +129 -10
- package/lib/services/template-manager.js.map +1 -1
- package/lib/services/vscode.js +1 -1
- package/lib/services/vscode.js.map +1 -1
- package/package.json +12 -4
- package/templates/general/code-review.instructions.md +265 -0
- package/templates/general/{files/copilot-instructions.md.template → copilot-instructions.md} +97 -140
- package/templates/{python/files → general}/docs-update.instructions.md +45 -32
- package/templates/general/release.instructions.md +242 -0
- package/templates/general/test-runner.instructions.md +188 -0
- package/templates/node/nodejs.coding.instructions.md +249 -0
- package/templates/node/nodejs.docs.instructions.md +234 -0
- package/templates/node/nodejs.testing.instructions.md +373 -0
- package/templates/python/python.coding.instructions.md +339 -0
- package/templates/python/python.docs.instructions.md +1147 -0
- package/templates/python/python.testing.instructions.md +1074 -0
- package/templates/react/react.coding.instructions.md +695 -0
- package/templates/react/react.docs.instructions.md +427 -0
- package/templates/react/react.testing.instructions.md +193 -0
- package/templates/react/test-runner.instructions.md +135 -0
- package/templates/typescript/template.json +16 -0
- package/templates/typescript/typescript.coding.instructions.md +368 -0
- package/templates/typescript/typescript.docs.instructions.md +734 -0
- package/templates/typescript/typescript.testing.instructions.md +740 -0
- package/templates/general/files/code-review.instructions.md +0 -111
- package/templates/general/files/docs-update.instructions.md +0 -203
- package/templates/general/files/release.instructions.md +0 -72
- package/templates/general/files/test-runner.instructions.md +0 -107
- package/templates/node/files/code-review.instructions.md +0 -222
- package/templates/node/files/copilot-instructions.md.template +0 -391
- package/templates/node/files/docs-update.instructions.md +0 -203
- package/templates/node/files/release.instructions.md +0 -72
- package/templates/node/files/test-runner.instructions.md +0 -108
- package/templates/python/files/code-review.instructions.md +0 -215
- package/templates/python/files/copilot-instructions.md.template +0 -418
- package/templates/python/files/release.instructions.md +0 -72
- package/templates/python/files/test-runner.instructions.md +0 -108
- package/templates/react/files/code-review.instructions.md +0 -160
- package/templates/react/files/copilot-instructions.md.template +0 -472
- package/templates/react/files/docs-update.instructions.md +0 -203
- package/templates/react/files/release.instructions.md +0 -72
- 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
|
+
```
|