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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/payments/admin/__init__.py +3 -2
- django_cfg/apps/payments/admin/balance_admin.py +18 -18
- django_cfg/apps/payments/admin/currencies_admin.py +319 -131
- django_cfg/apps/payments/admin/payments_admin.py +15 -4
- django_cfg/apps/payments/config/module.py +2 -2
- django_cfg/apps/payments/config/utils.py +2 -2
- django_cfg/apps/payments/decorators.py +2 -2
- django_cfg/apps/payments/management/commands/README.md +95 -127
- django_cfg/apps/payments/management/commands/currency_stats.py +5 -24
- django_cfg/apps/payments/management/commands/manage_currencies.py +229 -0
- django_cfg/apps/payments/management/commands/manage_providers.py +235 -0
- django_cfg/apps/payments/managers/__init__.py +3 -2
- django_cfg/apps/payments/managers/balance_manager.py +2 -2
- django_cfg/apps/payments/managers/currency_manager.py +272 -49
- django_cfg/apps/payments/managers/payment_manager.py +161 -13
- django_cfg/apps/payments/middleware/api_access.py +2 -2
- django_cfg/apps/payments/middleware/rate_limiting.py +8 -18
- django_cfg/apps/payments/middleware/usage_tracking.py +20 -17
- django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +241 -0
- django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +30 -0
- django_cfg/apps/payments/models/__init__.py +3 -2
- django_cfg/apps/payments/models/currencies.py +187 -71
- django_cfg/apps/payments/models/payments.py +3 -2
- django_cfg/apps/payments/serializers/__init__.py +3 -2
- django_cfg/apps/payments/serializers/currencies.py +20 -12
- django_cfg/apps/payments/services/cache/simple_cache.py +2 -2
- django_cfg/apps/payments/services/core/balance_service.py +2 -2
- django_cfg/apps/payments/services/core/fallback_service.py +2 -2
- django_cfg/apps/payments/services/core/payment_service.py +3 -6
- django_cfg/apps/payments/services/core/subscription_service.py +4 -7
- django_cfg/apps/payments/services/internal_types.py +171 -7
- django_cfg/apps/payments/services/monitoring/api_schemas.py +58 -204
- django_cfg/apps/payments/services/monitoring/provider_health.py +2 -2
- django_cfg/apps/payments/services/providers/base.py +144 -43
- django_cfg/apps/payments/services/providers/cryptapi/__init__.py +4 -0
- django_cfg/apps/payments/services/providers/cryptapi/config.py +8 -0
- django_cfg/apps/payments/services/providers/cryptapi/models.py +192 -0
- django_cfg/apps/payments/services/providers/cryptapi/provider.py +439 -0
- django_cfg/apps/payments/services/providers/cryptomus/__init__.py +4 -0
- django_cfg/apps/payments/services/providers/cryptomus/models.py +176 -0
- django_cfg/apps/payments/services/providers/cryptomus/provider.py +429 -0
- django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +564 -0
- django_cfg/apps/payments/services/providers/models/__init__.py +34 -0
- django_cfg/apps/payments/services/providers/models/currencies.py +190 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +4 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +196 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +380 -0
- django_cfg/apps/payments/services/providers/registry.py +294 -11
- django_cfg/apps/payments/services/providers/stripe/__init__.py +4 -0
- django_cfg/apps/payments/services/providers/stripe/models.py +184 -0
- django_cfg/apps/payments/services/providers/stripe/provider.py +109 -0
- django_cfg/apps/payments/services/security/error_handler.py +6 -8
- django_cfg/apps/payments/services/security/payment_notifications.py +2 -2
- django_cfg/apps/payments/services/security/webhook_validator.py +3 -4
- django_cfg/apps/payments/signals/api_key_signals.py +2 -2
- django_cfg/apps/payments/signals/payment_signals.py +11 -5
- django_cfg/apps/payments/signals/subscription_signals.py +2 -2
- django_cfg/apps/payments/tasks/webhook_processing.py +2 -2
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +50 -0
- django_cfg/apps/payments/templates/payments/base.html +4 -4
- django_cfg/apps/payments/templates/payments/components/payment_card.html +6 -6
- django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +4 -4
- django_cfg/apps/payments/templates/payments/components/progress_bar.html +14 -7
- django_cfg/apps/payments/templates/payments/components/provider_stats.html +2 -2
- django_cfg/apps/payments/templates/payments/components/status_badge.html +8 -1
- django_cfg/apps/payments/templates/payments/components/status_overview.html +34 -30
- django_cfg/apps/payments/templates/payments/dashboard.html +202 -290
- django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +35 -0
- django_cfg/apps/payments/templates/payments/payment_create.html +579 -0
- django_cfg/apps/payments/templates/payments/payment_detail.html +373 -0
- django_cfg/apps/payments/templates/payments/payment_list.html +354 -0
- django_cfg/apps/payments/templates/payments/stats.html +261 -0
- django_cfg/apps/payments/templates/payments/test.html +213 -0
- django_cfg/apps/payments/urls.py +3 -1
- django_cfg/apps/payments/{urls_templates.py → urls_admin.py} +6 -0
- django_cfg/apps/payments/utils/__init__.py +1 -3
- django_cfg/apps/payments/utils/billing_utils.py +2 -2
- django_cfg/apps/payments/utils/config_utils.py +2 -8
- django_cfg/apps/payments/utils/validation_utils.py +2 -2
- django_cfg/apps/payments/views/__init__.py +3 -2
- django_cfg/apps/payments/views/currency_views.py +31 -20
- django_cfg/apps/payments/views/payment_views.py +2 -2
- django_cfg/apps/payments/views/templates/ajax.py +141 -2
- django_cfg/apps/payments/views/templates/base.py +21 -13
- django_cfg/apps/payments/views/templates/payment_detail.py +1 -1
- django_cfg/apps/payments/views/templates/payment_management.py +34 -40
- django_cfg/apps/payments/views/templates/stats.py +8 -4
- django_cfg/apps/payments/views/webhook_views.py +2 -2
- django_cfg/apps/payments/viewsets.py +3 -2
- django_cfg/apps/tasks/urls.py +0 -2
- django_cfg/apps/tasks/urls_admin.py +14 -0
- django_cfg/apps/urls.py +4 -4
- django_cfg/core/config.py +35 -0
- django_cfg/models/payments.py +2 -8
- django_cfg/modules/django_currency/__init__.py +16 -11
- django_cfg/modules/django_currency/clients/__init__.py +4 -4
- django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
- django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
- django_cfg/modules/django_currency/core/__init__.py +1 -7
- django_cfg/modules/django_currency/core/converter.py +18 -23
- django_cfg/modules/django_currency/core/models.py +122 -11
- django_cfg/modules/django_currency/database/__init__.py +4 -4
- django_cfg/modules/django_currency/database/database_loader.py +190 -309
- django_cfg/modules/django_unfold/dashboard.py +7 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/templates/admin/components/action_grid.html +9 -9
- django_cfg/templates/admin/components/metric_card.html +5 -5
- django_cfg/templates/admin/components/status_badge.html +2 -2
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
- django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
- django_cfg/templates/admin/snippets/components/system_health.html +1 -1
- django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
- {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/METADATA +2 -4
- {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/RECORD +118 -96
- django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
- django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
- django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
- django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
- django_cfg/apps/payments/services/providers/nowpayments.py +0 -293
- django_cfg/apps/payments/services/validators/__init__.py +0 -8
- django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
- django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
- {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.29.dist-info → django_cfg-1.2.31.dist-info}/entry_points.txt +0 -0
- {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
|
File without changes
|
File without changes
|
File without changes
|