abstra 3.23.7__py3-none-any.whl → 3.23.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {abstra-3.23.7.dist-info → abstra-3.23.9.dist-info}/METADATA +1 -1
- {abstra-3.23.7.dist-info → abstra-3.23.9.dist-info}/RECORD +196 -191
- abstra_internals/consts/filepaths.py +1 -1
- abstra_internals/controllers/codebase.py +9 -18
- abstra_internals/controllers/codebase_events.py +6 -4
- abstra_internals/controllers/main.py +56 -67
- abstra_internals/entities/forms/template.py +84 -5
- abstra_internals/interface/cli/deploy.py +1 -3
- abstra_internals/modules_test.py +2 -2
- abstra_internals/repositories/linter/repository.py +9 -40
- abstra_internals/repositories/linter/rules/__init__.py +2 -0
- abstra_internals/repositories/linter/rules/big_py_files.py +40 -0
- abstra_internals/repositories/linter/rules/big_py_files_test.py +93 -0
- abstra_internals/repositories/linter/rules/duplicate_package_in_requirements.py +16 -4
- abstra_internals/repositories/linter/rules/duplicate_package_in_requirements_test.py +24 -3
- abstra_internals/repositories/linter/rules/env_in_bundle.py +3 -6
- abstra_internals/repositories/linter/rules/env_in_bundle_test.py +4 -3
- abstra_internals/repositories/linter/rules/missing_packages_in_requirements.py +12 -1
- abstra_internals/repositories/linter/rules/syntax_errors.py +2 -19
- abstra_internals/repositories/linter/rules/venv_in_bundle.py +3 -2
- abstra_internals/server/routes/requirements.py +7 -3
- abstra_internals/server/routes/workspace.py +0 -10
- abstra_internals/services/file_watcher.py +17 -14
- abstra_internals/services/fs.py +144 -99
- abstra_internals/services/fs_test.py +303 -8
- abstra_internals/services/requirements.py +271 -81
- abstra_internals/services/requirements_test.py +528 -164
- abstra_internals/templates/__init__.py +3 -1
- abstra_internals/utils/file.py +62 -0
- abstra_internals/utils/platform.py +5 -0
- abstra_statics/dist/assets/{AbstraButton.vue_vue_type_script_setup_true_lang.9812dba9.js → AbstraButton.vue_vue_type_script_setup_true_lang.e74c1d9b.js} +2 -2
- abstra_statics/dist/assets/AbstraLogo.vue_vue_type_script_setup_true_lang.f9d9018b.js +2 -0
- abstra_statics/dist/assets/{ApiKeys.902caf82.js → ApiKeys.1d2b9051.js} +2 -2
- abstra_statics/dist/assets/App.24328bec.js +2 -0
- abstra_statics/dist/assets/App.vue_vue_type_style_index_0_lang.13b52476.js +2 -0
- abstra_statics/dist/assets/BaseLayout.e2546be2.js +2 -0
- abstra_statics/dist/assets/{Billing.877a4614.js → Billing.ed96ff6d.js} +2 -2
- abstra_statics/dist/assets/{Breadcrumb.f312111a.js → Breadcrumb.21c760be.js} +2 -2
- abstra_statics/dist/assets/{Builds.a2c45c39.js → Builds.e0882931.js} +2 -2
- abstra_statics/dist/assets/{Card.5f504e7b.js → Card.714646f7.js} +5 -5
- abstra_statics/dist/assets/{CircularLoading.6f511e29.js → CircularLoading.5ac43298.js} +2 -2
- abstra_statics/dist/assets/CloseCircleOutlined.04918c3d.js +2 -0
- abstra_statics/dist/assets/ConnectorsView.78da900d.js +2 -0
- abstra_statics/dist/assets/{ConnectorsView.17764dde.css → ConnectorsView.aeb00ce8.css} +1 -1
- abstra_statics/dist/assets/ConsoleOmniChat.vue_vue_type_script_setup_true_lang.d7b17e09.js +2 -0
- abstra_statics/dist/assets/ContentLayout.3a69fd49.js +2 -0
- abstra_statics/dist/assets/{CrudView.5a642b48.js → CrudView.77b58a5a.js} +2 -2
- abstra_statics/dist/assets/DocsButton.vue_vue_type_script_setup_true_lang.836e8f5e.js +2 -0
- abstra_statics/dist/assets/{EditorLogin.d2224782.js → EditorLogin.4d61e44f.js} +2 -2
- abstra_statics/dist/assets/{EditorsView.5e769180.js → EditorsView.08a5cd3a.js} +2 -2
- abstra_statics/dist/assets/EnvVars.2f33a113.js +2 -0
- abstra_statics/dist/assets/{Error.dd899e38.js → Error.8a6cfa01.js} +2 -2
- abstra_statics/dist/assets/ExclamationCircleOutlined.93703670.js +2 -0
- abstra_statics/dist/assets/Files.6d07f9be.js +2 -0
- abstra_statics/dist/assets/Form.c1a08fa6.js +2 -0
- abstra_statics/dist/assets/{FormRunner.1c6a88dd.js → FormRunner.a00b296a.js} +2 -2
- abstra_statics/dist/assets/Home.332f8b44.js +2 -0
- abstra_statics/dist/assets/Home.964d824d.js +2 -0
- abstra_statics/dist/assets/{Live.a691b0eb.js → Live.ee0e73dd.js} +2 -2
- abstra_statics/dist/assets/LoadingContainer.cbb8e068.js +2 -0
- abstra_statics/dist/assets/LoadingOutlined.644899c2.js +2 -0
- abstra_statics/dist/assets/{Login.f96858b0.js → Login.0182140b.js} +2 -2
- abstra_statics/dist/assets/Login.ebf77147.js +2 -0
- abstra_statics/dist/assets/{Login.vue_vue_type_script_setup_true_lang.7d56cca3.js → Login.vue_vue_type_script_setup_true_lang.721bd893.js} +2 -2
- abstra_statics/dist/assets/Logo.c74119c7.js +2 -0
- abstra_statics/dist/assets/{Logs.8426d360.js → Logs.7a458aba.js} +2 -2
- abstra_statics/dist/assets/{LogsController.318117fd.js → LogsController.e7ad74db.js} +2 -2
- abstra_statics/dist/assets/Main.a2c84f7b.js +2 -0
- abstra_statics/dist/assets/{MockForm.f8600bec.js → MockForm.50f237ad.js} +2 -2
- abstra_statics/dist/assets/Navbar.0cf9e650.js +2 -0
- abstra_statics/dist/assets/{NewEditor.d3300cf0.css → NewEditor.0044878f.css} +1 -1
- abstra_statics/dist/assets/NewEditor.4519d8ac.js +8 -0
- abstra_statics/dist/assets/OidcLoginCallback.073de6bd.js +2 -0
- abstra_statics/dist/assets/{OidcLogoutCallback.485fb0b9.js → OidcLogoutCallback.e474320b.js} +2 -2
- abstra_statics/dist/assets/{OmniChat.097bec71.js → OmniChat.1a6ad90c.js} +4 -4
- abstra_statics/dist/assets/{OmniChat.7660057c.css → OmniChat.8a35a659.css} +1 -1
- abstra_statics/dist/assets/{OnboardingView.c9a3343e.js → OnboardingView.c4e859f1.js} +2 -2
- abstra_statics/dist/assets/{Organization.0833b7fe.js → Organization.432776d6.js} +2 -2
- abstra_statics/dist/assets/Organizations.cd6f9f61.js +2 -0
- abstra_statics/dist/assets/{PhArrowCounterClockwise.vue.aaa06bc0.js → PhArrowCounterClockwise.vue.72ed46a0.js} +2 -2
- abstra_statics/dist/assets/{PhArrowSquareOut.vue.ee4af292.js → PhArrowSquareOut.vue.ddb450f1.js} +2 -2
- abstra_statics/dist/assets/{PhBookBookmark.vue.681c5036.js → PhBookBookmark.vue.4a6ba053.js} +2 -2
- abstra_statics/dist/assets/{PhChats.vue.d61c3615.js → PhChats.vue.870bbba9.js} +2 -2
- abstra_statics/dist/assets/{PhClockCounterClockwise.vue.0457e9b2.js → PhClockCounterClockwise.vue.123f0e9b.js} +2 -2
- abstra_statics/dist/assets/{PhCopy.vue.391b0ef7.js → PhCopy.vue.70df4792.js} +2 -2
- abstra_statics/dist/assets/{PhCopySimple.vue.e887b43c.js → PhCopySimple.vue.f5246c24.js} +2 -2
- abstra_statics/dist/assets/{PhCube.vue.d070a184.js → PhCube.vue.8900248a.js} +2 -2
- abstra_statics/dist/assets/{PhDotsThreeVertical.vue.f4b60771.js → PhDotsThreeVertical.vue.14516e2e.js} +2 -2
- abstra_statics/dist/assets/{PhDownloadSimple.vue.3444d06b.js → PhDownloadSimple.vue.f8dc6a01.js} +2 -2
- abstra_statics/dist/assets/{PhFolderPlus.vue.d5788203.js → PhFolderPlus.vue.65855487.js} +2 -2
- abstra_statics/dist/assets/{PhGear.vue.e2b120bb.js → PhGear.vue.172823d3.js} +2 -2
- abstra_statics/dist/assets/{PhKey.vue.cf1e08ca.js → PhKey.vue.9d78ceda.js} +2 -2
- abstra_statics/dist/assets/{PhPencil.vue.20f1b3c4.js → PhPencil.vue.f326c6d0.js} +2 -2
- abstra_statics/dist/assets/{PhPencilSimple.vue.ec2125f5.js → PhPencilSimple.vue.31afd9b3.js} +2 -2
- abstra_statics/dist/assets/{PhPencilSimpleLine.vue.22e75a5a.js → PhPencilSimpleLine.vue.394969fe.js} +2 -2
- abstra_statics/dist/assets/{PhRocket.vue.27c6f935.js → PhRocket.vue.b69be43b.js} +2 -2
- abstra_statics/dist/assets/{PhSignOut.vue.61b63ec0.js → PhSignOut.vue.6be07f90.js} +2 -2
- abstra_statics/dist/assets/{PhSparkle.vue.fd6a9ad7.js → PhSparkle.vue.ca8d1014.js} +2 -2
- abstra_statics/dist/assets/{PhUserList.vue.abdd6da1.js → PhUserList.vue.43e7a38a.js} +2 -2
- abstra_statics/dist/assets/{PhUsersThree.vue.85d1a1f0.js → PhUsersThree.vue.fb26521a.js} +2 -2
- abstra_statics/dist/assets/{PhWebhooksLogo.vue.00b65b2c.js → PhWebhooksLogo.vue.ea667ed2.js} +2 -2
- abstra_statics/dist/assets/{PlayerConfigProvider.10f46997.js → PlayerConfigProvider.5aec8c16.js} +2 -2
- abstra_statics/dist/assets/{PlayerNavbar.f2f66852.js → PlayerNavbar.21883569.js} +2 -2
- abstra_statics/dist/assets/Project.a987ec46.js +2 -0
- abstra_statics/dist/assets/ProjectLogin.583ce83f.js +2 -0
- abstra_statics/dist/assets/{ProjectSettings.50027450.js → ProjectSettings.b949f40f.js} +2 -2
- abstra_statics/dist/assets/{ProjectsView.107f5e34.js → ProjectsView.7de28b8a.js} +2 -2
- abstra_statics/dist/assets/{SaveButton.cd025dae.js → SaveButton.f6870993.js} +2 -2
- abstra_statics/dist/assets/{files.f66880c3.js → ScrollArea.vue_vue_type_script_setup_true_lang.21ea9f38.js} +2 -2
- abstra_statics/dist/assets/{Sidebar.c3d5d187.js → Sidebar.1250b960.js} +2 -2
- abstra_statics/dist/assets/Sql.3cdc910a.css +1 -0
- abstra_statics/dist/assets/Sql.a8abfb57.js +5 -0
- abstra_statics/dist/assets/Steps.2f177a1f.js +2 -0
- abstra_statics/dist/assets/{TableEditor.7b07ece4.js → TableEditor.25046840.js} +2 -2
- abstra_statics/dist/assets/Tables.f0ea43f5.js +2 -0
- abstra_statics/dist/assets/{TablesDiagram.6736c045.js → TablesDiagram.eb43ee41.js} +3 -3
- abstra_statics/dist/assets/TablesTabs.vue_vue_type_script_setup_true_lang.a35b4471.js +2 -0
- abstra_statics/dist/assets/Tasks.5fde94ea.js +2 -0
- abstra_statics/dist/assets/{UploadOutlined.732440a5.js → UploadOutlined.3828d031.js} +2 -2
- abstra_statics/dist/assets/{View.283e52c1.js → View.f0be1038.js} +2 -2
- abstra_statics/dist/assets/View.vue_vue_type_script_setup_true_lang.bddece9c.js +2 -0
- abstra_statics/dist/assets/{Watermark.6076ef47.js → Watermark.1655dad8.js} +2 -2
- abstra_statics/dist/assets/{WebEditor.6a012d5b.js → WebEditor.f6ba39c6.js} +2 -2
- abstra_statics/dist/assets/WidgetPreview.70e3ecb3.js +2 -0
- abstra_statics/dist/assets/{ant-design.4302db30.js → ant-design.1cc60cde.js} +2 -2
- abstra_statics/dist/assets/{apiKey.1c96dd66.js → apiKey.49b8decf.js} +2 -2
- abstra_statics/dist/assets/asyncComputed.92146382.js +2 -0
- abstra_statics/dist/assets/{build.656c5601.js → build.261de82e.js} +2 -2
- abstra_statics/dist/assets/{colorHelpers.2a607581.js → colorHelpers.59e5ee56.js} +2 -2
- abstra_statics/dist/assets/{console.9b13e1da.js → console.0548c6a0.js} +2 -2
- abstra_statics/dist/assets/constants.f352d89b.js +2 -0
- abstra_statics/dist/assets/contracts.generated.ac0bb8ea.js +2 -0
- abstra_statics/dist/assets/{cssMode.6d17ca95.js → cssMode.c55f29d9.js} +2 -2
- abstra_statics/dist/assets/datetime.780c5d6b.js +2 -0
- abstra_statics/dist/assets/dayjs.b139fc88.js +2 -0
- abstra_statics/dist/assets/editor.f7e5ca32.js +2 -0
- abstra_statics/dist/assets/editor.main.eea24677.js +2 -0
- abstra_statics/dist/assets/fetch.e3c8dd68.js +2 -0
- abstra_statics/dist/assets/{folder.9092348a.js → folder.934b13ce.js} +2 -2
- abstra_statics/dist/assets/{freemarker2.82f2cb8c.js → freemarker2.72aea997.js} +2 -2
- abstra_statics/dist/assets/{handlebars.36ec2a3c.js → handlebars.607ed329.js} +2 -2
- abstra_statics/dist/assets/{html.845da565.js → html.1cebc021.js} +3 -3
- abstra_statics/dist/assets/{htmlMode.980f76b4.js → htmlMode.b3ab207b.js} +2 -2
- abstra_statics/dist/assets/{index.0f357aec.js → index.2efe5ae2.js} +2 -2
- abstra_statics/dist/assets/{index.55d10b71.js → index.424d5056.js} +2 -2
- abstra_statics/dist/assets/{index.1e12c884.js → index.61710ff9.js} +2 -2
- abstra_statics/dist/assets/{index.a2b9d34b.js → index.8ea8b90b.js} +2 -2
- abstra_statics/dist/assets/{index.1551abd6.js → index.a5ddebc4.js} +3 -3
- abstra_statics/dist/assets/{index.5dbe93c3.js → index.cef986ac.js} +3 -3
- abstra_statics/dist/assets/{index.81a2ae08.js → index.d73f307c.js} +2 -2
- abstra_statics/dist/assets/{index.b3b62f71.js → index.ded0f9ab.js} +2 -2
- abstra_statics/dist/assets/{index.4ecba4f7.js → index.e189c6ed.js} +2 -2
- abstra_statics/dist/assets/{javascript.b0154182.js → javascript.595850d9.js} +3 -3
- abstra_statics/dist/assets/{jsonMode.f86e9042.js → jsonMode.e3ed1113.js} +2 -2
- abstra_statics/dist/assets/{jwt-decode.esm.d86c27e0.js → jwt-decode.esm.1b301f74.js} +8 -8
- abstra_statics/dist/assets/linters.0018d44c.js +2 -0
- abstra_statics/dist/assets/{liquid.029287f8.js → liquid.3ef83be8.js} +3 -3
- abstra_statics/dist/assets/member.3c3a769a.js +2 -0
- abstra_statics/dist/assets/{metadata.18d0a278.js → metadata.e6e62729.js} +2 -2
- abstra_statics/dist/assets/omniChatStore.8902e4de.js +8 -0
- abstra_statics/dist/assets/{organization.8b2c1c53.js → organization.c92a9190.js} +2 -2
- abstra_statics/dist/assets/player.82dc9e2f.js +2 -0
- abstra_statics/dist/assets/{plotly.min.10467de2.js → plotly.min.ac699f04.js} +2 -2
- abstra_statics/dist/assets/polling.a3bae209.js +2 -0
- abstra_statics/dist/assets/{project.33809d47.js → project.48a235c1.js} +2 -2
- abstra_statics/dist/assets/{python.ee23fd86.js → python.b69cfb2a.js} +2 -2
- abstra_statics/dist/assets/{razor.4ae6d2a2.js → razor.0f42fef9.js} +2 -2
- abstra_statics/dist/assets/{record.d087b37e.js → record.b96b0275.js} +2 -2
- abstra_statics/dist/assets/{redirect.c06a7828.js → redirect.ebab71ed.js} +2 -2
- abstra_statics/dist/assets/repository.4920fb3b.js +2 -0
- abstra_statics/dist/assets/{repository.6fa74dff.js → repository.c0dc4e22.js} +2 -2
- abstra_statics/dist/assets/router.8882099a.js +2 -0
- abstra_statics/dist/assets/{router.7936fd78.js → router.99a53337.js} +3 -3
- abstra_statics/dist/assets/string.4796d44a.js +2 -0
- abstra_statics/dist/assets/{tables.d580be9d.js → tables.13d62f0c.js} +2 -2
- abstra_statics/dist/assets/tasksController.3255c760.js +4 -0
- abstra_statics/dist/assets/{toggleHighContrast.510bdb1d.js → toggleHighContrast.cb5e834a.js} +7 -7
- abstra_statics/dist/assets/{tsMode.5c0f732d.js → tsMode.95528ab9.js} +2 -2
- abstra_statics/dist/assets/{typescript.0643a053.js → typescript.b90bd43d.js} +3 -3
- abstra_statics/dist/assets/url.84e62ca8.js +2 -0
- abstra_statics/dist/assets/useCodebaseEvents.c47ad36d.js +2 -0
- abstra_statics/dist/assets/userStore.843da693.js +2 -0
- abstra_statics/dist/assets/uuid.58d26ff2.js +2 -0
- abstra_statics/dist/assets/{vue-flow-background.011d27ef.js → vue-flow-background.6b5566a8.js} +2 -2
- abstra_statics/dist/assets/{vue-quill.esm-bundler.487756a5.js → vue-quill.esm-bundler.3c4b0230.js} +2 -2
- abstra_statics/dist/assets/{workspaceStore.87f8dbc6.js → workspaceStore.6a20c00d.js} +2 -2
- abstra_statics/dist/assets/{xml.c3c548ab.js → xml.070d3630.js} +3 -3
- abstra_statics/dist/assets/{yaml.0d909e29.js → yaml.2a862691.js} +3 -3
- abstra_statics/dist/console.html +15 -15
- abstra_statics/dist/editor.html +13 -11
- abstra_statics/dist/player.html +9 -9
- tests/e2e/test_crud_files.py +0 -1
- tests/e2e/test_requirements.py +41 -4
- abstra_statics/dist/assets/AbstraLogo.vue_vue_type_script_setup_true_lang.0c707a8b.js +0 -2
- abstra_statics/dist/assets/App.f0468c7f.js +0 -2
- abstra_statics/dist/assets/App.vue_vue_type_style_index_0_lang.864018b5.js +0 -2
- abstra_statics/dist/assets/BaseLayout.8bd18c5f.js +0 -2
- abstra_statics/dist/assets/CloseCircleOutlined.2815d641.js +0 -2
- abstra_statics/dist/assets/ConnectorsView.4e22242f.js +0 -2
- abstra_statics/dist/assets/ConsoleOmniChat.vue_vue_type_script_setup_true_lang.17b546d4.js +0 -2
- abstra_statics/dist/assets/ContentLayout.c7733a0e.js +0 -2
- abstra_statics/dist/assets/DocsButton.vue_vue_type_script_setup_true_lang.2c5aae83.js +0 -2
- abstra_statics/dist/assets/EnvVars.74e357b2.js +0 -2
- abstra_statics/dist/assets/ExclamationCircleOutlined.9b25ffda.js +0 -2
- abstra_statics/dist/assets/Files.d9621f31.js +0 -2
- abstra_statics/dist/assets/Form.9eebd960.js +0 -2
- abstra_statics/dist/assets/Home.9531e545.js +0 -2
- abstra_statics/dist/assets/Home.b12bb81a.js +0 -2
- abstra_statics/dist/assets/LoadingContainer.c40ae513.js +0 -2
- abstra_statics/dist/assets/LoadingOutlined.b607eff2.js +0 -2
- abstra_statics/dist/assets/Login.5f104bb1.js +0 -2
- abstra_statics/dist/assets/Logo.a34929e1.js +0 -2
- abstra_statics/dist/assets/Main.7030ea1d.js +0 -2
- abstra_statics/dist/assets/Navbar.a1055174.js +0 -2
- abstra_statics/dist/assets/NewEditor.6b2cb8e6.js +0 -8
- abstra_statics/dist/assets/OidcLoginCallback.445dd392.js +0 -2
- abstra_statics/dist/assets/Organizations.1c35b6b8.js +0 -2
- abstra_statics/dist/assets/Project.2fdca57c.js +0 -2
- abstra_statics/dist/assets/ProjectLogin.7660cd84.js +0 -2
- abstra_statics/dist/assets/Sql.1afe0bac.css +0 -1
- abstra_statics/dist/assets/Sql.7d92acbb.js +0 -5
- abstra_statics/dist/assets/Steps.b12e16c6.js +0 -2
- abstra_statics/dist/assets/Tables.aa6b418c.js +0 -2
- abstra_statics/dist/assets/TablesTabs.vue_vue_type_script_setup_true_lang.95ea10aa.js +0 -2
- abstra_statics/dist/assets/Tasks.6660de00.js +0 -2
- abstra_statics/dist/assets/View.vue_vue_type_script_setup_true_lang.483e52f9.js +0 -2
- abstra_statics/dist/assets/WidgetPreview.b01eed73.js +0 -2
- abstra_statics/dist/assets/asyncComputed.59410422.js +0 -2
- abstra_statics/dist/assets/constants.733c6549.js +0 -2
- abstra_statics/dist/assets/datetime.adbf692e.js +0 -2
- abstra_statics/dist/assets/dayjs.9e279491.js +0 -2
- abstra_statics/dist/assets/editor.7e30500a.js +0 -2
- abstra_statics/dist/assets/editor.main.4675b13a.js +0 -2
- abstra_statics/dist/assets/fetch.13b54f0f.js +0 -2
- abstra_statics/dist/assets/linters.9ba6d5f8.js +0 -2
- abstra_statics/dist/assets/member.3aca30ee.js +0 -2
- abstra_statics/dist/assets/omniChatStore.16b8f156.js +0 -10
- abstra_statics/dist/assets/player.7f570660.js +0 -2
- abstra_statics/dist/assets/polling.0b08b681.js +0 -2
- abstra_statics/dist/assets/repository.02efcdbd.js +0 -2
- abstra_statics/dist/assets/router.7071f838.js +0 -2
- abstra_statics/dist/assets/string.360236ba.js +0 -2
- abstra_statics/dist/assets/tasksController.b66c85ee.js +0 -4
- abstra_statics/dist/assets/url.b31d406a.js +0 -2
- abstra_statics/dist/assets/userStore.f2537ff3.js +0 -2
- abstra_statics/dist/assets/uuid.d6d43ef5.js +0 -2
- {abstra-3.23.7.dist-info → abstra-3.23.9.dist-info}/WHEEL +0 -0
- {abstra-3.23.7.dist-info → abstra-3.23.9.dist-info}/entry_points.txt +0 -0
- {abstra-3.23.7.dist-info → abstra-3.23.9.dist-info}/top_level.txt +0 -0
|
@@ -11,5 +11,5 @@ CREDENTIALS_FILEPATH = f"{DOT_ABSTRA_DIR}/credentials"
|
|
|
11
11
|
TEST_DATA_FILEPATH = f"{DOT_ABSTRA_DIR}/test_data.json"
|
|
12
12
|
|
|
13
13
|
GIT_DIR_PATH = ".git"
|
|
14
|
-
ABSTRA_IGNORE_FILEPATH = ".
|
|
14
|
+
ABSTRA_IGNORE_FILEPATH = ".gitignore"
|
|
15
15
|
ABSTRA_TABLES_FILEPATH = "abstra-tables.json"
|
|
@@ -20,7 +20,7 @@ from abstra_internals.contracts_generated import (
|
|
|
20
20
|
)
|
|
21
21
|
from abstra_internals.repositories.factory import Repositories
|
|
22
22
|
from abstra_internals.services.file_watcher import crdt_managers
|
|
23
|
-
from abstra_internals.services.fs import
|
|
23
|
+
from abstra_internals.services.fs import FileSystemService
|
|
24
24
|
from abstra_internals.utils.crdt import CRDTManager
|
|
25
25
|
|
|
26
26
|
|
|
@@ -28,29 +28,20 @@ class CodebaseController:
|
|
|
28
28
|
def __init__(self, repos: Repositories):
|
|
29
29
|
self.repos = repos
|
|
30
30
|
|
|
31
|
-
def file_node(self,
|
|
32
|
-
is_dir =
|
|
33
|
-
stats =
|
|
34
|
-
|
|
35
|
-
grand_children = []
|
|
36
|
-
if is_dir:
|
|
37
|
-
grand_children = [
|
|
38
|
-
list(c.relative_to(base_path).parts)
|
|
39
|
-
for c in child_path.iterdir()
|
|
40
|
-
if c.name not in SKIPPED_DIRNAMES
|
|
41
|
-
]
|
|
31
|
+
def file_node(self, path: Path) -> CommonFileNode:
|
|
32
|
+
is_dir = path.is_dir()
|
|
33
|
+
stats = path.stat()
|
|
42
34
|
|
|
43
35
|
return CommonFileNode(
|
|
44
|
-
path_parts=list(
|
|
36
|
+
path_parts=list(path.parts),
|
|
45
37
|
size=stats.st_size,
|
|
46
38
|
last_modified=datetime.fromtimestamp(stats.st_mtime),
|
|
47
39
|
type="directory" if is_dir else "file",
|
|
48
|
-
children=grand_children,
|
|
49
40
|
)
|
|
50
41
|
|
|
51
42
|
def list_files(self, path) -> AbstraLibApiEditorFilesListResponse:
|
|
52
43
|
if path is None:
|
|
53
|
-
path = Path
|
|
44
|
+
path = Path()
|
|
54
45
|
elif isinstance(path, str):
|
|
55
46
|
path = Path(path)
|
|
56
47
|
elif not isinstance(path, Path):
|
|
@@ -60,17 +51,17 @@ class CodebaseController:
|
|
|
60
51
|
|
|
61
52
|
return [
|
|
62
53
|
AbstraLibApiEditorFilesListResponseItem(
|
|
63
|
-
file=self.file_node(child_path
|
|
54
|
+
file=self.file_node(child_path),
|
|
64
55
|
stages=[
|
|
65
56
|
AbstraLibApiEditorFilesListResponseItemStagesItem(
|
|
66
57
|
id=stage.id,
|
|
67
|
-
type=stage.type_name,
|
|
58
|
+
type=stage.type_name,
|
|
68
59
|
)
|
|
69
60
|
for stage in project.get_stages_by_file_path(child_path)
|
|
70
61
|
],
|
|
71
62
|
)
|
|
72
63
|
for child_path in FileSystemService.list_files(
|
|
73
|
-
path, include_dirs=True, use_ignore=False
|
|
64
|
+
path, include_dirs=True, use_ignore=False, recursive=False
|
|
74
65
|
)
|
|
75
66
|
]
|
|
76
67
|
|
|
@@ -6,13 +6,12 @@ from typing import List, Optional
|
|
|
6
6
|
import flask_sock
|
|
7
7
|
from dotenv import load_dotenv
|
|
8
8
|
|
|
9
|
-
from abstra_internals.contracts_generated import
|
|
10
|
-
AbstraLibApiEditorCodebaseEventsMessage,
|
|
11
|
-
)
|
|
9
|
+
from abstra_internals.contracts_generated import AbstraLibApiEditorCodebaseEventsMessage
|
|
12
10
|
from abstra_internals.logger import AbstraLogger
|
|
13
11
|
from abstra_internals.modules import reload_module
|
|
14
12
|
from abstra_internals.repositories.factory import Repositories
|
|
15
13
|
from abstra_internals.services.file_watcher import FSEventType
|
|
14
|
+
from abstra_internals.settings import Settings
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class CodebaseEventController:
|
|
@@ -37,8 +36,11 @@ class CodebaseEventController:
|
|
|
37
36
|
def broadcast_changes(
|
|
38
37
|
cls, filepath: Path, event: FSEventType, content: Optional[str]
|
|
39
38
|
):
|
|
39
|
+
absolute_root_path = Settings.root_path.resolve()
|
|
40
40
|
message = AbstraLibApiEditorCodebaseEventsMessage(
|
|
41
|
-
filepath=str(filepath),
|
|
41
|
+
filepath=str(filepath.relative_to(absolute_root_path)),
|
|
42
|
+
event=event,
|
|
43
|
+
content=content,
|
|
42
44
|
)
|
|
43
45
|
for listener in cls.listeners:
|
|
44
46
|
try:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import pkgutil
|
|
3
|
-
import webbrowser
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
from shutil import move
|
|
6
5
|
from tempfile import mkdtemp, mktemp
|
|
@@ -65,7 +64,7 @@ from abstra_internals.templates import (
|
|
|
65
64
|
)
|
|
66
65
|
from abstra_internals.utils.ai import AiWs
|
|
67
66
|
from abstra_internals.utils.diff import compute_updated_code_from_replacements
|
|
68
|
-
from abstra_internals.utils.file import
|
|
67
|
+
from abstra_internals.utils.file import path2module
|
|
69
68
|
from abstra_internals.utils.validate import validate_json
|
|
70
69
|
|
|
71
70
|
|
|
@@ -169,7 +168,7 @@ class MainController:
|
|
|
169
168
|
|
|
170
169
|
This method looks up and returns a single stage from the project
|
|
171
170
|
based on the provided ID. The stage can be of any type (form, hook,
|
|
172
|
-
job, or
|
|
171
|
+
job, or tasklet).
|
|
173
172
|
|
|
174
173
|
Args:
|
|
175
174
|
id (str): Unique identifier of the stage to retrieve.
|
|
@@ -202,7 +201,7 @@ class MainController:
|
|
|
202
201
|
elif isinstance(stage, JobStage):
|
|
203
202
|
print("This is a job stage")
|
|
204
203
|
elif isinstance(stage, ScriptStage):
|
|
205
|
-
print("This is a
|
|
204
|
+
print("This is a tasklet stage")
|
|
206
205
|
else:
|
|
207
206
|
print("Stage not found")
|
|
208
207
|
```
|
|
@@ -287,16 +286,6 @@ class MainController:
|
|
|
287
286
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
288
287
|
file_path.write_text(code, encoding="utf-8")
|
|
289
288
|
|
|
290
|
-
def open_file(self, file_path: str, mode: str, create_if_not_exists: bool = False):
|
|
291
|
-
if mode == "module" or mode == "package":
|
|
292
|
-
file_path = str(module2path(file_path, mode == "package"))
|
|
293
|
-
complete_file_path = Settings.root_path.joinpath(file_path)
|
|
294
|
-
|
|
295
|
-
if create_if_not_exists and not complete_file_path.is_file():
|
|
296
|
-
complete_file_path.touch()
|
|
297
|
-
|
|
298
|
-
webbrowser.open(complete_file_path.absolute().as_uri())
|
|
299
|
-
|
|
300
289
|
def read_file(self, file: str):
|
|
301
290
|
"""
|
|
302
291
|
Read the contents of a file from the project workspace.
|
|
@@ -316,13 +305,13 @@ class MainController:
|
|
|
316
305
|
```python
|
|
317
306
|
controller = MainController(repositories)
|
|
318
307
|
|
|
319
|
-
# Read a Python
|
|
320
|
-
|
|
321
|
-
if
|
|
322
|
-
print("
|
|
323
|
-
print(
|
|
308
|
+
# Read a Python tasklet file
|
|
309
|
+
tasklet_content = controller.read_file("tasklet_data_processor.py")
|
|
310
|
+
if tasklet_content:
|
|
311
|
+
print("Tasklet content:")
|
|
312
|
+
print(tasklet_content)
|
|
324
313
|
else:
|
|
325
|
-
print("
|
|
314
|
+
print("Tasklet file not found")
|
|
326
315
|
|
|
327
316
|
# Read configuration files
|
|
328
317
|
config_content = controller.read_file("config.json")
|
|
@@ -383,13 +372,13 @@ class MainController:
|
|
|
383
372
|
else:
|
|
384
373
|
print("No requirements file found")
|
|
385
374
|
|
|
386
|
-
# Check
|
|
387
|
-
|
|
388
|
-
if controller.check_file(
|
|
389
|
-
content = controller.read_file(
|
|
390
|
-
print("
|
|
375
|
+
# Check tasklet files before reading
|
|
376
|
+
tasklet_path = "tasklet_data_processor.py"
|
|
377
|
+
if controller.check_file(tasklet_path):
|
|
378
|
+
content = controller.read_file(tasklet_path)
|
|
379
|
+
print("Tasklet loaded successfully")
|
|
391
380
|
else:
|
|
392
|
-
print(f"
|
|
381
|
+
print(f"Tasklet {tasklet_path} not found")
|
|
393
382
|
|
|
394
383
|
# Check configuration files
|
|
395
384
|
config_files = ["config.json", "settings.yaml", ".env"]
|
|
@@ -401,8 +390,8 @@ class MainController:
|
|
|
401
390
|
print("No configuration files found")
|
|
402
391
|
|
|
403
392
|
# Directories return False
|
|
404
|
-
is_file = controller.check_file("
|
|
405
|
-
is_file = controller.check_file("
|
|
393
|
+
is_file = controller.check_file("tasklets") # Returns False (directory)
|
|
394
|
+
is_file = controller.check_file("tasklets/") # Returns False (directory)
|
|
406
395
|
```
|
|
407
396
|
|
|
408
397
|
Note:
|
|
@@ -499,7 +488,7 @@ class MainController:
|
|
|
499
488
|
|
|
500
489
|
This method provides different listing modes to browse the project filesystem,
|
|
501
490
|
supporting various file types and Python module discovery. It respects
|
|
502
|
-
ignore patterns defined in .
|
|
491
|
+
ignore patterns defined in .gitignore file.
|
|
503
492
|
|
|
504
493
|
Args:
|
|
505
494
|
path (str, optional): Relative path from project root to list contents.
|
|
@@ -531,8 +520,8 @@ class MainController:
|
|
|
531
520
|
for item in all_files:
|
|
532
521
|
print(f"{item['type']}: {item['name']} -> {item['path']}")
|
|
533
522
|
|
|
534
|
-
# List only Python files in
|
|
535
|
-
python_files = controller.list_files("
|
|
523
|
+
# List only Python files in tasklets directory
|
|
524
|
+
python_files = controller.list_files("tasklets", mode="python-file")
|
|
536
525
|
for file in python_files:
|
|
537
526
|
if file['type'] == 'file':
|
|
538
527
|
print(f"Python file: {file['path']}")
|
|
@@ -552,7 +541,7 @@ class MainController:
|
|
|
552
541
|
```
|
|
553
542
|
|
|
554
543
|
Note:
|
|
555
|
-
- Respects .
|
|
544
|
+
- Respects .gitignore and .gitignore patterns when use_ignore=True
|
|
556
545
|
- Image mode supports: .png, .jpg, .jpeg, .gif, .svg, .webp, .jfif, .pjp, .pjpeg
|
|
557
546
|
- Module mode uses Python's pkgutil.iter_modules for discovery
|
|
558
547
|
- Paths are always relative to the project root directory
|
|
@@ -841,49 +830,49 @@ class MainController:
|
|
|
841
830
|
id: Optional[str] = None,
|
|
842
831
|
) -> ScriptStage:
|
|
843
832
|
"""
|
|
844
|
-
Create a new
|
|
833
|
+
Create a new tasklet stage in the project workflow.
|
|
845
834
|
|
|
846
|
-
|
|
835
|
+
Tasklets are programmatic workflow stages that execute Python code
|
|
847
836
|
without user interaction. They are used for data processing, business
|
|
848
837
|
logic, integrations, and automation tasks within workflows.
|
|
849
838
|
|
|
850
839
|
Args:
|
|
851
|
-
title (str): Display name for the
|
|
852
|
-
file (str): Relative path where the
|
|
840
|
+
title (str): Display name for the tasklet stage.
|
|
841
|
+
file (str): Relative path where the tasklet's Python code will be stored.
|
|
853
842
|
Must end with .py extension.
|
|
854
843
|
workflow_position (Tuple[int, int], optional): X, Y coordinates for the
|
|
855
|
-
|
|
856
|
-
id (Optional[str], optional): Custom identifier for the
|
|
844
|
+
tasklet's position in the visual workflow editor. Defaults to (0, 0).
|
|
845
|
+
id (Optional[str], optional): Custom identifier for the tasklet. If None,
|
|
857
846
|
a unique ID will be automatically generated.
|
|
858
847
|
|
|
859
848
|
Returns:
|
|
860
|
-
ScriptStage: The newly created
|
|
849
|
+
ScriptStage: The newly created tasklet stage object containing all tasklet metadata.
|
|
861
850
|
|
|
862
851
|
Example:
|
|
863
852
|
```python
|
|
864
853
|
controller = MainController(repositories)
|
|
865
854
|
|
|
866
|
-
# Create a data processing
|
|
867
|
-
processor = controller.
|
|
855
|
+
# Create a data processing tasklet
|
|
856
|
+
processor = controller.create_tasklet(
|
|
868
857
|
title="Data Processor",
|
|
869
|
-
file="
|
|
858
|
+
file="tasklet_process_data.py"
|
|
870
859
|
)
|
|
871
|
-
print(f"Created
|
|
860
|
+
print(f"Created tasklet: {processor.id}")
|
|
872
861
|
|
|
873
|
-
# Create
|
|
874
|
-
validator = controller.
|
|
862
|
+
# Create tasklet with custom positioning
|
|
863
|
+
validator = controller.create_tasklet(
|
|
875
864
|
title="Input Validator",
|
|
876
|
-
file="
|
|
865
|
+
file="tasklet_validate_input.py",
|
|
877
866
|
workflow_position=(200, 300),
|
|
878
867
|
id="input-validator"
|
|
879
868
|
)
|
|
880
869
|
```
|
|
881
870
|
|
|
882
871
|
Note:
|
|
883
|
-
- The
|
|
884
|
-
-
|
|
885
|
-
-
|
|
886
|
-
-
|
|
872
|
+
- The tasklet file will be initialized with default tasklet template code
|
|
873
|
+
- Tasklets can receive data from previous workflow stages
|
|
874
|
+
- Tasklets can send tasks to trigger subsequent workflow stages
|
|
875
|
+
- Tasklets run without user interaction and are ideal for automation
|
|
887
876
|
|
|
888
877
|
Copywritings:
|
|
889
878
|
Create a new tasklet stage
|
|
@@ -909,13 +898,13 @@ class MainController:
|
|
|
909
898
|
|
|
910
899
|
def delete_tasklet(self, id: str, remove_file: bool = False):
|
|
911
900
|
"""
|
|
912
|
-
Delete a
|
|
901
|
+
Delete a tasklet stage from the project workflow.
|
|
913
902
|
|
|
914
|
-
This method removes a
|
|
903
|
+
This method removes a tasklet stage from the project configuration and
|
|
915
904
|
optionally deletes the associated Python file from the filesystem.
|
|
916
905
|
|
|
917
906
|
Args:
|
|
918
|
-
id (str): Unique identifier of the
|
|
907
|
+
id (str): Unique identifier of the tasklet stage to delete.
|
|
919
908
|
remove_file (bool, optional): Whether to also delete the associated
|
|
920
909
|
Python file from the filesystem. Defaults to False.
|
|
921
910
|
|
|
@@ -923,15 +912,15 @@ class MainController:
|
|
|
923
912
|
```python
|
|
924
913
|
controller = MainController(repositories)
|
|
925
914
|
|
|
926
|
-
# Delete
|
|
927
|
-
controller.
|
|
915
|
+
# Delete tasklet but preserve the file
|
|
916
|
+
controller.delete_tasklet("data-processor")
|
|
928
917
|
|
|
929
|
-
# Delete
|
|
930
|
-
controller.
|
|
918
|
+
# Delete tasklet and its file completely
|
|
919
|
+
controller.delete_tasklet("validator-tasklet", remove_file=True)
|
|
931
920
|
```
|
|
932
921
|
|
|
933
922
|
Warning:
|
|
934
|
-
- Deleting a
|
|
923
|
+
- Deleting a tasklet that is referenced by workflow transitions may
|
|
935
924
|
break the workflow flow
|
|
936
925
|
- If remove_file=True, the Python file will be permanently deleted
|
|
937
926
|
- This operation cannot be undone
|
|
@@ -976,14 +965,14 @@ class MainController:
|
|
|
976
965
|
# Create a simple form
|
|
977
966
|
form = controller.create_form(
|
|
978
967
|
title="User Registration",
|
|
979
|
-
file="
|
|
968
|
+
file="form_registration.py"
|
|
980
969
|
)
|
|
981
970
|
print(f"Created form with ID: {form.id}")
|
|
982
971
|
|
|
983
972
|
# Create form with custom position and ID
|
|
984
973
|
custom_form = controller.create_form(
|
|
985
974
|
title="Data Input Form",
|
|
986
|
-
file="
|
|
975
|
+
file="form_data_input.py",
|
|
987
976
|
workflow_position=(100, 200),
|
|
988
977
|
id="custom-form-id"
|
|
989
978
|
)
|
|
@@ -1099,14 +1088,14 @@ class MainController:
|
|
|
1099
1088
|
# Create a webhook for external API integration
|
|
1100
1089
|
webhook = controller.create_hook(
|
|
1101
1090
|
title="Payment Webhook",
|
|
1102
|
-
file="
|
|
1091
|
+
file="hook_payment_webhook.py"
|
|
1103
1092
|
)
|
|
1104
1093
|
print(f"Webhook URL: /hooks/{webhook.id}")
|
|
1105
1094
|
|
|
1106
1095
|
# Create hook with custom positioning
|
|
1107
1096
|
api_hook = controller.create_hook(
|
|
1108
1097
|
title="User API Endpoint",
|
|
1109
|
-
file="
|
|
1098
|
+
file="hook_user_api.py",
|
|
1110
1099
|
workflow_position=(300, 150),
|
|
1111
1100
|
id="user-api-hook"
|
|
1112
1101
|
)
|
|
@@ -1242,14 +1231,14 @@ class MainController:
|
|
|
1242
1231
|
# Create a daily data processing job
|
|
1243
1232
|
daily_job = controller.create_job(
|
|
1244
1233
|
title="Daily Data Sync",
|
|
1245
|
-
file="
|
|
1234
|
+
file="job_daily_sync.py"
|
|
1246
1235
|
)
|
|
1247
1236
|
print(f"Created job: {daily_job.title}")
|
|
1248
1237
|
|
|
1249
1238
|
# Create job with custom position and ID
|
|
1250
1239
|
cleanup_job = controller.create_job(
|
|
1251
1240
|
title="Weekly Cleanup",
|
|
1252
|
-
file="
|
|
1241
|
+
file="job_cleanup.py",
|
|
1253
1242
|
workflow_position=(500, 100),
|
|
1254
1243
|
id="weekly-cleanup"
|
|
1255
1244
|
)
|
|
@@ -1390,7 +1379,7 @@ class MainController:
|
|
|
1390
1379
|
Retrieve all workflow stages in the current project.
|
|
1391
1380
|
|
|
1392
1381
|
This method returns a complete list of all stages (forms, hooks, jobs,
|
|
1393
|
-
and
|
|
1382
|
+
and tasklets) that are part of the project workflow.
|
|
1394
1383
|
|
|
1395
1384
|
Returns:
|
|
1396
1385
|
List[Stage]: List containing all workflow stages, including:
|
|
@@ -1760,10 +1749,10 @@ class MainController:
|
|
|
1760
1749
|
|
|
1761
1750
|
script = self.get_script(id)
|
|
1762
1751
|
if not script:
|
|
1763
|
-
raise Exception(f"
|
|
1752
|
+
raise Exception(f"Tasklet with id {id} not found")
|
|
1764
1753
|
|
|
1765
1754
|
if not task_id:
|
|
1766
|
-
raise Exception("Task ID is required for
|
|
1755
|
+
raise Exception("Task ID is required for tasklet execution")
|
|
1767
1756
|
|
|
1768
1757
|
return ExecutionController(
|
|
1769
1758
|
repositories=self.repositories,
|
|
@@ -18,7 +18,43 @@ from abstra_internals.entities.forms.widgets.widget_base import InputWidget, Wid
|
|
|
18
18
|
@dataclass
|
|
19
19
|
class Button:
|
|
20
20
|
"""
|
|
21
|
-
|
|
21
|
+
Render buttons with custom functionality in your form for conditional logic and branching paths.
|
|
22
|
+
|
|
23
|
+
Buttons allow users to trigger custom Python functions, processing or different form behaviors. When a button is pressed, its key becomes `True` in the form state, enabling conditional logic to determine the next steps in your workflow. This makes buttons essential for creating approval workflows, multi-path forms, and interactive decision trees.
|
|
24
|
+
|
|
25
|
+
Unlike automatic navigation buttons (NextButton/BackButton), custom Button widgets give you full control over widget behavior within a page and can be used to implement complex conditional logic based on user choices.
|
|
26
|
+
|
|
27
|
+
## How Button State Works
|
|
28
|
+
|
|
29
|
+
When a user clicks a button, the button's key is set to `True` in the form state. You can then check this state using `state.get("button_key")` to determine which button was pressed and conditionally render different widgets or forms.
|
|
30
|
+
|
|
31
|
+
## Usage Pattern
|
|
32
|
+
|
|
33
|
+
Buttons are optionally returned from your Page function (a function that returns a list of widgets). You have two options:
|
|
34
|
+
|
|
35
|
+
**Option 1: Return only widgets (default behavior)**
|
|
36
|
+
```python
|
|
37
|
+
def my_page(state):
|
|
38
|
+
return [TextOutput("Hello"), TextOutput("world!")] # Default navigation buttons will be used
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Option 2: Return widgets + custom buttons as a tuple**
|
|
42
|
+
```python
|
|
43
|
+
def my_page(state):
|
|
44
|
+
widgets = [TextOutput("Hello"), TextOutput("world!")]
|
|
45
|
+
buttons = [Button("Label 1", key="key1"), Button("Label 2", key="key2")]
|
|
46
|
+
return widgets, buttons
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If you don't return buttons (Option 1), the system automatically provides default navigation buttons (Next/Back). If you want custom buttons, you must return a tuple with widgets as the first element and a list of Button objects as the second element.
|
|
50
|
+
|
|
51
|
+
## Common Use Cases
|
|
52
|
+
|
|
53
|
+
- **Approval Workflows**: Create "Approve" and "Reject" buttons for document review processes
|
|
54
|
+
- **Data Reload**: Refresh page content with updated information from external APIs or databases
|
|
55
|
+
- **Multi-path Forms**: Allow users to choose between different information collection paths
|
|
56
|
+
- **Decision Trees**: Build complex conditional forms based on user choices
|
|
57
|
+
- **Custom Navigation**: Implement non-linear form progression with custom logic
|
|
22
58
|
|
|
23
59
|
Args:
|
|
24
60
|
label (str): The text displayed on the button.
|
|
@@ -50,8 +86,26 @@ class Button:
|
|
|
50
86
|
|
|
51
87
|
class NextButton(Button):
|
|
52
88
|
"""
|
|
53
|
-
|
|
54
|
-
|
|
89
|
+
Automatic navigation button for progressing to the next step in multi-step forms.
|
|
90
|
+
|
|
91
|
+
NextButton is automatically added to forms when using multi-step workflows created with the `run()` function. It handles forward navigation between form steps without requiring manual implementation. The button is automatically labeled with internationalized "Next" text that adapts to the user's language settings.
|
|
92
|
+
|
|
93
|
+
## Automatic Behavior
|
|
94
|
+
|
|
95
|
+
- **Auto-added**: NextButton appears automatically on each step of a multi-step form (you don't need to import or create it)
|
|
96
|
+
- **Smart positioning**: Only shows when there are more steps ahead
|
|
97
|
+
- **Internationalized**: Button text automatically translates based on user locale
|
|
98
|
+
- **State preservation**: User input is preserved when navigating between steps
|
|
99
|
+
|
|
100
|
+
## When to Use
|
|
101
|
+
|
|
102
|
+
NextButton is ideal for:
|
|
103
|
+
- **Linear workflows**: Step-by-step data collection processes
|
|
104
|
+
- **Wizards**: Multi-step onboarding or configuration flows
|
|
105
|
+
- **Progressive forms**: Breaking long forms into manageable sections
|
|
106
|
+
- **Data collection pipelines**: Structured information gathering processes
|
|
107
|
+
|
|
108
|
+
Unlike custom Button widgets, NextButton doesn't require state management or conditional logic - it's handled automatically by the form system.
|
|
55
109
|
"""
|
|
56
110
|
|
|
57
111
|
def __init__(self):
|
|
@@ -61,8 +115,33 @@ class NextButton(Button):
|
|
|
61
115
|
|
|
62
116
|
class BackButton(Button):
|
|
63
117
|
"""
|
|
64
|
-
|
|
65
|
-
|
|
118
|
+
Automatic navigation button for returning to previous steps in multi-step forms.
|
|
119
|
+
|
|
120
|
+
BackButton is automatically added to forms starting from step 2 onwards in multi-step workflows. It enables backward navigation while preserving all user input, allowing users to review and modify their previous responses without losing data. The button is automatically labeled with internationalized "Back" text that adapts to the user's language settings.
|
|
121
|
+
|
|
122
|
+
## Automatic Behavior
|
|
123
|
+
|
|
124
|
+
- **Auto-added**: BackButton appears automatically from step 2 onwards in multi-step forms (you don't need to import or create it)
|
|
125
|
+
- **Smart positioning**: Only shows when there are previous steps to navigate to
|
|
126
|
+
- **Internationalized**: Button text automatically translates based on user locale
|
|
127
|
+
- **Data preservation**: All user input is maintained when navigating backwards
|
|
128
|
+
- **Seamless integration**: Works automatically with the form system's navigation logic
|
|
129
|
+
|
|
130
|
+
## Key Features
|
|
131
|
+
|
|
132
|
+
- **Non-destructive navigation**: Users can go back without losing their progress
|
|
133
|
+
- **Form state management**: Previous inputs are automatically restored when returning
|
|
134
|
+
- **User-friendly**: Provides confidence for users to explore and correct their inputs
|
|
135
|
+
|
|
136
|
+
## When BackButton Appears
|
|
137
|
+
|
|
138
|
+
BackButton is ideal for:
|
|
139
|
+
- **Multi-step wizards**: Allow users to review and modify previous steps
|
|
140
|
+
- **Data collection forms**: Enable correction of earlier responses
|
|
141
|
+
- **Progressive workflows**: Provide flexibility in non-linear completion
|
|
142
|
+
- **Review processes**: Allow users to double-check their inputs before submission
|
|
143
|
+
|
|
144
|
+
Unlike custom Button widgets, BackButton doesn't require any implementation - it's automatically managed by the form system.
|
|
66
145
|
"""
|
|
67
146
|
|
|
68
147
|
def __init__(self):
|
|
@@ -17,9 +17,7 @@ def _generate_zip_file() -> pathlib.Path:
|
|
|
17
17
|
zip_path = pathlib.Path(tempfile.gettempdir(), f"{uuid.uuid4()}.zip")
|
|
18
18
|
|
|
19
19
|
with zipfile.ZipFile(zip_path, "w") as zip_file:
|
|
20
|
-
for file in FileSystemService.list_files(
|
|
21
|
-
root_path, use_ignore=True, ignore_dotenv=True
|
|
22
|
-
):
|
|
20
|
+
for file in FileSystemService.list_files(root_path, use_ignore=True):
|
|
23
21
|
zip_file.write(file, file.relative_to(root_path))
|
|
24
22
|
|
|
25
23
|
return zip_path
|
abstra_internals/modules_test.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import platform
|
|
3
2
|
from unittest import TestCase, skipIf
|
|
4
3
|
|
|
5
4
|
from abstra_internals.modules import import_as_new, reload_module
|
|
@@ -7,11 +6,12 @@ from abstra_internals.repositories.project.project import (
|
|
|
7
6
|
LocalProjectRepository,
|
|
8
7
|
ScriptStage,
|
|
9
8
|
)
|
|
9
|
+
from abstra_internals.utils.platform import is_windows
|
|
10
10
|
from tests.fixtures import clear_dir, init_dir
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def can_create_symlinks():
|
|
14
|
-
if
|
|
14
|
+
if not is_windows():
|
|
15
15
|
return True
|
|
16
16
|
|
|
17
17
|
try:
|
|
@@ -12,7 +12,7 @@ class LinterRepository(ABC):
|
|
|
12
12
|
pass
|
|
13
13
|
|
|
14
14
|
@abstractmethod
|
|
15
|
-
def update_checks(self):
|
|
15
|
+
def update_checks(self) -> List[LinterCheck]:
|
|
16
16
|
pass
|
|
17
17
|
|
|
18
18
|
@abstractmethod
|
|
@@ -52,39 +52,6 @@ class LocalLinterRepository(LinterRepository):
|
|
|
52
52
|
- description: Human-readable description of what the rule checks
|
|
53
53
|
- issues: List of specific issues found by this rule
|
|
54
54
|
- fixes: Available automatic fixes for the issues
|
|
55
|
-
|
|
56
|
-
Example:
|
|
57
|
-
```python
|
|
58
|
-
linter_repo = LocalLinterRepository()
|
|
59
|
-
linter_repo.update_checks() # Run all checks first
|
|
60
|
-
|
|
61
|
-
checks = linter_repo.get_checks()
|
|
62
|
-
print(f"Found {len(checks)} linter checks")
|
|
63
|
-
|
|
64
|
-
for check in checks:
|
|
65
|
-
print(f"Rule: {check.name} ({check.type})")
|
|
66
|
-
print(f"Description: {check.description}")
|
|
67
|
-
|
|
68
|
-
if check.issues:
|
|
69
|
-
print(f"Issues found: {len(check.issues)}")
|
|
70
|
-
for issue in check.issues:
|
|
71
|
-
print(f" - {issue.message}")
|
|
72
|
-
print(f" File: {issue.file}:{issue.line}")
|
|
73
|
-
|
|
74
|
-
if issue.fixes:
|
|
75
|
-
print(f" Available fixes: {len(issue.fixes)}")
|
|
76
|
-
for fix in issue.fixes:
|
|
77
|
-
print(f" - {fix.name}: {fix.description}")
|
|
78
|
-
else:
|
|
79
|
-
print("✓ No issues found")
|
|
80
|
-
|
|
81
|
-
# Filter by severity
|
|
82
|
-
errors = [c for c in checks if c.type == 'error']
|
|
83
|
-
warnings = [c for c in checks if c.type == 'warning']
|
|
84
|
-
security_issues = [c for c in checks if c.type == 'security']
|
|
85
|
-
|
|
86
|
-
print(f"Errors: {len(errors)}, Warnings: {len(warnings)}")
|
|
87
|
-
print(f"Security issues: {len(security_issues)}")
|
|
88
55
|
```
|
|
89
56
|
|
|
90
57
|
Note:
|
|
@@ -118,6 +85,8 @@ class LocalLinterRepository(LinterRepository):
|
|
|
118
85
|
|
|
119
86
|
self.checks = new_checks
|
|
120
87
|
|
|
88
|
+
return self.checks
|
|
89
|
+
|
|
121
90
|
def fix_issue_in_codebase(self, rule_name: str, fix_name: str):
|
|
122
91
|
"""
|
|
123
92
|
Apply a specific automatic fix for a linter issue.
|
|
@@ -216,16 +185,16 @@ class ProductionLinterRepository(LinterRepository):
|
|
|
216
185
|
"""
|
|
217
186
|
|
|
218
187
|
def find_issues_in_codebase(self) -> List[LinterCheck]:
|
|
219
|
-
|
|
188
|
+
raise NotImplementedError("Linters are not available in production.")
|
|
220
189
|
|
|
221
|
-
def update_checks(self):
|
|
222
|
-
|
|
190
|
+
def update_checks(self) -> List[LinterCheck]:
|
|
191
|
+
raise NotImplementedError("Linters are not available in production.")
|
|
223
192
|
|
|
224
193
|
def fix_issue_in_codebase(self, rule_name: str, fix_name: str) -> bool:
|
|
225
|
-
|
|
194
|
+
raise NotImplementedError("Linters are not available in production.")
|
|
226
195
|
|
|
227
196
|
def fix_all_linters(self):
|
|
228
|
-
|
|
197
|
+
raise NotImplementedError("Linters are not available in production.")
|
|
229
198
|
|
|
230
199
|
def get_blocking_checks(self) -> List[LinterCheck]:
|
|
231
|
-
|
|
200
|
+
raise NotImplementedError("Linters are not available in production.")
|
|
@@ -3,6 +3,7 @@ from typing import List
|
|
|
3
3
|
|
|
4
4
|
from abstra_internals.repositories.linter.models import LinterRule
|
|
5
5
|
|
|
6
|
+
from .big_py_files import BigPyFiles
|
|
6
7
|
from .conflicting_name import ConflictingName
|
|
7
8
|
from .conflicting_path import ConflictingPath
|
|
8
9
|
from .deprecated_functions import DeprecatedFunctionUsage
|
|
@@ -30,6 +31,7 @@ core_rules: List[LinterRule] = [
|
|
|
30
31
|
Psycopg2MustBeBinary(),
|
|
31
32
|
ConflictingName(),
|
|
32
33
|
DeprecatedFunctionUsage(),
|
|
34
|
+
BigPyFiles(),
|
|
33
35
|
]
|
|
34
36
|
|
|
35
37
|
conditional_rules: List[LinterRule] = []
|