fal 1.36.1__tar.gz → 1.38.0__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.

Potentially problematic release.


This version of fal might be problematic. Click here for more details.

Files changed (187) hide show
  1. {fal-1.36.1/fal.egg-info → fal-1.38.0}/PKG-INFO +4 -2
  2. {fal-1.36.1 → fal-1.38.0/fal.egg-info}/PKG-INFO +4 -2
  3. {fal-1.36.1 → fal-1.38.0}/fal.egg-info/requires.txt +3 -1
  4. {fal-1.36.1 → fal-1.38.0}/pyproject.toml +3 -1
  5. {fal-1.36.1 → fal-1.38.0}/src/fal/_fal_version.py +2 -2
  6. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/apps.py +51 -22
  7. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/parser.py +57 -0
  8. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/runners.py +52 -9
  9. {fal-1.36.1 → fal-1.38.0}/src/fal/sdk.py +23 -4
  10. {fal-1.36.1 → fal-1.38.0}/.gitignore +0 -0
  11. {fal-1.36.1 → fal-1.38.0}/Makefile +0 -0
  12. {fal-1.36.1 → fal-1.38.0}/README.md +0 -0
  13. {fal-1.36.1 → fal-1.38.0}/docs/conf.py +0 -0
  14. {fal-1.36.1 → fal-1.38.0}/docs/index.rst +0 -0
  15. {fal-1.36.1 → fal-1.38.0}/fal.egg-info/SOURCES.txt +0 -0
  16. {fal-1.36.1 → fal-1.38.0}/fal.egg-info/dependency_links.txt +0 -0
  17. {fal-1.36.1 → fal-1.38.0}/fal.egg-info/entry_points.txt +0 -0
  18. {fal-1.36.1 → fal-1.38.0}/fal.egg-info/top_level.txt +0 -0
  19. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/README.md +0 -0
  20. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  21. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  22. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  23. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  24. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  25. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  26. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
  27. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
  28. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
  29. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
  30. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
  31. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
  32. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  33. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  34. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  35. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
  36. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
  37. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  38. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
  39. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
  40. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
  41. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
  42. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
  43. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  44. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  45. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  46. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  47. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  48. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
  49. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
  50. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
  51. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
  52. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
  53. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
  54. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
  55. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
  56. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  57. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  58. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  59. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  60. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
  61. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  62. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
  63. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
  64. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
  65. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  66. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
  67. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
  68. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  69. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  70. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
  71. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  72. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  73. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  74. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
  75. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  76. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  77. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  78. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  79. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  80. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  81. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  82. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  83. {fal-1.36.1 → fal-1.38.0}/openapi-fal-rest/pyproject.toml +0 -0
  84. {fal-1.36.1 → fal-1.38.0}/openapi_rest.config.yaml +0 -0
  85. {fal-1.36.1 → fal-1.38.0}/setup.cfg +0 -0
  86. {fal-1.36.1 → fal-1.38.0}/src/fal/__init__.py +0 -0
  87. {fal-1.36.1 → fal-1.38.0}/src/fal/__main__.py +0 -0
  88. {fal-1.36.1 → fal-1.38.0}/src/fal/_serialization.py +0 -0
  89. {fal-1.36.1 → fal-1.38.0}/src/fal/_version.py +0 -0
  90. {fal-1.36.1 → fal-1.38.0}/src/fal/api.py +0 -0
  91. {fal-1.36.1 → fal-1.38.0}/src/fal/app.py +0 -0
  92. {fal-1.36.1 → fal-1.38.0}/src/fal/apps.py +0 -0
  93. {fal-1.36.1 → fal-1.38.0}/src/fal/auth/__init__.py +0 -0
  94. {fal-1.36.1 → fal-1.38.0}/src/fal/auth/auth0.py +0 -0
  95. {fal-1.36.1 → fal-1.38.0}/src/fal/auth/local.py +0 -0
  96. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/__init__.py +0 -0
  97. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/_utils.py +0 -0
  98. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/api.py +0 -0
  99. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/auth.py +0 -0
  100. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/cli_nested_json.py +0 -0
  101. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/create.py +0 -0
  102. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/debug.py +0 -0
  103. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/deploy.py +0 -0
  104. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/doctor.py +0 -0
  105. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/files.py +0 -0
  106. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/keys.py +0 -0
  107. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/main.py +0 -0
  108. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/profile.py +0 -0
  109. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/run.py +0 -0
  110. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/secrets.py +0 -0
  111. {fal-1.36.1 → fal-1.38.0}/src/fal/cli/teams.py +0 -0
  112. {fal-1.36.1 → fal-1.38.0}/src/fal/config.py +0 -0
  113. {fal-1.36.1 → fal-1.38.0}/src/fal/console/__init__.py +0 -0
  114. {fal-1.36.1 → fal-1.38.0}/src/fal/console/icons.py +0 -0
  115. {fal-1.36.1 → fal-1.38.0}/src/fal/console/ux.py +0 -0
  116. {fal-1.36.1 → fal-1.38.0}/src/fal/container.py +0 -0
  117. {fal-1.36.1 → fal-1.38.0}/src/fal/exceptions/__init__.py +0 -0
  118. {fal-1.36.1 → fal-1.38.0}/src/fal/exceptions/_base.py +0 -0
  119. {fal-1.36.1 → fal-1.38.0}/src/fal/exceptions/_cuda.py +0 -0
  120. {fal-1.36.1 → fal-1.38.0}/src/fal/exceptions/auth.py +0 -0
  121. {fal-1.36.1 → fal-1.38.0}/src/fal/files.py +0 -0
  122. {fal-1.36.1 → fal-1.38.0}/src/fal/flags.py +0 -0
  123. {fal-1.36.1 → fal-1.38.0}/src/fal/logging/__init__.py +0 -0
  124. {fal-1.36.1 → fal-1.38.0}/src/fal/logging/isolate.py +0 -0
  125. {fal-1.36.1 → fal-1.38.0}/src/fal/logging/style.py +0 -0
  126. {fal-1.36.1 → fal-1.38.0}/src/fal/logging/trace.py +0 -0
  127. {fal-1.36.1 → fal-1.38.0}/src/fal/logging/user.py +0 -0
  128. {fal-1.36.1 → fal-1.38.0}/src/fal/project.py +0 -0
  129. {fal-1.36.1 → fal-1.38.0}/src/fal/py.typed +0 -0
  130. {fal-1.36.1 → fal-1.38.0}/src/fal/rest_client.py +0 -0
  131. {fal-1.36.1 → fal-1.38.0}/src/fal/sync.py +0 -0
  132. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/__init__.py +0 -0
  133. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/audio/__init__.py +0 -0
  134. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/audio/audio.py +0 -0
  135. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/exceptions.py +0 -0
  136. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/file/__init__.py +0 -0
  137. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/file/file.py +0 -0
  138. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/file/providers/fal.py +0 -0
  139. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/file/providers/gcp.py +0 -0
  140. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/file/providers/r2.py +0 -0
  141. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/file/providers/s3.py +0 -0
  142. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/file/types.py +0 -0
  143. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/image/__init__.py +0 -0
  144. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/image/image.py +0 -0
  145. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
  146. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
  147. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
  148. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
  149. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
  150. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/image/safety_checker.py +0 -0
  151. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/kv.py +0 -0
  152. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/optimize.py +0 -0
  153. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/types.py +0 -0
  154. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/utils/__init__.py +0 -0
  155. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/utils/download_utils.py +0 -0
  156. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/utils/endpoint.py +0 -0
  157. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/utils/retry.py +0 -0
  158. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/utils/setup_utils.py +0 -0
  159. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/video/__init__.py +0 -0
  160. {fal-1.36.1 → fal-1.38.0}/src/fal/toolkit/video/video.py +0 -0
  161. {fal-1.36.1 → fal-1.38.0}/src/fal/utils.py +0 -0
  162. {fal-1.36.1 → fal-1.38.0}/src/fal/workflows.py +0 -0
  163. {fal-1.36.1 → fal-1.38.0}/tests/__init__.py +0 -0
  164. {fal-1.36.1 → fal-1.38.0}/tests/assets/cat.png +0 -0
  165. {fal-1.36.1 → fal-1.38.0}/tests/cli/__init__.py +0 -0
  166. {fal-1.36.1 → fal-1.38.0}/tests/cli/test_apps.py +0 -0
  167. {fal-1.36.1 → fal-1.38.0}/tests/cli/test_auth.py +0 -0
  168. {fal-1.36.1 → fal-1.38.0}/tests/cli/test_deploy.py +0 -0
  169. {fal-1.36.1 → fal-1.38.0}/tests/cli/test_keys.py +0 -0
  170. {fal-1.36.1 → fal-1.38.0}/tests/cli/test_run.py +0 -0
  171. {fal-1.36.1 → fal-1.38.0}/tests/cli/test_secrets.py +0 -0
  172. {fal-1.36.1 → fal-1.38.0}/tests/conftest.py +0 -0
  173. {fal-1.36.1 → fal-1.38.0}/tests/integration_test.py +0 -0
  174. {fal-1.36.1 → fal-1.38.0}/tests/mainify_package/__init__.py +0 -0
  175. {fal-1.36.1 → fal-1.38.0}/tests/mainify_package/impl.py +0 -0
  176. {fal-1.36.1 → fal-1.38.0}/tests/mainify_package/utils.py +0 -0
  177. {fal-1.36.1 → fal-1.38.0}/tests/mainify_target.py +0 -0
  178. {fal-1.36.1 → fal-1.38.0}/tests/test_apps.py +0 -0
  179. {fal-1.36.1 → fal-1.38.0}/tests/test_files.py +0 -0
  180. {fal-1.36.1 → fal-1.38.0}/tests/test_kv.py +0 -0
  181. {fal-1.36.1 → fal-1.38.0}/tests/test_stability.py +0 -0
  182. {fal-1.36.1 → fal-1.38.0}/tests/toolkit/file/providers/test_fal_retry.py +0 -0
  183. {fal-1.36.1 → fal-1.38.0}/tests/toolkit/file_test.py +0 -0
  184. {fal-1.36.1 → fal-1.38.0}/tests/toolkit/image_test.py +0 -0
  185. {fal-1.36.1 → fal-1.38.0}/tests/toolkit/test_types.py +0 -0
  186. {fal-1.36.1 → fal-1.38.0}/tests/toolkit/utils/retry.py +0 -0
  187. {fal-1.36.1 → fal-1.38.0}/tools/demo_script.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.36.1
