nextmv 1.7.0.dev0__tar.gz → 1.7.2__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 (391) hide show
  1. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/PKG-INFO +1 -1
  2. nextmv-1.7.2/nextmv/__about__.py +1 -0
  3. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/main.py +57 -0
  4. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/local/executor.py +22 -14
  5. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/local/runner.py +14 -3
  6. nextmv-1.7.2/nextmv/local/uv_handler.py +58 -0
  7. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/output.py +10 -41
  8. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_class-assign/requirements.txt +1 -1
  9. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_demand-alloc/allocation/requirements.txt +1 -1
  10. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_demand-alloc/workflow/requirements.txt +1 -1
  11. {nextmv-1.7.0.dev0/nextmv/templates/python_multi-file_hello-world → nextmv-1.7.2/nextmv/templates/python_json_hello-world}/requirements.txt +1 -1
  12. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_class-assign/requirements.txt +1 -1
  13. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_demand-alloc/allocation/requirements.txt +1 -1
  14. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_demand-alloc/workflow/requirements.txt +1 -1
  15. {nextmv-1.7.0.dev0/nextmv/templates/python_json_hello-world → nextmv-1.7.2/nextmv/templates/python_multi-file_hello-world}/requirements.txt +1 -1
  16. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/pyproject.toml +0 -1
  17. nextmv-1.7.2/tests/cli/test_main.py +176 -0
  18. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/local/test_runner.py +34 -2
  19. nextmv-1.7.2/tests/local/test_uv_handler.py +164 -0
  20. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_output.py +16 -57
  21. nextmv-1.7.0.dev0/nextmv/__about__.py +0 -1
  22. nextmv-1.7.0.dev0/tests/cli/test_main.py +0 -68
  23. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/.gitignore +0 -0
  24. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/.python-version +0 -0
  25. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/CONTRIBUTING.md +0 -0
  26. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/LICENSE +0 -0
  27. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/README.md +0 -0
  28. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/__entrypoint__.py +0 -0
  29. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/__init__.py +0 -0
  30. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/_serialization.py +0 -0
  31. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/account.py +0 -0
  32. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/base_model.py +0 -0
  33. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/__init__.py +0 -0
  34. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/__init__.py +0 -0
  35. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/acceptance/__init__.py +0 -0
  36. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/acceptance/create.py +0 -0
  37. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/acceptance/delete.py +0 -0
  38. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/acceptance/get.py +0 -0
  39. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/acceptance/list.py +0 -0
  40. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/acceptance/update.py +0 -0
  41. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/account/__init__.py +0 -0
  42. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/account/create.py +0 -0
  43. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/account/delete.py +0 -0
  44. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/account/get.py +0 -0
  45. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/account/update.py +0 -0
  46. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/app/__init__.py +0 -0
  47. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/app/create.py +0 -0
  48. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/app/delete.py +0 -0
  49. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/app/exists.py +0 -0
  50. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/app/get.py +0 -0
  51. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/app/list.py +0 -0
  52. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/app/push.py +0 -0
  53. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/app/update.py +0 -0
  54. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/batch/__init__.py +0 -0
  55. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/batch/create.py +0 -0
  56. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/batch/delete.py +0 -0
  57. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/batch/get.py +0 -0
  58. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/batch/list.py +0 -0
  59. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/batch/metadata.py +0 -0
  60. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/batch/update.py +0 -0
  61. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/data/__init__.py +0 -0
  62. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/data/upload.py +0 -0
  63. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/ensemble/__init__.py +0 -0
  64. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/ensemble/create.py +0 -0
  65. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/ensemble/delete.py +0 -0
  66. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/ensemble/get.py +0 -0
  67. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/ensemble/list.py +0 -0
  68. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/ensemble/update.py +0 -0
  69. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/input_set/__init__.py +0 -0
  70. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/input_set/create.py +0 -0
  71. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/input_set/delete.py +0 -0
  72. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/input_set/get.py +0 -0
  73. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/input_set/list.py +0 -0
  74. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/input_set/update.py +0 -0
  75. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/instance/__init__.py +0 -0
  76. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/instance/create.py +0 -0
  77. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/instance/delete.py +0 -0
  78. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/instance/exists.py +0 -0
  79. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/instance/get.py +0 -0
  80. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/instance/list.py +0 -0
  81. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/instance/update.py +0 -0
  82. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/managed_input/__init__.py +0 -0
  83. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/managed_input/create.py +0 -0
  84. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/managed_input/delete.py +0 -0
  85. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/managed_input/get.py +0 -0
  86. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/managed_input/list.py +0 -0
  87. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/managed_input/update.py +0 -0
  88. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/__init__.py +0 -0
  89. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/app/__init__.py +0 -0
  90. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/app/create.py +0 -0
  91. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/app/get.py +0 -0
  92. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/app/list.py +0 -0
  93. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/app/update.py +0 -0
  94. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/subscription/__init__.py +0 -0
  95. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/subscription/create.py +0 -0
  96. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/subscription/delete.py +0 -0
  97. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/subscription/get.py +0 -0
  98. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/subscription/list.py +0 -0
  99. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/version/__init__.py +0 -0
  100. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/version/create.py +0 -0
  101. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/version/get.py +0 -0
  102. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/version/list.py +0 -0
  103. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/marketplace/version/update.py +0 -0
  104. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/run/__init__.py +0 -0
  105. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/run/cancel.py +0 -0
  106. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/run/create.py +0 -0
  107. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/run/delete.py +0 -0
  108. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/run/get.py +0 -0
  109. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/run/input.py +0 -0
  110. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/run/list.py +0 -0
  111. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/run/logs.py +0 -0
  112. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/run/metadata.py +0 -0
  113. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/run/track.py +0 -0
  114. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/scenario/__init__.py +0 -0
  115. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/scenario/create.py +0 -0
  116. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/scenario/delete.py +0 -0
  117. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/scenario/get.py +0 -0
  118. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/scenario/list.py +0 -0
  119. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/scenario/metadata.py +0 -0
  120. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/scenario/update.py +0 -0
  121. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/secrets/__init__.py +0 -0
  122. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/secrets/create.py +0 -0
  123. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/secrets/delete.py +0 -0
  124. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/secrets/get.py +0 -0
  125. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/secrets/list.py +0 -0
  126. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/secrets/update.py +0 -0
  127. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/shadow/__init__.py +0 -0
  128. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/shadow/create.py +0 -0
  129. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/shadow/delete.py +0 -0
  130. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/shadow/get.py +0 -0
  131. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/shadow/list.py +0 -0
  132. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/shadow/metadata.py +0 -0
  133. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/shadow/start.py +0 -0
  134. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/shadow/stop.py +0 -0
  135. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/shadow/update.py +0 -0
  136. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/sso/__init__.py +0 -0
  137. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/sso/create.py +0 -0
  138. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/sso/delete.py +0 -0
  139. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/sso/disable.py +0 -0
  140. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/sso/domain/__init__.py +0 -0
  141. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/sso/domain/delete.py +0 -0
  142. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/sso/enable.py +0 -0
  143. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/sso/get.py +0 -0
  144. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/sso/update.py +0 -0
  145. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/switchback/__init__.py +0 -0
  146. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/switchback/create.py +0 -0
  147. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/switchback/delete.py +0 -0
  148. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/switchback/get.py +0 -0
  149. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/switchback/list.py +0 -0
  150. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/switchback/metadata.py +0 -0
  151. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/switchback/start.py +0 -0
  152. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/switchback/stop.py +0 -0
  153. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/switchback/update.py +0 -0
  154. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/upload/__init__.py +0 -0
  155. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/upload/create.py +0 -0
  156. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/version/__init__.py +0 -0
  157. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/version/create.py +0 -0
  158. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/version/delete.py +0 -0
  159. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/version/exists.py +0 -0
  160. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/version/get.py +0 -0
  161. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/version/list.py +0 -0
  162. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/cloud/version/update.py +0 -0
  163. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/community/__init__.py +0 -0
  164. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/community/clone.py +0 -0
  165. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/community/list.py +0 -0
  166. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/configuration/__init__.py +0 -0
  167. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/configuration/config.py +0 -0
  168. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/configuration/create.py +0 -0
  169. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/configuration/delete.py +0 -0
  170. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/configuration/list.py +0 -0
  171. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/init.py +0 -0
  172. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/__init__.py +0 -0
  173. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/app/__init__.py +0 -0
  174. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/app/delete.py +0 -0
  175. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/app/get.py +0 -0
  176. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/app/list.py +0 -0
  177. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/app/register.py +0 -0
  178. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/app/registered.py +0 -0
  179. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/app/sync.py +0 -0
  180. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/app/update.py +0 -0
  181. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/run/__init__.py +0 -0
  182. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/run/create.py +0 -0
  183. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/run/get.py +0 -0
  184. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/run/input.py +0 -0
  185. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/run/list.py +0 -0
  186. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/run/logs.py +0 -0
  187. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/run/metadata.py +0 -0
  188. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/local/run/visuals.py +0 -0
  189. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/manifest/__init__.py +0 -0
  190. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/manifest/init.py +0 -0
  191. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/manifest/validate.py +0 -0
  192. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/__init__.py +0 -0
  193. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/serve.py +0 -0
  194. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/server.py +0 -0
  195. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/WORKFLOW.md +0 -0
  196. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/__init__.py +0 -0
  197. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/_helpers.py +0 -0
  198. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/acceptance.py +0 -0
  199. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/account.py +0 -0
  200. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/app.py +0 -0
  201. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/batch.py +0 -0
  202. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/community.py +0 -0
  203. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/ensemble.py +0 -0
  204. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/guide.py +0 -0
  205. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/input_set.py +0 -0
  206. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/instance.py +0 -0
  207. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/local.py +0 -0
  208. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/managed_input.py +0 -0
  209. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/profile.py +0 -0
  210. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/run.py +0 -0
  211. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/scenario.py +0 -0
  212. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/secrets.py +0 -0
  213. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/shadow.py +0 -0
  214. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/sso.py +0 -0
  215. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/switchback.py +0 -0
  216. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/mcp/tools/version.py +0 -0
  217. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/message.py +0 -0
  218. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/options.py +0 -0
  219. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cli/version.py +0 -0
  220. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/__init__.py +0 -0
  221. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/acceptance_test.py +0 -0
  222. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/account.py +0 -0
  223. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/application/__init__.py +0 -0
  224. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/application/_acceptance.py +0 -0
  225. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/application/_batch_scenario.py +0 -0
  226. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/application/_ensemble.py +0 -0
  227. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/application/_input_set.py +0 -0
  228. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/application/_instance.py +0 -0
  229. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/application/_managed_input.py +0 -0
  230. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/application/_run.py +0 -0
  231. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/application/_secrets.py +0 -0
  232. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/application/_shadow.py +0 -0
  233. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/application/_switchback.py +0 -0
  234. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/application/_utils.py +0 -0
  235. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/application/_version.py +0 -0
  236. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/assets.py +0 -0
  237. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/batch_experiment.py +0 -0
  238. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/client.py +0 -0
  239. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/community.py +0 -0
  240. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/ensemble.py +0 -0
  241. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/input_set.py +0 -0
  242. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/instance.py +0 -0
  243. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/integration.py +0 -0
  244. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/marketplace.py +0 -0
  245. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/package.py +0 -0
  246. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/scenario.py +0 -0
  247. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/secrets.py +0 -0
  248. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/shadow.py +0 -0
  249. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/sso.py +0 -0
  250. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/switchback.py +0 -0
  251. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/url.py +0 -0
  252. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/cloud/version.py +0 -0
  253. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/content_format.py +0 -0
  254. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/deprecated.py +0 -0
  255. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/input.py +0 -0
  256. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/local/__init__.py +0 -0
  257. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/local/application.py +0 -0
  258. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/local/geojson_handler.py +0 -0
  259. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/local/local.py +0 -0
  260. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/local/plotly_handler.py +0 -0
  261. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/local/registry.py +0 -0
  262. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/logger.py +0 -0
  263. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/manifest.py +0 -0
  264. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/model.py +0 -0
  265. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/options.py +0 -0
  266. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/polling.py +0 -0
  267. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/run.py +0 -0
  268. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/safe.py +0 -0
  269. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/status.py +0 -0
  270. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/binary_json_app.yaml +0 -0
  271. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/binary_multi-file_app.yaml +0 -0
  272. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/go_json_app.yaml +0 -0
  273. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/go_json_hello-world/.gitignore +0 -0
  274. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/go_json_hello-world/README.md +0 -0
  275. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/go_json_hello-world/app.yaml +0 -0
  276. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/go_json_hello-world/go.mod +0 -0
  277. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/go_json_hello-world/input.json +0 -0
  278. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/go_json_hello-world/main.go +0 -0
  279. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/go_multi-file_app.yaml +0 -0
  280. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/go_multi-file_hello-world/.gitignore +0 -0
  281. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/go_multi-file_hello-world/README.md +0 -0
  282. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/go_multi-file_hello-world/app.yaml +0 -0
  283. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/go_multi-file_hello-world/go.mod +0 -0
  284. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/go_multi-file_hello-world/inputs/input.json +0 -0
  285. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/go_multi-file_hello-world/main.go +0 -0
  286. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/java_json_app.yaml +0 -0
  287. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/java_json_hello-world/.gitignore +0 -0
  288. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/java_json_hello-world/Main.java +0 -0
  289. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/java_json_hello-world/README.md +0 -0
  290. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/java_json_hello-world/app.yaml +0 -0
  291. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/java_json_hello-world/input.json +0 -0
  292. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/java_json_hello-world/pom.xml +0 -0
  293. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/java_multi-file_app.yaml +0 -0
  294. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/java_multi-file_hello-world/.gitignore +0 -0
  295. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/java_multi-file_hello-world/Main.java +0 -0
  296. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/java_multi-file_hello-world/README.md +0 -0
  297. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/java_multi-file_hello-world/app.yaml +0 -0
  298. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/java_multi-file_hello-world/inputs/input.json +0 -0
  299. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/java_multi-file_hello-world/pom.xml +0 -0
  300. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_app.yaml +0 -0
  301. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_class-assign/.gitignore +0 -0
  302. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_class-assign/README.md +0 -0
  303. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_class-assign/app.yaml +0 -0
  304. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_class-assign/input.json +0 -0
  305. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_class-assign/main.py +0 -0
  306. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_class-assign/visualizations.py +0 -0
  307. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_demand-alloc/.gitignore +0 -0
  308. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_demand-alloc/README.md +0 -0
  309. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_demand-alloc/allocation/README.md +0 -0
  310. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_demand-alloc/allocation/app.yaml +0 -0
  311. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_demand-alloc/allocation/input.json +0 -0
  312. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_demand-alloc/allocation/main.py +0 -0
  313. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_demand-alloc/allocation/visuals.py +0 -0
  314. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_demand-alloc/workflow/README.md +0 -0
  315. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_demand-alloc/workflow/app.yaml +0 -0
  316. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_demand-alloc/workflow/input.json +0 -0
  317. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_demand-alloc/workflow/main.py +0 -0
  318. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_demand-alloc/workflow/visuals.py +0 -0
  319. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_hello-world/.gitignore +0 -0
  320. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_hello-world/README.md +0 -0
  321. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_hello-world/app.yaml +0 -0
  322. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_hello-world/input.json +0 -0
  323. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_json_hello-world/main.py +0 -0
  324. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_app.yaml +0 -0
  325. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_class-assign/.gitignore +0 -0
  326. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_class-assign/README.md +0 -0
  327. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_class-assign/app.yaml +0 -0
  328. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_class-assign/inputs/input.json +0 -0
  329. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_class-assign/main.py +0 -0
  330. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_class-assign/visualizations.py +0 -0
  331. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_demand-alloc/README.md +0 -0
  332. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_demand-alloc/allocation/README.md +0 -0
  333. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_demand-alloc/allocation/app.yaml +0 -0
  334. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_demand-alloc/allocation/input.json +0 -0
  335. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_demand-alloc/allocation/main.py +0 -0
  336. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_demand-alloc/allocation/visuals.py +0 -0
  337. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_demand-alloc/workflow/.gitignore +0 -0
  338. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_demand-alloc/workflow/README.md +0 -0
  339. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_demand-alloc/workflow/app.yaml +0 -0
  340. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_demand-alloc/workflow/inputs/input.json +0 -0
  341. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_demand-alloc/workflow/main.py +0 -0
  342. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_demand-alloc/workflow/visuals.py +0 -0
  343. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_hello-world/.gitignore +0 -0
  344. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_hello-world/README.md +0 -0
  345. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_hello-world/app.yaml +0 -0
  346. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_hello-world/inputs/input.json +0 -0
  347. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/nextmv/templates/python_multi-file_hello-world/main.py +0 -0
  348. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/__init__.py +0 -0
  349. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/cli/__init__.py +0 -0
  350. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/cli/test_configuration.py +0 -0
  351. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/cli/test_mcp.py +0 -0
  352. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/cli/test_version.py +0 -0
  353. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/cloud/__init__.py +0 -0
  354. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/cloud/app.yaml +0 -0
  355. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/cloud/test_client.py +0 -0
  356. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/cloud/test_instance.py +0 -0
  357. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/cloud/test_package.py +0 -0
  358. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/cloud/test_scenario.py +0 -0
  359. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/integration/__init__.py +0 -0
  360. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/integration/cloud/__init__.py +0 -0
  361. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/integration/cloud/test_integration_cloud.py +0 -0
  362. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/integration/cloud/test_integration_marketplace.py +0 -0
  363. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/local/__init__.py +0 -0
  364. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/local/test_application.py +0 -0
  365. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/local/test_executor.py +0 -0
  366. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/local/test_registry.py +0 -0
  367. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/scripts/__init__.py +0 -0
  368. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/scripts/options1.py +0 -0
  369. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/scripts/options2.py +0 -0
  370. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/scripts/options3.py +0 -0
  371. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/scripts/options4.py +0 -0
  372. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/scripts/options5.py +0 -0
  373. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/scripts/options6.py +0 -0
  374. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/scripts/options7.py +0 -0
  375. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_base_model.py +0 -0
  376. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_entrypoint/__init__.py +0 -0
  377. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_entrypoint/test_entrypoint.py +0 -0
  378. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_input.py +0 -0
  379. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_inputs/test_data.csv +0 -0
  380. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_inputs/test_data.json +0 -0
  381. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_inputs/test_data.txt +0 -0
  382. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_logger.py +0 -0
  383. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_manifest.py +0 -0
  384. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_model.py +0 -0
  385. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_options.py +0 -0
  386. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_polling.py +0 -0
  387. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_run.py +0 -0
  388. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_safe.py +0 -0
  389. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_serialization.py +0 -0
  390. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/tests/test_version.py +0 -0
  391. {nextmv-1.7.0.dev0 → nextmv-1.7.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextmv
3
- Version: 1.7.0.dev0
3
+ Version: 1.7.2
4
4
  Summary: The all-purpose Python SDK for Nextmv
5
5
  Project-URL: Homepage, https://www.nextmv.io
6
6
  Project-URL: Documentation, https://nextmv-py.docs.nextmv.io/en/latest/nextmv/
@@ -0,0 +1 @@
1
+ __version__ = "v1.7.2"
@@ -13,7 +13,9 @@ about the features used here. An example of Rich markup can be found in the
13
13
  epilog of the Typer application defined below.
14
14
  """
15
15
 
16
+ import io
16
17
  import os
18
+ import runpy
17
19
  import sys
18
20
  from typing import Annotated
19
21
 
@@ -32,6 +34,7 @@ from nextmv.cli.message import confirmation, error, info, success, warning
32
34
  from nextmv.cli.version import app as version_app
33
35
  from nextmv.cli.version import version_callback
34
36
  from nextmv.cloud.client import retrieve_endpoint_from_config, retrieve_key_from_config
37
+ from nextmv.local.uv_handler import _find_uv_binary
35
38
 
36
39
  # Disable dim text for the extended help of commands.
37
40
  rich_utils.STYLE_HELPTEXT = ""
@@ -261,6 +264,38 @@ def _remove_go_cli() -> None:
261
264
  success(f"Deleted [italic red]deprecated[/italic red] [magenta]{GO_CLI_PATH}[/magenta].")
262
265
 
263
266
 
267
+ def setup_encoding() -> None:
268
+ """
269
+ Configure UTF-8 encoding for Windows platforms to make sure emojis and rich text do
270
+ not cause encoding errors.
271
+ """
272
+ # Only force UTF-8 encoding on Windows where the default encoding is not already UTF-8
273
+ # and where sys.stdout has a buffer attribute (indicating it's a real stream and not a
274
+ # mock).
275
+ if (
276
+ sys.platform == "win32"
277
+ and getattr(sys.stdout, "encoding", "").lower() != "utf-8"
278
+ and hasattr(sys.stdout, "buffer")
279
+ ):
280
+ try:
281
+ sys.stdout = io.TextIOWrapper(
282
+ sys.stdout.buffer,
283
+ encoding="utf-8",
284
+ errors="replace", # Don't crash on bad chars
285
+ line_buffering=True,
286
+ )
287
+ sys.stderr = io.TextIOWrapper(
288
+ sys.stderr.buffer,
289
+ encoding="utf-8",
290
+ errors="replace",
291
+ line_buffering=True,
292
+ )
293
+ except Exception:
294
+ # If wrapping fails (e.g. in some CI environments),
295
+ # fall back to the original stream rather than crashing.
296
+ pass
297
+
298
+
264
299
  def main() -> None:
265
300
  """
266
301
  Entry point for the CLI with global exception handling.
@@ -269,6 +304,28 @@ def main() -> None:
269
304
  own exit codes) and displays a clean error message instead of a traceback.
