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
@@ -1,201 +1,565 @@
1
+ from pathlib import Path
1
2
  from unittest import TestCase
2
3
 
3
- from abstra_internals.services.requirements import Requirement, Requirements
4
+ from packaging.requirements import Requirement
4
5
 
6
+ from abstra_internals.services.requirements import (
7
+ RequirementRecommendation,
8
+ Requirements,
9
+ RequirementsRepository,
10
+ create_requirement,
11
+ requirement_from_dict,
12
+ requirement_from_text,
13
+ requirement_to_dict,
14
+ requirement_to_text,
15
+ )
16
+ from tests.fixtures import BaseTest
5
17
 
6
- class TestRequirementsRepository(TestCase):
7
- def test_parse_requirement(self):
8
- self.assertEqual(Requirement.from_text("foo"), Requirement(name="foo"))
9
- self.assertEqual(
10
- Requirement.from_text("foo==1.0.0"),
11
- Requirement(name="foo", version="1.0.0"),
12
- )
13
18
 
14
- def test_serialize_requirement(self):
15
- self.assertEqual(Requirement(name="foo").to_text(), "foo")
16
- self.assertEqual(
17
- Requirement(name="foo", version="1.0.0").to_text(), "foo==1.0.0"
18
- )
19
+ class TestRequirementHelperFunctions(TestCase):
20
+ def test_requirement_to_text(self):
21
+ req = Requirement("requests>=2.0.0")
22
+ self.assertEqual(requirement_to_text(req), "requests>=2.0.0")
19
23
 
20
- def test_parse_requirements(self):
21
- self.assertEqual(
22
- Requirements.from_text("foo\nbar==1.0.0"),
23
- Requirements(
24
- libraries=[
25
- Requirement(name="foo"),
26
- Requirement(name="bar", version="1.0.0"),
27
- ]
28
- ),
29
- )
24
+ def test_requirement_from_text_simple(self):
25
+ req = requirement_from_text("requests")
26
+ self.assertIsNotNone(req)
27
+ if req:
28
+ self.assertEqual(req.name, "requests")
30
29
 
31
- def test_serialize_requirements(self):
32
- self.assertEqual(
33
- Requirements(
34
- libraries=[
35
- Requirement(name="foo"),
36
- Requirement(name="bar", version="1.0.0"),
37
- ]
38
- ).to_text(),
39
- "foo\nbar==1.0.0",
40
- )
30
+ def test_requirement_from_text_with_version(self):
31
+ req = requirement_from_text("requests>=2.0.0")
32
+ self.assertIsNotNone(req)
33
+ if req:
34
+ self.assertEqual(req.name, "requests")
35
+ self.assertEqual(str(req.specifier), ">=2.0.0")
41
36
 
42
- def test_parse_empty_requirements(self):
43
- self.assertEqual(Requirements.from_text(""), Requirements(libraries=[]))
37
+ def test_requirement_from_text_with_extras(self):
38
+ req = requirement_from_text("requests[security]>=2.0.0")
39
+ self.assertIsNotNone(req)
40
+ if req:
41
+ self.assertEqual(req.name, "requests")
42
+ self.assertEqual(list(req.extras), ["security"])
44
43
 
45
- def test_parse_requirements_with_comments(self):
46
- self.assertEqual(
47
- Requirements.from_text("# foo\nfoo\n# bar\nbar==1.0.0"),
48
- Requirements(
49
- libraries=[
50
- Requirement(name="foo"),
51
- Requirement(name="bar", version="1.0.0"),
52
- ]
53
- ),
54
- )
44
+ def test_requirement_from_text_with_marker(self):
45
+ req = requirement_from_text('requests>=2.0.0; python_version>="3.8"')
46
+ self.assertIsNotNone(req)
47
+ if req:
48
+ self.assertEqual(req.name, "requests")
49
+ self.assertIsNotNone(req.marker)
55
50
 