3
+ Version: 1.38.0
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels <support@fal.ai>
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: isolate[build]<0.20.0,>=0.18.0
9
- Requires-Dist: isolate-proto<0.15.0,>=0.14.1
9
+ Requires-Dist: isolate-proto<0.17.0,>=0.16.0
10
10
  Requires-Dist: grpcio<2,>=1.64.0
11
11
  Requires-Dist: dill==0.3.7
12
12
  Requires-Dist: cloudpickle==3.0.0
@@ -28,6 +28,8 @@ Requires-Dist: httpx>=0.15.4
28
28
  Requires-Dist: attrs>=21.3.0
29
29
  Requires-Dist: python-dateutil<3,>=2.8.0
30
30
  Requires-Dist: types-python-dateutil<3,>=2.8.0
31
+ Requires-Dist: dateparser<2,>=1.2.0
32
+ Requires-Dist: types-dateparser<2,>=1.2.0
31
33
  Requires-Dist: importlib-metadata>=4.4; python_version < "3.10"
32
34
  Requires-Dist: msgpack<2,>=1.0.7
33
35
  Requires-Dist: websockets>=12.0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.36.1
3
+ Version: 1.38.0
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels <support@fal.ai>
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: isolate[build]<0.20.0,>=0.18.0
9
- Requires-Dist: isolate-proto<0.15.0,>=0.14.1
9
+ Requires-Dist: isolate-proto<0.17.0,>=0.16.0
10
10
  Requires-Dist: grpcio<2,>=1.64.0