270
305
  """
271
306
 
307
+ # Improve compatibility with Windows terminals.
308
+ setup_encoding()
309
+
310
+ # Handle --run-script and --run-uv for running scripts with the bundled Python
311
+ # interpreter or via uv. These are used internally in case of frozen PyInstaller
312
+ # distributions.
313
+ if len(sys.argv) > 1 and sys.argv[1] == "--run-script":
314
+ if len(sys.argv) < 3:
315
+ rich.print("[red]Error:[/red] --run-script requires a script path.", file=sys.stderr)
316
+ sys.exit(1)
317
+ script_path = sys.argv[2]
318
+ sys.argv = sys.argv[2:] # script becomes argv[0]; its own args follow
319
+ runpy.run_path(script_path, run_name="__main__")
320
+ sys.exit(0)
321
+ elif len(sys.argv) > 1 and sys.argv[1] == "--run-uv":
322
+ if len(sys.argv) < 3:
323
+ rich.print("[red]Error:[/red] --run-uv requires arguments for 'uv run'.", file=sys.stderr)
324
+ sys.exit(1)
325
+ uv_bin = _find_uv_binary()
326
+ uv_args = [uv_bin, "run"] + sys.argv[2:]
327
+ os.execv(uv_bin, uv_args)
328
+
272
329
  try:
273
330
  app()
274
331
  except (typer.Exit, typer.Abort, SystemExit):
@@ -418,11 +418,6 @@ def process_run_output(
418
418
  temp_run_outputs_dir = os.path.join(temp_src, OUTPUTS_KEY)
419
419
 
420
420
  output_format = manifest.configuration.content.format
421
- _process_run_information(
422
- run_id=run_id,
423
- run_dir=run_dir,
424
- result=result,
425
- )
426
421
  _process_run_metrics(
427
422
  temp_run_outputs_dir=temp_run_outputs_dir,
428
423
  outputs_dir=outputs_dir,
@@ -459,6 +454,16 @@ def process_run_output(
459
454
  run_dir=run_dir,
460
455
  outputs_dir=outputs_dir,
461
456
  )
457
+ # NOTE: _process_run_information must be called last. It sets status_v2 to "succeeded"
458
+ # or "failed", which signals to pollers that the run is complete. format.output
459
+ # (written by _process_run_solutions) must already be on disk before that status
460
+ # transition is visible, otherwise a poller can observe status=succeeded with
461
+ # format_output=None and crash.
462
+ _process_run_information(
463
+ run_id=run_id,
464
+ run_dir=run_dir,
465
+ result=result,
466
+ )
462
467
 
463
468
 
464
469
  def _process_run_information(run_id: str, run_dir: str, result: subprocess.CompletedProcess[str]) -> None:
@@ -1073,6 +1078,12 @@ def __determine_command(manifest: Manifest) -> list[str]:
1073
1078
  """
