workato-platform-cli 1.0.0rc5.dev5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. workato_platform_cli/__init__.py +135 -0
  2. workato_platform_cli/_version.py +34 -0
  3. workato_platform_cli/cli/__init__.py +126 -0
  4. workato_platform_cli/cli/commands/__init__.py +0 -0
  5. workato_platform_cli/cli/commands/api_clients.py +627 -0
  6. workato_platform_cli/cli/commands/api_collections.py +497 -0
  7. workato_platform_cli/cli/commands/assets.py +82 -0
  8. workato_platform_cli/cli/commands/connections.py +1205 -0
  9. workato_platform_cli/cli/commands/connectors/__init__.py +0 -0
  10. workato_platform_cli/cli/commands/connectors/command.py +178 -0
  11. workato_platform_cli/cli/commands/connectors/connector_manager.py +351 -0
  12. workato_platform_cli/cli/commands/data_tables.py +345 -0
  13. workato_platform_cli/cli/commands/guide.py +315 -0
  14. workato_platform_cli/cli/commands/init.py +229 -0
  15. workato_platform_cli/cli/commands/profiles.py +364 -0
  16. workato_platform_cli/cli/commands/projects/__init__.py +0 -0
  17. workato_platform_cli/cli/commands/projects/command.py +513 -0
  18. workato_platform_cli/cli/commands/projects/project_manager.py +338 -0
  19. workato_platform_cli/cli/commands/properties.py +174 -0
  20. workato_platform_cli/cli/commands/pull.py +327 -0
  21. workato_platform_cli/cli/commands/push/__init__.py +0 -0
  22. workato_platform_cli/cli/commands/push/command.py +320 -0
  23. workato_platform_cli/cli/commands/recipes/__init__.py +0 -0
  24. workato_platform_cli/cli/commands/recipes/command.py +847 -0
  25. workato_platform_cli/cli/commands/recipes/validator.py +1740 -0
  26. workato_platform_cli/cli/commands/workspace.py +73 -0
  27. workato_platform_cli/cli/containers.py +80 -0
  28. workato_platform_cli/cli/resources/data/connection-data.json +7364 -0
  29. workato_platform_cli/cli/resources/data/picklist-data.json +3706 -0
  30. workato_platform_cli/cli/resources/docs/README.md +178 -0
  31. workato_platform_cli/cli/resources/docs/actions.md +452 -0
  32. workato_platform_cli/cli/resources/docs/block-structure.md +424 -0
  33. workato_platform_cli/cli/resources/docs/connections-parameters.md +11946 -0
  34. workato_platform_cli/cli/resources/docs/data-mapping.md +779 -0
  35. workato_platform_cli/cli/resources/docs/formulas/array-list-formulas.md +1276 -0
  36. workato_platform_cli/cli/resources/docs/formulas/conditions.md +102 -0
  37. workato_platform_cli/cli/resources/docs/formulas/date-formulas.md +798 -0
  38. workato_platform_cli/cli/resources/docs/formulas/number-formulas.md +507 -0
  39. workato_platform_cli/cli/resources/docs/formulas/other-formulas.md +419 -0
  40. workato_platform_cli/cli/resources/docs/formulas/string-formulas.md +1353 -0
  41. workato_platform_cli/cli/resources/docs/formulas.md +214 -0
  42. workato_platform_cli/cli/resources/docs/naming-conventions.md +163 -0
  43. workato_platform_cli/cli/resources/docs/recipe-deployment-workflow.md +352 -0
  44. workato_platform_cli/cli/resources/docs/recipe-fundamentals.md +179 -0
  45. workato_platform_cli/cli/resources/docs/triggers.md +360 -0
  46. workato_platform_cli/cli/utils/__init__.py +10 -0
  47. workato_platform_cli/cli/utils/config/__init__.py +33 -0
  48. workato_platform_cli/cli/utils/config/manager.py +1001 -0
  49. workato_platform_cli/cli/utils/config/models.py +89 -0
  50. workato_platform_cli/cli/utils/config/profiles.py +491 -0
  51. workato_platform_cli/cli/utils/config/workspace.py +113 -0
  52. workato_platform_cli/cli/utils/exception_handler.py +531 -0
  53. workato_platform_cli/cli/utils/gitignore.py +32 -0
  54. workato_platform_cli/cli/utils/ignore_patterns.py +44 -0
  55. workato_platform_cli/cli/utils/spinner.py +63 -0
  56. workato_platform_cli/cli/utils/version_checker.py +237 -0
  57. workato_platform_cli/client/__init__.py +0 -0
  58. workato_platform_cli/client/workato_api/__init__.py +202 -0
  59. workato_platform_cli/client/workato_api/api/__init__.py +15 -0
  60. workato_platform_cli/client/workato_api/api/api_platform_api.py +2875 -0
  61. workato_platform_cli/client/workato_api/api/connections_api.py +1807 -0
  62. workato_platform_cli/client/workato_api/api/connectors_api.py +840 -0
  63. workato_platform_cli/client/workato_api/api/data_tables_api.py +604 -0
  64. workato_platform_cli/client/workato_api/api/export_api.py +621 -0
  65. workato_platform_cli/client/workato_api/api/folders_api.py +621 -0
  66. workato_platform_cli/client/workato_api/api/packages_api.py +1197 -0
  67. workato_platform_cli/client/workato_api/api/projects_api.py +590 -0
  68. workato_platform_cli/client/workato_api/api/properties_api.py +620 -0
  69. workato_platform_cli/client/workato_api/api/recipes_api.py +1379 -0
  70. workato_platform_cli/client/workato_api/api/users_api.py +285 -0
  71. workato_platform_cli/client/workato_api/api_client.py +807 -0
  72. workato_platform_cli/client/workato_api/api_response.py +21 -0
  73. workato_platform_cli/client/workato_api/configuration.py +601 -0
  74. workato_platform_cli/client/workato_api/docs/APIPlatformApi.md +844 -0
  75. workato_platform_cli/client/workato_api/docs/ApiClient.md +46 -0
  76. workato_platform_cli/client/workato_api/docs/ApiClientApiCollectionsInner.md +30 -0
  77. workato_platform_cli/client/workato_api/docs/ApiClientApiPoliciesInner.md +30 -0
  78. workato_platform_cli/client/workato_api/docs/ApiClientCreateRequest.md +46 -0
  79. workato_platform_cli/client/workato_api/docs/ApiClientListResponse.md +32 -0
  80. workato_platform_cli/client/workato_api/docs/ApiClientResponse.md +29 -0
  81. workato_platform_cli/client/workato_api/docs/ApiCollection.md +38 -0
  82. workato_platform_cli/client/workato_api/docs/ApiCollectionCreateRequest.md +32 -0
  83. workato_platform_cli/client/workato_api/docs/ApiEndpoint.md +41 -0
  84. workato_platform_cli/client/workato_api/docs/ApiKey.md +36 -0
  85. workato_platform_cli/client/workato_api/docs/ApiKeyCreateRequest.md +32 -0
  86. workato_platform_cli/client/workato_api/docs/ApiKeyListResponse.md +32 -0
  87. workato_platform_cli/client/workato_api/docs/ApiKeyResponse.md +29 -0
  88. workato_platform_cli/client/workato_api/docs/Asset.md +39 -0
  89. workato_platform_cli/client/workato_api/docs/AssetReference.md +37 -0
  90. workato_platform_cli/client/workato_api/docs/Connection.md +44 -0
  91. workato_platform_cli/client/workato_api/docs/ConnectionCreateRequest.md +35 -0
  92. workato_platform_cli/client/workato_api/docs/ConnectionUpdateRequest.md +34 -0
  93. workato_platform_cli/client/workato_api/docs/ConnectionsApi.md +526 -0
  94. workato_platform_cli/client/workato_api/docs/ConnectorAction.md +33 -0
  95. workato_platform_cli/client/workato_api/docs/ConnectorVersion.md +32 -0
  96. workato_platform_cli/client/workato_api/docs/ConnectorsApi.md +249 -0
  97. workato_platform_cli/client/workato_api/docs/CreateExportManifestRequest.md +29 -0
  98. workato_platform_cli/client/workato_api/docs/CreateFolderRequest.md +30 -0
  99. workato_platform_cli/client/workato_api/docs/CustomConnector.md +35 -0
  100. workato_platform_cli/client/workato_api/docs/CustomConnectorCodeResponse.md +29 -0
  101. workato_platform_cli/client/workato_api/docs/CustomConnectorCodeResponseData.md +29 -0
  102. workato_platform_cli/client/workato_api/docs/CustomConnectorListResponse.md +29 -0
  103. workato_platform_cli/client/workato_api/docs/DataTable.md +34 -0
  104. workato_platform_cli/client/workato_api/docs/DataTableColumn.md +37 -0
  105. workato_platform_cli/client/workato_api/docs/DataTableColumnRequest.md +37 -0
  106. workato_platform_cli/client/workato_api/docs/DataTableCreateRequest.md +31 -0
  107. workato_platform_cli/client/workato_api/docs/DataTableCreateResponse.md +29 -0
  108. workato_platform_cli/client/workato_api/docs/DataTableListResponse.md +29 -0
  109. workato_platform_cli/client/workato_api/docs/DataTableRelation.md +30 -0
  110. workato_platform_cli/client/workato_api/docs/DataTablesApi.md +172 -0
  111. workato_platform_cli/client/workato_api/docs/DeleteProject403Response.md +29 -0
  112. workato_platform_cli/client/workato_api/docs/Error.md +29 -0
  113. workato_platform_cli/client/workato_api/docs/ExportApi.md +175 -0
  114. workato_platform_cli/client/workato_api/docs/ExportManifestRequest.md +35 -0
  115. workato_platform_cli/client/workato_api/docs/ExportManifestResponse.md +29 -0
  116. workato_platform_cli/client/workato_api/docs/ExportManifestResponseResult.md +36 -0
  117. workato_platform_cli/client/workato_api/docs/Folder.md +35 -0
  118. workato_platform_cli/client/workato_api/docs/FolderAssetsResponse.md +29 -0
  119. workato_platform_cli/client/workato_api/docs/FolderAssetsResponseResult.md +29 -0
  120. workato_platform_cli/client/workato_api/docs/FolderCreationResponse.md +35 -0
  121. workato_platform_cli/client/workato_api/docs/FoldersApi.md +176 -0
  122. workato_platform_cli/client/workato_api/docs/ImportResults.md +32 -0
  123. workato_platform_cli/client/workato_api/docs/OAuthUrlResponse.md +29 -0
  124. workato_platform_cli/client/workato_api/docs/OAuthUrlResponseData.md +29 -0
  125. workato_platform_cli/client/workato_api/docs/OpenApiSpec.md +30 -0
  126. workato_platform_cli/client/workato_api/docs/PackageDetailsResponse.md +35 -0
  127. workato_platform_cli/client/workato_api/docs/PackageDetailsResponseRecipeStatusInner.md +30 -0
  128. workato_platform_cli/client/workato_api/docs/PackageResponse.md +33 -0
  129. workato_platform_cli/client/workato_api/docs/PackagesApi.md +364 -0
  130. workato_platform_cli/client/workato_api/docs/PicklistRequest.md +30 -0
  131. workato_platform_cli/client/workato_api/docs/PicklistResponse.md +29 -0
  132. workato_platform_cli/client/workato_api/docs/PlatformConnector.md +36 -0
  133. workato_platform_cli/client/workato_api/docs/PlatformConnectorListResponse.md +32 -0
  134. workato_platform_cli/client/workato_api/docs/Project.md +32 -0
  135. workato_platform_cli/client/workato_api/docs/ProjectsApi.md +173 -0
  136. workato_platform_cli/client/workato_api/docs/PropertiesApi.md +186 -0
  137. workato_platform_cli/client/workato_api/docs/Recipe.md +58 -0
  138. workato_platform_cli/client/workato_api/docs/RecipeConfigInner.md +33 -0
  139. workato_platform_cli/client/workato_api/docs/RecipeConnectionUpdateRequest.md +30 -0
  140. workato_platform_cli/client/workato_api/docs/RecipeListResponse.md +29 -0
  141. workato_platform_cli/client/workato_api/docs/RecipeStartResponse.md +31 -0
  142. workato_platform_cli/client/workato_api/docs/RecipesApi.md +367 -0
  143. workato_platform_cli/client/workato_api/docs/RuntimeUserConnectionCreateRequest.md +34 -0
  144. workato_platform_cli/client/workato_api/docs/RuntimeUserConnectionResponse.md +29 -0
  145. workato_platform_cli/client/workato_api/docs/RuntimeUserConnectionResponseData.md +30 -0
  146. workato_platform_cli/client/workato_api/docs/SuccessResponse.md +29 -0
  147. workato_platform_cli/client/workato_api/docs/UpsertProjectPropertiesRequest.md +29 -0
  148. workato_platform_cli/client/workato_api/docs/User.md +48 -0
  149. workato_platform_cli/client/workato_api/docs/UsersApi.md +84 -0
  150. workato_platform_cli/client/workato_api/docs/ValidationError.md +30 -0
  151. workato_platform_cli/client/workato_api/docs/ValidationErrorErrorsValue.md +28 -0
  152. workato_platform_cli/client/workato_api/exceptions.py +216 -0
  153. workato_platform_cli/client/workato_api/models/__init__.py +83 -0
  154. workato_platform_cli/client/workato_api/models/api_client.py +185 -0
  155. workato_platform_cli/client/workato_api/models/api_client_api_collections_inner.py +89 -0
  156. workato_platform_cli/client/workato_api/models/api_client_api_policies_inner.py +89 -0
  157. workato_platform_cli/client/workato_api/models/api_client_create_request.py +138 -0
  158. workato_platform_cli/client/workato_api/models/api_client_list_response.py +101 -0
  159. workato_platform_cli/client/workato_api/models/api_client_response.py +91 -0
  160. workato_platform_cli/client/workato_api/models/api_collection.py +110 -0
  161. workato_platform_cli/client/workato_api/models/api_collection_create_request.py +97 -0
  162. workato_platform_cli/client/workato_api/models/api_endpoint.py +117 -0
  163. workato_platform_cli/client/workato_api/models/api_key.py +102 -0
  164. workato_platform_cli/client/workato_api/models/api_key_create_request.py +93 -0
  165. workato_platform_cli/client/workato_api/models/api_key_list_response.py +101 -0
  166. workato_platform_cli/client/workato_api/models/api_key_response.py +91 -0
  167. workato_platform_cli/client/workato_api/models/asset.py +124 -0
  168. workato_platform_cli/client/workato_api/models/asset_reference.py +110 -0
  169. workato_platform_cli/client/workato_api/models/connection.py +173 -0
  170. workato_platform_cli/client/workato_api/models/connection_create_request.py +99 -0
  171. workato_platform_cli/client/workato_api/models/connection_update_request.py +97 -0
  172. workato_platform_cli/client/workato_api/models/connector_action.py +100 -0
  173. workato_platform_cli/client/workato_api/models/connector_version.py +99 -0
  174. workato_platform_cli/client/workato_api/models/create_export_manifest_request.py +91 -0
  175. workato_platform_cli/client/workato_api/models/create_folder_request.py +89 -0
  176. workato_platform_cli/client/workato_api/models/custom_connector.py +117 -0
  177. workato_platform_cli/client/workato_api/models/custom_connector_code_response.py +91 -0
  178. workato_platform_cli/client/workato_api/models/custom_connector_code_response_data.py +87 -0
  179. workato_platform_cli/client/workato_api/models/custom_connector_list_response.py +95 -0
  180. workato_platform_cli/client/workato_api/models/data_table.py +107 -0
  181. workato_platform_cli/client/workato_api/models/data_table_column.py +125 -0
  182. workato_platform_cli/client/workato_api/models/data_table_column_request.py +130 -0
  183. workato_platform_cli/client/workato_api/models/data_table_create_request.py +99 -0
  184. workato_platform_cli/client/workato_api/models/data_table_create_response.py +91 -0
  185. workato_platform_cli/client/workato_api/models/data_table_list_response.py +95 -0
  186. workato_platform_cli/client/workato_api/models/data_table_relation.py +90 -0
  187. workato_platform_cli/client/workato_api/models/delete_project403_response.py +87 -0
  188. workato_platform_cli/client/workato_api/models/error.py +87 -0
  189. workato_platform_cli/client/workato_api/models/export_manifest_request.py +107 -0
  190. workato_platform_cli/client/workato_api/models/export_manifest_response.py +91 -0
  191. workato_platform_cli/client/workato_api/models/export_manifest_response_result.py +112 -0
  192. workato_platform_cli/client/workato_api/models/folder.py +110 -0
  193. workato_platform_cli/client/workato_api/models/folder_assets_response.py +91 -0
  194. workato_platform_cli/client/workato_api/models/folder_assets_response_result.py +95 -0
  195. workato_platform_cli/client/workato_api/models/folder_creation_response.py +110 -0
  196. workato_platform_cli/client/workato_api/models/import_results.py +93 -0
  197. workato_platform_cli/client/workato_api/models/o_auth_url_response.py +91 -0
  198. workato_platform_cli/client/workato_api/models/o_auth_url_response_data.py +87 -0
  199. workato_platform_cli/client/workato_api/models/open_api_spec.py +96 -0
  200. workato_platform_cli/client/workato_api/models/package_details_response.py +126 -0
  201. workato_platform_cli/client/workato_api/models/package_details_response_recipe_status_inner.py +99 -0
  202. workato_platform_cli/client/workato_api/models/package_response.py +109 -0
  203. workato_platform_cli/client/workato_api/models/picklist_request.py +89 -0
  204. workato_platform_cli/client/workato_api/models/picklist_response.py +88 -0
  205. workato_platform_cli/client/workato_api/models/platform_connector.py +116 -0
  206. workato_platform_cli/client/workato_api/models/platform_connector_list_response.py +101 -0
  207. workato_platform_cli/client/workato_api/models/project.py +93 -0
  208. workato_platform_cli/client/workato_api/models/recipe.py +174 -0
  209. workato_platform_cli/client/workato_api/models/recipe_config_inner.py +100 -0
  210. workato_platform_cli/client/workato_api/models/recipe_connection_update_request.py +89 -0
  211. workato_platform_cli/client/workato_api/models/recipe_list_response.py +95 -0
  212. workato_platform_cli/client/workato_api/models/recipe_start_response.py +91 -0
  213. workato_platform_cli/client/workato_api/models/runtime_user_connection_create_request.py +97 -0
  214. workato_platform_cli/client/workato_api/models/runtime_user_connection_response.py +91 -0
  215. workato_platform_cli/client/workato_api/models/runtime_user_connection_response_data.py +89 -0
  216. workato_platform_cli/client/workato_api/models/success_response.py +87 -0
  217. workato_platform_cli/client/workato_api/models/upsert_project_properties_request.py +88 -0
  218. workato_platform_cli/client/workato_api/models/user.py +151 -0
  219. workato_platform_cli/client/workato_api/models/validation_error.py +102 -0
  220. workato_platform_cli/client/workato_api/models/validation_error_errors_value.py +143 -0
  221. workato_platform_cli/client/workato_api/rest.py +213 -0
  222. workato_platform_cli/client/workato_api/test/__init__.py +0 -0
  223. workato_platform_cli/client/workato_api/test/test_api_client.py +94 -0
  224. workato_platform_cli/client/workato_api/test/test_api_client_api_collections_inner.py +52 -0
  225. workato_platform_cli/client/workato_api/test/test_api_client_api_policies_inner.py +52 -0
  226. workato_platform_cli/client/workato_api/test/test_api_client_create_request.py +75 -0
  227. workato_platform_cli/client/workato_api/test/test_api_client_list_response.py +114 -0
  228. workato_platform_cli/client/workato_api/test/test_api_client_response.py +104 -0
  229. workato_platform_cli/client/workato_api/test/test_api_collection.py +72 -0
  230. workato_platform_cli/client/workato_api/test/test_api_collection_create_request.py +57 -0
  231. workato_platform_cli/client/workato_api/test/test_api_endpoint.py +75 -0
  232. workato_platform_cli/client/workato_api/test/test_api_key.py +64 -0
  233. workato_platform_cli/client/workato_api/test/test_api_key_create_request.py +56 -0
  234. workato_platform_cli/client/workato_api/test/test_api_key_list_response.py +78 -0
  235. workato_platform_cli/client/workato_api/test/test_api_key_response.py +68 -0
  236. workato_platform_cli/client/workato_api/test/test_api_platform_api.py +101 -0
  237. workato_platform_cli/client/workato_api/test/test_asset.py +67 -0
  238. workato_platform_cli/client/workato_api/test/test_asset_reference.py +62 -0
  239. workato_platform_cli/client/workato_api/test/test_connection.py +81 -0
  240. workato_platform_cli/client/workato_api/test/test_connection_create_request.py +59 -0
  241. workato_platform_cli/client/workato_api/test/test_connection_update_request.py +56 -0
  242. workato_platform_cli/client/workato_api/test/test_connections_api.py +73 -0
  243. workato_platform_cli/client/workato_api/test/test_connector_action.py +59 -0
  244. workato_platform_cli/client/workato_api/test/test_connector_version.py +58 -0
  245. workato_platform_cli/client/workato_api/test/test_connectors_api.py +52 -0
  246. workato_platform_cli/client/workato_api/test/test_create_export_manifest_request.py +88 -0
  247. workato_platform_cli/client/workato_api/test/test_create_folder_request.py +53 -0
  248. workato_platform_cli/client/workato_api/test/test_custom_connector.py +76 -0
  249. workato_platform_cli/client/workato_api/test/test_custom_connector_code_response.py +54 -0
  250. workato_platform_cli/client/workato_api/test/test_custom_connector_code_response_data.py +52 -0
  251. workato_platform_cli/client/workato_api/test/test_custom_connector_list_response.py +82 -0
  252. workato_platform_cli/client/workato_api/test/test_data_table.py +88 -0
  253. workato_platform_cli/client/workato_api/test/test_data_table_column.py +72 -0
  254. workato_platform_cli/client/workato_api/test/test_data_table_column_request.py +64 -0
  255. workato_platform_cli/client/workato_api/test/test_data_table_create_request.py +82 -0
  256. workato_platform_cli/client/workato_api/test/test_data_table_create_response.py +90 -0
  257. workato_platform_cli/client/workato_api/test/test_data_table_list_response.py +94 -0
  258. workato_platform_cli/client/workato_api/test/test_data_table_relation.py +54 -0
  259. workato_platform_cli/client/workato_api/test/test_data_tables_api.py +45 -0
  260. workato_platform_cli/client/workato_api/test/test_delete_project403_response.py +51 -0
  261. workato_platform_cli/client/workato_api/test/test_error.py +52 -0
  262. workato_platform_cli/client/workato_api/test/test_export_api.py +45 -0
  263. workato_platform_cli/client/workato_api/test/test_export_manifest_request.py +69 -0
  264. workato_platform_cli/client/workato_api/test/test_export_manifest_response.py +68 -0
  265. workato_platform_cli/client/workato_api/test/test_export_manifest_response_result.py +66 -0
  266. workato_platform_cli/client/workato_api/test/test_folder.py +64 -0
  267. workato_platform_cli/client/workato_api/test/test_folder_assets_response.py +80 -0
  268. workato_platform_cli/client/workato_api/test/test_folder_assets_response_result.py +78 -0
  269. workato_platform_cli/client/workato_api/test/test_folder_creation_response.py +64 -0
  270. workato_platform_cli/client/workato_api/test/test_folders_api.py +45 -0
  271. workato_platform_cli/client/workato_api/test/test_import_results.py +58 -0
  272. workato_platform_cli/client/workato_api/test/test_o_auth_url_response.py +54 -0
  273. workato_platform_cli/client/workato_api/test/test_o_auth_url_response_data.py +52 -0
  274. workato_platform_cli/client/workato_api/test/test_open_api_spec.py +54 -0
  275. workato_platform_cli/client/workato_api/test/test_package_details_response.py +64 -0
  276. workato_platform_cli/client/workato_api/test/test_package_details_response_recipe_status_inner.py +52 -0
  277. workato_platform_cli/client/workato_api/test/test_package_response.py +58 -0
  278. workato_platform_cli/client/workato_api/test/test_packages_api.py +59 -0
  279. workato_platform_cli/client/workato_api/test/test_picklist_request.py +53 -0
  280. workato_platform_cli/client/workato_api/test/test_picklist_response.py +52 -0
  281. workato_platform_cli/client/workato_api/test/test_platform_connector.py +94 -0
  282. workato_platform_cli/client/workato_api/test/test_platform_connector_list_response.py +106 -0
  283. workato_platform_cli/client/workato_api/test/test_project.py +57 -0
  284. workato_platform_cli/client/workato_api/test/test_projects_api.py +45 -0
  285. workato_platform_cli/client/workato_api/test/test_properties_api.py +45 -0
  286. workato_platform_cli/client/workato_api/test/test_recipe.py +124 -0
  287. workato_platform_cli/client/workato_api/test/test_recipe_config_inner.py +55 -0
  288. workato_platform_cli/client/workato_api/test/test_recipe_connection_update_request.py +54 -0
  289. workato_platform_cli/client/workato_api/test/test_recipe_list_response.py +134 -0
  290. workato_platform_cli/client/workato_api/test/test_recipe_start_response.py +54 -0
  291. workato_platform_cli/client/workato_api/test/test_recipes_api.py +59 -0
  292. workato_platform_cli/client/workato_api/test/test_runtime_user_connection_create_request.py +59 -0
  293. workato_platform_cli/client/workato_api/test/test_runtime_user_connection_response.py +56 -0
  294. workato_platform_cli/client/workato_api/test/test_runtime_user_connection_response_data.py +54 -0
  295. workato_platform_cli/client/workato_api/test/test_success_response.py +52 -0
  296. workato_platform_cli/client/workato_api/test/test_upsert_project_properties_request.py +52 -0
  297. workato_platform_cli/client/workato_api/test/test_user.py +85 -0
  298. workato_platform_cli/client/workato_api/test/test_users_api.py +38 -0
  299. workato_platform_cli/client/workato_api/test/test_validation_error.py +52 -0
  300. workato_platform_cli/client/workato_api/test/test_validation_error_errors_value.py +50 -0
  301. workato_platform_cli/client/workato_api_README.md +205 -0
  302. workato_platform_cli-1.0.0rc5.dev5.dist-info/METADATA +185 -0
  303. workato_platform_cli-1.0.0rc5.dev5.dist-info/RECORD +306 -0
  304. workato_platform_cli-1.0.0rc5.dev5.dist-info/WHEEL +4 -0
  305. workato_platform_cli-1.0.0rc5.dev5.dist-info/entry_points.txt +2 -0
  306. workato_platform_cli-1.0.0rc5.dev5.dist-info/licenses/LICENSE +7 -0
