tai-api 0.7.2__tar.gz → 0.7.4__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_api-0.7.2 → tai_api-0.7.4}/PKG-INFO +3 -3
- {tai_api-0.7.2 → tai_api-0.7.4}/pyproject.toml +3 -3
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/main_file/templates/app/__init__.py.j2 +24 -2
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/main_file/templates/app/docs.py.j2 +1 -1
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/main.py +12 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/post/create_many.py.j2 +9 -24
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/schema/_anidados.py.j2 +125 -21
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/tipos.py +1 -1
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/_shared/__init__.py +10 -0
- tai_api-0.7.4/tai_api/rbac/providers/database/auth/_shared/utils/hierarchy.py +616 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/role/dao.py +86 -15
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/role/dtos.py +3 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rules/static/api.md +2 -1
- {tai_api-0.7.2 → tai_api-0.7.4}/LICENSE +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_auth/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_auth/main.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_auth/model.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_dev/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_dev/main.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_generate/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_generate/main.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_generate/model.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_init/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_init/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_init/config.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_init/main.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_init/model.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_init/resources/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_init/resources/exceptions.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_init/resources/handlers.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_init/resources/mcp.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_init/resources/responses.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_init/templates/README.md.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_init/templates/dockerfile.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_init/templates/entrypoint.sh.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_init/templates/pyproject.toml.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_install/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_install/main.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_mcp/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_mcp/main.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_rbac/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_rbac/funcs.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_rbac/main.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_rules/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_rules/main.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/cmd_rules/model.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/commands/prompts.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/group.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/cli/main.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/config.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/contract/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/contract/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/contract/auth.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/contract/build.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/contract/model.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/errors/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/errors/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/errors/render.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/errors/types.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/frontend_checks.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generated_auth.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/main.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/rbac.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/db/dependencies.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/db/jwt.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/db/login_router.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/db/rbac_backend.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/kc/dependencies.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/kc/login_router.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/kc/rbac_backend.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/rbac/rbac_template.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/rbac/tables.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/shared/__init__.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/shared/rbac_models.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/shared/rbac_router.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/shared/sesion.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/shared/utils.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/auth/templates/shared/websocket.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/main_file/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/main_file/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/main_file/main.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/main_file/templates/__dev__.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/main_file/templates/__main__.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/main_file/templates/app/docs_assets/docs.css.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/main_file/templates/app/docs_assets/triplealpha.svg.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/main_file/templates/app/docs_assets/zoom.js.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/main_file/templates/app/health.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/propiedad.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/domain/__init__.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/delete/delete.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/delete/delete_many.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/delete/docs.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/get/count.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/get/docs.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/get/exists.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/get/find.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/get/find_many.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/get/macros.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/get/max.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/get/mean.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/get/min.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/get/sum.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/patch/docs.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/patch/update.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/patch/update_many.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/post/agg.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/post/create.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/post/docs.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/endpoints/shared_macros.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/enums/__init__.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/enums/docs.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/enums/endpoint.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/routers/__init__.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/schema/__init__.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/schema/__outerinit__.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/schema/_cabeceras.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/schema/_filtros.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/schema/_masivos.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/tx/__init__.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/tx/docs.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/routers/templates/tx/transaction.py.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/main.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/README.md.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/_interfaces.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/auth/models.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/auth/permissions.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/auth/session.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/core/bytes.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/core/custom.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/core/dates.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/core/errors.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/core/http.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/core/query.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/core/transaction.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/core/types.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/index.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/rbac/index.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/rbac/models.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/rbac/query.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/rbac/resources.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/react/context.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/react/hooks.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/react/index.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/react/render.tsx.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/react/resolve.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/react/routes.tsx.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/react/session.tsx.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/routes/index.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/schema/index.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/schema/models.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/schema/query.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/schema/resources.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/schema/zod.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/svelte/index.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/svelte/routes.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/svelte/session.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/generators/ts_client/templates/vite.ts.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/project.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/prompts.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/actions.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/declaration.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/drift.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/funcs.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/manager.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/_base.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/_session.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/_shared/aggregation.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/_shared/context.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/_shared/utils/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/_shared/utils/funcs.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/_shared/utils/rls.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/permission/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/permission/dao.py +2 -2
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/permission/dtos.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/permission/model.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/realm/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/realm/dao.py +2 -2
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/realm/dtos.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/realm/model.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/role/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/role/model.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/role_permission/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/role_permission/dao.py +2 -2
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/role_permission/dtos.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/role_permission/model.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/row_level_security/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/row_level_security/dao.py +2 -2
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/row_level_security/dtos.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/row_level_security/model.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/user/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/user/dao.py +2 -2
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/user/dtos.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/user/model.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/user_role/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/user_role/dao.py +2 -2
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/user_role/dtos.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/database/auth/user_role/model.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/providers/keycloak.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rbac/safety.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/resources_revision.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rules/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rules/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rules/catalog.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rules/derived.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rules/service.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rules/staleness.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rules/static/autenticacion.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rules/static/cliente-ts.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rules/static/comandos.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rules/static/errores.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rules/templates/endpoints.md.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/rules/templates/proyecto.md.j2 +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/service_dependencies.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/workflows/README.md +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/workflows/__init__.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/workflows/auth.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/workflows/generate.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/workflows/init.py +0 -0
- {tai_api-0.7.2 → tai_api-0.7.4}/tai_api/workflows/rbac.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tai-api
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.4
|
|
4
4
|
Summary: FastAPI framework with TAI ecosystem integration
|
|
5
5
|
License-Expression: LicenseRef-Proprietary
|
|
6
6
|
License-File: LICENSE
|
|
@@ -29,8 +29,8 @@ Requires-Dist: python-multipart (>=0.0.9,<1.0) ; extra == "run"
|
|
|
29
29
|
Requires-Dist: sqlalchemy[asyncio] (>=2.0.0,<3.0) ; extra == "run"
|
|
30
30
|
Requires-Dist: tai-alphi (>=2.0.1,<3.0)
|
|
31
31
|
Requires-Dist: tai-keycloak (>=0.3.3,<0.4) ; extra == "rbac"
|
|
32
|
-
Requires-Dist: tai-sql (>=0.8.
|
|
33
|
-
Requires-Dist: tai-sql[diagrams,hashing] (>=0.8.
|
|
32
|
+
Requires-Dist: tai-sql (>=0.8.4,<1.0)
|
|
33
|
+
Requires-Dist: tai-sql[diagrams,hashing] (>=0.8.4,<1.0) ; extra == "database"
|
|
34
34
|
Requires-Dist: uvicorn[standard] (>=0.30.0,<1.0) ; extra == "run"
|
|
35
35
|
Project-URL: Homepage, https://www.triplealpha.in/es/
|
|
36
36
|
Project-URL: Issues, https://github.com/triplealpha-innovation/tai-api/issues
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tai-api"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.4"
|
|
4
4
|
description = "FastAPI framework with TAI ecosystem integration"
|
|
5
5
|
license = "LicenseRef-Proprietary"
|
|
6
6
|
license-files = ["LICENSE"]
|
|
@@ -27,7 +27,7 @@ classifiers = [
|
|
|
27
27
|
# sqlalchemy llegan como dependencias obligatorias de tai-sql. tai-alphi no: llegaba de rebote por
|
|
28
28
|
# tai-keycloak 0.2, y con la 0.3 el CLI dejaba de arrancar en una instalación limpia.
|
|
29
29
|
dependencies = [
|
|
30
|
-
"tai-sql>=0.8.
|
|
30
|
+
"tai-sql>=0.8.4,<1.0",
|
|
31
31
|
"tai-alphi>=2.0.1,<3.0",
|
|
32
32
|
]
|
|
33
33
|
|
|
@@ -54,7 +54,7 @@ rbac = [
|
|
|
54
54
|
# que lleva tai-api dentro (rbac/providers/database/auth), con contraseñas hasheadas (extra
|
|
55
55
|
# `hashing`). pandas no: ese cliente solo lo importa dentro de los métodos de DataFrame.
|
|
56
56
|
database = [
|
|
57
|
-
"tai-sql[diagrams,hashing]>=0.8.
|
|
57
|
+
"tai-sql[diagrams,hashing]>=0.8.4,<1.0",
|
|
58
58
|
]
|
|
59
59
|
|
|
60
60
|
[project.urls]
|
|
@@ -39,12 +39,31 @@ from . import docs, health
|
|
|
39
39
|
TITLE = "{{ project_name }}"
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
class RootPath:
|
|
43
|
+
"""
|
|
44
|
+
El prefijo bajo el que se sirve la API (`ROOT_PATH` de `config.py`), en cada petición.
|
|
45
|
+
|
|
46
|
+
Es lo que hacía `FastAPI(root_path=...)`, que FastAPI desaprueba desde la 0.146 para que lo ponga
|
|
47
|
+
el servidor (`--root-path`). Aquí no depende de cómo se arranque —`tai-api dev`, `uvicorn` en el
|
|
48
|
+
contenedor, `__main__.py`—, y `ROOT_PATH` sigue en un solo sitio. De él cuelgan la ruta de la
|
|
49
|
+
cookie de sesión (`/api/auth`) y los enlaces de la documentación.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, app, root_path: str):
|
|
53
|
+
self.app = app
|
|
54
|
+
self.root_path = root_path
|
|
55
|
+
|
|
56
|
+
async def __call__(self, scope, receive, send):
|
|
57
|
+
if self.root_path and scope["type"] in ("http", "websocket"):
|
|
58
|
+
scope["root_path"] = self.root_path
|
|
59
|
+
await self.app(scope, receive, send)
|
|
60
|
+
|
|
61
|
+
|
|
42
62
|
def create_app() -> FastAPI:
|
|
43
63
|
app = FastAPI(
|
|
44
64
|
title=TITLE,
|
|
45
65
|
version=VERSION,
|
|
46
66
|
lifespan=lifespan,
|
|
47
|
-
root_path=ROOT_PATH,
|
|
48
67
|
# La documentación la sirve `docs.py`, con su propia página y su protección.
|
|
49
68
|
docs_url=None,
|
|
50
69
|
redoc_url=None,
|
|
@@ -120,6 +139,9 @@ def create_app() -> FastAPI:
|
|
|
120
139
|
|
|
121
140
|
_openapi_with_groups(app)
|
|
122
141
|
_extensions(app)
|
|
142
|
+
# El último, para que quede por fuera de todo, también de lo que añada `extensions`: el resto
|
|
143
|
+
# ya ve el `root_path` de la petición
|
|
144
|
+
app.add_middleware(RootPath, root_path=ROOT_PATH)
|
|
123
145
|
return app
|
|
124
146
|
|
|
125
147
|
|
|
@@ -163,7 +185,7 @@ def _openapi_with_groups(app: FastAPI) -> None:
|
|
|
163
185
|
)
|
|
164
186
|
if not secured:
|
|
165
187
|
docs.without_auth(schema)
|
|
166
|
-
docs.absolute_token_urls(schema,
|
|
188
|
+
docs.absolute_token_urls(schema, ROOT_PATH)
|
|
167
189
|
schema["x-tagGroups"] = docs.tag_groups(schema, docs.SCHEMAS)
|
|
168
190
|
schema["tags"] = docs.tag_names(schema, docs.SCHEMAS)
|
|
169
191
|
built[secured] = schema
|
|
@@ -154,7 +154,7 @@ def absolute_token_urls(openapi: dict, root_path: str) -> dict:
|
|
|
154
154
|
relativo, y el navegador acaba resolviéndolos contra la página. Desde `/docs/swagger` el login
|
|
155
155
|
iba a `/docs/auth/docslogin`, o a `/api/docs/auth/docslogin` detrás del proxy: un 404 en los
|
|
156
156
|
dos casos. Se completan aquí y no en `dependencies.py` porque es aquí donde se conoce el
|
|
157
|
-
`
|
|
157
|
+
`ROOT_PATH` de `config.py`, que no se puede importar desde las dependencias.
|
|
158
158
|
"""
|
|
159
159
|
raíz = root_path.rstrip("/")
|
|
160
160
|
for esquema in openapi.get("components", {}).get("securitySchemes", {}).values():
|
|
@@ -623,10 +623,22 @@ class RoutersGenerator(BaseGenerator):
|
|
|
623
623
|
'columnas': uniones[anidada.relation],
|
|
624
624
|
'inversa': inversas.get(anidada.relation),
|
|
625
625
|
}
|
|
626
|
+
# Con qué se puede pedir un lote de filas por cada columna: una lista (`in_`), un rango
|
|
627
|
+
# (`min_`/`max_`) o nada. Es el `filter_fields` del DAO, sin las columnas que el DAO no
|
|
628
|
+
# deja filtrar (cifradas y hasheadas).
|
|
629
|
+
filtros = {}
|
|
630
|
+
for columna in model_info['columns']:
|
|
631
|
+
if columna.get('encrypt') or columna.get('hash'):
|
|
632
|
+
continue
|
|
633
|
+
if 'in' in columna['filter_fields']:
|
|
634
|
+
filtros[columna['name']] = 'in'
|
|
635
|
+
elif 'range' in columna['filter_fields']:
|
|
636
|
+
filtros[columna['name']] = 'range'
|
|
626
637
|
tablas.append({
|
|
627
638
|
'name': model_info['name'],
|
|
628
639
|
'tablename': entity.tablename,
|
|
629
640
|
'clave': list(recurso.primary_key),
|
|
641
|
+
'filtros': filtros,
|
|
630
642
|
'crea': sorted(a.relation for a in crea),
|
|
631
643
|
'modifica': sorted(a.relation for a in modifica),
|
|
632
644
|
'relaciones': [relaciones[nombre] for nombre in sorted(relaciones)],
|
|
@@ -6,14 +6,15 @@ from fastapi import Body{% if has_auth %}, Depends{% endif %}
|
|
|
6
6
|
|
|
7
7
|
{% if has_auth %}
|
|
8
8
|
{{ shared.auth_imports(has_auth, auth_import, database_import) }}
|
|
9
|
-
from {{ auth_import }} import build_owner_rules, owner_violation
|
|
10
9
|
{% if anidadas.get('create_many') %}
|
|
11
|
-
from {{ anidados_import }} import
|
|
10
|
+
from {{ anidados_import }} import despues_de_crear_varias, en_create, exigir_permisos, traducir_anidada
|
|
11
|
+
{% else %}
|
|
12
|
+
from {{ anidados_import }} import despues_de_crear_varias
|
|
12
13
|
{% endif %}
|
|
13
14
|
{% elif anidadas.get('create_many') %}
|
|
14
15
|
from {{ anidados_import }} import traducir_anidada
|
|
15
16
|
{% endif %}
|
|
16
|
-
from {{ database_import }} import {{ facade }}, {{ model.name }}Read, {{ model.name }}Create
|
|
17
|
+
from {{ database_import }} import {{ facade }}, {{ model.name }}Read, {{ model.name }}Create
|
|
17
18
|
from {{ resources_import }} import APIResponse
|
|
18
19
|
from .docs import CREATE_MANY_LIMIT, CREATE_MANY_RESPONSES, CREATE_MANY_SUMMARY, CREATE_MANY_DESCRIPTION
|
|
19
20
|
from ... import {{ model.tablename }}_router
|
|
@@ -38,10 +39,6 @@ async def {{ model.tablename }}_create_many(
|
|
|
38
39
|
Crea varios {{ model.name }} en una sola transacción: o todos, o ninguno.
|
|
39
40
|
"""
|
|
40
41
|
{% if has_auth %}
|
|
41
|
-
propias = build_owner_rules(
|
|
42
|
-
token, "{{ schema_name }}", get_model_by_name, RLS,
|
|
43
|
-
model=get_model_by_name("{{ schema_name }}.{{ model.tablename }}"),
|
|
44
|
-
)
|
|
45
42
|
{% if anidadas.get('create_many') %}
|
|
46
43
|
# Lo que cada registro crea en otras tablas pide el permiso de cada una, antes de escribir
|
|
47
44
|
# nada: `field` lleva la posición en la lista (`3.etiquetas.0`).
|
|
@@ -67,23 +64,11 @@ async def {{ model.tablename }}_create_many(
|
|
|
67
64
|
session=session,
|
|
68
65
|
)
|
|
69
66
|
{% endif %}
|
|
70
|
-
# Como en `create`: cada fila tiene que ser del usuario, y
|
|
71
|
-
#
|
|
72
|
-
# transacción que la escritura,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if await {{ dao }}.find(
|
|
76
|
-
{% for column in model.columns if column.constraints.get('primary_key', False) %}
|
|
77
|
-
{{ column.name }}=creado.{{ column.name }},
|
|
78
|
-
{% endfor %}
|
|
79
|
-
rls=propias,
|
|
80
|
-
session=session,
|
|
81
|
-
) is None:
|
|
82
|
-
raise owner_violation()
|
|
83
|
-
{% if anidadas.get('create_many') %}
|
|
84
|
-
for posicion, (registro, creado) in enumerate(zip(registros, creados)):
|
|
85
|
-
await despues_de_crear(token, "{{ model.tablename }}", creado, registro, session, f"{posicion}.")
|
|
86
|
-
{% endif %}
|
|
67
|
+
# Como en `create`: cada fila tiene que ser del usuario, y lo que se creó anidado con
|
|
68
|
+
# ella también; si una no lo es, la excepción deshace todo el lote. Va en la misma
|
|
69
|
+
# transacción que la escritura, y es una consulta por tabla y nivel para todo el lote,
|
|
70
|
+
# no una por registro (`field` lleva la posición: `3`, `3.etiquetas`).
|
|
71
|
+
await despues_de_crear_varias(token, "{{ model.tablename }}", creados, registros, session)
|
|
87
72
|
|
|
88
73
|
return APIResponse.success(
|
|
89
74
|
data=creados,
|
|
@@ -97,6 +97,15 @@ CLAVES: Dict[str, Tuple[str, ...]] = {
|
|
|
97
97
|
{% endfor %}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
#: Cómo se pide un lote de filas por cada columna: `in` (`in_<columna>`), `range`
|
|
101
|
+
#: (`min_<columna>`/`max_<columna>`); la que no está no se puede pedir por lote. Es el
|
|
102
|
+
#: `filter_fields` del DAO.
|
|
103
|
+
FILTROS: Dict[str, Dict[str, str]] = {
|
|
104
|
+
{% for t in tablas %}
|
|
105
|
+
"{{ t.tablename }}": {{ t.filtros | tojson }},
|
|
106
|
+
{% endfor %}
|
|
107
|
+
}
|
|
108
|
+
|
|
100
109
|
#: El nombre de cada tabla en los mensajes: el de su clase.
|
|
101
110
|
NOMBRES: Dict[str, str] = {
|
|
102
111
|
{% for t in tablas %}
|
|
@@ -364,33 +373,126 @@ async def _relacionadas(tabla: str, fila: Any, relación: str, session) -> list:
|
|
|
364
373
|
return await DAOS[destino].find_many(**filtro, session=session)
|
|
365
374
|
|
|
366
375
|
|
|
367
|
-
async def
|
|
368
|
-
"""
|
|
376
|
+
async def _relacionadas_de(tabla: str, filas: List[Any], relación: str, session) -> List[list]:
|
|
377
|
+
"""
|
|
378
|
+
`_relacionadas` de varias filas a la vez, en su orden: una consulta con `in_` por cada columna de
|
|
379
|
+
la unión, repartida después por fila. Si alguna columna no tiene `in_`, una consulta por fila.
|
|
380
|
+
"""
|
|
381
|
+
destino, _, columnas, _ = RELACIONES[tabla][relación]
|
|
382
|
+
if len(filas) == 1 or any(FILTROS[destino].get(columna_destino) != "in" for columna_destino, _ in columnas):
|
|
383
|
+
return [await _relacionadas(tabla, fila, relación, session) for fila in filas]
|
|
384
|
+
uniones = [tuple(getattr(fila, columna_origen) for _, columna_origen in columnas) for fila in filas]
|
|
385
|
+
con_valor = [union for union in uniones if None not in union]
|
|
386
|
+
if not con_valor:
|
|
387
|
+
return [[] for _ in filas]
|
|
388
|
+
filtro = {
|
|
389
|
+
f"in_{columna_destino}": list({union[i] for union in con_valor})
|
|
390
|
+
for i, (columna_destino, _) in enumerate(columnas)
|
|
391
|
+
}
|
|
392
|
+
por_union: Dict[tuple, list] = {}
|
|
393
|
+
for hija in await DAOS[destino].find_many(**filtro, session=session):
|
|
394
|
+
por_union.setdefault(tuple(getattr(hija, columna_destino) for columna_destino, _ in columnas), []).append(hija)
|
|
395
|
+
return [por_union.get(union, []) for union in uniones]
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _tupla(tabla: str, fila: Any) -> tuple:
|
|
399
|
+
return tuple(getattr(fila, columna) for columna in CLAVES[tabla])
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
async def _primera_ajena(tabla: str, filas: List[Any], propias: list, session) -> Optional[int]:
|
|
403
|
+
"""
|
|
404
|
+
La posición de la primera de `filas` que la regla no alcanza, o `None` si las alcanza todas.
|
|
405
|
+
|
|
406
|
+
Una consulta para todas, no una por fila: con una clave simple con `in_`, cuántas de esas claves
|
|
407
|
+
alcanza la regla; si no, las filas que alcanza entre las que acotan la lista o el rango de cada
|
|
408
|
+
columna de la clave (puede traer alguna más; lo que cuenta es que estén todas). La que no aparece
|
|
409
|
+
—ajena, o con un valor que vuelve de la base de datos con otra forma, como una fecha con zona— se
|
|
410
|
+
busca sola con la regla, que es la comprobación exacta: el lote solo ahorra las que confirma.
|
|
411
|
+
"""
|
|
412
|
+
if not filas:
|
|
413
|
+
return None
|
|
414
|
+
dao, columnas = DAOS[tabla], CLAVES[tabla]
|
|
415
|
+
if len(columnas) == 1 and FILTROS[tabla].get(columnas[0]) == "in":
|
|
416
|
+
valores = list({getattr(fila, columnas[0]) for fila in filas})
|
|
417
|
+
if await dao.count(**{f"in_{columnas[0]}": valores}, rls=propias, session=session) == len(valores):
|
|
418
|
+
return None
|
|
419
|
+
acotado: Dict[str, Any] = {}
|
|
420
|
+
for columna in columnas:
|
|
421
|
+
valores = [getattr(fila, columna) for fila in filas]
|
|
422
|
+
if FILTROS[tabla].get(columna) == "in":
|
|
423
|
+
acotado[f"in_{columna}"] = list(set(valores))
|
|
424
|
+
elif FILTROS[tabla].get(columna) == "range":
|
|
425
|
+
try:
|
|
426
|
+
acotado[f"min_{columna}"], acotado[f"max_{columna}"] = min(valores), max(valores)
|
|
427
|
+
except TypeError: # valores que no se comparan entre sí (fechas con y sin zona)
|
|
428
|
+
pass
|
|
429
|
+
alcanzadas = set()
|
|
430
|
+
if acotado:
|
|
431
|
+
alcanzadas = {_tupla(tabla, fila) for fila in await dao.find_many(**acotado, rls=propias, session=session)}
|
|
432
|
+
for posición, fila in enumerate(filas):
|
|
433
|
+
if _tupla(tabla, fila) in alcanzadas:
|
|
434
|
+
continue
|
|
435
|
+
if await dao.find(**_clave_fila(tabla, fila), rls=propias, session=session) is None:
|
|
436
|
+
return posición
|
|
437
|
+
return None
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
async def _comprobar_filas(token: Any, tabla: str, filas: List[Tuple[Any, Dict[str, dict], str]], session, cache) -> None:
|
|
441
|
+
"""
|
|
442
|
+
Filas de `tabla` recién creadas, cada una con lo que se creó con ella (`forma`) y su `field`: tienen
|
|
443
|
+
que ser del usuario (403 `OWNER`), y lo de debajo también. Una consulta por tabla y nivel para
|
|
444
|
+
todas, no una por fila.
|
|
445
|
+
"""
|
|
369
446
|
propias = _propias(token, tabla, cache)
|
|
370
|
-
if propias
|
|
371
|
-
|
|
372
|
-
|
|
447
|
+
if propias:
|
|
448
|
+
ajena = await _primera_ajena(tabla, [fila for fila, _, _ in filas], propias, session)
|
|
449
|
+
if ajena is not None:
|
|
450
|
+
raise _en(owner_violation(), filas[ajena][2])
|
|
451
|
+
await _bajar(token, tabla, [(fila, forma, f"{campo}.") for fila, forma, campo in filas], session, cache)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
async def _bajar(token: Any, tabla: str, padres: List[Tuple[Any, Dict[str, dict], str]], session, cache) -> None:
|
|
455
|
+
"""
|
|
456
|
+
Lo que se creó bajo `padres` —filas de `tabla`, con su `forma` y el prefijo del `field` de lo suyo—,
|
|
457
|
+
relación a relación: las hijas de todos los padres en una consulta, y comprobadas todas juntas.
|
|
458
|
+
"""
|
|
459
|
+
for relación in sorted({relación for _, forma, _ in padres for relación in forma}):
|
|
373
460
|
destino = RELACIONES[tabla][relación][0]
|
|
374
|
-
|
|
461
|
+
con_ella = [(fila, forma[relación], prefijo) for fila, forma, prefijo in padres if relación in forma]
|
|
462
|
+
if not any(_propias(token, destino, cache) or _alcanza(token, destino, sub, cache) for _, sub, _ in con_ella):
|
|
375
463
|
continue
|
|
376
|
-
|
|
377
|
-
|
|
464
|
+
hijas = await _relacionadas_de(tabla, [fila for fila, _, _ in con_ella], relación, session)
|
|
465
|
+
await _comprobar_filas(token, destino, [
|
|
466
|
+
(hija, sub, f"{prefijo}{relación}")
|
|
467
|
+
for (_, sub, prefijo), suyas in zip(con_ella, hijas)
|
|
468
|
+
for hija in suyas
|
|
469
|
+
], session, cache)
|
|
378
470
|
|
|
379
471
|
|
|
380
472
|
async def despues_de_crear(token: Any, tabla: str, creada: Any, cuerpo: Any, session, prefijo: str = "") -> None:
|
|
381
473
|
"""
|
|
382
|
-
Después de un `create` (`POST`,
|
|
383
|
-
|
|
474
|
+
Después de un `create` (`POST`, el `create` de `_tx`), en su transacción: lo que se creó anidado en
|
|
475
|
+
`creada` tiene que ser del usuario. La fila de arriba la comprueba el endpoint.
|
|
384
476
|
|
|
385
477
|
Solo cuesta consultas si alguna tabla de lo creado tiene reglas de propietario.
|
|
386
478
|
"""
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
479
|
+
await _bajar(token, tabla, [(creada, _forma(tabla, cuerpo), prefijo)], session, {})
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
async def despues_de_crear_varias(token: Any, tabla: str, creadas: List[Any], cuerpos: List[Any], session) -> None:
|
|
483
|
+
"""
|
|
484
|
+
Después de `create_many` (`/bulk`), en su transacción: cada fila creada y lo que se creó anidado
|
|
485
|
+
con ella tiene que ser del usuario. El `field` de un 403 lleva la posición en la lista (`3`, o
|
|
486
|
+
`3.etiquetas` si es lo anidado).
|
|
487
|
+
|
|
488
|
+
Todo el lote a la vez: una consulta por tabla y nivel, no una por registro. Era un `find` por
|
|
489
|
+
fila, y con mil registros por petición eran mil viajes a la base de datos. Solo cuesta consultas
|
|
490
|
+
si alguna tabla de lo creado tiene reglas de propietario.
|
|
491
|
+
"""
|
|
492
|
+
await _comprobar_filas(token, tabla, [
|
|
493
|
+
(creada, _forma(tabla, cuerpo), str(posición))
|
|
494
|
+
for posición, (creada, cuerpo) in enumerate(zip(creadas, cuerpos))
|
|
495
|
+
], session, {})
|
|
394
496
|
|
|
395
497
|
|
|
396
498
|
@dataclass
|
|
@@ -479,8 +581,10 @@ async def despues_de_escribir(token: Any, plan: Plan, session) -> None:
|
|
|
479
581
|
fila = await DAOS[grupo.tabla].find(**ahora, session=session)
|
|
480
582
|
if fila is None:
|
|
481
583
|
continue
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
584
|
+
nuevas = [
|
|
585
|
+
(hija, grupo.forma, grupo.campo)
|
|
586
|
+
for hija in await _relacionadas(grupo.tabla, fila, grupo.relacion, session)
|
|
587
|
+
if _congelada(_clave_fila(destino, hija)) not in grupo.antes
|
|
588
|
+
]
|
|
589
|
+
await _comprobar_filas(token, destino, nuevas, session, plan.cache)
|
|
486
590
|
{% endif %}
|
|
@@ -197,7 +197,7 @@ def row(info: dict, t: Traductor) -> Interfaz:
|
|
|
197
197
|
else:
|
|
198
198
|
tipo = t.tipo(c)
|
|
199
199
|
campos.append(Campo(c['name'], _nulable(tipo) if c['nullable'] else tipo, doc=t.doc(c), formato=formato(c)))
|
|
200
|
-
if info.get('
|
|
200
|
+
if info.get('has_hierarchy'):
|
|
201
201
|
campos += [
|
|
202
202
|
Campo('tree_level', 'number | null', opcional=True),
|
|
203
203
|
Campo('descendants', f"{info['name']}Row[] | null", opcional=True),
|
|
@@ -43,6 +43,12 @@ from .aggregation import (
|
|
|
43
43
|
GROUP_BY_DATETIME_TYPES,
|
|
44
44
|
)
|
|
45
45
|
|
|
46
|
+
from .utils.hierarchy import (
|
|
47
|
+
HierarchyResolver,
|
|
48
|
+
HierarchyConfig,
|
|
49
|
+
TreeMode,
|
|
50
|
+
TreeNode,
|
|
51
|
+
)
|
|
46
52
|
|
|
47
53
|
|
|
48
54
|
__all__ = [
|
|
@@ -79,4 +85,8 @@ __all__ = [
|
|
|
79
85
|
'OPERATION_FUNCTIONS',
|
|
80
86
|
'GROUP_BY_FORBIDDEN_TYPES',
|
|
81
87
|
'GROUP_BY_DATETIME_TYPES',
|
|
88
|
+
'HierarchyResolver',
|
|
89
|
+
'HierarchyConfig',
|
|
90
|
+
'TreeMode',
|
|
91
|
+
'TreeNode',
|
|
82
92
|
]
|