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.
Files changed (237) hide show
  1. {abstra-3.23.7.dist-info → abstra-3.23.8.dist-info}/METADATA +1 -1
  2. {abstra-3.23.7.dist-info → abstra-3.23.8.dist-info}/RECORD +188 -186
  3. abstra_internals/consts/filepaths.py +1 -1
  4. abstra_internals/controllers/codebase.py +9 -18
  5. abstra_internals/controllers/codebase_events.py +6 -4
  6. abstra_internals/controllers/main.py +56 -67
  7. abstra_internals/interface/cli/deploy.py +1 -3
  8. abstra_internals/repositories/linter/repository.py +9 -40
  9. abstra_internals/repositories/linter/rules/__init__.py +2 -0
  10. abstra_internals/repositories/linter/rules/big_py_files.py +40 -0
  11. abstra_internals/repositories/linter/rules/big_py_files_test.py +93 -0
  12. abstra_internals/repositories/linter/rules/duplicate_package_in_requirements.py +16 -4
  13. abstra_internals/repositories/linter/rules/duplicate_package_in_requirements_test.py +24 -3
  14. abstra_internals/repositories/linter/rules/env_in_bundle.py +3 -6
  15. abstra_internals/repositories/linter/rules/env_in_bundle_test.py +4 -3
  16. abstra_internals/repositories/linter/rules/missing_packages_in_requirements.py +12 -1
  17. abstra_internals/repositories/linter/rules/syntax_errors.py +2 -19
  18. abstra_internals/repositories/linter/rules/venv_in_bundle.py +3 -2
  19. abstra_internals/server/routes/requirements.py +7 -3
  20. abstra_internals/server/routes/workspace.py +0 -10
  21. abstra_internals/services/fs.py +144 -99
  22. abstra_internals/services/fs_test.py +303 -8
  23. abstra_internals/services/requirements.py +271 -81
  24. abstra_internals/services/requirements_test.py +528 -164
  25. abstra_internals/templates/__init__.py +3 -1
  26. abstra_statics/dist/assets/AbstraButton.vue_vue_type_script_setup_true_lang.205eb76e.js +2 -0
  27. abstra_statics/dist/assets/AbstraLogo.vue_vue_type_script_setup_true_lang.32a17b0c.js +2 -0
  28. abstra_statics/dist/assets/ApiKeys.7fefafb4.js +2 -0
  29. abstra_statics/dist/assets/App.c29df54f.js +2 -0
  30. abstra_statics/dist/assets/App.vue_vue_type_style_index_0_lang.efd9d3fa.js +2 -0
  31. abstra_statics/dist/assets/{BaseLayout.8bd18c5f.js → BaseLayout.b60c33b8.js} +2 -2
  32. abstra_statics/dist/assets/{Billing.877a4614.js → Billing.b9f77b65.js} +2 -2
  33. abstra_statics/dist/assets/{Breadcrumb.f312111a.js → Breadcrumb.d0cc2c91.js} +2 -2
  34. abstra_statics/dist/assets/{Builds.a2c45c39.js → Builds.18e4ba1a.js} +2 -2
  35. abstra_statics/dist/assets/{Card.5f504e7b.js → Card.957a87b2.js} +2 -2
  36. abstra_statics/dist/assets/{CircularLoading.6f511e29.js → CircularLoading.3e4ddd6d.js} +2 -2
  37. abstra_statics/dist/assets/CloseCircleOutlined.4e05b917.js +2 -0
  38. abstra_statics/dist/assets/{ConnectorsView.4e22242f.js → ConnectorsView.562a5007.js} +2 -2
  39. abstra_statics/dist/assets/ConsoleOmniChat.vue_vue_type_script_setup_true_lang.1d09cfdd.js +2 -0
  40. abstra_statics/dist/assets/ContentLayout.7d5c9f09.js +2 -0
  41. abstra_statics/dist/assets/{CrudView.5a642b48.js → CrudView.8fb84eac.js} +2 -2
  42. abstra_statics/dist/assets/DocsButton.vue_vue_type_script_setup_true_lang.0555d923.js +2 -0
  43. abstra_statics/dist/assets/{EditorLogin.d2224782.js → EditorLogin.02eb6050.js} +2 -2
  44. abstra_statics/dist/assets/{EditorsView.5e769180.js → EditorsView.f0ea00fc.js} +2 -2
  45. abstra_statics/dist/assets/EnvVars.063644bb.js +2 -0
  46. abstra_statics/dist/assets/{Error.dd899e38.js → Error.a81122c4.js} +2 -2
  47. abstra_statics/dist/assets/ExclamationCircleOutlined.d410fb9a.js +2 -0
  48. abstra_statics/dist/assets/Files.f23b9c53.js +2 -0
  49. abstra_statics/dist/assets/{Form.9eebd960.js → Form.bfea5673.js} +2 -2
  50. abstra_statics/dist/assets/{FormRunner.1c6a88dd.js → FormRunner.8bbe841e.js} +2 -2
  51. abstra_statics/dist/assets/Home.3bf2f131.js +2 -0
  52. abstra_statics/dist/assets/Home.42964d5b.js +2 -0
  53. abstra_statics/dist/assets/{Live.a691b0eb.js → Live.5dc821b6.js} +2 -2
  54. abstra_statics/dist/assets/LoadingContainer.6e72d482.js +2 -0
  55. abstra_statics/dist/assets/LoadingOutlined.ee72932a.js +2 -0
  56. abstra_statics/dist/assets/Login.0b618d09.js +2 -0
  57. abstra_statics/dist/assets/{Login.f96858b0.js → Login.c702642b.js} +2 -2
  58. 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
  59. abstra_statics/dist/assets/Logo.83b476a4.js +2 -0
  60. abstra_statics/dist/assets/{Logs.8426d360.js → Logs.eebc0504.js} +2 -2
  61. abstra_statics/dist/assets/{LogsController.318117fd.js → LogsController.eb1b811d.js} +2 -2
  62. abstra_statics/dist/assets/Main.88719eb3.js +2 -0
  63. abstra_statics/dist/assets/{MockForm.f8600bec.js → MockForm.c3318be9.js} +2 -2
  64. abstra_statics/dist/assets/Navbar.abf206e9.js +2 -0
  65. abstra_statics/dist/assets/{NewEditor.d3300cf0.css → NewEditor.0044878f.css} +1 -1
  66. abstra_statics/dist/assets/NewEditor.5650f697.js +8 -0
  67. abstra_statics/dist/assets/OidcLoginCallback.df2bdeb0.js +2 -0
  68. abstra_statics/dist/assets/OidcLogoutCallback.2ba5316d.js +2 -0
  69. abstra_statics/dist/assets/{OmniChat.7660057c.css → OmniChat.8a35a659.css} +1 -1
  70. abstra_statics/dist/assets/{OmniChat.097bec71.js → OmniChat.de828c54.js} +4 -4
  71. abstra_statics/dist/assets/{OnboardingView.c9a3343e.js → OnboardingView.1c034f0d.js} +2 -2
  72. abstra_statics/dist/assets/{Organization.0833b7fe.js → Organization.855f95a9.js} +2 -2
  73. abstra_statics/dist/assets/Organizations.3db82ab2.js +2 -0
  74. abstra_statics/dist/assets/{PhArrowCounterClockwise.vue.aaa06bc0.js → PhArrowCounterClockwise.vue.76c9e146.js} +2 -2
  75. abstra_statics/dist/assets/{PhArrowSquareOut.vue.ee4af292.js → PhArrowSquareOut.vue.ecfa9cb2.js} +2 -2
  76. abstra_statics/dist/assets/{PhBookBookmark.vue.681c5036.js → PhBookBookmark.vue.f8f803d9.js} +2 -2
  77. abstra_statics/dist/assets/{PhChats.vue.d61c3615.js → PhChats.vue.dfda946c.js} +2 -2
  78. abstra_statics/dist/assets/{PhClockCounterClockwise.vue.0457e9b2.js → PhClockCounterClockwise.vue.cd77fd26.js} +2 -2
  79. abstra_statics/dist/assets/{PhCopy.vue.391b0ef7.js → PhCopy.vue.a63b48dd.js} +2 -2
  80. abstra_statics/dist/assets/{PhCopySimple.vue.e887b43c.js → PhCopySimple.vue.ecffb042.js} +2 -2
  81. abstra_statics/dist/assets/{PhCube.vue.d070a184.js → PhCube.vue.b5b96a33.js} +2 -2
  82. abstra_statics/dist/assets/{PhDotsThreeVertical.vue.f4b60771.js → PhDotsThreeVertical.vue.2db678ef.js} +2 -2
  83. abstra_statics/dist/assets/PhDownloadSimple.vue.cbca4f9b.js +2 -0
  84. abstra_statics/dist/assets/{PhFolderPlus.vue.d5788203.js → PhFolderPlus.vue.dfb9b117.js} +2 -2
  85. abstra_statics/dist/assets/{PhGear.vue.e2b120bb.js → PhGear.vue.6e1aeed0.js} +2 -2
  86. abstra_statics/dist/assets/{PhKey.vue.cf1e08ca.js → PhKey.vue.50d82bb5.js} +2 -2
  87. abstra_statics/dist/assets/{PhPencil.vue.20f1b3c4.js → PhPencil.vue.ae2943da.js} +2 -2
  88. abstra_statics/dist/assets/{PhPencilSimple.vue.ec2125f5.js → PhPencilSimple.vue.9042e169.js} +2 -2
  89. abstra_statics/dist/assets/{PhPencilSimpleLine.vue.22e75a5a.js → PhPencilSimpleLine.vue.f840cf0d.js} +2 -2
  90. abstra_statics/dist/assets/{PhRocket.vue.27c6f935.js → PhRocket.vue.3b5927aa.js} +2 -2
  91. abstra_statics/dist/assets/{PhSignOut.vue.61b63ec0.js → PhSignOut.vue.d00d3657.js} +2 -2
  92. abstra_statics/dist/assets/{PhSparkle.vue.fd6a9ad7.js → PhSparkle.vue.8a94f3a0.js} +2 -2
  93. abstra_statics/dist/assets/{PhUserList.vue.abdd6da1.js → PhUserList.vue.3791cb59.js} +2 -2
  94. abstra_statics/dist/assets/{PhUsersThree.vue.85d1a1f0.js → PhUsersThree.vue.ef0376b6.js} +2 -2
  95. abstra_statics/dist/assets/{PhWebhooksLogo.vue.00b65b2c.js → PhWebhooksLogo.vue.fe81fb65.js} +2 -2
  96. abstra_statics/dist/assets/{PlayerConfigProvider.10f46997.js → PlayerConfigProvider.2acd3a77.js} +2 -2
  97. abstra_statics/dist/assets/{PlayerNavbar.f2f66852.js → PlayerNavbar.13876ce7.js} +2 -2
  98. abstra_statics/dist/assets/Project.0277535f.js +2 -0
  99. abstra_statics/dist/assets/{ProjectLogin.7660cd84.js → ProjectLogin.46d5036b.js} +2 -2
  100. abstra_statics/dist/assets/{ProjectSettings.50027450.js → ProjectSettings.652a838b.js} +2 -2
  101. abstra_statics/dist/assets/{ProjectsView.107f5e34.js → ProjectsView.c5ec993b.js} +2 -2
  102. abstra_statics/dist/assets/{SaveButton.cd025dae.js → SaveButton.ac38b361.js} +2 -2
  103. abstra_statics/dist/assets/{files.f66880c3.js → ScrollArea.vue_vue_type_script_setup_true_lang.a58564d3.js} +2 -2
  104. abstra_statics/dist/assets/{Sidebar.c3d5d187.js → Sidebar.56e51ab5.js} +2 -2
  105. abstra_statics/dist/assets/Sql.3cdc910a.css +1 -0
  106. abstra_statics/dist/assets/Sql.6961306b.js +5 -0
  107. abstra_statics/dist/assets/Steps.f820fb18.js +2 -0
  108. abstra_statics/dist/assets/{TableEditor.7b07ece4.js → TableEditor.df6a4852.js} +2 -2
  109. abstra_statics/dist/assets/Tables.198b84c5.js +2 -0
  110. abstra_statics/dist/assets/{TablesDiagram.6736c045.js → TablesDiagram.811d464d.js} +3 -3
  111. abstra_statics/dist/assets/TablesTabs.vue_vue_type_script_setup_true_lang.e6074880.js +2 -0
  112. abstra_statics/dist/assets/{Tasks.6660de00.js → Tasks.df69d20e.js} +2 -2
  113. abstra_statics/dist/assets/{UploadOutlined.732440a5.js → UploadOutlined.b601a592.js} +2 -2
  114. abstra_statics/dist/assets/{View.283e52c1.js → View.7a2ccc33.js} +2 -2
  115. abstra_statics/dist/assets/View.vue_vue_type_script_setup_true_lang.424410a2.js +2 -0
  116. abstra_statics/dist/assets/{Watermark.6076ef47.js → Watermark.40c8c47e.js} +2 -2
  117. abstra_statics/dist/assets/{WebEditor.6a012d5b.js → WebEditor.82a3accf.js} +2 -2
  118. abstra_statics/dist/assets/WidgetPreview.0f938200.js +2 -0
  119. abstra_statics/dist/assets/ant-design.544de4a0.js +2 -0
  120. abstra_statics/dist/assets/{apiKey.1c96dd66.js → apiKey.95126643.js} +2 -2
  121. abstra_statics/dist/assets/asyncComputed.1b787534.js +2 -0
  122. abstra_statics/dist/assets/{build.656c5601.js → build.997fec15.js} +2 -2
  123. abstra_statics/dist/assets/colorHelpers.d4f3f275.js +2 -0
  124. abstra_statics/dist/assets/{console.9b13e1da.js → console.54ab975c.js} +4 -4
  125. abstra_statics/dist/assets/{constants.733c6549.js → constants.36bf7d70.js} +2 -2
  126. abstra_statics/dist/assets/{cssMode.6d17ca95.js → cssMode.c1aa21d7.js} +2 -2
  127. abstra_statics/dist/assets/{datetime.adbf692e.js → datetime.adc0acc4.js} +2 -2
  128. abstra_statics/dist/assets/dayjs.a6bd0ee0.js +2 -0
  129. abstra_statics/dist/assets/editor.0ce52658.js +2 -0
  130. abstra_statics/dist/assets/editor.main.353e9a8f.js +2 -0
  131. abstra_statics/dist/assets/fetch.83d89bdc.js +2 -0
  132. abstra_statics/dist/assets/{folder.9092348a.js → folder.41d37eb7.js} +2 -2
  133. abstra_statics/dist/assets/{freemarker2.82f2cb8c.js → freemarker2.40778f3f.js} +2 -2
  134. abstra_statics/dist/assets/{handlebars.36ec2a3c.js → handlebars.92d4ff2a.js} +2 -2
  135. abstra_statics/dist/assets/{html.845da565.js → html.b3c0c53a.js} +3 -3
  136. abstra_statics/dist/assets/{htmlMode.980f76b4.js → htmlMode.1212da49.js} +2 -2
  137. abstra_statics/dist/assets/{index.5dbe93c3.js → index.36d8b30a.js} +2 -2
  138. abstra_statics/dist/assets/{index.55d10b71.js → index.37c46161.js} +2 -2
  139. abstra_statics/dist/assets/index.3d2a7b6d.js +2 -0
  140. abstra_statics/dist/assets/{index.81a2ae08.js → index.3e7471f4.js} +2 -2
  141. abstra_statics/dist/assets/{index.1551abd6.js → index.3ff1c0be.js} +2 -2
  142. abstra_statics/dist/assets/{index.b3b62f71.js → index.5e22e010.js} +2 -2
  143. abstra_statics/dist/assets/{index.0f357aec.js → index.9e0166fe.js} +2 -2
  144. abstra_statics/dist/assets/{index.1e12c884.js → index.a7b8e25e.js} +2 -2
  145. abstra_statics/dist/assets/{index.a2b9d34b.js → index.d181a22c.js} +2 -2
  146. abstra_statics/dist/assets/{javascript.b0154182.js → javascript.f9710043.js} +3 -3
  147. abstra_statics/dist/assets/{jsonMode.f86e9042.js → jsonMode.16d00c0d.js} +2 -2
  148. abstra_statics/dist/assets/{jwt-decode.esm.d86c27e0.js → jwt-decode.esm.992666e9.js} +8 -8
  149. abstra_statics/dist/assets/{linters.9ba6d5f8.js → linters.3c5f0c83.js} +2 -2
  150. abstra_statics/dist/assets/{liquid.029287f8.js → liquid.9a777e1a.js} +3 -3
  151. abstra_statics/dist/assets/{member.3aca30ee.js → member.6b2b3438.js} +2 -2
  152. abstra_statics/dist/assets/{metadata.18d0a278.js → metadata.cfad5458.js} +2 -2
  153. abstra_statics/dist/assets/omniChatStore.2b85c342.js +8 -0
  154. abstra_statics/dist/assets/{organization.8b2c1c53.js → organization.53636095.js} +2 -2
  155. abstra_statics/dist/assets/player.5b7eaa25.js +2 -0
  156. abstra_statics/dist/assets/{plotly.min.10467de2.js → plotly.min.9b8bef5c.js} +2 -2
  157. abstra_statics/dist/assets/polling.88a266b3.js +2 -0
  158. abstra_statics/dist/assets/{project.33809d47.js → project.76f0af14.js} +2 -2
  159. abstra_statics/dist/assets/{python.ee23fd86.js → python.ae8270c8.js} +3 -3
  160. abstra_statics/dist/assets/{razor.4ae6d2a2.js → razor.4f24e19e.js} +3 -3
  161. abstra_statics/dist/assets/{record.d087b37e.js → record.87ef3b68.js} +2 -2
  162. abstra_statics/dist/assets/redirect.2fa4f8cf.js +2 -0
  163. abstra_statics/dist/assets/{repository.02efcdbd.js → repository.9d5310b6.js} +2 -2
  164. abstra_statics/dist/assets/{repository.6fa74dff.js → repository.af418855.js} +2 -2
  165. abstra_statics/dist/assets/{router.7936fd78.js → router.1324a1a9.js} +3 -3
  166. abstra_statics/dist/assets/router.55c0ff56.js +2 -0
  167. abstra_statics/dist/assets/{string.360236ba.js → string.f6a7565f.js} +2 -2
  168. abstra_statics/dist/assets/{tables.d580be9d.js → tables.c26107a1.js} +2 -2
  169. abstra_statics/dist/assets/{tasksController.b66c85ee.js → tasksController.22584849.js} +2 -2
  170. abstra_statics/dist/assets/{toggleHighContrast.510bdb1d.js → toggleHighContrast.fc60753d.js} +49 -49
  171. abstra_statics/dist/assets/{tsMode.5c0f732d.js → tsMode.a4b9b524.js} +2 -2
  172. abstra_statics/dist/assets/{typescript.0643a053.js → typescript.ab90fd1d.js} +2 -2
  173. abstra_statics/dist/assets/url.f490879d.js +2 -0
  174. abstra_statics/dist/assets/userStore.9e7a540a.js +2 -0
  175. abstra_statics/dist/assets/uuid.9161765c.js +2 -0
  176. abstra_statics/dist/assets/{vue-flow-background.011d27ef.js → vue-flow-background.8e4c22c3.js} +2 -2
  177. abstra_statics/dist/assets/{vue-quill.esm-bundler.487756a5.js → vue-quill.esm-bundler.64c5837f.js} +2 -2
  178. abstra_statics/dist/assets/{workspaceStore.87f8dbc6.js → workspaceStore.4f0c433f.js} +2 -2
  179. abstra_statics/dist/assets/{xml.c3c548ab.js → xml.3541c340.js} +3 -3
  180. abstra_statics/dist/assets/{yaml.0d909e29.js → yaml.314312d8.js} +3 -3
  181. abstra_statics/dist/console.html +15 -15
  182. abstra_statics/dist/editor.html +11 -11
  183. abstra_statics/dist/player.html +9 -9
  184. tests/e2e/test_crud_files.py +0 -1
  185. tests/e2e/test_requirements.py +41 -4
  186. abstra_statics/dist/assets/AbstraButton.vue_vue_type_script_setup_true_lang.9812dba9.js +0 -2
  187. abstra_statics/dist/assets/AbstraLogo.vue_vue_type_script_setup_true_lang.0c707a8b.js +0 -2
  188. abstra_statics/dist/assets/ApiKeys.902caf82.js +0 -2
  189. abstra_statics/dist/assets/App.f0468c7f.js +0 -2
  190. abstra_statics/dist/assets/App.vue_vue_type_style_index_0_lang.864018b5.js +0 -2
  191. abstra_statics/dist/assets/CloseCircleOutlined.2815d641.js +0 -2
  192. abstra_statics/dist/assets/ConsoleOmniChat.vue_vue_type_script_setup_true_lang.17b546d4.js +0 -2
  193. abstra_statics/dist/assets/ContentLayout.c7733a0e.js +0 -2
  194. abstra_statics/dist/assets/DocsButton.vue_vue_type_script_setup_true_lang.2c5aae83.js +0 -2
  195. abstra_statics/dist/assets/EnvVars.74e357b2.js +0 -2
  196. abstra_statics/dist/assets/ExclamationCircleOutlined.9b25ffda.js +0 -2
  197. abstra_statics/dist/assets/Files.d9621f31.js +0 -2
  198. abstra_statics/dist/assets/Home.9531e545.js +0 -2
  199. abstra_statics/dist/assets/Home.b12bb81a.js +0 -2
  200. abstra_statics/dist/assets/LoadingContainer.c40ae513.js +0 -2
  201. abstra_statics/dist/assets/LoadingOutlined.b607eff2.js +0 -2
  202. abstra_statics/dist/assets/Login.5f104bb1.js +0 -2
  203. abstra_statics/dist/assets/Logo.a34929e1.js +0 -2
  204. abstra_statics/dist/assets/Main.7030ea1d.js +0 -2
  205. abstra_statics/dist/assets/Navbar.a1055174.js +0 -2
  206. abstra_statics/dist/assets/NewEditor.6b2cb8e6.js +0 -8
  207. abstra_statics/dist/assets/OidcLoginCallback.445dd392.js +0 -2
  208. abstra_statics/dist/assets/OidcLogoutCallback.485fb0b9.js +0 -2
  209. abstra_statics/dist/assets/Organizations.1c35b6b8.js +0 -2
  210. abstra_statics/dist/assets/PhDownloadSimple.vue.3444d06b.js +0 -2
  211. abstra_statics/dist/assets/Project.2fdca57c.js +0 -2
  212. abstra_statics/dist/assets/Sql.1afe0bac.css +0 -1
  213. abstra_statics/dist/assets/Sql.7d92acbb.js +0 -5
  214. abstra_statics/dist/assets/Steps.b12e16c6.js +0 -2
  215. abstra_statics/dist/assets/Tables.aa6b418c.js +0 -2
  216. abstra_statics/dist/assets/TablesTabs.vue_vue_type_script_setup_true_lang.95ea10aa.js +0 -2
  217. abstra_statics/dist/assets/View.vue_vue_type_script_setup_true_lang.483e52f9.js +0 -2
  218. abstra_statics/dist/assets/WidgetPreview.b01eed73.js +0 -2
  219. abstra_statics/dist/assets/ant-design.4302db30.js +0 -2
  220. abstra_statics/dist/assets/asyncComputed.59410422.js +0 -2
  221. abstra_statics/dist/assets/colorHelpers.2a607581.js +0 -2
  222. abstra_statics/dist/assets/dayjs.9e279491.js +0 -2
  223. abstra_statics/dist/assets/editor.7e30500a.js +0 -2
  224. abstra_statics/dist/assets/editor.main.4675b13a.js +0 -2
  225. abstra_statics/dist/assets/fetch.13b54f0f.js +0 -2
  226. abstra_statics/dist/assets/index.4ecba4f7.js +0 -2
  227. abstra_statics/dist/assets/omniChatStore.16b8f156.js +0 -10
  228. abstra_statics/dist/assets/player.7f570660.js +0 -2
  229. abstra_statics/dist/assets/polling.0b08b681.js +0 -2
  230. abstra_statics/dist/assets/redirect.c06a7828.js +0 -2
  231. abstra_statics/dist/assets/router.7071f838.js +0 -2
  232. abstra_statics/dist/assets/url.b31d406a.js +0 -2
  233. abstra_statics/dist/assets/userStore.f2537ff3.js +0 -2
  234. abstra_statics/dist/assets/uuid.d6d43ef5.js +0 -2
  235. {abstra-3.23.7.dist-info → abstra-3.23.8.dist-info}/WHEEL +0 -0
  236. {abstra-3.23.7.dist-info → abstra-3.23.8.dist-info}/entry_points.txt +0 -0
  237. {abstra-3.23.7.dist-info → abstra-3.23.8.dist-info}/top_level.txt +0 -0