56
- def test_parse_requirements_with_empty_lines(self):
57
- self.assertEqual(
58
- Requirements.from_text("\nfoo\n\nbar==1.0.0\n"),
59
- Requirements(
60
- libraries=[
61
- Requirement(name="foo"),
62
- Requirement(name="bar", version="1.0.0"),
63
- ]
64
- ),
65
- )
51
+ def test_requirement_from_text_with_url(self):
52
+ req = requirement_from_text("package @ https://example.com/package.zip")
53
+ self.assertIsNotNone(req)
54
+ if req:
55
+ self.assertEqual(req.name, "package")
56
+ self.assertEqual(req.url, "https://example.com/package.zip")
57
+
58
+ def test_requirement_from_text_empty(self):
59
+ req = requirement_from_text("")
60
+ self.assertIsNone(req)
61
+
62
+ def test_requirement_from_text_whitespace(self):
63
+ req = requirement_from_text(" ")
64
+ self.assertIsNone(req)
65
+
66
+ def test_requirement_from_text_invalid(self):
67
+ req = requirement_from_text("invalid@#$%requirement")
68
+ # This actually parses as a weird requirement, so let's use a truly invalid one
69
+ req = requirement_from_text("@@invalid@@")
70
+ self.assertIsNone(req)
71
+
72
+ def test_requirement_to_dict_simple(self):
73
+ req = Requirement("requests")
74
+ result = requirement_to_dict(req)
75
+ expected = {
76
+ "name": "requests",
77
+ "specifiers": [],
78
+ "extras": [],
79
+ "marker": None,
80
+ "url": None,
81
+ "raw_requirement": "requests",
82
+ "installed_version": None, # Assuming not installed in test
83
+ }
84
+ # Remove installed_version for comparison as it may vary
85
+ result_copy = result.copy()
86
+ result_copy.pop("installed_version")
87
+ expected.pop("installed_version")
88
+ self.assertEqual(result_copy, expected)
89
+
90
+ def test_requirement_to_dict_complex(self):
91
+ req = Requirement('requests[security]>=2.0.0,<3.0.0; python_version>="3.8"')
92
+ result = requirement_to_dict(req)
93
+
94
+ self.assertEqual(result["name"], "requests")
95
+ self.assertEqual(len(result["specifiers"]), 2)
96
+ self.assertIn({"operator": ">=", "version": "2.0.0"}, result["specifiers"])
97
+ self.assertIn({"operator": "<", "version": "3.0.0"}, result["specifiers"])
98
+ self.assertEqual(result["extras"], ["security"])
99
+ self.assertEqual(result["marker"], 'python_version >= "3.8"')
100
+ self.assertIsNone(result["url"])
101
+
102
+ def test_requirement_to_dict_with_url(self):
103
+ req = Requirement("package @ https://example.com/package.zip")
104
+ result = requirement_to_dict(req)
105
+
106
+ self.assertEqual(result["name"], "package")
107
+ self.assertEqual(result["url"], "https://example.com/package.zip")
108
+
109
+ def test_requirement_from_dict_simple(self):
110
+ data = {
111
+ "name": "requests",
112
+ "specifiers": [],
113
+ "extras": [],
114
+ "marker": None,
115
+ "url": None,
116
+ "raw_requirement": "requests",
117
+ }
118
+ req = requirement_from_dict(data)
119
+ self.assertEqual(req.name, "requests")
120
+ self.assertEqual(str(req.specifier), "")
121
+
122
+ def test_requirement_from_dict_with_specifiers(self):
123
+ data = {
124
+ "name": "requests",
125
+ "specifiers": [
126
+ {"operator": ">=", "version": "2.0.0"},
127
+ {"operator": "<", "version": "3.0.0"},
128
+ ],
129
+ "extras": [],
130
+ "marker": None,
131
+ "url": None,
132
+ "raw_requirement": "requests>=2.0.0,<3.0.0",
133
+ }
134
+ req = requirement_from_dict(data)
135
+ self.assertEqual(req.name, "requests")
136
+ # Order of specifiers may vary, so check if it contains both
137
+ spec_str = str(req.specifier)
138
+ self.assertIn(">=2.0.0", spec_str)
139
+ self.assertIn("<3.0.0", spec_str)
140
+
141
+ def test_requirement_from_dict_with_extras(self):
142
+ data = {
143
+ "name": "requests",
144
+ "specifiers": [],
145
+ "extras": ["security"],
146
+ "marker": None,
147
+ "url": None,
148
+ "raw_requirement": "requests[security]",
149
+ }
150
+ req = requirement_from_dict(data)
151
+ self.assertEqual(req.name, "requests")
152
+ self.assertEqual(list(req.extras), ["security"])
153
+
154
+ def test_requirement_from_dict_backward_compatibility(self):
155
+ # Test old format with version field
156
+ data = {
157
+ "name": "requests",
158
+ "version": "2.0.0",
159
+ }
160
+ req = requirement_from_dict(data)
161
+ self.assertEqual(req.name, "requests")
162
+ self.assertEqual(str(req.specifier), "==2.0.0")
163
+
164
+ def test_create_requirement_simple(self):
165
+ req = create_requirement("requests")
166
+ self.assertEqual(req.name, "requests")
167
+ self.assertEqual(str(req.specifier), "")
168
+
169
+ def test_create_requirement_with_version(self):
170
+ req = create_requirement("requests", "2.0.0")
171
+ self.assertEqual(req.name, "requests")
172
+ self.assertEqual(str(req.specifier), "==2.0.0")
173
+
174
+
175
+ class TestRequirements(TestCase):
176
+ def test_from_text_simple(self):
177
+ text = "requests==2.0.0\nflask>=1.0.0"
178
+ reqs = Requirements.from_text(text)
66
179
 
