django-cfg 1.2.23__py3-none-any.whl → 1.2.25__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 (85) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/knowbase/tasks/archive_tasks.py +6 -6
  3. django_cfg/apps/knowbase/tasks/document_processing.py +3 -3
  4. django_cfg/apps/knowbase/tasks/external_data_tasks.py +2 -2
  5. django_cfg/apps/knowbase/tasks/maintenance.py +3 -3
  6. django_cfg/apps/payments/config/__init__.py +15 -37
  7. django_cfg/apps/payments/config/module.py +30 -122
  8. django_cfg/apps/payments/config/providers.py +22 -0
  9. django_cfg/apps/payments/config/settings.py +53 -93
  10. django_cfg/apps/payments/config/utils.py +10 -156
  11. django_cfg/apps/payments/management/__init__.py +3 -0
  12. django_cfg/apps/payments/management/commands/README.md +178 -0
  13. django_cfg/apps/payments/management/commands/__init__.py +3 -0
  14. django_cfg/apps/payments/management/commands/currency_stats.py +323 -0
  15. django_cfg/apps/payments/management/commands/populate_currencies.py +246 -0
  16. django_cfg/apps/payments/management/commands/update_currencies.py +336 -0
  17. django_cfg/apps/payments/managers/currency_manager.py +65 -14
  18. django_cfg/apps/payments/middleware/api_access.py +33 -0
  19. django_cfg/apps/payments/migrations/0001_initial.py +94 -1
  20. django_cfg/apps/payments/models/payments.py +110 -0
  21. django_cfg/apps/payments/services/__init__.py +7 -1
  22. django_cfg/apps/payments/services/core/balance_service.py +14 -16
  23. django_cfg/apps/payments/services/core/fallback_service.py +432 -0
  24. django_cfg/apps/payments/services/core/payment_service.py +212 -29
  25. django_cfg/apps/payments/services/core/subscription_service.py +15 -17
  26. django_cfg/apps/payments/services/internal_types.py +31 -0
  27. django_cfg/apps/payments/services/monitoring/__init__.py +22 -0
  28. django_cfg/apps/payments/services/monitoring/api_schemas.py +222 -0
  29. django_cfg/apps/payments/services/monitoring/provider_health.py +372 -0
  30. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  31. django_cfg/apps/payments/services/providers/cryptapi.py +14 -3
  32. django_cfg/apps/payments/services/providers/cryptomus.py +310 -0
  33. django_cfg/apps/payments/services/providers/registry.py +4 -0
  34. django_cfg/apps/payments/services/security/__init__.py +34 -0
  35. django_cfg/apps/payments/services/security/error_handler.py +637 -0
  36. django_cfg/apps/payments/services/security/payment_notifications.py +342 -0
  37. django_cfg/apps/payments/services/security/webhook_validator.py +475 -0
  38. django_cfg/apps/payments/signals/api_key_signals.py +10 -0
  39. django_cfg/apps/payments/signals/payment_signals.py +3 -2
  40. django_cfg/apps/payments/tasks/__init__.py +12 -0
  41. django_cfg/apps/payments/tasks/webhook_processing.py +177 -0
  42. django_cfg/apps/payments/utils/__init__.py +7 -4
  43. django_cfg/apps/payments/utils/billing_utils.py +342 -0
  44. django_cfg/apps/payments/utils/config_utils.py +2 -0
  45. django_cfg/apps/payments/views/payment_views.py +40 -2
  46. django_cfg/apps/payments/views/webhook_views.py +266 -0
  47. django_cfg/apps/payments/viewsets.py +65 -0
  48. django_cfg/cli/README.md +2 -2
  49. django_cfg/cli/commands/create_project.py +1 -1
  50. django_cfg/cli/commands/info.py +1 -1
  51. django_cfg/cli/main.py +1 -1
  52. django_cfg/cli/utils.py +5 -5
  53. django_cfg/core/config.py +18 -4
  54. django_cfg/models/payments.py +546 -0
  55. django_cfg/models/tasks.py +51 -2
  56. django_cfg/modules/base.py +11 -5
  57. django_cfg/modules/django_currency/README.md +104 -269
  58. django_cfg/modules/django_currency/__init__.py +99 -41
  59. django_cfg/modules/django_currency/clients/__init__.py +11 -0
  60. django_cfg/modules/django_currency/clients/coingecko_client.py +257 -0
  61. django_cfg/modules/django_currency/clients/yfinance_client.py +246 -0
  62. django_cfg/modules/django_currency/core/__init__.py +42 -0
  63. django_cfg/modules/django_currency/core/converter.py +169 -0
  64. django_cfg/modules/django_currency/core/exceptions.py +28 -0
  65. django_cfg/modules/django_currency/core/models.py +54 -0
  66. django_cfg/modules/django_currency/database/__init__.py +25 -0
  67. django_cfg/modules/django_currency/database/database_loader.py +507 -0
  68. django_cfg/modules/django_currency/utils/__init__.py +9 -0
  69. django_cfg/modules/django_currency/utils/cache.py +92 -0
  70. django_cfg/registry/core.py +10 -0
  71. django_cfg/template_archive/__init__.py +0 -0
  72. django_cfg/template_archive/django_sample.zip +0 -0
  73. {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/METADATA +10 -6
  74. {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/RECORD +77 -51
  75. django_cfg/apps/agents/examples/__init__.py +0 -3
  76. django_cfg/apps/agents/examples/simple_example.py +0 -161
  77. django_cfg/apps/knowbase/examples/__init__.py +0 -3
  78. django_cfg/apps/knowbase/examples/external_data_usage.py +0 -191
  79. django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +0 -199
  80. django_cfg/modules/django_currency/cache.py +0 -430
  81. django_cfg/modules/django_currency/converter.py +0 -324
  82. django_cfg/modules/django_currency/service.py +0 -277
  83. {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/WHEEL +0 -0
  84. {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/entry_points.txt +0 -0
  85. {django_cfg-1.2.23.dist-info → django_cfg-1.2.25.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,507 @@
1
+ """
2
+ Database loader for populating currency data from external APIs.
3
+ """
4
+
5
+ import logging
6
+ import time
7
+ from datetime import datetime, timedelta
8
+ from typing import List, Dict, Optional, Set
9
+ from dataclasses import dataclass
10
+
11
+ from pydantic import BaseModel, Field, validator
12
+ from cachetools import TTLCache
13
+
14
+ # CoinGecko API
15
+ from pycoingecko import CoinGeckoAPI
16
+ import yfinance as yf
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ # ============================================================================
22
+ # PYDANTIC MODELS
23
+ # ============================================================================
24
+
25
+ class CoinGeckoCoinInfo(BaseModel):
26
+ """Single coin information from CoinGecko."""
27
+ id: str = Field(description="CoinGecko coin ID")
28
+ symbol: str = Field(description="Currency symbol (e.g., BTC)")
29
+ name: str = Field(description="Full coin name")
30
+
31
+
32
+ class YFinanceCurrencyInfo(BaseModel):
33
+ """Single fiat currency information."""
34
+ code: str = Field(description="Currency code (e.g., USD)")
35
+ name: str = Field(description="Full currency name")
36
+ symbol: str = Field(default="", description="Currency symbol (e.g., $)")
37
+
38
+
39
+ class CurrencyRateInfo(BaseModel):
40
+ """Currency rate information for database."""
41
+ code: str = Field(description="Currency code")
42
+ name: str = Field(description="Full currency name")
43
+ symbol: str = Field(description="Currency symbol")
44
+ currency_type: str = Field(description="fiat or crypto")
45
+ decimal_places: int = Field(default=2, description="Decimal places")
46
+ usd_rate: float = Field(description="Rate to USD")
47
+ min_payment_amount: float = Field(default=1.0, description="Minimum payment amount")
48
+ is_active: bool = Field(default=True, description="Is currency active")
49
+
50
+
51
+ class DatabaseLoaderConfig(BaseModel):
52
+ """Configuration for database loader."""
53
+
54
+ # Rate limiting
55
+ coingecko_delay: float = Field(default=1.5, description="Delay between CoinGecko requests (seconds)")
56
+ yfinance_delay: float = Field(default=0.5, description="Delay between YFinance requests (seconds)")
57
+ max_requests_per_minute: int = Field(default=30, description="Max requests per minute")
58
+
59
+ # Limits
60
+ max_cryptocurrencies: int = Field(default=500, description="Max cryptocurrencies to load")
61
+ max_fiat_currencies: int = Field(default=50, description="Max fiat currencies to load")
62
+
63
+ # Filtering
64
+ min_market_cap_usd: float = Field(default=1000000, description="Minimum market cap in USD")
65
+ exclude_stablecoins: bool = Field(default=False, description="Exclude stablecoins")
66
+
67
+ # Cache
68
+ cache_ttl_hours: int = Field(default=24, description="Cache TTL in hours")
69
+
70
+
71
+ # ============================================================================
72
+ # RATE LIMITER
73
+ # ============================================================================
74
+
75
+ @dataclass
76
+ class RateLimiter:
77
+ """Simple rate limiter to prevent API throttling."""
78
+
79
+ def __init__(self, max_requests_per_minute: int = 30):
80
+ self.max_requests = max_requests_per_minute
81
+ self.requests = []
82
+ self.last_request_time = 0.0
83
+
84
+ def wait_if_needed(self, delay: float = 1.0):
85
+ """Wait if necessary to respect rate limits."""
86
+ current_time = time.time()
87
+
88
+ # Remove old requests (older than 1 minute)
89
+ self.requests = [req_time for req_time in self.requests
90
+ if current_time - req_time < 60]
91
+
92
+ # Check if we've hit the rate limit
93
+ if len(self.requests) >= self.max_requests:
94
+ sleep_time = 60 - (current_time - self.requests[0])
95
+ if sleep_time > 0:
96
+ logger.info(f"Rate limit reached, sleeping for {sleep_time:.2f}s")
97
+ time.sleep(sleep_time)
98
+
99
+ # Check minimum delay between requests
100
+ time_since_last = current_time - self.last_request_time
101
+ if time_since_last < delay:
102
+ sleep_time = delay - time_since_last
103
+ logger.debug(f"Rate limiting: sleeping for {sleep_time:.2f}s")
104
+ time.sleep(sleep_time)
105
+
106
+ # Record this request
107
+ self.requests.append(time.time())
108
+ self.last_request_time = time.time()
109
+
110
+
111
+ # ============================================================================
112
+ # DATABASE LOADER
113
+ # ============================================================================
114
+
115
+ class CurrencyDatabaseLoader:
116
+ """
117
+ Typed tool for loading currency data into database.
118
+
119
+ Features:
120
+ - Rate limiting to prevent API throttling
121
+ - Configurable limits on number of currencies
122
+ - Market cap filtering for cryptocurrencies
123
+ - Caching to avoid repeated API calls
124
+ - Type safety with Pydantic models
125
+ """
126
+
127
+ def __init__(self, config: DatabaseLoaderConfig = None):
128
+ """Initialize the database loader."""
129
+ self.config = config or DatabaseLoaderConfig()
130
+
131
+ # Initialize API clients
132
+ self.coingecko = CoinGeckoAPI()
133
+
134
+ # Rate limiters
135
+ self.coingecko_limiter = RateLimiter(self.config.max_requests_per_minute)
136
+ self.yfinance_limiter = RateLimiter(self.config.max_requests_per_minute)
137
+
138
+ # Cache
139
+ cache_ttl = self.config.cache_ttl_hours * 3600
140
+ self.crypto_cache = TTLCache(maxsize=10, ttl=cache_ttl)
141
+ self.fiat_cache = TTLCache(maxsize=10, ttl=cache_ttl)
142
+
143
+ logger.info(f"Initialized CurrencyDatabaseLoader with config: {self.config}")
144
+
145
+ def get_cryptocurrency_list(self) -> List[CoinGeckoCoinInfo]:
146
+ """
147
+ Get list of cryptocurrencies from CoinGecko with filtering.
148
+
149
+ Returns:
150
+ List of cryptocurrency information
151
+ """
152
+ cache_key = "crypto_list"
153
+ if cache_key in self.crypto_cache:
154
+ logger.debug("Retrieved cryptocurrency list from cache")
155
+ return self.crypto_cache[cache_key]
156
+
157
+ logger.info("Fetching cryptocurrency list from CoinGecko...")
158
+
159
+ try:
160
+ # Get coins with market data for filtering
161
+ self.coingecko_limiter.wait_if_needed(self.config.coingecko_delay)
162
+
163
+ coins_markets = self.coingecko.get_coins_markets(
164
+ vs_currency='usd',
165
+ order='market_cap_desc',
166
+ per_page=self.config.max_cryptocurrencies,
167
+ page=1,
168
+ sparkline=False
169
+ )
170
+
171
+ cryptocurrencies = []
172
+ for coin in coins_markets:
173
+ # Filter by market cap
174
+ market_cap = coin.get('market_cap', 0) or 0
175
+ if market_cap < self.config.min_market_cap_usd:
176
+ continue
177
+
178
+ # Filter stablecoins if requested
179
+ if self.config.exclude_stablecoins:
180
+ categories = coin.get('categories', []) or []
181
+ if any('stablecoin' in str(cat).lower() for cat in categories):
182
+ continue
183
+
184
+ crypto_info = CoinGeckoCoinInfo(
185
+ id=coin['id'],
186
+ symbol=coin['symbol'].upper(),
187
+ name=coin['name']
188
+ )
189
+ cryptocurrencies.append(crypto_info)
190
+
191
+ logger.info(f"Loaded {len(cryptocurrencies)} cryptocurrencies")
192
+ self.crypto_cache[cache_key] = cryptocurrencies
193
+ return cryptocurrencies
194
+
195
+ except Exception as e:
196
+ logger.error(f"Failed to fetch cryptocurrency list: {e}")
197
+ raise
198
+
199
+ def get_fiat_currency_list(self) -> List[YFinanceCurrencyInfo]:
200
+ """
201
+ Get list of fiat currencies.
202
+
203
+ Returns:
204
+ List of fiat currency information
205
+ """
206
+ cache_key = "fiat_list"
207
+ if cache_key in self.fiat_cache:
208
+ logger.debug("Retrieved fiat currency list from cache")
209
+ return self.fiat_cache[cache_key]
210
+
211
+ logger.info("Building fiat currency list...")
212
+
213
+ # Major fiat currencies with their symbols
214
+ fiat_currencies_data = [
215
+ ("USD", "US Dollar", "$"),
216
+ ("EUR", "Euro", "€"),
217
+ ("GBP", "British Pound", "£"),
218
+ ("JPY", "Japanese Yen", "¥"),
219
+ ("CNY", "Chinese Yuan", "¥"),
220
+ ("KRW", "South Korean Won", "₩"),
221
+ ("CAD", "Canadian Dollar", "C$"),
222
+ ("AUD", "Australian Dollar", "A$"),
223
+ ("CHF", "Swiss Franc", "₣"),
224
+ ("RUB", "Russian Ruble", "₽"),
225
+ ("BRL", "Brazilian Real", "R$"),
226
+ ("INR", "Indian Rupee", "₹"),
227
+ ("MXN", "Mexican Peso", "$"),
228
+ ("SGD", "Singapore Dollar", "S$"),
229
+ ("HKD", "Hong Kong Dollar", "HK$"),
230
+ ("SEK", "Swedish Krona", "kr"),
231
+ ("NOK", "Norwegian Krone", "kr"),
232
+ ("DKK", "Danish Krone", "kr"),
233
+ ("PLN", "Polish Zloty", "zł"),
234
+ ("CZK", "Czech Koruna", "Kč"),
235
+ ("HUF", "Hungarian Forint", "Ft"),
236
+ ("TRY", "Turkish Lira", "₺"),
237
+ ("ZAR", "South African Rand", "R"),
238
+ ("THB", "Thai Baht", "฿"),
239
+ ("MYR", "Malaysian Ringgit", "RM"),
240
+ ("PHP", "Philippine Peso", "₱"),
241
+ ("IDR", "Indonesian Rupiah", "Rp"),
242
+ ("VND", "Vietnamese Dong", "₫"),
243
+ ("TWD", "Taiwan Dollar", "NT$"),
244
+ ("NZD", "New Zealand Dollar", "NZ$")
245
+ ]
246
+
247
+ fiat_currencies = []
248
+ for code, name, symbol in fiat_currencies_data[:self.config.max_fiat_currencies]:
249
+ fiat_info = YFinanceCurrencyInfo(
250
+ code=code,
251
+ name=name,
252
+ symbol=symbol
253
+ )
254
+ fiat_currencies.append(fiat_info)
255
+
256
+ logger.info(f"Built list of {len(fiat_currencies)} fiat currencies")
257
+ self.fiat_cache[cache_key] = fiat_currencies
258
+ return fiat_currencies
259
+
260
+ def get_currency_rates(self, currencies: List[str], vs_currency: str = "usd") -> Dict[str, float]:
261
+ """
262
+ Get current rates for multiple currencies.
263
+
264
+ Args:
265
+ currencies: List of currency codes/IDs
266
+ vs_currency: Quote currency (default: usd)
267
+
268
+ Returns:
269
+ Dictionary mapping currency to its rate
270
+ """
271
+ if not currencies:
272
+ return {}
273
+
274
+ logger.info(f"Fetching rates for {len(currencies)} currencies vs {vs_currency}")
275
+
276
+ try:
277
+ # Split into chunks to avoid hitting API limits
278
+ chunk_size = 50
279
+ all_rates = {}
280
+
281
+ for i in range(0, len(currencies), chunk_size):
282
+ chunk = currencies[i:i + chunk_size]
283
+
284
+ self.coingecko_limiter.wait_if_needed(self.config.coingecko_delay)
285
+
286
+ # Join currency IDs for batch request
287
+ ids_str = ','.join(chunk)
288
+
289
+ price_data = self.coingecko.get_price(
290
+ ids=ids_str,
291
+ vs_currencies=vs_currency,
292
+ include_last_updated_at=True
293
+ )
294
+
295
+ # Extract rates
296
+ for currency_id, data in price_data.items():
297
+ if vs_currency in data:
298
+ all_rates[currency_id] = float(data[vs_currency])
299
+
300
+ logger.debug(f"Fetched rates for chunk {i//chunk_size + 1}")
301
+
302
+ logger.info(f"Successfully fetched {len(all_rates)} currency rates")
303
+ return all_rates
304
+
305
+ except Exception as e:
306
+ logger.error(f"Failed to fetch currency rates: {e}")
307
+ raise
308
+
309
+ def get_fiat_rate(self, base_currency: str, quote_currency: str = "USD") -> Optional[float]:
310
+ """
311
+ Get fiat currency rate using YFinance.
312
+
313
+ Args:
314
+ base_currency: Base currency code
315
+ quote_currency: Quote currency code
316
+
317
+ Returns:
318
+ Exchange rate or None if not available
319
+ """
320
+ try:
321
+ self.yfinance_limiter.wait_if_needed(self.config.yfinance_delay)
322
+
323
+ if base_currency == quote_currency:
324
+ return 1.0
325
+
326
+ symbol = f"{base_currency}{quote_currency}=X"
327
+ ticker = yf.Ticker(symbol)
328
+
329
+ # Try to get current price
330
+ info = ticker.info
331
+ if 'regularMarketPrice' in info and info['regularMarketPrice']:
332
+ return float(info['regularMarketPrice'])
333
+
334
+ # Fallback to history
335
+ hist = ticker.history(period="1d")
336
+ if not hist.empty:
337
+ return float(hist['Close'].iloc[-1])
338
+
339
+ return None
340
+
341
+ except Exception as e:
342
+ logger.debug(f"Failed to get fiat rate for {base_currency}/{quote_currency}: {e}")
343
+ return None
344
+
345
+ def build_currency_database_data(self) -> List[CurrencyRateInfo]:
346
+ """
347
+ Build complete currency data for database insertion.
348
+
349
+ Returns:
350
+ List of currency rate information ready for database
351
+ """
352
+ logger.info("Building complete currency database data...")
353
+
354
+ all_currencies = []
355
+
356
+ # 1. Get fiat currencies
357
+ logger.info("Processing fiat currencies...")
358
+ fiat_currencies = self.get_fiat_currency_list()
359
+
360
+ for fiat in fiat_currencies:
361
+ # Get USD rate (skip USD itself)
362
+ if fiat.code == "USD":
363
+ usd_rate = 1.0
364
+ else:
365
+ usd_rate = self.get_fiat_rate(fiat.code, "USD")
366
+ if usd_rate is None:
367
+ logger.warning(f"Could not get USD rate for {fiat.code}, skipping")
368
+ continue
369
+
370
+ currency_info = CurrencyRateInfo(
371
+ code=fiat.code,
372
+ name=fiat.name,
373
+ symbol=fiat.symbol,
374
+ currency_type="fiat",
375
+ decimal_places=2,
376
+ usd_rate=usd_rate,
377
+ min_payment_amount=1.0,
378
+ is_active=True
379
+ )
380
+ all_currencies.append(currency_info)
381
+
382
+ logger.debug(f"Added fiat currency: {fiat.code} = {usd_rate} USD")
383
+
384
+ # 2. Get cryptocurrencies
385
+ logger.info("Processing cryptocurrencies...")
386
+ crypto_currencies = self.get_cryptocurrency_list()
387
+
388
+ # Get rates in batches
389
+ crypto_ids = [crypto.id for crypto in crypto_currencies]
390
+ crypto_rates = self.get_currency_rates(crypto_ids, "usd")
391
+
392
+ for crypto in crypto_currencies:
393
+ if crypto.id not in crypto_rates:
394
+ logger.warning(f"No USD rate found for {crypto.symbol}, skipping")
395
+ continue
396
+
397
+ usd_rate = crypto_rates[crypto.id]
398
+
399
+ # Determine decimal places based on price
400
+ if usd_rate >= 1:
401
+ decimal_places = 2
402
+ elif usd_rate >= 0.01:
403
+ decimal_places = 4
404
+ else:
405
+ decimal_places = 8
406
+
407
+ # Determine minimum payment amount
408
+ if usd_rate >= 100:
409
+ min_payment = 0.001
410
+ elif usd_rate >= 1:
411
+ min_payment = 0.01
412
+ else:
413
+ min_payment = 1.0
414
+
415
+ currency_info = CurrencyRateInfo(
416
+ code=crypto.symbol,
417
+ name=crypto.name,
418
+ symbol=crypto.symbol, # Use symbol as symbol for crypto
419
+ currency_type="crypto",
420
+ decimal_places=decimal_places,
421
+ usd_rate=usd_rate,
422
+ min_payment_amount=min_payment,
423
+ is_active=True
424
+ )
425
+ all_currencies.append(currency_info)
426
+
427
+ logger.debug(f"Added cryptocurrency: {crypto.symbol} = {usd_rate} USD")
428
+
429
+ logger.info(f"Built currency data for {len(all_currencies)} currencies "
430
+ f"({len(fiat_currencies)} fiat, {len(crypto_currencies)} crypto)")
431
+
432
+ return all_currencies
433
+
434
+ def get_statistics(self) -> Dict[str, int]:
435
+ """Get statistics about available currencies."""
436
+ fiat_count = len(self.get_fiat_currency_list())
437
+ crypto_count = len(self.get_cryptocurrency_list())
438
+
439
+ return {
440
+ "total_fiat_currencies": fiat_count,
441
+ "total_cryptocurrencies": crypto_count,
442
+ "total_currencies": fiat_count + crypto_count,
443
+ "max_cryptocurrencies": self.config.max_cryptocurrencies,
444
+ "max_fiat_currencies": self.config.max_fiat_currencies,
445
+ "min_market_cap_usd": int(self.config.min_market_cap_usd)
446
+ }
447
+
448
+
449
+ # ============================================================================
450
+ # HELPER FUNCTIONS
451
+ # ============================================================================
452
+
453
+ def create_database_loader(
454
+ max_cryptocurrencies: int = 500,
455
+ max_fiat_currencies: int = 50,
456
+ min_market_cap_usd: float = 1000000,
457
+ coingecko_delay: float = 1.5
458
+ ) -> CurrencyDatabaseLoader:
459
+ """
460
+ Create a configured database loader.
461
+
462
+ Args:
463
+ max_cryptocurrencies: Maximum number of cryptocurrencies to load
464
+ max_fiat_currencies: Maximum number of fiat currencies to load
465
+ min_market_cap_usd: Minimum market cap for cryptocurrencies
466
+ coingecko_delay: Delay between CoinGecko requests
467
+
468
+ Returns:
469
+ Configured CurrencyDatabaseLoader instance
470
+ """
471
+ config = DatabaseLoaderConfig(
472
+ max_cryptocurrencies=max_cryptocurrencies,
473
+ max_fiat_currencies=max_fiat_currencies,
474
+ min_market_cap_usd=min_market_cap_usd,
475
+ coingecko_delay=coingecko_delay
476
+ )
477
+
478
+ return CurrencyDatabaseLoader(config)
479
+
480
+
481
+ def load_currencies_to_database_format() -> List[Dict]:
482
+ """
483
+ Convenience function to get currency data in Django ORM format.
484
+
485
+ Returns:
486
+ List of dictionaries ready for bulk_create
487
+ """
488
+ loader = create_database_loader()
489
+ currencies = loader.build_currency_database_data()
490
+
491
+ # Convert to dictionary format for Django ORM
492
+ currency_dicts = []
493
+ for currency in currencies:
494
+ currency_dict = {
495
+ 'code': currency.code,
496
+ 'name': currency.name,
497
+ 'symbol': currency.symbol,
498
+ 'currency_type': currency.currency_type,
499
+ 'decimal_places': currency.decimal_places,
500
+ 'usd_rate': currency.usd_rate,
501
+ 'min_payment_amount': currency.min_payment_amount,
502
+ 'is_active': currency.is_active,
503
+ 'rate_updated_at': datetime.now()
504
+ }
505
+ currency_dicts.append(currency_dict)
506
+
507
+ return currency_dicts
@@ -0,0 +1,9 @@
1
+ """
2
+ Utility functions and helpers for currency operations.
3
+ """
4
+
5
+ from .cache import CacheManager
6
+
7
+ __all__ = [
8
+ 'CacheManager'
9
+ ]
@@ -0,0 +1,92 @@
1
+ """
2
+ Simple cache manager for currency rates.
3
+ """
4
+
5
+ import logging
6
+ from datetime import datetime, timedelta
7
+ from typing import Optional, Dict, Any
8
+ from cachetools import TTLCache
9
+
10
+ from ..core.models import Rate
11
+ from ..core.exceptions import CacheError
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class CacheManager:
17
+ """Simple TTL cache for currency rates."""
18
+
19
+ def __init__(self, ttl: int = 300, maxsize: int = 1000):
20
+ """
21
+ Initialize cache manager.
22
+
23
+ Args:
24
+ ttl: Time to live in seconds (default 5 minutes)
25
+ maxsize: Maximum cache size (default 1000 items)
26
+ """
27
+ self.cache = TTLCache(maxsize=maxsize, ttl=ttl)
28
+ self.ttl = ttl
29
+
30
+ def get_rate(self, base: str, quote: str, source: str) -> Optional[Rate]:
31
+ """
32
+ Get cached rate.
33
+
34
+ Args:
35
+ base: Base currency
36
+ quote: Quote currency
37
+ source: Data source
38
+
39
+ Returns:
40
+ Cached Rate or None
41
+ """
42
+ key = self._make_key(base, quote, source)
43
+
44
+ try:
45
+ cached_rate = self.cache.get(key)
46
+ if cached_rate:
47
+ logger.debug(f"Cache hit for {key}")
48
+ return cached_rate
49
+ else:
50
+ logger.debug(f"Cache miss for {key}")
51
+ return None
52
+ except Exception as e:
53
+ logger.error(f"Cache get error: {e}")
54
+ return None
55
+
56
+ def set_rate(self, rate: Rate) -> bool:
57
+ """
58
+ Cache rate.
59
+
60
+ Args:
61
+ rate: Rate to cache
62
+
63
+ Returns:
64
+ True if cached successfully
65
+ """
66
+ key = self._make_key(rate.base_currency, rate.quote_currency, rate.source)
67
+
68
+ try:
69
+ self.cache[key] = rate
70
+ logger.debug(f"Cached rate for {key}")
71
+ return True
72
+ except Exception as e:
73
+ logger.error(f"Cache set error: {e}")
74
+ return False
75
+
76
+ def _make_key(self, base: str, quote: str, source: str) -> str:
77
+ """Make cache key."""
78
+ return f"{source}:{base}:{quote}".upper()
79
+
80
+ def clear(self) -> None:
81
+ """Clear all cached rates."""
82
+ self.cache.clear()
83
+ logger.info("Cache cleared")
84
+
85
+ def get_stats(self) -> Dict[str, Any]:
86
+ """Get cache statistics."""
87
+ return {
88
+ "size": len(self.cache),
89
+ "maxsize": self.cache.maxsize,
90
+ "ttl": self.ttl,
91
+ "currsize": self.cache.currsize
92
+ }
@@ -41,6 +41,16 @@ CORE_REGISTRY = {
41
41
  "TaskConfig": ("django_cfg.models.tasks", "TaskConfig"),
42
42
  "DramatiqConfig": ("django_cfg.models.tasks", "DramatiqConfig"),
43
43
 
44
+ # Payment system models
45
+ "PaymentsConfig": ("django_cfg.models.payments", "PaymentsConfig"),
46
+ "PaymentProviderConfig": ("django_cfg.models.payments", "PaymentProviderConfig"),
47
+ "NowPaymentsConfig": ("django_cfg.models.payments", "NowPaymentsConfig"),
48
+ "CryptAPIConfig": ("django_cfg.models.payments", "CryptAPIConfig"),
49
+ "StripeConfig": ("django_cfg.models.payments", "StripeConfig"),
50
+ "create_nowpayments_config": ("django_cfg.models.payments", "create_nowpayments_config"),
51
+ "create_cryptapi_config": ("django_cfg.models.payments", "create_cryptapi_config"),
52
+ "create_stripe_config": ("django_cfg.models.payments", "create_stripe_config"),
53
+
44
54
  # Pagination classes
45
55
  "DefaultPagination": ("django_cfg.middleware.pagination", "DefaultPagination"),
46
56
  "LargePagination": ("django_cfg.middleware.pagination", "LargePagination"),
File without changes