porringer 0.2.1.dev90__tar.gz → 0.2.1.dev92__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 (343) hide show
  1. porringer-0.2.1.dev92/PKG-INFO +87 -0
  2. porringer-0.2.1.dev92/README.md +69 -0
  3. porringer-0.2.1.dev92/porringer/__init__.py +60 -0
  4. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/api.py +46 -25
  5. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/__init__.py +3 -1
  6. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/backend.py +14 -13
  7. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/builder.py +43 -13
  8. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/cache.py +10 -14
  9. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/command/__init__.py +3 -1
  10. porringer-0.2.1.dev92/porringer/backend/command/client.py +47 -0
  11. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/command/core/__init__.py +3 -1
  12. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/command/core/action_builder.py +121 -119
  13. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/command/core/discovery.py +29 -8
  14. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/command/core/execution.py +459 -571
  15. porringer-0.2.1.dev92/porringer/backend/command/core/inspection.py +387 -0
  16. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/command/core/phase.py +8 -21
  17. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/command/core/presence.py +27 -25
  18. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/command/core/resolution.py +129 -82
  19. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/command/manifest.py +9 -7
  20. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/command/package.py +105 -131
  21. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/command/plugin.py +57 -32
  22. porringer-0.2.1.dev92/porringer/backend/command/profile.py +157 -0
  23. porringer-0.2.1.dev92/porringer/backend/command/project.py +318 -0
  24. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/command/self.py +4 -1
  25. porringer-0.2.1.dev92/porringer/backend/command/sync.py +594 -0
  26. porringer-0.2.1.dev92/porringer/backend/command/tool.py +269 -0
  27. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/resolver.py +4 -1
  28. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/backend/schema.py +6 -3
  29. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/console/__init__.py +3 -1
  30. porringer-0.2.1.dev92/porringer/console/command/__init__.py +4 -0
  31. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/console/command/cache.py +22 -17
  32. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/console/command/check.py +10 -7
  33. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/console/command/download.py +14 -23
  34. porringer-0.2.1.dev92/porringer/console/command/env.py +276 -0
  35. porringer-0.2.1.dev92/porringer/console/command/install.py +262 -0
  36. porringer-0.2.1.dev92/porringer/console/command/open.py +45 -0
  37. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/console/command/package.py +9 -6
  38. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/console/command/plugin.py +48 -42
  39. porringer-0.2.1.dev92/porringer/console/command/preview.py +296 -0
  40. porringer-0.2.1.dev92/porringer/console/command/schema.py +79 -0
  41. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/console/command/self.py +10 -7
  42. porringer-0.2.1.dev92/porringer/console/command/sync.py +377 -0
  43. porringer-0.2.1.dev92/porringer/console/common.py +50 -0
  44. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/console/entry.py +34 -8
  45. porringer-0.2.1.dev92/porringer/console/output.py +111 -0
  46. porringer-0.2.1.dev92/porringer/console/schema.py +56 -0
  47. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/core/__init__.py +3 -1
  48. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/core/path.py +1 -3
  49. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/core/plugin_schema/__init__.py +5 -2
  50. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/core/plugin_schema/environment.py +164 -48
  51. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/core/plugin_schema/manifest.py +4 -2
  52. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/core/plugin_schema/plugin_manager.py +41 -50
  53. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/core/plugin_schema/project_environment.py +131 -7
  54. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/core/plugin_schema/python_environment.py +21 -9
  55. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/core/plugin_schema/runtime.py +16 -0
  56. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/core/plugin_schema/scm.py +5 -3
  57. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/core/plugin_schema/tool_based.py +84 -95
  58. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/core/schema.py +85 -18
  59. porringer-0.2.1.dev92/porringer/core/target.py +100 -0
  60. porringer-0.2.1.dev92/porringer/plugin/__init__.py +6 -0
  61. porringer-0.2.1.dev92/porringer/plugin/apt/__init__.py +4 -0
  62. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/apt/plugin.py +6 -19
  63. porringer-0.2.1.dev92/porringer/plugin/brew/__init__.py +4 -0
  64. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/brew/plugin.py +31 -16
  65. porringer-0.2.1.dev92/porringer/plugin/git/__init__.py +4 -0
  66. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/git/plugin.py +4 -1
  67. porringer-0.2.1.dev92/porringer/plugin/npm/__init__.py +4 -0
  68. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/npm/plugin.py +7 -8
  69. porringer-0.2.1.dev92/porringer/plugin/npm_project/__init__.py +4 -0
  70. porringer-0.2.1.dev92/porringer/plugin/npm_project/plugin.py +25 -0
  71. porringer-0.2.1.dev92/porringer/plugin/pdm/__init__.py +4 -0
  72. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/pdm/plugin.py +10 -4
  73. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/pim/__init__.py +3 -1
  74. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/pim/plugin.py +158 -52
  75. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/pip/__init__.py +3 -1
  76. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/pip/plugin.py +67 -174
  77. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/pipx/__init__.py +3 -1
  78. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/pipx/plugin.py +15 -3
  79. porringer-0.2.1.dev92/porringer/plugin/pnpm/__init__.py +4 -0
  80. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/pnpm/plugin.py +7 -5
  81. porringer-0.2.1.dev92/porringer/plugin/pnpm_project/__init__.py +4 -0
  82. porringer-0.2.1.dev92/porringer/plugin/pnpm_project/plugin.py +29 -0
  83. porringer-0.2.1.dev92/porringer/plugin/poetry/__init__.py +4 -0
  84. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/poetry/plugin.py +26 -6
  85. porringer-0.2.1.dev92/porringer/plugin/pyenv/__init__.py +4 -0
  86. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/pyenv/plugin.py +17 -11
  87. porringer-0.2.1.dev92/porringer/plugin/uv/__init__.py +4 -0
  88. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/uv/plugin.py +4 -1
  89. porringer-0.2.1.dev92/porringer/plugin/uv_project/__init__.py +4 -0
  90. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/uv_project/plugin.py +6 -1
  91. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/winget/__init__.py +3 -1
  92. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/plugin/winget/plugin.py +4 -7
  93. porringer-0.2.1.dev92/porringer/plugin/yarn_project/__init__.py +4 -0
  94. porringer-0.2.1.dev92/porringer/plugin/yarn_project/plugin.py +33 -0
  95. porringer-0.2.1.dev92/porringer/schema/__init__.py +207 -0
  96. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/schema/cache.py +18 -4
  97. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/schema/check.py +18 -5
  98. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/schema/download.py +9 -4
  99. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/schema/execution.py +61 -17
  100. porringer-0.2.1.dev92/porringer/schema/inspection.py +151 -0
  101. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/schema/manifest.py +15 -70
  102. porringer-0.2.1.dev92/porringer/schema/observability.py +162 -0
  103. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/schema/plugin.py +5 -2
  104. porringer-0.2.1.dev92/porringer/schema/profile.py +67 -0
  105. porringer-0.2.1.dev92/porringer/schema/progress.py +331 -0
  106. porringer-0.2.1.dev92/porringer/schema/project.py +85 -0
  107. porringer-0.2.1.dev92/porringer/schema/snapshot.py +26 -0
  108. porringer-0.2.1.dev92/porringer/schema/tool.py +58 -0
  109. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/test/mock/__init__.py +3 -1
  110. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/test/mock/environment.py +6 -3
  111. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/test/mock/plugin_manager.py +17 -15
  112. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/test/mock/project_environment.py +4 -1
  113. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/test/mock/scm.py +4 -1
  114. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/test/mock/subprocess.py +3 -1
  115. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/test/pytest/__init__.py +3 -1
  116. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/test/pytest/plugin.py +4 -1
  117. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/test/pytest/shared.py +10 -7
  118. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/test/pytest/tests.py +90 -12
  119. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/test/pytest/variants.py +6 -3
  120. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/utility/__init__.py +3 -1
  121. porringer-0.2.1.dev92/porringer/utility/concurrency.py +57 -0
  122. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/utility/download.py +95 -76
  123. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/utility/exception.py +15 -12
  124. porringer-0.2.1.dev92/porringer/utility/observability.py +456 -0
  125. porringer-0.2.1.dev92/porringer/utility/tool_environment.py +69 -0
  126. porringer-0.2.1.dev92/porringer/utility/trace.py +229 -0
  127. porringer-0.2.1.dev92/porringer/utility/utility.py +307 -0
  128. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/pyproject.toml +33 -32
  129. porringer-0.2.1.dev92/tests/__init__.py +4 -0
  130. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/conftest.py +58 -60
  131. porringer-0.2.1.dev92/tests/fixtures/__init__.py +4 -0
  132. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/fixtures/api.py +6 -5
  133. porringer-0.2.1.dev92/tests/fixtures/command_process.py +154 -0
  134. porringer-0.2.1.dev92/tests/fixtures/disposable_environment.py +180 -0
  135. porringer-0.2.1.dev92/tests/fixtures/factories.py +134 -0
  136. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/fixtures/http.py +3 -1
  137. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/fixtures/mock_plugins.py +7 -71
  138. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/fixtures/packages.py +3 -1
  139. porringer-0.2.1.dev92/tests/fixtures/smoke_packages.py +117 -0
  140. porringer-0.2.1.dev92/tests/fixtures/strategies.py +161 -0
  141. porringer-0.2.1.dev92/tests/fixtures/tool_smoke.py +50 -0
  142. porringer-0.2.1.dev92/tests/integration/__init__.py +4 -0
  143. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/integration/plugins/__init__.py +3 -1
  144. porringer-0.2.1.dev92/tests/integration/plugins/winget/__init__.py +7 -0
  145. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/integration/plugins/winget/test_environment.py +6 -3
  146. porringer-0.2.1.dev92/tests/integration/test_bootstrap_presence.py +120 -0
  147. porringer-0.2.1.dev92/tests/integration/test_dry_run_acceptance.py +64 -0
  148. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/integration/test_example_bootstrap.py +19 -16
  149. porringer-0.2.1.dev92/tests/integration/test_example_presence.py +55 -0
  150. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/integration/test_frozen_app_presence.py +36 -32
  151. porringer-0.2.1.dev92/tests/integration/test_minimal_path.py +49 -0
  152. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/__init__.py +3 -1
  153. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/__init__.py +3 -1
  154. porringer-0.2.1.dev92/tests/unit/plugins/apt/__init__.py +4 -0
  155. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/apt/test_environment.py +6 -3
  156. porringer-0.2.1.dev92/tests/unit/plugins/brew/__init__.py +4 -0
  157. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/brew/test_environment.py +6 -3
  158. porringer-0.2.1.dev92/tests/unit/plugins/git/__init__.py +4 -0
  159. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/git/test_clone_detection.py +4 -1
  160. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/git/test_scm.py +4 -1
  161. porringer-0.2.1.dev92/tests/unit/plugins/npm/__init__.py +4 -0
  162. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/npm/test_environment.py +6 -3
  163. porringer-0.2.1.dev92/tests/unit/plugins/npm_project/__init__.py +4 -0
  164. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/npm_project/test_environment.py +4 -1
  165. porringer-0.2.1.dev92/tests/unit/plugins/pdm/__init__.py +4 -0
  166. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/pdm/test_environment.py +4 -1
  167. porringer-0.2.1.dev92/tests/unit/plugins/pim/__init__.py +4 -0
  168. porringer-0.2.1.dev92/tests/unit/plugins/pim/test_environment.py +207 -0
  169. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/pip/__init__.py +3 -1
  170. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/pip/conftest.py +4 -1
  171. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/pip/test_auxiliary_tools.py +11 -10
  172. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/pip/test_environment.py +6 -3
  173. porringer-0.2.1.dev92/tests/unit/plugins/pipx/__init__.py +4 -0
  174. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/pipx/test_environment.py +6 -3
  175. porringer-0.2.1.dev92/tests/unit/plugins/pnpm/__init__.py +4 -0
  176. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/pnpm/test_environment.py +6 -3
  177. porringer-0.2.1.dev92/tests/unit/plugins/pnpm_project/__init__.py +4 -0
  178. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/pnpm_project/test_environment.py +4 -1
  179. porringer-0.2.1.dev92/tests/unit/plugins/poetry/__init__.py +4 -0
  180. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/poetry/test_environment.py +4 -1
  181. porringer-0.2.1.dev92/tests/unit/plugins/pyenv/__init__.py +4 -0
  182. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/pyenv/test_environment.py +4 -1
  183. porringer-0.2.1.dev92/tests/unit/plugins/uv/__init__.py +4 -0
  184. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/uv/test_environment.py +6 -3
  185. porringer-0.2.1.dev92/tests/unit/plugins/uv_project/__init__.py +4 -0
  186. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/uv_project/test_environment.py +4 -1
  187. porringer-0.2.1.dev92/tests/unit/plugins/yarn_project/__init__.py +4 -0
  188. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/plugins/yarn_project/test_environment.py +4 -1
  189. porringer-0.2.1.dev92/tests/unit/test_action_filtering.py +145 -0
  190. porringer-0.2.1.dev92/tests/unit/test_action_progress.py +422 -0
  191. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_backend_resolver.py +10 -10
  192. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_bootstrap_cross_platform.py +7 -5
  193. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_cache.py +23 -20
  194. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_check.py +15 -12
  195. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_check_updates.py +8 -64
  196. porringer-0.2.1.dev92/tests/unit/test_cli.py +331 -0
  197. porringer-0.2.1.dev92/tests/unit/test_command_plugin.py +312 -0
  198. porringer-0.2.1.dev92/tests/unit/test_command_process.py +81 -0
  199. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_command_self.py +12 -9
  200. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_concurrency_and_client.py +146 -45
  201. porringer-0.2.1.dev92/tests/unit/test_console_output.py +109 -0
  202. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_deferred_resolution.py +3 -1
  203. porringer-0.2.1.dev92/tests/unit/test_disposable_environment.py +75 -0
  204. porringer-0.2.1.dev92/tests/unit/test_docs_contract.py +87 -0
  205. porringer-0.2.1.dev92/tests/unit/test_downstream_api.py +330 -0
  206. porringer-0.2.1.dev92/tests/unit/test_event_loop_safety.py +88 -0
  207. porringer-0.2.1.dev92/tests/unit/test_example_manifests.py +21 -0
  208. porringer-0.2.1.dev92/tests/unit/test_extension_list.py +79 -0
  209. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_extras_introspection.py +5 -2
  210. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_frozen_app_detection.py +3 -1
  211. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_frozen_presence_resolution.py +3 -1
  212. porringer-0.2.1.dev92/tests/unit/test_hermeticity.py +32 -0
  213. porringer-0.2.1.dev92/tests/unit/test_install_cli.py +136 -0
  214. porringer-0.2.1.dev92/tests/unit/test_manifest_discovery.py +324 -0
  215. porringer-0.2.1.dev92/tests/unit/test_manifest_inspect.py +180 -0
  216. porringer-0.2.1.dev92/tests/unit/test_manifest_loading.py +448 -0
  217. porringer-0.2.1.dev92/tests/unit/test_manifest_schema.py +521 -0
  218. porringer-0.2.1.dev92/tests/unit/test_manifest_validation.py +192 -0
  219. porringer-0.2.1.dev92/tests/unit/test_output_schema_roundtrip.py +245 -0
  220. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_package_ref.py +4 -1
  221. porringer-0.2.1.dev92/tests/unit/test_package_ref_invariants.py +96 -0
  222. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_package_relation.py +26 -11
  223. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_path_sync.py +4 -1
  224. porringer-0.2.1.dev92/tests/unit/test_pipx_bootstrap_contract.py +108 -0
  225. porringer-0.2.1.dev92/tests/unit/test_plugin_aux_and_errors.py +108 -0
  226. porringer-0.2.1.dev92/tests/unit/test_plugin_commands.py +98 -0
  227. porringer-0.2.1.dev92/tests/unit/test_plugin_dry_run_no_subprocess.py +67 -0
  228. porringer-0.2.1.dev92/tests/unit/test_plugin_lifecycle.py +397 -0
  229. porringer-0.2.1.dev92/tests/unit/test_plugin_manager.py +255 -0
  230. porringer-0.2.1.dev92/tests/unit/test_plugin_manager_presence.py +303 -0
  231. porringer-0.2.1.dev92/tests/unit/test_plugin_manager_upgrade.py +771 -0
  232. porringer-0.2.1.dev92/tests/unit/test_plugin_protocol.py +254 -0
  233. porringer-0.2.1.dev92/tests/unit/test_plugin_runtime.py +881 -0
  234. porringer-0.2.1.dev92/tests/unit/test_plugin_tags.py +777 -0
  235. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_presence_detection.py +3 -1
  236. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_project_directory.py +95 -52
  237. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_project_root.py +34 -28
  238. porringer-0.2.1.dev92/tests/unit/test_run_command_progress.py +63 -0
  239. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_runtime_context_seeding.py +3 -1
  240. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_runtime_propagation.py +38 -3
  241. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_runtime_updates.py +4 -1
  242. porringer-0.2.1.dev92/tests/unit/test_smoke_packages.py +27 -0
  243. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_sub_action_progress.py +149 -36
  244. porringer-0.2.1.dev92/tests/unit/test_sync_inspection.py +278 -0
  245. porringer-0.2.1.dev92/tests/unit/test_target_resolution.py +56 -0
  246. porringer-0.2.1.dev92/tests/unit/test_trace.py +162 -0
  247. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_uninstall.py +22 -20
  248. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_update_detection.py +84 -383
  249. porringer-0.2.1.dev92/tests/unit/test_update_detection_resolution.py +149 -0
  250. porringer-0.2.1.dev92/tests/unit/test_update_detection_spec.py +88 -0
  251. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/tests/unit/test_upgrade.py +7 -6
  252. porringer-0.2.1.dev92/tests/unit/update_detection_helpers.py +58 -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/__init__.py +0 -4
  264. porringer-0.2.1.dev90/porringer/plugin/apt/__init__.py +0 -1
  265. porringer-0.2.1.dev90/porringer/plugin/brew/__init__.py +0 -1
  266. porringer-0.2.1.dev90/porringer/plugin/bun/__init__.py +0 -1
  267. porringer-0.2.1.dev90/porringer/plugin/bun/plugin.py +0 -90
  268. porringer-0.2.1.dev90/porringer/plugin/bun_project/__init__.py +0 -1
  269. porringer-0.2.1.dev90/porringer/plugin/bun_project/plugin.py +0 -36
  270. porringer-0.2.1.dev90/porringer/plugin/deno/__init__.py +0 -1
  271. porringer-0.2.1.dev90/porringer/plugin/deno/plugin.py +0 -161
  272. porringer-0.2.1.dev90/porringer/plugin/deno_project/__init__.py +0 -1
  273. porringer-0.2.1.dev90/porringer/plugin/deno_project/plugin.py +0 -64
  274. porringer-0.2.1.dev90/porringer/plugin/git/__init__.py +0 -1
  275. porringer-0.2.1.dev90/porringer/plugin/npm/__init__.py +0 -1
  276. porringer-0.2.1.dev90/porringer/plugin/npm_project/__init__.py +0 -1
  277. porringer-0.2.1.dev90/porringer/plugin/npm_project/plugin.py +0 -34
  278. porringer-0.2.1.dev90/porringer/plugin/pdm/__init__.py +0 -1
  279. porringer-0.2.1.dev90/porringer/plugin/pnpm/__init__.py +0 -1
  280. porringer-0.2.1.dev90/porringer/plugin/pnpm_project/__init__.py +0 -1
  281. porringer-0.2.1.dev90/porringer/plugin/pnpm_project/plugin.py +0 -61
  282. porringer-0.2.1.dev90/porringer/plugin/poetry/__init__.py +0 -1
  283. porringer-0.2.1.dev90/porringer/plugin/pyenv/__init__.py +0 -1
  284. porringer-0.2.1.dev90/porringer/plugin/uv/__init__.py +0 -1
  285. porringer-0.2.1.dev90/porringer/plugin/uv_project/__init__.py +0 -1
  286. porringer-0.2.1.dev90/porringer/plugin/wsl/__init__.py +0 -6
  287. porringer-0.2.1.dev90/porringer/plugin/wsl/transport.py +0 -71
  288. porringer-0.2.1.dev90/porringer/plugin/wsl/utility.py +0 -122
  289. porringer-0.2.1.dev90/porringer/plugin/yarn_project/__init__.py +0 -1
  290. porringer-0.2.1.dev90/porringer/plugin/yarn_project/plugin.py +0 -66
  291. porringer-0.2.1.dev90/porringer/schema/__init__.py +0 -111
  292. porringer-0.2.1.dev90/porringer/schema/config.py +0 -14
  293. porringer-0.2.1.dev90/porringer/schema/progress.py +0 -157
  294. porringer-0.2.1.dev90/porringer/utility/utility.py +0 -226
  295. porringer-0.2.1.dev90/tests/__init__.py +0 -5
  296. porringer-0.2.1.dev90/tests/fixtures/__init__.py +0 -6
  297. porringer-0.2.1.dev90/tests/fixtures/manifests.py +0 -160
  298. porringer-0.2.1.dev90/tests/integration/__init__.py +0 -5
  299. porringer-0.2.1.dev90/tests/integration/plugins/git/__init__.py +0 -1
  300. porringer-0.2.1.dev90/tests/integration/plugins/git/test_scm.py +0 -20
  301. porringer-0.2.1.dev90/tests/integration/plugins/pip/__init__.py +0 -5
  302. porringer-0.2.1.dev90/tests/integration/plugins/pip/test_environment.py +0 -20
  303. porringer-0.2.1.dev90/tests/integration/plugins/pipx/__init__.py +0 -5
  304. porringer-0.2.1.dev90/tests/integration/plugins/pipx/test_environment.py +0 -20
  305. porringer-0.2.1.dev90/tests/integration/plugins/winget/__init__.py +0 -5
  306. porringer-0.2.1.dev90/tests/integration/test_bare_environment.py +0 -49
  307. porringer-0.2.1.dev90/tests/integration/test_bootstrap_presence.py +0 -115
  308. porringer-0.2.1.dev90/tests/integration/test_example_presence.py +0 -42
  309. porringer-0.2.1.dev90/tests/unit/plugins/apt/__init__.py +0 -1
  310. porringer-0.2.1.dev90/tests/unit/plugins/brew/__init__.py +0 -1
  311. porringer-0.2.1.dev90/tests/unit/plugins/bun/__init__.py +0 -1
  312. porringer-0.2.1.dev90/tests/unit/plugins/bun/test_environment.py +0 -20
  313. porringer-0.2.1.dev90/tests/unit/plugins/bun_project/__init__.py +0 -1
  314. porringer-0.2.1.dev90/tests/unit/plugins/bun_project/test_environment.py +0 -16
  315. porringer-0.2.1.dev90/tests/unit/plugins/deno/__init__.py +0 -1
  316. porringer-0.2.1.dev90/tests/unit/plugins/deno/test_environment.py +0 -20
  317. porringer-0.2.1.dev90/tests/unit/plugins/deno_project/__init__.py +0 -1
  318. porringer-0.2.1.dev90/tests/unit/plugins/deno_project/test_environment.py +0 -16
  319. porringer-0.2.1.dev90/tests/unit/plugins/git/__init__.py +0 -1
  320. porringer-0.2.1.dev90/tests/unit/plugins/npm/__init__.py +0 -1
  321. porringer-0.2.1.dev90/tests/unit/plugins/npm_project/__init__.py +0 -1
  322. porringer-0.2.1.dev90/tests/unit/plugins/pdm/__init__.py +0 -1
  323. porringer-0.2.1.dev90/tests/unit/plugins/pim/__init__.py +0 -1
  324. porringer-0.2.1.dev90/tests/unit/plugins/pim/test_environment.py +0 -83
  325. porringer-0.2.1.dev90/tests/unit/plugins/pipx/__init__.py +0 -1
  326. porringer-0.2.1.dev90/tests/unit/plugins/pnpm/__init__.py +0 -1
  327. porringer-0.2.1.dev90/tests/unit/plugins/pnpm_project/__init__.py +0 -1
  328. porringer-0.2.1.dev90/tests/unit/plugins/poetry/__init__.py +0 -1
  329. porringer-0.2.1.dev90/tests/unit/plugins/pyenv/__init__.py +0 -1
  330. porringer-0.2.1.dev90/tests/unit/plugins/uv/__init__.py +0 -1
  331. porringer-0.2.1.dev90/tests/unit/plugins/uv_project/__init__.py +0 -1
  332. porringer-0.2.1.dev90/tests/unit/plugins/yarn_project/__init__.py +0 -1
  333. porringer-0.2.1.dev90/tests/unit/test_cli.py +0 -98
  334. porringer-0.2.1.dev90/tests/unit/test_command_plugin.py +0 -1882
  335. porringer-0.2.1.dev90/tests/unit/test_event_loop_safety.py +0 -87
  336. porringer-0.2.1.dev90/tests/unit/test_manifest.py +0 -1461
  337. porringer-0.2.1.dev90/tests/unit/test_package_filtering.py +0 -194
  338. porringer-0.2.1.dev90/tests/unit/test_plugin_filtering.py +0 -219
  339. porringer-0.2.1.dev90/tests/unit/test_plugin_manager.py +0 -1333
  340. porringer-0.2.1.dev90/tests/unit/test_wsl.py +0 -906
  341. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/LICENSE.md +0 -0
  342. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/py.typed +0 -0
  343. {porringer-0.2.1.dev90 → porringer-0.2.1.dev92}/porringer/utility/py.typed +0 -0
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.1
2
+ Name: porringer
3
+ Version: 0.2.1.dev92
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,25 @@
1
- """API for Porringer"""
1
+ """Helpers for api.
2
+
3
+ Public API surface for Porringer.
4
+ """
2
5
 