67
- def test_parse_requirements_bad_lines(self):
180
+ self.assertEqual(len(reqs.libraries), 2)
181
+ self.assertEqual(reqs.libraries[0].name, "requests")
182
+ self.assertEqual(reqs.libraries[1].name, "flask")
183
+
184
+ def test_from_text_with_comments(self):
185
+ text = """# This is a comment
186
+ requests==2.0.0 # inline comment
187
+ # Another comment
188
+ flask>=1.0.0"""
189
+ reqs = Requirements.from_text(text)
190
+
191
+ self.assertEqual(len(reqs.libraries), 2)
192
+ self.assertEqual(reqs.libraries[0].name, "requests")
193
+ self.assertEqual(reqs.libraries[1].name, "flask")
194
+
195
+ def test_from_text_with_empty_lines(self):
196
+ text = """requests==2.0.0
197
+
198
+ flask>=1.0.0
199
+
200
+ """
201
+ reqs = Requirements.from_text(text)
202
+
203
+ self.assertEqual(len(reqs.libraries), 2)
204
+
205
+ def test_from_text_with_pip_options(self):
206
+ text = """-r other-requirements.txt
207
+ --find-links https://example.com
208
+ requests==2.0.0
209
+ -e git+https://github.com/user/repo.git#egg=package
210
+ flask>=1.0.0"""
211
+ reqs = Requirements.from_text(text)
212
+
213
+ self.assertEqual(len(reqs.libraries), 2)
214
+ self.assertEqual(reqs.libraries[0].name, "requests")
215
+ self.assertEqual(reqs.libraries[1].name, "flask")
216
+
217
+ def test_from_text_with_url_requirement(self):
218
+ # Test the case mentioned in the issue
219
+ text = """discord.py==2.4.0
220
+ plotly==5.18.0
221
+ pandas==2.2.2
222
+ abstra==0.0.0
223
+ https://releases.abstra.cloud/abstra/git-client/latest.zip"""
224
+
225
+ reqs = Requirements.from_text(text)
226
+
227
+ # Should now have 5 requirements (including the URL)
228
+ self.assertEqual(len(reqs.libraries), 5)
229
+ names = [req.name for req in reqs.libraries]
230
+ self.assertIn("discord.py", names)
231
+ self.assertIn("plotly", names)
232
+ self.assertIn("pandas", names)
233
+ self.assertIn("abstra", names)
234
+
235
+ # Check that URL requirement was parsed
236
+ url_reqs = [req for req in reqs.libraries if req.url]
237
+ self.assertEqual(len(url_reqs), 1)
238
+ url_req = url_reqs[0]
68
239
  self.assertEqual(
69
- Requirements.from_text("foo\nbar==1.0.0\nas9sdkjcb##8du3789====3ng8ds7\n"),
70
- Requirements(
71
- libraries=[
72
- Requirement(name="foo"),
73
- Requirement(name="bar", version="1.0.0"),
74
- ]
75
- ),
240
+ url_req.url, "https://releases.abstra.cloud/abstra/git-client/latest.zip"
241
+ )
242
+ # The extracted name should be something reasonable
243
+ self.assertTrue(
244
+ url_req.name.startswith("latest")
245
+ or "abstra" in url_req.name
246
+ or "git-client" in url_req.name
76
247
  )
