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.
Files changed (85) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/knowbase/tasks/archive_tasks.py +6 -6
  3. django_cfg/apps/knowbase/tasks/document_processing.py +3 -3
  4. django_cfg/apps/knowbase/tasks/external_data_tasks.py +2 -2
  5. django_cfg/apps/knowbase/tasks/maintenance.py +3 -3
  6. django_cfg/apps/payments/config/__init__.py +15 -37
  7. django_cfg/apps/payments/config/module.py +30 -122
  8. django_cfg/apps/payments/config/providers.py +28 -16
  9. django_cfg/apps/payments/config/settings.py +53 -93
  10. django_cfg/apps/payments/config/utils.py +10 -156
  11. django_cfg/apps/payments/management/__init__.py +3 -0
  12. django_cfg/apps/payments/management/commands/README.md +178 -0
  13. django_cfg/apps/payments/management/commands/__init__.py +3 -0
  14. django_cfg/apps/payments/management/commands/currency_stats.py +323 -0
  15. django_cfg/apps/payments/management/commands/populate_currencies.py +246 -0
  16. django_cfg/apps/payments/management/commands/update_currencies.py +336 -0
  17. django_cfg/apps/payments/managers/currency_manager.py +65 -14
  18. django_cfg/apps/payments/middleware/api_access.py +33 -0
  19. django_cfg/apps/payments/migrations/0001_initial.py +94 -1
  20. django_cfg/apps/payments/models/payments.py +110 -0
  21. django_cfg/apps/payments/services/__init__.py +7 -1
  22. django_cfg/apps/payments/services/core/balance_service.py +14 -16
  23. django_cfg/apps/payments/services/core/fallback_service.py +432 -0
  24. django_cfg/apps/payments/services/core/payment_service.py +212 -29
  25. django_cfg/apps/payments/services/core/subscription_service.py +15 -17
  26. django_cfg/apps/payments/services/internal_types.py +31 -0
  27. django_cfg/apps/payments/services/monitoring/__init__.py +22 -0
  28. django_cfg/apps/payments/services/monitoring/api_schemas.py +222 -0
  29. django_cfg/apps/payments/services/monitoring/provider_health.py +372 -0
  30. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  31. django_cfg/apps/payments/services/providers/cryptapi.py +14 -3
  32. django_cfg/apps/payments/services/providers/cryptomus.py +310 -0
  33. django_cfg/apps/payments/services/providers/registry.py +4 -0
  34. django_cfg/apps/payments/services/security/__init__.py +34 -0
  35. django_cfg/apps/payments/services/security/error_handler.py +637 -0
  36. django_cfg/apps/payments/services/security/payment_notifications.py +342 -0
  37. django_cfg/apps/payments/services/security/webhook_validator.py +475 -0
  38. django_cfg/apps/payments/signals/api_key_signals.py +10 -0
  39. django_cfg/apps/payments/signals/payment_signals.py +3 -2
  40. django_cfg/apps/payments/tasks/__init__.py +12 -0
  41. django_cfg/apps/payments/tasks/webhook_processing.py +177 -0
  42. django_cfg/apps/payments/utils/__init__.py +7 -4
  43. django_cfg/apps/payments/utils/billing_utils.py +342 -0
  44. django_cfg/apps/payments/utils/config_utils.py +2 -0
  45. django_cfg/apps/payments/views/payment_views.py +40 -2
  46. django_cfg/apps/payments/views/webhook_views.py +266 -0
  47. django_cfg/apps/payments/viewsets.py +65 -0
  48. django_cfg/cli/README.md +2 -2
  49. django_cfg/cli/commands/create_project.py +1 -1
  50. django_cfg/cli/commands/info.py +1 -1
  51. django_cfg/cli/main.py +1 -1
  52. django_cfg/cli/utils.py +5 -5
  53. django_cfg/core/config.py +18 -4
  54. django_cfg/models/payments.py +547 -0
  55. django_cfg/models/tasks.py +51 -2
  56. django_cfg/modules/base.py +11 -5
  57. django_cfg/modules/django_currency/README.md +104 -269
  58. django_cfg/modules/django_currency/__init__.py +99 -41
  59. django_cfg/modules/django_currency/clients/__init__.py +11 -0
  60. django_cfg/modules/django_currency/clients/coingecko_client.py +257 -0
  61. django_cfg/modules/django_currency/clients/yfinance_client.py +246 -0
  62. django_cfg/modules/django_currency/core/__init__.py +42 -0
  63. django_cfg/modules/django_currency/core/converter.py +169 -0
  64. django_cfg/modules/django_currency/core/exceptions.py +28 -0
  65. django_cfg/modules/django_currency/core/models.py +54 -0
  66. django_cfg/modules/django_currency/database/__init__.py +25 -0
  67. django_cfg/modules/django_currency/database/database_loader.py +507 -0
  68. django_cfg/modules/django_currency/utils/__init__.py +9 -0
  69. django_cfg/modules/django_currency/utils/cache.py +92 -0
  70. django_cfg/registry/core.py +10 -0
  71. django_cfg/template_archive/__init__.py +0 -0
  72. django_cfg/template_archive/django_sample.zip +0 -0
  73. {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/METADATA +10 -6
  74. {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/RECORD +77 -51
  75. django_cfg/apps/agents/examples/__init__.py +0 -3
  76. django_cfg/apps/agents/examples/simple_example.py +0 -161
  77. django_cfg/apps/knowbase/examples/__init__.py +0 -3
  78. django_cfg/apps/knowbase/examples/external_data_usage.py +0 -191
  79. django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +0 -199
  80. django_cfg/modules/django_currency/cache.py +0 -430
  81. django_cfg/modules/django_currency/converter.py +0 -324
  82. django_cfg/modules/django_currency/service.py +0 -277
  83. {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/WHEEL +0 -0
  84. {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/entry_points.txt +0 -0
  85. {django_cfg-1.2.23.dist-info → django_cfg-1.2.27.dist-info}/licenses/LICENSE +0 -0
@@ -1,430 +0,0 @@
1
- """
2
- Currency Cache Management for django_currency.
3
-
4
- Handles file-based and memory caching of currency rates with TTL support.
5
- Uses YAML format for better readability and human-friendly configuration.
6
- """
7
-
8
- import logging
9
- import yaml
10
- from datetime import datetime, timedelta
11
- from pathlib import Path
12
- from typing import Dict, Optional, Any
13
- from cachetools import TTLCache
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- class CurrencyCache:
19
- """
20
- Currency cache manager with file and memory caching.
21
-
22
- Features:
23
- - TTL-based memory cache (24 hours default)
24
- - File-based persistent cache
25
- - Automatic cache invalidation
26
- - Thread-safe operations
27
- """
28
-
29
- DEFAULT_TTL = 86400 # 24 hours in seconds
30
- DEFAULT_CACHE_SIZE = 1000
31
- CACHE_FILENAME = "currency_rates.yaml"
32
-
33
- def __init__(
34
- self,
35
- cache_dir: Optional[Path] = None,
36
- ttl: int = DEFAULT_TTL,
37
- max_size: int = DEFAULT_CACHE_SIZE
38
- ):
39
- """
40
- Initialize currency cache.
41
-
42
- Args:
43
- cache_dir: Directory for file cache
44
- ttl: Time-to-live for memory cache in seconds
45
- max_size: Maximum number of items in memory cache
46
- """
47
- # Default cache directory inside django-cfg structure
48
- if cache_dir is None:
49
- def make_cache_dir(cache_dir=Path.cwd()):
50
- return Path(cache_dir) / ".cache" / "currency"
51
- default_cache_dir = make_cache_dir()
52
-
53
- try:
54
- from django.conf import settings
55
- # Check if Django is configured before accessing settings
56
- if settings.configured and hasattr(settings, 'BASE_DIR'):
57
- default_cache_dir = make_cache_dir(settings.BASE_DIR)
58
- except (ImportError, Exception):
59
- pass
60
- else:
61
- default_cache_dir = Path(cache_dir)
62
-
63
- self.cache_dir = default_cache_dir
64
- self.cache_dir.mkdir(parents=True, exist_ok=True)
65
- self.cache_file = self.cache_dir / self.CACHE_FILENAME
66
-
67
- # TTL Cache for in-memory storage
68
- self._memory_cache = TTLCache(maxsize=max_size, ttl=ttl)
69
-
70
- # File cache metadata
71
- self._file_cache_data = None
72
- self._file_cache_timestamp = None
73
-
74
- logger.info(f"Currency cache initialized: {self.cache_dir}")
75
-
76
- def get_rates(self, source: str = "cbr") -> Optional[Dict[str, float]]:
77
- """
78
- Get currency rates from cache.
79
-
80
- Args:
81
- source: Rate source identifier (e.g., 'cbr', 'ecb')
82
-
83
- Returns:
84
- Dictionary of currency rates or None if not cached
85
- """
86
- cache_key = f"rates_{source}"
87
-
88
- # Try memory cache first
89
- if cache_key in self._memory_cache:
90
- logger.debug(f"Retrieved rates from memory cache: {source}")
91
- return self._memory_cache[cache_key]
92
-
93
- # Try file cache
94
- file_rates = self._load_from_file(source)
95
- if file_rates:
96
- # Update memory cache
97
- self._memory_cache[cache_key] = file_rates
98
- logger.debug(f"Retrieved rates from file cache: {source}")
99
- return file_rates
100
-
101
- logger.debug(f"No cached rates found for source: {source}")
102
- return None
103
-
104
- def set_rates(
105
- self,
106
- rates: Dict[str, float],
107
- source: str = "cbr",
108
- save_to_file: bool = True
109
- ) -> bool:
110
- """
111
- Store currency rates in cache.
112
-
113
- Args:
114
- rates: Dictionary of currency rates
115
- source: Rate source identifier
116
- save_to_file: Whether to persist to file
117
-
118
- Returns:
119
- True if successfully cached, False otherwise
120
- """
121
- try:
122
- cache_key = f"rates_{source}"
123
-
124
- # Store in memory cache
125
- self._memory_cache[cache_key] = rates
126
-
127
- # Store in file cache if requested
128
- if save_to_file:
129
- self._save_to_file(rates, source)
130
-
131
- logger.info(f"Cached {len(rates)} rates for source: {source}")
132
- return True
133
-
134
- except Exception as e:
135
- logger.error(f"Failed to cache rates for {source}: {e}")
136
- return False
137
-
138
- def _load_from_file(self, source: str) -> Optional[Dict[str, float]]:
139
- """Load rates from file cache."""
140
- try:
141
- if not self.cache_file.exists():
142
- return None
143
-
144
- with open(self.cache_file, 'r', encoding='utf-8') as f:
145
- data = yaml.safe_load(f)
146
-
147
- # Check if data is for the requested source
148
- if data.get('source') != source:
149
- return None
150
-
151
- # Check if data is still valid (not expired)
152
- timestamp_str = data.get('timestamp')
153
- if timestamp_str:
154
- timestamp = datetime.fromisoformat(timestamp_str)
155
- if datetime.now() - timestamp > timedelta(seconds=self.DEFAULT_TTL):
156
- logger.debug(f"File cache expired for source: {source}")
157
- return None
158
-
159
- rates = data.get('rates', {})
160
- if rates:
161
- self._file_cache_data = data
162
- self._file_cache_timestamp = timestamp if timestamp_str else None
163
- return rates
164
-
165
- except Exception as e:
166
- logger.error(f"Failed to load file cache for {source}: {e}")
167
-
168
- return None
169
-
170
- def _save_to_file(self, rates: Dict[str, float], source: str) -> bool:
171
- """Save rates to file cache."""
172
- try:
173
- now = datetime.now()
174
- data = {
175
- 'source': source,
176
- 'timestamp': now.isoformat(),
177
- 'rates': rates,
178
- 'metadata': {
179
- 'count': len(rates),
180
- 'cache_version': '1.0',
181
- 'format': 'YAML',
182
- 'description': f'Currency rates from {source.upper()} API',
183
- 'updated_at': now.strftime('%Y-%m-%d %H:%M:%S UTC'),
184
- 'ttl_hours': self.DEFAULT_TTL // 3600,
185
- 'next_update': (now + timedelta(seconds=self.DEFAULT_TTL)).strftime('%Y-%m-%d %H:%M:%S UTC')
186
- }
187
- }
188
-
189
- # Atomic write using temporary file
190
- temp_file = self.cache_file.with_suffix('.tmp')
191
- with open(temp_file, 'w', encoding='utf-8') as f:
192
- # Write header comment
193
- f.write(f"# Currency Rates Cache - Django CFG\n")
194
- f.write(f"# Generated: {now.strftime('%Y-%m-%d %H:%M:%S UTC')}\n")
195
- f.write(f"# Source: {source.upper()} API\n")
196
- f.write(f"# Rates count: {len(rates)}\n")
197
- f.write(f"# TTL: {self.DEFAULT_TTL // 3600} hours\n")
198
- f.write(f"# Auto-generated - do not edit manually\n\n")
199
-
200
- yaml.safe_dump(
201
- data,
202
- f,
203
- default_flow_style=False,
204
- allow_unicode=True,
205
- sort_keys=False,
206
- indent=2,
207
- width=120
208
- )
209
-
210
- # Move temp file to final location
211
- temp_file.replace(self.cache_file)
212
-
213
- self._file_cache_data = data
214
- self._file_cache_timestamp = datetime.now()
215
-
216
- logger.debug(f"Saved {len(rates)} rates to file cache: {source}")
217
- return True
218
-
219
- except Exception as e:
220
- logger.error(f"Failed to save file cache for {source}: {e}")
221
- return False
222
-
223
- def clear_cache(self, source: Optional[str] = None) -> bool:
224
- """
225
- Clear cache for specific source or all sources.
226
-
227
- Args:
228
- source: Source to clear (None for all)
229
-
230
- Returns:
231
- True if successfully cleared
232
- """
233
- try:
234
- if source:
235
- # Clear specific source from memory
236
- cache_key = f"rates_{source}"
237
- self._memory_cache.pop(cache_key, None)
238
-
239
- # Clear file cache if it matches the source
240
- if (self._file_cache_data and
241
- self._file_cache_data.get('source') == source):
242
- if self.cache_file.exists():
243
- self.cache_file.unlink()
244
- self._file_cache_data = None
245
- self._file_cache_timestamp = None
246
-
247
- logger.info(f"Cleared cache for source: {source}")
248
- else:
249
- # Clear all caches
250
- self._memory_cache.clear()
251
- if self.cache_file.exists():
252
- self.cache_file.unlink()
253
- self._file_cache_data = None
254
- self._file_cache_timestamp = None
255
-
256
- logger.info("Cleared all currency caches")
257
-
258
- return True
259
-
260
- except Exception as e:
261
- logger.error(f"Failed to clear cache: {e}")
262
- return False
263
-
264
- def get_cache_info(self) -> Dict[str, Any]:
265
- """Get information about cache status."""
266
- try:
267
- memory_info = {
268
- 'size': len(self._memory_cache),
269
- 'max_size': self._memory_cache.maxsize,
270
- 'ttl': self._memory_cache.ttl,
271
- 'keys': list(self._memory_cache.keys())
272
- }
273
-
274
- file_info = {
275
- 'exists': self.cache_file.exists(),
276
- 'path': str(self.cache_file),
277
- 'size_bytes': self.cache_file.stat().st_size if self.cache_file.exists() else 0,
278
- }
279
-
280
- if self._file_cache_data:
281
- file_info.update({
282
- 'source': self._file_cache_data.get('source'),
283
- 'timestamp': self._file_cache_data.get('timestamp'),
284
- 'rates_count': len(self._file_cache_data.get('rates', {}))
285
- })
286
-
287
- return {
288
- 'cache_directory': str(self.cache_dir),
289
- 'memory_cache': memory_info,
290
- 'file_cache': file_info,
291
- 'status': 'active'
292
- }
293
-
294
- except Exception as e:
295
- logger.error(f"Failed to get cache info: {e}")
296
- return {
297
- 'status': 'error',
298
- 'error': str(e)
299
- }
300
-
301
- def is_cache_valid(self, source: str = "cbr") -> bool:
302
- """Check if cache is valid and not expired."""
303
- cache_key = f"rates_{source}"
304
-
305
- # Check memory cache
306
- if cache_key in self._memory_cache:
307
- return True
308
-
309
- # Check file cache
310
- if self._file_cache_timestamp:
311
- age = datetime.now() - self._file_cache_timestamp
312
- return age.total_seconds() < self.DEFAULT_TTL
313
-
314
- return False
315
-
316
- def get_cache_age(self, source: str = "cbr") -> Optional[timedelta]:
317
- """Get age of cached data."""
318
- if self._file_cache_timestamp:
319
- return datetime.now() - self._file_cache_timestamp
320
- return None
321
-
322
- def _get_currency_description(self, currency_code: str) -> str:
323
- """Get human-readable description for currency code."""
324
- currency_names = {
325
- 'USD': 'US Dollar',
326
- 'EUR': 'Euro',
327
- 'GBP': 'British Pound',
328
- 'JPY': 'Japanese Yen',
329
- 'CNY': 'Chinese Yuan',
330
- 'KRW': 'South Korean Won',
331
- 'RUB': 'Russian Ruble',
332
- 'CAD': 'Canadian Dollar',
333
- 'AUD': 'Australian Dollar',
334
- 'CHF': 'Swiss Franc',
335
- 'SEK': 'Swedish Krona',
336
- 'NOK': 'Norwegian Krone',
337
- 'DKK': 'Danish Krone',
338
- 'PLN': 'Polish Zloty',
339
- 'CZK': 'Czech Koruna',
340
- 'HUF': 'Hungarian Forint',
341
- 'TRY': 'Turkish Lira',
342
- 'BRL': 'Brazilian Real',
343
- 'MXN': 'Mexican Peso',
344
- 'INR': 'Indian Rupee',
345
- 'SGD': 'Singapore Dollar',
346
- 'HKD': 'Hong Kong Dollar',
347
- 'NZD': 'New Zealand Dollar',
348
- 'ZAR': 'South African Rand',
349
- 'THB': 'Thai Baht',
350
- 'MYR': 'Malaysian Ringgit',
351
- 'PHP': 'Philippine Peso',
352
- 'IDR': 'Indonesian Rupiah',
353
- 'VND': 'Vietnamese Dong',
354
- }
355
- return currency_names.get(currency_code, currency_code)
356
-
357
- def export_rates_yaml(self, source: str = "cbr", output_file: Optional[Path] = None) -> str:
358
- """
359
- Export rates to a formatted YAML file with comments.
360
-
361
- Args:
362
- source: Rate source to export
363
- output_file: Optional output file path
364
-
365
- Returns:
366
- YAML content as string
367
- """
368
- rates = self.get_rates(source)
369
- if not rates:
370
- return "# No rates available for export"
371
-
372
- now = datetime.now()
373
-
374
- # Create structured data with comments
375
- yaml_content = f"""# Currency Exchange Rates - Django CFG
376
- # Source: {source.upper()} API
377
- # Generated: {now.strftime('%Y-%m-%d %H:%M:%S UTC')}
378
- # Total currencies: {len(rates)}
379
- # Cache TTL: {self.DEFAULT_TTL // 3600} hours
380
- #
381
- # Format: currency_code: rate_to_base_currency
382
- # Base currency for CBR: RUB (Russian Ruble)
383
- # Base currency for ECB: EUR (Euro)
384
-
385
- source: {source}
386
- timestamp: {now.isoformat()}
387
-
388
- metadata:
389
- count: {len(rates)}
390
- cache_version: '1.0'
391
- format: 'YAML'
392
- description: 'Currency rates from {source.upper()} API'
393
- updated_at: '{now.strftime('%Y-%m-%d %H:%M:%S UTC')}'
394
- ttl_hours: {self.DEFAULT_TTL // 3600}
395
- next_update: '{(now + timedelta(seconds=self.DEFAULT_TTL)).strftime('%Y-%m-%d %H:%M:%S UTC')}'
396
-
397
- # Currency Rates
398
- rates:
399
- """
400
-
401
- # Sort currencies: major currencies first, then alphabetically
402
- major_currencies = ['USD', 'EUR', 'GBP', 'JPY', 'CNY', 'KRW', 'RUB', 'CAD', 'AUD', 'CHF']
403
- sorted_currencies = []
404
-
405
- # Add major currencies first (if they exist)
406
- for curr in major_currencies:
407
- if curr in rates:
408
- sorted_currencies.append(curr)
409
-
410
- # Add remaining currencies alphabetically
411
- remaining = sorted([curr for curr in rates.keys() if curr not in major_currencies])
412
- sorted_currencies.extend(remaining)
413
-
414
- # Add rates with comments
415
- for currency in sorted_currencies:
416
- rate = rates[currency]
417
- description = self._get_currency_description(currency)
418
- yaml_content += f" {currency}: {rate:<12.6f} # {description}\n"
419
-
420
- # Save to file if requested
421
- if output_file:
422
- try:
423
- output_file.parent.mkdir(parents=True, exist_ok=True)
424
- with open(output_file, 'w', encoding='utf-8') as f:
425
- f.write(yaml_content)
426
- logger.info(f"Exported rates to: {output_file}")
427
- except Exception as e:
428
- logger.error(f"Failed to export rates to file: {e}")
429
-
430
- return yaml_content