seo-dev-env 0.1.0__py3-none-any.whl → 0.1.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.
Files changed (30) hide show
  1. seo/__init__.py +3 -3
  2. seo/cli.py +165 -0
  3. seo/commandes.py +93 -0
  4. seo/generators.py +206 -25
  5. seo/templates/debutant/app.py +16 -0
  6. seo/templates/debutant/index.html +46 -0
  7. seo/templates/debutant/style.css +110 -0
  8. seo/templates/intermediaire/app/__init__.py +38 -0
  9. seo/templates/intermediaire/app/core/__init__.py +0 -0
  10. seo/templates/intermediaire/app/core/config.py +23 -0
  11. seo/templates/intermediaire/app/taches/__init__.py +0 -0
  12. seo/templates/intermediaire/app/taches/models.py +18 -0
  13. seo/templates/intermediaire/app/taches/routes.py +44 -0
  14. seo/templates/intermediaire/app/utilisateurs/__init__.py +0 -0
  15. seo/templates/intermediaire/app/utilisateurs/models.py +29 -0
  16. seo/templates/intermediaire/app/utilisateurs/routes.py +34 -0
  17. seo/templates/intermediaire/config.py +10 -0
  18. seo/templates/intermediaire/docker-compose.yml +39 -0
  19. seo/templates/intermediaire/run.py +17 -0
  20. seo/templates/pro/Dockerfile +26 -0
  21. seo/templates/pro/docker-compose.yml +39 -0
  22. seo/utils.py +32 -24
  23. {seo_dev_env-0.1.0.dist-info → seo_dev_env-0.1.2.dist-info}/METADATA +2 -2
  24. seo_dev_env-0.1.2.dist-info/RECORD +38 -0
  25. {seo_dev_env-0.1.0.dist-info → seo_dev_env-0.1.2.dist-info}/WHEEL +1 -1
  26. seo_dev_env-0.1.2.dist-info/entry_points.txt +3 -0
  27. seo_dev_env-0.1.0.dist-info/RECORD +0 -26
  28. seo_dev_env-0.1.0.dist-info/entry_points.txt +0 -2
  29. {seo_dev_env-0.1.0.dist-info → seo_dev_env-0.1.2.dist-info}/licenses/LICENSE.ls +0 -0
  30. {seo_dev_env-0.1.0.dist-info → seo_dev_env-0.1.2.dist-info}/top_level.txt +0 -0
