django-cfg 1.3.1__py3-none-any.whl → 1.3.3__py3-none-any.whl

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 (25) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +419 -0
  3. django_cfg/apps/payments/management/commands/currency_stats.py +376 -0
  4. django_cfg/apps/payments/management/commands/process_pending_payments.py +357 -0
  5. django_cfg/apps/payments/management/commands/test_providers.py +434 -0
  6. django_cfg/apps/payments/models/balance.py +5 -2
  7. django_cfg/apps/payments/models/managers/api_key_managers.py +2 -2
  8. django_cfg/apps/payments/models/managers/balance_managers.py +3 -3
  9. django_cfg/apps/payments/models/managers/subscription_managers.py +3 -3
  10. django_cfg/apps/payments/services/cache_service/__init__.py +143 -0
  11. django_cfg/apps/payments/services/cache_service/api_key_cache.py +37 -0
  12. django_cfg/apps/payments/services/cache_service/interfaces.py +32 -0
  13. django_cfg/apps/payments/services/cache_service/keys.py +49 -0
  14. django_cfg/apps/payments/services/cache_service/rate_limit_cache.py +47 -0
  15. django_cfg/apps/payments/services/cache_service/simple_cache.py +101 -0
  16. django_cfg/apps/payments/services/core/payment_service.py +49 -22
  17. django_cfg/apps/payments/signals/api_key_signals.py +2 -2
  18. django_cfg/apps/payments/signals/balance_signals.py +1 -1
  19. django_cfg/utils/smart_defaults.py +10 -4
  20. {django_cfg-1.3.1.dist-info → django_cfg-1.3.3.dist-info}/METADATA +1 -1
  21. {django_cfg-1.3.1.dist-info → django_cfg-1.3.3.dist-info}/RECORD +24 -15
  22. django_cfg/apps/payments/services/cache/cache_service.py +0 -235
  23. {django_cfg-1.3.1.dist-info → django_cfg-1.3.3.dist-info}/WHEEL +0 -0
  24. {django_cfg-1.3.1.dist-info → django_cfg-1.3.3.dist-info}/entry_points.txt +0 -0
  25. {django_cfg-1.3.1.dist-info → django_cfg-1.3.3.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py CHANGED
@@ -32,7 +32,7 @@ Example:
32
32
  default_app_config = "django_cfg.apps.DjangoCfgConfig"
33
33
 
34
34
  # Version information
35
- __version__ = "1.3.1"
35
+ __version__ = "1.3.3"
36
36
  __license__ = "MIT"
37
37
 
38
38
  # Import registry for organized lazy loading
@@ -0,0 +1,419 @@
1
+ """
2
+ Cleanup Expired Data Management Command for Universal Payment System v2.0.
3
+
4
+ Clean up expired payments, sessions, and other temporary data.
5
+ """
6
+
7
+ from datetime import timedelta
8
+ from typing import List, Dict, Any, Optional
9
+
10
+ from django.core.management.base import BaseCommand, CommandError
11
+ from django.utils import timezone
12
+ from django.db import transaction
13
+ from django.db.models import Q, Count
14
+ from django.core.cache import cache
15
+
16
+ from django_cfg.modules.django_logger import get_logger
17
+ from django_cfg.apps.payments.models import UniversalPayment, APIKey, Transaction
18
+ from django_cfg.apps.payments.services.cache.cache_service import get_cache_service
19
+
20
+ logger = get_logger("cleanup_expired_data")
21
+
22
+
23
+ class Command(BaseCommand):
24
+ """
25
+ Clean up expired data from the payment system.
26
+
27
+ Features:
28
+ - Remove expired payments
29
+ - Clean up expired API keys
30
+ - Remove old transaction logs
31
+ - Clear stale cache entries
32
+ - Comprehensive logging and statistics
33
+ """
34
+
35
+ help = 'Clean up expired data from the payment system'
36
+
37
+ def add_arguments(self, parser):
38
+ """Add command line arguments."""
39
+ parser.add_argument(
40
+ '--payments-age-days',
41
+ type=int,
42
+ default=30,
43
+ help='Remove failed/expired payments older than N days (default: 30)'
44
+ )
45
+
46
+ parser.add_argument(
47
+ '--transactions-age-days',
48
+ type=int,
49
+ default=90,
50
+ help='Remove old transaction logs older than N days (default: 90)'
51
+ )
52
+
53
+ parser.add_argument(
54
+ '--api-keys',
55
+ action='store_true',
56
+ help='Clean up expired API keys'
57
+ )
58
+
59
+ parser.add_argument(
60
+ '--cache',
61
+ action='store_true',
62
+ help='Clear stale cache entries'
63
+ )
64
+
65
+ parser.add_argument(
66
+ '--all',
67
+ action='store_true',
68
+ help='Clean up all types of expired data'
69
+ )
70
+
71
+ parser.add_argument(
72
+ '--dry-run',
73
+ action='store_true',
74
+ help='Show what would be cleaned without making changes'
75
+ )
76
+
77
+ parser.add_argument(
78
+ '--batch-size',
79
+ type=int,
80
+ default=1000,
81
+ help='Number of records to process in each batch (default: 1000)'
82
+ )
83
+
84
+ parser.add_argument(
85
+ '--verbose',
86
+ action='store_true',
87
+ help='Show detailed cleanup information'
88
+ )
89
+
90
+ def handle(self, *args, **options):
91
+ """Execute the command."""
92
+ try:
93
+ self.options = options
94
+ self.dry_run = options['dry_run']
95
+ self.verbose = options['verbose']
96
+
97
+ self.show_header()
98
+
99
+ # Initialize statistics
100
+ self.stats = {
101
+ 'payments_removed': 0,
102
+ 'transactions_removed': 0,
103
+ 'api_keys_removed': 0,
104
+ 'cache_entries_cleared': 0,
105
+ 'errors': 0
106
+ }
107
+
108
+ # Determine what to clean
109
+ clean_all = options['all']
110
+
111
+ if clean_all or not any([options['api_keys'], options['cache']]):
112
+ # Default: clean payments and transactions
113
+ self.cleanup_expired_payments()
114
+ self.cleanup_old_transactions()
115
+
116
+ if clean_all or options['api_keys']:
117
+ self.cleanup_expired_api_keys()
118
+
119
+ if clean_all or options['cache']:
120
+ self.cleanup_stale_cache()
121
+
122
+ self.show_summary()
123
+
124
+ except Exception as e:
125
+ logger.error(f"Cleanup expired data command failed: {e}")
126
+ raise CommandError(f"Failed to cleanup expired data: {e}")
127
+
128
+ def show_header(self):
129
+ """Display command header."""
130
+ mode = "DRY RUN" if self.dry_run else "LIVE MODE"
131
+ self.stdout.write(
132
+ self.style.SUCCESS("=" * 60)
133
+ )
134
+ self.stdout.write(
135
+ self.style.SUCCESS(f"🧹 CLEANUP EXPIRED DATA - {mode}")
136
+ )
137
+ self.stdout.write(
138
+ self.style.SUCCESS("=" * 60)
139
+ )
140
+ self.stdout.write(f"Started: {timezone.now().strftime('%Y-%m-%d %H:%M:%S UTC')}")
141
+ self.stdout.write("")
142
+
143
+ def cleanup_expired_payments(self):
144
+ """Clean up expired and failed payments."""
145
+ self.stdout.write(self.style.SUCCESS("🗑️ CLEANING UP EXPIRED PAYMENTS"))
146
+ self.stdout.write("-" * 40)
147
+
148
+ # Calculate cutoff date
149
+ cutoff_date = timezone.now() - timedelta(days=self.options['payments_age_days'])
150
+
151
+ # Find payments to remove
152
+ expired_payments = UniversalPayment.objects.filter(
153
+ Q(status__in=['failed', 'expired', 'cancelled']) &
154
+ Q(created_at__lt=cutoff_date)
155
+ )
156
+
157
+ total_count = expired_payments.count()
158
+ self.stdout.write(f"Found {total_count} expired payments to remove")
159
+
160
+ if total_count == 0:
161
+ self.stdout.write(self.style.WARNING("No expired payments to clean up"))
162
+ return
163
+
164
+ if self.dry_run:
165
+ self.stdout.write(f"[DRY RUN] Would remove {total_count} expired payments")
166
+ self.stats['payments_removed'] = total_count
167
+ return
168
+
169
+ # Remove in batches
170
+ batch_size = self.options['batch_size']
171
+ removed_count = 0
172
+
173
+ try:
174
+ while True:
175
+ # Get batch of payments to delete
176
+ batch_ids = list(
177
+ expired_payments.values_list('id', flat=True)[:batch_size]
178
+ )
179
+
180
+ if not batch_ids:
181
+ break
182
+
183
+ with transaction.atomic():
184
+ # Delete the batch
185
+ deleted_count = UniversalPayment.objects.filter(
186
+ id__in=batch_ids
187
+ ).delete()[0]
188
+
189
+ removed_count += deleted_count
190
+
191
+ if self.verbose:
192
+ self.stdout.write(f" Removed batch: {deleted_count} payments")
193
+
194
+ # Update progress
195
+ progress = (removed_count / total_count) * 100
196
+ self.stdout.write(f"Progress: {removed_count}/{total_count} ({progress:.1f}%)")
197
+
198
+ self.stats['payments_removed'] = removed_count
199
+ logger.info(f"Removed {removed_count} expired payments")
200
+
201
+ except Exception as e:
202
+ logger.error(f"Error cleaning up payments: {e}")
203
+ self.stats['errors'] += 1
204
+ self.stdout.write(self.style.ERROR(f"Error: {e}"))
205
+
206
+ self.stdout.write("")
207
+
208
+ def cleanup_old_transactions(self):
209
+ """Clean up old transaction logs."""
210
+ self.stdout.write(self.style.SUCCESS("📋 CLEANING UP OLD TRANSACTIONS"))
211
+ self.stdout.write("-" * 40)
212
+
213
+ # Calculate cutoff date
214
+ cutoff_date = timezone.now() - timedelta(days=self.options['transactions_age_days'])
215
+
216
+ # Find transactions to remove (keep important ones)
217
+ old_transactions = Transaction.objects.filter(
218
+ created_at__lt=cutoff_date
219
+ ).exclude(
220
+ # Keep transactions for completed payments
221
+ payment__status='completed'
222
+ )
223
+
224
+ total_count = old_transactions.count()
225
+ self.stdout.write(f"Found {total_count} old transactions to remove")
226
+
227
+ if total_count == 0:
228
+ self.stdout.write(self.style.WARNING("No old transactions to clean up"))
229
+ return
230
+
231
+ if self.dry_run:
232
+ self.stdout.write(f"[DRY RUN] Would remove {total_count} old transactions")
233
+ self.stats['transactions_removed'] = total_count
234
+ return
235
+
236
+ # Remove in batches
237
+ batch_size = self.options['batch_size']
238
+ removed_count = 0
239
+
240
+ try:
241
+ while True:
242
+ # Get batch of transactions to delete
243
+ batch_ids = list(
244
+ old_transactions.values_list('id', flat=True)[:batch_size]
245
+ )
246
+
247
+ if not batch_ids:
248
+ break
249
+
250
+ with transaction.atomic():
251
+ # Delete the batch
252
+ deleted_count = Transaction.objects.filter(
253
+ id__in=batch_ids
254
+ ).delete()[0]
255
+
256
+ removed_count += deleted_count
257
+
258
+ if self.verbose:
259
+ self.stdout.write(f" Removed batch: {deleted_count} transactions")
260
+
261
+ # Update progress
262
+ progress = (removed_count / total_count) * 100
263
+ self.stdout.write(f"Progress: {removed_count}/{total_count} ({progress:.1f}%)")
264
+
265
+ self.stats['transactions_removed'] = removed_count
266
+ logger.info(f"Removed {removed_count} old transactions")
267
+
268
+ except Exception as e:
269
+ logger.error(f"Error cleaning up transactions: {e}")
270
+ self.stats['errors'] += 1
271
+ self.stdout.write(self.style.ERROR(f"Error: {e}"))
272
+
273
+ self.stdout.write("")
274
+
275
+ def cleanup_expired_api_keys(self):
276
+ """Clean up expired API keys."""
277
+ self.stdout.write(self.style.SUCCESS("🔑 CLEANING UP EXPIRED API KEYS"))
278
+ self.stdout.write("-" * 40)
279
+
280
+ # Find expired API keys
281
+ now = timezone.now()
282
+ expired_keys = APIKey.objects.filter(
283
+ Q(expires_at__lt=now) | Q(is_active=False)
284
+ ).filter(
285
+ # Only remove keys that haven't been used recently
286
+ last_used_at__lt=now - timedelta(days=7)
287
+ )
288
+
289
+ total_count = expired_keys.count()
290
+ self.stdout.write(f"Found {total_count} expired API keys to remove")
291
+
292
+ if total_count == 0:
293
+ self.stdout.write(self.style.WARNING("No expired API keys to clean up"))
294
+ return
295
+
296
+ if self.dry_run:
297
+ self.stdout.write(f"[DRY RUN] Would remove {total_count} expired API keys")
298
+ self.stats['api_keys_removed'] = total_count
299
+ return
300
+
301
+ try:
302
+ # Remove expired keys
303
+ removed_count = expired_keys.delete()[0]
304
+ self.stats['api_keys_removed'] = removed_count
305
+
306
+ self.stdout.write(f"Removed {removed_count} expired API keys")
307
+ logger.info(f"Removed {removed_count} expired API keys")
308
+
309
+ except Exception as e:
310
+ logger.error(f"Error cleaning up API keys: {e}")
311
+ self.stats['errors'] += 1
312
+ self.stdout.write(self.style.ERROR(f"Error: {e}"))
313
+
314
+ self.stdout.write("")
315
+
316
+ def cleanup_stale_cache(self):
317
+ """Clean up stale cache entries."""
318
+ self.stdout.write(self.style.SUCCESS("💾 CLEANING UP STALE CACHE"))
319
+ self.stdout.write("-" * 40)
320
+
321
+ if self.dry_run:
322
+ self.stdout.write("[DRY RUN] Would clear stale cache entries")
323
+ self.stats['cache_entries_cleared'] = 100 # Estimate
324
+ return
325
+
326
+ try:
327
+ # Get cache service
328
+ cache_service = get_cache_service()
329
+
330
+ # Clear payment-related caches
331
+ cache_patterns = [
332
+ 'payment:*',
333
+ 'balance:*',
334
+ 'api_key:*',
335
+ 'currency:*',
336
+ 'provider:*',
337
+ 'rate_limit:*'
338
+ ]
339
+
340
+ cleared_count = 0
341
+
342
+ for pattern in cache_patterns:
343
+ try:
344
+ # Clear cache entries matching pattern
345
+ if hasattr(cache_service, 'clear_pattern'):
346
+ count = cache_service.clear_pattern(pattern)
347
+ cleared_count += count
348
+ if self.verbose:
349
+ self.stdout.write(f" Cleared {count} entries for pattern: {pattern}")
350
+ except Exception as e:
351
+ logger.warning(f"Failed to clear cache pattern {pattern}: {e}")
352
+
353
+ # Fallback: clear all cache if pattern clearing not available
354
+ if cleared_count == 0:
355
+ cache.clear()
356
+ cleared_count = 1 # At least one operation
357
+ self.stdout.write("Cleared all cache entries")
358
+
359
+ self.stats['cache_entries_cleared'] = cleared_count
360
+ logger.info(f"Cleared {cleared_count} cache entries")
361
+
362
+ except Exception as e:
363
+ logger.error(f"Error cleaning up cache: {e}")
364
+ self.stats['errors'] += 1
365
+ self.stdout.write(self.style.ERROR(f"Error: {e}"))
366
+
367
+ self.stdout.write("")
368
+
369
+ def show_summary(self):
370
+ """Display cleanup summary."""
371
+ self.stdout.write(self.style.SUCCESS("📊 CLEANUP SUMMARY"))
372
+ self.stdout.write("-" * 40)
373
+
374
+ summary_items = [
375
+ ("Payments Removed", self.stats['payments_removed']),
376
+ ("Transactions Removed", self.stats['transactions_removed']),
377
+ ("API Keys Removed", self.stats['api_keys_removed']),
378
+ ("Cache Entries Cleared", self.stats['cache_entries_cleared']),
379
+ ("Errors", self.stats['errors']),
380
+ ]
381
+
382
+ for label, count in summary_items:
383
+ if count > 0:
384
+ style = self.style.SUCCESS if label != "Errors" else self.style.ERROR
385
+ self.stdout.write(f"{label:<22}: {style(count)}")
386
+
387
+ # Calculate total items processed
388
+ total_processed = (
389
+ self.stats['payments_removed'] +
390
+ self.stats['transactions_removed'] +
391
+ self.stats['api_keys_removed']
392
+ )
393
+
394
+ if total_processed > 0:
395
+ self.stdout.write("")
396
+ self.stdout.write(f"Total items processed: {self.style.SUCCESS(total_processed)}")
397
+
398
+ # Show completion time
399
+ self.stdout.write("")
400
+ self.stdout.write(f"Completed: {timezone.now().strftime('%Y-%m-%d %H:%M:%S UTC')}")
401
+
402
+ # Show recommendations
403
+ if self.stats['errors'] > 0:
404
+ self.stdout.write("")
405
+ self.stdout.write(
406
+ self.style.WARNING("⚠️ Some cleanup operations had errors. Check logs for details.")
407
+ )
408
+
409
+ if total_processed == 0 and self.stats['errors'] == 0:
410
+ self.stdout.write("")
411
+ self.stdout.write(
412
+ self.style.SUCCESS("✅ No expired data found. System is clean!")
413
+ )
414
+
415
+ if self.dry_run and total_processed > 0:
416
+ self.stdout.write("")
417
+ self.stdout.write(
418
+ self.style.SUCCESS("✅ Dry run completed. Run without --dry-run to apply changes.")
419
+ )