11
11
  Requires-Dist: dill==0.3.7
12
12
  Requires-Dist: cloudpickle==3.0.0
@@ -28,6 +28,8 @@ Requires-Dist: httpx>=0.15.4
28
28
  Requires-Dist: attrs>=21.3.0
29
29
  Requires-Dist: python-dateutil<3,>=2.8.0
30
30
  Requires-Dist: types-python-dateutil<3,>=2.8.0
31
+ Requires-Dist: dateparser<2,>=1.2.0
32
+ Requires-Dist: types-dateparser<2,>=1.2.0
31
33
  Requires-Dist: importlib-metadata>=4.4; python_version < "3.10"
32
34
  Requires-Dist: msgpack<2,>=1.0.7
33
35
  Requires-Dist: websockets>=12.0
@@ -1,5 +1,5 @@
1
1
  isolate[build]<0.20.0,>=0.18.0
2
- isolate-proto<0.15.0,>=0.14.1
2
+ isolate-proto<0.17.0,>=0.16.0
3
3
  grpcio<2,>=1.64.0
4
4
  dill==0.3.7
5
5
  cloudpickle==3.0.0
@@ -21,6 +21,8 @@ httpx>=0.15.4
21
21
  attrs>=21.3.0
22
22
  python-dateutil<3,>=2.8.0
