tai-sql 0.7.20__tar.gz → 0.8.0__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.20 → tai_sql-0.8.0}/PKG-INFO +2 -2
- {tai_sql-0.7.20 → tai_sql-0.8.0}/README.md +1 -1
- {tai_sql-0.7.20 → tai_sql-0.8.0}/pyproject.toml +1 -1
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_schema/schemas/chatbot.py +118 -36
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/__init__.py +23 -1
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/context.py +38 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/base.py.jinja2 +58 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/domain/dao.py.jinja2 +68 -5
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/domain/dtos.py.jinja2 +126 -18
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/facade.py.jinja2 +4 -1
- tai_sql-0.8.0/tai_sql/generators/python_client/templates/macros/aggregation.jinja2 +456 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/macros/filters.jinja2 +24 -13
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/macros/write.jinja2 +150 -54
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/shared/__init__.py.jinja2 +21 -2
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/shared/aggregation.py.jinja2 +80 -18
- tai_sql-0.8.0/tai_sql/generators/python_client/templates/shared/binary.py.jinja2 +99 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/shared/utils/__init__.py.jinja2 +5 -1
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/shared/utils/funcs.py.jinja2 +7 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/shared/utils/rls.py.jinja2 +43 -1
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/trigger_api.py +2 -2
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/types.py +56 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/rules/static/errores.md +70 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/rules/static/modelo.md +125 -0
- tai_sql-0.7.20/tai_sql/generators/python_client/templates/macros/aggregation.jinja2 +0 -910
- {tai_sql-0.7.20 → tai_sql-0.8.0}/LICENSE +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_backup/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_backup/main.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_deploy/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_deploy/config.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_deploy/local.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_deploy/main.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_deploy/workflow.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_feed/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_feed/main.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_generate/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_generate/funcs.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_generate/main.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_hash_migrate/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_hash_migrate/main.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_info/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_info/main.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_info/status.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_init/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_init/main.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_init/model.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_install/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_install/main.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_ping/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_ping/main.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_pull/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_pull/funcs.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_pull/main.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_restore/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_restore/main.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_rules/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_rules/main.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_schema/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_schema/model.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_schema/new.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_schema/schemas/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_schema/schemas/auth.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_schema/schemas/base.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_schema/schemas/default.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/cmd_schema/set_default.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/prompts.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/utils/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/utils/connectivity.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/utils/create.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/utils/github/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/utils/github/auth.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/utils/github/client.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/commands/utils/github/models.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/errors.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/main.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/preamble.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/services/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/cli/services/backup.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/connection/README.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/connection/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/connection/provider.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/connection/recipe.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/connection/session.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/connection/sources.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/crypto.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/drivers/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/drivers/base.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/drivers/bigquery.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/drivers/capabilities.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/drivers/mysql.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/drivers/postgresql.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/drivers/sqlserver.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/errors/README.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/errors/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/errors/collector.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/errors/render.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/errors/types.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/feed/README.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/feed/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/feed/collect.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/feed/executor.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/feed/feeder.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/feed/plan.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/feed/presenter.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/feed/records.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/feed/transform.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/base.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/diagram/README.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/diagram/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/diagram/assets/README.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/diagram/assets/cytoscape.min.js +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/diagram/builder.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/diagram/generator.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/diagram/graphviz_render.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/diagram/html_render.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/diagram/layout.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/diagram/model.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/diagram/templates/er.html.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/diagram/themes.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/calculated.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/domain/__init__.py.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/domain/model.py.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/macros/dataframe.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/macros/hierarchy.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/macros/read.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/macros/triggers.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/macros/upsert.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/macros/vector.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/session.py.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/shared/context.py.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/shared/utils/encoders.py.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/shared/utils/hierarchy.py.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/shared/vector.py.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/triggers/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/triggers/analyzer.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/triggers/renderer.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/triggers/scopes.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/triggers/steps.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/triggers/transformer.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/rules/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/rules/templates/schema.md.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/introspect/README.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/introspect/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/introspect/blueprint.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/introspect/build.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/introspect/defaults.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/introspect/naming.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/introspect/reader.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/introspect/templates/schema.py.jinja2 +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/introspect/types.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/introspect/writer.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/README.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/analysis/README.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/analysis/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/analysis/calculated.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/analysis/collector.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/analysis/column_analyzer.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/analysis/constraints.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/analysis/hints.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/analysis/relation_analyzer.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/analysis/schema_analyzer.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/README.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/annotations.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/base.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/calculated.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/columns.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/constraints.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/datasource.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/descriptors.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/encoders.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/enumerations.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/relations.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/table.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/triggers.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/declarative/view.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/mapping/README.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/mapping/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/mapping/sqlalchemy_mapper.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/model/README.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/model/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/model/column.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/model/constraints.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/model/entity.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/model/enumeration.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/model/relation.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/model/schema.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/model/trigger.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/naming.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/ordering.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/orm/registry.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/project/README.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/project/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/project/config.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/project/context.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/project/manager.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/project/paths.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/rules/README.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/rules/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/rules/catalog.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/rules/installer.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/rules/meta.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/rules/service.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/rules/staleness.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/rules/static/comandos.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/schema.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/README.md +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/diff/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/diff/constraints.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/diff/indexes.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/diff/tables.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/diff/views.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/executor.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/ownership.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/plan/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/plan/planner.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/plan/statements.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/presenter.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/report.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/safety.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/state/__init__.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/state/local.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/state/model.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/state/normalize.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/sync/state/remote.py +0 -0
- {tai_sql-0.7.20 → tai_sql-0.8.0}/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.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: SQL database management and code generation tool
|
|
5
5
|
License-Expression: LicenseRef-Proprietary
|
|
6
6
|
License-File: LICENSE
|
|
@@ -192,7 +192,7 @@ usuarios = public_sync_api.usuario.find_many(limit=10, includes=['posts'])
|
|
|
192
192
|
| [El schema](https://triplealpha-innovation.github.io/tai-sql/schema/) | Todo lo que se puede declarar, y cómo |
|
|
193
193
|
| [El CLI](https://triplealpha-innovation.github.io/tai-sql/cli/) | Qué hace cada comando, y qué puede destruir `push` |
|
|
194
194
|
| [El cliente generado](https://triplealpha-innovation.github.io/tai-sql/cliente/) | La API que vas a usar desde tu aplicación |
|
|
195
|
-
| [Referencia](https://triplealpha-innovation.github.io/tai-sql/referencia/dsl/) | Firmas del DSL, motores, extensión
|
|
195
|
+
| [Referencia](https://triplealpha-innovation.github.io/tai-sql/referencia/dsl/) | Firmas del DSL, motores, extensión, catálogo de errores y qué cambia al actualizar a la 0.8.0 |
|
|
196
196
|
|
|
197
197
|
---
|
|
198
198
|
|
|
@@ -129,7 +129,7 @@ usuarios = public_sync_api.usuario.find_many(limit=10, includes=['posts'])
|
|
|
129
129
|
| [El schema](https://triplealpha-innovation.github.io/tai-sql/schema/) | Todo lo que se puede declarar, y cómo |
|
|
130
130
|
| [El CLI](https://triplealpha-innovation.github.io/tai-sql/cli/) | Qué hace cada comando, y qué puede destruir `push` |
|
|
131
131
|
| [El cliente generado](https://triplealpha-innovation.github.io/tai-sql/cliente/) | La API que vas a usar desde tu aplicación |
|
|
132
|
-
| [Referencia](https://triplealpha-innovation.github.io/tai-sql/referencia/dsl/) | Firmas del DSL, motores, extensión
|
|
132
|
+
| [Referencia](https://triplealpha-innovation.github.io/tai-sql/referencia/dsl/) | Firmas del DSL, motores, extensión, catálogo de errores y qué cambia al actualizar a la 0.8.0 |
|
|
133
133
|
|
|
134
134
|
---
|
|
135
135
|
|
|
@@ -435,48 +435,130 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
435
435
|
''').rstrip()
|
|
436
436
|
|
|
437
437
|
consumo_diario_sql = dedent('''\
|
|
438
|
-
-- Vista para el consumo por usuario, día y modelo
|
|
438
|
+
-- Vista para el consumo por usuario, día y modelo. Los tokens y el importe son todos los
|
|
439
|
+
-- apuntes, tengan mensaje o no; los mensajes, los que envió el usuario
|
|
440
|
+
WITH consumo AS (
|
|
441
|
+
SELECT
|
|
442
|
+
tu.username,
|
|
443
|
+
DATE(tu.timestamp) as date,
|
|
444
|
+
m.provider,
|
|
445
|
+
m.model_name,
|
|
446
|
+
SUM(tu.prompt_tokens) as total_prompt_tokens,
|
|
447
|
+
SUM(tu.completion_tokens) as total_completion_tokens,
|
|
448
|
+
SUM(tu.total_tokens) as total_tokens,
|
|
449
|
+
SUM(ROUND(((tu.prompt_tokens * t.precio_input_eur_millon
|
|
450
|
+
+ tu.completion_tokens * t.precio_output_eur_millon)
|
|
451
|
+
* t.margen / 1000000)::numeric, 8))::double precision as total_cost_eur,
|
|
452
|
+
COUNT(DISTINCT COALESCE(tu.chat_id, me.chat_id)) as chat_count
|
|
453
|
+
FROM token_usage tu
|
|
454
|
+
JOIN tarifa t ON t.id = tu.tarifa_id
|
|
455
|
+
JOIN modelo m ON m.id = t.modelo_id
|
|
456
|
+
LEFT JOIN mensaje me ON me.id = tu.message_id
|
|
457
|
+
GROUP BY tu.username, DATE(tu.timestamp), m.provider, m.model_name
|
|
458
|
+
),
|
|
459
|
+
-- Cada mensaje del usuario cuenta en el modelo al que se envió. Los anteriores a guardarlo,
|
|
460
|
+
-- en el de la respuesta que los siguió; sin ninguno de los dos no se cuentan
|
|
461
|
+
enviados AS (
|
|
462
|
+
SELECT
|
|
463
|
+
c.username,
|
|
464
|
+
DATE(u.timestamp) as date,
|
|
465
|
+
COALESCE(u.provider, r.provider) as provider,
|
|
466
|
+
COALESCE(u.model_name, r.model_name) as model_name,
|
|
467
|
+
COUNT(*) as message_count
|
|
468
|
+
FROM mensaje u
|
|
469
|
+
JOIN chat c ON c.id = u.chat_id
|
|
470
|
+
LEFT JOIN LATERAL (
|
|
471
|
+
SELECT a.provider, a.model_name
|
|
472
|
+
FROM mensaje a
|
|
473
|
+
WHERE a.chat_id = u.chat_id
|
|
474
|
+
AND a.role IN ('asistente', 'ai', 'assistant')
|
|
475
|
+
AND a.timestamp >= u.timestamp
|
|
476
|
+
ORDER BY a.timestamp
|
|
477
|
+
LIMIT 1
|
|
478
|
+
) r ON u.model_name IS NULL
|
|
479
|
+
WHERE u.role IN ('usuario', 'human', 'user')
|
|
480
|
+
AND COALESCE(u.model_name, r.model_name) IS NOT NULL
|
|
481
|
+
GROUP BY c.username, DATE(u.timestamp), COALESCE(u.provider, r.provider), COALESCE(u.model_name, r.model_name)
|
|
482
|
+
)
|
|
439
483
|
SELECT
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
LEFT JOIN mensaje me ON me.id = tu.message_id
|
|
456
|
-
GROUP BY tu.username, DATE(tu.timestamp), m.provider, m.model_name
|
|
457
|
-
ORDER BY date DESC, total_tokens DESC;
|
|
484
|
+
COALESCE(co.username, e.username) as username,
|
|
485
|
+
COALESCE(co.date, e.date) as date,
|
|
486
|
+
COALESCE(co.provider, e.provider) as provider,
|
|
487
|
+
COALESCE(co.model_name, e.model_name) as model_name,
|
|
488
|
+
COALESCE(co.total_prompt_tokens, 0) as total_prompt_tokens,
|
|
489
|
+
COALESCE(co.total_completion_tokens, 0) as total_completion_tokens,
|
|
490
|
+
COALESCE(co.total_tokens, 0) as total_tokens,
|
|
491
|
+
COALESCE(co.total_cost_eur, 0) as total_cost_eur,
|
|
492
|
+
COALESCE(co.chat_count, 0) as chat_count,
|
|
493
|
+
COALESCE(e.message_count, 0) as message_count
|
|
494
|
+
FROM consumo co
|
|
495
|
+
FULL JOIN enviados e
|
|
496
|
+
ON e.username = co.username AND e.date = co.date
|
|
497
|
+
AND e.provider = co.provider AND e.model_name = co.model_name
|
|
498
|
+
ORDER BY date DESC, username, total_tokens DESC;
|
|
458
499
|
''').rstrip()
|
|
459
500
|
|
|
460
501
|
consumo_mensual_sql = dedent('''\
|
|
461
502
|
-- Vista para el consumo y el coste por usuario, mes y modelo. El importe se calcula aquí:
|
|
462
|
-
-- el apunte guarda con qué tarifa se valora y el margen no sale de la tabla `tarifa
|
|
503
|
+
-- el apunte guarda con qué tarifa se valora y el margen no sale de la tabla `tarifa`.
|
|
504
|
+
-- Los tokens y el importe son todos los apuntes, tengan mensaje o no (una redacción del modal
|
|
505
|
+
-- de incidencias también); los mensajes, los que envió el usuario
|
|
506
|
+
WITH consumo AS (
|
|
507
|
+
SELECT
|
|
508
|
+
tu.username,
|
|
509
|
+
DATE_TRUNC('month', tu.timestamp)::date as mes,
|
|
510
|
+
m.provider,
|
|
511
|
+
m.model_name,
|
|
512
|
+
SUM(tu.prompt_tokens) as total_prompt_tokens,
|
|
513
|
+
SUM(tu.completion_tokens) as total_completion_tokens,
|
|
514
|
+
SUM(tu.total_tokens) as total_tokens,
|
|
515
|
+
SUM(ROUND(((tu.prompt_tokens * t.precio_input_eur_millon
|
|
516
|
+
+ tu.completion_tokens * t.precio_output_eur_millon)
|
|
517
|
+
* t.margen / 1000000)::numeric, 8))::double precision as coste_eur
|
|
518
|
+
FROM token_usage tu
|
|
519
|
+
JOIN tarifa t ON t.id = tu.tarifa_id
|
|
520
|
+
JOIN modelo m ON m.id = t.modelo_id
|
|
521
|
+
GROUP BY tu.username, DATE_TRUNC('month', tu.timestamp)::date, m.provider, m.model_name
|
|
522
|
+
),
|
|
523
|
+
-- Cada mensaje del usuario cuenta en el modelo al que se envió. Los anteriores a guardarlo,
|
|
524
|
+
-- en el de la respuesta que los siguió; sin ninguno de los dos no se cuentan
|
|
525
|
+
enviados AS (
|
|
526
|
+
SELECT
|
|
527
|
+
c.username,
|
|
528
|
+
DATE_TRUNC('month', u.timestamp)::date as mes,
|
|
529
|
+
COALESCE(u.provider, r.provider) as provider,
|
|
530
|
+
COALESCE(u.model_name, r.model_name) as model_name,
|
|
531
|
+
COUNT(*) as message_count
|
|
532
|
+
FROM mensaje u
|
|
533
|
+
JOIN chat c ON c.id = u.chat_id
|
|
534
|
+
LEFT JOIN LATERAL (
|
|
535
|
+
SELECT a.provider, a.model_name
|
|
536
|
+
FROM mensaje a
|
|
537
|
+
WHERE a.chat_id = u.chat_id
|
|
538
|
+
AND a.role IN ('asistente', 'ai', 'assistant')
|
|
539
|
+
AND a.timestamp >= u.timestamp
|
|
540
|
+
ORDER BY a.timestamp
|
|
541
|
+
LIMIT 1
|
|
542
|
+
) r ON u.model_name IS NULL
|
|
543
|
+
WHERE u.role IN ('usuario', 'human', 'user')
|
|
544
|
+
AND COALESCE(u.model_name, r.model_name) IS NOT NULL
|
|
545
|
+
GROUP BY c.username, DATE_TRUNC('month', u.timestamp)::date, COALESCE(u.provider, r.provider), COALESCE(u.model_name, r.model_name)
|
|
546
|
+
)
|
|
463
547
|
SELECT
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
GROUP BY tu.username, DATE_TRUNC('month', tu.timestamp), m.provider, m.model_name
|
|
479
|
-
ORDER BY mes DESC, tu.username, total_tokens DESC;
|
|
548
|
+
COALESCE(co.username, e.username) as username,
|
|
549
|
+
COALESCE(co.mes, e.mes) as mes,
|
|
550
|
+
COALESCE(co.provider, e.provider) as provider,
|
|
551
|
+
COALESCE(co.model_name, e.model_name) as model_name,
|
|
552
|
+
COALESCE(co.total_prompt_tokens, 0) as total_prompt_tokens,
|
|
553
|
+
COALESCE(co.total_completion_tokens, 0) as total_completion_tokens,
|
|
554
|
+
COALESCE(co.total_tokens, 0) as total_tokens,
|
|
555
|
+
COALESCE(co.coste_eur, 0) as coste_eur,
|
|
556
|
+
COALESCE(e.message_count, 0) as message_count
|
|
557
|
+
FROM consumo co
|
|
558
|
+
FULL JOIN enviados e
|
|
559
|
+
ON e.username = co.username AND e.mes = co.mes
|
|
560
|
+
AND e.provider = co.provider AND e.model_name = co.model_name
|
|
561
|
+
ORDER BY mes DESC, username, total_tokens DESC;
|
|
480
562
|
''').rstrip()
|
|
481
563
|
|
|
482
564
|
modelo_disponible_sql = dedent('''\
|
|
@@ -428,9 +428,31 @@ class PythonClientGenerator(BaseGenerator):
|
|
|
428
428
|
for m in all_models_data
|
|
429
429
|
)
|
|
430
430
|
|
|
431
|
+
# Las columnas bytes viajan en base64 en JSON: su tipo en los DTOs vive en binary.py
|
|
432
|
+
has_bytes = any(
|
|
433
|
+
col.get('dto_type') == 'JsonBytes'
|
|
434
|
+
for m in all_models_data
|
|
435
|
+
for col in m.get('columns', [])
|
|
436
|
+
)
|
|
437
|
+
|
|
431
438
|
# _shared/__init__.py
|
|
432
439
|
init_template = self.jinja_env.get_template('shared/__init__.py.jinja2')
|
|
433
|
-
(shared_dir / '__init__.py').write_text(
|
|
440
|
+
(shared_dir / '__init__.py').write_text(
|
|
441
|
+
init_template.render(
|
|
442
|
+
has_hierarchy=has_hierarchy,
|
|
443
|
+
has_vector_encoders=has_vector_encoders,
|
|
444
|
+
has_bytes=has_bytes,
|
|
445
|
+
),
|
|
446
|
+
encoding='utf-8',
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# _shared/binary.py — only when some column is bytes
|
|
450
|
+
binary_path = shared_dir / 'binary.py'
|
|
451
|
+
if has_bytes:
|
|
452
|
+
binary_template = self.jinja_env.get_template('shared/binary.py.jinja2')
|
|
453
|
+
binary_path.write_text(binary_template.render(), encoding='utf-8')
|
|
454
|
+
elif binary_path.exists():
|
|
455
|
+
binary_path.unlink()
|
|
434
456
|
|
|
435
457
|
# _shared/context.py
|
|
436
458
|
context_template = self.jinja_env.get_template('shared/context.py.jinja2')
|
|
@@ -204,6 +204,14 @@ class TemplateContextBuilder:
|
|
|
204
204
|
constraints = self._constraints(column)
|
|
205
205
|
sa_type, sa_type_args = _type_with_params(column)
|
|
206
206
|
|
|
207
|
+
# La clave autoincremental solo se filtra por lista. Igualdad no hace falta —para un
|
|
208
|
+
# registro está `find(id)`— y un rango sobre un contador que pone el motor no dice
|
|
209
|
+
# nada del dato; pero «estos ids» sí es una pregunta legítima (recorrer un grafo por
|
|
210
|
+
# niveles, traer lo que otra consulta devolvió), y sin `in_<pk>` solo se podía hacer
|
|
211
|
+
# con una consulta por id. Hasta 0.7.21 no tenía ningún filtro.
|
|
212
|
+
if constraints['autoincrement']:
|
|
213
|
+
filter_fields = ['in']
|
|
214
|
+
|
|
207
215
|
context = {
|
|
208
216
|
'name': column.name,
|
|
209
217
|
'type': column.type,
|
|
@@ -231,6 +239,9 @@ class TemplateContextBuilder:
|
|
|
231
239
|
# El tamaño máximo de un texto (`column(length=n)`), para quien valide antes de
|
|
232
240
|
# escribir —los esquemas de formulario de tai-api—. `None` si no se declaró.
|
|
233
241
|
'max_length': column.length if column.type == 'str' else None,
|
|
242
|
+
# Las agregaciones que admite: el DAO rechaza el resto con un error, y tai-api
|
|
243
|
+
# restringe con esto las columnas de cada operación en su cliente TypeScript.
|
|
244
|
+
'aggregations': _aggregations(column, descriptor),
|
|
234
245
|
}
|
|
235
246
|
|
|
236
247
|
if column.is_vector:
|
|
@@ -250,6 +261,15 @@ class TemplateContextBuilder:
|
|
|
250
261
|
'threshold': vector.threshold,
|
|
251
262
|
})
|
|
252
263
|
|
|
264
|
+
# El tipo del campo en los DTOs. Es `pydantic_type` salvo en `bytes`, que en JSON
|
|
265
|
+
# viaja en base64 (`JsonBytes`, de `_shared/binary.py`): con `bytes` desnudo,
|
|
266
|
+
# pydantic lee y escribe el texto en UTF-8, así que guardaba el base64 que manda un
|
|
267
|
+
# frontend como texto y no podía devolver un binario de verdad. `pydantic_type` no
|
|
268
|
+
# cambia porque es contrato: tai-api lo lee y espera `'bytes'`.
|
|
269
|
+
context['dto_type'] = (
|
|
270
|
+
'JsonBytes' if context['pydantic_type'] == 'bytes' else context['pydantic_type']
|
|
271
|
+
)
|
|
272
|
+
|
|
253
273
|
return context
|
|
254
274
|
|
|
255
275
|
def calculated_column(
|
|
@@ -566,6 +586,24 @@ def _template_default(default: Any) -> Any:
|
|
|
566
586
|
return default
|
|
567
587
|
|
|
568
588
|
|
|
589
|
+
def _aggregations(column: ColumnModel, descriptor: Any) -> List[str]:
|
|
590
|
+
"""
|
|
591
|
+
Las agregaciones que admite una columna: las de su tipo, menos lo que la columna misma
|
|
592
|
+
hace absurdo o peligroso. En esas tres solo queda `count`, que no mira el valor:
|
|
593
|
+
|
|
594
|
+
- **cifrada**: el motor ordenaría el texto cifrado —con un vector de inicialización
|
|
595
|
+
aleatorio, así que sin relación con el valor— y devolvería el cifrado sin descifrar.
|
|
596
|
+
- **hasheada**: `max` devolvería un hash bcrypt, que el cliente no expone en ninguna
|
|
597
|
+
lectura (tai-api lo saca de toda respuesta).
|
|
598
|
+
- **enumerado**: se guarda como texto en los cuatro motores, así que `max` sería el
|
|
599
|
+
último valor **por orden alfabético**, no el último declarado —que es lo que alguien
|
|
600
|
+
espera de un estado—. La respuesta honesta es `count` con `group_by`.
|
|
601
|
+
"""
|
|
602
|
+
if column.encrypt or column.hash or column.options is not None:
|
|
603
|
+
return ['count']
|
|
604
|
+
return descriptor.aggregations() if descriptor else ['count']
|
|
605
|
+
|
|
606
|
+
|
|
569
607
|
def _type_with_params(column: ColumnModel) -> tuple:
|
|
570
608
|
"""
|
|
571
609
|
El tipo SQLAlchemy del modelo generado y sus argumentos: `('String', '(50)')`.
|
|
@@ -38,6 +38,64 @@ class TriggerAborted(ValueError):
|
|
|
38
38
|
self.message = message
|
|
39
39
|
super().__init__(f"Trigger abortado: {message}")
|
|
40
40
|
|
|
41
|
+
|
|
42
|
+
class NestedWriteError(Exception):
|
|
43
|
+
"""
|
|
44
|
+
Una escritura anidada del `update` no se puede aplicar al registro que se actualiza.
|
|
45
|
+
|
|
46
|
+
Un anidado nombra hijos **de ese registro**: los crea colgando de él, o los actualiza y
|
|
47
|
+
los borra por su clave. Lo que no casa con eso no se ignora —hasta 0.7.21 un hijo que
|
|
48
|
+
no se encontraba se saltaba sin error— y se lanza una de sus dos subclases. La
|
|
49
|
+
transacción se deshace entera: ni el padre ni los demás hijos quedan escritos.
|
|
50
|
+
|
|
51
|
+
- `message`: el texto para quien hizo la petición, con la solución.
|
|
52
|
+
- `field`: dónde está en el payload, con la forma del JSON (`resenas.delete.0`,
|
|
53
|
+
`libros.update.0.values.resenas.update.1`; en una uno a uno, sin índice). Lo
|
|
54
|
+
construye cada nivel al subir, así que es la ruta completa desde el registro raíz.
|
|
55
|
+
- `table`: la tabla del hijo. `key`: su clave, tal como llegó.
|
|
56
|
+
- `reason`: `'not_found'` o `'parent_mismatch'`.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
#: Para reconocerla sin importar la clase, como `tai_trigger_abort`: tai-api atiende a
|
|
60
|
+
#: los clientes de varios schemas con un solo manejador, y cada uno tiene la suya.
|
|
61
|
+
tai_nested_write = True
|
|
62
|
+
reason: str = ''
|
|
63
|
+
|
|
64
|
+
def __init__(self, message: str, *, field: str, table: str, key=None):
|
|
65
|
+
self.message = message
|
|
66
|
+
self.field = field
|
|
67
|
+
self.table = table
|
|
68
|
+
self.key = key
|
|
69
|
+
super().__init__(message)
|
|
70
|
+
|
|
71
|
+
def _under(self, prefix: str) -> 'NestedWriteError':
|
|
72
|
+
"""La misma excepción, vista desde el padre: antepone su tramo a `field`."""
|
|
73
|
+
self.field = f'{prefix}.{self.field}'
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class NestedRowNotFound(NestedWriteError, LookupError):
|
|
78
|
+
"""
|
|
79
|
+
Un `update` o `delete` anidado nombra una fila que no está bajo el registro que se
|
|
80
|
+
actualiza: no existe, cuelga de otro padre o la regla de RLS no la alcanza.
|
|
81
|
+
|
|
82
|
+
Los tres casos son el mismo a propósito —para quien llama, esa fila no es hija de este
|
|
83
|
+
registro— y distinguirlos delataría filas que la regla oculta. Es lo que tai-api
|
|
84
|
+
responde con un 404.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
reason = 'not_found'
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class NestedParentMismatch(NestedWriteError, ValueError):
|
|
91
|
+
"""
|
|
92
|
+
Un `update` anidado cambia las columnas que unen el hijo con su padre: lo sacaría del
|
|
93
|
+
registro bajo el que se ha nombrado. Para mover una fila de padre se usa el DAO de su
|
|
94
|
+
tabla. Es una petición inválida (un 422 en tai-api), no una fila que falte.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
reason = 'parent_mismatch'
|
|
98
|
+
|
|
41
99
|
{% if has_encrypted_columns %}
|
|
42
100
|
|
|
43
101
|
# Configuración de encriptación
|
{tai_sql-0.7.20 → tai_sql-0.8.0}/tai_sql/generators/python_client/templates/domain/dao.py.jinja2
RENAMED
|
@@ -59,7 +59,10 @@ from {{ import_prefix }}_shared import (
|
|
|
59
59
|
RLS,
|
|
60
60
|
_log_tx,
|
|
61
61
|
_log_commit,
|
|
62
|
-
|
|
62
|
+
AGG_OPERATIONS,
|
|
63
|
+
agg_field_error,
|
|
64
|
+
agg_expression_error,
|
|
65
|
+
agg_value,
|
|
63
66
|
OPERATION_FUNCTIONS,
|
|
64
67
|
GROUP_BY_FORBIDDEN_TYPES,
|
|
65
68
|
GROUP_BY_DATETIME_TYPES,
|
|
@@ -91,6 +94,17 @@ from {{ import_prefix }}_base import verify_hash, HASH_DE_RELLENO
|
|
|
91
94
|
{% if model.triggers is defined and (model.triggers['create'] or model.triggers['update'] or model.triggers['delete']) %}
|
|
92
95
|
from {{ import_prefix }}_base import TriggerAborted
|
|
93
96
|
{% endif %}
|
|
97
|
+
{# Las escrituras anidadas del `update`: se acotan al padre y lo que no casa se lanza. #}
|
|
98
|
+
{% set _dao_nested = [] %}
|
|
99
|
+
{% for relation in model.relations | default([]) if relation.supports_nested_write and not model.is_view %}
|
|
100
|
+
{% if models | selectattr('name', 'equalto', relation.target) | first %}
|
|
101
|
+
{% set _ = _dao_nested.append(relation.name) %}
|
|
102
|
+
{% endif %}
|
|
103
|
+
{% endfor %}
|
|
104
|
+
{% if _dao_nested %}
|
|
105
|
+
from {{ import_prefix }}_base import NestedWriteError, NestedRowNotFound, NestedParentMismatch
|
|
106
|
+
from {{ import_prefix }}_shared import scope_to_parent
|
|
107
|
+
{% endif %}
|
|
94
108
|
{# ── Collect cross-DAO targets (relations + trigger cross-table ops) ── #}
|
|
95
109
|
{% set _cross_dao_targets = [] %}
|
|
96
110
|
{% for rel in model.relations | default([]) %}
|
|
@@ -140,6 +154,41 @@ def _para_log(valores: dict) -> dict:
|
|
|
140
154
|
"""
|
|
141
155
|
return {clave: ('***' if clave in _COLUMNAS_PROTEGIDAS else valor) for clave, valor in valores.items()}
|
|
142
156
|
{% endif %}
|
|
157
|
+
{% if _dao_nested %}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _hijo_que_no_esta(tabla: str, clave: Any, field: str) -> NestedRowNotFound:
|
|
161
|
+
"""La excepción de un `update`/`delete` anidado que no alcanza ninguna fila."""
|
|
162
|
+
return NestedRowNotFound(
|
|
163
|
+
f"{tabla} {clave!r} no es hija de este {{ model.tablename }}: no existe, cuelga de "
|
|
164
|
+
"otro registro o la regla de seguridad no la alcanza. Un update o delete anidado "
|
|
165
|
+
"solo llega a los hijos del registro que se actualiza: comprueba la clave, o "
|
|
166
|
+
"escribe esa fila con el DAO de su tabla.",
|
|
167
|
+
field=field, table=tabla, key=clave,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _comprobar_union(valores: Any, bajo: Dict[str, Any], tabla: str, field: str) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Lanza si un `update` anidado cambia la unión del hijo con este registro.
|
|
174
|
+
|
|
175
|
+
Sacaría la fila de debajo del registro bajo el que se ha nombrado, que es justo lo que
|
|
176
|
+
acotar el anidado al padre impide. Reenviar el valor que ya tiene no es un cambio.
|
|
177
|
+
"""
|
|
178
|
+
cambios = {
|
|
179
|
+
columna: getattr(valores, columna)
|
|
180
|
+
for columna, valor in bajo.items()
|
|
181
|
+
if columna in valores.model_fields_set and getattr(valores, columna) != valor
|
|
182
|
+
}
|
|
183
|
+
if cambios:
|
|
184
|
+
raise NestedParentMismatch(
|
|
185
|
+
f"Un update anidado no puede cambiar la unión de {tabla} con este "
|
|
186
|
+
f"{{ model.tablename }} ({cambios}): la sacaría del registro bajo el que se ha "
|
|
187
|
+
"nombrado. Quita esas columnas de los valores, o mueve la fila con el DAO de "
|
|
188
|
+
"su tabla.",
|
|
189
|
+
field=f"{field}.{next(iter(cambios))}", table=tabla, key=None,
|
|
190
|
+
)
|
|
191
|
+
{% endif %}
|
|
143
192
|
|
|
144
193
|
{% set vector_cols_with_encoder = model.columns | selectattr('type', 'equalto', 'vector') | selectattr('has_encoder') | list %}
|
|
145
194
|
{% if vector_cols_with_encoder %}
|
|
@@ -167,6 +216,16 @@ from {{ import_prefix }}_shared.utils.encoders import {{ 'aencode_pending' if is
|
|
|
167
216
|
cambiado es quién contesta. Antes lo decidía aquí un bucle que no miraba las
|
|
168
217
|
constraints compuestas, así que `libro` y `resena` se quedaban sin `upsert()`. #}
|
|
169
218
|
{% set ns = namespace(all_pk_autoincrement=(model.natural_key | default([]) | length == 0)) %}
|
|
219
|
+
# Qué agregaciones admite cada columna, {columna: (tipo, operaciones)}. Sale del tipo del
|
|
220
|
+
# schema: el SQLAlchemy no distingue un enumerado ni una columna cifrada de un texto. Lo
|
|
221
|
+
# que no está aquí no se agrega, y pedirlo es un error con su motivo (`agg_field_error`).
|
|
222
|
+
_AGG_COLUMNS = {
|
|
223
|
+
{% for column in model.columns %}
|
|
224
|
+
'{{ column.name }}': ('{{ column.type }}{% if column.encrypt %}, cifrada{% elif column.hash %}, hasheada{% elif column.options is not none %}, enumerado{% endif %}', ({% for op in column.aggregations %}'{{ op }}'{% if not loop.last or loop.length == 1 %},{% endif %}{% if not loop.last %} {% endif %}{% endfor %})),
|
|
225
|
+
{% endfor %}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
170
229
|
class {{ model.name }}{{ prefix }}DAO:
|
|
171
230
|
"""
|
|
172
231
|
DAO síncrono de acceso a datos para el modelo ``{{ model.name }}``.
|
|
@@ -229,9 +288,10 @@ class {{ model.name }}{{ prefix }}DAO:
|
|
|
229
288
|
``flush`` y ``_on_create_after`` después. Retorna el DTO del
|
|
230
289
|
registro con los valores generados por la BD (IDs, timestamps...).
|
|
231
290
|
|
|
232
|
-
create_many(records, *operative_fields, session) →
|
|
291
|
+
create_many(records, *operative_fields, session) → List[Read]:
|
|
233
292
|
Inserción masiva con hooks de trigger por instancia.
|
|
234
|
-
Retorna
|
|
293
|
+
Retorna los DTO de los registros creados, en el mismo orden, con
|
|
294
|
+
los valores que haya generado la BD — como ``create``.
|
|
235
295
|
|
|
236
296
|
update(*pk_fields, updated_values, *operative_fields, session) → int:
|
|
237
297
|
Actualiza un registro por PK ejecutando ``_on_update_before/after``.
|
|
@@ -280,13 +340,16 @@ class {{ model.name }}{{ prefix }}DAO:
|
|
|
280
340
|
``result.data`` → ``{"mean_<campo>": float | None}``.
|
|
281
341
|
|
|
282
342
|
max(agg_fields, *filters, session) → AggregationResult:
|
|
283
|
-
Valor máximo de campos numéricos
|
|
343
|
+
Valor máximo de campos numéricos, de fecha (ISO 8601) o de texto.
|
|
284
344
|
``result.data`` → ``{"max_<campo>": float | str | None}``.
|
|
285
345
|
|
|
286
346
|
min(agg_fields, *filters, session) → AggregationResult:
|
|
287
|
-
Valor mínimo de campos numéricos
|
|
347
|
+
Valor mínimo de campos numéricos, de fecha (ISO 8601) o de texto.
|
|
288
348
|
``result.data`` → ``{"min_<campo>": float | str | None}``.
|
|
289
349
|
|
|
350
|
+
Lo que el tipo de una columna no admite (``_AGG_COLUMNS``) es un error:
|
|
351
|
+
``success=False``, sin consultar, y ``errors`` dice por qué.
|
|
352
|
+
|
|
290
353
|
agg(request, *filters, verbose, session) → AggregationResult:
|
|
291
354
|
Motor de agregaciones multi-operación. Acepta un ``AggRequest``
|
|
292
355
|
con múltiples operaciones (sum, mean, max, min, count),
|