1074
1079
  Returns the command to execute based on the application type.
1075
1080
 
1081
+ When running Python apps as a frozen PyInstaller single binary, ``sys.executable``
1082
+ points to the binary itself rather than a Python interpreter, so the normal
1083
+ ``[sys.executable, "-m", "uv", "run", ...]`` invocation cannot work. Instead, we use a
1084
+ special ``[sys.executable, "--run-uv", ...]`` invocation that signals to our own
1085
+ binary to re-execute and pass to the bundled uv binary.
1086
+
1076
1087
  Parameters
1077
1088
  ----------
1078
1089
  manifest : Manifest
@@ -1084,26 +1095,23 @@ def __determine_command(manifest: Manifest) -> list[str]:
1084
1095
  The command prefix to use for execution. Empty list for binary executables.
1085
1096
  """
1086
1097
  if manifest.type == ManifestType.PYTHON:
1098
+ is_frozen = getattr(sys, "frozen", False)
1099
+ entry_point = [sys.executable, "-m", "uv", "run"] if not is_frozen else [sys.executable, "--run-uv"]
1100
+
1087
1101
  if manifest.python and manifest.python.pip_requirements:
1088
1102
  if isinstance(manifest.python.pip_requirements, list):
1089
1103
  return [
1090
- sys.executable,
1091
- "-m",
1092
- "uv",
1093
- "run",
1104
+ *entry_point,
1094
1105
  "--with",
1095
1106
  ",".join(manifest.python.pip_requirements),
1096
1107
  ]
1097
1108
  elif isinstance(manifest.python.pip_requirements, str):
1098
1109
  return [
1099
- sys.executable,
1100
- "-m",
1101
- "uv",
1102
- "run",
1110
+ *entry_point,
1103
1111
  "--with-requirements",
1104
1112
  manifest.python.pip_requirements,
1105
1113
  ]
1106
- return [sys.executable, "-m", "uv", "run"]
1114
+ return [*entry_point]
1107
1115
  elif manifest.type == ManifestType.GO:
1108
1116
  return []
1109
1117
  elif manifest.type == ManifestType.BINARY:
@@ -122,7 +122,18 @@ def run(
122
122
  "options": options,
123
123
  }
124
124
  )
125
- args = [sys.executable, "executor.py"]
125
+ # When frozen as a single binary (PyInstaller), sys.executable points to
126
+ # the binary itself rather than a Python interpreter, so we use the
127
+ # --run-script mechanism to run executor.py with the bundled interpreter.
128
+ # In a normal Python package install, sys.executable is a real interpreter
129
+ # and executor.py can be launched directly.
130
+ # We always use the absolute path to executor.py so it can be resolved
131
+ # regardless of the working directory the binary was invoked from.
132
+ executor_path = os.path.join(os.path.dirname(__file__), "executor.py")
133
+ if getattr(sys, "frozen", False):
134
+ args = [sys.executable, "--run-script", executor_path]
135
+ else:
136
+ args = [sys.executable, executor_path]
126
137
  process = subprocess.Popen(
127
138
  args,
128
139
  env=os.environ,
@@ -130,8 +141,8 @@ def run(
130
141
  stdin=subprocess.PIPE,
131
142
  stdout=subprocess.DEVNULL,
132
143
  stderr=subprocess.DEVNULL,
133
- cwd=os.path.dirname(__file__),
134
- start_new_session=True, # Detach from parent process
144
+ cwd=os.path.dirname(executor_path),
145
+ start_new_session=True,
135
146
  )
136
147
  process.stdin.write(stdin_input)
137
148
  process.stdin.close()
@@ -0,0 +1,58 @@
1
+ import os
2
+ import platform
3
+ import sys
4
+
5
+
6
+ def _find_uv_binary() -> str:
7
+ """
8
+ Locate the uv binary at runtime.
9
+
10
+ Resolves the binary in this order:
11
+ 1. If running in a PyInstaller bundle, the uv binary is expected to be bundled under
12
+ `sys._MEIPASS/uv_bin/`.
13
+ 2. Otherwise, check whether uv module is installed and use its helper function to find
14
+ the binary.
15
+ 3. Finally, check if uv is available on the system PATH.
16
+
17
+ Returns
18
+ -------
19
+ str
20
+ Absolute path to the uv binary.
21
+
22
+ Raises
23
+ ------
24
+ FileNotFoundError
25
+ If the uv binary cannot be located.
26
+ """
27
+
28
+ # 1. Check for PyInstaller bundled uv binary.
29
+ if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
30
+ # In case of single binary PyInstaller distribution, the uv binary is bundled
31
+ # under sys._MEIPASS/uv_bin/.
32
+ exe_suffix = ".exe" if platform.system() == "Windows" else ""
33
+ uv_path = os.path.join(sys._MEIPASS, "uv_bin", f"uv{exe_suffix}")
34
+ if not os.path.isfile(uv_path):
35
+ raise FileNotFoundError(
36
+ f"Bundled uv binary not found at {uv_path}, please ensure proper installation of the Nextmv CLI."
37
+ )
38
+ return uv_path
39
+
40
+ # 2. Fall back to finding uv via the installed module (if available).
41
+ try:
42
+ from uv import find_uv_bin # type: ignore[import]
43
+
44
+ return find_uv_bin()
45
+ except (ImportError, AttributeError):
46
+ pass
47
+
48
+ # 3. Finally, check if uv is available on the system PATH.
49
+ from shutil import which
50
+
51
+ uv_path = which("uv")
52
+ if uv_path:
53
+ return uv_path
54
+
55
+ raise FileNotFoundError(
56
+ "uv binary not found. Please ensure that uv is installed and available "
57
+ + "on your system (via PATH or as module in Python)."
58
+ )
@@ -992,11 +992,10 @@ class Output:
992
992
  }
993
993
  ```
994
994
  """