@@ -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 RequirementsRepository
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 RequirementsRepository
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
- self.assertEqual(requirements.libraries[0].version, "1.0.0")
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
- self.assertEqual(requirements.libraries[0].version, "2.0.0")
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 / ".abstraignore"
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
- ignored_patterns = FileSystemService.load_ignore_patterns(Settings.root_path)
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 / ".abstraignore"
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 / ".abstraignore"
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 / ".abstraignore"
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
- requirement_version = self.requirement_recommendation.requirement.version
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 = [OpenBrokenFile(Path(error.filename))]
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 / ".abstraignore"
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 / ".abstraignore"
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 Requirement, RequirementsRepository
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 = Requirement(name=name)
44
- streamer = req.uninstall()
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():
@@ -1,25 +1,11 @@
1
+ import fnmatch
1
2
  import os
2
- import re
3
3
  from pathlib import Path
4
4
  from typing import List, Optional, Tuple
5
5
 
6
- from abstra_internals.consts.filepaths import (
7
- ABSTRA_IGNORE_FILEPATH,
8
- DOT_ABSTRA_DIR,
9
- GIT_DIR_PATH,
10
- )
6
+ from abstra_internals.consts.filepaths import ABSTRA_IGNORE_FILEPATH
11
7
  from abstra_internals.settings import Settings
