wrd 0.1.41__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.
@@ -0,0 +1,599 @@
1
+ """
2
+ Interaktywny mapper zadań do bibliotek z automatycznym odpytywaniem o dane wejściowe.
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import subprocess
8
+ import importlib
9
+ import inspect
10
+ import ast
11
+ from typing import Dict, List, Any, Optional, Tuple
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ from loguru import logger
15
+ import json
16
+
17
+
18
+ @dataclass
19
+ class LibraryInterface:
20
+ """Definicja interfejsu biblioteki."""
21
+ name: str
22
+ package: str
23
+ main_function: str
24
+ required_params: List[str]
25
+ optional_params: List[str]
26
+ param_types: Dict[str, str]
27
+ param_descriptions: Dict[str, str]
28
+ examples: List[Dict[str, Any]]
29
+ cli_interface: Optional[str] = None
30
+
31
+
32
+ @dataclass
33
+ class TaskMapping:
34
+ """Mapowanie zadania do bibliotek."""
35
+ task_keywords: List[str]
36
+ libraries: List[LibraryInterface]
37
+ priority: int = 1
38
+
39
+
40
+ class InteractiveMapper:
41
+ """Interaktywny mapper zadań do bibliotek."""
42
+
43
+ def __init__(self):
44
+ self.library_database = self._build_library_database()
45
+ self.task_mappings = self._build_task_mappings()
46
+ self.discovered_interfaces = {}
47
+
48
+ def _build_library_database(self) -> Dict[str, LibraryInterface]:
49
+ """Buduje bazę danych dostępnych bibliotek i ich interfejsów."""
50
+
51
+ return {
52
+ # Email processing
53
+ "imaplib": LibraryInterface(
54
+ name="IMAP Email Client",
55
+ package="imaplib",
56
+ main_function="IMAP4_SSL",
57
+ required_params=["server", "username", "password"],
58
+ optional_params=["port", "use_ssl", "folder"],
59
+ param_types={
60
+ "server": "str",
61
+ "username": "str",
62
+ "password": "str",
63
+ "port": "int",
64
+ "use_ssl": "bool",
65
+ "folder": "str"
66
+ },
67
+ param_descriptions={
68
+ "server": "Adres serwera IMAP (np. imap.gmail.com)",
69
+ "username": "Nazwa użytkownika/email",
70
+ "password": "Hasło do skrzynki",
71
+ "port": "Port serwera (143 dla IMAP, 993 dla IMAPS)",
72
+ "use_ssl": "Czy używać szyfrowania SSL",
73
+ "folder": "Folder do przetwarzania (domyślnie INBOX)"
74
+ },
75
+ examples=[
76
+ {
77
+ "server": "imap.gmail.com",
78
+ "username": "user@gmail.com",
79
+ "password": "app_password",
80
+ "port": 993,
81
+ "use_ssl": True
82
+ }
83
+ ]
84
+ ),
85
+
86
+ # Database access
87
+ "sqlalchemy": LibraryInterface(
88
+ name="SQL Database Access",
89
+ package="sqlalchemy",
90
+ main_function="create_engine",
91
+ required_params=["database_url"],
92
+ optional_params=["pool_size", "echo", "timeout"],
93
+ param_types={
94
+ "database_url": "str",
95
+ "pool_size": "int",
96
+ "echo": "bool",
97
+ "timeout": "int"
98
+ },
99
+ param_descriptions={
100
+ "database_url": "URL połączenia z bazą danych (postgresql://user:pass@host/db)",
101
+ "pool_size": "Rozmiar puli połączeń",
102
+ "echo": "Czy logować zapytania SQL",
103
+ "timeout": "Timeout połączenia w sekundach"
104
+ },
105
+ examples=[
106
+ {
107
+ "database_url": "postgresql://user:password@localhost:5432/mydb",
108
+ "pool_size": 5,
109
+ "echo": False
110
+ },
111
+ {
112
+ "database_url": "sqlite:///data.db"
113
+ }
114
+ ]
115
+ ),
116
+
117
+ # Web scraping
118
+ "requests": LibraryInterface(
119
+ name="HTTP Client",
120
+ package="requests",
121
+ main_function="get",
122
+ required_params=["url"],
123
+ optional_params=["headers", "timeout", "verify_ssl", "proxies"],
124
+ param_types={
125
+ "url": "str",
126
+ "headers": "dict",
127
+ "timeout": "int",
128
+ "verify_ssl": "bool",
129
+ "proxies": "dict"
130
+ },
131
+ param_descriptions={
132
+ "url": "URL do pobrania",
133
+ "headers": "Nagłówki HTTP (jako słownik)",
134
+ "timeout": "Timeout żądania w sekundach",
135
+ "verify_ssl": "Czy weryfikować certyfikaty SSL",
136
+ "proxies": "Konfiguracja proxy (jako słownik)"
137
+ },
138
+ examples=[
139
+ {
140
+ "url": "https://api.example.com/data",
141
+ "headers": {"User-Agent": "Dune Bot 1.0"},
142
+ "timeout": 30
143
+ }
144
+ ]
145
+ ),
146
+
147
+ # File processing
148
+ "pandas": LibraryInterface(
149
+ name="Data Analysis",
150
+ package="pandas",
151
+ main_function="read_csv",
152
+ required_params=["filepath"],
153
+ optional_params=["separator", "encoding", "header_row"],
154
+ param_types={
155
+ "filepath": "str",
156
+ "separator": "str",
157
+ "encoding": "str",
158
+ "header_row": "int"
159
+ },
160
+ param_descriptions={
161
+ "filepath": "Ścieżka do pliku CSV",
162
+ "separator": "Separator kolumn (domyślnie ',')",
163
+ "encoding": "Kodowanie pliku (np. utf-8, cp1250)",
164
+ "header_row": "Numer wiersza z nagłówkami (0 = pierwszy wiersz)"
165
+ },
166
+ examples=[
167
+ {
168
+ "filepath": "data.csv",
169
+ "separator": ",",
170
+ "encoding": "utf-8"
171
+ }
172
+ ]
173
+ ),
174
+
175
+ # Image processing
176
+ "pillow": LibraryInterface(
177
+ name="Image Processing",
178
+ package="Pillow",
179
+ main_function="Image.open",
180
+ required_params=["image_path"],
181
+ optional_params=["output_format", "quality", "resize_dimensions"],
182
+ param_types={
183
+ "image_path": "str",
184
+ "output_format": "str",
185
+ "quality": "int",
186
+ "resize_dimensions": "tuple"
187
+ },
188
+ param_descriptions={
189
+ "image_path": "Ścieżka do pliku obrazu",
190
+ "output_format": "Format wyjściowy (JPEG, PNG, WEBP)",
191
+ "quality": "Jakość kompresji (1-100 dla JPEG)",
192
+ "resize_dimensions": "Nowe wymiary jako (width, height)"
193
+ },
194
+ examples=[
195
+ {
196
+ "image_path": "input.jpg",
197
+ "output_format": "JPEG",
198
+ "quality": 85,
199
+ "resize_dimensions": (800, 600)
200
+ }
201
+ ]
202
+ )
203
+ }
204
+
205
+ def _build_task_mappings(self) -> List[TaskMapping]:
206
+ """Buduje mapowania zadań do bibliotek."""
207
+
208
+ return [
209
+ TaskMapping(
210
+ task_keywords=["email", "imap", "pop3", "skrzynka", "wiadomość", "poczta"],
211
+ libraries=[self.library_database["imaplib"]],
212
+ priority=1
213
+ ),
214
+ TaskMapping(
215
+ task_keywords=["baza danych", "sql", "postgresql", "mysql", "sqlite"],
216
+ libraries=[self.library_database["sqlalchemy"]],
217
+ priority=1
218
+ ),
219
+ TaskMapping(
220
+ task_keywords=["http", "api", "rest", "pobierz", "strona", "url"],
221
+ libraries=[self.library_database["requests"]],
222
+ priority=1
223
+ ),
224
+ TaskMapping(
225
+ task_keywords=["csv", "excel", "pandas", "dataframe", "tabela", "dane"],
226
+ libraries=[self.library_database["pandas"]],
227
+ priority=1
228
+ ),
229
+ TaskMapping(
230
+ task_keywords=["obraz", "zdjęcie", "jpg", "png", "resize", "grafika"],
231
+ libraries=[self.library_database["pillow"]],
232
+ priority=1
233
+ )
234
+ ]
235
+
236
+ def analyze_task_and_map_libraries(self, natural_request: str) -> List[LibraryInterface]:
237
+ """Analizuje zadanie i mapuje je do odpowiednich bibliotek."""
238
+
239
+ logger.info("🔍 Analizowanie zadania i mapowanie bibliotek...")
240
+
241
+ request_lower = natural_request.lower()
242
+ matched_libraries = []
243
+
244
+ # Znajdź pasujące mapowania
245
+ for mapping in self.task_mappings:
246
+ score = sum(1 for keyword in mapping.task_keywords
247
+ if keyword in request_lower)
248
+
249
+ if score > 0:
250
+ for library in mapping.libraries:
251
+ if library not in matched_libraries:
252
+ matched_libraries.append(library)
253
+ logger.info(f"📚 Znaleziono bibliotekę: {library.name} (score: {score})")
254
+
255
+ # Sortuj według priorytetu
256
+ matched_libraries.sort(key=lambda x: self._get_library_priority(x.name), reverse=True)
257
+
258
+ return matched_libraries
259
+
260
+ def _get_library_priority(self, library_name: str) -> int:
261
+ """Zwraca priorytet biblioteki."""
262
+ priorities = {
263
+ "IMAP Email Client": 10,
264
+ "SQL Database Access": 9,
265
+ "HTTP Client": 8,
266
+ "Data Analysis": 7,
267
+ "Image Processing": 6
268
+ }
269
+ return priorities.get(library_name, 1)
270
+
271
+ def discover_cli_interface(self, package_name: str) -> Optional[Dict[str, Any]]:
272
+ """Odkrywa interfejs CLI biblioteki poprzez analizę kodu."""
273
+
274
+ if package_name in self.discovered_interfaces:
275
+ return self.discovered_interfaces[package_name]
276
+
277
+ logger.info(f"🔎 Odkrywanie interfejsu CLI dla {package_name}...")
278
+
279
+ try:
280
+ # Spróbuj zaimportować pakiet
281
+ module = importlib.import_module(package_name)
282
+
283
+ # Sprawdź czy ma CLI
284
+ cli_info = self._analyze_module_for_cli(module)
285
+
286
+ if cli_info:
287
+ self.discovered_interfaces[package_name] = cli_info
288
+ logger.success(f"✅ Odkryto interfejs CLI dla {package_name}")
289
+ return cli_info
290
+
291
+ except ImportError:
292
+ logger.warning(f"⚠️ Pakiet {package_name} nie jest zainstalowany")
293
+ except Exception as e:
294
+ logger.warning(f"⚠️ Błąd analizy {package_name}: {e}")
295
+
296
+ return None
297
+
298
+ def _analyze_module_for_cli(self, module) -> Optional[Dict[str, Any]]:
299
+ """Analizuje moduł w poszukiwaniu interfejsu CLI."""
300
+
301
+ cli_info = {
302
+ "has_cli": False,
303
+ "main_functions": [],
304
+ "cli_commands": [],
305
+ "parameters": []
306
+ }
307
+
308
+ # Sprawdź główne funkcje modułu
309
+ for name, obj in inspect.getmembers(module):
310
+ if inspect.isfunction(obj) and not name.startswith('_'):
311
+ sig = inspect.signature(obj)
312
+ params = []
313
+
314
+ for param_name, param in sig.parameters.items():
315
+ param_info = {
316
+ "name": param_name,
317
+ "type": str(param.annotation) if param.annotation != param.empty else "Any",
318
+ "default": param.default if param.default != param.empty else None,
319
+ "required": param.default == param.empty
320
+ }
321
+ params.append(param_info)
322
+
323
+ cli_info["main_functions"].append({
324
+ "name": name,
325
+ "parameters": params,
326
+ "docstring": obj.__doc__
327
+ })
328
+
329
+ if cli_info["main_functions"]:
330
+ cli_info["has_cli"] = True
331
+
332
+ return cli_info if cli_info["has_cli"] else None
333
+
334
+ def interactive_parameter_collection(self, library: LibraryInterface) -> Dict[str, Any]:
335
+ """Interaktywnie zbiera parametry dla biblioteki."""
336
+
337
+ logger.info(f"📝 Zbieranie parametrów dla: {library.name}")
338
+ print(f"\n🔧 KONFIGURACJA: {library.name}")
339
+ print("=" * 50)
340
+
341
+ if library.param_descriptions:
342
+ print("📋 Opis biblioteki:")
343
+ for param, desc in library.param_descriptions.items():
344
+ if param in library.required_params:
345
+ print(f" • {param} (wymagany): {desc}")
346
+ else:
347
+ print(f" • {param} (opcjonalny): {desc}")
348
+
349
+ # Pokaż przykłady
350
+ if library.examples:
351
+ print(f"\n💡 Przykład konfiguracji:")
352
+ example = library.examples[0]
353
+ for key, value in example.items():
354
+ print(f" {key} = {value}")
355
+
356
+ print("\n" + "=" * 50)
357
+
358
+ collected_params = {}
359
+
360
+ # Zbierz wymagane parametry
361
+ for param in library.required_params:
362
+ collected_params[param] = self._collect_single_parameter(
363
+ param, library, required=True
364
+ )
365
+
366
+ # Zbierz opcjonalne parametry (zapytaj czy user chce)
367
+ if library.optional_params:
368
+ print(f"\n🔧 Parametry opcjonalne:")
369
+ for param in library.optional_params:
370
+ if self._ask_yes_no(f"Czy chcesz skonfigurować {param}?"):
371
+ collected_params[param] = self._collect_single_parameter(
372
+ param, library, required=False
373
+ )
374
+
375
+ return collected_params
376
+
377
+ def _collect_single_parameter(self, param_name: str, library: LibraryInterface,
378
+ required: bool = True) -> Any:
379
+ """Zbiera pojedynczy parametr."""
380
+
381
+ # Sprawdź czy jest w zmiennych środowiskowych
382
+ env_variants = [
383
+ param_name.upper(),
384
+ f"{library.package.upper()}_{param_name.upper()}",
385
+ f"DUNE_{param_name.upper()}"
386
+ ]
387
+
388
+ env_value = None
389
+ env_source = None
390
+
391
+ for env_var in env_variants:
392
+ env_value = os.getenv(env_var)
393
+ if env_value:
394
+ env_source = env_var
395
+ break
396
+
397
+ # Przygotuj prompt
398
+ param_desc = library.param_descriptions.get(param_name, "")
399
+ param_type = library.param_types.get(param_name, "str")
400
+
401
+ prompt = f"📌 {param_name}"
402
+ if param_desc:
403
+ prompt += f" ({param_desc})"
404
+ if not required:
405
+ prompt += " [opcjonalny]"
406
+
407
+ if env_value:
408
+ prompt += f" [znaleziono w {env_source}: {env_value}]"
409
+ if self._ask_yes_no(f"Użyć wartości z {env_source}?", default=True):
410
+ return self._convert_type(env_value, param_type)
411
+
412
+ # Pobierz od użytkownika
413
+ while True:
414
+ try:
415
+ user_input = input(f"{prompt}: ").strip()
416
+
417
+ if not user_input and not required:
418
+ return None
419
+
420
+ if not user_input and required:
421
+ print("❌ Ten parametr jest wymagany!")
422
+ continue
423
+
424
+ return self._convert_type(user_input, param_type)
425
+
426
+ except KeyboardInterrupt:
427
+ print("\n⚠️ Przerwano przez użytkownika")
428
+ sys.exit(1)
429
+ except Exception as e:
430
+ print(f"❌ Błąd: {e}. Spróbuj ponownie.")
431
+
432
+ def _convert_type(self, value: str, param_type: str) -> Any:
433
+ """Konwertuje wartość do odpowiedniego typu."""
434
+
435
+ if param_type == "int":
436
+ return int(value)
437
+ elif param_type == "bool":
438
+ return value.lower() in ["true", "1", "yes", "tak", "y"]
439
+ elif param_type == "float":
440
+ return float(value)
441
+ elif param_type == "list":
442
+ return value.split(",")
443
+ elif param_type == "dict":
444
+ return json.loads(value)
445
+ elif param_type == "tuple" and "," in value:
446
+ return tuple(map(int, value.split(",")))
447
+ else:
448
+ return value
449
+
450
+ def _ask_yes_no(self, question: str, default: bool = None) -> bool:
451
+ """Zadaje pytanie tak/nie."""
452
+
453
+ if default is True:
454
+ prompt = f"{question} [T/n]: "
455
+ elif default is False:
456
+ prompt = f"{question} [t/N]: "
457
+ else:
458
+ prompt = f"{question} [t/n]: "
459
+
460
+ while True:
461
+ try:
462
+ answer = input(prompt).strip().lower()
463
+
464
+ if not answer and default is not None:
465
+ return default
466
+
467
+ if answer in ["t", "tak", "y", "yes", "1", "true"]:
468
+ return True
469
+ elif answer in ["n", "nie", "no", "0", "false"]:
470
+ return False
471
+ else:
472
+ print("Odpowiedz 't' (tak) lub 'n' (nie)")
473
+
474
+ except KeyboardInterrupt:
475
+ print("\n⚠️ Przerwano przez użytkownika")
476
+ sys.exit(1)
477
+
478
+ def generate_runtime_config(self, libraries: List[LibraryInterface],
479
+ collected_params: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
480
+ """Generuje konfigurację runtime na podstawie zebranych danych."""
481
+
482
+ # Zbierz wszystkie wymagane pakiety
483
+ packages = {"required": [], "optional": []}
484
+ env_vars = {"required": [], "optional": []}
485
+
486
+ for library in libraries:
487
+ packages["required"].append(library.package)
488
+
489
+ # Dodaj zmienne środowiskowe na podstawie parametrów
490
+ lib_params = collected_params.get(library.name, {})
491
+ for param_name, value in lib_params.items():
492
+ env_var = f"{library.package.upper()}_{param_name.upper()}"
493
+ if param_name in library.required_params:
494
+ env_vars["required"].append(env_var)
495
+ else:
496
+ env_vars["optional"].append(env_var)
497
+
498
+ # Usuń duplikaty
499
+ packages["required"] = list(set(packages["required"]))
500
+ packages["optional"] = list(set(packages["optional"]))
501
+ env_vars["required"] = list(set(env_vars["required"]))
502
+ env_vars["optional"] = list(set(env_vars["optional"]))
503
+
504
+ return {
505
+ "runtime": {
506
+ "type": "docker",
507
+ "base_image": "python:3.11-slim",
508
+ "python_packages": packages,
509
+ "environment": env_vars
510
+ },
511
+ "collected_parameters": collected_params
512
+ }
513
+
514
+ def save_parameters_to_env(self, collected_params: Dict[str, Dict[str, Any]],
515
+ env_file: str = ".env") -> None:
516
+ """Zapisuje zebrane parametry do pliku .env."""
517
+
518
+ logger.info(f"💾 Zapisywanie parametrów do {env_file}")
519
+
520
+ # Wczytaj istniejący .env jeśli istnieje
521
+ existing_vars = {}
522
+ if os.path.exists(env_file):
523
+ with open(env_file, 'r') as f:
524
+ for line in f:
525
+ line = line.strip()
526
+ if '=' in line and not line.startswith('#'):
527
+ key, value = line.split('=', 1)
528
+ existing_vars[key] = value
529
+
530
+ # Dodaj nowe zmienne
531
+ new_vars = {}
532
+ for lib_name, params in collected_params.items():
533
+ for param_name, value in params.items():
534
+ if value is not None:
535
+ # Kilka wariantów nazwy zmiennej
536
+ env_var = f"DUNE_{param_name.upper()}"
537
+ new_vars[env_var] = str(value)
538
+
539
+ # Połącz i zapisz
540
+ all_vars = {**existing_vars, **new_vars}
541
+
542
+ with open(env_file, 'w') as f:
543
+ f.write("# Dune Configuration\n")
544
+ f.write("# Auto-generated parameters\n\n")
545
+
546
+ for key, value in sorted(all_vars.items()):
547
+ f.write(f"{key}={value}\n")
548
+
549
+ logger.success(f"✅ Parametry zapisane do {env_file}")
550
+ print(f"\n💾 Zapisano {len(new_vars)} nowych parametrów do {env_file}")
551
+
552
+ def run_interactive_mapping(self, natural_request: str) -> Dict[str, Any]:
553
+ """Uruchamia pełny interaktywny proces mapowania."""
554
+
555
+ print("\n" + "=" * 60)
556
+ print("🤖 DUNE - INTERAKTYWNY MAPPER BIBLIOTEK")
557
+ print("=" * 60)
558
+ print(f"📝 Zadanie: {natural_request}")
559
+ print("=" * 60)
560
+
561
+ # 1. Mapuj biblioteki
562
+ libraries = self.analyze_task_and_map_libraries(natural_request)
563
+
564
+ if not libraries:
565
+ print("❌ Nie znaleziono pasujących bibliotek dla tego zadania")
566
+ return {}
567
+
568
+ print(f"\n📚 Znalezione biblioteki ({len(libraries)}):")
569
+ for i, lib in enumerate(libraries, 1):
570
+ print(f" {i}. {lib.name} ({lib.package})")
571
+
572
+ # 2. Zbierz parametry dla każdej biblioteki
573
+ collected_params = {}
574
+
575
+ for library in libraries:
576
+ if self._ask_yes_no(f"\nKonfigurować {library.name}?", default=True):
577
+ params = self.interactive_parameter_collection(library)
578
+ collected_params[library.name] = params
579
+
580
+ # 3. Generuj konfigurację
581
+ runtime_config = self.generate_runtime_config(libraries, collected_params)
582
+
583
+ # 4. Zapisz do .env
584
+ if collected_params and self._ask_yes_no("\nZapisać parametry do .env?", default=True):
585
+ self.save_parameters_to_env(collected_params)
586
+
587
+ print("\n" + "=" * 60)
588
+ print("✅ MAPOWANIE ZAKOŃCZONE POMYŚLNIE!")
589
+ print("=" * 60)
590
+ print(f"📦 Pakiety do zainstalowania: {len(runtime_config['runtime']['python_packages']['required'])}")
591
+ print(f"🔧 Parametrów zebranych: {sum(len(params) for params in collected_params.values())}")
592
+ print("=" * 60)
593
+
594
+ return {
595
+ "libraries": libraries,
596
+ "parameters": collected_params,
597
+ "runtime_config": runtime_config,
598
+ "natural_request": natural_request
599
+ }