995
- output_format: ContentFormat | None = ContentFormat.JSON
995
+ output_format: ContentFormat | None = None
996
996
  """
997
- Format of the output data. Default is `ContentFormat.JSON`. When set to
998
- `ContentFormat.MULTI_FILE`, the `solution_files` field must be specified and
999
- cannot be `None`.
997
+ Format of the output data. When set to `ContentFormat.MULTI_FILE`, the
998
+ `solution_files` field must be specified and cannot be `None`.
1000
999
  """
1001
1000
  solution: dict[str, Any] | Any | dict[str, list[dict[str, Any]]] | None = None
1002
1001
  """
@@ -1066,7 +1065,7 @@ class Output:
1066
1065
  handle the serialization of the data.
1067
1066
  """
1068
1067
 
1069
- def __post_init__(self):
1068
+ def __post_init__(self): # noqa: C901
1070
1069
  """
1071
1070
  Initialize and validate the Output instance.
1072
1071
 
@@ -1085,7 +1084,7 @@ class Output:
1085
1084
  new_options = copy.deepcopy(init_options)
1086
1085
  self.options = new_options
1087
1086
 
1088
- if type(self.output_format) is OutputFormat:
1087
+ if self.output_format is not None and type(self.output_format) is OutputFormat:
1089
1088
  deprecated(name="OutputFormat", reason="`OutputFormat` is deprecated, use `ContentFormat` instead")
1090
1089
  if self.output_format in {OutputFormat.TEXT, OutputFormat.CSV_ARCHIVE}:
1091
1090
  deprecated(
@@ -1097,37 +1096,6 @@ class Output:
1097
1096
  else:
1098
1097
  raise ValueError(f"unsupported output_format: {self.output_format}")
1099
1098
 
1100
- if self.solution is not None:
1101
- if self.output_format == ContentFormat.JSON:
1102
- try:
1103
- _ = serialize_json(self.solution)
1104
- except (TypeError, OverflowError) as e:
1105
- raise ValueError(
1106
- f"Output has `output_format` `ContentFormat.JSON` and "
1107
- f"`Output.solution` is of type {type(self.solution)}, which is not JSON serializable"
1108
- ) from e
1109
-
1110
- elif (
1111
- type(self.output_format) is OutputFormat
1112
- and self.output_format == OutputFormat.CSV_ARCHIVE
1113
- and not isinstance(self.solution, dict)
1114
- ):
1115
- raise ValueError(
1116
- f"unsupported Output.solution type: {type(self.solution)} with "
1117
- "output_format OutputFormat.CSV_ARCHIVE, supported type is `dict`"
1118
- )
1119
-
1120
- if self.solution_files is not None and self.output_format != ContentFormat.MULTI_FILE:
1121
- raise ValueError(
1122
- f"`solution_files` are not `None`, but `output_format` is different from `ContentFormat.MULTI_FILE`: "
1123
- f"{self.output_format}. If you want to use `solution_files`, set `output_format` "
1124
- "to `ContentFormat.MULTI_FILE`."
1125
- )
1126
- elif self.solution_files is not None and not isinstance(self.solution_files, list):
1127
- raise TypeError(
1128
- f"unsupported `Output.solution_files` type: {type(self.solution_files)}, supported type is `list`"
1129
- )
1130
-
1131
1099
  def to_dict(self) -> dict[str, Any]: # noqa: C901
1132
1100
  """