77
248
 
249
+ def test_from_text_with_proper_url_format(self):
250
+ text = """requests==2.0.0
251
+ package @ https://example.com/package.zip"""
252
+
253
+ reqs = Requirements.from_text(text)
254
+
255
+ self.assertEqual(len(reqs.libraries), 2)
256
+ self.assertEqual(reqs.libraries[0].name, "requests")
257
+ self.assertEqual(reqs.libraries[1].name, "package")
258
+ self.assertEqual(reqs.libraries[1].url, "https://example.com/package.zip")
259
+
260
+ def test_from_text_with_invalid_lines(self):
261
+ text = """requests==2.0.0
262
+ invalid@#$%line
263
+ flask>=1.0.0
264
+ another invalid line!!!"""
265
+
266
+ reqs = Requirements.from_text(text)
267
+
268
+ # Should skip invalid lines and continue
269
+ self.assertEqual(len(reqs.libraries), 2)
270
+ self.assertEqual(reqs.libraries[0].name, "requests")
271
+ self.assertEqual(reqs.libraries[1].name, "flask")
272
+
273
+ def test_to_text(self):
274
+ req1 = Requirement("requests==2.0.0")
275
+ req2 = Requirement("flask>=1.0.0")
276
+ reqs = Requirements(libraries=[req1, req2])
277
+
278
+ text = reqs.to_text()
279
+ lines = text.split("\n")
280
+
281
+ self.assertEqual(len(lines), 2)
282
+ self.assertIn("requests==2.0.0", lines)
283
+ self.assertIn("flask>=1.0.0", lines)
284
+
78
285
  def test_to_dict(self):
79
- self.assertEqual(
80
- Requirements(
81
- libraries=[
82
- Requirement(name="foo"),
83
- Requirement(name="bar", version="1.0.0"),
84
- ]
85
- ).to_dict(),
86
- [
87
- {"installed_version": None, "name": "foo", "version": None},
88
- {"installed_version": None, "name": "bar", "version": "1.0.0"},
89
- ],
90
- )
286
+ req1 = Requirement("requests==2.0.0")
287
+ req2 = Requirement("flask>=1.0.0")
288
+ reqs = Requirements(libraries=[req1, req2])
289
+
290
+ result = reqs.to_dict()
291
+
292
+ self.assertEqual(len(result), 2)
293
+ self.assertEqual(result[0]["name"], "requests")
294
+ self.assertEqual(result[1]["name"], "flask")
91
295
 
92
296
  def test_from_dict(self):