seo/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
- from .generators import creer_environnement, creer_projet
2
-
3
- __all__ = ['creer_environnement', 'creer_projet']
1
+ from .generators import creer_environnement, creer_projet, creer_projet_interactif, main
2
+
3
+ __all__ = ['creer_environnement', 'creer_projet', 'creer_projet_interactif', 'main']
seo/cli.py ADDED
@@ -0,0 +1,165 @@
1
+ """CLI interactif pour SEO Dev Env"""
2
+ import sys
3
+ from typing import Dict, Any
4
+
5
+
6
+ def afficher_titre():
7
+ print("\n" + "="*60)
8
+ print(" SEO Dev Env - Créateur d'architecture Flask")
9
+ print(" pour développeurs francophones")
10
+ print("="*60 + "\n")
11
+
12
+
13
+ def poser_question(question: str, options: list, defaut: int = 1) -> int:
14
+ print(f"\n{question}")
15
+ for i, option in enumerate(options, 1):
16
+ marqueur = "" if i == defaut else " "
17
+ print(f" {marqueur} {i}. {option}")
18
+ while True:
19
+ try:
20
+ choix = input(f"\nVotre choix [1-{len(options)}] (défaut: {defaut}): ").strip()
21
+ if not choix:
22
+ return defaut - 1
23
+ choix_int = int(choix)
24
+ if 1 <= choix_int <= len(options):
25
+ return choix_int - 1
26
+ else:
27
+ print(f" Veuillez choisir entre 1 et {len(options)}")
28
+ except ValueError:
29
+ print(" Veuillez entrer un nombre valide")
30
+ except KeyboardInterrupt:
31
+ print("\n\n Annulé")
32
+ sys.exit(0)
33
+
34
+
35
+ def poser_question_texte(question: str, defaut: str = "") -> str:
36
+ prompt = f"{question} (défaut: {defaut}): " if defaut else f"{question}: "
37
+ try:
38
+ reponse = input(prompt).strip()
39
+ return reponse if reponse else defaut
40
+ except KeyboardInterrupt:
41
+ print("\n\n Annulé")
42
+ sys.exit(0)
43
+
44
+
45
+ def confirmer(question: str, defaut: bool = True) -> bool:
46
+ suffixe = "[O/n]" if defaut else "[o/N]"
47
+ try:
48
+ reponse = input(f"{question} {suffixe}: ").strip().lower()
49
+ if not reponse:
50
+ return defaut
51
+ return reponse in ["o", "oui", "y", "yes"]
52
+ except KeyboardInterrupt:
53
+ print("\n\n Annulé")
54
+ sys.exit(0)
55
+
56
+
57
+ def collecter_preferences() -> Dict[str, Any]:
58
+ afficher_titre()
59
+ preferences = {}
60
+
61
+ print(" Configuration du projet")
62
+ preferences["nom_projet"] = poser_question_texte("Nom de votre projet", "mon-projet")
63
+
64
+ type_options = [
65
+ "Apprentissage (simple, pour débuter)",
66
+ "Application web (structure MVC complète)",
67
+ "API professionnelle (production-ready)",
68
+ "Startup SaaS (auth, paiement, dashboard)"
69
+ ]
70
+ type_choix = poser_question(" Quel type de projet ?", type_options)
71
+ types_mapping = {0: "apprentissage", 1: "application", 2: "api", 3: "saas"}
72
+ preferences["type_projet"] = types_mapping[type_choix]
73
+
74
+ if preferences["type_projet"] in ["application", "api", "saas"]:
75
+ db_options = [
76
+ "SQLite (simple, fichier local)",
77
+ "PostgreSQL (recommandé pour production)",
78
+ "MySQL (compatible, largement utilisé)"
79
+ ]
80
+ db_choix = poser_question(" Quelle base de données ?", db_options,
81
+ defaut=2 if preferences["type_projet"] in ["api", "saas"] else 1)
82
+ db_mapping = {0: "sqlite", 1: "postgresql", 2: "mysql"}
83
+ preferences["base_donnees"] = db_mapping[db_choix]
84
+
85
+ if preferences["base_donnees"] == "sqlite" and preferences["type_projet"] in ["api", "saas"]:
86
+ print("\n ATTENTION: SQLite non recommandé pour production.")
87
+ if not confirmer("\n Continuer avec SQLite ?", defaut=False):
88
+ print("\n Passage à PostgreSQL")
89
+ preferences["base_donnees"] = "postgresql"
90
+
91
+ auth_options = [
92
+ "Session classique (cookies Flask)",
93
+ "JWT (tokens, pour API)",
94
+ "OAuth2 (Google, GitHub, etc.)"
95
+ ]
96
+ auth_choix = poser_question(" Type d'authentification ?", auth_options,
97
+ defaut=2 if preferences["type_projet"] == "api" else 1)
98
+ auth_mapping = {0: "session", 1: "jwt", 2: "oauth"}
99
+ preferences["auth"] = auth_mapping[auth_choix]
100
+
101
+ if preferences["type_projet"] in ["api", "saas"]:
102
+ preferences["docker"] = True
103
+ print("\n Docker inclus automatiquement")
104
+ else:
105
+ preferences["docker"] = confirmer("\n Inclure Docker ?", defaut=True)
106
+ else:
107
+ preferences["base_donnees"] = "sqlite"
108
+ preferences["auth"] = "session"
109
+ preferences["docker"] = False
110
+
111
+ if preferences["type_projet"] == "saas":
112
+ preferences["stripe"] = confirmer("\n Inclure Stripe ?", defaut=True)
113
+ preferences["email"] = confirmer(" Inclure Flask-Mail ?", defaut=True)
114
+ preferences["celery"] = confirmer(" Inclure Celery ?", defaut=True)
115
+
116
+ preferences["git"] = confirmer("\n Initialiser Git ?", defaut=True)
117
+ return preferences
118
+
119
+
120
+ def afficher_resume(preferences: Dict[str, Any]):
121
+ print("\n" + "="*60)
122
+ print(" Résumé")
123
+ print("="*60)
124
+ print(f"\n Projet: {preferences['nom_projet']}")
125
+ print(f" Type: {preferences['type_projet']}")
126
+ if "base_donnees" in preferences:
127
+ print(f" Base: {preferences['base_donnees']}")
128
+ if "auth" in preferences:
129
+ print(f" Auth: {preferences['auth']}")
130
+ if preferences.get("docker"):
131
+ print(" Docker: ")
132
+ if preferences.get("stripe"):
133
+ print(" Stripe: ")
134
+ if preferences.get("email"):
135
+ print(" Email: ")
136
+ if preferences.get("celery"):
137
+ print(" Celery: ")
138
+ if preferences.get("git"):
139
+ print(" Git: ")
140
+ print("\n" + "="*60 + "\n")
141
+
142
+
143
+ def afficher_prochaines_etapes(nom_projet: str, type_projet: str, docker: bool = False):
144
+ print("\n" + "="*60)
145
+ print(" Projet créé !")
146
+ print("="*60)
147
+ print(f"\n Prochaines étapes:\n")
148
+ print(f" 1. cd {nom_projet}")
149
+ if docker:
150
+ print(" 2. docker-compose up --build")
151
+ else:
152
+ if type_projet == "apprentissage":
153
+ print(" 2. python app.py")
154
+ else:
155
+ print(" 2. python -m venv venv")
156
+ print(" 3. venv\\Scripts\\activate")
157
+ print(" 4. pip install -r requirements.txt")
158
+ print(" 5. seo db init")
159
+ print(" 6. seo user create")
160
+ print(" 7. seo run")
161
+ print("\n Commandes:")
162
+ print(" seo db init - Initialiser la base")
163
+ print(" seo user create - Créer un admin")
164
+ print(" seo run - Lancer l'app")
165
+ print("\n" + "="*60 + "\n")
seo/commandes.py ADDED
@@ -0,0 +1,93 @@
1
+ """
2
+ Commandes internes pour gérer le projet Flask
3
+ """
4
+ import os
5
+ import sys
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+
10
+ def commande_db(action: str):
11
+ """Gestion de la base de données"""
12
+ if action == "init":
13
+ print(" Initialisation de la base...")
14
+ subprocess.run([sys.executable, "-m", "flask", "db", "init"])
15
+ print(" Base initialisée")
16
+ elif action == "migrate":
17
+ message = input("Message (optionnel): ").strip()
18
+ if message:
19
+ subprocess.run([sys.executable, "-m", "flask", "db", "migrate", "-m", message])
20
+ else:
21
+ subprocess.run([sys.executable, "-m", "flask", "db", "migrate"])
22
+ print(" Migration créée")
23
+ elif action == "upgrade":
24
+ subprocess.run([sys.executable, "-m", "flask", "db", "upgrade"])
25
+ print(" Migrations appliquées")
26
+ else:
27
+ print(f" Action '{action}' inconnue")
28
+
29
+
30
+ def commande_user(action: str):
31
+ """Gestion des utilisateurs"""
32
+ if action == "create":
33
+ print(" Création admin\n")
34
+ username = input("Username: ").strip()
35
+ email = input("Email: ").strip()
36
+ import getpass
37
+ password = getpass.getpass("Password: ")
38
+
39
+ try:
40
+ from app import create_app, db
41
+ from app.models import User
42
+ app = create_app()
43
+ with app.app_context():
44
+ user = User(username=username, email=email, is_admin=True)
45
+ user.set_password(password)
46
+ db.session.add(user)
47
+ db.session.commit()
48
+ print(f" Utilisateur '{username}' créé")
49
+ except ImportError as e:
50
+ print(f" Erreur: {e}")
51
+ elif action == "list":
52
+ try:
53
+ from app import create_app, db
54
+ from app.models import User
55
+ app = create_app()
56
+ with app.app_context():
57
+ users = User.query.all()
58
+ print("\n Utilisateurs:\n")
59
+ for user in users:
60
+ print(f" {user.username}")
61
+ except ImportError:
62
+ print(" Impossible de charger les utilisateurs")
63
+
64
+
65
+ def commande_run(mode: str = "dev"):
66
+ """Lance l'application"""
67
+ if mode == "dev":
68
+ print(" Mode développement...\n")
69
+ os.environ["FLASK_ENV"] = "development"
70
+ os.environ["FLASK_DEBUG"] = "1"
71
+ if Path("run.py").exists():
72
+ subprocess.run([sys.executable, "run.py"])
73
+ elif Path("app.py").exists():
74
+ subprocess.run([sys.executable, "app.py"])
75
+ else:
76
+ subprocess.run([sys.executable, "-m", "flask", "run", "--debug"])
77
+
78
+
79
+ def afficher_aide():
80
+ """Affiche l'aide"""
81
+ print("\n" + "="*60)
82
+ print(" Commandes SEO")
83
+ print("="*60 + "\n")
84
+ print(" Base de données:")
85
+ print(" seo db init - Initialiser")
86
+ print(" seo db migrate - Créer migration")
87
+ print(" seo db upgrade - Appliquer")
88
+ print("\n Utilisateurs:")
89
+ print(" seo user create - Créer admin")
90
+ print(" seo user list - Lister")
91
+ print("\n Lancement:")
92
+ print(" seo run - Mode dev")
93
+ print("\n" + "="*60 + "\n")
seo/generators.py CHANGED
@@ -1,11 +1,13 @@
1
- import os
2
- import subprocess
3
- import sys
4
- import shutil
5
- from pathlib import Path
6
- from .utils import creer_fichier, copier_dossier
7
-
8
- class EnvironnementGenerator:
1
+ import os
2
+ import subprocess
3
+ import sys
4
+ import shutil
5
+ from pathlib import Path
6
+ from typing import Dict, Any
7
+ from .utils import creer_fichier, copier_dossier
8
+ from .cli import collecter_preferences, afficher_resume, afficher_prochaines_etapes
9
+
10
+ class EnvironnementGenerator:
9
11
  """Classe de base pour générer des environnements"""