1133
1101
  Convert the `Output` object to a dictionary.
@@ -1204,14 +1172,15 @@ class Output:
1204
1172
  # Add the auxiliary configurations to the output dictionary if they are
1205
1173
  # defined and not empty.
1206
1174
  if (
1207
- self.output_format == OutputFormat.CSV_ARCHIVE
1175
+ self.output_format is not None
1176
+ and self.output_format == OutputFormat.CSV_ARCHIVE
1208
1177
  and self.csv_configurations is not None
1209
1178
  and self.csv_configurations != {}
1210
1179
  ):
1211
1180
  output_dict["csv_configurations"] = self.csv_configurations
1212
1181
 
1213
1182
  if (
1214
- self.output_format == ContentFormat.JSON
1183
+ (self.output_format is None or self.output_format == ContentFormat.JSON)
1215
1184
  and self.json_configurations is not None
1216
1185
  and self.json_configurations != {}
1217
1186
  ):
@@ -1505,9 +1474,9 @@ class LocalOutputWriter(OutputWriter):
1505
1474
  elif manifest is not None:
1506
1475
  resolved_content_format = manifest.configuration.content.format
1507
1476
  elif output is not None:
1508
- if isinstance(output, Output):
1477
+ if isinstance(output, Output) and output.output_format is not None:
1509
1478
  resolved_content_format = output.output_format