12
8
 
13
- SKIPPED_DIRNAMES = {
14
- "__pycache__",
15
- "venv",
16
- ".venv",
17
- ".vscode",
18
- ".cursor",
19
- ".ruff_cache",
20
- ".abstra",
21
- }
22
-
23
9
 
24
10
  class FileSystemService:
25
11
  @staticmethod
@@ -38,14 +24,14 @@ class FileSystemService:
38
24
  include_dirs: bool = False,
39
25
  use_ignore: bool = True,
40
26
  allowed_suffixes: Optional[List[str]] = None,
41
- ignore_dotenv: bool = False,
27
+ recursive: bool = True,
42
28
  ) -> List[Path]:
43
29
  """
44
30
  List all files in the given directory, optionally filtering by suffixes
45
31
  and applying ignore files.
46
32
  Args:
47
33
  dir (Path): The directory to list.
48
- apply_ignore_files (bool): Whether to apply ignore files: .gitignore and .abstraignore.
34
+ apply_ignore_files (bool): Whether to apply ignore files: .gitignore and .gitignore.
49
35
  allowed_suffixes (Optional[List[str]]): List of allowed file suffixes. If None, all files are included.
50
36
  """
51
37
  return FileSystemService.list_paths(
@@ -53,7 +39,7 @@ class FileSystemService:
53
39
  include_dirs=include_dirs,
54
40
  use_ignore=use_ignore,
55
41
  allowed_suffixes=allowed_suffixes,
56
- ignore_dotenv=ignore_dotenv,
42
+ recursive=recursive,
57
43
  )