10
12
 
11
13
  def __init__(self, niveau: str, type_app: str, chemin: str):
@@ -96,21 +98,28 @@ class IntermediaireWebGenerator(EnvironnementGenerator):
96
98
  class ProWebGenerator(EnvironnementGenerator):
97
99
  """Générateur pro - Applications professionnelles"""
98
100
 
99
- def __init__(self, chemin):
100
- super().__init__('pro', 'web', chemin)
101
- self.packages = [
102
- 'flask',
103
- 'flask-restx',
104
- 'flask-cors',
105
- 'flask-sqlalchemy',
106
- 'flask-migrate',
107
- 'flask-jwt-extended',
108
- 'python-dotenv',
109
- 'gunicorn',
110
- 'psycopg2-binary'
111
- ]
112
-
113
- def _post_creation(self):
101
+ def __init__(self, chemin):
102
+ super().__init__('pro', 'web', chemin)
103
+ self.packages = [
104
+ 'flask',
105
+ 'flask-restx',
106
+ 'flask-cors',
107
+ 'flask-sqlalchemy',
108
+ 'flask-migrate',
109
+ 'flask-jwt-extended',
110
+ 'python-dotenv',
111
+ 'gunicorn',
112
+ 'psycopg2-binary'
113
+ ]
114
+
115
+ def _creer_structure(self):
116
+ # Création du fichier requirements
117
+ creer_fichier(
118
+ self.chemin_projet / 'requirements.txt',
119
+ "\n".join(self.packages)
120
+ )
121
+
122
+ def _post_creation(self):
114
123
  # Initialiser un dépôt Git
115
124
  try:
116
125
  subprocess.run(['git', 'init', str(self.chemin_projet)], check=True)
@@ -157,5 +166,177 @@ def creer_environnement(niveau, type_app='web', chemin='.'):
157
166
  print(f"cd {chemin}")
158
167
  print("docker-compose up --build")
159
168
 
160
- # Alias pour une utilisation plus simple
161
- creer_projet = creer_environnement
169
+ # Alias pour une utilisation plus simple
170
+ creer_projet = creer_environnement
171
+
172
+
173
+ def main():
174
+ """Point d'entrée principal pour le CLI"""
175
+ import argparse
176
+
177
+ parser = argparse.ArgumentParser(
178
+ description='SEO Dev Env - Générateur de projets Flask pour francophones'
179
+ )
180
+
181
+ subparsers = parser.add_subparsers(dest='commande', help='Commandes disponibles')
182
+
183
+ # Commande create (mode interactif)
184
+ parser_create = subparsers.add_parser('create', help='Créer un nouveau projet (mode interactif)')
185
+ parser_create.add_argument('nom', nargs='?', help='Nom du projet (optionnel, sera demandé si non fourni)')
186
+
187
+ # Commandes db
188
+ parser_db = subparsers.add_parser('db', help='Gestion de la base de données')
189
+ parser_db.add_argument('action', choices=['init', 'migrate', 'upgrade', 'downgrade'])
190
+
191
+ # Commandes user
192
+ parser_user = subparsers.add_parser('user', help='Gestion des utilisateurs')
193
+ parser_user.add_argument('action', choices=['create', 'list'])
194
+
195
+ # Commande run
196
+ parser_run = subparsers.add_parser('run', help='Lancer l\'application')
197
+ parser_run.add_argument('mode', nargs='?', default='dev', choices=['dev', 'prod'])
198
+
199
+ # Commande help
200
+ parser_help = subparsers.add_parser('help', help='Afficher l\'aide')
201
+
202
+ args = parser.parse_args()
203
+
204
+ # Si aucune commande, afficher l'aide
205
+ if not args.commande:
206
+ from .commandes import afficher_aide
207
+ afficher_aide()
208
+ return
209
+
210
+ # Traiter les commandes
211
+ if args.commande == 'create':
212
+ creer_projet_interactif(args.nom)
213
+
214
+ elif args.commande == 'db':
215
+ from .commandes import commande_db
216
+ commande_db(args.action)
217
+
218
+ elif args.commande == 'user':
219
+ from .commandes import commande_user
220
+ commande_user(args.action)
221
+
222
+ elif args.commande == 'run':
223
+ from .commandes import commande_run
224
+ commande_run(args.mode)
225
+
226
+ elif args.commande == 'help':
227
+ from .commandes import afficher_aide
228
+ afficher_aide()
229
+
230
+
231
+ def creer_projet_interactif(nom_fourni: str = None):
232
+ """Crée un projet en mode interactif"""
233
+ # Collecter les préférences
234
+ preferences = collecter_preferences()
235
+
236
+ # Utiliser le nom fourni en argument si disponible
237
+ if nom_fourni:
238
+ preferences['nom_projet'] = nom_fourni
239
+
240
+ # Afficher le résumé
241
+ afficher_resume(preferences)
242
+
243
+ # Confirmer
244
+ from .cli import confirmer
245
+ if not confirmer("Créer le projet avec cette configuration ?"):
246
+ print("❌ Annulé")
247
+ return
248
+
249
+ # Générer le projet selon le type
250
+ generer_selon_preferences(preferences)
251
+
252
+ # Afficher les prochaines étapes
253
+ afficher_prochaines_etapes(
254
+ preferences['nom_projet'],
255
+ preferences['type_projet'],
256
+ preferences.get('docker', False)
257
+ )
258
+
259
+
260
+ def generer_selon_preferences(preferences: Dict[str, Any]):
261
+ """Génère le projet selon les préférences"""
262
+ type_projet = preferences['type_projet']
263
+ nom_projet = preferences['nom_projet']
264
+
265
+ if type_projet == 'apprentissage':
266
+ generator = ApprentissageGenerator(nom_projet, preferences)
267
+ elif type_projet == 'application':
268
+ generator = ApplicationGenerator(nom_projet, preferences)
269
+ elif type_projet == 'api':
270
+ generator = APIGenerator(nom_projet, preferences)
271
+ elif type_projet == 'saas':
272
+ generator = SaaSGenerator(nom_projet, preferences)
273
+ else:
274
+ raise ValueError(f"Type de projet inconnu: {type_projet}")
275
+
276
+ generator.generer()
277
+
278
+
279
+ class ApprentissageGenerator(EnvironnementGenerator):
280
+ """Générateur pour débutants"""
281
+ def __init__(self, nom: str, preferences: Dict[str, Any]):
282
+ super().__init__('debutant', 'web', nom)
283
+ self.preferences = preferences
284
+ self.packages = ['flask', 'python-dotenv']
285
+
286
+ def _creer_structure(self):
287
+ creer_fichier(self.chemin_projet / 'README.md',
288
+ f"# {self.preferences['nom_projet']}\n\nProjet Flask pour apprentissage")
289
+
290
+
291
+ class ApplicationGenerator(EnvironnementGenerator):
292
+ """Générateur pour applications web (architecture par feature)"""
293
+ def __init__(self, nom: str, preferences: Dict[str, Any]):
294
+ super().__init__('intermediaire', 'web', nom)
295
+ self.preferences = preferences
296
+ self.packages = [
297
+ 'flask', 'flask-sqlalchemy', 'flask-migrate',
298
+ 'flask-wtf', 'flask-login', 'python-dotenv'
299
+ ]
300
+
301
+ def _creer_structure(self):
302
+ creer_fichier(self.chemin_projet / 'requirements.txt', "\n".join(self.packages))
303
+
304
+
305
+ class APIGenerator(EnvironnementGenerator):
306
+ """Générateur pour API professionnelles"""
307
+ def __init__(self, nom: str, preferences: Dict[str, Any]):
308
+ super().__init__('pro', 'api', nom)
309
+ self.preferences = preferences
310
+ self.packages = [
311
+ 'flask', 'flask-restx', 'flask-cors', 'flask-sqlalchemy',
312
+ 'flask-migrate', 'flask-jwt-extended', 'python-dotenv', 'gunicorn'
313
+ ]
314
+ if preferences.get('base_donnees') == 'postgresql':
315
+ self.packages.append('psycopg2-binary')
316
+ elif preferences.get('base_donnees') == 'mysql':
317
+ self.packages.append('pymysql')
318
+
319
+ def _creer_structure(self):
320
+ creer_fichier(self.chemin_projet / 'requirements.txt', "\n".join(self.packages))
321
+
322
+
323
+ class SaaSGenerator(EnvironnementGenerator):
324
+ """Générateur pour startup SaaS"""
325
+ def __init__(self, nom: str, preferences: Dict[str, Any]):
326
+ super().__init__('pro', 'saas', nom)
327
+ self.preferences = preferences
328
+ self.packages = [
329
+ 'flask', 'flask-restx', 'flask-cors', 'flask-sqlalchemy',
330
+ 'flask-migrate', 'flask-jwt-extended', 'python-dotenv', 'gunicorn'
331
+ ]
332
+ if preferences.get('stripe'):
333
+ self.packages.append('stripe')
334
+ if preferences.get('email'):
335
+ self.packages.append('flask-mail')
336
+ if preferences.get('celery'):
337
+ self.packages.extend(['celery', 'redis'])
338
+ if preferences.get('base_donnees') == 'postgresql':
339
+ self.packages.append('psycopg2-binary')
340
+
341
+ def _creer_structure(self):
342
+ creer_fichier(self.chemin_projet / 'requirements.txt', "\n".join(self.packages))
@@ -0,0 +1,16 @@
1
+ from flask import Flask, render_template
2
+
3
+ app = Flask(__name__)
4
+ app.config["SECRET_KEY"] = "votre-cle-secrete-changez-moi"
5
+
6
+ @app.route("/")
7
+ def accueil():
8
+ return render_template("index.html", titre="Bienvenue")
9
+
10
+ @app.route("/a-propos")
11
+ def a_propos():
12
+ return render_template("a_propos.html", titre="À propos")
13
+
14
+ if __name__ == "__main__":
15
+ print(" Application Flask démarrée sur http://localhost:5000")
16
+ app.run(debug=True)
@@ -0,0 +1,46 @@
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ titre }} - Mon Projet Flask</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
8
+ </head>
9
+ <body>
10
+ <nav>
11
+ <div class="container">
12
+ <h1> Mon Projet Flask</h1>
13
+ <ul>
14
+ <li><a href="/">Accueil</a></li>
15
+ <li><a href="/a-propos">À propos</a></li>
16
+ </ul>
17
+ </div>
18
+ </nav>
19
+
20
+ <main class="container">
21
+ <h2>Bienvenue sur votre première application Flask !</h2>
22
+ <p>Cette application a été générée avec <strong>SEO Dev Env</strong>.</p>
23
+
24
+ <div class="card">
25
+ <h3> Prochaines étapes</h3>
26
+ <ul>
27
+ <li>Personnalisez ce template dans <code>templates/index.html</code></li>
28
+ <li>Modifiez le style dans <code>static/style.css</code></li>
29
+ <li>Ajoutez de nouvelles routes dans <code>app.py</code></li>
30
+ </ul>
31
+ </div>
32
+
33
+ <div class="card">
34
+ <h3> Ressources utiles</h3>
35
+ <ul>
36
+ <li><a href="https://flask.palletsprojects.com/" target="_blank">Documentation Flask</a></li>
37
+ <li><a href="https://jinja.palletsprojects.com/" target="_blank">Documentation Jinja2</a></li>
38
+ </ul>
39
+ </div>
40
+ </main>
41
+
42
+ <footer>
43
+ <p>Créé avec en utilisant Flask</p>
44
+ </footer>
45
+ </body>
46
+ </html>
@@ -0,0 +1,110 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
9
+ line-height: 1.6;
10
+ color: #333;
11
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12
+ min-height: 100vh;
13
+ }
14
+
15
+ .container {
16
+ max-width: 1200px;
17
+ margin: 0 auto;
18
+ padding: 0 20px;
19
+ }
20
+
21
+ nav {
22
+ background: rgba(255, 255, 255, 0.95);
23
+ padding: 1rem 0;
24
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
25
+ }
26
+
27
+ nav .container {
28
+ display: flex;
29
+ justify-content: space-between;
30
+ align-items: center;
31
+ }
32
+
33
+ nav h1 {
34
+ color: #667eea;
35
+ }
36
+
37
+ nav ul {
38
+ display: flex;
39
+ list-style: none;
40
+ gap: 2rem;
41
+ }
42
+
43
+ nav a {
44
+ color: #333;
45
+ text-decoration: none;
46
+ font-weight: 500;
47
+ transition: color 0.3s;
48
+ }
49
+
50
+ nav a:hover {
51
+ color: #667eea;
52
+ }
53
+
54
+ main {
55
+ margin-top: 3rem;
56
+ padding: 2rem;
57
+ background: white;
58
+ border-radius: 10px;
59
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
60
+ }
61
+
62
+ h2 {
63
+ color: #667eea;
64
+ margin-bottom: 1rem;
65
+ }
66
+
67
+ .card {
68
+ background: #f7f9fc;
69
+ padding: 1.5rem;
70
+ border-radius: 8px;
71
+ margin: 1.5rem 0;
72
+ border-left: 4px solid #667eea;
73
+ }
74
+
75
+ .card h3 {
76
+ margin-bottom: 1rem;
77
+ color: #333;
78
+ }
79
+
80
+ .card ul {
81
+ margin-left: 1.5rem;
82
+ }
83
+
84
+ .card li {
85
+ margin: 0.5rem 0;
86
+ }
87
+
88
+ code {
89
+ background: #e7eaf6;
90
+ padding: 2px 6px;
91
+ border-radius: 3px;
92
+ font-family: "Courier New", monospace;
93
+ color: #667eea;
94
+ }
95
+
96
+ footer {
97
+ text-align: center;
98
+ padding: 2rem;
99
+ color: white;
100
+ margin-top: 3rem;
101
+ }
102
+
103
+ a {
104
+ color: #667eea;
105
+ text-decoration: none;
106
+ }
107
+
108
+ a:hover {
109
+ text-decoration: underline;
110
+ }
@@ -0,0 +1,38 @@
1
+ """
2
+ Point d'entrée de l'application
3
+ Architecture par feature (domain-based)
4
+ """
5
+ from flask import Flask
6
+ from flask_sqlalchemy import SQLAlchemy
7
+ from flask_migrate import Migrate
8
+ from flask_login import LoginManager
9
+ from .core.config import Config
10
+
11
+ db = SQLAlchemy()
12
+ migrate = Migrate()
13
+ login_manager = LoginManager()
14
+ login_manager.login_view = "utilisateurs.connexion"
15
+ login_manager.login_message = "Veuillez vous connecter pour accéder à cette page."
16
+
17
+ def create_app(config_class=Config):
18
+ app = Flask(__name__)
19
+ app.config.from_object(config_class)
20
+
21
+ # Initialiser les extensions
22
+ db.init_app(app)
23
+ migrate.init_app(app, db)
24
+ login_manager.init_app(app)
25
+
26
+ # Enregistrer les blueprints (features)
27
+ from app.utilisateurs.routes import bp as utilisateurs_bp
28
+ from app.taches.routes import bp as taches_bp
29
+
30
+ app.register_blueprint(utilisateurs_bp)
31
+ app.register_blueprint(taches_bp)
32
+
33
+ # Route racine
34
+ @app.route("/")
35
+ def index():
36
+ return "Application Flask - Architecture par feature"
37
+
38
+ return app
@@ -0,0 +1,23 @@
1
+ """Configuration de l'application"""
2
+ import os
3
+ from pathlib import Path
4
+
5
+ basedir = Path(__file__).resolve().parent.parent.parent
6
+
7
+ class Config:
8
+ SECRET_KEY = os.environ.get("SECRET_KEY") or "dev-secret-key-changez-moi"
9
+ SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") or f"sqlite:///{basedir / 'app.db'}"
10
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
11
+
12
+ class DevelopmentConfig(Config):
13
+ DEBUG = True
14
+
15
+ class ProductionConfig(Config):
16
+ DEBUG = False
17
+ # Configurations production ici
18
+
19
+ config = {
20
+ "development": DevelopmentConfig,
21
+ "production": ProductionConfig,
22
+ "default": DevelopmentConfig
23
+ }
@@ -0,0 +1,18 @@
1
+ """Modèle Tâche"""
2
+ from app import db
3
+ from datetime import datetime
4
+
5
+ class Tache(db.Model):
6
+ __tablename__ = "taches"
7
+
8
+ id = db.Column(db.Integer, primary_key=True)
9
+ titre = db.Column(db.String(200), nullable=False)
10
+ description = db.Column(db.Text)
11
+ termine = db.Column(db.Boolean, default=False)
12
+ date_creation = db.Column(db.DateTime, default=datetime.utcnow)
13
+
14
+ # Relation avec utilisateur
15
+ user_id = db.Column(db.Integer, db.ForeignKey("utilisateurs.id"), nullable=False)
16
+
17
+ def __repr__(self):
18
+ return f"<Tache {self.titre}>"
@@ -0,0 +1,44 @@
1
+ """Routes tâches"""
2
+ from flask import Blueprint, render_template, redirect, url_for, flash, request
3
+ from flask_login import login_required, current_user
4
+ from app import db
5
+ from app.taches.models import Tache
6
+
7
+ bp = Blueprint("taches", __name__, url_prefix="/taches")
8
+
9
+ @bp.route("/")
10
+ @login_required
11
+ def liste():
12
+ taches = current_user.taches.order_by(Tache.date_creation.desc()).all()
13
+ return render_template("taches/liste.html", taches=taches)
14
+
15
+ @bp.route("/ajouter", methods=["POST"])
16
+ @login_required
17
+ def ajouter():
18
+ titre = request.form.get("titre")
19
+ if titre:
20
+ tache = Tache(titre=titre, proprietaire=current_user)
21
+ db.session.add(tache)
22
+ db.session.commit()
23
+ flash("Tâche ajoutée!", "success")
24
+ return redirect(url_for("taches.liste"))
25
+
26
+ @bp.route("/terminer/<int:id>")
27
+ @login_required
28
+ def terminer(id):
29
+ tache = Tache.query.get_or_404(id)
30
+ if tache.proprietaire == current_user:
31
+ tache.termine = not tache.termine
32
+ db.session.commit()
33
+ flash("Tâche mise à jour!", "success")
34
+ return redirect(url_for("taches.liste"))
35
+
36
+ @bp.route("/supprimer/<int:id>")
37
+ @login_required
38
+ def supprimer(id):
39
+ tache = Tache.query.get_or_404(id)
40
+ if tache.proprietaire == current_user:
41
+ db.session.delete(tache)
42
+ db.session.commit()
43
+ flash("Tâche supprimée!", "success")
44
+ return redirect(url_for("taches.liste"))
@@ -0,0 +1,29 @@
1
+ """Modèle Utilisateur (gestion des utilisateurs)"""
2
+ from app import db, login_manager
3
+ from flask_login import UserMixin
4
+ from werkzeug.security import generate_password_hash, check_password_hash
5
+
6
+ class User(UserMixin, db.Model):
7
+ __tablename__ = "utilisateurs"
8
+
9
+ id = db.Column(db.Integer, primary_key=True)
10
+ username = db.Column(db.String(64), unique=True, nullable=False, index=True)
11
+ email = db.Column(db.String(120), unique=True, nullable=False, index=True)
12
+ password_hash = db.Column(db.String(256))
13
+ is_admin = db.Column(db.Boolean, default=False)
14
+
15
+ # Relations
16
+ taches = db.relationship("Tache", backref="proprietaire", lazy="dynamic", cascade="all, delete-orphan")
17
+
18
+ def set_password(self, password):
19
+ self.password_hash = generate_password_hash(password)
20
+
21
+ def check_password(self, password):
22
+ return check_password_hash(self.password_hash, password)
23
+
24
+ def __repr__(self):
25
+ return f"<User {self.username}>"
26
+
27
+ @login_manager.user_loader
28
+ def load_user(user_id):
29
+ return User.query.get(int(user_id))
@@ -0,0 +1,34 @@
1
+ """Routes utilisateurs (auth, profil, etc.)"""
2
+ from flask import Blueprint, render_template, redirect, url_for, flash, request
3
+ from flask_login import login_user, logout_user, login_required, current_user
4
+ from app import db
5
+ from app.utilisateurs.models import User
6
+
7
+ bp = Blueprint("utilisateurs", __name__, url_prefix="/auth")
8
+
9
+ @bp.route("/connexion", methods=["GET", "POST"])
10
+ def connexion():
11
+ if current_user.is_authenticated:
12
+ return redirect(url_for("taches.liste"))
13
+
14
+ if request.method == "POST":
15
+ username = request.form.get("username")
16
+ password = request.form.get("password")
17
+
18
+ user = User.query.filter_by(username=username).first()
19
+ if user and user.check_password(password):
20
+ login_user(user)
21
+ flash("Connexion réussie!", "success")
22
+ next_page = request.args.get("next")
23
+ return redirect(next_page or url_for("taches.liste"))
24
+ else:
25
+ flash("Identifiants invalides", "error")
26
+
27
+ return render_template("utilisateurs/connexion.html")
28
+
29
+ @bp.route("/deconnexion")
30
+ @login_required
31
+ def deconnexion():
32
+ logout_user()
33
+ flash("Déconnexion réussie", "info")
34
+ return redirect(url_for("utilisateurs.connexion"))
@@ -0,0 +1,10 @@
1
+ """Configuration racine du projet"""
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+ class Config:
8
+ SECRET_KEY = os.environ.get("SECRET_KEY") or "dev-secret-changez-moi-en-production"
9
+ SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") or "sqlite:///app.db"
10
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
@@ -0,0 +1,39 @@
1
+ version: '3.8'
2
+
3
+ services:
4
+ web:
5
+ build: .
6
+ container_name: flask_app
7
+ ports:
8
+ - "5000:5000"
9
+ environment:
10
+ - FLASK_ENV=production
11
+ - DATABASE_URL=postgresql://postgres:postgres@db:5432/app_db
12
+ - SECRET_KEY=changez-moi-en-production
13
+ depends_on:
14
+ - db
15
+ - redis
16
+ volumes:
17
+ - .:/app
18
+ command: gunicorn --bind 0.0.0.0:5000 --workers 4 --reload run:app
19
+
20
+ db:
21
+ image: postgres:15-alpine
22
+ container_name: postgres_db
23
+ environment:
24
+ - POSTGRES_USER=postgres
25
+ - POSTGRES_PASSWORD=postgres
26
+ - POSTGRES_DB=app_db
27
+ volumes:
28
+ - postgres_data:/var/lib/postgresql/data
29
+ ports:
30
+ - "5432:5432"
31
+
32
+ redis:
33
+ image: redis:7-alpine
34
+ container_name: redis_cache
35
+ ports:
36
+ - "6379:6379"
37
+
38
+ volumes:
39
+ postgres_data:
@@ -0,0 +1,17 @@
1
+ """Point d'entrée pour lancer l'application"""
2
+ import os
3
+ from app import create_app, db
4
+ from app.utilisateurs.models import User
5
+ from app.taches.models import Tache
6
+
7
+ app = create_app()
8
+
9
+ @app.shell_context_processor
10
+ def make_shell_context():
11
+ return {"db": db, "User": User, "Tache": Tache}
12
+
13
+ if __name__ == "__main__":
14
+ print(" Application Flask démarrée")
15
+ print(" Architecture par feature (domain-based)")
16
+ print(" http://localhost:5000")
17
+ app.run(debug=True)
@@ -0,0 +1,26 @@
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Installer les dépendances système
6
+ RUN apt-get update && apt-get install -y \
7
+ gcc \
8
+ postgresql-client \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Copier les requirements
12
+ COPY requirements.txt .
13
+ RUN pip install --no-cache-dir -r requirements.txt
14
+
15
+ # Copier l'application
16
+ COPY . .
17
+
18
+ # Exposer le port
19
+ EXPOSE 5000
20
+
21
+ # Variables d'environnement
22
+ ENV FLASK_APP=run.py
23
+ ENV PYTHONUNBUFFERED=1
24
+
25
+ # Commande par défaut
26
+ CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "run:app"]
@@ -0,0 +1,39 @@
1
+ version: '3.8'
2
+
3
+ services:
4
+ web:
5
+ build: .
6
+ container_name: flask_app
7
+ ports:
8
+ - "5000:5000"
9
+ environment:
10
+ - FLASK_ENV=production
11
+ - DATABASE_URL=postgresql://postgres:postgres@db:5432/app_db
12
+ - SECRET_KEY=changez-moi-en-production
13
+ depends_on:
14
+ - db
15
+ - redis
16
+ volumes:
17
+ - .:/app
18
+ command: gunicorn --bind 0.0.0.0:5000 --workers 4 --reload run:app
19
+
20
+ db:
21
+ image: postgres:15-alpine
22
+ container_name: postgres_db
23
+ environment:
24
+ - POSTGRES_USER=postgres
25
+ - POSTGRES_PASSWORD=postgres
26
+ - POSTGRES_DB=app_db
27
+ volumes:
28
+ - postgres_data:/var/lib/postgresql/data
29
+ ports:
30
+ - "5432:5432"
31
+
32
+ redis:
33
+ image: redis:7-alpine
34
+ container_name: redis_cache
35
+ ports:
36
+ - "6379:6379"
37
+
38
+ volumes:
39
+ postgres_data:
seo/utils.py CHANGED
@@ -1,24 +1,32 @@
1
- import os
2
- from pathlib import Path
3
-
4
- def creer_fichier(chemin, contenu):
5
- """Crée un fichier avec le contenu spécifié"""
6
- chemin = Path(chemin)
7
- chemin.parent.mkdir(parents=True, exist_ok=True)
8
- with open(chemin, 'w', encoding='utf-8') as f:
9
- f.write(contenu)
10
-
11
- def copier_dossier(source, destination):
12
- """Copie récursivement un dossier"""
13
- source = Path(source)
14
- destination = Path(destination)
15
-
16
- destination.mkdir(parents=True, exist_ok=True)
17
-
18
- for item in source.iterdir():
19
- dest_item = destination / item.name
20
- if item.is_dir():
21
- copier_dossier(item, dest_item)
22
- else:
23
- with open(item, 'r', encoding='utf-8') as src_file:
24
- creer_fichier(dest_item, src_file.read())
1
+ import os
2
+ import shutil
3
+ from pathlib import Path
4
+
5
+ def creer_fichier(chemin, contenu):
6
+ """Crée un fichier avec le contenu spécifié"""
7
+ chemin = Path(chemin)
8
+ chemin.parent.mkdir(parents=True, exist_ok=True)
9
+ with open(chemin, 'w', encoding='utf-8') as f:
10
+ f.write(contenu)
11
+
12
+ def copier_dossier(source, destination):
13
+ """Copie récursivement un dossier"""
14
+ source = Path(source)
15
+ destination = Path(destination)
16
+
17
+ destination.mkdir(parents=True, exist_ok=True)
18
+
19
+ for item in source.iterdir():
20
+ # Skip .gitignore and other hidden files that might have encoding issues
21
+ if item.name.startswith('.'):
22
+ continue
23
+
24
+ dest_item = destination / item.name
25
+ if item.is_dir():
26
+ copier_dossier(item, dest_item)
27
+ else:
28
+ # Use shutil.copy2 to preserve metadata and handle binary files
29
+ try:
30
+ shutil.copy2(item, dest_item)
31
+ except Exception as e:
32
+ print(f"⚠️ Erreur lors de la copie de {item.name}: {e}")
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seo-dev-env
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Générateur d'environnements de développement pour tous niveaux
5
- Home-page: https://github.com/https://github.com/elkast/seo-dev-env
5
+ Home-page: https://github.com/elkast/seo-dev-env
6
6
  Author: Votre Nom
7
7
  Author-email: orsinimelchisedek@gmail.com
8
8
  Classifier: Programming Language :: Python :: 3
@@ -0,0 +1,38 @@
1
+ seo/__init__.py,sha256=5_-smvpT5QbwLmxKXc4X-JfmzFuyueswNbCKCQIGftA,174
2
+ seo/cli.py,sha256=svTbLiZHUuD5_PUBkeNzl-hjELggT_wHH_VDxymJfkk,6224
3
+ seo/commandes.py,sha256=IV2adIYBdP8PjZoE6MK9D0ZXrow1M7B6CX2TYQD99Io,3150
4
+ seo/generators.py,sha256=cXpXny_w6lXDY4XG_LjpQoYN-HnNB8PAb_4nxh064Gk,12268
5
+ seo/utils.py,sha256=ssHO8tNVJPKSTcfxRlFfijtNzZ2i1USOzMogQK9OviA,1067
6
+ seo/templates/debutant/app.py,sha256=NqLiFgvPttDi_Ce5O-oT0yiPtViTfbkWSuLpN1QRNDU,444
7
+ seo/templates/debutant/index.html,sha256=GWGIhLUjvY22vOgxO-s1ocU752KeR057CQEG4cTSwtE,1557
8
+ seo/templates/debutant/style.css,sha256=pIa_Il6FDr-WVS1x3IS9F0kYiDJTYIP4PjuHFnoj_7A,1661
9
+ seo/templates/intermediaire/config.py,sha256=mKWh1yCEsYS5PG_PBfXO9XQqlXNyC0Jf7X-xZhRvous,326
10
+ seo/templates/intermediaire/docker-compose.yml,sha256=hjtekZDcaxglQ4wNjE_3Yvlvb2YqAXX4KQCUGBiqrHs,805
11
+ seo/templates/intermediaire/run.py,sha256=2l746zNzGoSlxcr1Hk_1ydc3kQ9VS7yvRC0MWJJsu38,487
12
+ seo/templates/intermediaire/app/__init__.py,sha256=HBK_2SrWkde3etSPXH8OP2m7rs0g4ljYu73t0iYdBHs,1076
13
+ seo/templates/intermediaire/app/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ seo/templates/intermediaire/app/routes.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ seo/templates/intermediaire/app/core/__init__.py,sha256=LgjR9gAK71QXl9AIwFrDb02-v7NsusVhV4jm_MWzAKc,6
16
+ seo/templates/intermediaire/app/core/config.py,sha256=7M0zTUu1uwbbT8mCG5QcELVcmad8m6pHCb2BdL7qEd0,626
17
+ seo/templates/intermediaire/app/taches/__init__.py,sha256=LgjR9gAK71QXl9AIwFrDb02-v7NsusVhV4jm_MWzAKc,6
18
+ seo/templates/intermediaire/app/taches/models.py,sha256=0ieYf4AUqFvGb1DIYID5nyRIirQ304jenzVfMB8uECU,582
19
+ seo/templates/intermediaire/app/taches/routes.py,sha256=hnpX9_Ql1amxI8uopWlnF8A6WpGrGKlCSRwd1n888-s,1406
20
+ seo/templates/intermediaire/app/utilisateurs/__init__.py,sha256=LgjR9gAK71QXl9AIwFrDb02-v7NsusVhV4jm_MWzAKc,6
21
+ seo/templates/intermediaire/app/utilisateurs/models.py,sha256=-rWh2w4LvwlRLSmwhmEqgxSFrSj2OsfIcmB233y1Yo4,1086
22
+ seo/templates/intermediaire/app/utilisateurs/routes.py,sha256=uUE82cBWLL-PiFFlJd2w5AsbNbh31oOjD2o8GAyXgEg,1246
23
+ seo/templates/pro/Dockerfile,sha256=VlrKrPXWBj8Ohj6BENbgVTvyHGZeDHDB1Ybe8OHqSTI,526
24
+ seo/templates/pro/docker-compose.yml,sha256=hjtekZDcaxglQ4wNjE_3Yvlvb2YqAXX4KQCUGBiqrHs,805
25
+ seo/templates/pro/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ seo/templates/pro/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ seo/templates/pro/app/api/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ seo/templates/pro/app/api/v1/endpoints.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ seo/templates/pro/app/core/config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
+ seo/templates/pro/app/core/security.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ seo/templates/pro/app/db/base.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
+ seo/templates/pro/app/db/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
+ seo_dev_env-0.1.2.dist-info/licenses/LICENSE.ls,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
+ seo_dev_env-0.1.2.dist-info/METADATA,sha256=NsR7chxa3ym32ovKhaPLcsRnGRNr0cQxeEvcA-cvwAQ,13942
35
+ seo_dev_env-0.1.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
36
+ seo_dev_env-0.1.2.dist-info/entry_points.txt,sha256=tAn_vOcrXDG6ilFg7Zvacd4bf9fQXAhsmimMr9lEU84,96
37
+ seo_dev_env-0.1.2.dist-info/top_level.txt,sha256=5WsA3DhwhBGXTOKiKgDtiTUjXlpLk4oeUvrSKrtRFoQ,4
38
+ seo_dev_env-0.1.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ seo = seo.generators:main
3
+ seo-create = seo.generators:creer_projet_interactif
@@ -1,26 +0,0 @@
1
- seo/__init__.py,sha256=UZS5pTtyPZ4-3JGGZSgfEKl8aTTFY9IsiM6gvwKRMzA,110
2
- seo/generators.py,sha256=E2lTwkZNvtJiT4FyC3tMt_wDJaPGBxjVmvXTaxORwuo,5755
3
- seo/utils.py,sha256=ocTb_CotkmmB_5aZSZXGCG_gJNaC9GdvZFb2ayWHaLE,794
4
- seo/templates/debutant/app.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- seo/templates/debutant/index.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- seo/templates/debutant/style.css,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- seo/templates/intermediaire/config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- seo/templates/intermediaire/run.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- seo/templates/intermediaire/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- seo/templates/intermediaire/app/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- seo/templates/intermediaire/app/routes.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- seo/templates/pro/docker-compose.yml,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- seo/templates/pro/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- seo/templates/pro/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- seo/templates/pro/app/api/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- seo/templates/pro/app/api/v1/endpoints.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- seo/templates/pro/app/core/config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- seo/templates/pro/app/core/security.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- seo/templates/pro/app/db/base.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- seo/templates/pro/app/db/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- seo_dev_env-0.1.0.dist-info/licenses/LICENSE.ls,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- seo_dev_env-0.1.0.dist-info/METADATA,sha256=bKC9Xe9jcyCuwYf93L9W3o0-JAPJp268E_48UuIFWK8,13961
23
- seo_dev_env-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
- seo_dev_env-0.1.0.dist-info/entry_points.txt,sha256=lrqV5FhVM9pbEiYK-U9rtiWP1ZaUditqz9hpnLxy9E4,51
25
- seo_dev_env-0.1.0.dist-info/top_level.txt,sha256=5WsA3DhwhBGXTOKiKgDtiTUjXlpLk4oeUvrSKrtRFoQ,4
26
- seo_dev_env-0.1.0.dist-info/RECORD,,
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- seo-create = seo.generators:main