23
23
  types-python-dateutil<3,>=2.8.0
24
+ dateparser<2,>=1.2.0
25
+ types-dateparser<2,>=1.2.0
24
26
  msgpack<2,>=1.0.7
25
27
  websockets>=12.0
26
28
  pillow<11,>=10.2.0
@@ -23,7 +23,7 @@ readme = "README.md"
23
23
  requires-python = ">=3.8"
24
24
  dependencies = [
25
25
  "isolate[build]>=0.18.0,<0.20.0",
26
- "isolate-proto>=0.14.1,<0.15.0",
26
+ "isolate-proto>=0.16.0,<0.17.0",
27
27
  "grpcio>=1.64.0,<2",
28
28
  "dill==0.3.7",
29
29
  "cloudpickle==3.0.0",
@@ -47,6 +47,8 @@ dependencies = [
47
47
  "attrs>=21.3.0",
48
48
  "python-dateutil>=2.8.0,<3",
49
49
  "types-python-dateutil>=2.8.0,<3",
50
+ "dateparser>=1.2.0,<2",
51
+ "types-dateparser>=1.2.0,<2",
50
52
  # For 3.9 and earlier, importlib-metadata's newer versions are included in the standard library.
51
53
  'importlib-metadata>=4.4; python_version < "3.10"',
52
54
  "msgpack>=1.0.7,<2",
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.36.1'
32
- __version_tuple__ = version_tuple = (1, 36, 1)
31
+ __version__ = version = '1.38.0'
32
+ __version_tuple__ = version_tuple = (1, 38, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -8,7 +8,7 @@ import fal.cli.runners as runners
8
8
  from fal.sdk import RunnerState
9
9
 
10
10
  from ._utils import get_client
11
- from .parser import FalClientParser, get_output_parser
11
+ from .parser import FalClientParser, SinceAction, get_output_parser
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from fal.sdk import AliasInfo, ApplicationInfo
@@ -291,27 +291,38 @@ def _add_set_rev_parser(subparsers, parents):
291
291
  def _runners(args):
292
292
  client = get_client(args.host, args.team)
293
293
  with client.connect() as connection:
294
- alias_runners = connection.list_alias_runners(alias=args.app_name)
295
-
296
- runners_table = runners.runners_table(alias_runners)
297
- pending_runners = [
298
- runner for runner in alias_runners if runner.state == RunnerState.PENDING
299
- ]
300
- setup_runners = [
301
- runner for runner in alias_runners if runner.state == RunnerState.SETUP
302
- ]
303
- args.console.print(
304
- f"Runners: {len(alias_runners) - len(pending_runners) - len(setup_runners)}"
305
- )
306
- args.console.print(f"Runners Pending: {len(pending_runners)}")
307
- args.console.print(f"Runners Setting Up: {len(setup_runners)}")
308
- # Drop the alias column, which is the first column
309
- runners_table.columns.pop(0)
310
- args.console.print(runners_table)
294
+ start_time = getattr(args, "since", None)
295
+ alias_runners = connection.list_alias_runners(
296
+ alias=args.app_name, start_time=start_time
297
+ )
298
+ if getattr(args, "state", None):
299
+ states = set(args.state)
300
+ if "all" not in states:
301
+ alias_runners = [r for r in alias_runners if r.state.value in states]
302
+ if args.output == "pretty":
303
+ runners_table = runners.runners_table(alias_runners)
304
+ pending_runners = [
305
+ runner for runner in alias_runners if runner.state == RunnerState.PENDING
306
+ ]
307
+ setup_runners = [
308
+ runner for runner in alias_runners if runner.state == RunnerState.SETUP
309
+ ]
310
+ args.console.print(
311
+ f"Runners: {len(alias_runners) - len(pending_runners) - len(setup_runners)}"
312
+ )
313
+ args.console.print(f"Runners Pending: {len(pending_runners)}")
314
+ args.console.print(f"Runners Setting Up: {len(setup_runners)}")
315
+ # Drop the alias column, which is the first column
316
+ runners_table.columns.pop(0)
317
+ args.console.print(runners_table)
311
318
 
312
- requests_table = runners.runners_requests_table(alias_runners)
313
- args.console.print(f"Requests: {len(requests_table.rows)}")
314
- args.console.print(requests_table)
319
+ requests_table = runners.runners_requests_table(alias_runners)
320
+ args.console.print(f"Requests: {len(requests_table.rows)}")
321
+ args.console.print(requests_table)
322
+ elif args.output == "json":
323
+ runners._list_json(args, alias_runners)
324
+ else:
325
+ raise AssertionError(f"Invalid output format: {args.output}")
315
326
 
316
327
 
317
328
  def _add_runners_parser(subparsers, parents):
@@ -320,12 +331,30 @@ def _add_runners_parser(subparsers, parents):
320
331
  "runners",
321
332
  description=runners_help,
322
333
  help=runners_help,
323
- parents=parents,
334
+ parents=[*parents, get_output_parser()],
324
335
  )
325
336
  parser.add_argument(
326
337
  "app_name",
327
338
  help="Application name.",
328
339
  )
340
+ parser.add_argument(
341
+ "--since",
342
+ default=None,
343
+ action=SinceAction,
344
+ limit="1 day",
345
+ help=(
346
+ "Show dead runners since the given time. "
347
+ "Accepts 'now', relative like '30m', '1h', '1d', "
348
+ "or an ISO timestamp. Max 24 hours."
349
+ ),
350
+ )
351
+ parser.add_argument(
352
+ "--state",
353
+ choices=["all", "running", "pending", "setup", "dead"],
354
+ nargs="+",
355
+ default=None,
356
+ help=("Filter by runner state(s). Choose one or more, or 'all'(default)."),
357
+ )
329
358
  parser.set_defaults(func=_runners)
330
359
 
331
360
 
@@ -1,5 +1,7 @@
1
1
  import argparse
2
2
  import sys
3
+ from datetime import datetime, timedelta
4
+ from typing import Optional
3
5
 
4
6
  import rich_argparse
5
7
 
@@ -55,6 +57,61 @@ class DictAction(argparse.Action):
55
57
  setattr(args, self.dest, d)
56
58
 
57
59
 
60
+ class SinceAction(argparse.Action):
61
+ LIMIT_LEEWAY = timedelta(minutes=1)
62
+
63
+ def _parse_since(self, value: str) -> Optional[datetime]:
64
+ import dateparser
65
+
66
+ return dateparser.parse(
67
+ value,
68
+ settings={
69
+ "PREFER_DATES_FROM": "past",
70
+ },
71
+ )
72
+
73
+ def __init__(self, *args, **kwargs):
74
+ self._limit = kwargs.pop("limit", None)
75
+ if self._limit:
76
+ if not isinstance(self._limit, str):
77
+ raise ValueError(
78
+ f"Invalid 'limit' value for SinceAction: {self._limit!r}"
79
+ )
80
+
81
+ self._limit_dt = self._parse_since(self._limit)
82
+ if not self._limit_dt:
83
+ raise ValueError(
84
+ f"Invalid 'limit' value for SinceAction: {self._limit!r}"
85
+ )
86
+
87
+ super().__init__(*args, **kwargs)
88
+
89
+ def __call__(self, parser, args, values, option_string=None): # noqa: ARG002
90
+ if values is None:
91
+ setattr(args, self.dest, None)
92
+ return
93
+
94
+ dt = self._parse_since(values)
95
+ if not dt:
96
+ raise argparse.ArgumentError(
97
+ self,
98
+ (
99
+ f"Invalid since value: {values}. "
100
+ "Use 'now', relative like '15m' or '24h ago', "
101
+ "or an ISO timestamp."
102
+ ),
103
+ )
104
+
105
+ if self._limit_dt is not None:
106
+ if dt < self._limit_dt - self.LIMIT_LEEWAY:
107
+ raise argparse.ArgumentError(
108
+ self,
109
+ f"Since value is older than the allowed limit {self._limit}.",
110
+ )
111
+
112
+ setattr(args, self.dest, dt)
113
+
114
+
58
115
  def _find_parser(parser, func):
59
116
  defaults = parser._defaults
60
117
  if not func or func == defaults.get("func"):
@@ -1,12 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ from datetime import timedelta
4
5
  from typing import List
5
6
 
6
7
  from fal.sdk import RunnerInfo, RunnerState
7
8
 
8
9
  from ._utils import get_client
9
- from .parser import FalClientParser, get_output_parser
10
+ from .parser import FalClientParser, SinceAction, get_output_parser
10
11
 
11
12
 
12
13
  def runners_table(runners: List[RunnerInfo]):
@@ -15,8 +16,7 @@ def runners_table(runners: List[RunnerInfo]):
15
16
  table = Table()
16
17
  table.add_column("Alias")
17
18
  table.add_column("Runner ID")
18
- table.add_column("In Flight Requests")
19
- table.add_column("Missing Leases")
19
+ table.add_column("In Flight\nRequests")
20
20
  table.add_column("Expires In")
21
21
  table.add_column("Uptime")
22
22
  table.add_column("Revision")
@@ -34,18 +34,29 @@ def runners_table(runners: List[RunnerInfo]):
34
34
  ]
35
35
  )
