django-cfg 1.2.23__py3-none-any.whl → 1.2.27__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/knowbase/tasks/archive_tasks.py +6 -6
- django_cfg/apps/knowbase/tasks/document_processing.py +3 -3
- django_cfg/apps/knowbase/tasks/external_data_tasks.py +2 -2
- django_cfg/apps/knowbase/tasks/maintenance.py +3 -3
- django_cfg/apps/payments/config/__init__.py +15 -37
- django_cfg/apps/payments/config/module.py +30 -122
- django_cfg/apps/payments/config/providers.py +28 -16
- django_cfg/apps/payments/config/settings.py +53 -93
- django_cfg/apps/payments/config/utils.py +10 -156
- django_cfg/apps/payments/management/__init__.py +3 -0
- django_cfg/apps/payments/management/commands/README.md +178 -0
- django_cfg/apps/payments/management/commands/__init__.py +3 -0
- django_cfg/apps/payments/management/commands/currency_stats.py +323 -0
- django_cfg/apps/payments/management/commands/populate_currencies.py +246 -0
- django_cfg/apps/payments/management/commands/update_currencies.py +336 -0
- django_cfg/apps/payments/managers/currency_manager.py +65 -14
- django_cfg/apps/payments/middleware/api_access.py +33 -0
- django_cfg/apps/payments/migrations/0001_initial.py +94 -1
- django_cfg/apps/payments/models/payments.py +110 -0
- django_cfg/apps/payments/services/__init__.py +7 -1
- django_cfg/apps/payments/services/core/balance_service.py +14 -16
- django_cfg/apps/payments/services/core/fallback_service.py +432 -0
- django_cfg/apps/payments/services/core/payment_service.py +212 -29
- django_cfg/apps/payments/services/core/subscription_service.py +15 -17
- django_cfg/apps/payments/services/internal_types.py +31 -0
- django_cfg/apps/payments/services/monitoring/__init__.py +22 -0
- django_cfg/apps/payments/services/monitoring/api_schemas.py +222 -0
- django_cfg/apps/payments/services/monitoring/provider_health.py +372 -0
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/cryptapi.py +14 -3
- django_cfg/apps/payments/services/providers/cryptomus.py +310 -0
- django_cfg/apps/payments/services/providers/registry.py +4 -0
- django_cfg/apps/payments/services/security/__init__.py +34 -0
- django_cfg/apps/payments/services/security/error_handler.py +637 -0
- django_cfg/apps/payments/services/security/payment_notifications.py +342 -0
- django_cfg/apps/payments/services/security/webhook_validator.py +475 -0
- django_cfg/apps/payments/signals/api_key_signals.py +10 -0
- django_cfg/apps/payments/signals/payment_signals.py +3 -2
- django_cfg/apps/payments/tasks/__init__.py +12 -0
- django_cfg/apps/payments/tasks/webhook_processing.py +177 -0
- django_cfg/apps/payments/utils/__init__.py +7 -4
- django_cfg/apps/payments/utils/billing_utils.py +342 -0
- django_cfg/apps/payments/utils/config_utils.py +2 -0
- django_cfg/apps/payments/views/payment_views.py +40 -2
- django_cfg/apps/payments/views/webhook_views.py +266 -0
- django_cfg/apps/payments/viewsets.py +65 -0
- django_cfg/cli/README.md +2 -2
- django_cfg/cli/commands/create_project.py +1 -1
- django_cfg/cli/commands/info.py +1 -1
- django_cfg/cli/main.py +1 -1
- django_cfg/cli/utils.py +5 -5
- django_cfg/core/config.py +18 -4
- django_cfg/models/payments.py +547 -0
- django_cfg/models/tasks.py +51 -2
- django_cfg/modules/base.py +11 -5
- django_cfg/modules/django_currency/README.md +104 -269
- django_cfg/modules/django_currency/__init__.py +99 -41
- django_cfg/modules/django_currency/clients/__init__.py +11 -0
- django_cfg/modules/django_currency/clients/coingecko_client.py +257 -0
- django_cfg/modules/django_currency/clients/yfinance_client.py +246 -0
- django_cfg/modules/django_currency/core/__init__.py +42 -0
- django_cfg/modules/django_currency/core/converter.py +169 -0
- django_cfg/modules/django_currency/core/exceptions.py +28 -0
- django_cfg/modules/django_currency/core/models.py +54 -0
- django_cfg/modules/django_currency/database/__init__.py +25 -0
- django_cfg/modules/django_currency/database/database_loader.py +507 -0
- django_cfg/modules/django_currency/utils/__init__.py +9 -0
- django_cfg/modules/django_currency/utils/cache.py +92 -0
- django_cfg/registry/core.py +10 -0
- django_cfg/template_archive/__init__.py +0 -0
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/METADATA +10 -6
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/RECORD +77 -51
- django_cfg/apps/agents/examples/__init__.py +0 -3
- django_cfg/apps/agents/examples/simple_example.py +0 -161
- django_cfg/apps/knowbase/examples/__init__.py +0 -3
- django_cfg/apps/knowbase/examples/external_data_usage.py +0 -191
- django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +0 -199
- django_cfg/modules/django_currency/cache.py +0 -430
- django_cfg/modules/django_currency/converter.py +0 -324
- django_cfg/modules/django_currency/service.py +0 -277
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.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,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
|
+
}
|
django_cfg/registry/core.py
CHANGED
@@ -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
|
Binary file
|