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
|
@@ -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
|
|
@@ -5,7 +5,10 @@ from abstra_internals.repositories.linter.models import (
|
|
|
5
5
|
LinterIssue,
|
|
6
6
|
LinterRule,
|
|
7
7
|
)
|
|
8
|
-
from abstra_internals.services.requirements import
|
|
8
|
+
from abstra_internals.services.requirements import (
|
|
9
|
+
RequirementsRepository,
|
|
10
|
+
requirement_to_dict,
|
|
11
|
+
)
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
class MergeDuplicatePackages(LinterFix):
|
|
@@ -52,9 +55,18 @@ class DuplicatePackagesInRequirements(LinterRule):
|
|
|
52
55
|
duplicates = requirements.get_duplicates()
|
|
53
56
|
issues = []
|
|
54
57
|
for name, versions in duplicates.items():
|
|
58
|
+
# Extract exact versions from specifiers
|
|
59
|
+
version_list = []
|
|
60
|
+
for r in versions:
|
|
61
|
+
req_dict = requirement_to_dict(r)
|
|
62
|
+
exact_version = None
|
|
63
|
+
for spec in req_dict.get("specifiers", []):
|
|
64
|
+
if spec["operator"] == "==":
|
|
65
|
+
exact_version = spec["version"]
|
|
66
|
+
break
|
|
67
|
+
version_list.append(exact_version)
|
|
68
|
+
|
|
55
69
|
issues.append(
|
|
56
|
-
DuplicatePackagesInRequirementsFound(
|
|
57
|
-
name=name, versions=[r.version for r in versions]
|
|
58
|
-
)
|
|
70
|
+
DuplicatePackagesInRequirementsFound(name=name, versions=version_list)
|
|
59
71
|
)
|
|
60
72
|
return issues
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from abstra_internals.repositories.linter.rules.duplicate_package_in_requirements import (
|
|
2
2
|
DuplicatePackagesInRequirements,
|
|
3
3
|
)
|
|
4
|
-
from abstra_internals.services.requirements import
|
|
4
|
+
from abstra_internals.services.requirements import (
|
|
5
|
+
RequirementsRepository,
|
|
6
|
+
requirement_to_dict,
|
|
7
|
+
)
|
|
5
8
|
from tests.fixtures import BaseTest
|
|
6
9
|
|
|
7
10
|
|
|
@@ -49,7 +52,16 @@ class TestDuplicatePackagesInRequirements(BaseTest):
|
|
|
49
52
|
requirements = RequirementsRepository.load()
|
|
50
53
|
self.assertEqual(len(requirements.libraries), 1)
|
|
51
54
|
self.assertEqual(requirements.libraries[0].name, "abstra")
|
|
52
|
-
|
|
55
|
+
|
|
56
|
+
# Use helper function to get version from Requirement
|
|
57
|
+
req_dict = requirement_to_dict(requirements.libraries[0])
|
|
58
|
+
# Extract exact version from specifiers
|
|
59
|
+
exact_version = None
|
|
60
|
+
for spec in req_dict.get("specifiers", []):
|
|
61
|
+
if spec["operator"] == "==":
|
|
62
|
+
exact_version = spec["version"]
|
|
63
|
+
break
|
|
64
|
+
self.assertEqual(exact_version, "1.0.0")
|
|
53
65
|
|
|
54
66
|
def test_invalid_with_distinct_versions_choose_second(self):
|
|
55
67
|
requirements_txt = self.root / "requirements.txt"
|
|
@@ -66,4 +78,13 @@ class TestDuplicatePackagesInRequirements(BaseTest):
|
|
|
66
78
|
requirements = RequirementsRepository.load()
|
|
67
79
|
self.assertEqual(len(requirements.libraries), 1)
|
|
68
80
|
self.assertEqual(requirements.libraries[0].name, "abstra")
|
|
69
|
-
|
|
81
|
+
|
|
82
|
+
# Use helper function to get version from Requirement
|
|
83
|
+
req_dict = requirement_to_dict(requirements.libraries[0])
|
|
84
|
+
# Extract exact version from specifiers
|
|
85
|
+
exact_version = None
|
|
86
|
+
for spec in req_dict.get("specifiers", []):
|
|
87
|
+
if spec["operator"] == "==":
|
|
88
|
+
exact_version = spec["version"]
|
|
89
|
+
break
|
|
90
|
+
self.assertEqual(exact_version, "2.0.0")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
+
from abstra_internals.consts.filepaths import ABSTRA_IGNORE_FILEPATH
|
|
3
4
|
from abstra_internals.repositories.linter.models import (
|
|
4
5
|
LinterFix,
|
|
5
6
|
LinterIssue,
|
|
@@ -13,7 +14,7 @@ class AddEnvToAbstraIgnore(LinterFix):
|
|
|
13
14
|
label = "Add env to abstra ignore"
|
|
14
15
|
|
|
15
16
|
def fix(self):
|
|
16
|
-
abstraignore_file = Settings.root_path /
|
|
17
|
+
abstraignore_file = Settings.root_path / ABSTRA_IGNORE_FILEPATH
|
|
17
18
|
with abstraignore_file.open("a") as file:
|
|
18
19
|
file.write("\n.env")
|
|
19
20
|
|
|
@@ -34,11 +35,7 @@ class EnvInBundle(LinterRule):
|
|
|
34
35
|
if not env_file.exists():
|
|
35
36
|
return []
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if FileSystemService.is_ignored(
|
|
40
|
-
ignored_patterns, env_file.relative_to(Settings.root_path)
|
|
41
|
-
):
|
|
38
|
+
if FileSystemService.is_ignored(env_file):
|
|
42
39
|
return []
|
|
43
40
|
|
|
44
41
|
return [EnvInBundleFound()]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from unittest import TestCase
|
|
2
2
|
|
|
3
|
+
from abstra_internals.consts.filepaths import ABSTRA_IGNORE_FILEPATH
|
|
3
4
|
from abstra_internals.repositories.linter.rules.env_in_bundle import EnvInBundle
|
|
4
5
|
from tests.fixtures import clear_dir, init_dir
|
|
5
6
|
|
|
@@ -20,7 +21,7 @@ class EnvInBundleTest(TestCase):
|
|
|
20
21
|
def test_env_on_bundle_valid_with_env(self):
|
|
21
22
|
rule = EnvInBundle()
|
|
22
23
|
env_file = self.root / ".env"
|
|
23
|
-
abstraignore_file = self.root /
|
|
24
|
+
abstraignore_file = self.root / ABSTRA_IGNORE_FILEPATH
|
|
24
25
|
abstraignore_file.write_text(".env")
|
|
25
26
|
env_file.touch()
|
|
26
27
|
self.assertEqual(len(rule.find_issues()), 0)
|
|
@@ -34,7 +35,7 @@ class EnvInBundleTest(TestCase):
|
|
|
34
35
|
def test_env_on_bundle_invalid_with_abstraignore_file(self):
|
|
35
36
|
env_file = self.root / ".env"
|
|
36
37
|
env_file.touch()
|
|
37
|
-
abstraignore_file = self.root /
|
|
38
|
+
abstraignore_file = self.root / ABSTRA_IGNORE_FILEPATH
|
|
38
39
|
abstraignore_file.touch()
|
|
39
40
|
rule = EnvInBundle()
|
|
40
41
|
self.assertEqual(len(rule.find_issues()), 1)
|
|
@@ -46,7 +47,7 @@ class EnvInBundleTest(TestCase):
|
|
|
46
47
|
self.assertEqual(len(rule.find_issues()), 1)
|
|
47
48
|
rule.find_issues()[0].fixes[0].fix()
|
|
48
49
|
self.assertEqual(len(rule.find_issues()), 0)
|
|
49
|
-
abstraignore_file = self.root /
|
|
50
|
+
abstraignore_file = self.root / ABSTRA_IGNORE_FILEPATH
|
|
50
51
|
self.assertTrue(abstraignore_file.exists())
|
|
51
52
|
with abstraignore_file.open("r") as file:
|
|
52
53
|
content = file.read()
|
|
@@ -8,6 +8,7 @@ from abstra_internals.repositories.linter.models import (
|
|
|
8
8
|
from abstra_internals.services.requirements import (
|
|
9
9
|
RequirementRecommendation,
|
|
10
10
|
RequirementsRepository,
|
|
11
|
+
requirement_to_dict,
|
|
11
12
|
)
|
|
12
13
|
|
|
13
14
|
|
|
@@ -21,7 +22,17 @@ class AddMissingPackagesToRequirements(LinterFix):
|
|
|
21
22
|
def fix(self):
|
|
22
23
|
requirements = RequirementsRepository.load()
|
|
23
24
|
requirement_name = self.requirement_recommendation.requirement.name
|
|
24
|
-
|
|
25
|
+
requirement_dict = requirement_to_dict(
|
|
26
|
+
self.requirement_recommendation.requirement
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Extract exact version from specifiers if available
|
|
30
|
+
requirement_version = None
|
|
31
|
+
for spec in requirement_dict.get("specifiers", []):
|
|
32
|
+
if spec["operator"] == "==":
|
|
33
|
+
requirement_version = spec["version"]
|
|
34
|
+
break
|
|
35
|
+
|
|
25
36
|
requirements.add(requirement_name, requirement_version)
|
|
26
37
|
RequirementsRepository.save(requirements)
|
|
27
38
|
|
|
@@ -1,32 +1,15 @@
|
|
|
1
|
-
import webbrowser
|
|
2
|
-
from pathlib import Path
|
|
3
1
|
from typing import List
|
|
4
2
|
|
|
5
|
-
from abstra_internals.repositories.linter.models import
|
|
6
|
-
LinterFix,
|
|
7
|
-
LinterIssue,
|
|
8
|
-
LinterRule,
|
|
9
|
-
)
|
|
3
|
+
from abstra_internals.repositories.linter.models import LinterIssue, LinterRule
|
|
10
4
|
from abstra_internals.repositories.project.project import LocalProjectRepository
|
|
11
5
|
from abstra_internals.utils.file import silent_traverse_code
|
|
12
6
|
|
|
13
7
|
|
|
14
|
-
class OpenBrokenFile(LinterFix):
|
|
15
|
-
file: Path
|
|
16
|
-
|
|
17
|
-
def __init__(self, file: Path) -> None:
|
|
18
|
-
self.label = f"Open {file}"
|
|
19
|
-
self.file = file
|
|
20
|
-
|
|
21
|
-
def fix(self):
|
|
22
|
-
webbrowser.open(self.file.absolute().as_uri())
|
|
23
|
-
|
|
24
|
-
|
|
25
8
|
class SyntaxErrorFound(LinterIssue):
|
|
26
9
|
def __init__(self, error: SyntaxError) -> None:
|
|
27
10
|
self.label = str(error)
|
|
28
11
|
if error.filename is not None:
|
|
29
|
-
self.fixes = [
|
|
12
|
+
self.fixes = []
|
|
30
13
|
|
|
31
14
|
|
|
32
15
|
class SyntaxErrors(LinterRule):
|
|
@@ -2,6 +2,7 @@ import sys
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import List
|
|
4
4
|
|
|
5
|
+
from abstra_internals.consts.filepaths import ABSTRA_IGNORE_FILEPATH
|
|
5
6
|
from abstra_internals.repositories.linter.models import (
|
|
6
7
|
LinterFix,
|
|
7
8
|
LinterIssue,
|
|
@@ -36,7 +37,7 @@ class AddVenvToAbstraIgnore(LinterFix):
|
|
|
36
37
|
root_path, prefix_path = get_root_and_prefix_path()
|
|
37
38
|
venv_folder = prefix_path.replace(root_path, "").lstrip("/")
|
|
38
39
|
|
|
39
|
-
abstraignore_file = Settings.root_path /
|
|
40
|
+
abstraignore_file = Settings.root_path / ABSTRA_IGNORE_FILEPATH
|
|
40
41
|
with abstraignore_file.open("a") as file:
|
|
41
42
|
file.write("\n")
|
|
42
43
|
file.write(venv_folder)
|
|
@@ -57,7 +58,7 @@ class VenvInBundle(LinterRule):
|
|
|
57
58
|
return prefix_path.startswith(root_path)
|
|
58
59
|
|
|
59
60
|
def virtualenv_in_abstraignore(self) -> bool:
|
|
60
|
-
abstraignore_file = Settings.root_path /
|
|
61
|
+
abstraignore_file = Settings.root_path / ABSTRA_IGNORE_FILEPATH
|
|
61
62
|
if not abstraignore_file.exists():
|
|
62
63
|
return False
|
|
63
64
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import flask
|
|
2
2
|
|
|
3
3
|
from abstra_internals.controllers.main import MainController
|
|
4
|
-
from abstra_internals.services.requirements import
|
|
4
|
+
from abstra_internals.services.requirements import (
|
|
5
|
+
RequirementsRepository,
|
|
6
|
+
create_requirement,
|
|
7
|
+
uninstall_requirement,
|
|
8
|
+
)
|
|
5
9
|
from abstra_internals.usage import editor_usage
|
|
6
10
|
|
|
7
11
|
|
|
@@ -40,8 +44,8 @@ def get_editor_bp(controller: MainController):
|
|
|
40
44
|
|
|
41
45
|
@bp.post("/<name>/uninstall")
|
|
42
46
|
def _uninstall_requirement(name: str):
|
|
43
|
-
req =
|
|
44
|
-
streamer = req
|
|
47
|
+
req = create_requirement(name)
|
|
48
|
+
streamer = uninstall_requirement(req)
|
|
45
49
|
if streamer is None:
|
|
46
50
|
flask.abort(403)
|
|
47
51
|
reqs = RequirementsRepository.load()
|
|
@@ -41,16 +41,6 @@ def get_editor_bp(controller: MainController):
|
|
|
41
41
|
except Exception as e:
|
|
42
42
|
return {"error": f"Could not get version: {str(e)}"}, 500
|
|
43
43
|
|
|
44
|
-
@bp.post("/open-file")
|
|
45
|
-
@editor_usage
|
|
46
|
-
def _open_file():
|
|
47
|
-
if not flask.request.json:
|
|
48
|
-
flask.abort(400)
|
|
49
|
-
file_path = flask.request.json["path"]
|
|
50
|
-
mode = flask.request.json.get("mode", "file")
|
|
51
|
-
controller.open_file(file_path, create_if_not_exists=True, mode=mode)
|
|
52
|
-
return {"success": True}
|
|
53
|
-
|
|
54
44
|
@bp.post("/init-file")
|
|
55
45
|
@editor_usage
|
|
56
46
|
def _init_file():
|
|
@@ -15,6 +15,7 @@ from watchdog.observers import Observer
|
|
|
15
15
|
|
|
16
16
|
from abstra_internals.settings import Settings
|
|
17
17
|
from abstra_internals.utils.crdt import CRDTManager
|
|
18
|
+
from abstra_internals.utils.file import safe_read_file, safe_write_file
|
|
18
19
|
|
|
19
20
|
IGNORED_PATHS = [
|
|
20
21
|
".abstra/",
|
|
@@ -55,19 +56,21 @@ class FileWatcher(FileSystemEventHandler):
|
|
|
55
56
|
event_type = "moved"
|
|
56
57
|
elif isinstance(event, FileModifiedEvent):
|
|
57
58
|
event_type = "changed"
|
|
58
|
-
content = (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
crdt_managers[filepath_str].
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
content = safe_read_file(filepath, 2.0)
|
|
60
|
+
|
|
61
|
+
if content is not None:
|
|
62
|
+
if (
|
|
63
|
+
filepath_str not in crdt_managers
|
|
64
|
+
or crdt_managers[filepath_str] is None
|
|
65
|
+
):
|
|
66
|
+
crdt_managers[filepath_str] = CRDTManager(file_path=filepath)
|
|
67
|
+
|
|
68
|
+
old_content = crdt_managers[filepath_str].get_content()
|
|
69
|
+
if old_content != content:
|
|
70
|
+
crdt_managers[filepath_str].apply_operations_from_diff(content)
|
|
71
|
+
new_content = crdt_managers[filepath_str].get_content()
|
|
72
|
+
if content != new_content:
|
|
73
|
+
safe_write_file(filepath, new_content, 2.0)
|
|
71
74
|
|
|
72
75
|
else:
|
|
73
76
|
return
|
|
@@ -90,7 +93,7 @@ class FileWatcher(FileSystemEventHandler):
|
|
|
90
93
|
|
|
91
94
|
self._debounce_timers[filepath_str] = threading.Timer(
|
|
92
95
|
interval=0.5, function=execute_handlers
|
|
93
|
-
)
|
|
96
|
+
)
|
|
94
97
|
self._debounce_timers[filepath_str].start()
|
|
95
98
|
|
|
96
99
|
def should_ignore_path(self, path: Path) -> bool:
|