36
36
 
37
+ in_flight = str(runner.in_flight_requests)
38
+ missing_leases = runner.in_flight_requests - num_leases_with_request
39
+ if missing_leases > 0:
40
+ # Show a small indicator of in flight requests that are not visible in the
41
+ # leases lists
42
+ # This can be due to race conditions, so only important to report if it's
43
+ # consistent
44
+ in_flight = f"{in_flight} [dim]({missing_leases})[/]"
45
+
46
+ uptime = timedelta(
47
+ seconds=int(runner.uptime.total_seconds()),
48
+ )
37
49
  table.add_row(
38
50
  runner.alias,
39
51
  # Mark lost runners in red
40
52
  runner.runner_id if present else f"[red]{runner.runner_id}[/]",
41
- str(runner.in_flight_requests),
42
- str(runner.in_flight_requests - num_leases_with_request),
53
+ in_flight,
43
54
  (
44
- "N/A (active)"
55
+ "N/A"
45
56
  if runner.expiration_countdown is None
46
57
  else f"{runner.expiration_countdown}s"
47
58
  ),
48
- f"{runner.uptime} ({runner.uptime.total_seconds()}s)",
59
+ f"{uptime} ({uptime.total_seconds():.0f}s)",
49
60
  runner.revision,
50
61
  runner.state.value,
51
62
  )