3
6
  import asyncio
4
7
  import logging
5
8
 
6
9
  from porringer.backend.builder import Builder
7
10
  from porringer.backend.cache import DirectoryCacheManager
11
+ from porringer.backend.command.client import ClientCommands
8
12
  from porringer.backend.command.core.discovery import DiscoveredPlugins, discover_all_plugins
9
13
  from porringer.backend.command.package import PackageCommands
10
14
  from porringer.backend.command.plugin import PluginCommands
15
+ from porringer.backend.command.profile import ProfileCommands
16
+ from porringer.backend.command.project import ProjectCommands
11
17
  from porringer.backend.command.self import check_self_updates
12
18
  from porringer.backend.command.sync import SyncCommands
19
+ from porringer.backend.command.tool import ToolCommands
13
20
  from porringer.backend.resolver import resolve_configuration
14
21
  from porringer.backend.schema import GlobalConfiguration
22
+ from porringer.core.plugin_schema.runtime import RuntimeContext
15
23
  from porringer.schema import (
16
24
  DownloadParameters,
17
25
  DownloadResult,
@@ -25,19 +33,14 @@ logger = logging.getLogger(__name__)
25
33
 
26
34
 
27
35
  class API:
28
- """API for programmatic access to Porringer's functionality.
29
-
30
- Provides namespace sub-APIs:
36
+ """Programmatic interface for Porringer's core operations.
31
37
 
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.
38
+ The class exposes stable sub-APIs for manifest inspection and execution,
39
+ package management, project registration, cached tool operations, and
40
+ profile handling.
38
41
 
39
42
  Cross-cutting helpers live directly on the ``API`` class:
40
- :meth:`discover_plugins`, :meth:`check_self_updates`,
43
+ :meth:`discover_plugins`, :meth:`check_self_updates`, and
41
44
  :meth:`download`.
42
45
  """
43
46
 
@@ -46,7 +49,7 @@ class API:
46
49
  local_configuration: LocalConfiguration,
47
50
  global_configuration: GlobalConfiguration | None = None,
48
51
  ) -> None:
49
- """Initializes the API
52
+ """Initializes the API.
50
53
 
51
54
  Args:
52
55
  local_configuration: The local configuration.
@@ -57,13 +60,17 @@ class API:
57
60
 
58
61
  configuration = resolve_configuration(local_configuration, global_configuration)
59
62
 
60
- self.cache = DirectoryCacheManager(configuration.data_directory)
63
+ self._cache = DirectoryCacheManager(configuration.data_directory)
61
64
 
62
- self.plugin = PluginCommands()
65
+ self.extension = PluginCommands()
63
66
  self.package = PackageCommands()
64
- self.sync = SyncCommands(self.cache)
67
+ self.sync = SyncCommands(self._cache)
68
+ self.project = ProjectCommands(self._cache, self.sync)
69
+ self.tool = ToolCommands(self.sync, self.package)
70
+ self.profile = ProfileCommands(self.sync)
71
+ self.client = ClientCommands(self.project, self.tool)
65
72
 
66
- # --- Discovery & runtime resolution ---
73
+ # Discover plugins and resolve runtime information.
67
74
 
68
75
  @staticmethod
69
76
  async def discover_plugins(
@@ -76,14 +83,14 @@ class API:
76
83
  This is the recommended entry-point for GUI callers. It
77
84
  returns a :class:`DiscoveredPlugins` object that can be
78
85
  forwarded to every subsequent operation
79
- (``api.sync.execute_stream``, ``api.package.list``,
86
+ (``api.sync.inspect``, ``api.sync.run``, ``api.package.list``,
80
87
  ``api.package.upgrade``, etc.) so that plugin discovery and
81
88
  runtime resolution happen exactly once.
82
89
 
83
90
  Args:
84
91
  use_cache: Reuse cached entry-point scan metadata when
85
92
  ``True`` (the default). Pass ``False`` to force a
86
- fresh scan after installing/removing plugin packages.
93
+ fresh scan after installing or uninstalling extension packages.
87
94
  resolve_runtime: When ``True`` (the default), resolve a
88
95
  :class:`RuntimeContext` from available
89
96
  ``RuntimeProvider`` plugins and attach it to the
@@ -96,15 +103,29 @@ class API:
96
103
  """
97
104
  plugins = await asyncio.to_thread(discover_all_plugins, use_cache=use_cache)
98
105
  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
- )
106
+ await API.resolve_runtime_context(plugins)
106
107
  return plugins
