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
@@ -0,0 +1,434 @@
1
+ """
2
+ Test Providers Management Command for Universal Payment System v2.0.
3
+
4
+ Test payment provider connections, configurations, and functionality.
5
+ """
6
+
7
+ from typing import List, Dict, Any, Optional
8
+ from decimal import Decimal
9
+ import time
10
+
11
+ from django.core.management.base import BaseCommand, CommandError
12
+ from django.utils import timezone
13
+
14
+ from django_cfg.modules.django_logger import get_logger
15
+ from django_cfg.apps.payments.services.providers.registry import get_provider_registry
16
+ from django_cfg.apps.payments.models import Currency, Network
17
+ from django_cfg.apps.payments.services.types.requests import PaymentCreateRequest
18
+
19
+ logger = get_logger("test_providers")
20
+
21
+
22
+ class Command(BaseCommand):
23
+ """
24
+ Test payment provider connections and functionality.
25
+
26
+ Features:
27
+ - Test provider connectivity
28
+ - Validate configurations
29
+ - Test currency support
30
+ - Check API responses
31
+ - Performance testing
32
+ """
33
+
34
+ help = 'Test payment provider connections and functionality'
35
+
36
+ def add_arguments(self, parser):
37
+ """Add command line arguments."""
38
+ parser.add_argument(
39
+ '--provider',
40
+ type=str,
41
+ help='Test specific provider only'
42
+ )
43
+
44
+ parser.add_argument(
45
+ '--test-type',
46
+ choices=['connectivity', 'currencies', 'payment', 'all'],
47
+ default='all',
48
+ help='Type of test to run (default: all)'
49
+ )
50
+
51
+ parser.add_argument(
52
+ '--timeout',
53
+ type=int,
54
+ default=30,
55
+ help='Request timeout in seconds (default: 30)'
56
+ )
57
+
58
+ parser.add_argument(
59
+ '--verbose',
60
+ action='store_true',
61
+ help='Show detailed test information'
62
+ )
63
+
64
+ parser.add_argument(
65
+ '--create-test-payment',
66
+ action='store_true',
67
+ help='Create actual test payment (use with caution!)'
68
+ )
69
+
70
+ parser.add_argument(
71
+ '--test-amount',
72
+ type=float,
73
+ default=1.0,
74
+ help='Test payment amount in USD (default: 1.0)'
75
+ )
76
+
77
+ def handle(self, *args, **options):
78
+ """Execute the command."""
79
+ try:
80
+ self.options = options
81
+ self.verbose = options['verbose']
82
+
83
+ self.show_header()
84
+
85
+ # Get provider registry
86
+ self.provider_registry = get_provider_registry()
87
+
88
+ # Initialize test results
89
+ self.test_results = {}
90
+
91
+ # Get providers to test
92
+ if options['provider']:
93
+ providers = [options['provider']]
94
+ else:
95
+ providers = self.provider_registry.get_available_providers()
96
+
97
+ if not providers:
98
+ self.stdout.write(self.style.WARNING("No providers available to test"))
99
+ return
100
+
101
+ # Run tests
102
+ for provider_name in providers:
103
+ self.test_provider(provider_name)
104
+
105
+ self.show_summary()
106
+
107
+ except Exception as e:
108
+ logger.error(f"Test providers command failed: {e}")
109
+ raise CommandError(f"Failed to test providers: {e}")
110
+
111
+ def show_header(self):
112
+ """Display command header."""
113
+ self.stdout.write(
114
+ self.style.SUCCESS("=" * 60)
115
+ )
116
+ self.stdout.write(
117
+ self.style.SUCCESS("🧪 PAYMENT PROVIDER TESTING")
118
+ )
119
+ self.stdout.write(
120
+ self.style.SUCCESS("=" * 60)
121
+ )
122
+ self.stdout.write(f"Started: {timezone.now().strftime('%Y-%m-%d %H:%M:%S UTC')}")
123
+ self.stdout.write("")
124
+
125
+ def test_provider(self, provider_name: str):
126
+ """Test a specific provider."""
127
+ self.stdout.write(self.style.SUCCESS(f"🔍 TESTING PROVIDER: {provider_name.upper()}"))
128
+ self.stdout.write("-" * 50)
129
+
130
+ # Initialize provider results
131
+ self.test_results[provider_name] = {
132
+ 'connectivity': False,
133
+ 'currencies': False,
134
+ 'payment': False,
135
+ 'errors': [],
136
+ 'warnings': [],
137
+ 'performance': {}
138
+ }
139
+
140
+ try:
141
+ # Get provider instance
142
+ provider = self.provider_registry.get_provider(provider_name)
143
+ if not provider:
144
+ self.test_results[provider_name]['errors'].append("Provider not available")
145
+ self.stdout.write(self.style.ERROR(f"❌ Provider {provider_name} not available"))
146
+ return
147
+
148
+ # Run selected tests
149
+ test_type = self.options['test_type']
150
+
151
+ if test_type in ['connectivity', 'all']:
152
+ self.test_connectivity(provider_name, provider)
153
+
154
+ if test_type in ['currencies', 'all']:
155
+ self.test_currencies(provider_name, provider)
156
+
157
+ if test_type in ['payment', 'all'] and self.options['create_test_payment']:
158
+ self.test_payment_creation(provider_name, provider)
159
+
160
+ except Exception as e:
161
+ error_msg = f"Provider test failed: {e}"
162
+ self.test_results[provider_name]['errors'].append(error_msg)
163
+ logger.error(f"Error testing provider {provider_name}: {e}")
164
+ self.stdout.write(self.style.ERROR(f"❌ {error_msg}"))
165
+
166
+ self.stdout.write("")
167
+
168
+ def test_connectivity(self, provider_name: str, provider):
169
+ """Test provider connectivity."""
170
+ self.stdout.write(" 🌐 Testing connectivity...")
171
+
172
+ try:
173
+ start_time = time.time()
174
+
175
+ # Test basic connectivity (health check or similar)
176
+ if hasattr(provider, 'health_check'):
177
+ result = provider.health_check()
178
+ success = result.success if hasattr(result, 'success') else True
179
+ elif hasattr(provider, 'get_supported_currencies'):
180
+ # Fallback: try to get currencies as connectivity test
181
+ result = provider.get_supported_currencies()
182
+ success = len(result) > 0 if isinstance(result, list) else bool(result)
183
+ else:
184
+ # Last resort: assume connectivity is OK if provider exists
185
+ success = True
186
+
187
+ end_time = time.time()
188
+ response_time = (end_time - start_time) * 1000 # Convert to milliseconds
189
+
190
+ self.test_results[provider_name]['performance']['connectivity_ms'] = response_time
191
+
192
+ if success:
193
+ self.test_results[provider_name]['connectivity'] = True
194
+ self.stdout.write(f" ✅ Connectivity OK ({response_time:.0f}ms)")
195
+ else:
196
+ self.test_results[provider_name]['errors'].append("Connectivity test failed")
197
+ self.stdout.write(f" ❌ Connectivity failed")
198
+
199
+ except Exception as e:
200
+ error_msg = f"Connectivity test error: {e}"
201
+ self.test_results[provider_name]['errors'].append(error_msg)
202
+ self.stdout.write(f" ❌ Connectivity error: {e}")
203
+
204
+ def test_currencies(self, provider_name: str, provider):
205
+ """Test provider currency support."""
206
+ self.stdout.write(" 💰 Testing currency support...")
207
+
208
+ try:
209
+ start_time = time.time()
210
+
211
+ # Get supported currencies from provider
212
+ if hasattr(provider, 'get_supported_currencies'):
213
+ supported_currencies = provider.get_supported_currencies()
214
+ else:
215
+ self.test_results[provider_name]['warnings'].append("Provider doesn't support currency listing")
216
+ self.stdout.write(" ⚠️ Provider doesn't support currency listing")
217
+ return
218
+
219
+ end_time = time.time()
220
+ response_time = (end_time - start_time) * 1000
221
+
222
+ self.test_results[provider_name]['performance']['currencies_ms'] = response_time
223
+
224
+ if isinstance(supported_currencies, list) and len(supported_currencies) > 0:
225
+ self.test_results[provider_name]['currencies'] = True
226
+ currency_count = len(supported_currencies)
227
+ self.stdout.write(f" ✅ {currency_count} currencies supported ({response_time:.0f}ms)")
228
+
229
+ if self.verbose:
230
+ # Show first few currencies
231
+ sample_currencies = supported_currencies[:5]
232
+ currency_codes = [c.get('code', str(c)) for c in sample_currencies]
233
+ self.stdout.write(f" Sample: {', '.join(currency_codes)}")
234
+
235
+ # Check if our database currencies are supported
236
+ our_currencies = Currency.objects.filter(is_active=True)[:10]
237
+ supported_codes = []
238
+
239
+ if isinstance(supported_currencies[0], dict):
240
+ supported_codes = [c.get('code', '').upper() for c in supported_currencies]
241
+ else:
242
+ supported_codes = [str(c).upper() for c in supported_currencies]
243
+
244
+ matching_count = 0
245
+ for currency in our_currencies:
246
+ if currency.code.upper() in supported_codes:
247
+ matching_count += 1
248
+
249
+ if matching_count > 0:
250
+ self.stdout.write(f" ✅ {matching_count}/{our_currencies.count()} of our currencies supported")
251
+ else:
252
+ self.test_results[provider_name]['warnings'].append("No matching currencies found")
253
+ self.stdout.write(" ⚠️ No matching currencies found")
254
+
255
+ else:
256
+ self.test_results[provider_name]['errors'].append("No currencies returned")
257
+ self.stdout.write(" ❌ No currencies returned")
258
+
259
+ except Exception as e:
260
+ error_msg = f"Currency test error: {e}"
261
+ self.test_results[provider_name]['errors'].append(error_msg)
262
+ self.stdout.write(f" ❌ Currency test error: {e}")
263
+
264
+ def test_payment_creation(self, provider_name: str, provider):
265
+ """Test payment creation (creates actual test payment!)."""
266
+ self.stdout.write(" 💳 Testing payment creation...")
267
+ self.stdout.write(" ⚠️ WARNING: This creates an actual payment!")
268
+
269
+ try:
270
+ # Get a supported currency for testing
271
+ test_currency = self.get_test_currency(provider)
272
+ if not test_currency:
273
+ self.test_results[provider_name]['warnings'].append("No suitable test currency found")
274
+ self.stdout.write(" ⚠️ No suitable test currency found")
275
+ return
276
+
277
+ # Create test payment request
278
+ payment_request = PaymentCreateRequest(
279
+ amount_usd=self.options['test_amount'],
280
+ currency_code=test_currency,
281
+ description=f"Test payment from django-cfg ({timezone.now().isoformat()})",
282
+ callback_url="https://example.com/webhook/test",
283
+ success_url="https://example.com/success",
284
+ cancel_url="https://example.com/cancel"
285
+ )
286
+
287
+ start_time = time.time()
288
+
289
+ # Create payment
290
+ result = provider.create_payment(payment_request)
291
+
292
+ end_time = time.time()
293
+ response_time = (end_time - start_time) * 1000
294
+
295
+ self.test_results[provider_name]['performance']['payment_creation_ms'] = response_time
296
+
297
+ if hasattr(result, 'success') and result.success:
298
+ self.test_results[provider_name]['payment'] = True
299
+ payment_id = getattr(result, 'provider_payment_id', 'Unknown')
300
+ payment_url = getattr(result, 'payment_url', 'No URL')
301
+
302
+ self.stdout.write(f" ✅ Payment created ({response_time:.0f}ms)")
303
+ self.stdout.write(f" Payment ID: {payment_id}")
304
+
305
+ if self.verbose and payment_url != 'No URL':
306
+ self.stdout.write(f" Payment URL: {payment_url}")
307
+
308
+ # Test payment status check
309
+ if hasattr(provider, 'get_payment_status') and payment_id != 'Unknown':
310
+ try:
311
+ status_result = provider.get_payment_status(payment_id)
312
+ if hasattr(status_result, 'success') and status_result.success:
313
+ status = getattr(status_result, 'status', 'Unknown')
314
+ self.stdout.write(f" ✅ Status check OK: {status}")
315
+ else:
316
+ self.test_results[provider_name]['warnings'].append("Status check failed")
317
+ self.stdout.write(" ⚠️ Status check failed")
318
+ except Exception as e:
319
+ self.test_results[provider_name]['warnings'].append(f"Status check error: {e}")
320
+ self.stdout.write(f" ⚠️ Status check error: {e}")
321
+
322
+ else:
323
+ error = getattr(result, 'error', 'Unknown error')
324
+ self.test_results[provider_name]['errors'].append(f"Payment creation failed: {error}")
325
+ self.stdout.write(f" ❌ Payment creation failed: {error}")
326
+
327
+ except Exception as e:
328
+ error_msg = f"Payment creation error: {e}"
329
+ self.test_results[provider_name]['errors'].append(error_msg)
330
+ self.stdout.write(f" ❌ Payment creation error: {e}")
331
+
332
+ def get_test_currency(self, provider) -> Optional[str]:
333
+ """Get a suitable currency for testing."""
334
+ # Common test currencies
335
+ test_currencies = ['BTC', 'ETH', 'USDT', 'LTC', 'USD']
336
+
337
+ try:
338
+ # Get supported currencies from provider
339
+ if hasattr(provider, 'get_supported_currencies'):
340
+ supported = provider.get_supported_currencies()
341
+
342
+ if isinstance(supported, list) and len(supported) > 0:
343
+ # Extract currency codes
344
+ if isinstance(supported[0], dict):
345
+ supported_codes = [c.get('code', '').upper() for c in supported]
346
+ else:
347
+ supported_codes = [str(c).upper() for c in supported]
348
+
349
+ # Find first matching test currency
350
+ for currency in test_currencies:
351
+ if currency in supported_codes:
352
+ return currency
353
+
354
+ # Fallback: return first supported currency
355
+ if supported_codes:
356
+ return supported_codes[0]
357
+
358
+ # Last resort: try BTC
359
+ return 'BTC'
360
+
361
+ except Exception:
362
+ return 'BTC'
363
+
364
+ def show_summary(self):
365
+ """Display test summary."""
366
+ self.stdout.write(self.style.SUCCESS("📊 TEST SUMMARY"))
367
+ self.stdout.write("-" * 40)
368
+
369
+ total_providers = len(self.test_results)
370
+ successful_providers = 0
371
+ total_errors = 0
372
+ total_warnings = 0
373
+
374
+ for provider_name, results in self.test_results.items():
375
+ errors = len(results['errors'])
376
+ warnings = len(results['warnings'])
377
+
378
+ total_errors += errors
379
+ total_warnings += warnings
380
+
381
+ # Count as successful if no errors and at least one test passed
382
+ if errors == 0 and (results['connectivity'] or results['currencies']):
383
+ successful_providers += 1
384
+
385
+ # Show provider summary
386
+ status_icon = "✅" if errors == 0 else "❌"
387
+ self.stdout.write(f"{status_icon} {provider_name}:")
388
+
389
+ if results['connectivity']:
390
+ self.stdout.write(" ✅ Connectivity")
391
+ if results['currencies']:
392
+ self.stdout.write(" ✅ Currencies")
393
+ if results['payment']:
394
+ self.stdout.write(" ✅ Payment Creation")
395
+
396
+ if errors > 0:
397
+ self.stdout.write(f" ❌ {errors} error(s)")
398
+ if warnings > 0:
399
+ self.stdout.write(f" ⚠️ {warnings} warning(s)")
400
+
401
+ # Show performance metrics
402
+ if self.verbose and results['performance']:
403
+ perf = results['performance']
404
+ if 'connectivity_ms' in perf:
405
+ self.stdout.write(f" ⏱️ Connectivity: {perf['connectivity_ms']:.0f}ms")
406
+ if 'currencies_ms' in perf:
407
+ self.stdout.write(f" ⏱️ Currencies: {perf['currencies_ms']:.0f}ms")
408
+ if 'payment_creation_ms' in perf:
409
+ self.stdout.write(f" ⏱️ Payment: {perf['payment_creation_ms']:.0f}ms")
410
+
411
+ # Overall summary
412
+ self.stdout.write("")
413
+ self.stdout.write(f"Providers tested: {total_providers}")
414
+ self.stdout.write(f"Successful: {self.style.SUCCESS(successful_providers)}")
415
+ self.stdout.write(f"Failed: {self.style.ERROR(total_providers - successful_providers)}")
416
+ self.stdout.write(f"Total errors: {self.style.ERROR(total_errors)}")
417
+ self.stdout.write(f"Total warnings: {self.style.WARNING(total_warnings)}")
418
+
419
+ # Show completion time
420
+ self.stdout.write("")
421
+ self.stdout.write(f"Completed: {timezone.now().strftime('%Y-%m-%d %H:%M:%S UTC')}")
422
+
423
+ # Recommendations
424
+ if total_errors > 0:
425
+ self.stdout.write("")
426
+ self.stdout.write(
427
+ self.style.WARNING("⚠️ Some providers have errors. Check configurations and network connectivity.")
428
+ )
429
+
430
+ if successful_providers == total_providers and total_errors == 0:
431
+ self.stdout.write("")
432
+ self.stdout.write(
433
+ self.style.SUCCESS("🎉 All providers tested successfully!")
434
+ )
@@ -239,6 +239,9 @@ class Transaction(UUIDTimestampedModel):
239
239
 