1510
- elif isinstance(output, dict) or isinstance(output, BaseModel):
1479
+ elif isinstance(output, (Output, dict, BaseModel)):
1511
1480
  resolved_content_format = ContentFormat.JSON
1512
1481
  else:
1513
1482
  raise ValueError(
@@ -1,3 +1,3 @@
1
- nextmv>=1.6.2
1
+ nextmv>=1.7.0
2
2
  pyomo>=6.7.0
3
3
  highspy>=1.7.0
@@ -1,4 +1,4 @@
1
- nextmv>=1.6.2
1
+ nextmv>=1.7.0
2
2
  numpy>=2.2.0
3
3
  pyomo>=6.7.0
4
4
  highspy>=1.7.0
@@ -1,4 +1,4 @@
1
- nextmv>=1.6.2
1
+ nextmv>=1.7.0
2
2
  nextpipe>=0.6.1
3
3
  numpy>=2.2.0
4
4
  pandas>=2.2.3
@@ -1,3 +1,3 @@
1
- nextmv>=1.6.2
1
+ nextmv>=1.7.0
2
2
  pyomo>=6.7.0
3
3
  highspy>=1.7.0
@@ -1,4 +1,4 @@
1
- nextmv>=1.6.2
1
+ nextmv>=1.7.0
2
2
  numpy>=2.2.0
3
3
  pyomo>=6.7.0
4
4
  highspy>=1.7.0
@@ -1,4 +1,4 @@
1
- nextmv>=1.6.2
1
+ nextmv>=1.7.0
2
2
  nextpipe>=0.6.1
3
3
  numpy>=2.2.0
4
4
  pandas>=2.2.3
@@ -55,7 +55,6 @@ Homepage = "https://www.nextmv.io"
55
55
  Documentation = "https://nextmv-py.docs.nextmv.io/en/latest/nextmv/"
56
56
  Repository = "https://github.com/nextmv-io/nextmv-py"
57
57
 
58
- # TODO: Uncomment when CLI is ready for general rollout.
59
58
  [project.scripts]
60
59
  nextmv = "nextmv.cli.main:main"
61
60
 
@@ -0,0 +1,176 @@
1
+ """
2
+ Unit tests for the nextmv CLI main module.
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import tempfile
8
+ import unittest
9
+ from unittest.mock import patch
10
+
11
+ from nextmv.cli.main import app, main
12
+ from typer.testing import CliRunner
13
+
14
+
15
+ class TestCallback(unittest.TestCase):
16
+ """Tests for the CLI callback behavior."""
17
+
18
+ def setUp(self):
19
+ self.runner = CliRunner()
20
+ self.app = app
21
+
22
+ @patch("nextmv.cli.main._go_cli_exists")
23
+ @patch("nextmv.cli.main.load_config")
24
+ def test_callback_skips_config_check_for_configure(self, mock_load_config, mock_go_cli_exists):
25
+ """Test that the callback skips config check when running configuration."""
26
+ mock_load_config.return_value = {}
27
+ mock_go_cli_exists.return_value = False
28
+
29
+ # Running configuration should not trigger the error even with empty config
30
+ result = self.runner.invoke(self.app, ["configuration", "--help"])
31
+ self.assertEqual(result.exit_code, 0)
32
+ self.assertIn("Configure the CLI", result.output)
33
+
34
+ @patch("nextmv.cli.main._go_cli_exists")
35
+ @patch("nextmv.cli.main.load_config")
36
+ def test_callback_shows_error_when_no_config(self, mock_load_config, mock_go_cli_exists):
37
+ """Test that the callback shows error when no config exists for other commands."""
38
+ mock_load_config.return_value = {}
39
+ mock_go_cli_exists.return_value = False
40
+
41
+ result = self.runner.invoke(self.app, ["version"])
42
+ self.assertEqual(result.exit_code, 0)
43
+
44
+ @patch("nextmv.cli.main._go_cli_exists")
45
+ @patch("nextmv.cli.main.load_config")
46
+ def test_callback_allows_command_when_config_exists(self, mock_load_config, mock_go_cli_exists):
47
+ """Test that the callback allows commands when config exists."""
48
+ mock_load_config.return_value = {"api_key": "test_key"}
49
+ mock_go_cli_exists.return_value = False
50
+
51
+ result = self.runner.invoke(self.app, ["version"])
52
+ self.assertEqual(result.exit_code, 0)
53
+
54
+
55
+ class TestHandleGoCli(unittest.TestCase):
56
+ """Tests for the Go CLI handling behavior."""
57
+
58
+ def setUp(self):
59
+ self.runner = CliRunner()
60
+ self.app = app
61
+
62
+ @patch("nextmv.cli.main._go_cli_exists")
63
+ @patch("nextmv.cli.main.load_config")
64
+ def test_no_prompt_when_go_cli_not_exists(self, mock_load_config, mock_go_cli_exists):
65
+ """Test that no prompt is shown when Go CLI does not exist."""
66
+ mock_go_cli_exists.return_value = False
67
+ mock_load_config.return_value = {"api_key": "test_key"}
68
+
69
+ result = self.runner.invoke(self.app, ["version"])
70
+ self.assertEqual(result.exit_code, 0)
71
+ self.assertNotIn("deprecated", result.output)
72
+
73
+
74
+ class TestRunScript(unittest.TestCase):
75
+ """Tests for the --run-script hidden flag."""
76
+
77
+ def _write_script(self, content: str) -> str:
78
+ """Write content to a temporary script file and return its path."""
79
+ fd, path = tempfile.mkstemp(suffix=".py")
80
+ with os.fdopen(fd, "w") as f:
81
+ f.write(content)
82
+ return path
83
+
84
+ def test_run_script_executes_script(self):
85
+ """Test that --run-script runs the given script."""
86
+ script = self._write_script("import sys; sys.exit(42)\n")
87
+ try:
88
+ with patch.object(sys, "argv", ["nextmv", "--run-script", script]):
89
+ with self.assertRaises(SystemExit) as cm:
90
+ main()
91
+ self.assertEqual(cm.exception.code, 42)
92
+ finally:
93
+ os.unlink(script)
94
+
95
+ def test_run_script_forwards_trailing_args(self):
96
+ """Test that trailing arguments are visible to the script as sys.argv."""
97
+ # The script writes its own argv to a temp file so we can inspect it.
98
+ fd, result_file = tempfile.mkstemp(suffix=".txt")
99
+ os.close(fd)
100
+ script = self._write_script(f"import sys\nopen({result_file!r}, 'w').write(','.join(sys.argv))\n")
101
+ try:
102
+ with patch.object(sys, "argv", ["nextmv", "--run-script", script, "arg1", "arg2"]):
103
+ with self.assertRaises(SystemExit) as cm:
104
+ main()
105
+ self.assertEqual(cm.exception.code, 0)
106
+ with open(result_file) as f:
107
+ recorded = f.read().split(",")
108
+ # The script sees itself as argv[0] and its own trailing args after.
109
+ self.assertEqual(recorded[0], script)
110
+ self.assertEqual(recorded[1], "arg1")
111
+ self.assertEqual(recorded[2], "arg2")
112
+ finally:
113
+ os.unlink(script)
114
+ if os.path.exists(result_file):
115
+ os.unlink(result_file)
116
+
117
+ def test_run_script_missing_path_exits_with_error(self):
118
+ """Test that omitting the script path prints an error and exits 1."""
119
+ with patch.object(sys, "argv", ["nextmv", "--run-script"]):
120
+ with self.assertRaises(SystemExit) as cm:
121
+ main()
122
+ self.assertEqual(cm.exception.code, 1)
123
+
124
+
125
+ class TestRunUv(unittest.TestCase):
126
+ """Tests for the --run-uv hidden flag."""
127
+
128
+ def test_run_uv_missing_args_exits_with_error(self):
129
+ """--run-uv with no further arguments prints an error and exits 1."""
130
+ with patch.object(sys, "argv", ["nextmv", "--run-uv"]):
131
+ with self.assertRaises(SystemExit) as cm:
132
+ main()
133
+ self.assertEqual(cm.exception.code, 1)
134
+
135
+ @patch("nextmv.cli.main._find_uv_binary")
136
+ @patch("os.execv")
137
+ def test_run_uv_calls_execv_with_correct_args(self, mock_execv, mock_find_uv):
138
+ """--run-uv resolves the uv binary and calls os.execv with 'uv run <args>'."""
139
+ mock_find_uv.return_value = "/usr/bin/uv"
140
+ # os.execv normally replaces the process and never returns; raise SystemExit
141
+ # so that main() stops at that point rather than falling through to app().
142
+ mock_execv.side_effect = SystemExit(0)
143
+
144
+ with patch.object(sys, "argv", ["nextmv", "--run-uv", "script.py", "--option", "val"]):
145
+ with self.assertRaises(SystemExit) as cm:
146
+ main()
147
+ self.assertEqual(cm.exception.code, 0)
148
+
149
+ mock_find_uv.assert_called_once()
150
+ mock_execv.assert_called_once_with(
151
+ "/usr/bin/uv",
152
+ ["/usr/bin/uv", "run", "script.py", "--option", "val"],
153
+ )
154
+
155
+ @patch("nextmv.cli.main._find_uv_binary")
156
+ @patch("os.execv")
157
+ def test_run_uv_single_extra_arg(self, mock_execv, mock_find_uv):
158
+ """--run-uv works with a single extra argument."""
159
+ mock_find_uv.return_value = "/opt/uv/uv"
160
+ mock_execv.side_effect = SystemExit(0)
161
+
162
+ with patch.object(sys, "argv", ["nextmv", "--run-uv", "app.py"]):
163
+ with self.assertRaises(SystemExit):
164
+ main()
165
+
166
+ mock_execv.assert_called_once_with(
167
+ "/opt/uv/uv",
168
+ ["/opt/uv/uv", "run", "app.py"],
169
+ )
170
+
171
+ @patch("nextmv.cli.main._find_uv_binary", side_effect=FileNotFoundError("uv not found"))
172
+ def test_run_uv_uv_binary_not_found_raises(self, mock_find_uv):
173
+ """--run-uv propagates FileNotFoundError when the uv binary cannot be located."""
174
+ with patch.object(sys, "argv", ["nextmv", "--run-uv", "script.py"]):
175
+ with self.assertRaises(FileNotFoundError):
176
+ main()
@@ -10,10 +10,13 @@ import tempfile
10
10
  import unittest
11
11
  from unittest.mock import Mock, patch
12
12
 
13
+ import nextmv.local.runner as runner_module
13
14
  from nextmv.local.local import NEXTMV_DIR, RUNS_KEY
14
15
  from nextmv.local.runner import new_run, record_input, run
15
16
  from nextmv.manifest import Manifest, ManifestRuntime
16
17
 
18
+ EXECUTOR_PATH = os.path.join(os.path.dirname(runner_module.__file__), "executor.py")
19
+
17
20
 
18
21
  class TestLocalRunner(unittest.TestCase):
19
22
  """Test cases for the local runner module."""
@@ -268,8 +271,8 @@ print(json.dumps(output))
268
271
  mock_popen.assert_called_once()
269
272
  popen_args = mock_popen.call_args
270
273
 
271
- # Check the command
272
- self.assertEqual(popen_args[0][0], [sys.executable, "executor.py"])
274
+ # Check the command (normal Python install, not frozen)
275
+ self.assertEqual(popen_args[0][0], [sys.executable, EXECUTOR_PATH])
273
276
 
274
277
  # Check that stdin was written to
275
278
  mock_process.stdin.write.assert_called_once()
@@ -296,6 +299,35 @@ print(json.dumps(output))
296
299
  self.assertEqual(stdin_json["options"], options)
297
300
  self.assertEqual(stdin_json["run_config"], run_config)
298
301
 
302
+ @patch("nextmv.local.runner.subprocess.Popen")
303
+ @patch("nextmv.local.runner.safe_id")
304
+ def test_run_function_execution_frozen(self, mock_safe_id, mock_popen):
305
+ """Test that --run-script is used when running as a frozen PyInstaller binary."""
306
+ mock_safe_id.return_value = "test-run-id"
307
+ mock_process = Mock()
308
+ mock_process.stdin = Mock()
309
+ mock_popen.return_value = mock_process
310
+
311
+ manifest = Manifest(
312
+ files=["main.py"],
313
+ runtime=ManifestRuntime.PYTHON,
314
+ )
315
+
316
+ run_config = {"format": {"input": {"type": "json"}, "output": {"type": "json"}}}
317
+
318
+ with patch.object(sys, "frozen", True, create=True):
319
+ run(
320
+ app_id="sample-app",
321
+ src=self.test_src,
322
+ manifest=manifest,
323
+ run_config=run_config,
324
+ input_data={},
325
+ options={},
326
+ )
327
+
328
+ popen_args = mock_popen.call_args
329
+ self.assertEqual(popen_args[0][0], [sys.executable, "--run-script", EXECUTOR_PATH])
330
+
299
331
  @patch("nextmv.local.runner.subprocess.Popen")
300
332
  @patch("nextmv.local.runner.safe_id")
301
333
  def test_run_with_inputs_dir_path(self, mock_safe_id, mock_popen):