93
- self.assertEqual(
94
- Requirements.from_dict(
95
- [{"name": "foo", "version": None}, {"name": "bar", "version": "1.0.0"}]
96
- ),
97
- Requirements(
98
- libraries=[
99
- Requirement(name="foo"),
100
- Requirement(name="bar", version="1.0.0"),
101
- ]
102
- ),
103
- )
297
+ data = [
298
+ {
299
+ "name": "requests",
300
+ "specifiers": [{"operator": "==", "version": "2.0.0"}],
301
+ "extras": [],
302
+ "marker": None,
303
+ "url": None,
304
+ "raw_requirement": "requests==2.0.0",
305
+ },
306
+ {
307
+ "name": "flask",
308
+ "specifiers": [{"operator": ">=", "version": "1.0.0"}],
309
+ "extras": [],
310
+ "marker": None,
311
+ "url": None,
312
+ "raw_requirement": "flask>=1.0.0",
313
+ },
314
+ ]
315
+
316
+ reqs = Requirements.from_dict(data)
317
+
318
+ self.assertEqual(len(reqs.libraries), 2)
319
+ self.assertEqual(reqs.libraries[0].name, "requests")
320
+ self.assertEqual(reqs.libraries[1].name, "flask")
104
321
 
105
322
  def test_add(self):
106
- requirements = Requirements(libraries=[])
107
- requirements.add("foo")
108
- self.assertEqual(requirements.libraries, [Requirement(name="foo")])
109
- requirements.add("bar", "1.0.0")
110
- self.assertEqual(
111
- requirements.libraries,
112
- [Requirement(name="foo"), Requirement(name="bar", version="1.0.0")],
113
- )
323
+ reqs = Requirements(libraries=[])
324
+ reqs.add("requests", "2.0.0")
114
325
 
115
- def test_add_no_duplicates(self):
116
- requirements = Requirements(libraries=[Requirement(name="foo")])
117
- requirements.add("foo")
118
- self.assertEqual(requirements.libraries, [Requirement(name="foo")])
326
+ self.assertEqual(len(reqs.libraries), 1)
327
+ self.assertEqual(reqs.libraries[0].name, "requests")
119
328
 
120
- def test_valid_has_without_version(self):
121
- requirements = Requirements(libraries=[Requirement(name="foo")])
122
- self.assertTrue(requirements.has("foo"))
329
+ def test_add_duplicate(self):
330
+ req1 = Requirement("requests==2.0.0")
331
+ reqs = Requirements(libraries=[req1])
332
+ reqs.add("requests", "2.0.0")
123
333
 
124
- def test_valid_has_with_version(self):
125
- requirements = Requirements(
126
- libraries=[Requirement(name="foo", version="1.0.0")]
127
- )
128
- self.assertTrue(requirements.has("foo", "1.0.0"))
334
+ # Should not add duplicate
335
+ self.assertEqual(len(reqs.libraries), 1)
129
336
 
130
- def test_invalid_has_without_lib(self):
131
- requirements = Requirements(libraries=[Requirement(name="foo")])
132
- self.assertFalse(requirements.has("bar"))
337
+ def test_update(self):
338
+ req1 = Requirement("requests==1.0.0")
339
+ reqs = Requirements(libraries=[req1])
340
+ reqs.update("requests", "2.0.0")
133
341
 
134
- def test_invalid_has_with_wrong_version(self):
135
- requirements = Requirements(
136
- libraries=[Requirement(name="foo", version="1.0.0")]
137
- )
138
- self.assertFalse(requirements.has("foo", "1.0.1"))
342
+ self.assertEqual(len(reqs.libraries), 1)
343
+ self.assertEqual(str(reqs.libraries[0].specifier), "==2.0.0")
139
344
 
140
- def test_valid_ensure_with_version_from_specified_version(self):
141
- requirements = Requirements(
142
- libraries=[Requirement(name="foo", version="1.0.0")]
143
- )
144
- requirements.ensure("foo", "1.0.0")
145
- self.assertEqual(
146
- requirements.libraries, [Requirement(name="foo", version="1.0.0")]
147
- )
345
+ def test_delete(self):
346
+ req1 = Requirement("requests==2.0.0")
347
+ req2 = Requirement("flask>=1.0.0")
348
+ reqs = Requirements(libraries=[req1, req2])
349
+ reqs.delete("requests")
148
350
 