240
240
  def save(self, *args, **kwargs):
241
241
  """Override save to ensure immutability."""
242
- if self.pk:
243
- raise ValidationError("Transactions are immutable and cannot be modified")
242
+ # Only prevent updates, not creation
243
+ if self.pk and not kwargs.get('force_insert', False):
244
+ # Check if this is actually an update (record exists in DB)
245
+ if Transaction.objects.filter(pk=self.pk).exists():
246
+ raise ValidationError("Transactions are immutable and cannot be modified")
244
247
  super().save(*args, **kwargs)
@@ -230,7 +230,7 @@ class APIKeyManager(models.Manager):
230
230
  logger.info(f"Created API key for user", extra={
231
231
  'api_key_id': str(api_key.id),
232
232
  'user_id': user.id,
233
- 'name': name,
233
+ 'key_name': name,
234
234
  'expires_in_days': expires_in_days
235
235
  })
236
236
 
@@ -239,7 +239,7 @@ class APIKeyManager(models.Manager):
239
239
  except Exception as e:
240
240
  logger.error(f"Failed to create API key: {e}", extra={
241
241
  'user_id': user.id,
242
- 'name': name
242
+ 'key_name': name
243
243
  })
244
244
  raise
245
245
 
@@ -4,7 +4,7 @@ Balance and transaction managers for the Universal Payment System v2.0.
4
4
  Optimized querysets and managers for balance and transaction operations.