58
44
 
59
45
  @staticmethod
@@ -63,7 +49,7 @@ class FileSystemService:
63
49
  include_dirs: bool = True,
64
50
  use_ignore: bool = True,
65
51
  allowed_suffixes: Optional[List[str]] = None,
66
- ignore_dotenv: bool = False,
52
+ recursive: bool = True,
67
53
  ) -> List[Path]:
68
54
  """ "
69
55
  List all files and directories in the given directory, optionally filtering by suffixes
@@ -71,7 +57,7 @@ class FileSystemService:
71
57
  Args:
72
58
  dir (Path): The directory to list.
73
59
  include_dirs (bool): Whether to include directories in the results.
74
- apply_ignore_files (bool): Whether to apply ignore files: .gitignore and .abstraignore.
60
+ apply_ignore_files (bool): Whether to apply ignore files: .gitignore and .gitignore.
75
61
  allowed_suffixes (Optional[List[str]]): List of allowed file suffixes. If None, all files are included.
76
62
  """
77
63
  if not dirpath.exists():
@@ -80,22 +66,7 @@ class FileSystemService:
80
66
  if not dirpath.is_dir():
81
67
  raise ValueError(f"Provided path {dirpath} is not a directory.")
82
68
 
83
- ignored_patterns = [
84
- *FileSystemService.load_ignore_patterns(
85
- dirpath, ignore_dotenv=ignore_dotenv
86
- ),
87
- *FileSystemService.load_ignore_patterns(
88
- Path.cwd(), ignore_dotenv=ignore_dotenv
89
- ),
90
- ]
91
-
92
- if use_ignore and FileSystemService.is_ignored(ignored_patterns, dirpath):
93
- return []
94
-
95
- if dirpath.name in SKIPPED_DIRNAMES:
96
- return []
97
-
98
- if dirpath == FileSystemService.venv_path():
69
+ if use_ignore and (FileSystemService.is_ignored(dirpath)):
99
70
  return []