149
- def test_valid_ensure_with_version_from_non_specified_version(self):
150
- requirements = Requirements(libraries=[Requirement(name="foo")])
151
- requirements.ensure("foo", "1.0.1")
152
- self.assertEqual(
153
- requirements.libraries, [Requirement(name="foo", version="1.0.1")]
154
- )
351
+ self.assertEqual(len(reqs.libraries), 1)
352
+ self.assertEqual(reqs.libraries[0].name, "flask")
155
353
 
156
- def test_valid_ensure_without_version_from_specified_version(self):
157
- requirements = Requirements(
158
- libraries=[Requirement(name="foo", version="1.0.0")]
159
- )
160
- requirements.ensure("foo")
161
- self.assertEqual(
162
- requirements.libraries, [Requirement(name="foo", version="1.0.0")]
354
+ def test_has_simple(self):
355
+ req1 = Requirement("requests==2.0.0")
356
+ reqs = Requirements(libraries=[req1])
357
+
358
+ self.assertTrue(reqs.has("requests"))
359
+ self.assertFalse(reqs.has("flask"))
360
+
361
+ def test_has_with_version(self):
362
+ req1 = Requirement("requests==2.0.0")
363
+ reqs = Requirements(libraries=[req1])
364
+
365
+ self.assertTrue(reqs.has("requests", "2.0.0"))
366
+ self.assertFalse(reqs.has("requests", "1.0.0"))
367
+
368
+ def test_get_duplicates(self):
369
+ req1 = Requirement("requests==1.0.0")
370
+ req2 = Requirement("requests==2.0.0")
371
+ req3 = Requirement("flask>=1.0.0")
372
+ reqs = Requirements(libraries=[req1, req2, req3])
373
+
374
+ duplicates = reqs.get_duplicates()
375
+
376
+ self.assertEqual(len(duplicates), 1)
377
+ self.assertIn("requests", duplicates)
378
+ self.assertEqual(len(duplicates["requests"]), 2)
379
+
380
+ def test_ensure_new_package(self):
381
+ reqs = Requirements(libraries=[])
382
+ reqs.ensure("requests", "2.0.0")
383
+
384
+ self.assertEqual(len(reqs.libraries), 1)
385
+ self.assertEqual(reqs.libraries[0].name, "requests")
386
+
387
+ def test_ensure_existing_package_different_version(self):
388
+ req1 = Requirement("requests==1.0.0")
389
+ reqs = Requirements(libraries=[req1])
390
+ reqs.ensure("requests", "2.0.0")
391
+
392
+ self.assertEqual(len(reqs.libraries), 1)
393
+ self.assertEqual(str(reqs.libraries[0].specifier), "==2.0.0")
394
+
395
+ def test_ensure_existing_package_same_version(self):
396
+ req1 = Requirement("requests==2.0.0")
397
+ reqs = Requirements(libraries=[req1])
398
+ reqs.ensure("requests", "2.0.0")
399
+
400
+ # Should not change anything
401
+ self.assertEqual(len(reqs.libraries), 1)
402
+ self.assertEqual(str(reqs.libraries[0].specifier), "==2.0.0")
403
+
404
+
405
+ class TestRequirementRecommendation(TestCase):
406
+ def test_to_dict(self):
407
+ req = Requirement("requests==2.0.0")
408
+ recommendation = RequirementRecommendation(
409
+ requirement=req,
410
+ reason_file=Path("test.py"),
411
+ reason_line=10,
412
+ reason_code="import requests",
163
413
  )
164
414
 
165
- def test_valid_ensure_without_version_from_non_specified_version(self):
166
- requirements = Requirements(libraries=[Requirement(name="foo")])
167
- requirements.ensure("foo")
168
- self.assertEqual(requirements.libraries, [Requirement(name="foo")])
415
+ result = recommendation.to_dict()
416
+
417
+ self.assertEqual(result["name"], "requests")
418
+ self.assertEqual(result["reason_file"], "test.py")
419
+ self.assertEqual(result["reason_line"], 10)
420
+ self.assertEqual(result["reason_code"], "import requests")
169
421
 
170
- def test_valid_update_with_specified_version(self):
171
- requirements = Requirements(
172
- libraries=[Requirement(name="foo", version="0.0.0")]
422
+ def test_hash(self):
423
+ req = Requirement("requests==2.0.0")
424
+ recommendation1 = RequirementRecommendation(
425
+ requirement=req,
426
+ reason_file=Path("test.py"),
427
+ reason_line=10,
428
+ reason_code="import requests",
173
429
  )
174
- requirements.update("foo", "0.0.1")
175
- self.assertEqual(
176
- requirements.libraries, [Requirement(name="foo", version="0.0.1")]
430
+ recommendation2 = RequirementRecommendation(
431
+ requirement=req,
432
+ reason_file=Path("test.py"),
433
+ reason_line=10,
434
+ reason_code="import requests",
177
435
  )
178
436
 
179
- def test_valid_update_without_specified_version(self):
180
- requirements = Requirements(libraries=[Requirement(name="foo")])
181
- requirements.update("foo", "0.0.1")
182
- self.assertEqual(
183
- requirements.libraries, [Requirement(name="foo", version="0.0.1")]
184
- )
437
+ # Should have same hash for same data
438
+ self.assertEqual(hash(recommendation1), hash(recommendation2))
439
+
440
+
441
+ class TestRequirementsRepository(BaseTest):
442
+ def test_load_nonexistent_file(self):
443
+ # Ensure requirements.txt doesn't exist in test environment
444
+ requirements_txt = self.root / "requirements.txt"
445
+ if requirements_txt.exists():
446
+ requirements_txt.unlink()
447
+
448
+ reqs = RequirementsRepository.load()
449
+ self.assertEqual(len(reqs.libraries), 0)
450
+
451
+ def test_load_existing_file(self):
452
+ requirements_txt = self.root / "requirements.txt"
453
+ requirements_txt.write_text("requests==2.0.0\nflask>=1.0.0")
454
+
455
+ reqs = RequirementsRepository.load()
456
+
457
+ self.assertEqual(len(reqs.libraries), 2)
458
+ names = [req.name for req in reqs.libraries]
459
+ self.assertIn("requests", names)
460
+ self.assertIn("flask", names)
461
+
462
+ def test_save_and_load(self):
463
+ req1 = Requirement("requests==2.0.0")
464
+ req2 = Requirement("flask>=1.0.0")
465
+ reqs = Requirements(libraries=[req1, req2])
466
+
467
+ RequirementsRepository.save(reqs)
468
+ loaded_reqs = RequirementsRepository.load()
469
+
470
+ self.assertEqual(len(loaded_reqs.libraries), 2)
471
+ names = [req.name for req in loaded_reqs.libraries]
472
+ self.assertIn("requests", names)
473
+ self.assertIn("flask", names)
185
474
 
186
- def test_invalid_update_when_not_found(self):
187
- requirements = Requirements(libraries=[Requirement(name="foo")])
188
- requirements.update("bar", "0.0.1")
189
- self.assertEqual(requirements.libraries, [Requirement(name="foo")])
475
+ def test_get_file_path(self):
476
+ file_path = RequirementsRepository.get_file_path()
477
+ self.assertTrue(str(file_path).endswith("requirements.txt"))
190
478
 
191
- def test_is_installed_when_installed(self):
192
- req = Requirement(name="requests")
193
- self.assertIsNotNone(req.installed_version())
194
479
 
195
- def test_is_installed_when_not_installed(self):
196
- req = Requirement(name="not_installed")
197
- self.assertIsNone(req.installed_version())
480
+ class TestUrlRequirements(TestCase):
481
+ """Special test class for URL requirement handling."""
482
+
483
+ def test_url_formats_that_should_work(self):
484
+ """Test URL formats that packaging.requirements.Requirement supports."""
485
+ valid_urls = [
486
+ "package @ https://github.com/user/repo/archive/main.zip",
487
+ "package @ git+https://github.com/user/repo.git",
488
+ "package @ git+ssh://git@github.com/user/repo.git",
489
+ "package @ file:///absolute/path/to/package",
490
+ ]
491
+
492
+ for url_format in valid_urls:
493
+ with self.subTest(url_format=url_format):
494
+ req = requirement_from_text(url_format)
495
+ self.assertIsNotNone(req, f"Failed to parse: {url_format}")
496
+ if req:
497
+ self.assertIsNotNone(req.url, f"No URL found in: {url_format}")
498
+
499
+ def test_url_formats_that_currently_fail(self):
500
+ """Test URL formats that should now work with auto-conversion."""
501
+ # These are common formats found in requirements.txt files
502
+ problematic_urls = [
503
+ "https://github.com/user/repo/archive/main.zip",
504
+ "git+https://github.com/user/repo.git",
505
+ "https://files.pythonhosted.org/packages/source/p/package/package-1.0.tar.gz",
506
+ ]
507
+
508
+ for url_format in problematic_urls:
509
+ with self.subTest(url_format=url_format):
510
+ # These should now work when parsed through Requirements.from_text
511
+ reqs = Requirements.from_text(url_format)
512
+ self.assertEqual(
513
+ len(reqs.libraries), 1, f"Failed to parse: {url_format}"
514
+ )
515
+ req = reqs.libraries[0]
516
+ self.assertEqual(
517
+ req.url, url_format, f"URL not preserved: {url_format}"
518
+ )
519
+ self.assertIsNotNone(req.name, f"No name extracted: {url_format}")
520
+
521
+ def test_requirements_from_text_with_mixed_formats(self):
522
+ """Test parsing requirements.txt with mixed URL formats."""
523
+ text = """# Regular packages
524
+ requests==2.0.0
525
+ flask>=1.0.0
526
+
527
+ # Proper URL format (should work)
528
+ custom-package @ https://github.com/user/repo/archive/main.zip
529
+
530
+ # Raw URL (currently doesn't work)
531
+ https://github.com/another/repo/archive/main.zip
532
+
533
+ # Git URL (currently doesn't work)
534
+ git+https://github.com/third/repo.git
535
+ """
536
+
537
+ reqs = Requirements.from_text(text)
538
+
539
+ # Should now find 5 requirements (2 regular + 3 URL formats)
540
+ # All URL formats should now be supported
541
+ self.assertEqual(len(reqs.libraries), 5)
542
+
543
+ names = [req.name for req in reqs.libraries]
544
+ self.assertIn("requests", names)
545
+ self.assertIn("flask", names)
546
+ self.assertIn("custom-package", names)
547
+
548
+ # Check that all URL requirements were parsed
549
+ url_reqs = [req for req in reqs.libraries if req.url]
550
+ self.assertEqual(len(url_reqs), 3)
551
+
552
+ # Find the specific URL requirement
553
+ custom_package_req = next(
554
+ (req for req in reqs.libraries if req.name == "custom-package"), None
555
+ )
556
+ self.assertIsNotNone(custom_package_req)
557
+ if custom_package_req:
558
+ self.assertEqual(
559
+ custom_package_req.url, "https://github.com/user/repo/archive/main.zip"
560
+ )
198
561
 
199
- def test_is_installed_when_installed_with_different_version(self):
200
- req = Requirement(name="requests", version="0.0.1234")
201
- self.assertNotEqual(req.installed_version(), req.version)
562
+ # Check that auto-converted URLs are present
563
+ urls = [req.url for req in url_reqs]
564
+ self.assertIn("https://github.com/another/repo/archive/main.zip", urls)
565
+ self.assertIn("git+https://github.com/third/repo.git", urls)