AlleksDev commited on
Commit
20ea7ff
·
unverified ·
1 Parent(s): e26d94c

Place embeddings

Browse files
.env.example CHANGED
@@ -2,7 +2,7 @@ ENV=local
2
  API_HOST=0.0.0.0
3
  API_PORT=8080
4
 
5
- MAIN_API_BASE_URL=http://52.86.8.11
6
  MAIN_API_PLACES_SEARCH_PATH=/api/v1/places/search
7
  MAIN_API_PLACES_NEARBY_PATH=/api/v1/places/nearby
8
  MAIN_API_POSTS_SEARCH_PATH=/api/v1/posts/search
 
2
  API_HOST=0.0.0.0
3
  API_PORT=8080
4
 
5
+ MAIN_API_BASE_URL=http://3.212.166.108
6
  MAIN_API_PLACES_SEARCH_PATH=/api/v1/places/search
7
  MAIN_API_PLACES_NEARBY_PATH=/api/v1/places/nearby
8
  MAIN_API_POSTS_SEARCH_PATH=/api/v1/posts/search
README.md CHANGED
@@ -49,7 +49,7 @@ ENV=local
49
  API_HOST=0.0.0.0
50
  API_PORT=8080
51
 
52
- MAIN_API_BASE_URL=http://52.86.8.11
53
  MAIN_API_PLACES_SEARCH_PATH=/api/v1/places/search
54
  MAIN_API_PLACES_NEARBY_PATH=/api/v1/places/nearby
55
  MAIN_API_POSTS_SEARCH_PATH=/api/v1/posts/search
 
49
  API_HOST=0.0.0.0
50
  API_PORT=8080
51
 
52
+ MAIN_API_BASE_URL=http://3.212.166.108
53
  MAIN_API_PLACES_SEARCH_PATH=/api/v1/places/search
54
  MAIN_API_PLACES_NEARBY_PATH=/api/v1/places/nearby
55
  MAIN_API_POSTS_SEARCH_PATH=/api/v1/posts/search
app/shared/config/settings.py CHANGED
@@ -16,7 +16,7 @@ class Settings(BaseSettings):
16
  api_port: int = Field(default=8080, alias="API_PORT")
17
 
18
  main_api_base_url: str = Field(
19
- default="http://52.86.8.11",
20
  alias="MAIN_API_BASE_URL",
21
  )
