tai-api 0.2.5__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.
- tai_api/__init__.py +19 -0
- tai_api/cli/commands/__init__.py +13 -0
- tai_api/cli/commands/cmd_auth/__init__.py +1 -0
- tai_api/cli/commands/cmd_auth/database.py +153 -0
- tai_api/cli/commands/cmd_auth/keycloak.py +24 -0
- tai_api/cli/commands/cmd_auth/main.py +179 -0
- tai_api/cli/commands/cmd_dev/__init__.py +1 -0
- tai_api/cli/commands/cmd_dev/main.py +27 -0
- tai_api/cli/commands/cmd_generate/__init__.py +5 -0
- tai_api/cli/commands/cmd_generate/funcs.py +61 -0
- tai_api/cli/commands/cmd_generate/main.py +44 -0
- tai_api/cli/commands/cmd_init/__init__.py +1 -0
- tai_api/cli/commands/cmd_init/main.py +22 -0
- tai_api/cli/commands/cmd_init/model.py +160 -0
- tai_api/cli/commands/cmd_init/resources/__init__.py +67 -0
- tai_api/cli/commands/cmd_init/resources/exceptions.py +232 -0
- tai_api/cli/commands/cmd_init/resources/handlers.py +334 -0
- tai_api/cli/commands/cmd_init/resources/mcp.py +76 -0
- tai_api/cli/commands/cmd_init/resources/responses.py +180 -0
- tai_api/cli/commands/cmd_mcp/__init__.py +5 -0
- tai_api/cli/commands/cmd_mcp/main.py +37 -0
- tai_api/cli/main.py +22 -0
- tai_api/config.py +227 -0
- tai_api/generators/__init__.py +10 -0
- tai_api/generators/auth/__init__.py +2 -0
- tai_api/generators/auth/db.py +185 -0
- tai_api/generators/auth/keycloak.py +151 -0
- tai_api/generators/auth/templates/db/__init__.py.j2 +28 -0
- tai_api/generators/auth/templates/db/auth_router.py.j2 +230 -0
- tai_api/generators/auth/templates/db/dependencies.py.j2 +69 -0
- tai_api/generators/auth/templates/db/jwt.py.j2 +103 -0
- tai_api/generators/auth/templates/kc/__init__.py.j2 +28 -0
- tai_api/generators/auth/templates/kc/dependencies.py.j2 +96 -0
- tai_api/generators/auth/templates/kc/keycloak_router.py.j2 +187 -0
- tai_api/generators/auth/templates/kc/login_router.py.j2 +135 -0
- tai_api/generators/auth/templates/kc/utils.py.j2 +57 -0
- tai_api/generators/main_file/__init__.py +14 -0
- tai_api/generators/main_file/main.py +86 -0
- tai_api/generators/main_file/templates/__dev__.py.j2 +74 -0
- tai_api/generators/main_file/templates/__main__.py.j2 +114 -0
- tai_api/generators/routers/__init__.py +3 -0
- tai_api/generators/routers/main.py +196 -0
- tai_api/generators/routers/templates/__init__.py.j2 +7 -0
- tai_api/generators/routers/templates/__outerinit__.py.j2 +7 -0
- tai_api/generators/routers/templates/enums_template.py.j2 +26 -0
- tai_api/generators/routers/templates/macros.j2 +324 -0
- tai_api/generators/routers/templates/router_template.py.j2 +1003 -0
- tai_api/project.py +177 -0
- tai_api-0.2.5.dist-info/METADATA +28 -0
- tai_api-0.2.5.dist-info/RECORD +53 -0
- tai_api-0.2.5.dist-info/WHEEL +4 -0
- tai_api-0.2.5.dist-info/entry_points.txt +3 -0
- tai_api-0.2.5.dist-info/licenses/LICENSE +674 -0
tai_api/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .project import ProjectManager as pm
|
|
2
|
+
from .config import (
|
|
3
|
+
ProjectConfig,
|
|
4
|
+
AuthConfig,
|
|
5
|
+
AuthType,
|
|
6
|
+
DatabaseAuthConfig,
|
|
7
|
+
KeycloakAuthConfig
|
|
8
|
+
)
|
|
9
|
+
from .generators import MainFileGenerator
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
'ProjectConfig',
|
|
13
|
+
'AuthConfig',
|
|
14
|
+
'AuthType',
|
|
15
|
+
'DatabaseAuthConfig',
|
|
16
|
+
'KeycloakAuthConfig',
|
|
17
|
+
'MainFileGenerator',
|
|
18
|
+
'pm'
|
|
19
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import set_auth
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import click
|
|
3
|
+
|
|
4
|
+
from tai_sql import pm as sqlpm
|
|
5
|
+
from tai_api import DatabaseAuthConfig
|
|
6
|
+
|
|
7
|
+
def rundbconfig() -> DatabaseAuthConfig:
|
|
8
|
+
"""
|
|
9
|
+
Configura la autenticación basada en base de datos.
|
|
10
|
+
"""
|
|
11
|
+
click.echo("\n🗄️ Configurando autenticación de base de datos...")
|
|
12
|
+
click.echo("-" * 40)
|
|
13
|
+
|
|
14
|
+
if not sqlpm.db.tables:
|
|
15
|
+
click.echo("❌ No se encontraron tablas en la base de datos.", err=True)
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
|
|
18
|
+
tables = sqlpm.db.tables
|
|
19
|
+
|
|
20
|
+
# Mostrar tablas disponibles
|
|
21
|
+
click.echo("\n📊 Tablas disponibles:")
|
|
22
|
+
for i, table in enumerate(tables, 1):
|
|
23
|
+
column_count = len(table.columns)
|
|
24
|
+
click.echo(f" {i}. {table._name} ({column_count} columnas)")
|
|
25
|
+
|
|
26
|
+
# Seleccionar tabla de usuarios
|
|
27
|
+
while True:
|
|
28
|
+
choice: int = click.prompt(
|
|
29
|
+
f"\n🔢 Selecciona la tabla de usuarios (1-{len(tables)})",
|
|
30
|
+
type=int,
|
|
31
|
+
show_default=False
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if 1 <= choice <= len(tables):
|
|
35
|
+
selected_table = tables[choice - 1]
|
|
36
|
+
break
|
|
37
|
+
else:
|
|
38
|
+
click.echo(f"❌ Opción no válida. Selecciona un número entre 1 y {len(tables)}.")
|
|
39
|
+
|
|
40
|
+
click.echo(f"✅ Tabla seleccionada: {selected_table._name}")
|
|
41
|
+
|
|
42
|
+
columns = list(selected_table.columns.values())
|
|
43
|
+
|
|
44
|
+
# Seleccionar campo de username
|
|
45
|
+
click.echo(f"\n🏷️ Selecciona el campo para 'username':")
|
|
46
|
+
for i, column in enumerate(columns, 1):
|
|
47
|
+
click.echo(f" {i}. {column.name} ({column.type})")
|
|
48
|
+
|
|
49
|
+
while True:
|
|
50
|
+
choice: int = click.prompt(
|
|
51
|
+
f"🔢 Selecciona el campo de 'username' (1-{len(columns)})",
|
|
52
|
+
type=int,
|
|
53
|
+
show_default=False
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if 1 <= choice <= len(columns):
|
|
57
|
+
username_field = columns[choice - 1]
|
|
58
|
+
if not username_field.args.primary_key:
|
|
59
|
+
click.echo("❌ El campo de 'username' debe ser una clave primaria.", err=True)
|
|
60
|
+
continue
|
|
61
|
+
break
|
|
62
|
+
else:
|
|
63
|
+
click.echo(f"❌ Opción no válida. Selecciona un número entre 1 y {len(columns)}.")
|
|
64
|
+
|
|
65
|
+
click.echo(f"✅ Campo de username: {username_field.name}")
|
|
66
|
+
|
|
67
|
+
# Seleccionar campo de password
|
|
68
|
+
click.echo(f"\n🏷️ Selecciona el campo para 'password':")
|
|
69
|
+
for i, column in enumerate(columns, 1):
|
|
70
|
+
click.echo(f" {i}. {column.name} ({column.type})")
|
|
71
|
+
|
|
72
|
+
while True:
|
|
73
|
+
choice: int = click.prompt(
|
|
74
|
+
f"🔢 Selecciona el campo de 'password' (1-{len(columns)})",
|
|
75
|
+
type=int,
|
|
76
|
+
show_default=False
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if 1 <= choice <= len(columns):
|
|
80
|
+
password_field = columns[choice - 1]
|
|
81
|
+
break
|
|
82
|
+
else:
|
|
83
|
+
click.echo(f"❌ Opción no válida. Selecciona un número entre 1 y {len(columns)}.")
|
|
84
|
+
|
|
85
|
+
click.echo(f"✅ Campo de password: {password_field.name}")
|
|
86
|
+
|
|
87
|
+
# Seleccionar campo de session_id (opcional)
|
|
88
|
+
click.echo(f"\n🔐 ¿Deseas configurar manejo de sesiones concurrentes?")
|
|
89
|
+
click.echo(" Esto permite invalidar sesiones cuando un usuario se loguea desde otro lugar.")
|
|
90
|
+
click.echo(" Si seleccionas 'Sí', debes elegir un campo para almacenar el session_id.")
|
|
91
|
+
|
|
92
|
+
session_management = click.confirm("\n🔄 ¿Habilitar manejo de sesiones?", default=True)
|
|
93
|
+
session_id_field = None
|
|
94
|
+
|
|
95
|
+
if session_management:
|
|
96
|
+
click.echo(f"\n🏷️ Selecciona el campo para 'session_id':")
|
|
97
|
+
click.echo(" (Este campo debe poder almacenar texto, como VARCHAR, TEXT, etc.)")
|
|
98
|
+
for i, column in enumerate(columns, 1):
|
|
99
|
+
click.echo(f" {i}. {column.name} ({column.type})")
|
|
100
|
+
|
|
101
|
+
while True:
|
|
102
|
+
choice: int = click.prompt(
|
|
103
|
+
f"🔢 Selecciona el campo de 'session_id' (1-{len(columns)})",
|
|
104
|
+
type=int,
|
|
105
|
+
show_default=False
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if 1 <= choice <= len(columns):
|
|
109
|
+
session_id_field = columns[choice - 1]
|
|
110
|
+
break
|
|
111
|
+
else:
|
|
112
|
+
click.echo(f"❌ Opción no válida. Selecciona un número entre 1 y {len(columns)}.")
|
|
113
|
+
|
|
114
|
+
click.echo(f"✅ Campo de session_id: {session_id_field.name}")
|
|
115
|
+
|
|
116
|
+
# Seleccionar campo de password_expiration (opcional)
|
|
117
|
+
click.echo(f"\n🔐 ¿Deseas configurar renovación de contraseñas?")
|
|
118
|
+
click.echo(" Esto obliga al usuario a renovar su contraseña cada cierto tiempo.")
|
|
119
|
+
|
|
120
|
+
pwd_renewal = click.confirm("\n🔄 ¿Habilitar renovación de contraseñas?", default=True)
|
|
121
|
+
pwd_expiration_field = None
|
|
122
|
+
|
|
123
|
+
if pwd_renewal:
|
|
124
|
+
click.echo(f"\n🏷️ Selecciona el campo para 'password_expiration':")
|
|
125
|
+
click.echo(" (Este campo debe ser DATE o DATETIME.)")
|
|
126
|
+
for i, column in enumerate(columns, 1):
|
|
127
|
+
click.echo(f" {i}. {column.name} ({column.type})")
|
|
128
|
+
|
|
129
|
+
while True:
|
|
130
|
+
choice: int = click.prompt(
|
|
131
|
+
f"🔢 Selecciona el campo de 'session_id' (1-{len(columns)})",
|
|
132
|
+
type=int,
|
|
133
|
+
show_default=False
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if 1 <= choice <= len(columns):
|
|
137
|
+
pwd_expiration_field = columns[choice - 1]
|
|
138
|
+
break
|
|
139
|
+
else:
|
|
140
|
+
click.echo(f"❌ Opción no válida. Selecciona un número entre 1 y {len(columns)}.")
|
|
141
|
+
|
|
142
|
+
click.echo(f"✅ Campo de password_expiration: {pwd_expiration_field.name}")
|
|
143
|
+
click.echo("💡 El sistema generará un UUID único para cada sesión y lo almacenará en este campo.")
|
|
144
|
+
else:
|
|
145
|
+
click.echo("✅ Manejo de sesiones deshabilitado (se permitirán múltiples sesiones concurrentes)")
|
|
146
|
+
|
|
147
|
+
return DatabaseAuthConfig(
|
|
148
|
+
table_name=selected_table._name,
|
|
149
|
+
username_field=username_field.name,
|
|
150
|
+
password_field=password_field.name,
|
|
151
|
+
session_id_field=session_id_field.name,
|
|
152
|
+
password_expiration_field=pwd_expiration_field.name if pwd_renewal else None,
|
|
153
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from tai_api import KeycloakAuthConfig
|
|
3
|
+
|
|
4
|
+
def runkeycloakconfig() -> KeycloakAuthConfig:
|
|
5
|
+
click.echo(click.style("🚀 Running Keycloak configuration...", fg='cyan', bold=True))
|
|
6
|
+
click.echo("")
|
|
7
|
+
click.echo(click.style("📋 Para que la API funcione correctamente con Keycloak, necesitas provisionar las siguientes variables de entorno:", fg='yellow', bold=True))
|
|
8
|
+
click.echo("")
|
|
9
|
+
click.echo(click.style("1️⃣ MAIN_KEYCLOAK_URL", fg='blue', bold=True))
|
|
10
|
+
click.echo(click.style(" Formato: ", fg='white') + click.style("MAIN_KEYCLOAK_URL=<protocol>://<user>:<pwd>@<host>:<port>", fg='magenta'))
|
|
11
|
+
click.echo(click.style(" Ejemplo: ", fg='white') + click.style("MAIN_KEYCLOAK_URL=http://admin:admin@localhost:8090", fg='green'))
|
|
12
|
+
click.echo("")
|
|
13
|
+
click.echo(click.style("2️⃣ KEYCLOAK_API_CLIENT_SECRET", fg='blue', bold=True))
|
|
14
|
+
click.echo(click.style(" Formato: ", fg='white') + click.style("KEYCLOAK_API_CLIENT_SECRET='secreto_del_cliente_api'", fg='magenta'))
|
|
15
|
+
click.echo("")
|
|
16
|
+
click.echo(click.style("🔍 Para encontrar el KEYCLOAK_API_CLIENT_SECRET:", fg='yellow', bold=True))
|
|
17
|
+
click.echo(click.style(" 1. Accede a ", fg='white') + click.style("<host>:<port>", fg='cyan') + click.style(" (ej: ", fg='white') + click.style("localhost:8090", fg='green') + click.style(")", fg='white'))
|
|
18
|
+
click.echo(click.style(" 2. Ve a ", fg='white') + click.style("Main Realm > Clients > \"api\" > Credentials", fg='cyan'))
|
|
19
|
+
click.echo(click.style(" 3. Copia el Client Secret que aparece ahí", fg='white'))
|
|
20
|
+
click.echo("")
|
|
21
|
+
click.echo(click.style("✅ Una vez configuradas estas variables, la API podrá autenticarse con Keycloak", fg='green', bold=True))
|
|
22
|
+
click.echo("")
|
|
23
|
+
|
|
24
|
+
return KeycloakAuthConfig(realm_name="main-realm", client_name="api", audience="api")
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import click
|
|
3
|
+
|
|
4
|
+
from tai_sql import pm as sqlpm
|
|
5
|
+
from tai_sql.generators import BaseGenerator
|
|
6
|
+
from tai_api import pm, AuthConfig, AuthType
|
|
7
|
+
from tai_api.generators import AuthDatabaseGenerator, AuthKeycloakGenerator, MainFileGenerator, RoutersGenerator
|
|
8
|
+
|
|
9
|
+
from .database import rundbconfig
|
|
10
|
+
from .keycloak import runkeycloakconfig
|
|
11
|
+
|
|
12
|
+
@click.command()
|
|
13
|
+
def set_auth():
|
|
14
|
+
"""Genera recursos para la seguridad de la API"""
|
|
15
|
+
|
|
16
|
+
click.echo("🔐 Configuración de Autenticación - tai-api")
|
|
17
|
+
click.echo("=" * 50)
|
|
18
|
+
|
|
19
|
+
# Verificar configuración de tai-api
|
|
20
|
+
config = pm.get_project_config()
|
|
21
|
+
if not config:
|
|
22
|
+
click.echo("❌ No se encontró la configuración del proyecto tai-api.", err=True)
|
|
23
|
+
click.echo(" Asegúrate de haber inicializado el proyecto con tai-api init.", err=True)
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
|
|
26
|
+
# Seleccionar tipo de autenticación
|
|
27
|
+
click.echo("\n📋 Selecciona el tipo de autenticación:")
|
|
28
|
+
click.echo(" 1. Database - Autenticación basada en base de datos")
|
|
29
|
+
click.echo(" 2. Keycloak - Autenticación con Keycloak")
|
|
30
|
+
|
|
31
|
+
while True:
|
|
32
|
+
choice = click.prompt(
|
|
33
|
+
"\n🔢 Selecciona una opción (1 o 2)",
|
|
34
|
+
type=int,
|
|
35
|
+
show_default=False
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if choice == 1:
|
|
39
|
+
click.echo("✅ Has seleccionado: Database")
|
|
40
|
+
click.echo()
|
|
41
|
+
auth_type = "database"
|
|
42
|
+
break
|
|
43
|
+
elif choice == 2:
|
|
44
|
+
click.echo("✅ Has seleccionado: Keycloak")
|
|
45
|
+
click.echo()
|
|
46
|
+
auth_type = "keycloak"
|
|
47
|
+
break
|
|
48
|
+
else:
|
|
49
|
+
click.echo("❌ Opción no válida. Por favor selecciona 1 o 2.")
|
|
50
|
+
|
|
51
|
+
if auth_type == "database":
|
|
52
|
+
|
|
53
|
+
# Verificar configuración de tai-sql
|
|
54
|
+
sqlconfig = sqlpm.get_project_config()
|
|
55
|
+
if not sqlconfig:
|
|
56
|
+
click.echo("❌ No se encontró la configuración de tai-sql.", err=True)
|
|
57
|
+
click.echo(" Asegúrate de haber inicializado el proyecto con tai-sql init.", err=True)
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
# Establecer esquema por defecto si existe
|
|
61
|
+
if sqlpm.config.default_schema:
|
|
62
|
+
sqlpm.set_current_schema(sqlconfig.default_schema)
|
|
63
|
+
|
|
64
|
+
# Verificar que existe información de la base de datos
|
|
65
|
+
if not sqlpm.db or not sqlpm.db.tables:
|
|
66
|
+
click.echo("❌ No se encontró información de tablas en la base de datos.", err=True)
|
|
67
|
+
sys.exit(1)
|
|
68
|
+
|
|
69
|
+
# Obtener configuración de la base de datos
|
|
70
|
+
db_auth_config = rundbconfig()
|
|
71
|
+
|
|
72
|
+
# Crear configuración de autenticación
|
|
73
|
+
auth_config = AuthConfig(
|
|
74
|
+
type=AuthType.DATABASE,
|
|
75
|
+
config=db_auth_config
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Guardar en la configuración del proyecto
|
|
79
|
+
try:
|
|
80
|
+
pm.update_auth_config(auth_config)
|
|
81
|
+
except ValueError as e:
|
|
82
|
+
click.echo(f"❌ Error al guardar la configuración: {e}", err=True)
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
|
|
85
|
+
# Mostrar mensaje de configuración
|
|
86
|
+
click.echo("\n⚙️ Configuración de autenticación...")
|
|
87
|
+
click.echo("-" * 40)
|
|
88
|
+
click.echo(f"📝 Configuración seleccionada: database")
|
|
89
|
+
click.echo(f" • Tabla: {db_auth_config.table_name}")
|
|
90
|
+
click.echo(f" • Campo username: {db_auth_config.username_field}")
|
|
91
|
+
click.echo(f" • Campo password: {db_auth_config.password_field}")
|
|
92
|
+
|
|
93
|
+
if db_auth_config.has_session_management:
|
|
94
|
+
click.echo(f" • Campo session_id: {db_auth_config.session_id_field}")
|
|
95
|
+
click.echo(" • ✅ Manejo de sesiones concurrentes habilitado")
|
|
96
|
+
else:
|
|
97
|
+
click.echo(" • ❌ Manejo de sesiones concurrentes deshabilitado")
|
|
98
|
+
click.echo("")
|
|
99
|
+
|
|
100
|
+
auth_generator = AuthDatabaseGenerator(output_dir=pm.config.auth_namespace.as_posix())
|
|
101
|
+
main_file_generator = MainFileGenerator(
|
|
102
|
+
output_dir=pm.config.main_namespace.as_posix()
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
generators: list[BaseGenerator] = [auth_generator, main_file_generator]
|
|
106
|
+
|
|
107
|
+
for generator in generators:
|
|
108
|
+
|
|
109
|
+
generator_name = generator.__class__.__name__
|
|
110
|
+
click.echo(f"Ejecutando: {click.style(generator_name, bold=True)}")
|
|
111
|
+
|
|
112
|
+
# El generador se encargará de descubrir los modelos internamente
|
|
113
|
+
result = generator.generate()
|
|
114
|
+
|
|
115
|
+
click.echo(f"✅ Generador {generator_name} completado con éxito.")
|
|
116
|
+
if result:
|
|
117
|
+
click.echo(f" Recursos en: {result}")
|
|
118
|
+
click.echo("")
|
|
119
|
+
|
|
120
|
+
elif auth_type == "keycloak":
|
|
121
|
+
# Obtener configuración de Keycloak
|
|
122
|
+
kc_auth_config = runkeycloakconfig()
|
|
123
|
+
|
|
124
|
+
# Crear configuración de autenticación
|
|
125
|
+
auth_config = AuthConfig(
|
|
126
|
+
type=AuthType.KEYCLOAK,
|
|
127
|
+
config=kc_auth_config
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Guardar en la configuración del proyecto
|
|
131
|
+
try:
|
|
132
|
+
pm.update_auth_config(auth_config)
|
|
133
|
+
except ValueError as e:
|
|
134
|
+
click.echo(f"❌ Error al guardar la configuración: {e}", err=True)
|
|
135
|
+
sys.exit(1)
|
|
136
|
+
|
|
137
|
+
if pm.config.has_routers:
|
|
138
|
+
|
|
139
|
+
# Verificar configuración de tai-sql
|
|
140
|
+
sqlconfig = sqlpm.get_project_config()
|
|
141
|
+
if not sqlconfig:
|
|
142
|
+
click.echo("❌ No se encontró la configuración de tai-sql.", err=True)
|
|
143
|
+
click.echo(" Asegúrate de haber inicializado el proyecto con tai-sql init.", err=True)
|
|
144
|
+
sys.exit(1)
|
|
145
|
+
|
|
146
|
+
for schema_name in sqlpm.discover_schemas():
|
|
147
|
+
sqlpm.set_current_schema(schema_name)
|
|
148
|
+
click.echo(f"Ejecutando: {click.style(RoutersGenerator.__name__, bold=True)}")
|
|
149
|
+
click.echo(f" • Esquema: {schema_name}")
|
|
150
|
+
result = RoutersGenerator(
|
|
151
|
+
output_dir=(pm.config.routers_namespace / schema_name).as_posix()
|
|
152
|
+
).generate()
|
|
153
|
+
click.echo(f"✅ Generador {RoutersGenerator.__name__} completado con éxito.")
|
|
154
|
+
if result:
|
|
155
|
+
click.echo(f" Recursos en: {result}")
|
|
156
|
+
click.echo("")
|
|
157
|
+
|
|
158
|
+
auth_generator = AuthKeycloakGenerator(output_dir=pm.config.auth_namespace.as_posix())
|
|
159
|
+
main_file_generator = MainFileGenerator(
|
|
160
|
+
output_dir=pm.config.main_namespace.as_posix()
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
base_generators: list[BaseGenerator] = [auth_generator, main_file_generator]
|
|
164
|
+
|
|
165
|
+
for generator in base_generators:
|
|
166
|
+
|
|
167
|
+
generator_name = generator.__class__.__name__
|
|
168
|
+
click.echo(f"Ejecutando: {click.style(generator_name, bold=True)}")
|
|
169
|
+
|
|
170
|
+
# El generador se encargará de descubrir los modelos internamente
|
|
171
|
+
result = generator.generate()
|
|
172
|
+
|
|
173
|
+
click.echo(f"✅ Generador {generator_name} completado con éxito.")
|
|
174
|
+
if result:
|
|
175
|
+
click.echo(f" Recursos en: {result}")
|
|
176
|
+
click.echo("")
|
|
177
|
+
else:
|
|
178
|
+
click.echo("❌ Opción no válida.", err=True)
|
|
179
|
+
sys.exit(1)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import dev
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import click
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
from tai_api import pm
|
|
6
|
+
|
|
7
|
+
@click.command()
|
|
8
|
+
@click.option('--auth', '-w', is_flag=True, help='Activar autenticación')
|
|
9
|
+
def dev(auth: bool = False):
|
|
10
|
+
"""Inicia el servidor en modo desarrollo."""
|
|
11
|
+
|
|
12
|
+
config = pm.get_project_config()
|
|
13
|
+
|
|
14
|
+
if not config:
|
|
15
|
+
click.echo("❌ No se encontró la configuración del proyecto. Asegúrate de haber inicializado el proyecto con tai-api init.", err=True)
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
|
|
18
|
+
if auth:
|
|
19
|
+
if pm.config.auth is None:
|
|
20
|
+
click.echo("⚠️ Advertencia: Se ejecutará sin autenticación porque no ha sido configurada.", err=True)
|
|
21
|
+
file_name = '__main__.py'
|
|
22
|
+
else:
|
|
23
|
+
file_name = '__dev__.py'
|
|
24
|
+
|
|
25
|
+
main_file = pm.config.main_namespace / file_name
|
|
26
|
+
|
|
27
|
+
sys.exit(subprocess.call(["fastapi", "dev", main_file.as_posix()]))
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import sys
|
|
3
|
+
from tai_sql.generators import (
|
|
4
|
+
BaseGenerator,
|
|
5
|
+
ModelsGenerator,
|
|
6
|
+
CRUDGenerator,
|
|
7
|
+
ERDiagramGenerator
|
|
8
|
+
)
|
|
9
|
+
from tai_api.generators import RoutersGenerator, MainFileGenerator
|
|
10
|
+
|
|
11
|
+
from tai_api import pm
|
|
12
|
+
from tai_sql import pm as sqlpm
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run_generate():
|
|
16
|
+
"""Run the configured generators."""
|
|
17
|
+
# Ejecutar cada generador
|
|
18
|
+
click.echo("🚀 Ejecutando generadores...")
|
|
19
|
+
click.echo()
|
|
20
|
+
|
|
21
|
+
models_generator = ModelsGenerator(pm.config.database_namespace.as_posix())
|
|
22
|
+
crud_generator = CRUDGenerator(
|
|
23
|
+
output_dir=pm.config.database_namespace.as_posix(),
|
|
24
|
+
models_import_path=pm.config.models_import_path,
|
|
25
|
+
mode='async'
|
|
26
|
+
)
|
|
27
|
+
er_generator = ERDiagramGenerator(pm.config.diagrams_namespace.as_posix())
|
|
28
|
+
|
|
29
|
+
endpoints_generator = RoutersGenerator(
|
|
30
|
+
output_dir=(pm.config.routers_namespace / sqlpm.db.schema_name).as_posix()
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
main_file_generator = MainFileGenerator(
|
|
34
|
+
output_dir=pm.config.main_namespace.as_posix()
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
generators: list[BaseGenerator] = [
|
|
38
|
+
models_generator,
|
|
39
|
+
crud_generator,
|
|
40
|
+
er_generator,
|
|
41
|
+
endpoints_generator,
|
|
42
|
+
main_file_generator
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
for generator in generators:
|
|
46
|
+
try:
|
|
47
|
+
generator_name = generator.__class__.__name__
|
|
48
|
+
click.echo(f"Ejecutando: {click.style(generator_name, bold=True)}")
|
|
49
|
+
|
|
50
|
+
# El generador se encargará de descubrir los modelos internamente
|
|
51
|
+
result = generator.generate()
|
|
52
|
+
|
|
53
|
+
click.echo(f"✅ Generador {generator_name} completado con éxito.")
|
|
54
|
+
if result:
|
|
55
|
+
click.echo(f" Recursos en: {result}")
|
|
56
|
+
except Exception as e:
|
|
57
|
+
click.echo(f"❌ Error al ejecutar el generador {generator_name}: {str(e)}", err=True)
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
finally:
|
|
61
|
+
click.echo()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import click
|
|
3
|
+
|
|
4
|
+
from tai_sql import pm as sqlpm
|
|
5
|
+
from tai_api import pm
|
|
6
|
+
from .funcs import run_generate
|
|
7
|
+
|
|
8
|
+
@click.command()
|
|
9
|
+
@click.option('--schema', '-s', help='Nombre del esquema')
|
|
10
|
+
@click.option('--all', is_flag=True, help='Generar para todos los esquemas')
|
|
11
|
+
def generate(schema: str=None, all: bool=False):
|
|
12
|
+
"""Genera recursos para la API."""
|
|
13
|
+
|
|
14
|
+
if not pm.get_project_config():
|
|
15
|
+
click.echo("❌ No se encontró la configuración del proyecto. Asegúrate de haber inicializado el proyecto con tai-api init.", err=True)
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
|
|
18
|
+
if schema and all:
|
|
19
|
+
click.echo("❌ Las opciones --schema y --all no pueden usarse juntas.", err=True)
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
if schema:
|
|
23
|
+
sqlpm.set_current_schema(schema)
|
|
24
|
+
run_generate()
|
|
25
|
+
|
|
26
|
+
elif all:
|
|
27
|
+
for schema_name in sqlpm.discover_schemas():
|
|
28
|
+
click.echo(f"\n🔄 Generando para esquema: {schema_name}\n")
|
|
29
|
+
sqlpm.set_current_schema(schema_name)
|
|
30
|
+
run_generate()
|
|
31
|
+
|
|
32
|
+
else:
|
|
33
|
+
sqlconfig = sqlpm.get_project_config()
|
|
34
|
+
if sqlconfig:
|
|
35
|
+
sqlpm.set_current_schema(sqlconfig.default_schema)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if not schema and not sqlpm.db:
|
|
39
|
+
click.echo(f"❌ No existe ningún esquema por defecto", err=True)
|
|
40
|
+
click.echo(f" Puedes definir uno con: tai-sql set-default-schema <nombre>", err=True)
|
|
41
|
+
click.echo(f" O usar la opción: --schema <nombre_esquema>", err=True)
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
|
|
44
|
+
run_generate()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import init
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import click
|
|
3
|
+
from .model import InitCommand
|
|
4
|
+
|
|
5
|
+
@click.command()
|
|
6
|
+
@click.argument('project', type=str)
|
|
7
|
+
@click.option('--namespace', '-n', default='api', help='Nombre del proyecto a crear')
|
|
8
|
+
def init(project: str, namespace: str):
|
|
9
|
+
"""Inicializa un nuevo proyecto tai-api"""
|
|
10
|
+
command = InitCommand(project=project, namespace=namespace)
|
|
11
|
+
try:
|
|
12
|
+
command.check_poetry()
|
|
13
|
+
command.check_directory_is_avaliable()
|
|
14
|
+
command.check_virtualenv()
|
|
15
|
+
command.create_project()
|
|
16
|
+
command.create_project_config()
|
|
17
|
+
command.add_dependencies()
|
|
18
|
+
command.add_folders()
|
|
19
|
+
command.msg()
|
|
20
|
+
except Exception as e:
|
|
21
|
+
click.echo(f"❌ Error al inicializar el proyecto: {str(e)}", err=True)
|
|
22
|
+
sys.exit(1)
|