5
5
  """
6
6
 
7
- from django.db import models
7
+ from django.db import models, transaction
8
8
  from django.utils import timezone
9
9
  from django_cfg.modules.django_logger import get_logger
10
10
 
@@ -62,7 +62,7 @@ class UserBalanceManager(models.Manager):
62
62
  # Get or create balance
63
63
  balance = self.get_or_create_for_user(user)
64
64
 
65
- with models.transaction.atomic():
65
+ with transaction.atomic():
66
66
  # Update balance
67
67
  balance.balance_usd += amount
68
68
  balance.total_deposited += amount
@@ -122,7 +122,7 @@ class UserBalanceManager(models.Manager):
122
122
  if amount > balance.balance_usd:
123
123
  raise ValueError(f"Insufficient balance: ${balance.balance_usd:.2f} < ${amount:.2f}")
124
124
 
125
- with models.transaction.atomic():
125
+ with transaction.atomic():
126
126
  # Update balance
127
127
  balance.balance_usd -= amount
128
128
  balance.total_spent += amount
@@ -544,7 +544,7 @@ class SubscriptionManager(models.Manager):
544
544
  else:
545
545
  subscription = subscription_id
546
546
 
547
- subscription.status = subscription.model.SubscriptionStatus.CANCELLED
547
+ subscription.status = self.model.SubscriptionStatus.CANCELLED
548
548
  subscription.save(update_fields=['status', 'updated_at'])
549
549
 
550
550
  logger.info(f"Subscription cancelled", extra={
@@ -580,7 +580,7 @@ class SubscriptionManager(models.Manager):
580
580
 
581
581
  from datetime import timedelta
582
582
 
583
- if subscription.is_expired:
583
+ if subscription.expires_at <= timezone.now():
584
584
  # If expired, start from now
585
585
  subscription.starts_at = timezone.now()
586
586
  subscription.expires_at = subscription.starts_at + timedelta(days=duration_days)
@@ -588,7 +588,7 @@ class SubscriptionManager(models.Manager):
588
588
  # If not expired, extend from current expiration
589
589
  subscription.expires_at += timedelta(days=duration_days)
590
590
 
591
- subscription.status = subscription.model.SubscriptionStatus.ACTIVE
591
+ subscription.status = self.model.SubscriptionStatus.ACTIVE
592
592
  subscription.save(update_fields=['starts_at', 'expires_at', 'status', 'updated_at'])
593
593
 
594
594
  logger.info(f"Subscription renewed", extra={
@@ -0,0 +1,143 @@
1
+ """
2
+ Cache services for the Universal Payment System v2.0.
3
+
4
+ Main entry point for cache functionality.
5
+ Based on proven solutions from payments_old with improvements.
6
+ ONLY for API access control - NOT payment data!
7
+ """
8
+
9
+ from typing import Dict, Any
10
+ from django.core.cache import cache
11
+ from django_cfg.modules.django_logger import get_logger
12
+
13
+ from .interfaces import CacheInterface
14
+ from .simple_cache import SimpleCache
15
+ from .api_key_cache import ApiKeyCache
16
+ from .rate_limit_cache import RateLimitCache
17
+ from .keys import CacheKeys
18
+
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ class CacheService:
23
+ """
24
+ Main cache service providing access to specialized caches.
25
+
26
+ Provides centralized access to different cache types.
27
+ """
28
+
29
+ def __init__(self):
30
+ """Initialize cache service with specialized caches."""
31
+ self.simple_cache = SimpleCache()
32
+ self.api_key_cache = ApiKeyCache()
33
+ self.rate_limit_cache = RateLimitCache()
34
+ # Backward compatibility attributes
35
+ self.default_timeout = 300
36
+ self.key_prefix = "payments"
37
+
38
+ # Backward compatibility methods - delegate to simple_cache
39
+ def get(self, key: str):
40
+ """Get value from cache."""
41
+ return self.simple_cache.get(key)
42
+
43
+ def set(self, key: str, value, timeout=None):
44
+ """Set value in cache."""
45
+ return self.simple_cache.set(key, value, timeout)
46
+
47
+ def delete(self, key: str):
48
+ """Delete value from cache."""
49
+ return self.simple_cache.delete(key)
50
+
51
+ def exists(self, key: str):
52
+ """Check if key exists in cache."""
53
+ return self.simple_cache.exists(key)
54
+
55
+ def get_or_set(self, key: str, default, timeout=None):
56
+ """Get value or set default if not exists."""
57
+ value = self.get(key)
58
+ if value is None:
59
+ if callable(default):
60
+ value = default()
61
+ else:
62
+ value = default
63
+ self.set(key, value, timeout)
64
+ return value
65
+
66
+ def set_many(self, data: dict, timeout=None):
67
+ """Set multiple values."""
68
+ for key, value in data.items():
69
+ self.set(key, value, timeout)
70
+
71
+ def get_many(self, keys: list):
72
+ """Get multiple values."""
73
+ result = {}
74
+ for key in keys:
75
+ value = self.get(key)
76
+ if value is not None:
77
+ result[key] = value
78
+ return result
79
+
80
+ def delete_many(self, keys: list):
81
+ """Delete multiple values."""
82
+ for key in keys:
83
+ self.delete(key)
84
+
85
+ def clear(self):
86
+ """Clear all cache (not implemented for safety)."""
87
+ # For safety, we don't implement cache.clear()
88
+ # as it would clear the entire cache backend
89
+ # Instead, we clear Django's cache which is safe for tests
90
+ from django.core.cache import cache
91
+ cache.clear()
92
+
93
+ def health_check(self) -> Dict[str, Any]:
94
+ """Check cache health."""
95
+ try:
96
+ test_key = "health_check"
97
+ test_value = "ok"
98
+
99
+ # Test set/get/delete
100
+ self.simple_cache.set(test_key, test_value, 10)
101
+ retrieved = self.simple_cache.get(test_key)
102
+ self.simple_cache.delete(test_key)
103
+
104
+ is_healthy = retrieved == test_value
105
+
106
+ return {
107
+ 'healthy': is_healthy,
108
+ 'backend': cache.__class__.__name__,
109
+ 'simple_cache': True,
110
+ 'api_key_cache': True,
111
+ 'rate_limit_cache': True
112
+ }
113
+ except Exception as e:
114
+ logger.error(f"Cache health check failed: {e}")
115
+ return {
116
+ 'healthy': False,
117
+ 'error': str(e),
118
+ 'backend': cache.__class__.__name__
119
+ }
120
+
121
+
122
+ # Global cache service instance
123
+ _cache_service = None
124
+
125
+
126
+ def get_cache_service() -> CacheService:
127
+ """Get global cache service instance."""
128
+ global _cache_service
129
+ if _cache_service is None:
130
+ _cache_service = CacheService()
131
+ return _cache_service
132
+
133
+
134
+ # Export main classes for backward compatibility
135
+ __all__ = [
136
+ 'CacheService',
137
+ 'CacheInterface',
138
+ 'SimpleCache',
139
+ 'ApiKeyCache',
140
+ 'RateLimitCache',
141
+ 'CacheKeys',
142
+ 'get_cache_service'
143
+ ]