@@ -0,0 +1,1001 @@
1
+ """Main configuration manager with simplified workspace rules."""
2
+
3
+ import json
4
+ import sys
5
+
6
+ from pathlib import Path
7
+ from typing import Any
8
+ from urllib.parse import urlparse
9
+
10
+ import asyncclick as click
11
+ import certifi
12
+ import inquirer
13
+
14
+ from workato_platform_cli import Workato
15
+ from workato_platform_cli.cli.commands.projects.project_manager import ProjectManager
16
+ from workato_platform_cli.client.workato_api.configuration import Configuration
17
+ from workato_platform_cli.client.workato_api.models.project import Project
18
+
19
+ from .models import AVAILABLE_REGIONS, ConfigData, ProfileData, ProjectInfo, RegionInfo
20
+ from .profiles import ProfileManager, _validate_url_security
21
+ from .workspace import WorkspaceManager
22
+
23
+
24
+ class ConfigManager:
25
+ """Simplified configuration manager with clear workspace rules"""
26
+
27
+ def __init__(self, config_dir: Path | None = None, skip_validation: bool = False):
28
+ start_path = config_dir or Path.cwd()
29
+ self.workspace_manager = WorkspaceManager(start_path)
30
+
31
+ # If explicit config_dir provided, use it directly
32
+ if config_dir:
33
+ self.config_dir = config_dir
34
+ else:
35
+ # Find nearest .workatoenv file and use that directory as config directory
36
+ nearest_config = self.workspace_manager.find_nearest_workatoenv()
37
+ self.config_dir = nearest_config or start_path
38
+
39
+ self.profile_manager = ProfileManager()
40
+
41
+ # Validate credentials unless skipped
42
+ if not skip_validation:
43
+ self._validate_credentials_or_exit()
44
+
45
+ @classmethod
46
+ async def initialize(
47
+ cls,
48
+ config_dir: Path | None = None,
49
+ profile_name: str | None = None,
50
+ region: str | None = None,
51
+ api_token: str | None = None,
52
+ api_url: str | None = None,
53
+ project_name: str | None = None,
54
+ project_id: int | None = None,
55
+ output_mode: str = "table",
56
+ non_interactive: bool = False,
57
+ ) -> "ConfigManager":
58
+ """Initialize workspace with interactive or non-interactive setup"""
59
+ if output_mode == "table":
60
+ if non_interactive:
61
+ click.echo("🚀 Welcome to Workato CLI (Non-interactive mode)")
62
+ else:
63
+ click.echo("🚀 Welcome to Workato CLI")
64
+ click.echo()
65
+
66
+ # Create manager without validation for setup
67
+ manager = cls(config_dir, skip_validation=True)
68
+
69
+ # Validate we're not in a project directory
70
+ manager.workspace_manager.validate_not_in_project()
71
+
72
+ if non_interactive and (profile_name or (region and api_token)):
73
+ # Non-interactive setup
74
+ await manager._setup_non_interactive(
75
+ profile_name=profile_name,
76
+ region=region,
77
+ api_token=api_token,
78
+ api_url=api_url,
79
+ project_name=project_name,
80
+ project_id=project_id,
81
+ )
82
+ else:
83
+ # Run setup flow
84
+ await manager._run_setup_flow()
85
+
86
+ return manager
87
+
88
+ async def _run_setup_flow(self) -> None:
89
+ """Run the complete setup flow"""
90
+ workspace_root = self.workspace_manager.find_workspace_root()
91
+ self.config_dir = workspace_root
92
+
93
+ click.echo(f"📁 Workspace root: {workspace_root}")
94
+ click.echo()
95
+
96
+ # Step 1: Profile setup
97
+ profile_name = await self._setup_profile()
98
+
99
+ # Step 2: Project setup
100
+ await self._setup_project(profile_name, workspace_root)
101
+
102
+ # Step 3: Create workspace files
103
+ self._create_workspace_files(workspace_root)
104
+
105
+ click.echo("🎉 Configuration complete!")
106
+
107
+ async def _setup_non_interactive(
108
+ self,
109
+ profile_name: str | None = None,
110
+ region: str | None = None,
111
+ api_token: str | None = None,
112
+ api_url: str | None = None,
113
+ project_name: str | None = None,
114
+ project_id: int | None = None,
115
+ ) -> None:
116
+ """Perform all setup actions non-interactively"""
117
+
118
+ workspace_root = self.workspace_manager.find_workspace_root()
119
+ self.config_dir = workspace_root
120
+
121
+ current_profile_name = self.profile_manager.get_current_profile_name(
122
+ profile_name
123
+ )
124
+ if not current_profile_name:
125
+ raise click.ClickException("Profile name is required")
126
+
127
+ profile = self.profile_manager.get_profile(current_profile_name)
128
+ if profile and profile.region:
129
+ region = profile.region
130
+ api_token, api_url = self.profile_manager.resolve_environment_variables(
131
+ project_profile_override=current_profile_name
132
+ )
133
+
134
+ # Map region to URL
135
+ if region == "custom":
136
+ if not api_url:
137
+ raise click.ClickException("--api-url is required when region=custom")
138
+ region_info = RegionInfo(region="custom", name="Custom URL", url=api_url)
139
+ else:
140
+ if region not in AVAILABLE_REGIONS:
141
+ raise click.ClickException(f"Invalid region: {region}")
142
+ region_info = AVAILABLE_REGIONS[region]
143
+
144
+ # Test authentication and get workspace info
145
+ api_config = Configuration(
146
+ access_token=api_token, host=region_info.url, ssl_ca_cert=certifi.where()
147
+ )
148
+
149
+ async with Workato(configuration=api_config) as workato_api_client:
150
+ user_info = await workato_api_client.users_api.get_workspace_details()
151
+
152
+ # Create and save profile
153
+ if not region_info.url:
154
+ raise click.ClickException("Region URL is required")
155
+ profile_data = ProfileData(
156
+ region=region_info.region,
157
+ region_url=region_info.url,
158
+ workspace_id=user_info.id,
159
+ )
160
+
161
+ self.profile_manager.set_profile(current_profile_name, profile_data, api_token)
162
+ self.profile_manager.set_current_profile(current_profile_name)
163
+
164
+ # Get API client for project operations
165
+ api_config = Configuration(
166
+ access_token=api_token, host=region_info.url, ssl_ca_cert=certifi.where()
167
+ )
168
+
169
+ async with Workato(configuration=api_config) as workato_api_client:
170
+ project_manager = ProjectManager(workato_api_client=workato_api_client)
171
+
172
+ selected_project = None
173
+
174
+ if project_name:
175
+ selected_project = await project_manager.create_project(project_name)
176
+ elif project_id:
177
+ # Use existing project
178
+ projects = await project_manager.get_all_projects()
179
+ selected_project = next(
180
+ (p for p in projects if p.id == project_id),
181
+ None,
182
+ )
183
+ if not selected_project:
184
+ raise click.ClickException(
185
+ f"Project with ID {project_id} not found"
186
+ )
187
+
188
+ if not selected_project:
189
+ raise click.ClickException("No project selected")
190
+
191
+ # Check if this specific project already exists locally in the workspace
192
+ local_projects = self._find_all_projects(workspace_root)
193
+ existing_local_path = None
194
+
195
+ for project_path_candidate, _ in local_projects:
196
+ try:
197
+ project_config_manager = ConfigManager(
198
+ project_path_candidate, skip_validation=True
199
+ )
200
+ config_data = project_config_manager.load_config()
201
+ if config_data.project_id == selected_project.id:
202
+ existing_local_path = project_path_candidate
203
+ break
204
+ except (json.JSONDecodeError, OSError):
205
+ continue
206
+
207
+ # In non-interactive mode, fail if project already exists
208
+ if existing_local_path:
209
+ relative_path = existing_local_path.relative_to(workspace_root)
210
+ raise click.ClickException(
211
+ f"Project '{selected_project.name}' (ID: {selected_project.id}) "
212
+ f"already exists locally at: {relative_path}. "
213
+ f"Cannot reinitialize in non-interactive mode."
214
+ )
215
+
216
+ # Project doesn't exist locally - create new directory
217
+ current_dir = Path.cwd().resolve()
218
+ project_path = current_dir / selected_project.name
219
+
220
+ # Create project directory
221
+ project_path.mkdir(parents=True, exist_ok=True)
222
+
223
+ # Save workspace config (with project_path)
224
+ relative_project_path = str(project_path.relative_to(workspace_root))
225
+ workspace_config = ConfigData(
226
+ project_id=selected_project.id,
227
+ project_name=selected_project.name,
228
+ project_path=relative_project_path,
229
+ folder_id=selected_project.folder_id,
230
+ profile=profile_name,
231
+ )
232
+
233
+ self.save_config(workspace_config)
234
+
235
+ # Save project config (without project_path)
236
+ project_config_manager = ConfigManager(project_path, skip_validation=True)
237
+ project_config = ConfigData(
238
+ project_id=selected_project.id,
239
+ project_name=selected_project.name,
240
+ project_path=None, # No project_path in project directory
241
+ folder_id=selected_project.folder_id,
242
+ profile=profile_name,
243
+ )
244
+
245
+ project_config_manager.save_config(project_config)
246
+
247
+ # Step 3: Create workspace files
248
+ self._create_workspace_files(workspace_root)
249
+
250
+ async def _setup_profile(self) -> str:
251
+ """Setup or select profile"""
252
+ click.echo("📋 Step 1: Configure profile")
253
+
254
+ existing_profiles = self.profile_manager.list_profiles()
255
+ profile_name: str | None = None
256
+
257
+ if existing_profiles:
258
+ choices = list(existing_profiles.keys()) + ["Create new profile"]
259
+ questions = [
260
+ inquirer.List(
261
+ "profile_choice",
262
+ message="Select a profile",
263
+ choices=choices,
264
+ )
265
+ ]
266
+
267
+ answers: dict[str, str] = inquirer.prompt(questions)
268
+ if not answers:
269
+ click.echo("❌ No profile selected")
270
+ sys.exit(1)
271
+
272
+ if answers["profile_choice"] == "Create new profile":
273
+ profile_name = (
274
+ await click.prompt("Enter new profile name", type=str)
275
+ ).strip()
276
+ if not profile_name:
277
+ click.echo("❌ Profile name cannot be empty")
278
+ sys.exit(1)
279
+ await self._create_new_profile(profile_name)
280
+ else:
281
+ profile_name = answers["profile_choice"]
282
+ else:
283
+ profile_name = (
284
+ await click.prompt("Enter profile name", default="default", type=str)
285
+ ).strip()
286
+ if not profile_name:
287
+ click.echo("❌ Profile name cannot be empty")
288
+ sys.exit(1)
289
+ await self._create_new_profile(profile_name)
290
+
291
+ # Set as current profile
292
+ self.profile_manager.set_current_profile(profile_name)
293
+ click.echo(f"✅ Profile: {profile_name}")
294
+
295
+ return profile_name
296
+
297
+ async def _create_new_profile(self, profile_name: str) -> None:
298
+ """Create a new profile interactively"""
299
+ # AVAILABLE_REGIONS and RegionInfo already imported at top
300
+
301
+ # Region selection
302
+ click.echo("📍 Select your Workato region")
303
+ regions = list(AVAILABLE_REGIONS.values())
304
+ choices = []
305
+
306
+ for region in regions:
307
+ if region.region == "custom":
308
+ choice_text = "Custom URL"
309
+ else:
310
+ choice_text = f"{region.name} ({region.url})"
311
+ choices.append(choice_text)
312
+
313
+ questions = [
314
+ inquirer.List(
315
+ "region",
316
+ message="Select your Workato region",
317
+ choices=choices,
318
+ ),
319
+ ]
320
+
321
+ answers = inquirer.prompt(questions)
322
+ if not answers:
323
+ click.echo("❌ Setup cancelled")
324
+ sys.exit(1)
325
+
326
+ selected_index = choices.index(answers["region"])
327
+ selected_region = regions[selected_index]
328
+
329
+ # Handle custom URL
330
+ if selected_region.region == "custom":
331
+ custom_url = await click.prompt(
332
+ "Enter your custom Workato base URL",
333
+ type=str,
334
+ default="https://www.workato.com",
335
+ )
336
+ selected_region = RegionInfo(
337
+ region="custom", name="Custom URL", url=custom_url
338
+ )
339
+
340
+ # Get API token
341
+ click.echo("🔐 Enter your API token")
342
+ token = await click.prompt("Enter your Workato API token", hide_input=True)
343
+ if not token.strip():
344
+ click.echo("❌ No token provided")
345
+ sys.exit(1)
346
+
347
+ # Test authentication and get workspace info
348
+ api_config = Configuration(
349
+ access_token=token, host=selected_region.url, ssl_ca_cert=certifi.where()
350
+ )
351
+
352
+ async with Workato(configuration=api_config) as workato_api_client:
353
+ user_info = await workato_api_client.users_api.get_workspace_details()
354
+
355
+ # Create and save profile
356
+ if not selected_region.url:
357
+ raise click.ClickException("Region URL is required")
358
+ profile_data = ProfileData(
359
+ region=selected_region.region,
360
+ region_url=selected_region.url,
361
+ workspace_id=user_info.id,
362
+ )
363
+
364
+ self.profile_manager.set_profile(profile_name, profile_data, token)
365
+ click.echo(f"✅ Authenticated as: {user_info.name}")
366
+
367
+ async def _setup_project(self, profile_name: str, workspace_root: Path) -> None:
368
+ """Setup project interactively"""
369
+ click.echo("📁 Step 2: Setup project")
370
+
371
+ # Get API client for project operations
372
+ api_token, api_host = self.profile_manager.resolve_environment_variables(
373
+ profile_name
374
+ )
375
+ api_config = Configuration(
376
+ access_token=api_token, host=api_host, ssl_ca_cert=certifi.where()
377
+ )
378
+
379
+ async with Workato(configuration=api_config) as workato_api_client:
380
+ project_manager = ProjectManager(workato_api_client=workato_api_client)
381
+
382
+ # Get available projects
383
+ projects = await project_manager.get_all_projects()
384
+ choices = ["Create new project"]
385
+
386
+ if projects:
387
+ project_choices = [(f"{p.name} (ID: {p.id})", p) for p in projects]
388
+ choices.extend([choice[0] for choice in project_choices])
389
+
390
+ questions = [
391
+ inquirer.List(
392
+ "project",
393
+ message="Select a project",
394
+ choices=choices,
395
+ )
396
+ ]
397
+
398
+ answers = inquirer.prompt(questions)
399
+ if not answers:
400
+ click.echo("❌ No project selected")
401
+ sys.exit(1)
402
+
403
+ selected_project = None
404
+
405
+ if answers["project"] == "Create new project":
406
+ project_name = await click.prompt("Enter project name", type=str)
407
+ if not project_name or not project_name.strip():
408
+ click.echo("❌ Project name cannot be empty")
409
+ sys.exit(1)
410
+
411
+ click.echo(f"🔨 Creating project: {project_name}")
412
+ selected_project = await project_manager.create_project(project_name)
413
+ click.echo(f"✅ Created project: {selected_project.name}")
414
+ else:
415
+ # Find selected existing project
416
+ for choice_text, project in [(choices[0], None)] + project_choices:
417
+ if choice_text == answers["project"] and project:
418
+ selected_project = project
419
+ break
420
+
421
+ if not selected_project:
422
+ click.echo("❌ No project selected")
423
+ sys.exit(1)
424
+
425
+ # Check if this specific project already exists locally in the workspace
426
+ local_projects = self._find_all_projects(workspace_root)
427
+ existing_local_path = None
428
+
429
+ for project_path_candidate, _ in local_projects:
430
+ try:
431
+ project_config_manager = ConfigManager(
432
+ project_path_candidate, skip_validation=True
433
+ )
434
+ config_data = project_config_manager.load_config()
435
+ if config_data.project_id == selected_project.id:
436
+ existing_local_path = project_path_candidate
437
+ break
438
+ except (json.JSONDecodeError, OSError):
439
+ continue
440
+
441
+ # If project exists locally, prompt for reinitialization
442
+ if existing_local_path:
443
+ relative_path = existing_local_path.relative_to(workspace_root)
444
+ click.echo(
445
+ f"Project '{selected_project.name}' (ID: {selected_project.id}) "
446
+ f"already exists locally at: {relative_path}"
447
+ )
448
+ if not click.confirm(
449
+ "Reinitialize this project? "
450
+ "This may overwrite or delete local files.",
451
+ default=False,
452
+ ):
453
+ click.echo("❌ Initialization cancelled")
454
+ sys.exit(1)
455
+ # Use existing path instead of creating new one
456
+ project_path = existing_local_path
457
+ else:
458
+ # Project doesn't exist locally - create new directory
459
+ current_dir = Path.cwd().resolve()
460
+ project_path = current_dir / selected_project.name
461
+
462
+ # Validate project path
463
+ try:
464
+ self.workspace_manager.validate_project_path(
465
+ project_path, workspace_root
466
+ )
467
+ except ValueError as e:
468
+ click.echo(f"❌ {e}")
469
+ sys.exit(1)
470
+
471
+ # Check if project directory already exists and is non-empty
472
+ if not project_path.exists():
473
+ pass # Directory doesn't exist, we can proceed
474
+ else:
475
+ try:
476
+ existing_files = list(project_path.iterdir())
477
+ if not existing_files:
478
+ pass # Directory is empty, we can proceed
479
+ else:
480
+ # Directory has files - check if it's the same Workato project
481
+ existing_project_id = self._get_existing_project_id(
482
+ project_path
483
+ )
484
+
485
+ if existing_project_id == selected_project.id:
486
+ # Same project ID - allow reconfiguration
487
+ click.echo(
488
+ f"🔄 Reconfiguring existing project: "
489
+ f"{selected_project.name}"
490
+ )
491
+ elif existing_project_id:
492
+ # Different project ID - block it
493
+ self._handle_different_project_error(
494
+ project_path, existing_project_id, selected_project
495
+ )
496
+ else:
497
+ # Not a Workato project - block it
498
+ self._handle_non_empty_directory_error(
499
+ project_path, workspace_root, existing_files
500
+ )
501
+ except OSError:
502
+ pass # If we can't read the directory, let mkdir handle it
503
+
504
+ # Create project directory
505
+ project_path.mkdir(parents=True, exist_ok=True)
506
+ click.echo(
507
+ f"✅ Project directory: {project_path.relative_to(workspace_root)}"
508
+ )
509
+
510
+ # Save workspace config (with project_path)
511
+ relative_project_path = str(project_path.relative_to(workspace_root))
512
+ workspace_config = ConfigData(
513
+ project_id=selected_project.id,
514
+ project_name=selected_project.name,
515
+ project_path=relative_project_path,
516
+ folder_id=selected_project.folder_id,
517
+ profile=profile_name,
518
+ )
519
+
520
+ self.save_config(workspace_config)
521
+
522
+ # Save project config (without project_path)
523
+ project_config_manager = ConfigManager(project_path, skip_validation=True)
524
+ project_config = ConfigData(
525
+ project_id=selected_project.id,
526
+ project_name=selected_project.name,
527
+ project_path=None, # No project_path in project directory
528
+ folder_id=selected_project.folder_id,
529
+ profile=profile_name,
530
+ )
531
+
532
+ project_config_manager.save_config(project_config)
533
+
534
+ click.echo(f"✅ Project: {selected_project.name}")
535
+
536
+ def _create_workspace_files(self, workspace_root: Path) -> None:
537
+ """Create workspace .gitignore and .workato-ignore files"""
538
+ # Create .gitignore entry for .workatoenv
539
+ gitignore_file = workspace_root / ".gitignore"
540
+ workatoenv_entry = ".workatoenv"
541
+
542
+ existing_lines = []
543
+ if gitignore_file.exists():
544
+ with open(gitignore_file) as f:
545
+ existing_lines = [line.rstrip("\n") for line in f.readlines()]
546
+
547
+ if workatoenv_entry not in existing_lines:
548
+ with open(gitignore_file, "a") as f:
549
+ if existing_lines and existing_lines[-1] != "":
550
+ f.write("\n")
551
+ f.write(f"{workatoenv_entry}\n")
552
+
553
+ # Create .workato-ignore file
554
+ workato_ignore_file = workspace_root / ".workato-ignore"
555
+ if not workato_ignore_file.exists():
556
+ workato_ignore_content = """# Workato CLI ignore patterns
557
+ # Files matching these patterns will be preserved during 'workato pull'
558
+ # and excluded from 'workato push' operations
559
+
560
+ # Configuration files
561
+ .workatoenv
562
+
563
+ # Git files
564
+ .git
565
+ .gitignore
566
+ .gitattributes
567
+ .gitmodules
568
+
569
+ # Python files and virtual environments
570
+ *.py
571
+ *.pyc
572
+ *.pyo
573
+ *.pyd
574
+ __pycache__/
575
+ *.egg-info/
576
+ .venv/
577
+ venv/
578
+ .env
579
+
580
+ # Node.js files
581
+ node_modules/
582
+ package.json
583
+ package-lock.json
584
+ yarn.lock
585
+ .npmrc
586
+
587
+ # Development tools
588
+ .pytest_cache/
589
+ .mypy_cache/
590
+ .ruff_cache/
591
+ htmlcov/
592
+ .coverage
593
+ .tox/
594
+
595
+ # IDE and editor files
596
+ .vscode/
597
+ .idea/
598
+ *.swp
599
+ *.swo
600
+ *~
601
+
602
+ # Build artifacts
603
+ dist/
604
+ build/
605
+ *.egg
606
+
607
+ # Documentation and project files
608
+ *.md
609
+ LICENSE
610
+ .editorconfig
611
+ pyproject.toml
612
+ setup.py
613
+ setup.cfg
614
+ Makefile
615
+ Dockerfile
616
+ docker-compose.yml
617
+
618
+ # OS files
619
+ .DS_Store
620
+ Thumbs.db
621
+
622
+ # Add your own patterns below
623
+ """
624
+ with open(workato_ignore_file, "w") as f:
625
+ f.write(workato_ignore_content)
626
+
627
+ # Configuration file management
628
+
629
+ def load_config(self) -> ConfigData:
630
+ """Load configuration from .workatoenv file"""
631
+ config_file = self.config_dir / ".workatoenv"
632
+
633
+ if not config_file.exists():
634
+ return ConfigData.model_construct()
635
+
636
+ try:
637
+ with open(config_file) as f:
638
+ data = json.load(f)
639
+ return ConfigData.model_validate(data)
640
+ except (json.JSONDecodeError, ValueError):
641
+ return ConfigData.model_construct()
642
+
643
+ def save_config(self, config_data: ConfigData) -> None:
644
+ """Save configuration to .workatoenv file"""
645
+ config_file = self.config_dir / ".workatoenv"
646
+
647
+ with open(config_file, "w") as f:
648
+ json.dump(config_data.model_dump(exclude_none=True), f, indent=2)
649
+
650
+ def save_project_info(self, project_info: ProjectInfo) -> None:
651
+ """Save project information to configuration"""
652
+ config_data = self.load_config()
653
+ config_data.project_id = project_info.id
654
+ config_data.project_name = project_info.name
655
+ config_data.folder_id = project_info.folder_id
656
+ self.save_config(config_data)
657
+
658
+ # Project and workspace detection methods
659
+
660
+ def get_workspace_root(self) -> Path:
661
+ """Get workspace root directory"""
662
+ return self.workspace_manager.find_workspace_root()
663
+
664
+ def get_project_directory(self) -> Path | None:
665
+ """Get project directory from closest .workatoenv config"""
666
+ # Load config from closest .workatoenv file (already found in __init__)
667
+ config_data = self.load_config()
668
+
669
+ if not config_data.project_id:
670
+ return None
671
+
672
+ if not config_data.project_path:
673
+ # No project_path means this .workatoenv IS in the project directory
674
+ # Update workspace root to select this project as current
675
+ self._update_workspace_selection()
676
+ return self.config_dir
677
+
678
+ # Has project_path, so this is a workspace config - resolve relative path
679
+ workspace_root = self.config_dir
680
+ project_dir = workspace_root / config_data.project_path
681
+
682
+ if project_dir.exists():
683
+ return project_dir.resolve()
684
+ else:
685
+ # Current selection is invalid - let user choose from available projects
686
+ return self._handle_invalid_project_selection(workspace_root, config_data)
687
+
688
+ def _update_workspace_selection(self) -> None:
689
+ """Update workspace root config to select current project"""
690
+ workspace_root = self.workspace_manager.find_workspace_root()
691
+ if not workspace_root or workspace_root == self.config_dir:
692
+ return # Already at workspace root or no workspace found
693
+
694
+ # Load current project config
695
+ project_config = self.load_config()
696
+ if not project_config.project_id:
697
+ return
698
+
699
+ # Calculate relative path from workspace root to current project
700
+ try:
701
+ relative_path = self.config_dir.relative_to(workspace_root)
702
+ except ValueError:
703
+ return # Current dir not within workspace
704
+
705
+ # Update workspace config
706
+ workspace_manager = ConfigManager(workspace_root, skip_validation=True)
707
+ workspace_config = workspace_manager.load_config()
708
+ workspace_config.project_id = project_config.project_id
709
+ workspace_config.project_name = project_config.project_name
710
+ workspace_config.project_path = str(relative_path)
711
+ workspace_config.folder_id = project_config.folder_id
712
+ workspace_config.profile = project_config.profile
713
+ workspace_manager.save_config(workspace_config)
714
+
715
+ click.echo(f"✅ Selected '{project_config.project_name}' as current project")
716
+
717
+ def _handle_invalid_project_selection(
718
+ self, workspace_root: Path, current_config: ConfigData
719
+ ) -> Path | None:
720
+ """Handle case where current project selection is invalid"""
721
+ click.echo(
722
+ f"⚠️ Configured project directory does not exist: "
723
+ f"{current_config.project_path}"
724
+ )
725
+ click.echo(f" Project: {current_config.project_name}")
726
+ click.echo()
727
+
728
+ # Find all available projects in workspace hierarchy
729
+ available_projects = self._find_all_projects(workspace_root)
730
+
731
+ if not available_projects:
732
+ click.echo("❌ No projects found in workspace")
733
+ click.echo("💡 Run 'workato init' to create a new project")
734
+ return None
735
+
736
+ # Prepare choices for inquirer
737
+ choices = []
738
+ for project_path, project_name in available_projects:
739
+ rel_path = project_path.relative_to(workspace_root)
740
+ choice_text = f"{project_name} ({rel_path})"
741
+ choices.append(choice_text)
742
+
743
+ # Let user select
744
+ try:
745
+ questions = [
746
+ inquirer.List(
747
+ "project",
748
+ message="Select a project to use",
749
+ choices=choices,
750
+ )
751
+ ]
752
+
753
+ answers = inquirer.prompt(questions)
754
+ if not answers:
755
+ return None
756
+
757
+ # Find selected project
758
+ selected_index = choices.index(answers["project"])
759
+ selected_path, selected_name = available_projects[selected_index]
760
+
761
+ # Load selected project config to get full details
762
+ selected_manager = ConfigManager(selected_path, skip_validation=True)
763
+ selected_config = selected_manager.load_config()
764
+
765
+ # Update workspace config
766
+ workspace_manager = ConfigManager(workspace_root, skip_validation=True)
767
+ workspace_config = workspace_manager.load_config()
768
+ workspace_config.project_id = selected_config.project_id
769
+ workspace_config.project_name = selected_config.project_name
770
+ workspace_config.project_path = str(
771
+ selected_path.relative_to(workspace_root)
772
+ )
773
+ workspace_config.folder_id = selected_config.folder_id
774
+ workspace_config.profile = selected_config.profile
775
+ workspace_manager.save_config(workspace_config)
776
+
777
+ click.echo(f"✅ Selected '{selected_name}' as current project")
778
+ return selected_path
779
+
780
+ except (ImportError, KeyboardInterrupt):
781
+ click.echo("❌ Project selection cancelled")
782
+ return None
783
+
784
+ def _find_all_projects(self, workspace_root: Path) -> list[tuple[Path, str]]:
785
+ """Find all project directories in workspace hierarchy"""
786
+ projects = []
787
+
788
+ # Recursively search for .workatoenv files without project_path
789
+ for workatoenv_file in workspace_root.rglob(".workatoenv"):
790
+ try:
791
+ with open(workatoenv_file) as f:
792
+ data = json.load(f)
793
+ # Project config has project_id but no project_path
794
+ if (
795
+ "project_id" in data
796
+ and data.get("project_id")
797
+ and not data.get("project_path")
798
+ ):
799
+ project_dir = workatoenv_file.parent
800
+ project_name = data.get("project_name", project_dir.name)
801
+ projects.append((project_dir, project_name))
802
+ except (json.JSONDecodeError, OSError):
803
+ continue
804
+
805
+ return sorted(projects, key=lambda x: x[1]) # Sort by project name
806
+
807
+ def get_current_project_name(self) -> str | None:
808
+ """Get current project name"""
809
+ config_data = self.load_config()
810
+ return config_data.project_name
811
+
812
+ def get_project_root(self) -> Path | None:
813
+ """Get project root (directory containing .workatoenv)"""
814
+ # For compatibility - this is the same as config_dir when in project
815
+ if self.workspace_manager.is_in_project_directory():
816
+ return self.config_dir
817
+
818
+ # If in workspace, return project directory
819
+ return self.get_project_directory()
820
+
821
+ def is_in_project_workspace(self) -> bool:
822
+ """Check if in a project workspace"""
823
+ return self.get_workspace_root() is not None
824
+
825
+ def _get_existing_project_id(self, project_path: Path) -> int | None:
826
+ """Get project ID from existing .workatoenv file, if valid."""
827
+ workatoenv_path = project_path / ".workatoenv"
828
+ if not workatoenv_path.exists():
829
+ return None
830
+
831
+ try:
832
+ with open(workatoenv_path) as f:
833
+ data: dict[str, Any] = json.load(f)
834
+ return data.get("project_id")
835
+ except (json.JSONDecodeError, OSError):
836
+ return None
837
+
838
+ def _handle_different_project_error(
839
+ self,
840
+ project_path: Path,
841
+ existing_project_id: int,
842
+ selected_project: Project,
843
+ ) -> None:
844
+ """Handle error when directory contains different Workato project."""
845
+ workatoenv_path = project_path / ".workatoenv"
846
+ try:
847
+ with open(workatoenv_path) as f:
848
+ existing_data = json.load(f)
849
+ existing_name = existing_data.get("project_name", "Unknown")
850
+ except (json.JSONDecodeError, OSError):
851
+ existing_name = "Unknown"
852
+
853
+ click.echo(
854
+ f"❌ Directory contains different Workato project: "
855
+ f"{existing_name} (ID: {existing_project_id})"
856
+ )
857
+ click.echo(
858
+ f" Cannot initialize {selected_project.name} "
859
+ f"(ID: {selected_project.id}) here"
860
+ )
861
+ click.echo("💡 Choose a different directory or project name")
862
+ sys.exit(1)
863
+
864
+ def _handle_non_empty_directory_error(
865
+ self, project_path: Path, workspace_root: Path, existing_files: list
866
+ ) -> None:
867
+ """Handle error when directory is non-empty but not a Workato project."""
868
+ click.echo(
869
+ f"❌ Project directory is not empty: "
870
+ f"{project_path.relative_to(workspace_root)}"
871
+ )
872
+ click.echo(f" Found {len(existing_files)} existing files")
873
+ click.echo("💡 Choose a different project name or clean the directory first")
874
+ sys.exit(1)
875
+
876
+ # Credential management
877
+
878
+ def _validate_credentials_or_exit(self) -> None:
879
+ """Validate credentials and exit if missing"""
880
+ is_valid, missing_items = self.validate_environment_config()
881
+ if not is_valid:
882
+ click.echo("❌ Missing required credentials:")
883
+ for item in missing_items:
884
+ click.echo(f" • {item}")
885
+ click.echo()
886
+ click.echo("💡 Run 'workato init' to set up authentication")
887
+ sys.exit(1)
888
+
889
+ def validate_environment_config(self) -> tuple[bool, list[str]]:
890
+ """Validate environment configuration"""
891
+ config_data = self.load_config()
892
+ return self.profile_manager.validate_credentials(config_data.profile)
893
+
894
+ @property
895
+ def api_token(self) -> str | None:
896
+ """Get API token"""
897
+ config_data = self.load_config()
898
+ api_token, _ = self.profile_manager.resolve_environment_variables(
899
+ config_data.profile
900
+ )
901
+ return api_token
902
+
903
+ @api_token.setter
904
+ def api_token(self, value: str) -> None:
905
+ """Save API token to current profile"""
906
+ config_data = self.load_config()
907
+ current_profile_name = self.profile_manager.get_current_profile_name(
908
+ config_data.profile
909
+ )
910
+
911
+ if not current_profile_name:
912
+ current_profile_name = "default"
913
+
914
+ # Check if profile exists
915
+ profiles = self.profile_manager.load_profiles()
916
+ if current_profile_name not in profiles.profiles:
917
+ raise ValueError(
918
+ f"Profile '{current_profile_name}' does not exist. "
919
+ "Please run 'workato init' to create a profile first."
920
+ )
921
+
922
+ # Store token in keyring
923
+ success = self.profile_manager._store_token_in_keyring(
924
+ current_profile_name, value
925
+ )
926
+ if not success:
927
+ if self.profile_manager._is_keyring_enabled():
928
+ raise ValueError(
929
+ "Failed to store token in keyring. "
930
+ "Please check your system keyring setup."
931
+ )
932
+ else:
933
+ raise ValueError(
934
+ "Keyring is disabled. "
935
+ "Please set WORKATO_API_TOKEN environment variable instead."
936
+ )
937
+
938
+ click.echo(f"✅ API token saved to profile '{current_profile_name}'")
939
+
940
+ @property
941
+ def api_host(self) -> str | None:
942
+ """Get API host"""
943
+ config_data = self.load_config()
944
+ _, api_host = self.profile_manager.resolve_environment_variables(
945
+ config_data.profile
946
+ )
947
+ return api_host
948
+
949
+ # Region management methods
950
+
951
+ def validate_region(self, region_code: str) -> bool:
952
+ """Validate if region code is valid"""
953
+ return region_code.lower() in AVAILABLE_REGIONS
954
+
955
+ def set_region(
956
+ self, region_code: str, custom_url: str | None = None
957
+ ) -> tuple[bool, str]:
958
+ """Set region by updating the current profile"""
959
+
960
+ if region_code.lower() not in AVAILABLE_REGIONS:
961
+ return False, f"Invalid region: {region_code}"
962
+
963
+ region_info = AVAILABLE_REGIONS[region_code.lower()]
964
+
965
+ # Get current profile
966
+ config_data = self.load_config()
967
+ current_profile_name = self.profile_manager.get_current_profile_name(
968
+ config_data.profile
969
+ )
970
+ if not current_profile_name:
971
+ current_profile_name = "default"
972
+
973
+ # Load profiles
974
+ profiles = self.profile_manager.load_profiles()
975
+ if current_profile_name not in profiles.profiles:
976
+ return False, f"Profile '{current_profile_name}' does not exist"
977
+
978
+ # Handle custom region
979
+ if region_code.lower() == "custom":
980
+ if not custom_url:
981
+ return False, "Custom region requires a URL to be provided"
982
+
983
+ # Validate URL security
984
+ is_valid, error_msg = _validate_url_security(custom_url)
985
+ if not is_valid:
986
+ return False, error_msg
987
+
988
+ # Parse URL and keep only scheme + netloc
989
+ parsed = urlparse(custom_url)
990
+ region_url = f"{parsed.scheme}://{parsed.netloc}"
991
+ else:
992
+ region_url = region_info.url or ""
993
+
994
+ # Update profile
995
+ profiles.profiles[current_profile_name].region = region_code.lower()
996
+ profiles.profiles[current_profile_name].region_url = region_url
997
+
998
+ # Save updated profiles
999
+ self.profile_manager.save_profiles(profiles)
1000
+
1001
+ return True, f"{region_info.name} ({region_url})"