porringer 0.2.1.dev90__tar.gz → 0.2.1.dev91__tar.gz

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 (340) hide show
  1. porringer-0.2.1.dev91/PKG-INFO +87 -0
  2. porringer-0.2.1.dev91/README.md +69 -0
  3. porringer-0.2.1.dev91/porringer/__init__.py +60 -0
  4. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/api.py +45 -25
  5. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/__init__.py +2 -0
  6. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/backend.py +14 -13
  7. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/builder.py +43 -13
  8. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/cache.py +10 -14
  9. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/command/__init__.py +2 -0
  10. porringer-0.2.1.dev91/porringer/backend/command/client.py +46 -0
  11. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/command/core/__init__.py +2 -0
  12. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/command/core/action_builder.py +118 -118
  13. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/command/core/discovery.py +28 -7
  14. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/command/core/execution.py +429 -559
  15. porringer-0.2.1.dev91/porringer/backend/command/core/inspection.py +386 -0
  16. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/command/core/phase.py +7 -20
  17. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/command/core/presence.py +27 -25
  18. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/command/core/resolution.py +121 -80
  19. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/command/manifest.py +8 -6
  20. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/command/package.py +104 -130
  21. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/command/plugin.py +30 -29
  22. porringer-0.2.1.dev91/porringer/backend/command/profile.py +147 -0
  23. porringer-0.2.1.dev91/porringer/backend/command/project.py +317 -0
  24. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/command/self.py +2 -0
  25. porringer-0.2.1.dev91/porringer/backend/command/sync.py +594 -0
  26. porringer-0.2.1.dev91/porringer/backend/command/tool.py +268 -0
  27. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/resolver.py +3 -1
  28. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/backend/schema.py +5 -3
  29. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/console/__init__.py +2 -0
  30. porringer-0.2.1.dev91/porringer/console/command/__init__.py +3 -0
  31. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/console/command/cache.py +20 -16
  32. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/console/command/check.py +8 -6
  33. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/console/command/download.py +12 -22
  34. porringer-0.2.1.dev91/porringer/console/command/env.py +275 -0
  35. porringer-0.2.1.dev91/porringer/console/command/install.py +261 -0
  36. porringer-0.2.1.dev91/porringer/console/command/open.py +44 -0
  37. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/console/command/package.py +7 -5
  38. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/console/command/plugin.py +47 -42
  39. porringer-0.2.1.dev91/porringer/console/command/preview.py +301 -0
  40. porringer-0.2.1.dev91/porringer/console/command/schema.py +79 -0
  41. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/console/command/self.py +8 -6
  42. porringer-0.2.1.dev91/porringer/console/command/sync.py +377 -0
  43. porringer-0.2.1.dev91/porringer/console/common.py +49 -0
  44. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/console/entry.py +33 -8
  45. porringer-0.2.1.dev91/porringer/console/output.py +113 -0
  46. porringer-0.2.1.dev91/porringer/console/schema.py +56 -0
  47. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/core/__init__.py +2 -0
  48. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/core/path.py +1 -3
  49. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/core/plugin_schema/__init__.py +4 -1
  50. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/core/plugin_schema/environment.py +163 -47
  51. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/core/plugin_schema/manifest.py +3 -1
  52. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/core/plugin_schema/plugin_manager.py +40 -49
  53. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/core/plugin_schema/project_environment.py +130 -6
  54. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/core/plugin_schema/python_environment.py +20 -8
  55. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/core/plugin_schema/runtime.py +16 -0
  56. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/core/plugin_schema/scm.py +4 -2
  57. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/core/plugin_schema/tool_based.py +83 -94
  58. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/core/schema.py +84 -18
  59. porringer-0.2.1.dev91/porringer/core/target.py +100 -0
  60. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/__init__.py +2 -0
  61. porringer-0.2.1.dev91/porringer/plugin/apt/__init__.py +3 -0
  62. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/apt/plugin.py +4 -18
  63. porringer-0.2.1.dev91/porringer/plugin/brew/__init__.py +3 -0
  64. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/brew/plugin.py +29 -15
  65. porringer-0.2.1.dev91/porringer/plugin/git/__init__.py +3 -0
  66. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/git/plugin.py +2 -0
  67. porringer-0.2.1.dev91/porringer/plugin/npm/__init__.py +3 -0
  68. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/npm/plugin.py +6 -8
  69. porringer-0.2.1.dev91/porringer/plugin/npm_project/__init__.py +3 -0
  70. porringer-0.2.1.dev91/porringer/plugin/npm_project/plugin.py +24 -0
  71. porringer-0.2.1.dev91/porringer/plugin/pdm/__init__.py +3 -0
  72. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/pdm/plugin.py +8 -3
  73. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/pim/__init__.py +2 -0
  74. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/pim/plugin.py +157 -52
  75. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/pip/__init__.py +2 -0
  76. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/pip/plugin.py +66 -174
  77. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/pipx/__init__.py +2 -0
  78. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/pipx/plugin.py +14 -3
  79. porringer-0.2.1.dev91/porringer/plugin/pnpm/__init__.py +3 -0
  80. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/pnpm/plugin.py +5 -4
  81. porringer-0.2.1.dev91/porringer/plugin/pnpm_project/__init__.py +3 -0
  82. porringer-0.2.1.dev91/porringer/plugin/pnpm_project/plugin.py +28 -0
  83. porringer-0.2.1.dev91/porringer/plugin/poetry/__init__.py +3 -0
  84. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/poetry/plugin.py +24 -5
  85. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/pyenv/__init__.py +2 -0
  86. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/pyenv/plugin.py +15 -10
  87. porringer-0.2.1.dev91/porringer/plugin/uv/__init__.py +3 -0
  88. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/uv/plugin.py +3 -1
  89. porringer-0.2.1.dev91/porringer/plugin/uv_project/__init__.py +3 -0
  90. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/uv_project/plugin.py +4 -0
  91. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/winget/__init__.py +2 -0
  92. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/plugin/winget/plugin.py +3 -7
  93. porringer-0.2.1.dev91/porringer/plugin/yarn_project/__init__.py +3 -0
  94. porringer-0.2.1.dev91/porringer/plugin/yarn_project/plugin.py +32 -0
  95. porringer-0.2.1.dev91/porringer/schema/__init__.py +207 -0
  96. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/schema/cache.py +16 -3
  97. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/schema/check.py +14 -2
  98. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/schema/download.py +7 -3
  99. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/schema/execution.py +58 -15
  100. porringer-0.2.1.dev91/porringer/schema/inspection.py +150 -0
  101. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/schema/manifest.py +13 -69
  102. porringer-0.2.1.dev91/porringer/schema/observability.py +162 -0
  103. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/schema/plugin.py +3 -1
  104. porringer-0.2.1.dev91/porringer/schema/profile.py +66 -0
  105. porringer-0.2.1.dev91/porringer/schema/progress.py +331 -0
  106. porringer-0.2.1.dev91/porringer/schema/project.py +84 -0
  107. porringer-0.2.1.dev91/porringer/schema/snapshot.py +25 -0
  108. porringer-0.2.1.dev91/porringer/schema/tool.py +57 -0
  109. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/test/mock/__init__.py +2 -0
  110. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/test/mock/environment.py +5 -3
  111. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/test/mock/plugin_manager.py +16 -14
  112. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/test/mock/project_environment.py +3 -1
  113. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/test/mock/scm.py +2 -0
  114. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/test/mock/subprocess.py +2 -0
  115. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/test/pytest/__init__.py +2 -0
  116. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/test/pytest/plugin.py +3 -1
  117. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/test/pytest/shared.py +9 -7
  118. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/test/pytest/tests.py +89 -12
  119. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/test/pytest/variants.py +5 -3
  120. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/utility/__init__.py +2 -0
  121. porringer-0.2.1.dev91/porringer/utility/concurrency.py +57 -0
  122. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/utility/download.py +90 -75
  123. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/utility/exception.py +12 -10
  124. porringer-0.2.1.dev91/porringer/utility/observability.py +456 -0
  125. porringer-0.2.1.dev91/porringer/utility/tool_environment.py +69 -0
  126. porringer-0.2.1.dev91/porringer/utility/trace.py +228 -0
  127. porringer-0.2.1.dev91/porringer/utility/utility.py +297 -0
  128. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/pyproject.toml +35 -25
  129. porringer-0.2.1.dev91/tests/__init__.py +3 -0
  130. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/conftest.py +58 -60
  131. porringer-0.2.1.dev91/tests/fixtures/__init__.py +3 -0
  132. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/fixtures/api.py +5 -4
  133. porringer-0.2.1.dev91/tests/fixtures/command_process.py +154 -0
  134. porringer-0.2.1.dev91/tests/fixtures/disposable_environment.py +180 -0
  135. porringer-0.2.1.dev91/tests/fixtures/factories.py +134 -0
  136. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/fixtures/http.py +2 -0
  137. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/fixtures/mock_plugins.py +6 -70
  138. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/fixtures/packages.py +2 -0
  139. porringer-0.2.1.dev91/tests/fixtures/smoke_packages.py +117 -0
  140. porringer-0.2.1.dev91/tests/fixtures/strategies.py +161 -0
  141. porringer-0.2.1.dev91/tests/fixtures/tool_smoke.py +50 -0
  142. porringer-0.2.1.dev91/tests/integration/__init__.py +3 -0
  143. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/integration/plugins/__init__.py +2 -0
  144. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/integration/plugins/winget/__init__.py +2 -0
  145. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/integration/plugins/winget/test_environment.py +5 -3
  146. porringer-0.2.1.dev91/tests/integration/test_bootstrap_presence.py +120 -0
  147. porringer-0.2.1.dev91/tests/integration/test_dry_run_acceptance.py +64 -0
  148. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/integration/test_example_bootstrap.py +18 -15
  149. porringer-0.2.1.dev91/tests/integration/test_example_presence.py +55 -0
  150. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/integration/test_frozen_app_presence.py +35 -31
  151. porringer-0.2.1.dev91/tests/integration/test_minimal_path.py +49 -0
  152. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/__init__.py +2 -0
  153. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/__init__.py +2 -0
  154. porringer-0.2.1.dev91/tests/unit/plugins/apt/__init__.py +3 -0
  155. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/apt/test_environment.py +5 -3
  156. porringer-0.2.1.dev91/tests/unit/plugins/brew/__init__.py +3 -0
  157. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/brew/test_environment.py +5 -3
  158. porringer-0.2.1.dev91/tests/unit/plugins/git/__init__.py +3 -0
  159. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/git/test_clone_detection.py +2 -0
  160. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/git/test_scm.py +2 -0
  161. porringer-0.2.1.dev91/tests/unit/plugins/npm/__init__.py +3 -0
  162. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/npm/test_environment.py +5 -3
  163. porringer-0.2.1.dev91/tests/unit/plugins/npm_project/__init__.py +3 -0
  164. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/npm_project/test_environment.py +2 -0
  165. porringer-0.2.1.dev91/tests/unit/plugins/pdm/__init__.py +3 -0
  166. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/pdm/test_environment.py +2 -0
  167. porringer-0.2.1.dev91/tests/unit/plugins/pim/__init__.py +3 -0
  168. porringer-0.2.1.dev91/tests/unit/plugins/pim/test_environment.py +206 -0
  169. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/pip/__init__.py +2 -0
  170. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/pip/conftest.py +2 -0
  171. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/pip/test_auxiliary_tools.py +9 -9
  172. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/pip/test_environment.py +4 -2
  173. porringer-0.2.1.dev91/tests/unit/plugins/pipx/__init__.py +3 -0
  174. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/pipx/test_environment.py +5 -3
  175. porringer-0.2.1.dev91/tests/unit/plugins/pnpm/__init__.py +3 -0
  176. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/pnpm/test_environment.py +5 -3
  177. porringer-0.2.1.dev91/tests/unit/plugins/pnpm_project/__init__.py +3 -0
  178. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/pnpm_project/test_environment.py +2 -0
  179. porringer-0.2.1.dev91/tests/unit/plugins/poetry/__init__.py +3 -0
  180. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/poetry/test_environment.py +2 -0
  181. porringer-0.2.1.dev91/tests/unit/plugins/pyenv/__init__.py +3 -0
  182. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/pyenv/test_environment.py +2 -0
  183. porringer-0.2.1.dev91/tests/unit/plugins/uv/__init__.py +3 -0
  184. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/uv/test_environment.py +5 -3
  185. porringer-0.2.1.dev91/tests/unit/plugins/uv_project/__init__.py +3 -0
  186. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/uv_project/test_environment.py +2 -0
  187. porringer-0.2.1.dev91/tests/unit/plugins/yarn_project/__init__.py +3 -0
  188. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/plugins/yarn_project/test_environment.py +2 -0
  189. porringer-0.2.1.dev91/tests/unit/test_action_filtering.py +145 -0
  190. porringer-0.2.1.dev91/tests/unit/test_action_progress.py +421 -0
  191. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_backend_resolver.py +9 -9
  192. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_bootstrap_cross_platform.py +6 -4
  193. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_cache.py +22 -20
  194. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_check.py +14 -12
  195. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_check_updates.py +7 -63
  196. porringer-0.2.1.dev91/tests/unit/test_cli.py +330 -0
  197. porringer-0.2.1.dev91/tests/unit/test_command_plugin.py +311 -0
  198. porringer-0.2.1.dev91/tests/unit/test_command_process.py +81 -0
  199. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_command_self.py +11 -9
  200. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_concurrency_and_client.py +145 -44
  201. porringer-0.2.1.dev91/tests/unit/test_console_output.py +108 -0
  202. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_deferred_resolution.py +2 -0
  203. porringer-0.2.1.dev91/tests/unit/test_disposable_environment.py +74 -0
  204. porringer-0.2.1.dev91/tests/unit/test_docs_contract.py +87 -0
  205. porringer-0.2.1.dev91/tests/unit/test_downstream_api.py +329 -0
  206. porringer-0.2.1.dev91/tests/unit/test_event_loop_safety.py +88 -0
  207. porringer-0.2.1.dev91/tests/unit/test_example_manifests.py +21 -0
  208. porringer-0.2.1.dev91/tests/unit/test_extension_list.py +78 -0
  209. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_extras_introspection.py +4 -1
  210. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_frozen_app_detection.py +2 -0
  211. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_frozen_presence_resolution.py +2 -0
  212. porringer-0.2.1.dev91/tests/unit/test_hermeticity.py +32 -0
  213. porringer-0.2.1.dev91/tests/unit/test_install_cli.py +135 -0
  214. porringer-0.2.1.dev91/tests/unit/test_manifest_discovery.py +323 -0
  215. porringer-0.2.1.dev91/tests/unit/test_manifest_inspect.py +179 -0
  216. porringer-0.2.1.dev91/tests/unit/test_manifest_loading.py +443 -0
  217. porringer-0.2.1.dev91/tests/unit/test_manifest_schema.py +526 -0
  218. porringer-0.2.1.dev91/tests/unit/test_manifest_validation.py +191 -0
  219. porringer-0.2.1.dev91/tests/unit/test_output_schema_roundtrip.py +243 -0
  220. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_package_ref.py +2 -0
  221. porringer-0.2.1.dev91/tests/unit/test_package_ref_invariants.py +96 -0
  222. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_package_relation.py +24 -10
  223. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_path_sync.py +2 -0
  224. porringer-0.2.1.dev91/tests/unit/test_pipx_bootstrap_contract.py +108 -0
  225. porringer-0.2.1.dev91/tests/unit/test_plugin_aux_and_errors.py +107 -0
  226. porringer-0.2.1.dev91/tests/unit/test_plugin_commands.py +98 -0
  227. porringer-0.2.1.dev91/tests/unit/test_plugin_dry_run_no_subprocess.py +67 -0
  228. porringer-0.2.1.dev91/tests/unit/test_plugin_lifecycle.py +397 -0
  229. porringer-0.2.1.dev91/tests/unit/test_plugin_manager.py +254 -0
  230. porringer-0.2.1.dev91/tests/unit/test_plugin_manager_presence.py +302 -0
  231. porringer-0.2.1.dev91/tests/unit/test_plugin_manager_upgrade.py +770 -0
  232. porringer-0.2.1.dev91/tests/unit/test_plugin_protocol.py +254 -0
  233. porringer-0.2.1.dev91/tests/unit/test_plugin_runtime.py +880 -0
  234. porringer-0.2.1.dev91/tests/unit/test_plugin_tags.py +776 -0
  235. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_presence_detection.py +2 -0
  236. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_project_directory.py +93 -51
  237. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_project_root.py +33 -27
  238. porringer-0.2.1.dev91/tests/unit/test_run_command_progress.py +62 -0
  239. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_runtime_context_seeding.py +2 -0
  240. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_runtime_propagation.py +36 -2
  241. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_runtime_updates.py +2 -0
  242. porringer-0.2.1.dev91/tests/unit/test_smoke_packages.py +26 -0
  243. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_sub_action_progress.py +148 -36
  244. porringer-0.2.1.dev91/tests/unit/test_sync_inspection.py +277 -0
  245. porringer-0.2.1.dev91/tests/unit/test_target_resolution.py +55 -0
  246. porringer-0.2.1.dev91/tests/unit/test_trace.py +161 -0
  247. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_uninstall.py +21 -19
  248. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_update_detection.py +84 -383
  249. porringer-0.2.1.dev91/tests/unit/test_update_detection_resolution.py +149 -0
  250. porringer-0.2.1.dev91/tests/unit/test_update_detection_spec.py +87 -0
  251. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/tests/unit/test_upgrade.py +6 -5
  252. porringer-0.2.1.dev91/tests/unit/update_detection_helpers.py +57 -0
  253. porringer-0.2.1.dev90/PKG-INFO +0 -54
  254. porringer-0.2.1.dev90/README.md +0 -38
  255. porringer-0.2.1.dev90/porringer/__init__.py +0 -21
  256. porringer-0.2.1.dev90/porringer/backend/command/core/wsl_overlay.py +0 -42
  257. porringer-0.2.1.dev90/porringer/backend/command/sync.py +0 -482
  258. porringer-0.2.1.dev90/porringer/console/command/__init__.py +0 -1
  259. porringer-0.2.1.dev90/porringer/console/command/schema.py +0 -58
  260. porringer-0.2.1.dev90/porringer/console/command/sync.py +0 -534
  261. porringer-0.2.1.dev90/porringer/console/schema.py +0 -39
  262. porringer-0.2.1.dev90/porringer/core/transport.py +0 -89
  263. porringer-0.2.1.dev90/porringer/plugin/apt/__init__.py +0 -1
  264. porringer-0.2.1.dev90/porringer/plugin/brew/__init__.py +0 -1
  265. porringer-0.2.1.dev90/porringer/plugin/bun/__init__.py +0 -1
  266. porringer-0.2.1.dev90/porringer/plugin/bun/plugin.py +0 -90
  267. porringer-0.2.1.dev90/porringer/plugin/bun_project/__init__.py +0 -1
  268. porringer-0.2.1.dev90/porringer/plugin/bun_project/plugin.py +0 -36
  269. porringer-0.2.1.dev90/porringer/plugin/deno/__init__.py +0 -1
  270. porringer-0.2.1.dev90/porringer/plugin/deno/plugin.py +0 -161
  271. porringer-0.2.1.dev90/porringer/plugin/deno_project/__init__.py +0 -1
  272. porringer-0.2.1.dev90/porringer/plugin/deno_project/plugin.py +0 -64
  273. porringer-0.2.1.dev90/porringer/plugin/git/__init__.py +0 -1
  274. porringer-0.2.1.dev90/porringer/plugin/npm/__init__.py +0 -1
  275. porringer-0.2.1.dev90/porringer/plugin/npm_project/__init__.py +0 -1
  276. porringer-0.2.1.dev90/porringer/plugin/npm_project/plugin.py +0 -34
  277. porringer-0.2.1.dev90/porringer/plugin/pdm/__init__.py +0 -1
  278. porringer-0.2.1.dev90/porringer/plugin/pnpm/__init__.py +0 -1
  279. porringer-0.2.1.dev90/porringer/plugin/pnpm_project/__init__.py +0 -1
  280. porringer-0.2.1.dev90/porringer/plugin/pnpm_project/plugin.py +0 -61
  281. porringer-0.2.1.dev90/porringer/plugin/poetry/__init__.py +0 -1
  282. porringer-0.2.1.dev90/porringer/plugin/uv/__init__.py +0 -1
  283. porringer-0.2.1.dev90/porringer/plugin/uv_project/__init__.py +0 -1
  284. porringer-0.2.1.dev90/porringer/plugin/wsl/__init__.py +0 -6
  285. porringer-0.2.1.dev90/porringer/plugin/wsl/transport.py +0 -71
  286. porringer-0.2.1.dev90/porringer/plugin/wsl/utility.py +0 -122
  287. porringer-0.2.1.dev90/porringer/plugin/yarn_project/__init__.py +0 -1
  288. porringer-0.2.1.dev90/porringer/plugin/yarn_project/plugin.py +0 -66
  289. porringer-0.2.1.dev90/porringer/schema/__init__.py +0 -111
  290. porringer-0.2.1.dev90/porringer/schema/config.py +0 -14
  291. porringer-0.2.1.dev90/porringer/schema/progress.py +0 -157
  292. porringer-0.2.1.dev90/porringer/utility/utility.py +0 -226
  293. porringer-0.2.1.dev90/tests/__init__.py +0 -5
  294. porringer-0.2.1.dev90/tests/fixtures/__init__.py +0 -6
  295. porringer-0.2.1.dev90/tests/fixtures/manifests.py +0 -160
  296. porringer-0.2.1.dev90/tests/integration/__init__.py +0 -5
  297. porringer-0.2.1.dev90/tests/integration/plugins/git/__init__.py +0 -1
  298. porringer-0.2.1.dev90/tests/integration/plugins/git/test_scm.py +0 -20
  299. porringer-0.2.1.dev90/tests/integration/plugins/pip/__init__.py +0 -5
  300. porringer-0.2.1.dev90/tests/integration/plugins/pip/test_environment.py +0 -20
  301. porringer-0.2.1.dev90/tests/integration/plugins/pipx/__init__.py +0 -5
  302. porringer-0.2.1.dev90/tests/integration/plugins/pipx/test_environment.py +0 -20
  303. porringer-0.2.1.dev90/tests/integration/test_bare_environment.py +0 -49
  304. porringer-0.2.1.dev90/tests/integration/test_bootstrap_presence.py +0 -115
  305. porringer-0.2.1.dev90/tests/integration/test_example_presence.py +0 -42
  306. porringer-0.2.1.dev90/tests/unit/plugins/apt/__init__.py +0 -1
  307. porringer-0.2.1.dev90/tests/unit/plugins/brew/__init__.py +0 -1
  308. porringer-0.2.1.dev90/tests/unit/plugins/bun/__init__.py +0 -1
  309. porringer-0.2.1.dev90/tests/unit/plugins/bun/test_environment.py +0 -20
  310. porringer-0.2.1.dev90/tests/unit/plugins/bun_project/__init__.py +0 -1
  311. porringer-0.2.1.dev90/tests/unit/plugins/bun_project/test_environment.py +0 -16
  312. porringer-0.2.1.dev90/tests/unit/plugins/deno/__init__.py +0 -1
  313. porringer-0.2.1.dev90/tests/unit/plugins/deno/test_environment.py +0 -20
  314. porringer-0.2.1.dev90/tests/unit/plugins/deno_project/__init__.py +0 -1
  315. porringer-0.2.1.dev90/tests/unit/plugins/deno_project/test_environment.py +0 -16
  316. porringer-0.2.1.dev90/tests/unit/plugins/git/__init__.py +0 -1
  317. porringer-0.2.1.dev90/tests/unit/plugins/npm/__init__.py +0 -1
  318. porringer-0.2.1.dev90/tests/unit/plugins/npm_project/__init__.py +0 -1
  319. porringer-0.2.1.dev90/tests/unit/plugins/pdm/__init__.py +0 -1
  320. porringer-0.2.1.dev90/tests/unit/plugins/pim/__init__.py +0 -1
  321. porringer-0.2.1.dev90/tests/unit/plugins/pim/test_environment.py +0 -83
  322. porringer-0.2.1.dev90/tests/unit/plugins/pipx/__init__.py +0 -1
  323. porringer-0.2.1.dev90/tests/unit/plugins/pnpm/__init__.py +0 -1
  324. porringer-0.2.1.dev90/tests/unit/plugins/pnpm_project/__init__.py +0 -1
  325. porringer-0.2.1.dev90/tests/unit/plugins/poetry/__init__.py +0 -1
  326. porringer-0.2.1.dev90/tests/unit/plugins/pyenv/__init__.py +0 -1
  327. porringer-0.2.1.dev90/tests/unit/plugins/uv/__init__.py +0 -1
  328. porringer-0.2.1.dev90/tests/unit/plugins/uv_project/__init__.py +0 -1
  329. porringer-0.2.1.dev90/tests/unit/plugins/yarn_project/__init__.py +0 -1
  330. porringer-0.2.1.dev90/tests/unit/test_cli.py +0 -98
  331. porringer-0.2.1.dev90/tests/unit/test_command_plugin.py +0 -1882
  332. porringer-0.2.1.dev90/tests/unit/test_event_loop_safety.py +0 -87
  333. porringer-0.2.1.dev90/tests/unit/test_manifest.py +0 -1461
  334. porringer-0.2.1.dev90/tests/unit/test_package_filtering.py +0 -194
  335. porringer-0.2.1.dev90/tests/unit/test_plugin_filtering.py +0 -219
  336. porringer-0.2.1.dev90/tests/unit/test_plugin_manager.py +0 -1333
  337. porringer-0.2.1.dev90/tests/unit/test_wsl.py +0 -906
  338. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/LICENSE.md +0 -0
  339. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/py.typed +0 -0
  340. {porringer-0.2.1.dev90 → porringer-0.2.1.dev91}/porringer/utility/py.typed +0 -0
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.1
2
+ Name: porringer
3
+ Version: 0.2.1.dev91
4
+ Author-Email: Synodic Software <contact@synodic.software>
5
+ License: MIT
6
+ Project-URL: homepage, https://github.com/synodic/porringer
7
+ Project-URL: repository, https://github.com/synodic/porringer
8
+ Requires-Python: >=3.14
9
+ Requires-Dist: typer[all]>=0.26.8
10
+ Requires-Dist: pydantic>=2.13.4
11
+ Requires-Dist: platformdirs>=4.10.0
12
+ Requires-Dist: userpath>=1.9.2
13
+ Requires-Dist: packaging>=26.2
14
+ Requires-Dist: aiohttp>=3.14.1
15
+ Requires-Dist: stamina>=26.1.0
16
+ Requires-Dist: filelock>=3.29.4
17
+ Description-Content-Type: text/markdown
18
+
19
+ <p align="center">
20
+ <picture>
21
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/synodic/porringer/development/docs/images/porringer-dark.svg">
22
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/synodic/porringer/development/docs/images/porringer-light.svg">
23
+ <img width=25% alt="Porringer Logo" src="https://raw.githubusercontent.com/synodic/porringer/development/docs/images/porringer-light.svg">
24
+ </picture>
25
+ <br>
26
+ <em>A meta-package manager for all porridges</em>
27
+ </p>
28
+
29
+ # Porringer
30
+
31
+ A CLI and Python API for synchronizing developer environments from a declarative manifest. Porringer reads the packages, tools, runtimes, repositories, and project sync work you want, then delegates the actual work to installed package managers through plugins.
32
+
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.md)
34
+ [![PyPI version](https://img.shields.io/pypi/v/porringer.svg)](https://pypi.org/project/porringer/)
35
+
36
+ ## Features
37
+
38
+ - Manifest-driven setup from `porringer.json` or `[tool.porringer]` in `pyproject.toml`.
39
+ - Plugin-based installers for Python, Node, system packages, project sync, runtimes, and source repositories.
40
+ - Read-only previews with stable action IDs, command previews, diagnostics, and JSON output.
41
+ - Sync strategies for minimal installs, latest allowed versions, or exact declared constraints.
42
+ - JSONL progress streams, replay records, and trace artifacts for downstream clients.
43
+ - Python APIs for inspection, execution, cached projects, profiles, tools, progress events, and client snapshots.
44
+
45
+ ## Quick Start
46
+
47
+ Install Porringer into an isolated CLI environment, then preview a manifest before running it:
48
+
49
+ ```shell
50
+ pipx install porringer
51
+ porringer preview examples/python-dev
52
+ porringer install examples/python-dev
53
+ ```
54
+
55
+ See [Setup](https://synodic.github.io/porringer/setup/) for `uv`, `pip`, and development checkout instructions.
56
+
57
+ ## Development
58
+
59
+ Porringer uses [PDM](https://pdm-project.org/en/latest/) for local development. Common tasks are defined in `pyproject.toml` under `[tool.pdm.scripts]`:
60
+
61
+ ```shell
62
+ pdm install
63
+ pdm run test
64
+ pdm run generate
65
+ ```
66
+
67
+ See [Development](https://synodic.github.io/porringer/development/) for test lanes, traces, smoke tests, and diagnostics.
68
+
69
+ For contribution guidelines, see [CONTRIBUTING.md](https://github.com/synodic/.github/blob/stable/CONTRIBUTING.md).
70
+
71
+ ## Documentation
72
+
73
+ - [Documentation home](https://synodic.github.io/porringer/)
74
+ - [Setup](https://synodic.github.io/porringer/setup/)
75
+ - [Install links](https://synodic.github.io/porringer/links/)
76
+ - [Install command](https://synodic.github.io/porringer/install/)
77
+ - [Preview command](https://synodic.github.io/porringer/preview/)
78
+ - [Check command](https://synodic.github.io/porringer/check/)
79
+ - [Download command](https://synodic.github.io/porringer/download/)
80
+ - [API surface](https://synodic.github.io/porringer/api/)
81
+ - [Development](https://synodic.github.io/porringer/development/)
82
+
83
+ ## License
84
+
85
+ This project is licensed under the MIT License. See [LICENSE.md](LICENSE.md) for details.
86
+
87
+ Copyright © 2025 Synodic Software
@@ -0,0 +1,69 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/synodic/porringer/development/docs/images/porringer-dark.svg">
4
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/synodic/porringer/development/docs/images/porringer-light.svg">
5
+ <img width=25% alt="Porringer Logo" src="https://raw.githubusercontent.com/synodic/porringer/development/docs/images/porringer-light.svg">
6
+ </picture>
7
+ <br>
8
+ <em>A meta-package manager for all porridges</em>
9
+ </p>
10
+
11
+ # Porringer
12
+
13
+ A CLI and Python API for synchronizing developer environments from a declarative manifest. Porringer reads the packages, tools, runtimes, repositories, and project sync work you want, then delegates the actual work to installed package managers through plugins.
14
+
15
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.md)
16
+ [![PyPI version](https://img.shields.io/pypi/v/porringer.svg)](https://pypi.org/project/porringer/)
17
+
18
+ ## Features
19
+
20
+ - Manifest-driven setup from `porringer.json` or `[tool.porringer]` in `pyproject.toml`.
21
+ - Plugin-based installers for Python, Node, system packages, project sync, runtimes, and source repositories.
22
+ - Read-only previews with stable action IDs, command previews, diagnostics, and JSON output.
23
+ - Sync strategies for minimal installs, latest allowed versions, or exact declared constraints.
24
+ - JSONL progress streams, replay records, and trace artifacts for downstream clients.
25
+ - Python APIs for inspection, execution, cached projects, profiles, tools, progress events, and client snapshots.
26
+
27
+ ## Quick Start
28
+
29
+ Install Porringer into an isolated CLI environment, then preview a manifest before running it:
30
+
31
+ ```shell
32
+ pipx install porringer
33
+ porringer preview examples/python-dev
34
+ porringer install examples/python-dev
35
+ ```
36
+
37
+ See [Setup](https://synodic.github.io/porringer/setup/) for `uv`, `pip`, and development checkout instructions.
38
+
39
+ ## Development
40
+
41
+ Porringer uses [PDM](https://pdm-project.org/en/latest/) for local development. Common tasks are defined in `pyproject.toml` under `[tool.pdm.scripts]`:
42
+
43
+ ```shell
44
+ pdm install
45
+ pdm run test
46
+ pdm run generate
47
+ ```
48
+
49
+ See [Development](https://synodic.github.io/porringer/development/) for test lanes, traces, smoke tests, and diagnostics.
50
+
51
+ For contribution guidelines, see [CONTRIBUTING.md](https://github.com/synodic/.github/blob/stable/CONTRIBUTING.md).
52
+
53
+ ## Documentation
54
+
55
+ - [Documentation home](https://synodic.github.io/porringer/)
56
+ - [Setup](https://synodic.github.io/porringer/setup/)
57
+ - [Install links](https://synodic.github.io/porringer/links/)
58
+ - [Install command](https://synodic.github.io/porringer/install/)
59
+ - [Preview command](https://synodic.github.io/porringer/preview/)
60
+ - [Check command](https://synodic.github.io/porringer/check/)
61
+ - [Download command](https://synodic.github.io/porringer/download/)
62
+ - [API surface](https://synodic.github.io/porringer/api/)
63
+ - [Development](https://synodic.github.io/porringer/development/)
64
+
65
+ ## License
66
+
67
+ This project is licensed under the MIT License. See [LICENSE.md](LICENSE.md) for details.
68
+
69
+ Copyright © 2025 Synodic Software
@@ -0,0 +1,60 @@
1
+ """Public package exports."""
2
+
3
+ """Public package entry points for Porringer.
4
+
5
+ This package exposes the public API, backend helpers, CLI commands, and
6
+ supporting utilities that applications use when integrating with Porringer.
7
+ """
8
+
9
+ import logging
10
+ from importlib.metadata import PackageNotFoundError
11
+ from importlib.metadata import version as _metadata_version
12
+
13
+ from porringer.api import API
14
+ from porringer.backend.command.core.discovery import DiscoveredPlugins
15
+ from porringer.core.plugin_schema.runtime import RuntimeContext
16
+ from porringer.schema.cache import LocalConfiguration
17
+ from porringer.schema.execution import SetupParameters
18
+ from porringer.schema.manifest import SetupManifest
19
+ from porringer.utility.exception import (
20
+ CommandTimeoutError,
21
+ ManifestError,
22
+ ManifestValidationCode,
23
+ NotSupportedError,
24
+ PluginDependencyError,
25
+ PluginError,
26
+ PorringerError,
27
+ ProcessError,
28
+ SetupError,
29
+ UpdateError,
30
+ )
31
+
32
+ __all__ = [
33
+ 'API',
34
+ 'CommandTimeoutError',
35
+ 'DiscoveredPlugins',
36
+ 'LocalConfiguration',
37
+ 'ManifestError',
38
+ 'ManifestValidationCode',
39
+ 'NotSupportedError',
40
+ 'PluginDependencyError',
41
+ 'PluginError',
42
+ 'PorringerError',
43
+ 'ProcessError',
44
+ 'RuntimeContext',
45
+ 'SetupError',
46
+ 'SetupManifest',
47
+ 'SetupParameters',
48
+ 'UpdateError',
49
+ '__version__',
50
+ ]
51
+
52
+ try:
53
+ __version__: str = _metadata_version('porringer')
54
+ except PackageNotFoundError:
55
+ __version__ = '0.0.0'
56
+
57
+ # Register a NullHandler so library consumers can attach their own logging
58
+ # handlers without seeing warnings about an unconfigured logger.
59
+ # See https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library
60
+ logging.getLogger(__name__).addHandler(logging.NullHandler())
@@ -1,17 +1,24 @@
1
- """API for Porringer"""
1
+ """Helpers for api."""
2
+
3
+ """Public API surface for Porringer."""
2
4
 
3
5
  import asyncio
4
6
  import logging
5
7
 
6
8
  from porringer.backend.builder import Builder
7
9
  from porringer.backend.cache import DirectoryCacheManager
10
+ from porringer.backend.command.client import ClientCommands
8
11
  from porringer.backend.command.core.discovery import DiscoveredPlugins, discover_all_plugins
9
12
  from porringer.backend.command.package import PackageCommands
10
13
  from porringer.backend.command.plugin import PluginCommands
14
+ from porringer.backend.command.profile import ProfileCommands
15
+ from porringer.backend.command.project import ProjectCommands
11
16
  from porringer.backend.command.self import check_self_updates
12
17
  from porringer.backend.command.sync import SyncCommands
18
+ from porringer.backend.command.tool import ToolCommands
13
19
  from porringer.backend.resolver import resolve_configuration
14
20
  from porringer.backend.schema import GlobalConfiguration
21
+ from porringer.core.plugin_schema.runtime import RuntimeContext
15
22
  from porringer.schema import (
16
23
  DownloadParameters,
17
24
  DownloadResult,
@@ -25,19 +32,14 @@ logger = logging.getLogger(__name__)
25
32
 
26
33
 
27
34
  class API:
28
- """API for programmatic access to Porringer's functionality.
29
-
30
- Provides namespace sub-APIs:
35
+ """Programmatic interface for Porringer's core operations.
31
36
 
32
- * ``api.plugin`` — porringer extension management (install,
33
- update, remove extension packages).
34
- * ``api.package`` — managed-package operations (list, install,
35
- upgrade, uninstall packages, check for updates).
36
- * ``api.sync`` — manifest loading, streaming execution.
37
- * ``api.cache`` — directory registration and validation.
37
+ The class exposes stable sub-APIs for manifest inspection and execution,
38
+ package management, project registration, cached tool operations, and
39
+ profile handling.
38
40
 
39
41
  Cross-cutting helpers live directly on the ``API`` class:
40
- :meth:`discover_plugins`, :meth:`check_self_updates`,
42
+ :meth:`discover_plugins`, :meth:`check_self_updates`, and
41
43
  :meth:`download`.
42
44
  """
43
45
 
@@ -46,7 +48,7 @@ class API:
46
48
  local_configuration: LocalConfiguration,
47
49
  global_configuration: GlobalConfiguration | None = None,
48
50
  ) -> None:
49
- """Initializes the API
51
+ """Initializes the API.
50
52
 
51
53
  Args:
52
54
  local_configuration: The local configuration.
@@ -57,13 +59,17 @@ class API:
57
59
 
58
60
  configuration = resolve_configuration(local_configuration, global_configuration)
59
61
 
60
- self.cache = DirectoryCacheManager(configuration.data_directory)
62
+ self._cache = DirectoryCacheManager(configuration.data_directory)
61
63
 
62
- self.plugin = PluginCommands()
64
+ self.extension = PluginCommands()
63
65
  self.package = PackageCommands()
64
- self.sync = SyncCommands(self.cache)
66
+ self.sync = SyncCommands(self._cache)
67
+ self.project = ProjectCommands(self._cache, self.sync)
68
+ self.tool = ToolCommands(self.sync, self.package)
69
+ self.profile = ProfileCommands(self.sync)
70
+ self.client = ClientCommands(self.project, self.tool)
65
71
 
66
- # --- Discovery & runtime resolution ---
72
+ # Discover plugins and resolve runtime information.
67
73
 
68
74
  @staticmethod
69
75
  async def discover_plugins(
@@ -76,14 +82,14 @@ class API:
76
82
  This is the recommended entry-point for GUI callers. It
77
83
  returns a :class:`DiscoveredPlugins` object that can be
78
84
  forwarded to every subsequent operation
79
- (``api.sync.execute_stream``, ``api.package.list``,
85
+ (``api.sync.inspect``, ``api.sync.run``, ``api.package.list``,
80
86
  ``api.package.upgrade``, etc.) so that plugin discovery and
81
87
  runtime resolution happen exactly once.
82
88
 
83
89
  Args:
84
90
  use_cache: Reuse cached entry-point scan metadata when
85
91
  ``True`` (the default). Pass ``False`` to force a
86
- fresh scan after installing/removing plugin packages.
92
+ fresh scan after installing or uninstalling extension packages.
87
93
  resolve_runtime: When ``True`` (the default), resolve a
88
94
  :class:`RuntimeContext` from available
89
95
  ``RuntimeProvider`` plugins and attach it to the
@@ -96,15 +102,29 @@ class API:
96
102
  """
97
103
  plugins = await asyncio.to_thread(discover_all_plugins, use_cache=use_cache)
98
104
  if resolve_runtime:
99
- plugins.runtime_context = await Builder.resolve_runtime_context(plugins.environments)
100
- logger.debug(
101
- 'discover_plugins: runtime_context=%s',
102
- {k: str(v) for k, v in plugins.runtime_context.executables.items()}
103
- if plugins.runtime_context.executables
104
- else '<empty>',
105
- )
105
+ await API.resolve_runtime_context(plugins)
106
106
  return plugins
107
107
 
108
+ @staticmethod
109
+ async def resolve_runtime_context(plugins: DiscoveredPlugins) -> RuntimeContext:
110
+ """Resolve and attach runtime context for previously discovered plugins.
111
+
112
+ Args:
113
+ plugins: A :class:`DiscoveredPlugins` object, usually from
114
+ :meth:`discover_plugins` with ``resolve_runtime=False``.
115
+
116
+ Returns:
117
+ The resolved :class:`RuntimeContext` attached to ``plugins``.
118
+ """
119
+ plugins.runtime_context = await Builder.resolve_runtime_context(plugins.environments)
120
+ logger.debug(
121
+ 'resolve_runtime_context: runtime_context=%s',
122
+ {k: str(v) for k, v in plugins.runtime_context.executables.items()}
123
+ if plugins.runtime_context.executables
124
+ else '<empty>',
125
+ )
126
+ return plugins.runtime_context
127
+
108
128
  @staticmethod
109
129
  async def check_self_updates() -> PackageUpdateInfo:
110
130
  """Check for updates to the Porringer package by querying PyPI.
@@ -1,3 +1,5 @@
1
+ """Public package exports for the backend module."""
2
+
1
3
  """Backend package for Porringer.
2
4
 
3
5
  This package contains modules and utilities for backend processing,
@@ -1,9 +1,11 @@
1
- """Backend resolution for mapping (kind, ecosystem) pairs to installer plugins.
1
+ """Backend helpers for backend."""
2
2
 
3
- The `BackendResolver` determines which plugin should handle each
4
- `(PluginKind, ecosystem)` pair declared in a manifest. For example,
5
- `(PACKAGE, "python")` might resolve to `uv` or `pip` depending
6
- on availability and user preferences.
3
+ """Backend resolution for mapping `(kind, ecosystem)` pairs to installer plugins.
4
+
5
+ The `BackendResolver` chooses the plugin that should handle each
6
+ `(PluginKind, ecosystem)` pair declared in a manifest. For example,
7
+ `(PACKAGE, "python")` might resolve to `uv`, `pip`, or another matching
8
+ plugin depending on availability and user preferences.
7
9
  """
8
10
 
9
11
  import logging
@@ -16,7 +18,7 @@ from porringer.core.schema import Ecosystem, Plugin, PluginKind
16
18
 
17
19
  logger = logging.getLogger(__name__)
18
20
 
19
- # Type alias for any plugin that participates in backend resolution.
21
+ # A plugin that participates in backend resolution.
20
22
  BackendPlugin = Plugin
21
23
 
22
24
 
@@ -62,19 +64,18 @@ class BackendResolver:
62
64
  self._preferences = preferences or {}
63
65
  self._runtime_context = runtime_context
64
66
 
65
- # Index: (kind, ecosystem) -> [plugin_name, ...]
67
+ # Index registered plugin names by `(kind, ecosystem)` pair.
66
68
  self._backend_plugins: dict[tuple[PluginKind, Ecosystem], list[str]] = defaultdict(list)
67
69
  for name, plugin in self._all_plugins.items():
68
70
  ecosystem = type(plugin).ecosystem()
69
71
  if ecosystem is not None:
70
72
  self._backend_plugins[(type(plugin).plugin_kind(), ecosystem)].append(name)
71
73
 
72
- # Resolve only the pairs the caller actually needs. When
73
- # *needed_pairs* is ``None`` every registered pair is resolved
74
- # (existing behaviour). Passing an explicit set avoids
75
- # spurious "No available plugin" log messages for ecosystems
76
- # that are registered via entry-points but irrelevant to the
77
- # current manifest.
74
+ # Resolve only the pairs the caller needs right away. When
75
+ # *needed_pairs* is ``None``, every registered pair is resolved to
76
+ # preserve the existing behavior. Passing an explicit set avoids
77
+ # noisy "No available plugin" log messages for ecosystems that are
78
+ # registered via entry points but irrelevant to the current manifest.
78
79
  resolve_keys = needed_pairs if needed_pairs is not None else set(self._backend_plugins)
79
80
 
80
81
  # Resolve once and cache
@@ -1,8 +1,10 @@
1
- """Builder generic plugin discovery and construction.
1
+ """Backend helpers for builder."""
2
2
 
3
- Provides `Builder.find_plugins()` and `Builder.build_plugins()`
4
- for discovering and instantiating plugins from any entry-point group
5
- without per-kind boilerplate.
3
+ """Helpers for discovering and constructing plugins from entry points.
4
+
5
+ The builder offers reusable logic for scanning entry-point groups,
6
+ instantiating plugins, and resolving runtime executables without duplicating
7
+ per-plugin-kind boilerplate.
6
8
  """
7
9
 
8
10
  import asyncio
@@ -16,7 +18,12 @@ from packaging.utils import canonicalize_name
16
18
  from packaging.version import Version
17
19
 
18
20
  from porringer.core.plugin_schema.environment import Environment
19
- from porringer.core.plugin_schema.runtime import ResolvedRuntime, RuntimeContext, RuntimeProvider
21
+ from porringer.core.plugin_schema.runtime import (
22
+ DefaultRuntimeExecutableProvider,
23
+ ResolvedRuntime,
24
+ RuntimeContext,
25
+ RuntimeProvider,
26
+ )
20
27
  from porringer.core.schema import Distribution, Plugin, PluginDependency, PluginParameters
21
28
 
22
29
  logger = logging.getLogger(__name__)
@@ -36,12 +43,29 @@ async def _resolve_provider_executable(
36
43
  env: RuntimeProvider,
37
44
  kind: str,
38
45
  ) -> Path | None:
39
- """Resolve a single executable from *env* trying default_tag then available_tags.
46
+ """Resolve a single executable from *env* by trying the fastest route first.
40
47
 
41
48
  Returns the resolved :class:`~pathlib.Path` or ``None`` when no
42
- executable could be obtained.
49
+ executable can be obtained.
43
50
  """
44
- # --- Fast path: provider-reported default --------------------------
51
+ # Use the provider's built-in default executable when it is available.
52
+ if isinstance(env, DefaultRuntimeExecutableProvider):
53
+ try:
54
+ executable = await env.default_executable()
55
+ except Exception:
56
+ logger.debug("default_executable() failed for '%s'", name, exc_info=True)
57
+ executable = None
58
+
59
+ if executable is not None:
60
+ logger.debug(
61
+ "Resolved runtime '%s' via provider '%s' default_executable: path=%s",
62
+ kind,
63
+ name,
64
+ executable,
65
+ )
66
+ return executable
67
+
68
+ # Try the provider's preferred default tag next.
45
69
  try:
46
70
  default = await env.default_tag()
47
71
  except Exception:
@@ -76,7 +100,7 @@ async def _resolve_provider_executable(
76
100
  name,
77
101
  )
78
102
 
79
- # --- Fallback: enumerate all tags ----------------------------------
103
+ # Fall back to scanning every available tag.
80
104
  try:
81
105
  tags = await env.available_tags()
82
106
  except Exception:
@@ -122,7 +146,7 @@ async def _resolve_provider_executable(
122
146
 
123
147
 
124
148
  class Builder:
125
- """Helper class for building Porringer projects"""
149
+ """Helper class for building Porringer projects."""
126
150
 
127
151
  # ------------------------------------------------------------------
128
152
  # Generic discovery & construction
@@ -134,7 +158,7 @@ class Builder:
134
158
  base_class: type[T],
135
159
  *,
136
160
  check_dependencies: bool = False,
137
- ) -> list[PluginInformation[T]]:
161
+ ) -> tuple[list[PluginInformation[T]], dict[str, str]]:
138
162
  """Search for registered plugins in an entry-point group.
139
163
 
140
164
  Scans `porringer.<group>` for classes that are subclasses of
@@ -147,9 +171,12 @@ class Builder:
147
171
  and filters out plugins with unmet required dependencies.
148
172
 
149
173
  Returns:
150
- A list of discovered plugin information objects.
174
+ A tuple of ``(infos, load_errors)`` where *load_errors* maps
175
+ entry-point name to a human-readable error message for every
176
+ plugin that could not be loaded.
151
177
  """
152
178
  plugin_types: list[PluginInformation[T]] = []
179
+ load_errors: dict[str, str] = {}
153
180
 
154
181
  entry_points = list(metadata.entry_points(group=f'porringer.{group}'))
155
182
  logger.debug('Entry points for porringer.%s: %s', group, [ep.name for ep in entry_points])
@@ -159,12 +186,15 @@ class Builder:
159
186
  loaded_type = entry_point.load()
160
187
  except Exception as e:
161
188
  logger.warning("Plugin '%s' could not be loaded: %s. Skipping", entry_point.name, e)
189
+ load_errors[entry_point.name] = str(e)
162
190
  continue
163
191
 
164
192
  plugin_name = str(canonicalize_name(entry_point.name))
165
193
 
166
194
  if entry_point.dist is None:
195
+ msg = 'plugin is not installed'
167
196
  logger.warning("Plugin '%s' is not installed. Skipping", plugin_name)
197
+ load_errors[plugin_name] = msg
168
198
  continue
169
199
 
170
200
  if not issubclass(loaded_type, base_class):
@@ -176,7 +206,7 @@ class Builder:
176
206
  if check_dependencies:
177
207
  plugin_types = Builder._resolve_dependencies(plugin_types)
178
208
 
179
- return plugin_types
209
+ return plugin_types, load_errors
180
210
 
181
211
  @staticmethod
182
212
  def build_plugin[T: Plugin](info: PluginInformation[T]) -> T:
@@ -1,9 +1,11 @@
1
1
  """Directory cache management for manifest directories."""
2
2
 
3
+ import contextlib
3
4
  import json
4
5
  import logging
5
6
  from pathlib import Path
6
7
 
8
+ from filelock import FileLock
7
9
  from pydantic import ValidationError
8
10
 
9
11
  from porringer.backend.command.manifest import has_manifest
@@ -69,32 +71,26 @@ class DirectoryCacheManager:
69
71
  return self._cache
70
72
 
71
73
  def _save(self) -> None:
72
- """Save cache to disk atomically."""
74
+ """Save cache to disk atomically under a cross-process lock."""
73
75
  if self._cache is None:
74
76
  return
75
77
 
76
78
  self.data_directory.mkdir(parents=True, exist_ok=True)
77
79
 
78
- # Write to temp file then rename for atomic write
79
80
  temp_path = self._cache_path.with_suffix('.tmp')
81
+ lock_path = self._cache_path.with_suffix(f'{self._cache_path.suffix}.lock')
80
82
  try:
81
- temp_path.write_text(self._cache.model_dump_json(indent=2), encoding='utf-8')
82
- temp_path.replace(self._cache_path)
83
+ with FileLock(lock_path):
84
+ temp_path.write_text(self._cache.model_dump_json(indent=2), encoding='utf-8')
85
+ temp_path.replace(self._cache_path)
83
86
  logger.debug(f'Saved directory cache to {self._cache_path}')
84
- except PermissionError as e:
85
- if temp_path.exists():
86
- temp_path.unlink()
87
- logger.error(f'Permission denied writing cache: {e}')
88
- raise
89
87
  except OSError as e:
90
- if temp_path.exists():
91
- temp_path.unlink()
92
88
  logger.error(f'Failed to write cache: {e}')
93
89
  raise
94
- except Exception:
90
+ finally:
95
91
  if temp_path.exists():
96
- temp_path.unlink()
97
- raise
92
+ with contextlib.suppress(OSError):
93
+ temp_path.unlink()
98
94
 
99
95
  @staticmethod
100
96
  def _normalize_path(path: Path) -> Path:
@@ -1,3 +1,5 @@
1
+ """Public package exports for the backend.command module."""
2
+
1
3
  """Command package for Porringer backend.
2
4
 
3
5
  This package contains command modules for backend operations,
@@ -0,0 +1,46 @@
1
+ """CLI command implementation for client."""
2
+
3
+ """Client-oriented aggregate commands."""
4
+
5
+ import asyncio
6
+
7
+ from porringer.backend.command.core.discovery import DiscoveredPlugins, discover_all_plugins
8
+ from porringer.backend.command.core.inspection import plugin_snapshots
9
+ from porringer.backend.command.project import ProjectCommands
10
+ from porringer.backend.command.tool import ToolCommands
11
+ from porringer.schema import ClientSnapshot, InspectionMode, ManagedToolReport
12
+ from porringer.utility.observability import result_status
13
+
14
+
15
+ class ClientCommands:
16
+ """Aggregate read models for long-lived clients."""
17
+
18
+ def __init__(self, project_commands: ProjectCommands, tool_commands: ToolCommands) -> None:
19
+ """Initialize client commands."""
20
+ self._project = project_commands
21
+ self._tool = tool_commands
22
+
23
+ async def snapshot(
24
+ self,
25
+ *,
26
+ inspection_mode: InspectionMode = InspectionMode.FAST,
27
+ include_updates: bool = False,
28
+ plugins: DiscoveredPlugins | None = None,
29
+ ) -> ClientSnapshot:
30
+ """Return a low-latency state bundle for GUI or agent clients."""
31
+ shared_plugins = plugins or await asyncio.to_thread(discover_all_plugins, use_cache=True)
32
+ projects = await self._project.inspect_cached(inspection_mode=inspection_mode, plugins=shared_plugins)
33
+ updates: ManagedToolReport | None = None
34
+ if include_updates:
35
+ updates = await self._tool.check_updates(plugins=shared_plugins)
36
+ diagnostics = projects.diagnostics + (updates.diagnostics if updates is not None else ())
37
+ follow_up_actions = projects.follow_up_actions + (updates.follow_up_actions if updates is not None else ())
38
+ return ClientSnapshot(
39
+ status=result_status(projects.success and (updates.success if updates is not None else True), diagnostics),
40
+ inspection_mode=inspection_mode,
41
+ plugins=plugin_snapshots(shared_plugins),
42
+ projects=projects,
43
+ updates=updates,
44
+ diagnostics=diagnostics,
45
+ follow_up_actions=follow_up_actions,
46
+ )
@@ -1,3 +1,5 @@
1
+ """Public package exports for the backend.command.core module."""
2
+
1
3
  """Core backend command modules.
2
4
 
3
5
  This package contains the core implementation modules for the