22
  main_api_places_search_path: str = Field(
 
16
  api_port: int = Field(default=8080, alias="API_PORT")
17
 
18
  main_api_base_url: str = Field(
19
+ default="http://3.212.166.108",
20
  alias="MAIN_API_BASE_URL",
21
  )
22
  main_api_places_search_path: str = Field(
docs/pgadmin_colab_fasttext.md CHANGED
@@ -39,6 +39,11 @@ principal. Tambien puedes sobrescribirlos con Secrets llamados
39
  `PGVECTOR_HOST`, `PGVECTOR_DATABASE`, `PGVECTOR_WRITER_USER` y
40
  `MAIN_API_BASE_URL`.
41
 
 
 
 
 
 
42
  ## 4. Permitir Temporalmente La IP De Colab
43
 
44
  RDS debe ser accesible desde el runtime. Obten la IP publica actual en una celda:
@@ -69,9 +74,11 @@ Primero lugares:
69
  !python scripts/colab_initial_load_places.py
70
  ```
71
 
72
- Se recomienda ejecutar los archivos con `!python` como arriba. Tambien pueden pegarse
73
- en una celda: ahora detectan la carpeta del repositorio aunque Jupyter no defina
74
- `__file__`, siempre que antes se haya ejecutado `%cd Frimeet-API-NLP`.
 
 
75
 
76
  Despues publicaciones, reutilizando dependencias y el modelo ya descargado:
77
 
 
39
  `PGVECTOR_HOST`, `PGVECTOR_DATABASE`, `PGVECTOR_WRITER_USER` y
40
  `MAIN_API_BASE_URL`.
41
 
42
+ La URL base actual es `http://3.212.166.108`. No agregues `/api/v1` al valor de
43
+ `MAIN_API_BASE_URL`, porque los paths de lugares y publicaciones ya incluyen ese
44
+ segmento. Si existe un Secret `MAIN_API_BASE_URL` en Colab, actualizalo o eliminalo
45
+ para evitar que reemplace este valor.
46
+
47
  ## 4. Permitir Temporalmente La IP De Colab
48
 
49
  RDS debe ser accesible desde el runtime. Obten la IP publica actual en una celda:
 
74
  !python scripts/colab_initial_load_places.py
75
  ```
76
 
77
+ Se recomienda ejecutar los archivos con `!python` como arriba. Tambien puedes copiar
78
+ el contenido completo de cada script en una celda vacia y ejecutarlo directamente:
79
+ si no existe el repositorio, el propio script clonara `hf-deploy`. En ese modo ignora
80
+ los argumentos internos de Jupyter. Si no configuraste `PGVECTOR_WRITER_PASSWORD` en
81
+ Secrets, mostrara una entrada oculta para solicitarlo.
82
 
83
  Despues publicaciones, reutilizando dependencias y el modelo ya descargado:
84
 
scripts/colab_initial_load_places.py CHANGED
@@ -11,10 +11,20 @@ de las variables de entorno. Nunca imprime los valores secretos.
11
  from __future__ import annotations
12
 
13
  import argparse
 
14
  import os
15
  from pathlib import Path
 
16
  import subprocess
17
  import sys
 
 
 
 
 
 
 
 
18
 
19
 
20
  def _find_repo_root() -> Path:
@@ -32,15 +42,36 @@ def _find_repo_root() -> Path:
32
  candidates.extend(current_directory.parents)
33
 
34
  for candidate in candidates:
35
- if (candidate / "requirements.txt").is_file() and (
36
- candidate / "app"
37
- ).is_dir():
38
  return candidate
39
 
40
- raise RuntimeError(
41
- "No se encontro la raiz de Frimeet-API-NLP. Ejecuta primero "
42
- "%cd /content/Frimeet-API-NLP o corre el archivo desde el repositorio."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  )
 
 
 
 
 
 
 
44
 
45
 
46
  REPO_ROOT = _find_repo_root()
@@ -53,6 +84,7 @@ def main() -> None:
53
  _configure_environment()
54
 
55
  if not args.skip_install:
 
56
  _run(
57
  sys.executable,
58
  "-m",
@@ -62,8 +94,15 @@ def main() -> None:
62
  "-r",
63
  str(REPO_ROOT / "requirements.txt"),
64
  )
 
 
 
 
 
 
65
 
66
  if not args.skip_download:
 
67
  _run(
68
  sys.executable,
69
  "-m",
@@ -75,6 +114,8 @@ def main() -> None:
75
  "--destination",
76
  os.environ["FASTTEXT_MODEL_PATH"],
77
  )
 
 
78
 
79
  command = [
80
  sys.executable,
@@ -90,6 +131,11 @@ def main() -> None:
90
  if args.dry_run:
91
  command.append("--dry-run")
92
 
 
 
 
 
 
93
  _run(*command)
94
  print("Carga de lugares terminada correctamente.")
95
 
@@ -97,7 +143,7 @@ def main() -> None:
97
  def _configure_environment() -> None:
98
  defaults = {
99
  "ENV": "colab",
100
- "MAIN_API_BASE_URL": "http://52.86.8.11",
101
  "MAIN_API_PLACES_SEARCH_PATH": "/api/v1/places/search",
102
  "MAIN_API_TIMEOUT_SECONDS": "60",
103
  "MAIN_API_PLACES_PAGE_LIMIT": "50",
@@ -119,11 +165,13 @@ def _configure_environment() -> None:
119
  "LOG_LEVEL": "INFO",
120
  }
121
  for name, default in defaults.items():
122
- os.environ[name] = _read_setting(name, default=default)
 
 
 
123
 
124
- os.environ["PGVECTOR_WRITER_PASSWORD"] = _read_setting(
125
- "PGVECTOR_WRITER_PASSWORD",
126
- required=True,
127
  )
128
 
129
  for optional_name in (
@@ -139,16 +187,21 @@ def _configure_environment() -> None:
139
  def _read_setting(
140
  name: str,
141
  default: str | None = None,
142
- required: bool = False,
143
  ) -> str:
144
  value = os.getenv(name) or _read_colab_secret(name) or default
145
- if required and not value:
146
- raise RuntimeError(
147
- f"Falta el Secret {name!r} en Colab. Activa tambien Notebook access."
148
- )
149
  return value or ""
150
 
151
 
 
 
 
 
 
 
 
 
 
 
152
  def _read_colab_secret(name: str) -> str | None:
153
  try:
154
  from google.colab import userdata
@@ -159,8 +212,58 @@ def _read_colab_secret(name: str) -> str | None:
159
  return None
160
 
161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  def _run(*command: str) -> None:
163
- subprocess.run(list(command), cwd=REPO_ROOT, check=True)
 
 
 
 
 
 
 
 
164
 
165
 
166
  def _parse_args() -> argparse.Namespace:
@@ -171,7 +274,8 @@ def _parse_args() -> argparse.Namespace:
171
  parser.add_argument("--dry-run", action="store_true")
172
  parser.add_argument("--skip-install", action="store_true")
173
  parser.add_argument("--skip-download", action="store_true")
174
- return parser.parse_args()
 
175
 
176
 
177
  if __name__ == "__main__":
 
11
  from __future__ import annotations
12
 
13
  import argparse
14
+ import getpass
15
  import os
16
  from pathlib import Path
17
+ import socket
18
  import subprocess
19
  import sys
20
+ from urllib.error import HTTPError, URLError
21
+ from urllib.parse import urlencode
22
+ from urllib.request import Request, urlopen
23
+
24
+
25
+ REPOSITORY_URL = "https://github.com/AlleksDev/Frimeet-API-NLP.git"
26
+ REPOSITORY_BRANCH = "hf-deploy"
27
+ COLAB_REPOSITORY_PATH = Path("/content/Frimeet-API-NLP")
28
 
29
 
30
  def _find_repo_root() -> Path:
 
42
  candidates.extend(current_directory.parents)
43
 
44
  for candidate in candidates:
45
+ if _is_repository_root(candidate):
 
 
46
  return candidate
47
 
48
+ if COLAB_REPOSITORY_PATH.exists():
49
+ raise RuntimeError(
50
+ f"Existe {COLAB_REPOSITORY_PATH}, pero no contiene un clon valido. "
51
+ "Reinicia el runtime de Colab o elimina esa carpeta incompleta."
52
+ )
53
+
54
+ print("No se encontro el repositorio; clonando la rama hf-deploy...")
55
+ subprocess.run(
56
+ [
57
+ "git",
58
+ "clone",
59
+ "--depth",
60
+ "1",
61
+ "--branch",
62
+ REPOSITORY_BRANCH,
63
+ REPOSITORY_URL,
64
+ str(COLAB_REPOSITORY_PATH),
65
+ ],
66
+ check=True,
67
  )
68
+ if not _is_repository_root(COLAB_REPOSITORY_PATH):
69
+ raise RuntimeError("El repositorio se clono, pero su estructura no es valida.")
70
+ return COLAB_REPOSITORY_PATH
71
+
72
+
73
+ def _is_repository_root(path: Path) -> bool:
74
+ return (path / "requirements.txt").is_file() and (path / "app").is_dir()
75
 
76
 
77
  REPO_ROOT = _find_repo_root()
 
84
  _configure_environment()
85
 
86
  if not args.skip_install:
87
+ print("[1/4] Instalando dependencias del proyecto...", flush=True)
88
  _run(
89
  sys.executable,
90
  "-m",
 
94
  "-r",
95
  str(REPO_ROOT / "requirements.txt"),
96
  )
97
+ else:
98
+ print("[1/4] Reutilizando dependencias instaladas.", flush=True)
99
+
100
+ print("[2/4] Verificando API principal y acceso de red a RDS...", flush=True)
101
+ _check_main_api()
102
+ _check_pgvector_network()
103
 
104
  if not args.skip_download:
105
+ print("[3/4] Descargando o reutilizando el modelo FastText...", flush=True)
106
  _run(
107
  sys.executable,
108
  "-m",
 
114
  "--destination",
115
  os.environ["FASTTEXT_MODEL_PATH"],
116
  )
117
+ else:
118
+ print("[3/4] Reutilizando el modelo FastText descargado.", flush=True)
119
 
120
  command = [
121
  sys.executable,
 
131
  if args.dry_run:
132
  command.append("--dry-run")
133
 
134
+ print(
135
+ "[4/4] Cargando FastText y sincronizando lugares. "
136
+ "La carga inicial del modelo puede tardar varios minutos...",
137
+ flush=True,
138
+ )
139
  _run(*command)
140
  print("Carga de lugares terminada correctamente.")
141
 
 
143
  def _configure_environment() -> None:
144
  defaults = {
145
  "ENV": "colab",
146
+ "MAIN_API_BASE_URL": "http://3.212.166.108",
147
  "MAIN_API_PLACES_SEARCH_PATH": "/api/v1/places/search",
148
  "MAIN_API_TIMEOUT_SECONDS": "60",
149
  "MAIN_API_PLACES_PAGE_LIMIT": "50",
 
165
  "LOG_LEVEL": "INFO",
166
  }
167
  for name, default in defaults.items():
168
+ # A raw notebook cell shares os.environ with every previous execution.
169
+ # Use a Colab Secret when explicitly configured; otherwise reset the
170
+ # value to this script's current default instead of inheriting stale data.
171
+ os.environ[name] = _read_colab_secret(name) or default
172
 
173
+ os.environ["PGVECTOR_WRITER_PASSWORD"] = _read_required_secret(
174
+ "PGVECTOR_WRITER_PASSWORD"
 
175
  )
176
 
177
  for optional_name in (
 
187
  def _read_setting(
188
  name: str,
189
  default: str | None = None,
 
190
  ) -> str:
191
  value = os.getenv(name) or _read_colab_secret(name) or default
 
 
 
 
192
  return value or ""
193
 
194
 
195
+ def _read_required_secret(name: str) -> str:
196
+ value = _read_setting(name)
197
+ if value:
198
+ return value
199
+ value = getpass.getpass(f"Escribe {name} (la entrada permanecera oculta): ").strip()
200
+ if not value:
201
+ raise RuntimeError(f"No se proporciono el valor requerido {name!r}.")
202
+ return value
203
+
204
+
205
  def _read_colab_secret(name: str) -> str | None:
206
  try:
207
  from google.colab import userdata
 
212
  return None
213
 
214
 
215
+ def _check_main_api() -> None:
216
+ base_url = os.environ["MAIN_API_BASE_URL"].rstrip("/")
217
+ path = os.environ["MAIN_API_PLACES_SEARCH_PATH"]
218
+ url = f"{base_url}/{path.lstrip('/')}?{urlencode({'limit': 1})}"
219
+ headers: dict[str, str] = {}
220
+ token = os.getenv("MAIN_API_INTERNAL_TOKEN") or os.getenv("MAIN_API_AUTH_TOKEN")
221
+ if token:
222
+ headers["Authorization"] = f"Bearer {token}"
223
+
224
+ try:
225
+ with urlopen(Request(url, headers=headers), timeout=30) as response:
226
+ status = response.status
227
+ except HTTPError as exc:
228
+ raise RuntimeError(
229
+ f"La API principal respondio HTTP {exc.code} en {url}. "
230
+ "Revisa MAIN_API_INTERNAL_TOKEN y MAIN_API_BASE_URL."
231
+ ) from exc
232
+ except URLError as exc:
233
+ raise RuntimeError(
234
+ f"Colab no pudo conectarse a la API principal {url}: {exc.reason}"
235
+ ) from exc
236
+
237
+ if status >= 400:
238
+ raise RuntimeError(f"La API principal respondio HTTP {status} en {url}.")
239
+ print(f" API principal accesible (HTTP {status}).", flush=True)
240
+
241
+
242
+ def _check_pgvector_network() -> None:
243
+ host = os.environ["PGVECTOR_HOST"]
244
+ port = int(os.environ["PGVECTOR_PORT"])
245
+ try:
246
+ with socket.create_connection((host, port), timeout=15):
247
+ pass
248
+ except OSError as exc:
249
+ raise RuntimeError(
250
+ f"Colab no puede abrir una conexion TCP a {host}:{port}. "
251
+ "Agrega temporalmente la IP publica de este runtime como /32 en el "
252
+ "Security Group de RDS y confirma que la instancia sea accesible."
253
+ ) from exc
254
+ print(f" RDS accesible por red en {host}:{port}.", flush=True)
255
+
256
+
257
  def _run(*command: str) -> None:
258
+ try:
259
+ subprocess.run(list(command), cwd=REPO_ROOT, check=True)
260
+ except subprocess.CalledProcessError as exc:
261
+ executable = " ".join(command)
262
+ raise RuntimeError(
263
+ f"Fallo el comando con codigo {exc.returncode}: {executable}. "
264
+ "Revisa la salida inmediatamente anterior; si API y red aparecen OK, "
265
+ "verifica la password/permisos de nlp_writer y la migracion VECTOR(300)."
266
+ ) from exc
267
 
268
 
269
  def _parse_args() -> argparse.Namespace:
 
274
  parser.add_argument("--dry-run", action="store_true")
275
  parser.add_argument("--skip-install", action="store_true")
276
  parser.add_argument("--skip-download", action="store_true")
277
+ arguments = None if "__file__" in globals() else []
278
+ return parser.parse_args(arguments)
279
 
280
 
281
  if __name__ == "__main__":
scripts/colab_initial_load_posts.py CHANGED
@@ -11,10 +11,20 @@ de las variables de entorno. Nunca imprime los valores secretos.
11
  from __future__ import annotations
12
 
13
  import argparse
 
14
  import os
15
  from pathlib import Path
 
16
  import subprocess
17
  import sys
 
 
 
 
 
 
 
 
18
 
19
 
20
  def _find_repo_root() -> Path:
@@ -32,15 +42,36 @@ def _find_repo_root() -> Path:
32
  candidates.extend(current_directory.parents)
33
 
34
  for candidate in candidates:
35
- if (candidate / "requirements.txt").is_file() and (
36
- candidate / "app"
37
- ).is_dir():
38
  return candidate
39
 
40
- raise RuntimeError(
41
- "No se encontro la raiz de Frimeet-API-NLP. Ejecuta primero "
42
- "%cd /content/Frimeet-API-NLP o corre el archivo desde el repositorio."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  )
 
 
 
 
 
 
 
44
 
45
 
46
  REPO_ROOT = _find_repo_root()
@@ -53,6 +84,7 @@ def main() -> None:
53
  _configure_environment()
54
 
55
  if not args.skip_install:
 
56
  _run(
57
  sys.executable,
58
  "-m",
@@ -62,8 +94,15 @@ def main() -> None:
62
  "-r",
63
  str(REPO_ROOT / "requirements.txt"),
64
  )
 
 
 
 
 
 
65
 
66
  if not args.skip_download:
 
67
  _run(
68
  sys.executable,
69
  "-m",
@@ -75,6 +114,8 @@ def main() -> None:
75
  "--destination",
76
  os.environ["FASTTEXT_MODEL_PATH"],
77
  )
 
 
78
 
79
  command = [
80
  sys.executable,
@@ -90,6 +131,11 @@ def main() -> None:
90
  if args.dry_run:
91
  command.append("--dry-run")
92
 
 
 
 
 
 
93
  _run(*command)
94
  print("Carga de publicaciones terminada correctamente.")
95
 
@@ -97,7 +143,7 @@ def main() -> None:
97
  def _configure_environment() -> None:
98
  defaults = {
99
  "ENV": "colab",
100
- "MAIN_API_BASE_URL": "http://52.86.8.11",
101
  "MAIN_API_POSTS_SEARCH_PATH": "/api/v1/posts/search",
102
  "MAIN_API_TIMEOUT_SECONDS": "60",
103
  "MAIN_API_POSTS_PAGE_LIMIT": "50",
@@ -119,11 +165,13 @@ def _configure_environment() -> None:
119
  "LOG_LEVEL": "INFO",
120
  }
121
  for name, default in defaults.items():
122
- os.environ[name] = _read_setting(name, default=default)
 
 
 
123
 
124
- os.environ["PGVECTOR_WRITER_PASSWORD"] = _read_setting(
125
- "PGVECTOR_WRITER_PASSWORD",
126
- required=True,
127
  )
128
 
129
  for optional_name in (
@@ -139,16 +187,21 @@ def _configure_environment() -> None:
139
  def _read_setting(
140
  name: str,
141
  default: str | None = None,
142
- required: bool = False,
143
  ) -> str:
144
  value = os.getenv(name) or _read_colab_secret(name) or default
145
- if required and not value:
146
- raise RuntimeError(
147
- f"Falta el Secret {name!r} en Colab. Activa tambien Notebook access."
148
- )
149
  return value or ""
150
 
151
 
 
 
 
 
 
 
 
 
 
 
152
  def _read_colab_secret(name: str) -> str | None:
153
  try:
154
  from google.colab import userdata
@@ -159,8 +212,58 @@ def _read_colab_secret(name: str) -> str | None:
159
  return None
160
 
161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  def _run(*command: str) -> None:
163
- subprocess.run(list(command), cwd=REPO_ROOT, check=True)
 
 
 
 
 
 
 
 
164
 
165
 
166
  def _parse_args() -> argparse.Namespace:
@@ -171,7 +274,8 @@ def _parse_args() -> argparse.Namespace:
171
  parser.add_argument("--dry-run", action="store_true")
172
  parser.add_argument("--skip-install", action="store_true")
173
  parser.add_argument("--skip-download", action="store_true")
174
- return parser.parse_args()
 
175
 
176
 
177
  if __name__ == "__main__":
 
11
  from __future__ import annotations
12
 
13
  import argparse
14
+ import getpass
15
  import os
16
  from pathlib import Path
17
+ import socket
18
  import subprocess
19
  import sys
20
+ from urllib.error import HTTPError, URLError
21
+ from urllib.parse import urlencode
22
+ from urllib.request import Request, urlopen
23
+
24
+
25
+ REPOSITORY_URL = "https://github.com/AlleksDev/Frimeet-API-NLP.git"
26
+ REPOSITORY_BRANCH = "hf-deploy"
27
+ COLAB_REPOSITORY_PATH = Path("/content/Frimeet-API-NLP")
28
 
29
 
30
  def _find_repo_root() -> Path:
 
42
  candidates.extend(current_directory.parents)
43
 
44
  for candidate in candidates:
45
+ if _is_repository_root(candidate):
 
 
46
  return candidate
47
 
48
+ if COLAB_REPOSITORY_PATH.exists():
49
+ raise RuntimeError(
50
+ f"Existe {COLAB_REPOSITORY_PATH}, pero no contiene un clon valido. "
51
+ "Reinicia el runtime de Colab o elimina esa carpeta incompleta."
52
+ )
53
+
54
+ print("No se encontro el repositorio; clonando la rama hf-deploy...")
55
+ subprocess.run(
56
+ [
57
+ "git",
58
+ "clone",
59
+ "--depth",
60
+ "1",
61
+ "--branch",
62
+ REPOSITORY_BRANCH,
63
+ REPOSITORY_URL,
64
+ str(COLAB_REPOSITORY_PATH),
65
+ ],
66
+ check=True,
67
  )
68
+ if not _is_repository_root(COLAB_REPOSITORY_PATH):
69
+ raise RuntimeError("El repositorio se clono, pero su estructura no es valida.")
70
+ return COLAB_REPOSITORY_PATH
71
+
72
+
73
+ def _is_repository_root(path: Path) -> bool:
74
+ return (path / "requirements.txt").is_file() and (path / "app").is_dir()
75
 
76
 
77
  REPO_ROOT = _find_repo_root()
 
84
  _configure_environment()
85
 
86
  if not args.skip_install:
87
+ print("[1/4] Instalando dependencias del proyecto...", flush=True)
88
  _run(
89
  sys.executable,
90
  "-m",
 
94
  "-r",
95
  str(REPO_ROOT / "requirements.txt"),
96
  )
97
+ else:
98
+ print("[1/4] Reutilizando dependencias instaladas.", flush=True)
99
+
100
+ print("[2/4] Verificando API principal y acceso de red a RDS...", flush=True)
101
+ _check_main_api()
102
+ _check_pgvector_network()
103
 
104
  if not args.skip_download:
105
+ print("[3/4] Descargando o reutilizando el modelo FastText...", flush=True)
106
  _run(
107
  sys.executable,
108
  "-m",
 
114
  "--destination",
115
  os.environ["FASTTEXT_MODEL_PATH"],
116
  )
117
+ else:
118
+ print("[3/4] Reutilizando el modelo FastText descargado.", flush=True)
119
 
120
  command = [
121
  sys.executable,
 
131
  if args.dry_run:
132
  command.append("--dry-run")
133
 
134
+ print(
135
+ "[4/4] Cargando FastText y sincronizando publicaciones. "
136
+ "La carga inicial del modelo puede tardar varios minutos...",
137
+ flush=True,
138
+ )
139
  _run(*command)
140
  print("Carga de publicaciones terminada correctamente.")
141
 
 
143
  def _configure_environment() -> None:
144
  defaults = {
145
  "ENV": "colab",
146
+ "MAIN_API_BASE_URL": "http://3.212.166.108",
147
  "MAIN_API_POSTS_SEARCH_PATH": "/api/v1/posts/search",
148
  "MAIN_API_TIMEOUT_SECONDS": "60",
149
  "MAIN_API_POSTS_PAGE_LIMIT": "50",
 
165
  "LOG_LEVEL": "INFO",
166
  }
167
  for name, default in defaults.items():
168
+ # A raw notebook cell shares os.environ with every previous execution.
169
+ # Use a Colab Secret when explicitly configured; otherwise reset the
170
+ # value to this script's current default instead of inheriting stale data.
171
+ os.environ[name] = _read_colab_secret(name) or default
172
 
173
+ os.environ["PGVECTOR_WRITER_PASSWORD"] = _read_required_secret(
174
+ "PGVECTOR_WRITER_PASSWORD"
 
175
  )
176
 
177
  for optional_name in (
 
187
  def _read_setting(
188
  name: str,
189
  default: str | None = None,
 
190
  ) -> str:
191
  value = os.getenv(name) or _read_colab_secret(name) or default
 
 
 
 
192
  return value or ""
193
 
194
 
195
+ def _read_required_secret(name: str) -> str:
196
+ value = _read_setting(name)
197
+ if value:
198
+ return value
199
+ value = getpass.getpass(f"Escribe {name} (la entrada permanecera oculta): ").strip()
200
+ if not value:
201
+ raise RuntimeError(f"No se proporciono el valor requerido {name!r}.")
202
+ return value
203
+
204
+
205
  def _read_colab_secret(name: str) -> str | None:
206
  try:
207
  from google.colab import userdata
 
212
  return None
213
 
214
 
215
+ def _check_main_api() -> None:
216
+ base_url = os.environ["MAIN_API_BASE_URL"].rstrip("/")
217
+ path = os.environ["MAIN_API_POSTS_SEARCH_PATH"]
218
+ url = f"{base_url}/{path.lstrip('/')}?{urlencode({'limit': 1})}"
219
+ headers: dict[str, str] = {}
220
+ token = os.getenv("MAIN_API_INTERNAL_TOKEN") or os.getenv("MAIN_API_AUTH_TOKEN")
221
+ if token:
222
+ headers["Authorization"] = f"Bearer {token}"
223
+
224
+ try:
225
+ with urlopen(Request(url, headers=headers), timeout=30) as response:
226
+ status = response.status
227
+ except HTTPError as exc:
228
+ raise RuntimeError(
229
+ f"La API principal respondio HTTP {exc.code} en {url}. "
230
+ "Revisa MAIN_API_INTERNAL_TOKEN y MAIN_API_BASE_URL."
231
+ ) from exc
232
+ except URLError as exc:
233
+ raise RuntimeError(
234
+ f"Colab no pudo conectarse a la API principal {url}: {exc.reason}"
235
+ ) from exc
236
+
237
+ if status >= 400:
238
+ raise RuntimeError(f"La API principal respondio HTTP {status} en {url}.")
239
+ print(f" API principal accesible (HTTP {status}).", flush=True)
240
+
241
+
242
+ def _check_pgvector_network() -> None:
243
+ host = os.environ["PGVECTOR_HOST"]
244
+ port = int(os.environ["PGVECTOR_PORT"])
245
+ try:
246
+ with socket.create_connection((host, port), timeout=15):
247
+ pass
248
+ except OSError as exc:
249
+ raise RuntimeError(
250
+ f"Colab no puede abrir una conexion TCP a {host}:{port}. "
251
+ "Agrega temporalmente la IP publica de este runtime como /32 en el "
252
+ "Security Group de RDS y confirma que la instancia sea accesible."
253
+ ) from exc
254
+ print(f" RDS accesible por red en {host}:{port}.", flush=True)
255
+
256
+
257
  def _run(*command: str) -> None:
258
+ try:
259
+ subprocess.run(list(command), cwd=REPO_ROOT, check=True)
260
+ except subprocess.CalledProcessError as exc:
261
+ executable = " ".join(command)
262
+ raise RuntimeError(
263
+ f"Fallo el comando con codigo {exc.returncode}: {executable}. "
264
+ "Revisa la salida inmediatamente anterior; si API y red aparecen OK, "
265
+ "verifica la password/permisos de nlp_writer y la migracion VECTOR(300)."
266
+ ) from exc
267
 
268
 
269
  def _parse_args() -> argparse.Namespace:
 
274
  parser.add_argument("--dry-run", action="store_true")
275
  parser.add_argument("--skip-install", action="store_true")
276
  parser.add_argument("--skip-download", action="store_true")
277
+ arguments = None if "__file__" in globals() else []
278
+ return parser.parse_args(arguments)
279
 
280
 
281
  if __name__ == "__main__":