100
71
 
101
72
  matches: List[Path] = []
@@ -103,28 +74,20 @@ class FileSystemService:
103
74
  matches.append(dirpath)
104
75
 
105
76
  for child in dirpath.iterdir():
106
- if (
107
- child.is_file()
108
- and FileSystemService._suffix_allowed(child, allowed_suffixes)
109
- and (
110
- not use_ignore
111
- or not FileSystemService.is_ignored(
112
- ignored_patterns, child.relative_to(dirpath)
113
- )
114
- )
77
+ if use_ignore and (FileSystemService.is_ignored(child)):
78
+ continue
79
+ if child.is_dir() or FileSystemService._suffix_allowed(
80
+ child, allowed_suffixes
115
81
  ):
116
82
  matches.append(child)
117
- elif child.is_dir():
118
- if use_ignore and FileSystemService.is_ignored(
119
- ignored_patterns, child.relative_to(dirpath)
120
- ):
121
- continue
83
+ if child.is_dir() and recursive:
122
84
  matches.extend(
123
85
  FileSystemService.list_paths(
124
86
  child,
125
87
  include_dirs=include_dirs,
126
88
  use_ignore=use_ignore,
127
89
  allowed_suffixes=allowed_suffixes,
90
+ recursive=True,
128
91
  )
129
92
  )
130
93
  return sorted(matches, key=lambda p: p.as_posix())
@@ -165,13 +128,8 @@ class FileSystemService:
165
128
  - Case-sensitive search
166
129
  """
167
130
  results: List[Tuple[str, int, str]] = []
168
- ignored_patterns = FileSystemService.load_ignore_patterns(
169
- root_dir, ignore_dotenv=False
170
- )
171
131
  for file_path in root_dir.glob(glob):
172
- if file_path.is_file() and not FileSystemService.is_ignored(
173
- ignored_patterns, file_path.relative_to(root_dir)
174
- ):
132
+ if file_path.is_file() and not FileSystemService.is_ignored(root_dir):
175
133
  try:
176
134
  with open(file_path, "r", encoding="utf-8") as f:
177
135
  for line_number, line in enumerate(f, start=1):
@@ -211,52 +169,139 @@ class FileSystemService:
211
169
  return any(path.suffix.lower() == suffix.lower() for suffix in allowed_suffixes)
212
170
 
213
171
  @staticmethod
214
- def is_ignored(ignored_paths: List[re.Pattern], path: Path):
215
- for ignored_path in ignored_paths:
216
- if ignored_path.search(path.as_posix()):
217
- return True
172
+ def is_ignored(path: Path):
173
+ path = path.resolve()
174
+
175
+ # Look for ignore file starting from the path's directory up to the root
176
+ # For files that don't exist, we need to check if it would be a file based on the path structure
177
+ # If the path has a suffix or ends with a filename-like component, treat it as a file
178
+ if path.exists():
179
+ current_dir = path.parent if path.is_file() else path
180
+ else:
181
+ # For non-existent paths, assume it's a file if it has a suffix or doesn't end with '/'
182
+ current_dir = (
183
+ path.parent if path.suffix or not str(path).endswith("/") else path
184
+ )
185
+
186
+ while current_dir != current_dir.parent: # Stop at filesystem root
187
+ ignore_file = current_dir / ABSTRA_IGNORE_FILEPATH
188
+ if ignore_file.exists():
189
+ try:
190
+ patterns = ignore_file.read_text().splitlines()
191
+ for pattern in patterns:
192
+ pattern = pattern.strip()
193
+ if pattern and not pattern.startswith("#"):
194
+ if FileSystemService._matches_gitignore_pattern(
195
+ path, pattern, current_dir
196
+ ):
197
+ return True
198
+ except (IOError, UnicodeDecodeError):
199
+ continue
200
+ current_dir = current_dir.parent
201
+
218
202
  return False
219
203
 
220
204
  @staticmethod
221
- def load_ignore_patterns(root_dir: Path, ignore_dotenv=True) -> List[re.Pattern]:
222
- git_ignore_path = root_dir.joinpath(".gitignore")
223
- abstra_ignore_path = root_dir.joinpath(ABSTRA_IGNORE_FILEPATH)
224
- git_path = root_dir.joinpath(GIT_DIR_PATH)
225
- abstra_path = root_dir.joinpath(DOT_ABSTRA_DIR)
226
-
227
- ignored: List[str] = [
228
- abstra_ignore_path.name,
229
- git_ignore_path.name,
230
- abstra_path.name,
231
- git_path.name,
232
- ]
205
+ def _matches_gitignore_pattern(path: Path, pattern: str, ignore_dir: Path) -> bool:
206
+ """
207
+ Check if a path matches a gitignore pattern according to git rules.
208
+ """
209
+ # Handle trailing slash - only matches directories
210
+ is_dir_only_pattern = pattern.endswith("/")
211
+ if is_dir_only_pattern:
212
+ pattern = pattern.rstrip("/")
213
+ # If pattern is for directories only, the path must be a directory
214
+ if path.exists() and not path.is_dir():
215
+ return False
216
+ # For non-existent paths, assume it's a directory if it doesn't have a suffix
217
+ elif not path.exists() and path.suffix:
218
+ return False
233
219
 
220
+ # Get relative path from ignore file directory
234
221
  try:
235
- if abstra_ignore_path.exists():
236
- with open(abstra_ignore_path, "r", encoding="utf-8") as f:
237
- ignored.extend(f.read().splitlines())
238
- elif git_ignore_path.exists():
239
- with open(git_ignore_path, "r", encoding="utf-8") as f:
240
- ignored.extend(f.read().splitlines())
241
- except (IOError, UnicodeDecodeError) as e:
242
- print(f"Warning: Could not read ignore file: {e}")
243
- return []
222
+ relative_path = path.relative_to(ignore_dir)
223
+ except ValueError:
224
+ # Path is not under the ignore directory
225
+ return False
244
226
 
245
- return [
246
- FileSystemService._make_ignore_regex(p)
247
- for p in ignored
248
- if p
249
- and not p.startswith("#")
250
- and not p.startswith("!")
251
- and (not p == ".env" or ignore_dotenv)
252
- ]
227
+ relative_str = relative_path.as_posix()
253
228
 
254
- @staticmethod
255
- def _make_ignore_regex(path: str) -> re.Pattern:
256
- posix_path = path.replace("\\", "/")
257
- ignore_regex = r"^" + re.escape(posix_path).replace(r"\*", ".*").replace(
258
- r"\?", "."
259
- )
260
- if not posix_path.endswith("/"):
261
- ignore_regex += r"(/|$)"
262
- return re.compile(ignore_regex)
229
+ # Handle leading slash - pattern is relative to ignore file directory only
230
+ if pattern.startswith("/"):
231
+ pattern = pattern.lstrip("/")
232
+ # For patterns starting with /, only match at the immediate level
233
+ return fnmatch.fnmatch(relative_str, pattern)
234
+
235
+ # Handle double asterisk patterns
236
+ if "**" in pattern:
237
+ # Handle special cases for ** patterns
238
+ if pattern.startswith("**/"):
239
+ # **/foo matches foo at any level
240
+ sub_pattern = pattern[3:] # Remove **/
241
+ if fnmatch.fnmatch(path.name, sub_pattern):
242
+ return True
243
+ if fnmatch.fnmatch(relative_str, sub_pattern):
244
+ return True
245
+ # Also try matching at any directory level
246
+ parts = relative_path.parts
247
+ for i in range(len(parts)):
248
+ sub_path = "/".join(parts[i:])
249
+ if fnmatch.fnmatch(sub_path, sub_pattern):
250
+ return True
251
+ elif pattern.endswith("/**"):
252
+ # foo/** matches everything inside foo directory, NOT foo itself
253
+ base_pattern = pattern[:-3] # Remove /**
254
+ if relative_str.startswith(base_pattern + "/"):
255
+ return True
256
+ else:
257
+ # General ** handling - a/**/b patterns
258
+ # Split pattern on /** to handle complex cases
259
+ if "/**/" in pattern:
260
+ # Pattern like a/**/b
261
+ parts = pattern.split("/**/")
262
+ if len(parts) == 2:
263
+ prefix, suffix = parts
264
+ # Check if path matches prefix/**/suffix pattern
265
+ # This should match prefix/suffix, prefix/x/suffix, etc.
266
+ if relative_str == f"{prefix}/{suffix}":
267
+ return True
268
+ if fnmatch.fnmatch(relative_str, pattern.replace("**", "*")):
269
+ return True
270
+ # Try matching at different levels
271
+ path_parts = relative_path.parts
272
+ for i in range(len(path_parts)):
273
+ sub_path = "/".join(path_parts[i:])
274
+ test_pattern = pattern.replace("**", "*")
275
+ if fnmatch.fnmatch(sub_path, test_pattern):
276
+ return True
277
+ return False
278
+
279
+ # Simple ** replacement
280
+ expanded_pattern = pattern.replace("**", "*")
281
+ if fnmatch.fnmatch(relative_str, expanded_pattern):
282
+ return True
283
+ # Also try matching the pattern at any directory level
284
+ parts = relative_path.parts
285
+ for i in range(len(parts)):
286
+ sub_path = "/".join(parts[i:])
287
+ if fnmatch.fnmatch(sub_path, expanded_pattern):
288
+ return True
289
+ return False
290
+
291
+ # For patterns with slash in middle, treat as relative to ignore file
292
+ if "/" in pattern:
293
+ return fnmatch.fnmatch(relative_str, pattern)
294
+
295
+ # For patterns without slash, can match at any level
296
+ # Try matching the filename only
297
+ if fnmatch.fnmatch(path.name, pattern):
298
+ return True
299
+
300
+ # Try matching at any directory level
301
+ parts = relative_path.parts
302
+ for i in range(len(parts)):
303
+ sub_path = "/".join(parts[i:])
304
+ if fnmatch.fnmatch(sub_path, pattern):
305
+ return True
306
+
307
+ return False