tai-sql 0.7.18__tar.gz → 0.7.20__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tai_sql-0.7.18 → tai_sql-0.7.20}/PKG-INFO +5 -4
- {tai_sql-0.7.18 → tai_sql-0.7.20}/README.md +4 -3
- {tai_sql-0.7.18 → tai_sql-0.7.20}/pyproject.toml +1 -1
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_generate/funcs.py +16 -1
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_schema/schemas/chatbot.py +5 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/errors/render.py +21 -8
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/__init__.py +17 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/domain/dao.py.jinja2 +5 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/domain/dtos.py.jinja2 +5 -26
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/macros/hierarchy.jinja2 +7 -33
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/macros/read.jinja2 +10 -1
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/macros/upsert.jinja2 +5 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/macros/vector.jinja2 +2 -2
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/macros/write.jinja2 +24 -21
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/shared/utils/encoders.py.jinja2 +66 -6
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/triggers/__init__.py +3 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/triggers/analyzer.py +82 -22
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/triggers/renderer.py +67 -10
- tai_sql-0.7.20/tai_sql/generators/python_client/triggers/scopes.py +164 -0
- tai_sql-0.7.20/tai_sql/generators/python_client/triggers/transformer.py +724 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/trigger_api.py +4 -2
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/triggers.py +32 -9
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/mapping/sqlalchemy_mapper.py +15 -5
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/types.py +9 -4
- tai_sql-0.7.18/tai_sql/generators/python_client/triggers/transformer.py +0 -691
- {tai_sql-0.7.18 → tai_sql-0.7.20}/LICENSE +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_backup/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_backup/main.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_deploy/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_deploy/config.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_deploy/local.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_deploy/main.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_deploy/workflow.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_feed/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_feed/main.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_generate/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_generate/main.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_hash_migrate/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_hash_migrate/main.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_info/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_info/main.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_info/status.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_init/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_init/main.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_init/model.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_install/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_install/main.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_ping/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_ping/main.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_pull/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_pull/funcs.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_pull/main.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_restore/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_restore/main.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_rules/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_rules/main.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_schema/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_schema/model.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_schema/new.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_schema/schemas/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_schema/schemas/auth.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_schema/schemas/base.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_schema/schemas/default.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/cmd_schema/set_default.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/prompts.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/utils/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/utils/connectivity.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/utils/create.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/utils/github/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/utils/github/auth.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/utils/github/client.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/commands/utils/github/models.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/errors.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/main.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/preamble.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/services/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/cli/services/backup.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/connection/README.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/connection/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/connection/provider.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/connection/recipe.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/connection/session.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/connection/sources.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/crypto.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/drivers/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/drivers/base.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/drivers/bigquery.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/drivers/capabilities.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/drivers/mysql.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/drivers/postgresql.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/drivers/sqlserver.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/errors/README.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/errors/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/errors/collector.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/errors/types.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/feed/README.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/feed/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/feed/collect.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/feed/executor.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/feed/feeder.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/feed/plan.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/feed/presenter.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/feed/records.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/feed/transform.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/base.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/diagram/README.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/diagram/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/diagram/assets/README.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/diagram/assets/cytoscape.min.js +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/diagram/builder.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/diagram/generator.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/diagram/graphviz_render.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/diagram/html_render.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/diagram/layout.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/diagram/model.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/diagram/templates/er.html.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/diagram/themes.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/calculated.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/context.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/base.py.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/domain/__init__.py.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/domain/model.py.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/facade.py.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/macros/aggregation.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/macros/dataframe.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/macros/filters.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/macros/triggers.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/session.py.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/shared/__init__.py.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/shared/aggregation.py.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/shared/context.py.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/shared/utils/__init__.py.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/shared/utils/funcs.py.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/shared/utils/hierarchy.py.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/shared/utils/rls.py.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/shared/vector.py.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/triggers/steps.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/rules/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/rules/templates/schema.md.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/introspect/README.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/introspect/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/introspect/blueprint.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/introspect/build.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/introspect/defaults.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/introspect/naming.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/introspect/reader.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/introspect/templates/schema.py.jinja2 +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/introspect/types.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/introspect/writer.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/README.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/analysis/README.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/analysis/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/analysis/calculated.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/analysis/collector.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/analysis/column_analyzer.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/analysis/constraints.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/analysis/hints.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/analysis/relation_analyzer.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/analysis/schema_analyzer.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/README.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/annotations.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/base.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/calculated.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/columns.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/constraints.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/datasource.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/descriptors.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/encoders.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/enumerations.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/relations.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/table.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/declarative/view.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/mapping/README.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/mapping/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/model/README.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/model/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/model/column.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/model/constraints.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/model/entity.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/model/enumeration.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/model/relation.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/model/schema.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/model/trigger.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/naming.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/ordering.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/orm/registry.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/project/README.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/project/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/project/config.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/project/context.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/project/manager.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/project/paths.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/rules/README.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/rules/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/rules/catalog.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/rules/installer.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/rules/meta.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/rules/service.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/rules/staleness.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/rules/static/comandos.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/rules/static/errores.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/rules/static/modelo.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/schema.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/README.md +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/diff/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/diff/constraints.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/diff/indexes.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/diff/tables.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/diff/views.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/executor.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/ownership.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/plan/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/plan/planner.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/plan/statements.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/presenter.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/report.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/safety.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/state/__init__.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/state/local.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/state/model.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/state/normalize.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/state/remote.py +0 -0
- {tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/sync/synchronizer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tai-sql
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.20
|
|
4
4
|
Summary: SQL database management and code generation tool
|
|
5
5
|
License-Expression: LicenseRef-Proprietary
|
|
6
6
|
License-File: LICENSE
|
|
@@ -211,9 +211,10 @@ poetry run pytest -m mysql # el ciclo completo contra MySQL
|
|
|
211
211
|
Los tests marcados `db` necesitan un PostgreSQL alcanzable y se **saltan** si no lo hay; los
|
|
212
212
|
marcados `mysql`, un MySQL en `TAI_SQL_MYSQL_URL`. Sus schemas salen de `tests/fixtures/project/`.
|
|
213
213
|
|
|
214
|
-
El CI tiene tres workflows: `tests.yaml` ejecuta la suite en cada
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
El CI tiene tres workflows: `tests.yaml` ejecuta la suite en cada PR a `main` y `dev` —un trabajo
|
|
215
|
+
con PostgreSQL y otro con MySQL—, `docs.yaml` publica el manual y `publish.yaml` publica a PyPI en
|
|
216
|
+
push a `main`, después de volver a pasar la suite. En los push no se ejecuta aparte, ni en una PR
|
|
217
|
+
que solo cambia documentación (`docs/`, `specs/`, `.claude/`). La rama de trabajo habitual es `dev`, y la versión se bumpea a mano en
|
|
217
218
|
`pyproject.toml`.
|
|
218
219
|
|
|
219
220
|
### El manual, en local
|
|
@@ -148,9 +148,10 @@ poetry run pytest -m mysql # el ciclo completo contra MySQL
|
|
|
148
148
|
Los tests marcados `db` necesitan un PostgreSQL alcanzable y se **saltan** si no lo hay; los
|
|
149
149
|
marcados `mysql`, un MySQL en `TAI_SQL_MYSQL_URL`. Sus schemas salen de `tests/fixtures/project/`.
|
|
150
150
|
|
|
151
|
-
El CI tiene tres workflows: `tests.yaml` ejecuta la suite en cada
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
El CI tiene tres workflows: `tests.yaml` ejecuta la suite en cada PR a `main` y `dev` —un trabajo
|
|
152
|
+
con PostgreSQL y otro con MySQL—, `docs.yaml` publica el manual y `publish.yaml` publica a PyPI en
|
|
153
|
+
push a `main`, después de volver a pasar la suite. En los push no se ejecuta aparte, ni en una PR
|
|
154
|
+
que solo cambia documentación (`docs/`, `specs/`, `.claude/`). La rama de trabajo habitual es `dev`, y la versión se bumpea a mano en
|
|
154
155
|
`pyproject.toml`.
|
|
155
156
|
|
|
156
157
|
### El manual, en local
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import click
|
|
2
2
|
|
|
3
3
|
from tai_sql import pm
|
|
4
|
-
from tai_sql.errors import ErrorCode, GenerationError, render_warning
|
|
4
|
+
from tai_sql.errors import ErrorCode, GenerationError, TaiSqlError, render_warning
|
|
5
5
|
from tai_sql.rules import warn_if_outdated
|
|
6
6
|
|
|
7
7
|
from ...preamble import load_schema
|
|
@@ -49,6 +49,21 @@ def run_generate(schema_name: str = None):
|
|
|
49
49
|
)
|
|
50
50
|
generator.generate()
|
|
51
51
|
|
|
52
|
+
except TaiSqlError as e:
|
|
53
|
+
# El generador ya sabe qué ha fallado y cómo se arregla —un trigger que no se
|
|
54
|
+
# puede transpilar dice cuál y por qué—: su solución vale más que la genérica.
|
|
55
|
+
# Se conserva el tipo y se añade el schema y el generador delante del sitio
|
|
56
|
+
# que traía (el trigger), en vez de pisarlo.
|
|
57
|
+
raise type(e)(
|
|
58
|
+
message=f"{generator_name} falló al generar: {e.message}",
|
|
59
|
+
solution=e.solution,
|
|
60
|
+
location=' · '.join(filter(None, (
|
|
61
|
+
f"Schema '{schema_name}'", generator_name, e.diagnostic.location,
|
|
62
|
+
))),
|
|
63
|
+
code=e.diagnostic.code,
|
|
64
|
+
**e.diagnostic.context,
|
|
65
|
+
) from e
|
|
66
|
+
|
|
52
67
|
except Exception as e:
|
|
53
68
|
raise GenerationError(
|
|
54
69
|
message=f"{generator_name} falló al generar: {e}",
|
|
@@ -62,6 +62,11 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
62
62
|
|
|
63
63
|
visible: col[bool] = column(default=True, index=True, description='Si no, la conversación no se lista')
|
|
64
64
|
last_message_timestamp: col[datetime | None] = column(description='Timestamp del último mensaje del chat')
|
|
65
|
+
# Por alias y no como FK a modelo.id, a propósito: el alias es con lo que el catálogo y
|
|
66
|
+
# el front piden un modelo, y uno desactivado no tiene por qué romper la conversación
|
|
67
|
+
# (cae al de por defecto). Opcional y sin default: un push la añade a una base de
|
|
68
|
+
# datos existente sin tocar sus filas
|
|
69
|
+
modelo: col[str | None] = column(description='Alias del modelo con el que responde esta conversación; sin él, el de por defecto')
|
|
65
70
|
|
|
66
71
|
messages: onetomany[Mensaje] # Relación one-to-many con la tabla Mensaje
|
|
67
72
|
token_usage: onetomany[TokenUsage] # El consumo de la conversación, tenga mensaje o no
|
|
@@ -49,6 +49,19 @@ def _console(stream: Optional[TextIO] = None):
|
|
|
49
49
|
return Console(file=stream or sys.stderr, soft_wrap=True)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
def _escape(text) -> str:
|
|
53
|
+
"""
|
|
54
|
+
El texto de un diagnóstico, a salvo del marcado de `rich`.
|
|
55
|
+
|
|
56
|
+
Lo que se interpola en `console.print()` se interpreta como marcado, y un corchete se
|
|
57
|
+
toma por una etiqueta de estilo: `pip install 'tai-sql[vectors]'` salía como
|
|
58
|
+
`pip install 'tai-sql'`, justo la solución que no funciona.
|
|
59
|
+
"""
|
|
60
|
+
from rich.markup import escape
|
|
61
|
+
|
|
62
|
+
return escape(str(text))
|
|
63
|
+
|
|
64
|
+
|
|
52
65
|
def shorten_path(path) -> str:
|
|
53
66
|
"""
|
|
54
67
|
Deja una ruta relativa al directorio actual, si cuelga de él.
|
|
@@ -111,17 +124,17 @@ def render_diagnostics(
|
|
|
111
124
|
def _render_one_rich(console, diagnostic: Diagnostic, context: Optional[str]) -> None:
|
|
112
125
|
"""Un problema: tres líneas, sin marco."""
|
|
113
126
|
console.print()
|
|
114
|
-
console.print(f"[bold red]❌ {diagnostic.message}[/bold red]")
|
|
127
|
+
console.print(f"[bold red]❌ {_escape(diagnostic.message)}[/bold red]")
|
|
115
128
|
|
|
116
129
|
detail = _detail_line(diagnostic, context)
|
|
117
130
|
if detail:
|
|
118
|
-
console.print(f" [dim]📍 {detail}[/dim]")
|
|
131
|
+
console.print(f" [dim]📍 {_escape(detail)}[/dim]")
|
|
119
132
|
|
|
120
|
-
console.print(f" [yellow]💡 {diagnostic.solution}[/yellow]")
|
|
133
|
+
console.print(f" [yellow]💡 {_escape(diagnostic.solution)}[/yellow]")
|
|
121
134
|
|
|
122
135
|
if diagnostic.context:
|
|
123
136
|
extra = ', '.join(f'{k}={v}' for k, v in diagnostic.context.items())
|
|
124
|
-
console.print(f" [dim]ℹ️ {extra}[/dim]")
|
|
137
|
+
console.print(f" [dim]ℹ️ {_escape(extra)}[/dim]")
|
|
125
138
|
|
|
126
139
|
console.print()
|
|
127
140
|
|
|
@@ -134,7 +147,7 @@ def _render_many_rich(
|
|
|
134
147
|
from rich.text import Text
|
|
135
148
|
|
|
136
149
|
total = len(diagnostics)
|
|
137
|
-
where = f' en {context}' if context else ''
|
|
150
|
+
where = f' en {_escape(context)}' if context else ''
|
|
138
151
|
console.print(f"\n[bold red]❌ Se encontraron {total} errores{where}[/bold red]\n")
|
|
139
152
|
|
|
140
153
|
for i, diagnostic in enumerate(diagnostics, 1):
|
|
@@ -235,11 +248,11 @@ def render_warning(
|
|
|
235
248
|
print(f" 💡 {hint}", file=out)
|
|
236
249
|
return
|
|
237
250
|
|
|
238
|
-
console.print(f"[bold yellow]⚠️ {message}[/bold yellow]")
|
|
251
|
+
console.print(f"[bold yellow]⚠️ {_escape(message)}[/bold yellow]")
|
|
239
252
|
if location:
|
|
240
|
-
console.print(f" [dim]📍 {location}[/dim]")
|
|
253
|
+
console.print(f" [dim]📍 {_escape(location)}[/dim]")
|
|
241
254
|
if hint:
|
|
242
|
-
console.print(f" [yellow]💡 {hint}[/yellow]")
|
|
255
|
+
console.print(f" [yellow]💡 {_escape(hint)}[/yellow]")
|
|
243
256
|
|
|
244
257
|
|
|
245
258
|
__all__ = ['render_diagnostics', 'render_warning']
|
|
@@ -307,6 +307,22 @@ class PythonClientGenerator(BaseGenerator):
|
|
|
307
307
|
def _all_models_data(self, mode: str = 'sync') -> List[Dict[str, Any]]:
|
|
308
308
|
return self.context.entities(mode)
|
|
309
309
|
|
|
310
|
+
@staticmethod
|
|
311
|
+
def _writes_vectors(all_models: List[Dict[str, Any]]) -> bool:
|
|
312
|
+
"""
|
|
313
|
+
Whether some table of the schema computes a vector on write (`vector_column` with
|
|
314
|
+
an encoder and a `source`).
|
|
315
|
+
|
|
316
|
+
It is schema-wide, not per table: a DAO also writes the nested rows of other
|
|
317
|
+
tables, and those can carry a vector.
|
|
318
|
+
"""
|
|
319
|
+
return any(
|
|
320
|
+
col.get('type') == 'vector' and col.get('has_encoder')
|
|
321
|
+
and col.get('encoder_config') and col.get('source')
|
|
322
|
+
for m in all_models
|
|
323
|
+
for col in m.get('columns', [])
|
|
324
|
+
)
|
|
325
|
+
|
|
310
326
|
# ── Generate ──────────────────────────────────────────────────────────
|
|
311
327
|
|
|
312
328
|
def generate(self) -> str:
|
|
@@ -525,6 +541,7 @@ class PythonClientGenerator(BaseGenerator):
|
|
|
525
541
|
schema_name=self.schema.name,
|
|
526
542
|
max_depth=self.max_depth,
|
|
527
543
|
logger_name=self.logger_name,
|
|
544
|
+
writes_vectors=self._writes_vectors(all_models),
|
|
528
545
|
is_async=is_async,
|
|
529
546
|
prefix=prefix,
|
|
530
547
|
dao_module_name=dao_module_name,
|
{tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/domain/dao.py.jinja2
RENAMED
|
@@ -153,6 +153,11 @@ from {{ import_prefix }}_shared.utils.encoders import {{ model.tablename }}_{{ v
|
|
|
153
153
|
import asyncio
|
|
154
154
|
{% endif %}
|
|
155
155
|
{% endif %}
|
|
156
|
+
{% if writes_vectors and not model.is_view %}
|
|
157
|
+
{# Los vectores se calculan al escribir, sobre todo lo pendiente de la sesión: una fila
|
|
158
|
+
anidada de otra tabla también puede llevar uno, así que no depende de esta tabla. #}
|
|
159
|
+
from {{ import_prefix }}_shared.utils.encoders import {{ 'aencode_pending' if is_async else 'encode_pending' }} as _encode_pending
|
|
160
|
+
{% endif %}
|
|
156
161
|
|
|
157
162
|
{# `natural_key` es la respuesta del modelo a «por qué columnas se reconoce una fila»:
|
|
158
163
|
la clave primaria si quien escribe la trae, si no la primera columna única, y si no la
|
{tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/domain/dtos.py.jinja2
RENAMED
|
@@ -67,15 +67,6 @@ from {{ import_prefix }}_shared import (
|
|
|
67
67
|
if TYPE_CHECKING:
|
|
68
68
|
from pandas import DataFrame, Series # type: ignore[import-untyped]
|
|
69
69
|
|
|
70
|
-
{% set _dto_encoder_cols = model.columns | selectattr('type', 'equalto', 'vector') | selectattr('has_encoder') | list %}
|
|
71
|
-
{% if _dto_encoder_cols %}
|
|
72
|
-
# ── Vector encoders ──────────────────────────────────────────────────────
|
|
73
|
-
{% for vcol in _dto_encoder_cols %}
|
|
74
|
-
{% if vcol.encoder_config %}
|
|
75
|
-
from {{ import_prefix }}_shared.utils.encoders import {{ model.tablename }}_{{ vcol.name }}_encoder as _{{ vcol.name }}_encoder
|
|
76
|
-
{% endif %}
|
|
77
|
-
{% endfor %}
|
|
78
|
-
{% endif %}
|
|
79
70
|
|
|
80
71
|
{# ── Read DTO ─────────────────────────────────────────────────────────── #}
|
|
81
72
|
|
|
@@ -332,28 +323,16 @@ class {{ model.name }}Create(PrettyModel):
|
|
|
332
323
|
return self.model_dump(exclude_none=True)
|
|
333
324
|
|
|
334
325
|
{% if vector_cols_create_encoder %}
|
|
335
|
-
def model_post_init(self, __context: Any) -> None:
|
|
336
|
-
{% for vcol in vector_cols_create_encoder %}
|
|
337
|
-
{% if vcol.source %}
|
|
338
|
-
if self.{{ vcol.name }} is None and self.{{ vcol.source }} is not None:
|
|
339
|
-
self.{{ vcol.name }} = _{{ vcol.name }}_encoder.encode(self.{{ vcol.source }})
|
|
340
|
-
{% endif %}
|
|
341
|
-
{% endfor %}
|
|
342
|
-
|
|
343
326
|
@classmethod
|
|
344
327
|
def deferred(cls, **kwargs) -> '{{ model.name }}Create':
|
|
345
328
|
"""
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
Use when building lists for ``create_many()``: encoding will be done
|
|
349
|
-
in a single ``encode_batch`` call inside the DAO instead of once per record.
|
|
350
|
-
|
|
351
|
-
Example::
|
|
329
|
+
Lo mismo que el constructor. Se conserva por compatibilidad.
|
|
352
330
|
|
|
353
|
-
|
|
354
|
-
|
|
331
|
+
Construir un DTO no calcula ningún vector: lo calcula el DAO al escribir, en lote y,
|
|
332
|
+
en el DAO asíncrono, sin bloquear el bucle de eventos. Antes el constructor lo
|
|
333
|
+
calculaba en el acto, y `deferred()` existía para evitarlo.
|
|
355
334
|
"""
|
|
356
|
-
return cls
|
|
335
|
+
return cls(**kwargs)
|
|
357
336
|
{% endif %}
|
|
358
337
|
{% endif %}
|
|
359
338
|
|
{tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/macros/hierarchy.jinja2
RENAMED
|
@@ -86,40 +86,11 @@ tree_depth: Optional[int] = None,
|
|
|
86
86
|
|
|
87
87
|
{{ def_keyword }} execute_query(session: {{ session_type }}) -> List[{{ model.name }}Read]:
|
|
88
88
|
if _hierarchy_mode == TreeMode.FLAT:
|
|
89
|
-
# Standard flat query
|
|
90
|
-
results = {{ await_keyword -}} session.execute(query)
|
|
91
89
|
{% if vector_cols_with_encoder %}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
rows = results.all()
|
|
95
|
-
_log_commit(session, f"[{{ schema_name }}] ✅ Encontrados {len(rows)} registros {{ model.name }} (similitud)")
|
|
96
|
-
return [
|
|
97
|
-
{{ model.name }}Read.from_instance(
|
|
98
|
-
row[0],
|
|
99
|
-
{% if not model.is_view %}
|
|
100
|
-
includes=includes,
|
|
101
|
-
max_depth={{ max_depth }},
|
|
102
|
-
{% endif %}
|
|
103
|
-
include_vectors=include_vectors,
|
|
104
|
-
vector_search=_build_vector_search_result(row, _active_vectors)
|
|
105
|
-
)
|
|
106
|
-
for row in rows
|
|
107
|
-
]
|
|
108
|
-
else:
|
|
109
|
-
instances = results.scalars().all()
|
|
110
|
-
_log_commit(session, f"[{{ schema_name }}] ✅ Encontrados {len(instances)} registros {{ model.name }}")
|
|
111
|
-
return [
|
|
112
|
-
{{ model.name }}Read.from_instance(
|
|
113
|
-
instance,
|
|
114
|
-
{% if not model.is_view %}
|
|
115
|
-
includes=includes,
|
|
116
|
-
max_depth={{ max_depth }},
|
|
117
|
-
{% endif %}
|
|
118
|
-
include_vectors=include_vectors,
|
|
119
|
-
)
|
|
120
|
-
for instance in instances
|
|
121
|
-
]
|
|
90
|
+
# Sin recursión es la consulta normal, con o sin similitud
|
|
91
|
+
return {{ await_keyword -}} _execute_flat(session)
|
|
122
92
|
{% else %}
|
|
93
|
+
results = {{ await_keyword -}} session.execute(query)
|
|
123
94
|
instances = results.scalars().all()
|
|
124
95
|
|
|
125
96
|
_log_commit(session, f"[{{ schema_name }}] ✅ Encontrados {len(instances)} registros {{ model.name }}")
|
|
@@ -129,7 +100,10 @@ tree_depth: Optional[int] = None,
|
|
|
129
100
|
instance,
|
|
130
101
|
{% if not model.is_view %}
|
|
131
102
|
includes=includes,
|
|
132
|
-
max_depth={{ max_depth }}
|
|
103
|
+
max_depth={{ max_depth }},
|
|
104
|
+
{% endif %}
|
|
105
|
+
{% if model.columns | selectattr('type', 'equalto', 'vector') | list %}
|
|
106
|
+
include_vectors=include_vectors,
|
|
133
107
|
{% endif %}
|
|
134
108
|
)
|
|
135
109
|
for instance in instances
|
{tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/macros/read.jinja2
RENAMED
|
@@ -270,6 +270,11 @@
|
|
|
270
270
|
|
|
271
271
|
# Ejecutar query
|
|
272
272
|
{% if model.has_self_reference %}
|
|
273
|
+
{% if vector_cols_with_encoder %}
|
|
274
|
+
{# El modo `flat` de la jerarquía es la consulta vectorial de siempre: se emite con otro
|
|
275
|
+
nombre y el bloque jerárquico la llama, en vez de repetirla a medias. #}
|
|
276
|
+
{{ vector_macros.generate_vector_execute(model, max_depth, '_execute_flat') }}
|
|
277
|
+
{% endif %}
|
|
273
278
|
{{ hierarchy_macros.generate_hierarchy_execute(model, max_depth) }}
|
|
274
279
|
{% elif vector_cols_with_encoder %}
|
|
275
280
|
{{ vector_macros.generate_vector_execute(model, max_depth) }}
|
|
@@ -285,7 +290,11 @@
|
|
|
285
290
|
instance,
|
|
286
291
|
{% if not model.is_view %}
|
|
287
292
|
includes=includes,
|
|
288
|
-
max_depth={{ max_depth }}
|
|
293
|
+
max_depth={{ max_depth }}{% if _find_many_vector_cols %},{% endif %}
|
|
294
|
+
|
|
295
|
+
{% endif %}
|
|
296
|
+
{% if _find_many_vector_cols %}
|
|
297
|
+
include_vectors=include_vectors,
|
|
289
298
|
{% endif %}
|
|
290
299
|
)
|
|
291
300
|
for instance in instances
|
{tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/macros/upsert.jinja2
RENAMED
|
@@ -197,6 +197,11 @@
|
|
|
197
197
|
if not match_fields:
|
|
198
198
|
raise ValueError("match_fields no puede estar vacío. Debe contener al menos un campo para identificar registros.")
|
|
199
199
|
|
|
200
|
+
{% if model.columns | selectattr('type', 'equalto', 'vector') | selectattr('has_encoder') | selectattr('source') | list %}
|
|
201
|
+
# El camino masivo escribe diccionarios por Core, sin pasar por el modelo: los vectores
|
|
202
|
+
# se calculan antes, sobre los DTOs
|
|
203
|
+
{{ await_keyword -}} _encode_pending(records, '{{ model.name }}')
|
|
204
|
+
{% endif %}
|
|
200
205
|
records_data = [record.to_dict() for record in records]
|
|
201
206
|
{% if derived_columns %}
|
|
202
207
|
records_data = cls._as_row_values(records_data)
|
{tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/macros/vector.jinja2
RENAMED
|
@@ -110,7 +110,7 @@ vector_weights: Optional[Dict[str, float]] = None,
|
|
|
110
110
|
{# Replaces the default scalars().all() block #}
|
|
111
111
|
{# ============================================ #}
|
|
112
112
|
|
|
113
|
-
{% macro generate_vector_execute(model, max_depth) -%}
|
|
113
|
+
{% macro generate_vector_execute(model, max_depth, fn_name='execute_query') -%}
|
|
114
114
|
{% set vector_cols_with_encoder = [] -%}
|
|
115
115
|
{% for vcol in model.columns | selectattr('type', 'equalto', 'vector') | list -%}
|
|
116
116
|
{% if vcol.get('has_encoder') -%}
|
|
@@ -154,7 +154,7 @@ vector_weights: Optional[Dict[str, float]] = None,
|
|
|
154
154
|
)
|
|
155
155
|
{% endif %}
|
|
156
156
|
|
|
157
|
-
{{ def_keyword }}
|
|
157
|
+
{{ def_keyword }} {{ fn_name }}(session: {{ session_type }}) -> List[{{ model.name }}Read]:
|
|
158
158
|
results = {{ await_keyword -}} session.execute(query)
|
|
159
159
|
|
|
160
160
|
if _has_vector_search:
|
{tai_sql-0.7.18 → tai_sql-0.7.20}/tai_sql/generators/python_client/templates/macros/write.jinja2
RENAMED
|
@@ -153,6 +153,10 @@
|
|
|
153
153
|
included = get_included_relations(new_instance, {{ model.tablename }})
|
|
154
154
|
{{ await_keyword -}} cls._on_create_before(new_instance, included, session)
|
|
155
155
|
session.add(new_instance)
|
|
156
|
+
{% if writes_vectors %}
|
|
157
|
+
# Después de los triggers `before`, que pueden cambiar el texto del que sale el vector
|
|
158
|
+
{{ await_keyword -}} _encode_pending(session.new)
|
|
159
|
+
{% endif %}
|
|
156
160
|
{{ await_keyword -}} session.flush()
|
|
157
161
|
{{ await_keyword -}} cls._on_create_after(new_instance, included, session)
|
|
158
162
|
return {{ model.name }}Read.from_created_instance(new_instance, included)
|
|
@@ -196,22 +200,6 @@
|
|
|
196
200
|
"""
|
|
197
201
|
_log_tx(session, f"[{{ schema_name }}] 🔢 Creando {len(records)} registros {{ model.name }}")
|
|
198
202
|
|
|
199
|
-
{% set vcols_batch = model.columns | selectattr('type', 'equalto', 'vector') | selectattr('has_encoder') | selectattr('source') | list %}
|
|
200
|
-
{% if vcols_batch %}
|
|
201
|
-
# Batch encode any records with deferred vector fields (created via model_construct)
|
|
202
|
-
{% for vcol in vcols_batch %}
|
|
203
|
-
_batch_{{ vcol.name }}_idx = [i for i, r in enumerate(records) if r.{{ vcol.name }} is None and r.{{ vcol.source }} is not None]
|
|
204
|
-
if _batch_{{ vcol.name }}_idx:
|
|
205
|
-
_batch_{{ vcol.name }}_texts = [records[i].{{ vcol.source }} for i in _batch_{{ vcol.name }}_idx]
|
|
206
|
-
{% if is_async %}
|
|
207
|
-
_batch_{{ vcol.name }}_vecs = await _{{ vcol.name }}_encoder.aencode_batch(_batch_{{ vcol.name }}_texts)
|
|
208
|
-
{% else %}
|
|
209
|
-
_batch_{{ vcol.name }}_vecs = _{{ vcol.name }}_encoder.encode_batch(_batch_{{ vcol.name }}_texts)
|
|
210
|
-
{% endif %}
|
|
211
|
-
for _i, _vec in zip(_batch_{{ vcol.name }}_idx, _batch_{{ vcol.name }}_vecs):
|
|
212
|
-
object.__setattr__(records[_i], '{{ vcol.name }}', _vec)
|
|
213
|
-
{% endfor %}
|
|
214
|
-
{% endif %}
|
|
215
203
|
{% set operative_str_cols = model.columns | selectattr('is_operative') | selectattr('python_type', 'equalto', 'str') | list %}
|
|
216
204
|
{{ def_keyword }} _execute_create_many(session: {{ session_type }}) -> int:
|
|
217
205
|
instances = []
|
|
@@ -221,6 +209,10 @@
|
|
|
221
209
|
{{ await_keyword -}} cls._on_create_before(instance, included, session)
|
|
222
210
|
instances.append(instance)
|
|
223
211
|
session.add_all(instances)
|
|
212
|
+
{% if writes_vectors %}
|
|
213
|
+
# Todos los vectores pendientes en una llamada por columna, anidados incluidos
|
|
214
|
+
{{ await_keyword -}} _encode_pending(session.new)
|
|
215
|
+
{% endif %}
|
|
224
216
|
{{ await_keyword -}} session.flush()
|
|
225
217
|
for instance in instances:
|
|
226
218
|
included = get_included_relations(instance, record)
|
|
@@ -322,11 +314,10 @@
|
|
|
322
314
|
{% for vcol in model.columns | selectattr('type', 'equalto', 'vector') | list %}
|
|
323
315
|
{% if vcol.source and vcol.has_encoder %}
|
|
324
316
|
if '{{ vcol.source }}' in update_data and '{{ vcol.name }}' not in update_data:
|
|
325
|
-
{
|
|
326
|
-
|
|
327
|
-
{
|
|
328
|
-
|
|
329
|
-
{% endif %}
|
|
317
|
+
update_data['{{ vcol.name }}'] = (
|
|
318
|
+
None if update_data['{{ vcol.source }}'] is None
|
|
319
|
+
else {{ await_keyword }}_{{ vcol.name }}_encoder.{{ 'aencode' if is_async else 'encode' }}(update_data['{{ vcol.source }}'])
|
|
320
|
+
)
|
|
330
321
|
{% endif %}
|
|
331
322
|
{% endfor %}
|
|
332
323
|
for key, value in update_data.items():
|
|
@@ -520,6 +511,18 @@
|
|
|
520
511
|
|
|
521
512
|
if not filters and not values: # Solo actualizar si hay filtros y valores
|
|
522
513
|
return 0
|
|
514
|
+
{% for vcol in model.columns | selectattr('type', 'equalto', 'vector') | list %}
|
|
515
|
+
{% if vcol.source and vcol.has_encoder %}
|
|
516
|
+
|
|
517
|
+
# Un `UPDATE ... SET` no pasa por el modelo: si cambia el texto, el vector se calcula
|
|
518
|
+
# aquí. Es el mismo texto para todas las filas, así que es un solo vector.
|
|
519
|
+
if '{{ vcol.source }}' in values and '{{ vcol.name }}' not in values:
|
|
520
|
+
values['{{ vcol.name }}'] = (
|
|
521
|
+
None if values['{{ vcol.source }}'] is None
|
|
522
|
+
else {{ await_keyword }}_{{ vcol.name }}_encoder.{{ 'aencode' if is_async else 'encode' }}(values['{{ vcol.source }}'])
|
|
523
|
+
)
|
|
524
|
+
{% endif %}
|
|
525
|
+
{% endfor %}
|
|
523
526
|
|
|
524
527
|
query = update({{ model.name }})
|
|
525
528
|
query = payload.filter.apply_to_query(query)
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
# No modifiques este archivo directamente
|
|
3
3
|
#
|
|
4
4
|
# _shared/utils/encoders.py
|
|
5
|
-
# Encoder instances shared across all DAOs
|
|
5
|
+
# Encoder instances shared across all DAOs in this schema.
|
|
6
6
|
# Each instance is named {tablename}_{colname}_encoder.
|
|
7
7
|
#
|
|
8
8
|
# All encoders use langchain as the unified embedding interface.
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
from abc import ABC, abstractmethod
|
|
12
|
-
from typing import List
|
|
12
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
|
13
13
|
import asyncio
|
|
14
14
|
import time
|
|
15
15
|
import os
|
|
@@ -78,7 +78,7 @@ class HuggingFaceEncoder(BaseEncoder):
|
|
|
78
78
|
except ImportError:
|
|
79
79
|
raise ImportError(
|
|
80
80
|
"langchain-huggingface is required for HuggingFaceEncoder. "
|
|
81
|
-
"Install it
|
|
81
|
+
"Install it in this project: pip install langchain-huggingface"
|
|
82
82
|
)
|
|
83
83
|
self._client = HuggingFaceEmbeddings(model_name=self._model_name)
|
|
84
84
|
# Resolve dimensions from a test embedding if not provided
|
|
@@ -131,7 +131,7 @@ class OpenAIEncoder(BaseEncoder):
|
|
|
131
131
|
except ImportError:
|
|
132
132
|
raise ImportError(
|
|
133
133
|
"langchain-openai is required for OpenAIEncoder. "
|
|
134
|
-
"Install it
|
|
134
|
+
"Install it in this project: pip install langchain-openai"
|
|
135
135
|
)
|
|
136
136
|
kwargs = {"model": self._model_name}
|
|
137
137
|
if self._dimensions is not None:
|
|
@@ -205,7 +205,7 @@ class AzureOpenAIEncoder(BaseEncoder):
|
|
|
205
205
|
except ImportError:
|
|
206
206
|
raise ImportError(
|
|
207
207
|
"langchain-openai is required for AzureOpenAIEncoder. "
|
|
208
|
-
"Install it
|
|
208
|
+
"Install it in this project: pip install langchain-openai"
|
|
209
209
|
)
|
|
210
210
|
kwargs = {"model": self._model_name}
|
|
211
211
|
if self._dimensions is not None:
|
|
@@ -283,7 +283,7 @@ class GoogleEncoder(BaseEncoder):
|
|
|
283
283
|
except ImportError:
|
|
284
284
|
raise ImportError(
|
|
285
285
|
"langchain-google-genai is required for GoogleEncoder. "
|
|
286
|
-
"Install it
|
|
286
|
+
"Install it in this project: pip install langchain-google-genai"
|
|
287
287
|
)
|
|
288
288
|
kwargs = {"model": self._model_name}
|
|
289
289
|
if self._dimensions is not None:
|
|
@@ -340,3 +340,63 @@ class GoogleEncoder(BaseEncoder):
|
|
|
340
340
|
{% endif %}
|
|
341
341
|
{% endfor %}
|
|
342
342
|
{% endfor %}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
# ── Vectors computed on write ─────────────────────────────────────────────────
|
|
346
|
+
# Per model: (vector column, source column, encoder). Only the vector columns that
|
|
347
|
+
# declare a `source` are computed on write; the rest are written as given.
|
|
348
|
+
VECTOR_SOURCES: Dict[str, Tuple[Tuple[str, str, BaseEncoder], ...]] = {
|
|
349
|
+
{% for model in models %}
|
|
350
|
+
{% set _sources = [] %}
|
|
351
|
+
{% for col in model.columns %}
|
|
352
|
+
{% if col.type == 'vector' and col.has_encoder and col.encoder_config and col.source %}
|
|
353
|
+
{% set _ = _sources.append(col) %}
|
|
354
|
+
{% endif %}
|
|
355
|
+
{% endfor %}
|
|
356
|
+
{% if _sources %}
|
|
357
|
+
'{{ model.name }}': (
|
|
358
|
+
{% for col in _sources %}
|
|
359
|
+
('{{ col.name }}', '{{ col.source }}', {{ model.tablename }}_{{ col.name }}_encoder),
|
|
360
|
+
{% endfor %}
|
|
361
|
+
),
|
|
362
|
+
{% endif %}
|
|
363
|
+
{% endfor %}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _pending(objects: Iterable[Any], model_name: Optional[str]) -> List[Tuple[BaseEncoder, str, str, List[Any]]]:
|
|
368
|
+
"""
|
|
369
|
+
Groups the objects whose vector is missing but whose source has a value, by column.
|
|
370
|
+
|
|
371
|
+
`model_name` is for objects that are not instances of the model —the `Create`
|
|
372
|
+
DTOs that `upsert_many` writes as dicts—; without it, each object is looked up by
|
|
373
|
+
its class name, which is what `session.new` holds.
|
|
374
|
+
"""
|
|
375
|
+
groups: Dict[Tuple[str, str], Tuple[BaseEncoder, str, str, List[Any]]] = {}
|
|
376
|
+
for obj in objects:
|
|
377
|
+
for column, source, encoder in VECTOR_SOURCES.get(model_name or type(obj).__name__, ()):
|
|
378
|
+
if getattr(obj, column, None) is None and getattr(obj, source, None) is not None:
|
|
379
|
+
key = (model_name or type(obj).__name__, column)
|
|
380
|
+
groups.setdefault(key, (encoder, column, source, []))[3].append(obj)
|
|
381
|
+
return list(groups.values())
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def encode_pending(objects: Iterable[Any], model_name: Optional[str] = None) -> None:
|
|
385
|
+
"""
|
|
386
|
+
Computes the vectors that are missing, one `encode_batch` per column.
|
|
387
|
+
|
|
388
|
+
It is what the DAOs call right before writing —on `session.new` before the flush,
|
|
389
|
+
so nested rows are included—: a DTO does not encode when it is built.
|
|
390
|
+
"""
|
|
391
|
+
for encoder, column, source, objs in _pending(list(objects), model_name):
|
|
392
|
+
vectors = encoder.encode_batch([getattr(o, source) for o in objs])
|
|
393
|
+
for obj, vector in zip(objs, vectors):
|
|
394
|
+
setattr(obj, column, vector)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
async def aencode_pending(objects: Iterable[Any], model_name: Optional[str] = None) -> None:
|
|
398
|
+
"""`encode_pending` for the async DAOs: it awaits the encoder instead of blocking the loop."""
|
|
399
|
+
for encoder, column, source, objs in _pending(list(objects), model_name):
|
|
400
|
+
vectors = await encoder.aencode_batch([getattr(o, source) for o in objs])
|
|
401
|
+
for obj, vector in zip(objs, vectors):
|
|
402
|
+
setattr(obj, column, vector)
|
|
@@ -12,6 +12,9 @@ ninguna capa de despacho — el coste es cero.
|
|
|
12
12
|
(metadato puro) → transformer → AST reescrito
|
|
13
13
|
→ renderer → dict de contexto para Jinja2
|
|
14
14
|
|
|
15
|
+
`scopes` lo usan los dos primeros: dice qué `t` es el parámetro y cuál es otra variable
|
|
16
|
+
con el mismo nombre en un ámbito interno.
|
|
17
|
+
|
|
15
18
|
Este código vivía en `orm/triggers.py` (1.300 de sus 2.092 líneas). Está aquí porque
|
|
16
19
|
conoce los nombres de los artefactos generados (`{Model}SyncDAO`, `{Model}Create`…),
|
|
17
20
|
que son conocimiento del generador, no de la definición del schema.
|