@@ -104,17 +115,31 @@ def _list_json(args, runners: list[RunnerInfo]):
104
115
  def _list(args):
105
116
  client = get_client(args.host, args.team)
106
117
  with client.connect() as connection:
107
- runners = connection.list_runners()
118
+ start_time = getattr(args, "since", None)
119
+ runners = connection.list_runners(start_time=start_time)
120
+
121
+ if getattr(args, "state", None):
122
+ states = set(args.state)
123
+ if "all" not in states:
124
+ runners = [r for r in runners if r.state.value in states]
108
125
  pending_runners = [
109
126
  runner for runner in runners if runner.state == RunnerState.PENDING
110
127
  ]
111
128
  setup_runners = [
112
129
  runner for runner in runners if runner.state == RunnerState.SETUP
113
130
  ]
131
+ dead_runners = [
132
+ runner for runner in runners if runner.state == RunnerState.DEAD
133
+ ]
114
134
  if args.output == "pretty":
115
135
  args.console.print(
116
136
  "Runners: "
117
- + str(len(runners) - len(pending_runners) - len(setup_runners))
137
+ + str(
138
+ len(runners)
139
+ - len(pending_runners)
140
+ - len(setup_runners)
141
+ - len(dead_runners)
142
+ )
118
143
  )
119
144
  args.console.print(f"Runners Pending: {len(pending_runners)}")
