django-cfg 1.2.29__py3-none-any.whl → 1.2.31__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 (126) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/payments/admin/__init__.py +3 -2
  3. django_cfg/apps/payments/admin/balance_admin.py +18 -18
  4. django_cfg/apps/payments/admin/currencies_admin.py +319 -131
  5. django_cfg/apps/payments/admin/payments_admin.py +15 -4
  6. django_cfg/apps/payments/config/module.py +2 -2
  7. django_cfg/apps/payments/config/utils.py +2 -2
  8. django_cfg/apps/payments/decorators.py +2 -2
  9. django_cfg/apps/payments/management/commands/README.md +95 -127
  10. django_cfg/apps/payments/management/commands/currency_stats.py +5 -24
  11. django_cfg/apps/payments/management/commands/manage_currencies.py +229 -0
  12. django_cfg/apps/payments/management/commands/manage_providers.py +235 -0
  13. django_cfg/apps/payments/managers/__init__.py +3 -2
  14. django_cfg/apps/payments/managers/balance_manager.py +2 -2
  15. django_cfg/apps/payments/managers/currency_manager.py +272 -49
  16. django_cfg/apps/payments/managers/payment_manager.py +161 -13
  17. django_cfg/apps/payments/middleware/api_access.py +2 -2
  18. django_cfg/apps/payments/middleware/rate_limiting.py +8 -18
  19. django_cfg/apps/payments/middleware/usage_tracking.py +20 -17
  20. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +241 -0
  21. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +30 -0
  22. django_cfg/apps/payments/models/__init__.py +3 -2
  23. django_cfg/apps/payments/models/currencies.py +187 -71
  24. django_cfg/apps/payments/models/payments.py +3 -2
  25. django_cfg/apps/payments/serializers/__init__.py +3 -2
  26. django_cfg/apps/payments/serializers/currencies.py +20 -12
  27. django_cfg/apps/payments/services/cache/simple_cache.py +2 -2
  28. django_cfg/apps/payments/services/core/balance_service.py +2 -2
  29. django_cfg/apps/payments/services/core/fallback_service.py +2 -2
  30. django_cfg/apps/payments/services/core/payment_service.py +3 -6
  31. django_cfg/apps/payments/services/core/subscription_service.py +4 -7
  32. django_cfg/apps/payments/services/internal_types.py +171 -7
  33. django_cfg/apps/payments/services/monitoring/api_schemas.py +58 -204
  34. django_cfg/apps/payments/services/monitoring/provider_health.py +2 -2
  35. django_cfg/apps/payments/services/providers/base.py +144 -43
  36. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +4 -0
  37. django_cfg/apps/payments/services/providers/cryptapi/config.py +8 -0
  38. django_cfg/apps/payments/services/providers/cryptapi/models.py +192 -0
  39. django_cfg/apps/payments/services/providers/cryptapi/provider.py +439 -0
  40. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +4 -0
  41. django_cfg/apps/payments/services/providers/cryptomus/models.py +176 -0
  42. django_cfg/apps/payments/services/providers/cryptomus/provider.py +429 -0
  43. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +564 -0
  44. django_cfg/apps/payments/services/providers/models/__init__.py +34 -0
  45. django_cfg/apps/payments/services/providers/models/currencies.py +190 -0
  46. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +4 -0
  47. django_cfg/apps/payments/services/providers/nowpayments/models.py +196 -0
  48. django_cfg/apps/payments/services/providers/nowpayments/provider.py +380 -0
  49. django_cfg/apps/payments/services/providers/registry.py +294 -11
  50. django_cfg/apps/payments/services/providers/stripe/__init__.py +4 -0
  51. django_cfg/apps/payments/services/providers/stripe/models.py +184 -0
  52. django_cfg/apps/payments/services/providers/stripe/provider.py +109 -0
  53. django_cfg/apps/payments/services/security/error_handler.py +6 -8
  54. django_cfg/apps/payments/services/security/payment_notifications.py +2 -2
  55. django_cfg/apps/payments/services/security/webhook_validator.py +3 -4
  56. django_cfg/apps/payments/signals/api_key_signals.py +2 -2
  57. django_cfg/apps/payments/signals/payment_signals.py +11 -5
  58. django_cfg/apps/payments/signals/subscription_signals.py +2 -2
  59. django_cfg/apps/payments/tasks/webhook_processing.py +2 -2
  60. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +50 -0
  61. django_cfg/apps/payments/templates/payments/base.html +4 -4
  62. django_cfg/apps/payments/templates/payments/components/payment_card.html +6 -6
  63. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +4 -4
  64. django_cfg/apps/payments/templates/payments/components/progress_bar.html +14 -7
  65. django_cfg/apps/payments/templates/payments/components/provider_stats.html +2 -2
  66. django_cfg/apps/payments/templates/payments/components/status_badge.html +8 -1
  67. django_cfg/apps/payments/templates/payments/components/status_overview.html +34 -30
  68. django_cfg/apps/payments/templates/payments/dashboard.html +202 -290
  69. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +35 -0
  70. django_cfg/apps/payments/templates/payments/payment_create.html +579 -0
  71. django_cfg/apps/payments/templates/payments/payment_detail.html +373 -0
  72. django_cfg/apps/payments/templates/payments/payment_list.html +354 -0
  73. django_cfg/apps/payments/templates/payments/stats.html +261 -0
  74. django_cfg/apps/payments/templates/payments/test.html +213 -0
  75. django_cfg/apps/payments/urls.py +3 -1
  76. django_cfg/apps/payments/{urls_templates.py → urls_admin.py} +6 -0
  77. django_cfg/apps/payments/utils/__init__.py +1 -3
  78. django_cfg/apps/payments/utils/billing_utils.py +2 -2
  79. django_cfg/apps/payments/utils/config_utils.py +2 -8
  80. django_cfg/apps/payments/utils/validation_utils.py +2 -2
  81. django_cfg/apps/payments/views/__init__.py +3 -2
  82. django_cfg/apps/payments/views/currency_views.py +31 -20
  83. django_cfg/apps/payments/views/payment_views.py +2 -2
  84. django_cfg/apps/payments/views/templates/ajax.py +141 -2
  85. django_cfg/apps/payments/views/templates/base.py +21 -13
  86. django_cfg/apps/payments/views/templates/payment_detail.py +1 -1
  87. django_cfg/apps/payments/views/templates/payment_management.py +34 -40
  88. django_cfg/apps/payments/views/templates/stats.py +8 -4
  89. django_cfg/apps/payments/views/webhook_views.py +2 -2
  90. django_cfg/apps/payments/viewsets.py +3 -2
  91. django_cfg/apps/tasks/urls.py +0 -2
  92. django_cfg/apps/tasks/urls_admin.py +14 -0
  93. django_cfg/apps/urls.py +4 -4
  94. django_cfg/core/config.py +35 -0
  95. django_cfg/models/payments.py +2 -8
  96. django_cfg/modules/django_currency/__init__.py +16 -11
  97. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  98. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  99. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  100. django_cfg/modules/django_currency/core/__init__.py +1 -7
  101. django_cfg/modules/django_currency/core/converter.py +18 -23
  102. django_cfg/modules/django_currency/core/models.py +122 -11
  103. django_cfg/modules/django_currency/database/__init__.py +4 -4
  104. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  105. django_cfg/modules/django_unfold/dashboard.py +7 -2
  106. django_cfg/template_archive/django_sample.zip +0 -0
  107. django_cfg/templates/admin/components/action_grid.html +9 -9
  108. django_cfg/templates/admin/components/metric_card.html +5 -5
  109. django_cfg/templates/admin/components/status_badge.html +2 -2
  110. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  111. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  112. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  113. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  114. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/METADATA +2 -4
  115. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/RECORD +118 -96
  116. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  117. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  118. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  119. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  120. django_cfg/apps/payments/services/providers/nowpayments.py +0 -293
  121. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  122. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  123. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  124. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/WHEEL +0 -0
  125. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/entry_points.txt +0 -0
  126. {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/licenses/LICENSE +0 -0
@@ -1,246 +0,0 @@
1
- """
2
- YFinance client for fiat currencies only.
3
- """
4
-
5
- import logging
6
- import yfinance as yf
7
- from datetime import datetime
8
- from typing import Set, Optional
9
- from cachetools import TTLCache
10
- from concurrent.futures import ThreadPoolExecutor, as_completed
11
- import time
12
- from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
13
-
14
- from ..core.models import Rate
15
- from ..core.exceptions import RateFetchError
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
-
20
- class YFinanceClient:
21
- """Client for fetching fiat currency rates from Yahoo Finance."""
22
-
23
- def __init__(self, cache_ttl: int = 3600):
24
- """Initialize YFinance client with TTL cache."""
25
- self._currency_cache = TTLCache(maxsize=1, ttl=cache_ttl) # Cache currencies for 1 hour
26
- self._rate_cache = TTLCache(maxsize=1000, ttl=300) # Cache rates for 5 minutes
27
-
28
- def fetch_rate(self, base: str, quote: str) -> Rate:
29
- """
30
- Fetch exchange rate from YFinance with caching.
31
-
32
- Args:
33
- base: Base currency code
34
- quote: Quote currency code
35
-
36
- Returns:
37
- Rate object with exchange rate data
38
-
39
- Raises:
40
- RateFetchError: If rate fetch fails
41
- """
42
- cache_key = f"{base}_{quote}"
43
-
44
- # Try cache first
45
- if cache_key in self._rate_cache:
46
- logger.debug(f"Retrieved rate {base}/{quote} from cache")
47
- return self._rate_cache[cache_key]
48
-
49
- try:
50
- rate = self._fetch_rate_with_retry(base, quote)
51
-
52
- # Cache the result
53
- self._rate_cache[cache_key] = rate
54
-
55
- return rate
56
-
57
- except Exception as e:
58
- logger.error(f"Failed to fetch rate for {base}/{quote}: {e}")
59
- raise RateFetchError(f"YFinance fetch failed: {e}")
60
-
61
- @retry(
62
- stop=stop_after_attempt(3),
63
- wait=wait_exponential(multiplier=1, min=1, max=10),
64
- retry=retry_if_exception_type((ConnectionError, TimeoutError, Exception)),
65
- reraise=True
66
- )
67
- def _fetch_rate_with_retry(self, base: str, quote: str) -> Rate:
68
- """
69
- Fetch rate with retry logic and exponential backoff.
70
-
71
- Args:
72
- base: Base currency code
73
- quote: Quote currency code
74
-
75
- Returns:
76
- Rate object with exchange rate data
77
- """
78
- symbol = self._build_symbol(base, quote)
79
- logger.debug(f"Fetching rate for {symbol}")
80
-
81
- ticker = yf.Ticker(symbol)
82
-
83
- # Try to get current price from info
84
- info = ticker.info
85
- if 'regularMarketPrice' in info and info['regularMarketPrice']:
86
- rate_value = float(info['regularMarketPrice'])
87
- logger.debug(f"Got rate from info: {rate_value}")
88
- else:
89
- # Fallback to history
90
- hist = ticker.history(period="1d")
91
- if hist.empty:
92
- raise RateFetchError(f"No data available for {symbol}")
93
- rate_value = float(hist['Close'].iloc[-1])
94
- logger.debug(f"Got rate from history: {rate_value}")
95
-
96
- return Rate(
97
- source="yfinance",
98
- base_currency=base,
99
- quote_currency=quote,
100
- rate=rate_value,
101
- timestamp=datetime.now()
102
- )
103
-
104
- def get_fiat_currencies(self) -> Set[str]:
105
- """Get all supported fiat currencies dynamically with caching."""
106
- cache_key = "fiat_currencies"
107
-
108
- # Try cache first
109
- if cache_key in self._currency_cache:
110
- logger.debug("Retrieved fiat currencies from cache")
111
- return self._currency_cache[cache_key]
112
-
113
- # Load currencies dynamically
114
- currencies = self._discover_fiat_currencies()
115
-
116
- # Cache the result
117
- self._currency_cache[cache_key] = currencies
118
- logger.info(f"Loaded and cached {len(currencies)} fiat currencies from YFinance")
119
-
120
- return currencies
121
-
122
- def _discover_fiat_currencies(self) -> Set[str]:
123
- """Discover available fiat currencies dynamically using YFinance with multithreading."""
124
- currencies = set()
125
-
126
- try:
127
- # Known major currencies to test efficiently
128
- test_currencies = [
129
- "USD", "EUR", "GBP", "JPY", "CAD", "AUD", "CHF", "CNY", "KRW", "RUB",
130
- "SGD", "HKD", "INR", "THB", "MYR", "PHP", "IDR", "VND", "BRL", "MXN",
131
- "ZAR", "TRY", "PLN", "CZK", "HUF", "DKK", "SEK", "NOK", "NZD", "TWD"
132
- ]
133
-
134
- logger.debug(f"Testing {len(test_currencies)} currency pairs with multithreading...")
135
-
136
- @retry(
137
- stop=stop_after_attempt(2),
138
- wait=wait_exponential(multiplier=1, min=1, max=5),
139
- retry=retry_if_exception_type((ConnectionError, TimeoutError)),
140
- reraise=False # Don't reraise for currency discovery
141
- )
142
- def test_currency(base_currency):
143
- """Test a single currency pair with retry logic."""
144
- try:
145
- # Test against USD
146
- symbol = f"{base_currency}USD=X" if base_currency != "USD" else "EURUSD=X"
147
- ticker = yf.Ticker(symbol)
148
- info = ticker.info
149
-
150
- # If ticker has valid data, return the currencies
151
- if info and 'symbol' in info:
152
- result_currencies = set()
153
- if base_currency != "USD":
154
- result_currencies.add(base_currency)
155
- result_currencies.add("USD")
156
- logger.debug(f"Verified: {symbol}")
157
- return result_currencies
158
- return set()
159
-
160
- except Exception as e:
161
- logger.debug(f"Failed to verify {base_currency}: {e}")
162
- return set()
163
-
164
- # Use ThreadPoolExecutor for parallel testing
165
- with ThreadPoolExecutor(max_workers=5) as executor:
166
- # Submit all tasks
167
- future_to_currency = {
168
- executor.submit(test_currency, currency): currency
169
- for currency in test_currencies
170
- }
171
-
172
- # Collect results as they complete
173
- for future in as_completed(future_to_currency):
174
- try:
175
- result = future.result(timeout=10) # 10 second timeout per request
176
- currencies.update(result)
177
- except Exception as e:
178
- currency = future_to_currency[future]
179
- logger.debug(f"Future failed for {currency}: {e}")
180
-
181
- logger.info(f"Discovered {len(currencies)} fiat currencies dynamically with multithreading")
182
- return currencies if currencies else {"USD", "EUR", "GBP", "JPY"} # Fallback to major currencies
183
-
184
- except Exception as e:
185
- logger.warning(f"Failed to discover currencies dynamically: {e}")
186
- # Return minimal set as fallback
187
- return {"USD", "EUR", "GBP", "JPY", "CAD", "AUD"}
188
-
189
- def fetch_multiple_rates(self, pairs: list) -> dict:
190
- """
191
- Fetch multiple currency rates in parallel.
192
-
193
- Args:
194
- pairs: List of tuples (base, quote) to fetch
195
-
196
- Returns:
197
- Dictionary mapping "BASE_QUOTE" to Rate objects
198
- """
199
- results = {}
200
-
201
- def fetch_single_rate(pair):
202
- base, quote = pair
203
- try:
204
- rate = self.fetch_rate(base, quote)
205
- return f"{base}_{quote}", rate
206
- except Exception as e:
207
- logger.warning(f"Failed to fetch {base}/{quote}: {e}")
208
- return f"{base}_{quote}", None
209
-
210
- # Use ThreadPoolExecutor for parallel fetching
211
- with ThreadPoolExecutor(max_workers=8) as executor: # YFinance can handle more parallel requests
212
- future_to_pair = {executor.submit(fetch_single_rate, pair): pair for pair in pairs}
213
-
214
- for future in as_completed(future_to_pair):
215
- try:
216
- key, rate = future.result(timeout=15)
217
- if rate:
218
- results[key] = rate
219
- except Exception as e:
220
- pair = future_to_pair[future]
221
- logger.error(f"Failed to fetch rate for {pair}: {e}")
222
-
223
- logger.info(f"Successfully fetched {len(results)}/{len(pairs)} rates")
224
- return results
225
-
226
- def _build_symbol(self, base: str, quote: str) -> str:
227
- """Build YFinance symbol from currency pair."""
228
- base = base.upper()
229
- quote = quote.upper()
230
-
231
- # Only handle fiat pairs
232
- fiat_currencies = self.get_fiat_currencies()
233
- if base in fiat_currencies and quote in fiat_currencies:
234
- if base == quote:
235
- raise RateFetchError("Same currency conversion not needed")
236
- return f"{base}{quote}=X"
237
-
238
- raise RateFetchError(f"Unsupported fiat currency pair: {base}/{quote}")
239
-
240
- def supports_pair(self, base: str, quote: str) -> bool:
241
- """Check if fiat currency pair is supported."""
242
- base = base.upper()
243
- quote = quote.upper()
244
-
245
- fiat_currencies = self.get_fiat_currencies()
246
- return base in fiat_currencies and quote in fiat_currencies and base != quote