107
108
 
109
+ @staticmethod
110
+ async def resolve_runtime_context(plugins: DiscoveredPlugins) -> RuntimeContext:
111
+ """Resolve and attach runtime context for previously discovered plugins.
112
+
113
+ Args:
114
+ plugins: A :class:`DiscoveredPlugins` object, usually from
115
+ :meth:`discover_plugins` with ``resolve_runtime=False``.
116
+
117
+ Returns:
118
+ The resolved :class:`RuntimeContext` attached to ``plugins``.
119
+ """
120
+ plugins.runtime_context = await Builder.resolve_runtime_context(plugins.environments)
121
+ logger.debug(
122
+ 'resolve_runtime_context: runtime_context=%s',
123
+ {k: str(v) for k, v in plugins.runtime_context.executables.items()}
124
+ if plugins.runtime_context.executables
125
+ else '<empty>',
126
+ )
127
+ return plugins.runtime_context
128
+
108
129
  @staticmethod
109
130
  async def check_self_updates() -> PackageUpdateInfo:
110
131
  """Check for updates to the Porringer package by querying PyPI.
@@ -1,4 +1,6 @@
1
- """Backend package for Porringer.
1
+ """Public package exports for the backend module.
2
+
3
+ Backend package for Porringer.
2
4
 
3
5
  This package contains modules and utilities for backend processing,
4
6
  including configuration resolution, plugin management, and command execution.
@@ -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,4 +1,6 @@
1
- """Command package for Porringer backend.
1
+ """Public package exports for the backend.command module.
2
+
3
+ Command package for Porringer backend.
2
4
 
3
5
  This package contains command modules for backend operations,
4
6
  including plugin management and self-update functionalities.
@@ -0,0 +1,47 @@
1
+ """CLI command implementation for client.
2
+
3
+ Client-oriented aggregate commands.
4
+ """
5
+
6
+ import asyncio
7
+
8
+ from porringer.backend.command.core.discovery import DiscoveredPlugins, discover_all_plugins
9
+ from porringer.backend.command.core.inspection import plugin_snapshots
10
+ from porringer.backend.command.project import ProjectCommands
11
+ from porringer.backend.command.tool import ToolCommands
12
+ from porringer.schema import ClientSnapshot, InspectionMode, ManagedToolReport
13
+ from porringer.utility.observability import result_status
14
+
15
+
16
+ class ClientCommands:
17
+ """Aggregate read models for long-lived clients."""
18
+
19
+ def __init__(self, project_commands: ProjectCommands, tool_commands: ToolCommands) -> None:
20
+ """Initialize client commands."""
21
+ self._project = project_commands
22
+ self._tool = tool_commands
23
+
24
+ async def snapshot(
25
+ self,
26
+ *,
27
+ inspection_mode: InspectionMode = InspectionMode.FAST,
28
+ include_updates: bool = False,
29
+ plugins: DiscoveredPlugins | None = None,
30
+ ) -> ClientSnapshot:
31
+ """Return a low-latency state bundle for GUI or agent clients."""
32
+ shared_plugins = plugins or await asyncio.to_thread(discover_all_plugins, use_cache=True)
33
+ projects = await self._project.inspect_cached(inspection_mode=inspection_mode, plugins=shared_plugins)
34
+ updates: ManagedToolReport | None = None
35
+ if include_updates:
36
+ updates = await self._tool.check_updates(plugins=shared_plugins)
37
+ diagnostics = projects.diagnostics + (updates.diagnostics if updates is not None else ())
38
+ follow_up_actions = projects.follow_up_actions + (updates.follow_up_actions if updates is not None else ())
39
+ return ClientSnapshot(
40
+ status=result_status(projects.success and (updates.success if updates is not None else True), diagnostics),
41
+ inspection_mode=inspection_mode,
42
+ plugins=plugin_snapshots(shared_plugins),
43
+ projects=projects,
44
+ updates=updates,
45
+ diagnostics=diagnostics,
46
+ follow_up_actions=follow_up_actions,
47
+ )
@@ -1,4 +1,6 @@
1
- """Core backend command modules.
1
+ """Public package exports for the backend.command.core module.
2
+
3
+ Core backend command modules.
2
4
 
3
5
  This package contains the core implementation modules for the
4
6
  backend command system: action building, execution, plugin