120
145
  args.console.print(f"Runners Setting Up: {len(setup_runners)}")
@@ -152,6 +177,24 @@ def _add_list_parser(subparsers, parents):
152
177
  help=list_help,
153
178
  parents=[*parents, get_output_parser()],
154
179
  )
180
+ parser.add_argument(
181
+ "--since",
182
+ default=None,
183
+ action=SinceAction,
184
+ limit="1 day",
185
+ help=(
186
+ "Show dead runners since the given time. "
187
+ "Accepts 'now', relative like '30m', '1h', '1d', "
188
+ "or an ISO timestamp. Max 24 hours."
189
+ ),
190
+ )
191
+ parser.add_argument(
192
+ "--state",
193
+ choices=["all", "running", "pending", "setup", "dead"],
194
+ nargs="+",
195
+ default=None,
196
+ help=("Filter by runner state(s). Choose one or more, or 'all'(default)."),
197
+ )
155
198
  parser.set_defaults(func=_list)
156
199
 
157
200
 
@@ -265,6 +265,7 @@ class RunnerState(Enum):
265
265
  RUNNING = "running"
266
266
  PENDING = "pending"
267
267
  SETUP = "setup"
268
+ DEAD = "dead"
268
269
  UNKNOWN = "unknown"
269
270
 
270
271
  @staticmethod
@@ -275,6 +276,8 @@ class RunnerState(Enum):
275
276
  return RunnerState.PENDING
276
277
  elif proto is isolate_proto.RunnerInfo.State.SETUP:
277
278
  return RunnerState.SETUP
279
+ elif proto is isolate_proto.RunnerInfo.State.DEAD:
280
+ return RunnerState.DEAD
278
281
  else:
279
282
  return RunnerState.UNKNOWN
280
283
 
@@ -811,8 +814,18 @@ class FalServerlessConnection:
811
814
  response: isolate_proto.ListAliasesResult = self.stub.ListAliases(request)
812
815
  return [from_grpc(alias) for alias in response.aliases]
813
816
 
814
- def list_alias_runners(self, alias: str) -> list[RunnerInfo]:
815
- request = isolate_proto.ListAliasRunnersRequest(alias=alias, list_pending=True)
817
+ def list_alias_runners(
818
+ self,
819
+ alias: str,
820
+ *,
821
+ list_pending: bool = True,
822
+ start_time: datetime | None = None,
823
+ ) -> list[RunnerInfo]:
824
+ kwargs = {"alias": alias, "list_pending": list_pending}
825
+ if start_time:
826
+ kwargs["start_time"] = isolate_proto.timestamp_from_datetime(start_time)
827
+
828
+ request = isolate_proto.ListAliasRunnersRequest(**kwargs)
816
829
  response = self.stub.ListAliasRunners(request)
817
830
  return [from_grpc(runner) for runner in response.runners]
818
831
 
@@ -839,7 +852,13 @@ class FalServerlessConnection:
839
852
  request = isolate_proto.KillRunnerRequest(runner_id=runner_id)
840
853
  self.stub.KillRunner(request)
841
854
 
842
- def list_runners(self) -> list[RunnerInfo]:
843
- request = isolate_proto.ListRunnersRequest(list_pending=True)
855
+ def list_runners(self, start_time: datetime | None = None) -> list[RunnerInfo]:
856
+ kwargs = {
857
+ "list_pending": True,
858
+ }
859
+ if start_time:
860
+ kwargs["start_time"] = isolate_proto.timestamp_from_datetime(start_time)
861
+
862
+ request = isolate_proto.ListRunnersRequest(**kwargs)
844
863
  response = self.stub.ListRunners(request)
845
864
  return [from_grpc(runner) for runner in response.runners]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes