@@ -38,6 +38,7 @@ import os
3838import platform
3939import re
4040import shutil
41+ import signal
4142import subprocess
4243import sys
4344import tempfile
@@ -93,6 +94,99 @@ def _get_7z_installation_instructions() -> str:
9394 return "Installez p7zip depuis https://www.7-zip.org/download.html"
9495
9596
97+ def _load_dotenv_file (path : Path , loaded_env : dict [str , str ]) -> None :
98+ """Charge un fichier .env simple en mémoire, sans écraser l'environnement exporté."""
99+ if not path .exists ():
100+ return
101+
102+ for raw_line in path .read_text (encoding = "utf-8" ).splitlines ():
103+ line = raw_line .strip ()
104+
105+ if not line or line .startswith ("#" ) or "=" not in line :
106+ continue
107+
108+ key , value = line .split ("=" , 1 )
109+ key = key .strip ()
110+ value = value .strip ()
111+
112+ if not key :
113+ continue
114+
115+ if (value .startswith ('"' ) and value .endswith ('"' )) or (
116+ value .startswith ("'" ) and value .endswith ("'" )
117+ ):
118+ value = value [1 :- 1 ]
119+
120+ def _replace_env_var (match : re .Match [str ]) -> str :
121+ variable_name = match .group (1 )
122+ return os .environ .get (variable_name , loaded_env .get (variable_name , "" ))
123+
124+ value = re .sub (r"\$\{([A-Z0-9_]+)\}" , _replace_env_var , value )
125+
126+ if key not in os .environ :
127+ loaded_env [key ] = value
128+
129+
130+ def _load_project_env () -> None :
131+ """
132+ Charge `.env` puis `.env.local`, avec surcharge par `.env.local`,
133+ sans écraser les variables déjà exportées dans le shell.
134+ """
135+ project_root = Path (__file__ ).parent .parent
136+ loaded_env : dict [str , str ] = {}
137+
138+ for env_file in (project_root / ".env" , project_root / ".env.local" ):
139+ _load_dotenv_file (env_file , loaded_env )
140+
141+ for key , value in loaded_env .items ():
142+ os .environ .setdefault (key , value )
143+
144+
145+ def _get_prod_target_config (target : str ) -> tuple [str , str ]:
146+ if target == "2025" :
147+ return "BDTOPO_2025_DATABASE_URL" , "dialog-bdtopo-2025"
148+
149+ if target == "2025_2" :
150+ return "BDTOPO_2025_2_DATABASE_URL" , "dialog-bdtopo-2025-2"
151+
152+ raise ValueError (f"Cible de production inconnue: { target } " )
153+
154+
155+ def _open_prod_tunnel (target : str , port : int = 10002 ) -> tuple [subprocess .Popen [str ], str ]:
156+ env_var_name , app_name = _get_prod_target_config (target )
157+ database_url = os .environ .get (env_var_name )
158+
159+ if not database_url :
160+ print (
161+ f"ERROR: { env_var_name } est absente de l'environnement" ,
162+ file = sys .stderr ,
163+ )
164+ return None , ""
165+
166+ print (f"Ouverture d'un tunnel vers { app_name } ..." )
167+
168+ tunnel_proc = subprocess .Popen (
169+ ["./tools/scalingodbtunnel" , app_name , "--host-url" , "--port" , str (port )],
170+ stdout = subprocess .PIPE ,
171+ text = True ,
172+ )
173+
174+ tunnel_url = tunnel_proc .stdout .readline ().strip () if tunnel_proc .stdout else ""
175+
176+ if not tunnel_url :
177+ tunnel_proc .send_signal (signal .SIGINT )
178+ tunnel_proc .wait ()
179+ print (
180+ f"ERROR: impossible d'ouvrir le tunnel vers { app_name } " ,
181+ file = sys .stderr ,
182+ )
183+ return None , ""
184+
185+ print (f"Tunnel ouvert vers { app_name } sur 127.0.0.1:{ port } " )
186+
187+ return tunnel_proc , tunnel_url
188+
189+
96190def _download_file (url : str , dest_path : Path , chunk_size : int = 8192 ) -> None :
97191 """Télécharge un fichier depuis une URL."""
98192 print (f"Téléchargement: { url } " )
@@ -276,31 +370,17 @@ def _resolve_target_database_url(args: argparse.Namespace) -> tuple[str | None,
276370 return args .url , "--url"
277371
278372 if args .prod == "2025" :
279- database_url = os .environ .get ("BDTOPO_2025_DATABASE_URL" )
280- if not database_url :
281- print (
282- "ERROR: BDTOPO_2025_DATABASE_URL est absente de l'environnement" ,
283- file = sys .stderr ,
284- )
285- return None , None
286- print ("Utilisation de BDTOPO_2025_DATABASE_URL pour charger la base cible" )
287- return database_url , "BDTOPO_2025_DATABASE_URL"
373+ return os .environ .get ("BDTOPO_2025_DATABASE_URL" ), "BDTOPO_2025_DATABASE_URL"
288374
289375 if args .prod == "2025_2" :
290- database_url = os .environ .get ("BDTOPO_2025_2_DATABASE_URL" )
291- if not database_url :
292- print (
293- "ERROR: BDTOPO_2025_2_DATABASE_URL est absente de l'environnement" ,
294- file = sys .stderr ,
295- )
296- return None , None
297- print ("Utilisation de BDTOPO_2025_2_DATABASE_URL pour charger la base cible" )
298- return database_url , "BDTOPO_2025_2_DATABASE_URL"
376+ return os .environ .get ("BDTOPO_2025_2_DATABASE_URL" ), "BDTOPO_2025_2_DATABASE_URL"
299377
300378 return None , None
301379
302380
303381def main () -> int :
382+ _load_project_env ()
383+
304384 parser = argparse .ArgumentParser (
305385 description = "Télécharge, dézippe et importe les données BDTOPO EXPRESS" ,
306386 formatter_class = argparse .RawDescriptionHelpFormatter ,
@@ -514,10 +594,15 @@ def main() -> int:
514594
515595 database_url_from_env , database_url_source = _resolve_target_database_url (args )
516596 database_url_for_migrations = None # Sera utilisé pour les migrations
597+ tunnel_proc = None
517598
518- if database_url_from_env :
519- cmd .extend (["--url" , database_url_from_env ])
520- database_url_for_migrations = database_url_from_env
599+ if args .prod :
600+ tunnel_proc , tunnel_url = _open_prod_tunnel (args .prod )
601+ if tunnel_proc is None :
602+ return 1
603+
604+ cmd .extend (["--url" , tunnel_url ])
605+ database_url_for_migrations = tunnel_url
521606 print (f"Base cible sélectionnée via { database_url_source } " )
522607 elif args .url :
523608 cmd .extend (["--url" , args .url ])
@@ -535,7 +620,13 @@ def main() -> int:
535620 cmd .append ("-y" )
536621
537622 print (f"Exécution de: { ' ' .join (cmd )} " )
538- result = subprocess .run (cmd )
623+ try :
624+ result = subprocess .run (cmd )
625+ finally :
626+ if tunnel_proc is not None :
627+ print ("Fermeture du tunnel..." )
628+ tunnel_proc .send_signal (signal .SIGINT )
629+ tunnel_proc .wait ()
539630
540631 if result .returncode != 0 :
541632 print ("ERROR: L'import a échoué" , file = sys .stderr )
0 commit comments