django-cfg 1.2.29__py3-none-any.whl → 1.3.1__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 (258) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -9
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/manage_currencies.py +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +207 -67
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -284
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -62
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -111
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,257 +0,0 @@
1
- """
2
- CoinGecko client for crypto rates only.
3
- """
4
-
5
- import logging
6
- import time
7
- from datetime import datetime
8
- from typing import Dict, Set, Optional
9
- from cachetools import TTLCache
10
- from pycoingecko import CoinGeckoAPI
11
- from concurrent.futures import ThreadPoolExecutor, as_completed
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 CoinGeckoClient:
21
- """Client for fetching crypto rates from CoinGecko."""
22
-
23
- def __init__(self, cache_ttl: int = 3600, rate_limit_delay: float = 1.2):
24
- """Initialize CoinGecko client with TTL cache and rate limiting."""
25
- self.client = CoinGeckoAPI()
26
- self._crypto_cache = TTLCache(maxsize=2, ttl=cache_ttl) # Cache crypto data for 1 hour
27
- self._rate_cache = TTLCache(maxsize=1000, ttl=600) # Cache rates for 10 minutes
28
- self._last_request_time = 0.0
29
- self._rate_limit_delay = rate_limit_delay # Delay between requests to avoid 429
30
-
31
- def fetch_rate(self, base: str, quote: str) -> Rate:
32
- """
33
- Fetch crypto exchange rate from CoinGecko with caching.
34
-
35
- Args:
36
- base: Base currency code (crypto)
37
- quote: Quote currency code
38
-
39
- Returns:
40
- Rate object with exchange rate data
41
-
42
- Raises:
43
- RateFetchError: If rate fetch fails
44
- """
45
- cache_key = f"{base}_{quote}"
46
-
47
- # Try cache first
48
- if cache_key in self._rate_cache:
49
- logger.debug(f"Retrieved rate {base}/{quote} from cache")
50
- return self._rate_cache[cache_key]
51
-
52
- try:
53
- rate = self._fetch_rate_with_retry(base, quote)
54
-
55
- # Cache the result
56
- self._rate_cache[cache_key] = rate
57
-
58
- return rate
59
-
60
- except Exception as e:
61
- logger.error(f"Failed to fetch rate for {base}/{quote}: {e}")
62
- raise RateFetchError(f"CoinGecko fetch failed: {e}")
63
-
64
- @retry(
65
- stop=stop_after_attempt(4), # More retries for CoinGecko due to rate limits
66
- wait=wait_exponential(multiplier=2, min=2, max=30), # Longer waits for rate-limited API
67
- retry=retry_if_exception_type((ConnectionError, TimeoutError, Exception)),
68
- reraise=True
69
- )
70
- def _fetch_rate_with_retry(self, base: str, quote: str) -> Rate:
71
- """
72
- Fetch rate with retry logic and exponential backoff.
73
-
74
- Args:
75
- base: Base currency code (crypto)
76
- quote: Quote currency code
77
-
78
- Returns:
79
- Rate object with exchange rate data
80
- """
81
- base_id = self._get_crypto_id(base)
82
- quote_currency = quote.lower()
83
-
84
- vs_currencies = self.get_vs_currencies()
85
- if quote_currency not in vs_currencies:
86
- raise RateFetchError(f"Quote currency {quote} not supported by CoinGecko")
87
-
88
- logger.debug(f"Fetching rate for {base_id} vs {quote_currency}")
89
-
90
- # Fetch price from CoinGecko with rate limiting
91
- self._rate_limit()
92
- price_data = self.client.get_price(
93
- ids=base_id,
94
- vs_currencies=quote_currency,
95
- include_last_updated_at=True
96
- )
97
-
98
- if base_id not in price_data:
99
- raise RateFetchError(f"No data for {base}")
100
-
101
- rate_value = price_data[base_id][quote_currency]
102
-
103
- return Rate(
104
- source="coingecko",
105
- base_currency=base.upper(),
106
- quote_currency=quote.upper(),
107
- rate=float(rate_value),
108
- timestamp=datetime.now()
109
- )
110
-
111
- def get_crypto_ids(self) -> Dict[str, str]:
112
- """Get all supported cryptocurrencies dynamically with caching."""
113
- cache_key = "crypto_ids"
114
-
115
- # Try cache first
116
- if cache_key in self._crypto_cache:
117
- logger.debug("Retrieved crypto IDs from cache")
118
- return self._crypto_cache[cache_key]
119
-
120
- try:
121
- crypto_ids = self._get_coins_list_with_retry()
122
-
123
- # Cache the result
124
- self._crypto_cache[cache_key] = crypto_ids
125
- logger.info(f"Loaded and cached {len(crypto_ids)} cryptocurrencies from CoinGecko")
126
-
127
- return crypto_ids
128
-
129
- except Exception as e:
130
- logger.error(f"Failed to load cryptocurrencies: {e}")
131
- raise RateFetchError(f"Failed to load cryptocurrencies from CoinGecko: {e}")
132
-
133
- def get_vs_currencies(self) -> Set[str]:
134
- """Get all supported quote currencies dynamically with caching."""
135
- cache_key = "vs_currencies"
136
-
137
- # Try cache first
138
- if cache_key in self._crypto_cache:
139
- logger.debug("Retrieved vs_currencies from cache")
140
- return self._crypto_cache[cache_key]
141
-
142
- try:
143
- vs_currencies_set = self._get_vs_currencies_with_retry()
144
-
145
- # Cache the result
146
- self._crypto_cache[cache_key] = vs_currencies_set
147
- logger.info(f"Loaded and cached {len(vs_currencies_set)} vs_currencies from CoinGecko")
148
-
149
- return vs_currencies_set
150
-
151
- except Exception as e:
152
- logger.error(f"Failed to load vs_currencies: {e}")
153
- raise RateFetchError(f"Failed to load vs_currencies from CoinGecko: {e}")
154
-
155
- def _get_crypto_id(self, currency: str) -> str:
156
- """Get CoinGecko crypto ID from currency code."""
157
- currency = currency.upper()
158
- crypto_ids = self.get_crypto_ids()
159
-
160
- if currency in crypto_ids:
161
- return crypto_ids[currency]
162
-
163
- raise RateFetchError(f"Unknown cryptocurrency: {currency}")
164
-
165
- def _rate_limit(self):
166
- """Enforce rate limiting to prevent API throttling."""
167
- current_time = time.time()
168
- time_since_last = current_time - self._last_request_time
169
-
170
- if time_since_last < self._rate_limit_delay:
171
- sleep_time = self._rate_limit_delay - time_since_last
172
- logger.debug(f"Rate limiting: sleeping for {sleep_time:.2f}s")
173
- time.sleep(sleep_time)
174
-
175
- self._last_request_time = time.time()
176
-
177
- @retry(
178
- stop=stop_after_attempt(3),
179
- wait=wait_exponential(multiplier=2, min=2, max=15),
180
- retry=retry_if_exception_type((ConnectionError, TimeoutError, Exception)),
181
- reraise=True
182
- )
183
- def _get_coins_list_with_retry(self) -> Dict[str, str]:
184
- """Get coins list with retry logic."""
185
- self._rate_limit()
186
- coins_list = self.client.get_coins_list()
187
- crypto_ids = {}
188
-
189
- for coin in coins_list:
190
- symbol = coin['symbol'].upper()
191
- crypto_ids[symbol] = coin['id']
192
-
193
- return crypto_ids
194
-
195
- @retry(
196
- stop=stop_after_attempt(3),
197
- wait=wait_exponential(multiplier=2, min=2, max=15),
198
- retry=retry_if_exception_type((ConnectionError, TimeoutError, Exception)),
199
- reraise=True
200
- )
201
- def _get_vs_currencies_with_retry(self) -> Set[str]:
202
- """Get vs currencies with retry logic."""
203
- self._rate_limit()
204
- vs_currencies = self.client.get_supported_vs_currencies()
205
- return set(vs_currencies)
206
-
207
- def fetch_multiple_rates(self, pairs: list) -> Dict[str, Rate]:
208
- """
209
- Fetch multiple currency rates in parallel.
210
-
211
- Args:
212
- pairs: List of tuples (base, quote) to fetch
213
-
214
- Returns:
215
- Dictionary mapping "BASE_QUOTE" to Rate objects
216
- """
217
- results = {}
218
-
219
- def fetch_single_rate(pair):
220
- base, quote = pair
221
- try:
222
- rate = self.fetch_rate(base, quote)
223
- return f"{base}_{quote}", rate
224
- except Exception as e:
225
- logger.warning(f"Failed to fetch {base}/{quote}: {e}")
226
- return f"{base}_{quote}", None
227
-
228
- # Use ThreadPoolExecutor for parallel fetching with rate limiting
229
- with ThreadPoolExecutor(max_workers=3) as executor: # Limited workers to respect rate limits
230
- future_to_pair = {executor.submit(fetch_single_rate, pair): pair for pair in pairs}
231
-
232
- for future in as_completed(future_to_pair):
233
- try:
234
- key, rate = future.result(timeout=30)
235
- if rate:
236
- results[key] = rate
237
- except Exception as e:
238
- pair = future_to_pair[future]
239
- logger.error(f"Failed to fetch rate for {pair}: {e}")
240
-
241
- logger.info(f"Successfully fetched {len(results)}/{len(pairs)} rates")
242
- return results
243
-
244
- def supports_pair(self, base: str, quote: str) -> bool:
245
- """Check if crypto currency pair is supported."""
246
- try:
247
- # Base must be a crypto
248
- crypto_ids = self.get_crypto_ids()
249
- if base.upper() not in crypto_ids:
250
- return False
251
-
252
- # Quote must be a supported vs_currency
253
- vs_currencies = self.get_vs_currencies()
254
- return quote.lower() in vs_currencies
255
-
256
- except Exception:
257
- return False
@@ -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
@@ -1 +0,0 @@
1
- *.zip
File without changes
django_cfg/urls.py DELETED
@@ -1,33 +0,0 @@
1
- """
2
- URL Configuration for Django Config Toolkit
3
-
4
- Provides URL patterns for health checks and other toolkit endpoints.
5
- """
6
-
7
- from django.urls import path, include
8
- from .health import HealthCheckView, SimpleHealthView
9
-
10
-
11
- app_name = 'django_cfg'
12
-
13
- urlpatterns = [
14
- # Health check endpoints
15
- path('health/', HealthCheckView.as_view(), name='health-check'),
16
- path('health/simple/', SimpleHealthView.as_view(), name='simple-health'),
17
- ]
18
-
19
-
20
- def get_toolkit_urls():
21
- """
22
- Get URL patterns for Django Config Toolkit.
23
-
24
- Include in your main urls.py:
25
-
26
- from django_cfg.urls import get_toolkit_urls
27
-
28
- urlpatterns = [
29
- path('admin/', admin.site.urls),
30
- path('toolkit/', include(get_toolkit_urls())),
31
- ]
32
- """
33
- return urlpatterns