devflow-tools 0.1.0__tar.gz
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.
- devflow_tools-0.1.0/LICENSE +21 -0
- devflow_tools-0.1.0/PKG-INFO +29 -0
- devflow_tools-0.1.0/README.md +14 -0
- devflow_tools-0.1.0/devflow/__init__.py +4 -0
- devflow_tools-0.1.0/devflow/ai.py +108 -0
- devflow_tools-0.1.0/devflow/cli.py +209 -0
- devflow_tools-0.1.0/devflow/help.py +170 -0
- devflow_tools-0.1.0/devflow/network.py +121 -0
- devflow_tools-0.1.0/devflow_tools.egg-info/PKG-INFO +29 -0
- devflow_tools-0.1.0/devflow_tools.egg-info/SOURCES.txt +13 -0
- devflow_tools-0.1.0/devflow_tools.egg-info/dependency_links.txt +1 -0
- devflow_tools-0.1.0/devflow_tools.egg-info/requires.txt +2 -0
- devflow_tools-0.1.0/devflow_tools.egg-info/top_level.txt +1 -0
- devflow_tools-0.1.0/pyproject.toml +22 -0
- devflow_tools-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alex74
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: devflow-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Un couteau suisse pour créer des CLI stylisées, du réseau TCP et intégrer l'IA.
|
|
5
|
+
Author: Alex
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: rich>=12.0.0
|
|
13
|
+
Requires-Dist: google-genai>=0.1.0
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# DevFlow 🚀
|
|
17
|
+
|
|
18
|
+
**DevFlow** est un couteau suisse Python pour simplifier la création d'interfaces en ligne de commande (CLI) stylisées, la programmation réseau par Sockets TCP et l'intégration de l'IA (Gemini).
|
|
19
|
+
|
|
20
|
+
## 📥 Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install devflow-tools
|
|
24
|
+
```
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 👨💻 À propos de l'auteur
|
|
28
|
+
|
|
29
|
+
Projet codé et conçu par **Alex74** (développeur passionné jeune) pour simplifier le développement d'outils CLI et réseau en Python.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# DevFlow 🚀
|
|
2
|
+
|
|
3
|
+
**DevFlow** est un couteau suisse Python pour simplifier la création d'interfaces en ligne de commande (CLI) stylisées, la programmation réseau par Sockets TCP et l'intégration de l'IA (Gemini).
|
|
4
|
+
|
|
5
|
+
## 📥 Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install devflow-tools
|
|
9
|
+
```
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 👨💻 À propos de l'auteur
|
|
13
|
+
|
|
14
|
+
Projet codé et conçu par **Alex74** (développeur passionné jeune) pour simplifier le développement d'outils CLI et réseau en Python.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeoutError
|
|
4
|
+
from typing import Union, List, Optional
|
|
5
|
+
from google import genai
|
|
6
|
+
from google.genai import errors
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AI:
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
api_keys: Union[str, List[str], bool] = False,
|
|
13
|
+
model: str = "gemini-3.6-flash",
|
|
14
|
+
):
|
|
15
|
+
"""Initialise l'instance AI.
|
|
16
|
+
|
|
17
|
+
:param api_keys: Clé unique (str), liste de clés (list), ou False pour
|
|
18
|
+
utiliser os.environ['gemini_1']
|
|
19
|
+
:param model: Nom du modèle Gemini (par défaut: gemini-3.6-flash)
|
|
20
|
+
"""
|
|
21
|
+
self.model = model
|
|
22
|
+
self.key_list: List[str] = []
|
|
23
|
+
self.current_key_index: int = 0
|
|
24
|
+
|
|
25
|
+
# Gestion des clés d'API
|
|
26
|
+
if api_keys is False or api_keys is None:
|
|
27
|
+
env_key = os.environ.get("gemini_1") or os.environ.get("GEMINI_API_KEY")
|
|
28
|
+
if not env_key:
|
|
29
|
+
raise ValueError(
|
|
30
|
+
"Aucune clé trouvée. Définis la variable 'gemini_1' ou passe une clé."
|
|
31
|
+
)
|
|
32
|
+
self.key_list = [env_key]
|
|
33
|
+
elif isinstance(api_keys, str):
|
|
34
|
+
self.key_list = [api_keys]
|
|
35
|
+
elif isinstance(api_keys, list):
|
|
36
|
+
if not api_keys:
|
|
37
|
+
raise ValueError("La liste de clés API ne peut pas être vide.")
|
|
38
|
+
self.key_list = api_keys
|
|
39
|
+
else:
|
|
40
|
+
raise TypeError("Format de api_keys invalide.")
|
|
41
|
+
|
|
42
|
+
self._init_client()
|
|
43
|
+
|
|
44
|
+
def _init_client(self):
|
|
45
|
+
"""Instancie le client Gemini avec la clé courante."""
|
|
46
|
+
current_key = self.key_list[self.current_key_index]
|
|
47
|
+
self.client = genai.Client(api_key=current_key)
|
|
48
|
+
|
|
49
|
+
def _rotate_key(self) -> bool:
|
|
50
|
+
"""Passe à la clé suivante dans la liste si disponible."""
|
|
51
|
+
if len(self.key_list) <= 1:
|
|
52
|
+
return False
|
|
53
|
+
self.current_key_index = (self.current_key_index + 1) % len(self.key_list)
|
|
54
|
+
self._init_client()
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
def request(self, prompt: str, timeout: Optional[float] = None) -> str:
|
|
58
|
+
"""Envoie un prompt à Gemini et retourne la réponse complète.
|
|
59
|
+
|
|
60
|
+
:param prompt: Le texte à envoyer à l'IA
|
|
61
|
+
:param timeout: Temps max en secondes avant d'abandonner (Optionnel)
|
|
62
|
+
"""
|
|
63
|
+
attempts = 0
|
|
64
|
+
max_attempts = len(self.key_list)
|
|
65
|
+
|
|
66
|
+
def _execute():
|
|
67
|
+
response = self.client.models.generate_content(
|
|
68
|
+
model=self.model, contents=prompt
|
|
69
|
+
)
|
|
70
|
+
return response.text
|
|
71
|
+
|
|
72
|
+
while attempts < max_attempts:
|
|
73
|
+
try:
|
|
74
|
+
if timeout is not None:
|
|
75
|
+
# Vrai timeout exécuté dans un thread séparé
|
|
76
|
+
with ThreadPoolExecutor(max_workers=1) as executor:
|
|
77
|
+
future = executor.submit(_execute)
|
|
78
|
+
return future.result(timeout=timeout)
|
|
79
|
+
else:
|
|
80
|
+
return _execute()
|
|
81
|
+
|
|
82
|
+
except FutureTimeoutError:
|
|
83
|
+
raise TimeoutError(f"La requête a dépassé le timeout de {timeout}s")
|
|
84
|
+
except (errors.APIError, errors.ServerError) as e:
|
|
85
|
+
attempts += 1
|
|
86
|
+
if not self._rotate_key() or attempts >= max_attempts:
|
|
87
|
+
raise e
|
|
88
|
+
except Exception as e:
|
|
89
|
+
raise e
|
|
90
|
+
|
|
91
|
+
def ask_stream(self, prompt: str):
|
|
92
|
+
"""Génère du texte en streaming (compatible avec devflow.cli)."""
|
|
93
|
+
attempts = 0
|
|
94
|
+
max_attempts = len(self.key_list)
|
|
95
|
+
|
|
96
|
+
while attempts < max_attempts:
|
|
97
|
+
try:
|
|
98
|
+
response = self.client.models.generate_content_stream(
|
|
99
|
+
model=self.model, contents=prompt
|
|
100
|
+
)
|
|
101
|
+
for chunk in response:
|
|
102
|
+
if chunk.text:
|
|
103
|
+
yield chunk.text
|
|
104
|
+
return
|
|
105
|
+
except errors.APIError as e:
|
|
106
|
+
attempts += 1
|
|
107
|
+
if not self._rotate_key() or attempts >= max_attempts:
|
|
108
|
+
raise e
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
import random
|
|
3
|
+
import time
|
|
4
|
+
from typing import List, Optional, Union
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.live import Live
|
|
7
|
+
from rich.markdown import Markdown
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
10
|
+
from rich.prompt import Prompt
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
|
|
13
|
+
# Console unique partagée
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CLI:
|
|
18
|
+
# -------------------------------------------------------------------------
|
|
19
|
+
# 1. RENDU DE TEXTE ET STREAMING
|
|
20
|
+
# -------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def print_stream(generator) -> str:
|
|
24
|
+
"""Affiche un flux de texte (generator) en Markdown et en temps réel."""
|
|
25
|
+
full_text = ""
|
|
26
|
+
with Live(
|
|
27
|
+
Markdown(full_text),
|
|
28
|
+
console=console,
|
|
29
|
+
refresh_per_second=12,
|
|
30
|
+
vertical_overflow="visible",
|
|
31
|
+
) as live:
|
|
32
|
+
for chunk in generator:
|
|
33
|
+
if chunk:
|
|
34
|
+
full_text += chunk
|
|
35
|
+
live.update(Markdown(full_text))
|
|
36
|
+
return full_text
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def type_text(
|
|
40
|
+
text: str,
|
|
41
|
+
speed: float = 0.03,
|
|
42
|
+
color: str = "green",
|
|
43
|
+
cursor: bool = True,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Simule un effet de frappe à la machine à écrire avec curseur clignotant."""
|
|
46
|
+
displayed = ""
|
|
47
|
+
with Live(
|
|
48
|
+
console=console, refresh_per_second=20, transient=True
|
|
49
|
+
) as live:
|
|
50
|
+
for letter in text:
|
|
51
|
+
displayed += letter
|
|
52
|
+
cur_str = (
|
|
53
|
+
("_" if int(time.time() * 4) % 2 == 0 else " ")
|
|
54
|
+
if cursor
|
|
55
|
+
else ""
|
|
56
|
+
)
|
|
57
|
+
live.update(f"[{color}]{displayed}{cur_str}[/{color}]")
|
|
58
|
+
time.sleep(speed)
|
|
59
|
+
|
|
60
|
+
# Affichage final fixé dans la console
|
|
61
|
+
console.print(f"[{color}]{text}[/{color}]")
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def live_sequence(
|
|
65
|
+
items: List[str], delay: float = 0.01, repeat: int = 1
|
|
66
|
+
) -> str:
|
|
67
|
+
"""Anime l'affichage d'une liste de fragments de texte progressivement."""
|
|
68
|
+
full_text = ""
|
|
69
|
+
with Live(
|
|
70
|
+
Markdown(""),
|
|
71
|
+
console=console,
|
|
72
|
+
refresh_per_second=30,
|
|
73
|
+
vertical_overflow="visible",
|
|
74
|
+
) as live_display:
|
|
75
|
+
for cycle in range(repeat):
|
|
76
|
+
full_text = ""
|
|
77
|
+
for chunk in items:
|
|
78
|
+
full_text += chunk
|
|
79
|
+
live_display.update(Markdown(full_text))
|
|
80
|
+
time.sleep(delay)
|
|
81
|
+
if cycle < repeat - 1:
|
|
82
|
+
time.sleep(0.2)
|
|
83
|
+
return full_text
|
|
84
|
+
|
|
85
|
+
# -------------------------------------------------------------------------
|
|
86
|
+
# 2. INDICATEURS DE CHARGEMENT ET PROGRESSION
|
|
87
|
+
# -------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
@contextmanager
|
|
91
|
+
def status(message: str = "Traitement en cours..."):
|
|
92
|
+
"""Affiche un spinner animé pendant l'exécution d'un bloc de code."""
|
|
93
|
+
with console.status(
|
|
94
|
+
f"[bold cyan]{message}[/bold cyan]", spinner="dots"
|
|
95
|
+
):
|
|
96
|
+
yield
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def animate_dots(
|
|
100
|
+
text: str = "Chargement",
|
|
101
|
+
max_dots: int = 20,
|
|
102
|
+
speed: float = 0.08,
|
|
103
|
+
color: str = "cyan",
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Affiche un texte avec des points d'attente animés (ex: Chargement...)."""
|
|
106
|
+
with Live(
|
|
107
|
+
console=console, refresh_per_second=15, transient=True
|
|
108
|
+
) as live:
|
|
109
|
+
for i in range(max_dots + 1):
|
|
110
|
+
live.update(f"[{color}]{text}{'.' * i}[/{color}]")
|
|
111
|
+
time.sleep(speed)
|
|
112
|
+
console.print(f"[bold green]✔ {text} terminé.[/bold green]")
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def animate_frames(
|
|
116
|
+
frames: List[str],
|
|
117
|
+
repetitions: int = 5,
|
|
118
|
+
speed: float = 0.25,
|
|
119
|
+
color: str = "cyan",
|
|
120
|
+
) -> None:
|
|
121
|
+
"""Boucle sur une liste de frames textuelles (ex: spinners personnalisés)."""
|
|
122
|
+
with Live(
|
|
123
|
+
console=console, refresh_per_second=15, transient=True
|
|
124
|
+
) as live:
|
|
125
|
+
for _ in range(repetitions):
|
|
126
|
+
for frame in frames:
|
|
127
|
+
live.update(Text(frame, style=color))
|
|
128
|
+
time.sleep(speed)
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def progress_bar(
|
|
132
|
+
title: str = "Progression",
|
|
133
|
+
total: int = 100,
|
|
134
|
+
speed: float = 0.03,
|
|
135
|
+
color: str = "yellow",
|
|
136
|
+
) -> None:
|
|
137
|
+
"""Affiche une barre de progression simple et fluide."""
|
|
138
|
+
with Progress(
|
|
139
|
+
SpinnerColumn(),
|
|
140
|
+
TextColumn("[progress.description]{task.description}"),
|
|
141
|
+
console=console,
|
|
142
|
+
transient=True,
|
|
143
|
+
) as progress:
|
|
144
|
+
task = progress.add_task(f"[{color}]{title}", total=total)
|
|
145
|
+
while not progress.finished:
|
|
146
|
+
time.sleep(speed)
|
|
147
|
+
progress.update(task, advance=1)
|
|
148
|
+
|
|
149
|
+
# -------------------------------------------------------------------------
|
|
150
|
+
# 3. INTERFACES, PANNEAUX ET SÉPARATEURS
|
|
151
|
+
# -------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def rule(
|
|
155
|
+
title: str = "", character: str = "─", color: str = "bright_blue"
|
|
156
|
+
) -> None:
|
|
157
|
+
"""Trace une ligne séparatrice horizontale avec un titre optionnel."""
|
|
158
|
+
width = console.width
|
|
159
|
+
if title:
|
|
160
|
+
text = f" {title} "
|
|
161
|
+
remaining = width - len(text)
|
|
162
|
+
left = remaining // 2
|
|
163
|
+
right = remaining - left
|
|
164
|
+
line = character * left + text + character * right
|
|
165
|
+
else:
|
|
166
|
+
line = character * width
|
|
167
|
+
console.print(line, style=color)
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def print_panel(
|
|
171
|
+
content: Union[str, Markdown],
|
|
172
|
+
title: str = "devflow",
|
|
173
|
+
style: str = "cyan",
|
|
174
|
+
expand: bool = True,
|
|
175
|
+
) -> None:
|
|
176
|
+
"""Affiche un contenu encadré dans un panneau stylisé."""
|
|
177
|
+
md = Markdown(content) if isinstance(content, str) else content
|
|
178
|
+
console.print(
|
|
179
|
+
Panel(
|
|
180
|
+
md, title=f"[bold]{title}[/bold]", border_style=style, expand=expand
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def ask(prompt_text: str = "Entrez votre message") -> str:
|
|
186
|
+
"""Pose une question à l'utilisateur via une invite propre."""
|
|
187
|
+
return Prompt.ask(f"[bold green]?[/bold green] {prompt_text}")
|
|
188
|
+
|
|
189
|
+
# -------------------------------------------------------------------------
|
|
190
|
+
# 4. SIMULATION DE LOGS & UTILS
|
|
191
|
+
# -------------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def generate_logs(count: int = 10, speed: float = 0.01) -> None:
|
|
195
|
+
"""Génère de faux logs système en défilement rapide pour des démos/tests."""
|
|
196
|
+
log_types = [
|
|
197
|
+
("ERROR", "bold red"),
|
|
198
|
+
("INFO", "bold white"),
|
|
199
|
+
("WARNING", "bold yellow"),
|
|
200
|
+
("SUCCESS", "bold green"),
|
|
201
|
+
]
|
|
202
|
+
for i in range(1, count + 1):
|
|
203
|
+
level, color = random.choice(log_types)
|
|
204
|
+
pid = random.randint(1000, 9999)
|
|
205
|
+
code = random.randint(100000, 999999)
|
|
206
|
+
status = random.choice(["OK", "FAIL", "PENDING"])
|
|
207
|
+
|
|
208
|
+
msg = f"[{i:03d}] [{level}] Process_{pid} status={status} code={code}"
|
|
209
|
+
CLI.type_text(msg, speed=speed, color=color, cursor=False)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from rich.console import Console
|
|
2
|
+
from rich.panel import Panel
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
from rich.syntax import Syntax
|
|
5
|
+
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def display_help(module_name: str = None) -> None:
|
|
10
|
+
"""Affiche l'aide interactive et la documentation de DevFlow."""
|
|
11
|
+
|
|
12
|
+
# Titre principal
|
|
13
|
+
console.print()
|
|
14
|
+
console.print(
|
|
15
|
+
Panel(
|
|
16
|
+
"[bold cyan]🚀 DevFlow - Documentation & Aide[/bold cyan]\n"
|
|
17
|
+
"[dim]Bibliothèque modulaire : IA (Gemini), CLI (Rich), Sockets TCP.[/dim]",
|
|
18
|
+
border_style="bright_blue",
|
|
19
|
+
expand=False,
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# -------------------------------------------------------------------------
|
|
24
|
+
# MODULE : AI
|
|
25
|
+
# -------------------------------------------------------------------------
|
|
26
|
+
if module_name in (None, "ai"):
|
|
27
|
+
console.print("\n[bold yellow]🤖 MODULE : devflow.AI[/bold yellow]")
|
|
28
|
+
table_ai = Table(show_header=True, header_style="bold magenta", expand=True)
|
|
29
|
+
table_ai.add_column("Méthode", style="cyan", width=22)
|
|
30
|
+
table_ai.add_column("Paramètres", style="green")
|
|
31
|
+
table_ai.add_column("Description", style="white")
|
|
32
|
+
|
|
33
|
+
table_ai.add_row(
|
|
34
|
+
"__init__()",
|
|
35
|
+
"model='gemini-2.5-flash', system_instruction=None",
|
|
36
|
+
"Initialise l'instance AI avec la clé API Google.",
|
|
37
|
+
)
|
|
38
|
+
table_ai.add_row(
|
|
39
|
+
"ask()",
|
|
40
|
+
"prompt: str",
|
|
41
|
+
"Envoie une requête et retourne la réponse complète sous forme de texte.",
|
|
42
|
+
)
|
|
43
|
+
table_ai.add_row(
|
|
44
|
+
"ask_stream()",
|
|
45
|
+
"prompt: str",
|
|
46
|
+
"Retourne un générateur pour lire la réponse morceau par morceau.",
|
|
47
|
+
)
|
|
48
|
+
console.print(table_ai)
|
|
49
|
+
|
|
50
|
+
example_ai = (
|
|
51
|
+
"from devflow import AI, CLI\n\n"
|
|
52
|
+
"ai = AI()\n"
|
|
53
|
+
"# Utilisation classique\n"
|
|
54
|
+
"reponse = ai.ask('Explique les sockets en 2 phrases')\n"
|
|
55
|
+
"print(reponse)\n\n"
|
|
56
|
+
"# Utilisation en streaming avec la CLI\n"
|
|
57
|
+
"CLI.print_stream(ai.ask_stream('Raconte une histoire courte'))"
|
|
58
|
+
)
|
|
59
|
+
console.print(Panel(Syntax(example_ai, "python", theme="monokai"), title="[bold]Exemple AI[/bold]", border_style="dim"))
|
|
60
|
+
|
|
61
|
+
# -------------------------------------------------------------------------
|
|
62
|
+
# MODULE : CLI
|
|
63
|
+
# -------------------------------------------------------------------------
|
|
64
|
+
if module_name in (None, "cli"):
|
|
65
|
+
console.print("\n[bold yellow]💻 MODULE : devflow.CLI[/bold yellow]")
|
|
66
|
+
table_cli = Table(show_header=True, header_style="bold magenta", expand=True)
|
|
67
|
+
table_cli.add_column("Méthode", style="cyan", width=22)
|
|
68
|
+
table_cli.add_column("Paramètres", style="green")
|
|
69
|
+
table_cli.add_column("Description", style="white")
|
|
70
|
+
|
|
71
|
+
table_cli.add_row(
|
|
72
|
+
"print_stream()",
|
|
73
|
+
"generator",
|
|
74
|
+
"Affiche un flux de texte (MarkDown) en temps réel.",
|
|
75
|
+
)
|
|
76
|
+
table_cli.add_row(
|
|
77
|
+
"type_text()",
|
|
78
|
+
"text, speed=0.03, color='green', cursor=True",
|
|
79
|
+
"Effet machine à écrire avec curseur clignotant.",
|
|
80
|
+
)
|
|
81
|
+
table_cli.add_row(
|
|
82
|
+
"animate_dots()",
|
|
83
|
+
"text='Chargement', max_dots=20, speed=0.08, color='cyan'",
|
|
84
|
+
"Animation de points d'attente dynamiques.",
|
|
85
|
+
)
|
|
86
|
+
table_cli.add_row(
|
|
87
|
+
"progress_bar()",
|
|
88
|
+
"title='Progression', total=100, speed=0.03, color='yellow'",
|
|
89
|
+
"Barre de progression fluide.",
|
|
90
|
+
)
|
|
91
|
+
table_cli.add_row(
|
|
92
|
+
"rule()",
|
|
93
|
+
"title='', character='─', color='bright_blue'",
|
|
94
|
+
"Ligne séparatrice avec ou sans titre.",
|
|
95
|
+
)
|
|
96
|
+
table_cli.add_row(
|
|
97
|
+
"print_panel()",
|
|
98
|
+
"content, title='devflow', style='cyan', expand=True",
|
|
99
|
+
"Encadre du texte dans un panneau stylisé.",
|
|
100
|
+
)
|
|
101
|
+
table_cli.add_row(
|
|
102
|
+
"ask()",
|
|
103
|
+
"prompt_text='Entrez votre message'",
|
|
104
|
+
"Invite de saisie utilisateur stylisée.",
|
|
105
|
+
)
|
|
106
|
+
console.print(table_cli)
|
|
107
|
+
|
|
108
|
+
example_cli = (
|
|
109
|
+
"from devflow import CLI\n\n"
|
|
110
|
+
"CLI.rule(title='DEBUT DE TÂCHE')\n"
|
|
111
|
+
"CLI.type_text('Connexion au serveur...', color='cyan')\n"
|
|
112
|
+
"CLI.progress_bar(title='Téléchargement', total=50)\n"
|
|
113
|
+
"CLI.print_panel('Operation réussie !', title='Succès', style='green')"
|
|
114
|
+
)
|
|
115
|
+
console.print(Panel(Syntax(example_cli, "python", theme="monokai"), title="[bold]Exemple CLI[/bold]", border_style="dim"))
|
|
116
|
+
|
|
117
|
+
# -------------------------------------------------------------------------
|
|
118
|
+
# MODULE : NETWORK
|
|
119
|
+
# -------------------------------------------------------------------------
|
|
120
|
+
if module_name in (None, "network"):
|
|
121
|
+
console.print("\n[bold yellow]🌐 MODULE : devflow.NETWORK[/bold yellow]")
|
|
122
|
+
table_net = Table(show_header=True, header_style="bold magenta", expand=True)
|
|
123
|
+
table_net.add_column("Classe / Méthode", style="cyan", width=22)
|
|
124
|
+
table_net.add_column("Paramètres", style="green")
|
|
125
|
+
table_net.add_column("Description", style="white")
|
|
126
|
+
|
|
127
|
+
table_net.add_row(
|
|
128
|
+
"SocketServer()",
|
|
129
|
+
"host='0.0.0.0', port=65432, buffer_size=4096",
|
|
130
|
+
"Serveur TCP multithreadé.",
|
|
131
|
+
)
|
|
132
|
+
table_net.add_row(
|
|
133
|
+
"SocketServer.start()",
|
|
134
|
+
"handler: Callable[[str], Union[str, Generator]]",
|
|
135
|
+
"Lance le serveur et exécute le handler à chaque message.",
|
|
136
|
+
)
|
|
137
|
+
table_net.add_row(
|
|
138
|
+
"SocketClient()",
|
|
139
|
+
"host='127.0.0.1', port=65432, buffer_size=4096",
|
|
140
|
+
"Client TCP simple.",
|
|
141
|
+
)
|
|
142
|
+
table_net.add_row(
|
|
143
|
+
"SocketClient.send()",
|
|
144
|
+
"message: str, timeout=10.0",
|
|
145
|
+
"Envoie un message et attend la réponse complète.",
|
|
146
|
+
)
|
|
147
|
+
table_net.add_row(
|
|
148
|
+
"SocketClient.send_stream()",
|
|
149
|
+
"message: str, timeout=10.0",
|
|
150
|
+
"Envoie un message et reçoit la réponse en flux (generator).",
|
|
151
|
+
)
|
|
152
|
+
console.print(table_net)
|
|
153
|
+
|
|
154
|
+
example_net = (
|
|
155
|
+
"# SERVEUR TCP\n"
|
|
156
|
+
"from devflow import SocketServer\n\n"
|
|
157
|
+
"server = SocketServer(port=65432)\n"
|
|
158
|
+
"server.start(handler=lambda msg: f'Reçu : {msg}')\n\n"
|
|
159
|
+
"# CLIENT TCP\n"
|
|
160
|
+
"from devflow import SocketClient\n\n"
|
|
161
|
+
"client = SocketClient(port=65432)\n"
|
|
162
|
+
"reponse = client.send('Hello Server !')\n"
|
|
163
|
+
"print(reponse)"
|
|
164
|
+
)
|
|
165
|
+
console.print(Panel(Syntax(example_net, "python", theme="monokai"), title="[bold]Exemple Network[/bold]", border_style="dim"))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def help(module_name: str = None) -> None:
|
|
169
|
+
"""Raccourci pour afficher l'aide."""
|
|
170
|
+
display_help(module_name)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import threading
|
|
3
|
+
from typing import Callable, Optional, Generator, Union
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SocketServer:
|
|
7
|
+
"""Serveur TCP multithreadé simple et réutilisable."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, host: str = "0.0.0.0", port: int = 65432, buffer_size: int = 4096):
|
|
10
|
+
self.host = host
|
|
11
|
+
self.port = port
|
|
12
|
+
self.buffer_size = buffer_size
|
|
13
|
+
self.server_socket: Optional[socket.socket] = None
|
|
14
|
+
self.is_running: bool = False
|
|
15
|
+
|
|
16
|
+
def _handle_client(
|
|
17
|
+
self,
|
|
18
|
+
conn: socket.socket,
|
|
19
|
+
addr: tuple,
|
|
20
|
+
handler: Callable[[str], Union[str, Generator[str, None, None]]],
|
|
21
|
+
):
|
|
22
|
+
"""Gère la communication avec un client connecté dans un thread dédié."""
|
|
23
|
+
try:
|
|
24
|
+
while self.is_running:
|
|
25
|
+
data = conn.recv(self.buffer_size)
|
|
26
|
+
if not data:
|
|
27
|
+
break
|
|
28
|
+
|
|
29
|
+
message = data.decode("utf-8").strip()
|
|
30
|
+
if not message:
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
# Exécution de la fonction de traitement (callback)
|
|
34
|
+
result = handler(message)
|
|
35
|
+
|
|
36
|
+
# Si le handler est un générateur (ex: streaming Gemini)
|
|
37
|
+
if isinstance(result, Generator):
|
|
38
|
+
for chunk in result:
|
|
39
|
+
if chunk:
|
|
40
|
+
conn.sendall(chunk.encode("utf-8"))
|
|
41
|
+
elif isinstance(result, str):
|
|
42
|
+
conn.sendall(result.encode("utf-8"))
|
|
43
|
+
|
|
44
|
+
except (ConnectionResetError, BrokenPipeError):
|
|
45
|
+
pass
|
|
46
|
+
finally:
|
|
47
|
+
conn.close()
|
|
48
|
+
|
|
49
|
+
def start(self, handler: Callable[[str], Union[str, Generator[str, None, None]]]):
|
|
50
|
+
"""Démarre le serveur et écoute les connexions entrantes (bloquant).
|
|
51
|
+
|
|
52
|
+
:param handler: Fonction recevant un message (str) et retournant une réponse (str ou générateur)
|
|
53
|
+
"""
|
|
54
|
+
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
55
|
+
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
56
|
+
self.server_socket.bind((self.host, self.port))
|
|
57
|
+
self.server_socket.listen()
|
|
58
|
+
self.is_running = True
|
|
59
|
+
|
|
60
|
+
print(f"[devflow.network] Serveur actif sur {self.host}:{self.port}")
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
while self.is_running:
|
|
64
|
+
conn, addr = self.server_socket.accept()
|
|
65
|
+
# Un thread par client connecté
|
|
66
|
+
client_thread = threading.Thread(
|
|
67
|
+
target=self._handle_client,
|
|
68
|
+
args=(conn, addr, handler),
|
|
69
|
+
daemon=True,
|
|
70
|
+
)
|
|
71
|
+
client_thread.start()
|
|
72
|
+
except KeyboardInterrupt:
|
|
73
|
+
print("\n[devflow.network] Arrêt du serveur.")
|
|
74
|
+
finally:
|
|
75
|
+
self.stop()
|
|
76
|
+
|
|
77
|
+
def stop(self):
|
|
78
|
+
"""Arrête proprement le serveur socket."""
|
|
79
|
+
self.is_running = False
|
|
80
|
+
if self.server_socket:
|
|
81
|
+
self.server_socket.close()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class SocketClient:
|
|
85
|
+
"""Client TCP rapide pour envoyer et recevoir des données."""
|
|
86
|
+
|
|
87
|
+
def __init__(self, host: str = "127.0.0.1", port: int = 65432, buffer_size: int = 4096):
|
|
88
|
+
self.host = host
|
|
89
|
+
self.port = port
|
|
90
|
+
self.buffer_size = buffer_size
|
|
91
|
+
|
|
92
|
+
def send(self, message: str, timeout: float = 10.0) -> str:
|
|
93
|
+
"""Envoie un message au serveur et attend la réponse complète.
|
|
94
|
+
|
|
95
|
+
:param message: Le texte à envoyer
|
|
96
|
+
:param timeout: Temps d'attente maximum en secondes
|
|
97
|
+
:return: La réponse reçue du serveur
|
|
98
|
+
"""
|
|
99
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
|
|
100
|
+
client_socket.settimeout(timeout)
|
|
101
|
+
client_socket.connect((self.host, self.port))
|
|
102
|
+
client_socket.sendall(message.encode("utf-8"))
|
|
103
|
+
|
|
104
|
+
response = client_socket.recv(self.buffer_size)
|
|
105
|
+
return response.decode("utf-8")
|
|
106
|
+
|
|
107
|
+
def send_stream(self, message: str, timeout: float = 10.0) -> Generator[str, None, None]:
|
|
108
|
+
"""Envoie un message et reçoit la réponse en streaming fragment par fragment."""
|
|
109
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
|
|
110
|
+
client_socket.settimeout(timeout)
|
|
111
|
+
client_socket.connect((self.host, self.port))
|
|
112
|
+
client_socket.sendall(message.encode("utf-8"))
|
|
113
|
+
|
|
114
|
+
while True:
|
|
115
|
+
try:
|
|
116
|
+
chunk = client_socket.recv(self.buffer_size)
|
|
117
|
+
if not chunk:
|
|
118
|
+
break
|
|
119
|
+
yield chunk.decode("utf-8")
|
|
120
|
+
except socket.timeout:
|
|
121
|
+
break
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: devflow-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Un couteau suisse pour créer des CLI stylisées, du réseau TCP et intégrer l'IA.
|
|
5
|
+
Author: Alex
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: rich>=12.0.0
|
|
13
|
+
Requires-Dist: google-genai>=0.1.0
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# DevFlow 🚀
|
|
17
|
+
|
|
18
|
+
**DevFlow** est un couteau suisse Python pour simplifier la création d'interfaces en ligne de commande (CLI) stylisées, la programmation réseau par Sockets TCP et l'intégration de l'IA (Gemini).
|
|
19
|
+
|
|
20
|
+
## 📥 Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install devflow-tools
|
|
24
|
+
```
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 👨💻 À propos de l'auteur
|
|
28
|
+
|
|
29
|
+
Projet codé et conçu par **Alex74** (développeur passionné jeune) pour simplifier le développement d'outils CLI et réseau en Python.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
devflow/__init__.py
|
|
5
|
+
devflow/ai.py
|
|
6
|
+
devflow/cli.py
|
|
7
|
+
devflow/help.py
|
|
8
|
+
devflow/network.py
|
|
9
|
+
devflow_tools.egg-info/PKG-INFO
|
|
10
|
+
devflow_tools.egg-info/SOURCES.txt
|
|
11
|
+
devflow_tools.egg-info/dependency_links.txt
|
|
12
|
+
devflow_tools.egg-info/requires.txt
|
|
13
|
+
devflow_tools.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
devflow
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "devflow-tools" # Choisis le nom final de ton package
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Alex" },
|
|
10
|
+
]
|
|
11
|
+
description = "Un couteau suisse pour créer des CLI stylisées, du réseau TCP et intégrer l'IA."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.8"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"rich>=12.0.0",
|
|
21
|
+
"google-genai>=0.1.0",
|
|
22
|
+
]
|