abstra 3.23.7__py3-none-any.whl → 3.23.8__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.8.dist-info}/METADATA +1 -1
- {abstra-3.23.7.dist-info → abstra-3.23.8.dist-info}/RECORD +188 -186
- 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/interface/cli/deploy.py +1 -3
- 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/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_statics/dist/assets/AbstraButton.vue_vue_type_script_setup_true_lang.205eb76e.js +2 -0
- abstra_statics/dist/assets/AbstraLogo.vue_vue_type_script_setup_true_lang.32a17b0c.js +2 -0
- abstra_statics/dist/assets/ApiKeys.7fefafb4.js +2 -0
- abstra_statics/dist/assets/App.c29df54f.js +2 -0
- abstra_statics/dist/assets/App.vue_vue_type_style_index_0_lang.efd9d3fa.js +2 -0
- abstra_statics/dist/assets/{BaseLayout.8bd18c5f.js → BaseLayout.b60c33b8.js} +2 -2
- abstra_statics/dist/assets/{Billing.877a4614.js → Billing.b9f77b65.js} +2 -2
- abstra_statics/dist/assets/{Breadcrumb.f312111a.js → Breadcrumb.d0cc2c91.js} +2 -2
- abstra_statics/dist/assets/{Builds.a2c45c39.js → Builds.18e4ba1a.js} +2 -2
- abstra_statics/dist/assets/{Card.5f504e7b.js → Card.957a87b2.js} +2 -2
- abstra_statics/dist/assets/{CircularLoading.6f511e29.js → CircularLoading.3e4ddd6d.js} +2 -2
- abstra_statics/dist/assets/CloseCircleOutlined.4e05b917.js +2 -0
- abstra_statics/dist/assets/{ConnectorsView.4e22242f.js → ConnectorsView.562a5007.js} +2 -2
- abstra_statics/dist/assets/ConsoleOmniChat.vue_vue_type_script_setup_true_lang.1d09cfdd.js +2 -0
- abstra_statics/dist/assets/ContentLayout.7d5c9f09.js +2 -0
- abstra_statics/dist/assets/{CrudView.5a642b48.js → CrudView.8fb84eac.js} +2 -2
- abstra_statics/dist/assets/DocsButton.vue_vue_type_script_setup_true_lang.0555d923.js +2 -0
- abstra_statics/dist/assets/{EditorLogin.d2224782.js → EditorLogin.02eb6050.js} +2 -2
- abstra_statics/dist/assets/{EditorsView.5e769180.js → EditorsView.f0ea00fc.js} +2 -2
- abstra_statics/dist/assets/EnvVars.063644bb.js +2 -0
- abstra_statics/dist/assets/{Error.dd899e38.js → Error.a81122c4.js} +2 -2
- abstra_statics/dist/assets/ExclamationCircleOutlined.d410fb9a.js +2 -0
- abstra_statics/dist/assets/Files.f23b9c53.js +2 -0
- abstra_statics/dist/assets/{Form.9eebd960.js → Form.bfea5673.js} +2 -2
- abstra_statics/dist/assets/{FormRunner.1c6a88dd.js → FormRunner.8bbe841e.js} +2 -2
- abstra_statics/dist/assets/Home.3bf2f131.js +2 -0
- abstra_statics/dist/assets/Home.42964d5b.js +2 -0
- abstra_statics/dist/assets/{Live.a691b0eb.js → Live.5dc821b6.js} +2 -2
- abstra_statics/dist/assets/LoadingContainer.6e72d482.js +2 -0
- abstra_statics/dist/assets/LoadingOutlined.ee72932a.js +2 -0
- abstra_statics/dist/assets/Login.0b618d09.js +2 -0
- abstra_statics/dist/assets/{Login.f96858b0.js → Login.c702642b.js} +2 -2
- abstra_statics/dist/assets/{Login.vue_vue_type_script_setup_true_lang.7d56cca3.js → Login.vue_vue_type_script_setup_true_lang.a92a80d0.js} +2 -2
- abstra_statics/dist/assets/Logo.83b476a4.js +2 -0
- abstra_statics/dist/assets/{Logs.8426d360.js → Logs.eebc0504.js} +2 -2
- abstra_statics/dist/assets/{LogsController.318117fd.js → LogsController.eb1b811d.js} +2 -2
- abstra_statics/dist/assets/Main.88719eb3.js +2 -0
- abstra_statics/dist/assets/{MockForm.f8600bec.js → MockForm.c3318be9.js} +2 -2
- abstra_statics/dist/assets/Navbar.abf206e9.js +2 -0
- abstra_statics/dist/assets/{NewEditor.d3300cf0.css → NewEditor.0044878f.css} +1 -1
- abstra_statics/dist/assets/NewEditor.5650f697.js +8 -0
- abstra_statics/dist/assets/OidcLoginCallback.df2bdeb0.js +2 -0
- abstra_statics/dist/assets/OidcLogoutCallback.2ba5316d.js +2 -0
- abstra_statics/dist/assets/{OmniChat.7660057c.css → OmniChat.8a35a659.css} +1 -1
- abstra_statics/dist/assets/{OmniChat.097bec71.js → OmniChat.de828c54.js} +4 -4
- abstra_statics/dist/assets/{OnboardingView.c9a3343e.js → OnboardingView.1c034f0d.js} +2 -2
- abstra_statics/dist/assets/{Organization.0833b7fe.js → Organization.855f95a9.js} +2 -2
- abstra_statics/dist/assets/Organizations.3db82ab2.js +2 -0
- abstra_statics/dist/assets/{PhArrowCounterClockwise.vue.aaa06bc0.js → PhArrowCounterClockwise.vue.76c9e146.js} +2 -2
- abstra_statics/dist/assets/{PhArrowSquareOut.vue.ee4af292.js → PhArrowSquareOut.vue.ecfa9cb2.js} +2 -2
- abstra_statics/dist/assets/{PhBookBookmark.vue.681c5036.js → PhBookBookmark.vue.f8f803d9.js} +2 -2
- abstra_statics/dist/assets/{PhChats.vue.d61c3615.js → PhChats.vue.dfda946c.js} +2 -2
- abstra_statics/dist/assets/{PhClockCounterClockwise.vue.0457e9b2.js → PhClockCounterClockwise.vue.cd77fd26.js} +2 -2
- abstra_statics/dist/assets/{PhCopy.vue.391b0ef7.js → PhCopy.vue.a63b48dd.js} +2 -2
- abstra_statics/dist/assets/{PhCopySimple.vue.e887b43c.js → PhCopySimple.vue.ecffb042.js} +2 -2
- abstra_statics/dist/assets/{PhCube.vue.d070a184.js → PhCube.vue.b5b96a33.js} +2 -2
- abstra_statics/dist/assets/{PhDotsThreeVertical.vue.f4b60771.js → PhDotsThreeVertical.vue.2db678ef.js} +2 -2
- abstra_statics/dist/assets/PhDownloadSimple.vue.cbca4f9b.js +2 -0
- abstra_statics/dist/assets/{PhFolderPlus.vue.d5788203.js → PhFolderPlus.vue.dfb9b117.js} +2 -2
- abstra_statics/dist/assets/{PhGear.vue.e2b120bb.js → PhGear.vue.6e1aeed0.js} +2 -2
- abstra_statics/dist/assets/{PhKey.vue.cf1e08ca.js → PhKey.vue.50d82bb5.js} +2 -2
- abstra_statics/dist/assets/{PhPencil.vue.20f1b3c4.js → PhPencil.vue.ae2943da.js} +2 -2
- abstra_statics/dist/assets/{PhPencilSimple.vue.ec2125f5.js → PhPencilSimple.vue.9042e169.js} +2 -2
- abstra_statics/dist/assets/{PhPencilSimpleLine.vue.22e75a5a.js → PhPencilSimpleLine.vue.f840cf0d.js} +2 -2
- abstra_statics/dist/assets/{PhRocket.vue.27c6f935.js → PhRocket.vue.3b5927aa.js} +2 -2
- abstra_statics/dist/assets/{PhSignOut.vue.61b63ec0.js → PhSignOut.vue.d00d3657.js} +2 -2
- abstra_statics/dist/assets/{PhSparkle.vue.fd6a9ad7.js → PhSparkle.vue.8a94f3a0.js} +2 -2
- abstra_statics/dist/assets/{PhUserList.vue.abdd6da1.js → PhUserList.vue.3791cb59.js} +2 -2
- abstra_statics/dist/assets/{PhUsersThree.vue.85d1a1f0.js → PhUsersThree.vue.ef0376b6.js} +2 -2
- abstra_statics/dist/assets/{PhWebhooksLogo.vue.00b65b2c.js → PhWebhooksLogo.vue.fe81fb65.js} +2 -2
- abstra_statics/dist/assets/{PlayerConfigProvider.10f46997.js → PlayerConfigProvider.2acd3a77.js} +2 -2
- abstra_statics/dist/assets/{PlayerNavbar.f2f66852.js → PlayerNavbar.13876ce7.js} +2 -2
- abstra_statics/dist/assets/Project.0277535f.js +2 -0
- abstra_statics/dist/assets/{ProjectLogin.7660cd84.js → ProjectLogin.46d5036b.js} +2 -2
- abstra_statics/dist/assets/{ProjectSettings.50027450.js → ProjectSettings.652a838b.js} +2 -2
- abstra_statics/dist/assets/{ProjectsView.107f5e34.js → ProjectsView.c5ec993b.js} +2 -2
- abstra_statics/dist/assets/{SaveButton.cd025dae.js → SaveButton.ac38b361.js} +2 -2
- abstra_statics/dist/assets/{files.f66880c3.js → ScrollArea.vue_vue_type_script_setup_true_lang.a58564d3.js} +2 -2
- abstra_statics/dist/assets/{Sidebar.c3d5d187.js → Sidebar.56e51ab5.js} +2 -2
- abstra_statics/dist/assets/Sql.3cdc910a.css +1 -0
- abstra_statics/dist/assets/Sql.6961306b.js +5 -0
- abstra_statics/dist/assets/Steps.f820fb18.js +2 -0
- abstra_statics/dist/assets/{TableEditor.7b07ece4.js → TableEditor.df6a4852.js} +2 -2
- abstra_statics/dist/assets/Tables.198b84c5.js +2 -0
- abstra_statics/dist/assets/{TablesDiagram.6736c045.js → TablesDiagram.811d464d.js} +3 -3
- abstra_statics/dist/assets/TablesTabs.vue_vue_type_script_setup_true_lang.e6074880.js +2 -0
- abstra_statics/dist/assets/{Tasks.6660de00.js → Tasks.df69d20e.js} +2 -2
- abstra_statics/dist/assets/{UploadOutlined.732440a5.js → UploadOutlined.b601a592.js} +2 -2
- abstra_statics/dist/assets/{View.283e52c1.js → View.7a2ccc33.js} +2 -2
- abstra_statics/dist/assets/View.vue_vue_type_script_setup_true_lang.424410a2.js +2 -0
- abstra_statics/dist/assets/{Watermark.6076ef47.js → Watermark.40c8c47e.js} +2 -2
- abstra_statics/dist/assets/{WebEditor.6a012d5b.js → WebEditor.82a3accf.js} +2 -2
- abstra_statics/dist/assets/WidgetPreview.0f938200.js +2 -0
- abstra_statics/dist/assets/ant-design.544de4a0.js +2 -0
- abstra_statics/dist/assets/{apiKey.1c96dd66.js → apiKey.95126643.js} +2 -2
- abstra_statics/dist/assets/asyncComputed.1b787534.js +2 -0
- abstra_statics/dist/assets/{build.656c5601.js → build.997fec15.js} +2 -2
- abstra_statics/dist/assets/colorHelpers.d4f3f275.js +2 -0
- abstra_statics/dist/assets/{console.9b13e1da.js → console.54ab975c.js} +4 -4
- abstra_statics/dist/assets/{constants.733c6549.js → constants.36bf7d70.js} +2 -2
- abstra_statics/dist/assets/{cssMode.6d17ca95.js → cssMode.c1aa21d7.js} +2 -2
- abstra_statics/dist/assets/{datetime.adbf692e.js → datetime.adc0acc4.js} +2 -2
- abstra_statics/dist/assets/dayjs.a6bd0ee0.js +2 -0
- abstra_statics/dist/assets/editor.0ce52658.js +2 -0
- abstra_statics/dist/assets/editor.main.353e9a8f.js +2 -0
- abstra_statics/dist/assets/fetch.83d89bdc.js +2 -0
- abstra_statics/dist/assets/{folder.9092348a.js → folder.41d37eb7.js} +2 -2
- abstra_statics/dist/assets/{freemarker2.82f2cb8c.js → freemarker2.40778f3f.js} +2 -2
- abstra_statics/dist/assets/{handlebars.36ec2a3c.js → handlebars.92d4ff2a.js} +2 -2
- abstra_statics/dist/assets/{html.845da565.js → html.b3c0c53a.js} +3 -3
- abstra_statics/dist/assets/{htmlMode.980f76b4.js → htmlMode.1212da49.js} +2 -2
- abstra_statics/dist/assets/{index.5dbe93c3.js → index.36d8b30a.js} +2 -2
- abstra_statics/dist/assets/{index.55d10b71.js → index.37c46161.js} +2 -2
- abstra_statics/dist/assets/index.3d2a7b6d.js +2 -0
- abstra_statics/dist/assets/{index.81a2ae08.js → index.3e7471f4.js} +2 -2
- abstra_statics/dist/assets/{index.1551abd6.js → index.3ff1c0be.js} +2 -2
- abstra_statics/dist/assets/{index.b3b62f71.js → index.5e22e010.js} +2 -2
- abstra_statics/dist/assets/{index.0f357aec.js → index.9e0166fe.js} +2 -2
- abstra_statics/dist/assets/{index.1e12c884.js → index.a7b8e25e.js} +2 -2
- abstra_statics/dist/assets/{index.a2b9d34b.js → index.d181a22c.js} +2 -2
- abstra_statics/dist/assets/{javascript.b0154182.js → javascript.f9710043.js} +3 -3
- abstra_statics/dist/assets/{jsonMode.f86e9042.js → jsonMode.16d00c0d.js} +2 -2
- abstra_statics/dist/assets/{jwt-decode.esm.d86c27e0.js → jwt-decode.esm.992666e9.js} +8 -8
- abstra_statics/dist/assets/{linters.9ba6d5f8.js → linters.3c5f0c83.js} +2 -2
- abstra_statics/dist/assets/{liquid.029287f8.js → liquid.9a777e1a.js} +3 -3
- abstra_statics/dist/assets/{member.3aca30ee.js → member.6b2b3438.js} +2 -2
- abstra_statics/dist/assets/{metadata.18d0a278.js → metadata.cfad5458.js} +2 -2
- abstra_statics/dist/assets/omniChatStore.2b85c342.js +8 -0
- abstra_statics/dist/assets/{organization.8b2c1c53.js → organization.53636095.js} +2 -2
- abstra_statics/dist/assets/player.5b7eaa25.js +2 -0
- abstra_statics/dist/assets/{plotly.min.10467de2.js → plotly.min.9b8bef5c.js} +2 -2
- abstra_statics/dist/assets/polling.88a266b3.js +2 -0
- abstra_statics/dist/assets/{project.33809d47.js → project.76f0af14.js} +2 -2
- abstra_statics/dist/assets/{python.ee23fd86.js → python.ae8270c8.js} +3 -3
- abstra_statics/dist/assets/{razor.4ae6d2a2.js → razor.4f24e19e.js} +3 -3
- abstra_statics/dist/assets/{record.d087b37e.js → record.87ef3b68.js} +2 -2
- abstra_statics/dist/assets/redirect.2fa4f8cf.js +2 -0
- abstra_statics/dist/assets/{repository.02efcdbd.js → repository.9d5310b6.js} +2 -2
- abstra_statics/dist/assets/{repository.6fa74dff.js → repository.af418855.js} +2 -2
- abstra_statics/dist/assets/{router.7936fd78.js → router.1324a1a9.js} +3 -3
- abstra_statics/dist/assets/router.55c0ff56.js +2 -0
- abstra_statics/dist/assets/{string.360236ba.js → string.f6a7565f.js} +2 -2
- abstra_statics/dist/assets/{tables.d580be9d.js → tables.c26107a1.js} +2 -2
- abstra_statics/dist/assets/{tasksController.b66c85ee.js → tasksController.22584849.js} +2 -2
- abstra_statics/dist/assets/{toggleHighContrast.510bdb1d.js → toggleHighContrast.fc60753d.js} +49 -49
- abstra_statics/dist/assets/{tsMode.5c0f732d.js → tsMode.a4b9b524.js} +2 -2
- abstra_statics/dist/assets/{typescript.0643a053.js → typescript.ab90fd1d.js} +2 -2
- abstra_statics/dist/assets/url.f490879d.js +2 -0
- abstra_statics/dist/assets/userStore.9e7a540a.js +2 -0
- abstra_statics/dist/assets/uuid.9161765c.js +2 -0
- abstra_statics/dist/assets/{vue-flow-background.011d27ef.js → vue-flow-background.8e4c22c3.js} +2 -2
- abstra_statics/dist/assets/{vue-quill.esm-bundler.487756a5.js → vue-quill.esm-bundler.64c5837f.js} +2 -2
- abstra_statics/dist/assets/{workspaceStore.87f8dbc6.js → workspaceStore.4f0c433f.js} +2 -2
- abstra_statics/dist/assets/{xml.c3c548ab.js → xml.3541c340.js} +3 -3
- abstra_statics/dist/assets/{yaml.0d909e29.js → yaml.314312d8.js} +3 -3
- abstra_statics/dist/console.html +15 -15
- abstra_statics/dist/editor.html +11 -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/AbstraButton.vue_vue_type_script_setup_true_lang.9812dba9.js +0 -2
- abstra_statics/dist/assets/AbstraLogo.vue_vue_type_script_setup_true_lang.0c707a8b.js +0 -2
- abstra_statics/dist/assets/ApiKeys.902caf82.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/CloseCircleOutlined.2815d641.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/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/OidcLogoutCallback.485fb0b9.js +0 -2
- abstra_statics/dist/assets/Organizations.1c35b6b8.js +0 -2
- abstra_statics/dist/assets/PhDownloadSimple.vue.3444d06b.js +0 -2
- abstra_statics/dist/assets/Project.2fdca57c.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/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/ant-design.4302db30.js +0 -2
- abstra_statics/dist/assets/asyncComputed.59410422.js +0 -2
- abstra_statics/dist/assets/colorHelpers.2a607581.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/index.4ecba4f7.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/redirect.c06a7828.js +0 -2
- abstra_statics/dist/assets/router.7071f838.js +0 -2
- 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.8.dist-info}/WHEEL +0 -0
- {abstra-3.23.7.dist-info → abstra-3.23.8.dist-info}/entry_points.txt +0 -0
- {abstra-3.23.7.dist-info → abstra-3.23.8.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,
|
|
@@ -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
|
|
@@ -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] = []
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from abstra_internals.repositories.linter.models import LinterIssue, LinterRule
|
|
5
|
+
from abstra_internals.repositories.project.project import LocalProjectRepository
|
|
6
|
+
|
|
7
|
+
# Constant to make it easy to change the line limit
|
|
8
|
+
MAX_LINES_THRESHOLD = 500
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BigPyFileFound(LinterIssue):
|
|
12
|
+
def __init__(self, file_path: Path, line_count: int):
|
|
13
|
+
self.label = f"File {file_path.name} has {line_count} lines (limit: {MAX_LINES_THRESHOLD}). Consider splitting this file into multiple smaller files organized by responsibilities to improve code maintainability."
|
|
14
|
+
self.fixes = [] # No automatic fix available for this type of issue
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BigPyFiles(LinterRule):
|
|
18
|
+
label = "Large Python files"
|
|
19
|
+
type = "info"
|
|
20
|
+
|
|
21
|
+
def find_issues(self) -> List[LinterIssue]:
|
|
22
|
+
project = LocalProjectRepository().load()
|
|
23
|
+
issues = []
|
|
24
|
+
|
|
25
|
+
# Check all Python files in the project
|
|
26
|
+
for py_file in project.iter_py_files():
|
|
27
|
+
try:
|
|
28
|
+
if py_file.exists() and py_file.is_file():
|
|
29
|
+
# Count the number of lines in the file
|
|
30
|
+
with open(py_file, "r", encoding="utf-8") as f:
|
|
31
|
+
line_count = sum(1 for _ in f)
|
|
32
|
+
|
|
33
|
+
# If it exceeds the limit, add an issue
|
|
34
|
+
if line_count > MAX_LINES_THRESHOLD:
|
|
35
|
+
issues.append(BigPyFileFound(py_file, line_count))
|
|
36
|
+
except (UnicodeDecodeError, OSError):
|
|
37
|
+
# Ignore files that cannot be read as text
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
return issues
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from abstra_internals.repositories.linter.rules.big_py_files import (
|
|
2
|
+
MAX_LINES_THRESHOLD,
|
|
3
|
+
BigPyFiles,
|
|
4
|
+
)
|
|
5
|
+
from tests.fixtures import BaseTest
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BigPyFilesTest(BaseTest):
|
|
9
|
+
def test_big_py_files_valid_small_file(self):
|
|
10
|
+
"""Tests that small files don't generate issues"""
|
|
11
|
+
# Create a small file
|
|
12
|
+
script = self.controller.create_tasklet("Small script", "small_script.py")
|
|
13
|
+
script.file_path.write_text("print('hello world')\n" * 10) # 10 lines
|
|
14
|
+
|
|
15
|
+
rule = BigPyFiles()
|
|
16
|
+
issues = rule.find_issues()
|
|
17
|
+
self.assertEqual(len(issues), 0)
|
|
18
|
+
|
|
19
|
+
def test_big_py_files_valid_threshold_file(self):
|
|
20
|
+
"""Tests that files exactly at the limit don't generate issues"""
|
|
21
|
+
script = self.controller.create_tasklet(
|
|
22
|
+
"Threshold script", "threshold_script.py"
|
|
23
|
+
)
|
|
24
|
+
script.file_path.write_text(
|
|
25
|
+
"print('line')\n" * MAX_LINES_THRESHOLD
|
|
26
|
+
) # Exactly at the limit
|
|
27
|
+
|
|
28
|
+
rule = BigPyFiles()
|
|
29
|
+
issues = rule.find_issues()
|
|
30
|
+
self.assertEqual(len(issues), 0)
|
|
31
|
+
|
|
32
|
+
def test_big_py_files_invalid_large_file(self):
|
|
33
|
+
"""Tests that large files generate issues"""
|
|
34
|
+
script = self.controller.create_tasklet("Large script", "large_script.py")
|
|
35
|
+
# Create a file with more lines than the limit
|
|
36
|
+
script.file_path.write_text("print('line')\n" * (MAX_LINES_THRESHOLD + 50))
|
|
37
|
+
|
|
38
|
+
rule = BigPyFiles()
|
|
39
|
+
issues = rule.find_issues()
|
|
40
|
+
self.assertEqual(len(issues), 1)
|
|
41
|
+
|
|
42
|
+
issue = issues[0]
|
|
43
|
+
self.assertIn("large_script.py", issue.label)
|
|
44
|
+
self.assertIn(str(MAX_LINES_THRESHOLD + 50), issue.label)
|
|
45
|
+
self.assertIn("lines", issue.label)
|
|
46
|
+
self.assertIn("responsibilities", issue.label)
|
|
47
|
+
|
|
48
|
+
def test_big_py_files_multiple_files(self):
|
|
49
|
+
"""Tests behavior with multiple files"""
|
|
50
|
+
# Small file
|
|
51
|
+
small_script = self.controller.create_tasklet("Small script", "small.py")
|
|
52
|
+
small_script.file_path.write_text("print('small')\n" * 10)
|
|
53
|
+
|
|
54
|
+
# Large file
|
|
55
|
+
large_script = self.controller.create_tasklet("Large script", "large.py")
|
|
56
|
+
large_script.file_path.write_text(
|
|
57
|
+
"print('large')\n" * (MAX_LINES_THRESHOLD + 100)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Another large file
|
|
61
|
+
another_large_script = self.controller.create_tasklet(
|
|
62
|
+
"Another large", "another_large.py"
|
|
63
|
+
)
|
|
64
|
+
another_large_script.file_path.write_text(
|
|
65
|
+
"print('another')\n" * (MAX_LINES_THRESHOLD + 200)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
rule = BigPyFiles()
|
|
69
|
+
issues = rule.find_issues()
|
|
70
|
+
self.assertEqual(len(issues), 2) # Only the 2 large files
|
|
71
|
+
|
|
72
|
+
# Check if the file names are in the issues
|
|
73
|
+
issue_labels = [issue.label for issue in issues]
|
|
74
|
+
self.assertTrue(any("large.py" in label for label in issue_labels))
|
|
75
|
+
self.assertTrue(any("another_large.py" in label for label in issue_labels))
|
|
76
|
+
|
|
77
|
+
def test_big_py_files_empty_file(self):
|
|
78
|
+
"""Tests behavior with empty file"""
|
|
79
|
+
script = self.controller.create_tasklet("Empty script", "empty.py")
|
|
80
|
+
script.file_path.touch() # Create empty file
|
|
81
|
+
|
|
82
|
+
rule = BigPyFiles()
|
|
83
|
+
issues = rule.find_issues()
|
|
84
|
+
self.assertEqual(len(issues), 0)
|
|
85
|
+
|
|
86
|
+
def test_big_py_files_nonexistent_file(self):
|
|
87
|
+
"""Tests behavior when file doesn't exist"""
|
|
88
|
+
script = self.controller.create_tasklet("Nonexistent script", "nonexistent.py")
|
|
89
|
+
script.file_path.unlink() # Remove the file
|
|
90
|
+
|
|
91
|
+
rule = BigPyFiles()
|
|
92
|
+
issues = rule.find_issues()
|
|
93
|
+
self.assertEqual(len(issues), 0) # Should not generate error or issue
|