wrd 0.1.41__py3-none-any.whl → 1.0.2__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.
dune/smart_env_manager.py DELETED
@@ -1,573 +0,0 @@
1
- """
2
- Inteligentny menedżer zmiennych środowiskowych z automatycznym wykrywaniem i validacją.
3
- """
4
-
5
- import os
6
- import re
7
- import json
8
- import subprocess
9
- import platform
10
- from typing import Dict, List, Any, Optional, Tuple
11
- from pathlib import Path
12
- from loguru import logger
13
- from dataclasses import dataclass
14
-
15
-
16
- @dataclass
17
- class EnvVariable:
18
- """Definicja zmiennej środowiskowej."""
19
- name: str
20
- description: str
21
- type: str # str, int, bool, path, url, email
22
- required: bool
23
- default_value: Optional[str] = None
24
- validation_pattern: Optional[str] = None
25
- auto_detect_methods: List[str] = None
26
- examples: List[str] = None
27
-
28
-
29
- class SmartEnvManager:
30
- """Inteligentny menedżer zmiennych środowiskowych."""
31
-
32
- def __init__(self):
33
- self.env_definitions = self._load_env_definitions()
34
- self.auto_detected = {}
35
- self.user_provided = {}
36
-
37
- def _load_env_definitions(self) -> Dict[str, EnvVariable]:
38
- """Ładuje definicje zmiennych środowiskowych."""
39
-
40
- return {
41
- # IMAP Configuration
42
- "IMAP_SERVER": EnvVariable(
43
- name="IMAP_SERVER",
44
- description="Adres serwera IMAP (np. imap.gmail.com)",
45
- type="str",
46
- required=True,
47
- validation_pattern=r"^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
48
- auto_detect_methods=["common_providers", "dns_lookup"],
49
- examples=["imap.gmail.com", "imap.outlook.com", "mail.company.com"]
50
- ),
51
-
52
- "IMAP_PORT": EnvVariable(
53
- name="IMAP_PORT",
54
- description="Port serwera IMAP",
55
- type="int",
56
- required=False,
57
- default_value="993",
58
- validation_pattern=r"^(143|993|[1-9]\d{1,4})$",
59
- examples=["143", "993"]
60
- ),
61
-
62
- "IMAP_USERNAME": EnvVariable(
63
- name="IMAP_USERNAME",
64
- description="Nazwa użytkownika IMAP (zazwyczaj email)",
65
- type="email",
66
- required=True,
67
- validation_pattern=r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
68
- auto_detect_methods=["git_config", "system_user"],
69
- examples=["user@gmail.com", "john.doe@company.com"]
70
- ),
71
-
72
- "IMAP_PASSWORD": EnvVariable(
73
- name="IMAP_PASSWORD",
74
- description="Hasło IMAP (lub hasło aplikacji)",
75
- type="str",
76
- required=True,
77
- auto_detect_methods=["keyring", "env_file"]
78
- ),
79
-
80
- # Database Configuration
81
- "DATABASE_URL": EnvVariable(
82
- name="DATABASE_URL",
83
- description="URL połączenia z bazą danych",
84
- type="url",
85
- required=False,
86
- validation_pattern=r"^(postgresql|mysql|sqlite)://.*",
87
- auto_detect_methods=["docker_compose", "local_services"],
88
- examples=[
89
- "postgresql://user:pass@localhost:5432/dbname",
90
- "sqlite:///data.db",
91
- "mysql://user:pass@localhost:3306/dbname"
92
- ]
93
- ),
94
-
95
- # API Configuration
96
- "API_KEY": EnvVariable(
97
- name="API_KEY",
98
- description="Klucz API do autoryzacji",
99
- type="str",
100
- required=False,
101
- auto_detect_methods=["env_file", "keyring"]
102
- ),
103
-
104
- "API_BASE_URL": EnvVariable(
105
- name="API_BASE_URL",
106
- description="Bazowy URL API",
107
- type="url",
108
- required=False,
109
- validation_pattern=r"^https?://.*",
110
- examples=["https://api.example.com", "http://localhost:8000"]
111
- ),
112
-
113
- # File Paths
114
- "INPUT_DIR": EnvVariable(
115
- name="INPUT_DIR",
116
- description="Katalog z plikami wejściowymi",
117
- type="path",
118
- required=False,
119
- default_value="./input",
120
- auto_detect_methods=["current_directory", "common_paths"],
121
- examples=["./data", "/home/user/documents", "C:\\Data"]
122
- ),
123
-
124
- "OUTPUT_DIR": EnvVariable(
125
- name="OUTPUT_DIR",
126
- description="Katalog dla plików wyjściowych",
127
- type="path",
128
- required=False,
129
- default_value="./output",
130
- auto_detect_methods=["current_directory"],
131
- examples=["./output", "/tmp/results", "C:\\Results"]
132
- ),
133
-
134
- # System Configuration
135
- "LOG_LEVEL": EnvVariable(
136
- name="LOG_LEVEL",
137
- description="Poziom logowania",
138
- type="str",
139
- required=False,
140
- default_value="INFO",
141
- validation_pattern=r"^(DEBUG|INFO|WARNING|ERROR|CRITICAL)$",
142
- examples=["DEBUG", "INFO", "WARNING", "ERROR"]
143
- )
144
- }
145
-
146
- def auto_detect_environment_variables(self, required_vars: List[str]) -> Dict[str, str]:
147
- """Automatycznie wykrywa zmienne środowiskowe."""
148
-
149
- logger.info("🔍 Automatyczne wykrywanie zmiennych środowiskowych...")
150
-
151
- detected = {}
152
-
153
- for var_name in required_vars:
154
- if var_name in self.env_definitions:
155
- value = self._auto_detect_single_var(self.env_definitions[var_name])
156
- if value:
157
- detected[var_name] = value
158
- self.auto_detected[var_name] = value
159
- logger.success(f"✅ Auto-wykryto {var_name}: {value}")
160
-
161
- return detected
162
-
163
- def _auto_detect_single_var(self, env_var: EnvVariable) -> Optional[str]:
164
- """Automatycznie wykrywa pojedynczą zmienną."""
165
-
166
- if not env_var.auto_detect_methods:
167
- return None
168
-
169
- for method in env_var.auto_detect_methods:
170
- try:
171
- value = self._run_detection_method(method, env_var)
172
- if value and self._validate_env_value(value, env_var):
173
- return value
174
- except Exception as e:
175
- logger.debug(f"Błąd detekcji {method} dla {env_var.name}: {e}")
176
-
177
- return None
178
-
179
- def _run_detection_method(self, method: str, env_var: EnvVariable) -> Optional[str]:
180
- """Uruchamia konkretną metodę detekcji."""
181
-
182
- if method == "git_config":
183
- return self._detect_from_git_config(env_var)
184
- elif method == "system_user":
185
- return self._detect_from_system_user(env_var)
186
- elif method == "common_providers":
187
- return self._detect_common_providers(env_var)
188
- elif method == "dns_lookup":
189
- return self._detect_via_dns(env_var)
190
- elif method == "docker_compose":
191
- return self._detect_from_docker_compose(env_var)
192
- elif method == "local_services":
193
- return self._detect_local_services(env_var)
194
- elif method == "current_directory":
195
- return self._detect_from_current_dir(env_var)
196
- elif method == "common_paths":
197
- return self._detect_common_paths(env_var)
198
- elif method == "env_file":
199
- return self._detect_from_env_files(env_var)
200
- elif method == "keyring":
201
- return self._detect_from_keyring(env_var)
202
-
203
- return None
204
-
205
- def _detect_from_git_config(self, env_var: EnvVariable) -> Optional[str]:
206
- """Wykrywa z konfiguracji Git."""
207
-
208
- if env_var.type != "email":
209
- return None
210
-
211
- try:
212
- result = subprocess.run(
213
- ["git", "config", "user.email"],
214
- capture_output=True, text=True, timeout=5
215
- )
216
- if result.returncode == 0:
217
- return result.stdout.strip()
218
- except:
219
- pass
220
-
221
- return None
222
-
223
- def _detect_from_system_user(self, env_var: EnvVariable) -> Optional[str]:
224
- """Wykrywa z informacji o użytkowniku systemu."""
225
-
226
- if env_var.type == "email":
227
- username = os.getenv("USER") or os.getenv("USERNAME")
228
- if username:
229
- # Spróbuj utworzyć email na podstawie nazwy użytkownika
230
- common_domains = ["gmail.com", "outlook.com", "company.com"]
231
- for domain in common_domains:
232
- potential_email = f"{username}@{domain}"
233
- if self._validate_env_value(potential_email, env_var):
234
- return potential_email
235
-
236
- return None
237
-
238
- def _detect_common_providers(self, env_var: EnvVariable) -> Optional[str]:
239
- """Wykrywa z listy popularnych dostawców."""
240
-
241
- if env_var.name == "IMAP_SERVER":
242
- # Lista popularnych serwerów IMAP
243
- common_servers = [
244
- "imap.gmail.com",
245
- "imap.outlook.com",
246
- "imap.mail.yahoo.com",
247
- "localhost"
248
- ]
249
-
250
- # Sprawdź czy któryś jest dostępny
251
- for server in common_servers:
252
- if self._test_server_connectivity(server, 993) or self._test_server_connectivity(server, 143):
253
- return server
254
-
255
- return None
256
-
257
- def _detect_via_dns(self, env_var: EnvVariable) -> Optional[str]:
258
- """Wykrywa przez zapytania DNS."""
259
-
260
- # Implementacja sprawdzania rekordów MX dla domen
261
- return None
262
-
263
- def _detect_from_docker_compose(self, env_var: EnvVariable) -> Optional[str]:
264
- """Wykrywa z plików docker-compose."""
265
-
266
- compose_files = ["docker-compose.yml", "docker-compose.yaml"]
267
-
268
- for compose_file in compose_files:
269
- if Path(compose_file).exists():
270
- try:
271
- import yaml
272
- with open(compose_file, 'r') as f:
273
- compose_data = yaml.safe_load(f)
274
-
275
- # Szukaj baz danych w serwisach
276
- services = compose_data.get("services", {})
277
- for service_name, service_config in services.items():
278
- if "postgres" in service_name.lower() or service_config.get("image", "").startswith("postgres"):
279
- return f"postgresql://user:password@localhost:5432/dbname"
280
- elif "mysql" in service_name.lower() or service_config.get("image", "").startswith("mysql"):
281
- return f"mysql://user:password@localhost:3306/dbname"
282
-
283
- except:
284
- pass
285
-
286
- return None
287
-
288
- def _detect_local_services(self, env_var: EnvVariable) -> Optional[str]:
289
- """Wykrywa lokalne usługi."""
290
-
291
- if env_var.name == "DATABASE_URL":
292
- # Sprawdź popularne porty baz danych
293
- db_ports = {
294
- 5432: "postgresql://user:password@localhost:5432/dbname",
295
- 3306: "mysql://user:password@localhost:3306/dbname",
296
- 6379: "redis://localhost:6379"
297
- }
298
-
299
- for port, url in db_ports.items():
300
- if self._test_server_connectivity("localhost", port):
301
- return url
302
-
303
- return None
304
-
305
- def _detect_from_current_dir(self, env_var: EnvVariable) -> Optional[str]:
306
- """Wykrywa z bieżącego katalogu."""
307
-
308
- if env_var.type == "path":
309
- current_dir = Path.cwd()
310
-
311
- if env_var.name == "INPUT_DIR":
312
- candidates = ["input", "data", "src/data", "inputs"]
313
- elif env_var.name == "OUTPUT_DIR":
314
- candidates = ["output", "results", "out", "outputs"]
315
- else:
316
- return str(current_dir)
317
-
318
- for candidate in candidates:
319
- path = current_dir / candidate
320
- if path.exists():
321
- return str(path)
322
-
323
- # Zwróć domyślną wartość względem bieżącego katalogu
324
- return str(current_dir / env_var.default_value.lstrip("./")) if env_var.default_value else None
325
-
326
- return None
327
-
328
- def _detect_common_paths(self, env_var: EnvVariable) -> Optional[str]:
329
- """Wykrywa z popularnych ścieżek."""
330
-
331
- if env_var.type == "path":
332
- if platform.system() == "Windows":
333
- common_paths = ["C:\\Data", "C:\\Users\\%USERNAME%\\Documents"]
334
- else:
335
- common_paths = ["/home/$USER/data", "/tmp", "/var/data"]
336
-
337
- for path_template in common_paths:
338
- path = os.path.expandvars(path_template)
339
- if Path(path).exists():
340
- return path
341
-
342
- return None
343
-
344
- def _detect_from_env_files(self, env_var: EnvVariable) -> Optional[str]:
345
- """Wykrywa z istniejących plików .env."""
346
-
347
- env_files = [".env", ".env.local", ".env.example"]
348
-
349
- for env_file in env_files:
350
- if Path(env_file).exists():
351
- try:
352
- with open(env_file, 'r') as f:
353
- for line in f:
354
- line = line.strip()
355
- if line.startswith(f"{env_var.name}="):
356
- value = line.split("=", 1)[1]
357
- return value
358
- except:
359
- pass
360
-
361
- return None
362
-
363
- def _detect_from_keyring(self, env_var: EnvVariable) -> Optional[str]:
364
- """Wykrywa z systemowego keyring."""
365
-
366
- try:
367
- import keyring
368
- return keyring.get_password("dune", env_var.name)
369
- except ImportError:
370
- return None
371
- except:
372
- return None
373
-
374
- def _test_server_connectivity(self, host: str, port: int, timeout: int = 3) -> bool:
375
- """Testuje połączenie z serwerem."""
376
-
377
- import socket
378
-
379
- try:
380
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
381
- sock.settimeout(timeout)
382
- result = sock.connect_ex((host, port))
383
- sock.close()
384
- return result == 0
385
- except:
386
- return False
387
-
388
- def _validate_env_value(self, value: str, env_var: EnvVariable) -> bool:
389
- """Waliduje wartość zmiennej środowiskowej."""
390
-
391
- if not value:
392
- return False
393
-
394
- # Walidacja przez pattern
395
- if env_var.validation_pattern:
396
- if not re.match(env_var.validation_pattern, value):
397
- return False
398
-
399
- # Walidacja przez typ
400
- if env_var.type == "int":
401
- try:
402
- int(value)
403
- except ValueError:
404
- return False
405
- elif env_var.type == "bool":
406
- if value.lower() not in ["true", "false", "1", "0", "yes", "no"]:
407
- return False
408
- elif env_var.type == "path":
409
- # Sprawdź czy ścieżka jest sensowna
410
- if not re.match(r"^[a-zA-Z0-9._/\\-]+$", value):
411
- return False
412
- elif env_var.type == "url":
413
- if not value.startswith(("http://", "https://", "ftp://", "file://")):
414
- return False
415
-
416
- return True
417
-
418
- def interactive_env_collection(self, required_vars: List[str],
419
- optional_vars: List[str] = None) -> Dict[str, str]:
420
- """Interaktywnie zbiera zmienne środowiskowe."""
421
-
422
- print(f"\n🔧 KONFIGURACJA ZMIENNYCH ŚRODOWISKOWYCH")
423
- print("=" * 50)
424
-
425
- # Najpierw spróbuj auto-detekcji
426
- auto_detected = self.auto_detect_environment_variables(required_vars)
427
-
428
- collected = {}
429
-
430
- # Zbierz wymagane zmienne
431
- for var_name in required_vars:
432
- value = self._collect_single_env_var(var_name, auto_detected.get(var_name), required=True)
433
- if value:
434
- collected[var_name] = value
435
-
436
- # Zbierz opcjonalne zmienne
437
- if optional_vars:
438
- print(f"\n🔧 Zmienne opcjonalne:")
439
- for var_name in optional_vars:
440
- if self._ask_yes_no(f"Skonfigurować {var_name}?"):
441
- value = self._collect_single_env_var(var_name, auto_detected.get(var_name), required=False)
442
- if value:
443
- collected[var_name] = value
444
-
445
- return collected
446
-
447
- def _collect_single_env_var(self, var_name: str, auto_value: Optional[str],
448
- required: bool = True) -> Optional[str]:
449
- """Zbiera pojedynczą zmienną środowiskową."""
450
-
451
- env_def = self.env_definitions.get(var_name)
452
- if not env_def:
453
- # Stwórz podstawową definicję
454
- env_def = EnvVariable(var_name, f"Zmienna {var_name}", "str", required)
455
-
456
- # Sprawdź obecną wartość
457
- current_value = os.getenv(var_name) or auto_value
458
-
459
- prompt = f"📌 {var_name}"
460
- if env_def.description:
461
- prompt += f" ({env_def.description})"
462
-
463
- if current_value:
464
- prompt += f" [obecna: {current_value}]"
465
- elif env_def.default_value:
466
- prompt += f" [domyślna: {env_def.default_value}]"
467
-
468
- if not required:
469
- prompt += " [opcjonalna]"
470
-
471
- # Pokaż przykłady
472
- if env_def.examples:
473
- print(f" 💡 Przykłady: {', '.join(env_def.examples[:3])}")
474
-
475
- while True:
476
- try:
477
- user_input = input(f"{prompt}: ").strip()
478
-
479
- # Użyj obecnej wartości jeśli nic nie podano
480
- if not user_input:
481
- if current_value:
482
- return current_value
483
- elif env_def.default_value:
484
- return env_def.default_value
485
- elif not required:
486
- return None
487
- else:
488
- print("❌ Ta zmienna jest wymagana!")
489
- continue
490
-
491
- # Waliduj wartość
492
- if self._validate_env_value(user_input, env_def):
493
- return user_input
494
- else:
495
- print(f"❌ Nieprawidłowa wartość dla {env_def.type}")
496
- if env_def.validation_pattern:
497
- print(f" Wzorzec: {env_def.validation_pattern}")
498
- continue
499
-
500
- except KeyboardInterrupt:
501
- print("\n⚠️ Przerwano przez użytkownika")
502
- return None
503
-
504
- def _ask_yes_no(self, question: str, default: bool = None) -> bool:
505
- """Zadaje pytanie tak/nie."""
506
-
507
- if default is True:
508
- prompt = f"{question} [T/n]: "
509
- elif default is False:
510
- prompt = f"{question} [t/N]: "
511
- else:
512
- prompt = f"{question} [t/n]: "
513
-
514
- while True:
515
- try:
516
- answer = input(prompt).strip().lower()
517
-
518
- if not answer and default is not None:
519
- return default
520
-
521
- if answer in ["t", "tak", "y", "yes", "1", "true"]:
522
- return True
523
- elif answer in ["n", "nie", "no", "0", "false"]:
524
- return False
525
- else:
526
- print("Odpowiedz 't' (tak) lub 'n' (nie)")
527
-
528
- except KeyboardInterrupt:
529
- return False
530
-
531
- def save_to_env_file(self, env_vars: Dict[str, str], filename: str = ".env") -> None:
532
- """Zapisuje zmienne do pliku .env."""
533
-
534
- logger.info(f"💾 Zapisywanie zmiennych do {filename}")
535
-
536
- # Wczytaj istniejące zmienne
537
- existing_vars = {}
538
- if Path(filename).exists():
539
- with open(filename, 'r') as f:
540
- for line in f:
541
- line = line.strip()
542
- if '=' in line and not line.startswith('#'):
543
- key, value = line.split('=', 1)
544
- existing_vars[key] = value
545
-
546
- # Połącz ze nowymi
547
- all_vars = {**existing_vars, **env_vars}
548
-
549
- # Zapisz
550
- with open(filename, 'w') as f:
551
- f.write("# Dune Environment Configuration\n")
552
- f.write("# Auto-generated and user-provided variables\n\n")
553
-
554
- for key, value in sorted(all_vars.items()):
555
- f.write(f"{key}={value}\n")
556
-
557
- logger.success(f"✅ Zapisano {len(env_vars)} zmiennych do {filename}")
558
-
559
- def validate_environment(self, required_vars: List[str]) -> Tuple[bool, List[str]]:
560
- """Waliduje kompletność środowiska."""
561
-
562
- missing_vars = []
563
-
564
- for var_name in required_vars:
565
- value = os.getenv(var_name)
566
- if not value:
567
- missing_vars.append(var_name)
568
- else:
569
- env_def = self.env_definitions.get(var_name)
570
- if env_def and not self._validate_env_value(value, env_def):
571
- missing_vars.append(f"{var_name} (nieprawidłowa wartość)")
572
-
573
- return len(missing_vars) == 0, missing_vars