fal 1.11.1__tar.gz → 1.11.3__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 (175) hide show
  1. {fal-1.11.1/fal.egg-info → fal-1.11.3}/PKG-INFO +2 -2
  2. {fal-1.11.1 → fal-1.11.3/fal.egg-info}/PKG-INFO +2 -2
  3. {fal-1.11.1 → fal-1.11.3}/fal.egg-info/SOURCES.txt +1 -0
  4. {fal-1.11.1 → fal-1.11.3}/fal.egg-info/requires.txt +1 -1
  5. {fal-1.11.1 → fal-1.11.3}/pyproject.toml +1 -1
  6. {fal-1.11.1 → fal-1.11.3}/src/fal/_fal_version.py +2 -2
  7. {fal-1.11.1 → fal-1.11.3}/src/fal/auth/__init__.py +36 -0
  8. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/_utils.py +7 -0
  9. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/apps.py +8 -21
  10. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/auth.py +27 -0
  11. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/keys.py +4 -9
  12. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/main.py +2 -0
  13. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/parser.py +4 -0
  14. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/profile.py +22 -27
  15. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/runners.py +2 -3
  16. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/secrets.py +4 -9
  17. fal-1.11.3/src/fal/cli/teams.py +89 -0
  18. {fal-1.11.1 → fal-1.11.3}/src/fal/config.py +17 -4
  19. {fal-1.11.1 → fal-1.11.3}/src/fal/sdk.py +21 -4
  20. {fal-1.11.1 → fal-1.11.3}/.gitignore +0 -0
  21. {fal-1.11.1 → fal-1.11.3}/Makefile +0 -0
  22. {fal-1.11.1 → fal-1.11.3}/README.md +0 -0
  23. {fal-1.11.1 → fal-1.11.3}/docs/conf.py +0 -0
  24. {fal-1.11.1 → fal-1.11.3}/docs/index.rst +0 -0
  25. {fal-1.11.1 → fal-1.11.3}/fal.egg-info/dependency_links.txt +0 -0
  26. {fal-1.11.1 → fal-1.11.3}/fal.egg-info/entry_points.txt +0 -0
  27. {fal-1.11.1 → fal-1.11.3}/fal.egg-info/top_level.txt +0 -0
  28. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/README.md +0 -0
  29. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  30. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  31. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  32. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  33. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  34. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  35. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/__init__.py +0 -0
  36. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/create_workflow.py +0 -0
  37. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/delete_workflow.py +0 -0
  38. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/get_workflow.py +0 -0
  39. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/list_user_workflows.py +0 -0
  40. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/comfy/update_workflow.py +0 -0
  41. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  42. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  43. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  44. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/users/__init__.py +0 -0
  45. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/users/get_current_user.py +0 -0
  46. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/__init__.py +0 -0
  47. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/create_workflow.py +0 -0
  48. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/delete_workflow.py +0 -0
  49. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/get_workflow.py +0 -0
  50. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/list_user_workflows.py +0 -0
  51. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/api/workflows/update_workflow.py +0 -0
  52. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  53. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  54. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +0 -0
  55. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  56. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  57. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_detail.py +0 -0
  58. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_item.py +0 -0
  59. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema.py +0 -0
  60. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_extra_data.py +0 -0
  61. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs.py +0 -0
  62. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_fal_inputs_dev_info.py +0 -0
  63. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/comfy_workflow_schema_prompt.py +0 -0
  64. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/current_user.py +0 -0
  65. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  66. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  67. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  68. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/lock_reason.py +0 -0
  69. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/page_comfy_workflow_item.py +0 -0
  70. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/page_workflow_item.py +0 -0
  71. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/team_role.py +0 -0
  72. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow.py +0 -0
  73. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/typed_comfy_workflow_update.py +0 -0
  74. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow.py +0 -0
  75. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/typed_workflow_update.py +0 -0
  76. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/user_member.py +0 -0
  77. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  78. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents.py +0 -0
  79. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_metadata.py +0 -0
  80. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_nodes.py +0 -0
  81. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_contents_output.py +0 -0
  82. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail.py +0 -0
  83. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_detail_contents.py +0 -0
  84. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_item.py +0 -0
  85. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_node.py +0 -0
  86. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_node_type.py +0 -0
  87. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema.py +0 -0
  88. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_input.py +0 -0
  89. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/models/workflow_schema_output.py +0 -0
  90. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  91. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  92. {fal-1.11.1 → fal-1.11.3}/openapi-fal-rest/pyproject.toml +0 -0
  93. {fal-1.11.1 → fal-1.11.3}/openapi_rest.config.yaml +0 -0
  94. {fal-1.11.1 → fal-1.11.3}/setup.cfg +0 -0
  95. {fal-1.11.1 → fal-1.11.3}/src/fal/__init__.py +0 -0
  96. {fal-1.11.1 → fal-1.11.3}/src/fal/__main__.py +0 -0
  97. {fal-1.11.1 → fal-1.11.3}/src/fal/_serialization.py +0 -0
  98. {fal-1.11.1 → fal-1.11.3}/src/fal/_version.py +0 -0
  99. {fal-1.11.1 → fal-1.11.3}/src/fal/api.py +0 -0
  100. {fal-1.11.1 → fal-1.11.3}/src/fal/app.py +0 -0
  101. {fal-1.11.1 → fal-1.11.3}/src/fal/apps.py +0 -0
  102. {fal-1.11.1 → fal-1.11.3}/src/fal/auth/auth0.py +0 -0
  103. {fal-1.11.1 → fal-1.11.3}/src/fal/auth/local.py +0 -0
  104. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/__init__.py +0 -0
  105. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/api.py +0 -0
  106. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/cli_nested_json.py +0 -0
  107. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/create.py +0 -0
  108. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/debug.py +0 -0
  109. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/deploy.py +0 -0
  110. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/doctor.py +0 -0
  111. {fal-1.11.1 → fal-1.11.3}/src/fal/cli/run.py +0 -0
  112. {fal-1.11.1 → fal-1.11.3}/src/fal/console/__init__.py +0 -0
  113. {fal-1.11.1 → fal-1.11.3}/src/fal/console/icons.py +0 -0
  114. {fal-1.11.1 → fal-1.11.3}/src/fal/console/ux.py +0 -0
  115. {fal-1.11.1 → fal-1.11.3}/src/fal/container.py +0 -0
  116. {fal-1.11.1 → fal-1.11.3}/src/fal/exceptions/__init__.py +0 -0
  117. {fal-1.11.1 → fal-1.11.3}/src/fal/exceptions/_base.py +0 -0
  118. {fal-1.11.1 → fal-1.11.3}/src/fal/exceptions/_cuda.py +0 -0
  119. {fal-1.11.1 → fal-1.11.3}/src/fal/exceptions/auth.py +0 -0
  120. {fal-1.11.1 → fal-1.11.3}/src/fal/files.py +0 -0
  121. {fal-1.11.1 → fal-1.11.3}/src/fal/flags.py +0 -0
  122. {fal-1.11.1 → fal-1.11.3}/src/fal/logging/__init__.py +0 -0
  123. {fal-1.11.1 → fal-1.11.3}/src/fal/logging/isolate.py +0 -0
  124. {fal-1.11.1 → fal-1.11.3}/src/fal/logging/style.py +0 -0
  125. {fal-1.11.1 → fal-1.11.3}/src/fal/logging/trace.py +0 -0
  126. {fal-1.11.1 → fal-1.11.3}/src/fal/logging/user.py +0 -0
  127. {fal-1.11.1 → fal-1.11.3}/src/fal/py.typed +0 -0
  128. {fal-1.11.1 → fal-1.11.3}/src/fal/rest_client.py +0 -0
  129. {fal-1.11.1 → fal-1.11.3}/src/fal/sync.py +0 -0
  130. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/__init__.py +0 -0
  131. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/exceptions.py +0 -0
  132. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/file/__init__.py +0 -0
  133. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/file/file.py +0 -0
  134. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/file/providers/fal.py +0 -0
  135. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/file/providers/gcp.py +0 -0
  136. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/file/providers/r2.py +0 -0
  137. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/file/providers/s3.py +0 -0
  138. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/file/types.py +0 -0
  139. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/image/__init__.py +0 -0
  140. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/image/image.py +0 -0
  141. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/image/nsfw_filter/__init__.py +0 -0
  142. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/image/nsfw_filter/env.py +0 -0
  143. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/image/nsfw_filter/inference.py +0 -0
  144. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/image/nsfw_filter/model.py +0 -0
  145. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/image/nsfw_filter/requirements.txt +0 -0
  146. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/image/safety_checker.py +0 -0
  147. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/optimize.py +0 -0
  148. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/types.py +0 -0
  149. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/utils/__init__.py +0 -0
  150. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/utils/download_utils.py +0 -0
  151. {fal-1.11.1 → fal-1.11.3}/src/fal/toolkit/utils/retry.py +0 -0
  152. {fal-1.11.1 → fal-1.11.3}/src/fal/utils.py +0 -0
  153. {fal-1.11.1 → fal-1.11.3}/src/fal/workflows.py +0 -0
  154. {fal-1.11.1 → fal-1.11.3}/tests/__init__.py +0 -0
  155. {fal-1.11.1 → fal-1.11.3}/tests/assets/cat.png +0 -0
  156. {fal-1.11.1 → fal-1.11.3}/tests/cli/__init__.py +0 -0
  157. {fal-1.11.1 → fal-1.11.3}/tests/cli/test_apps.py +0 -0
  158. {fal-1.11.1 → fal-1.11.3}/tests/cli/test_auth.py +0 -0
  159. {fal-1.11.1 → fal-1.11.3}/tests/cli/test_deploy.py +0 -0
  160. {fal-1.11.1 → fal-1.11.3}/tests/cli/test_keys.py +0 -0
  161. {fal-1.11.1 → fal-1.11.3}/tests/cli/test_run.py +0 -0
  162. {fal-1.11.1 → fal-1.11.3}/tests/cli/test_secrets.py +0 -0
  163. {fal-1.11.1 → fal-1.11.3}/tests/conftest.py +0 -0
  164. {fal-1.11.1 → fal-1.11.3}/tests/integration_test.py +0 -0
  165. {fal-1.11.1 → fal-1.11.3}/tests/mainify_package/__init__.py +0 -0
  166. {fal-1.11.1 → fal-1.11.3}/tests/mainify_package/impl.py +0 -0
  167. {fal-1.11.1 → fal-1.11.3}/tests/mainify_package/utils.py +0 -0
  168. {fal-1.11.1 → fal-1.11.3}/tests/mainify_target.py +0 -0
  169. {fal-1.11.1 → fal-1.11.3}/tests/test_apps.py +0 -0
  170. {fal-1.11.1 → fal-1.11.3}/tests/test_stability.py +0 -0
  171. {fal-1.11.1 → fal-1.11.3}/tests/toolkit/file_test.py +0 -0
  172. {fal-1.11.1 → fal-1.11.3}/tests/toolkit/image_test.py +0 -0
  173. {fal-1.11.1 → fal-1.11.3}/tests/toolkit/test_types.py +0 -0
  174. {fal-1.11.1 → fal-1.11.3}/tests/toolkit/utils/retry.py +0 -0
  175. {fal-1.11.1 → fal-1.11.3}/tools/demo_script.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.11.1
3
+ Version: 1.11.3
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
@@ -22,7 +22,7 @@ Requires-Dist: rich<14,>=13.3.2
22
22
  Requires-Dist: rich_argparse
23
23
  Requires-Dist: packaging>=21.3
24
24
  Requires-Dist: pathspec<1,>=0.11.1
25
- Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
25
+ Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<2.11
26
26
  Requires-Dist: structlog>=22.0
27
27
  Requires-Dist: fastapi<1,>=0.99.1
28
28
  Requires-Dist: starlette-exporter>=0.21.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fal
3
- Version: 1.11.1
3
+ Version: 1.11.3
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
@@ -22,7 +22,7 @@ Requires-Dist: rich<14,>=13.3.2
22
22
  Requires-Dist: rich_argparse
23
23
  Requires-Dist: packaging>=21.3
24
24
  Requires-Dist: pathspec<1,>=0.11.1
25
- Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
25
+ Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<2.11
26
26
  Requires-Dist: structlog>=22.0
27
27
  Requires-Dist: fastapi<1,>=0.99.1
28
28
  Requires-Dist: starlette-exporter>=0.21.0
@@ -114,6 +114,7 @@ src/fal/cli/profile.py
114
114
  src/fal/cli/run.py
115
115
  src/fal/cli/runners.py
116
116
  src/fal/cli/secrets.py
117
+ src/fal/cli/teams.py
117
118
  src/fal/console/__init__.py
118
119
  src/fal/console/icons.py
119
120
  src/fal/console/ux.py
@@ -15,7 +15,7 @@ rich<14,>=13.3.2
15
15
  rich_argparse
16
16
  packaging>=21.3
17
17
  pathspec<1,>=0.11.1
18
- pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3
18
+ pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<2.11
19
19
  structlog>=22.0
20
20
  fastapi<1,>=0.99.1
21
21
  starlette-exporter>=0.21.0
@@ -39,7 +39,7 @@ dependencies = [
39
39
  "rich_argparse",
40
40
  "packaging>=21.3",
41
41
  "pathspec>=0.11.1,<1",
42
- "pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<3",
42
+ "pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,<2.11",
43
43
  # serve=True dependencies
44
44
  "structlog>=22.0",
45
45
  "fastapi>=0.99.1,<1",
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '1.11.1'
21
- __version_tuple__ = version_tuple = (1, 11, 1)
20
+ __version__ = version = '1.11.3'
21
+ __version_tuple__ = version_tuple = (1, 11, 3)
@@ -128,16 +128,40 @@ def _fetch_access_token() -> str:
128
128
  return token_data["access_token"]
129
129
 
130
130
 
131
+ def _fetch_teams(bearer_token: str) -> list[dict]:
132
+ import json
133
+ from urllib.error import HTTPError
134
+ from urllib.request import Request, urlopen
135
+
136
+ from fal.exceptions import FalServerlessException
137
+ from fal.flags import REST_URL
138
+
139
+ request = Request(
140
+ method="GET",
141
+ url=f"{REST_URL}/users/teams",
142
+ headers={"Authorization": bearer_token},
143
+ )
144
+ try:
145
+ with urlopen(request) as response:
146
+ teams = json.load(response)
147
+ except HTTPError as exc:
148
+ raise FalServerlessException("Failed to fetch teams") from exc
149
+
150
+ return [team for team in teams if not team["is_personal"]]
151
+
152
+
131
153
  @dataclass
132
154
  class UserAccess:
133
155
  _access_token: str | None = field(repr=False, default=None)
134
156
  _user_info: dict | None = field(repr=False, default=None)
135
157
  _exc: Exception | None = field(repr=False, default=None)
158
+ _teams: list[dict] | None = field(repr=False, default=None)
136
159
 
137
160
  def invalidate(self) -> None:
138
161
  self._access_token = None
139
162
  self._user_info = None
140
163
  self._exc = None
164
+ self._teams = None
141
165
 
142
166
  @property
143
167
  def info(self) -> dict:
@@ -167,5 +191,17 @@ class UserAccess:
167
191
  def bearer_token(self) -> str:
168
192
  return "Bearer " + self.access_token
169
193
 
194
+ @property
195
+ def teams(self) -> list[dict]:
196
+ if self._teams is None:
197
+ self._teams = _fetch_teams(self.bearer_token)
198
+ return self._teams
199
+
200
+ def get_team(self, team: str) -> dict:
201
+ for t in self.teams:
202
+ if t["nickname"].lower() == team.lower():
203
+ return t
204
+ raise ValueError(f"Team {team} not found")
205
+
170
206
 
171
207
  USER = UserAccess()
@@ -3,6 +3,13 @@ from __future__ import annotations
3
3
  from fal.files import find_project_root, find_pyproject_toml, parse_pyproject_toml
4
4
 
5
5
 
6
+ def get_client(host: str, team: str | None = None):
7
+ from fal.sdk import FalServerlessClient, get_default_credentials
8
+
9
+ credentials = get_default_credentials(team=team)
10
+ return FalServerlessClient(host, credentials)
11
+
12
+
6
13
  def is_app_name(app_ref: tuple[str, str | None]) -> bool:
7
14
  is_single_file = app_ref[1] is None
8
15
  is_python_file = app_ref[0].endswith(".py")
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
+ from ._utils import get_client
5
6
  from .parser import FalClientParser
6
7
 
7
8
  if TYPE_CHECKING:
@@ -47,9 +48,7 @@ def _apps_table(apps: list[AliasInfo]):
47
48
 
48
49
 
49
50
  def _list(args):
50
- from fal.sdk import FalServerlessClient
51
-
52
- client = FalServerlessClient(args.host)
51
+ client = get_client(args.host, args.team)
53
52
  with client.connect() as connection:
54
53
  apps = connection.list_aliases()
55
54
 
@@ -120,9 +119,7 @@ def _app_rev_table(revs: list[ApplicationInfo]):
120
119
 
121
120
 
122
121
  def _list_rev(args):
123
- from fal.sdk import FalServerlessClient
124
-
125
- client = FalServerlessClient(args.host)
122
+ client = get_client(args.host, args.team)
126
123
  with client.connect() as connection:
127
124
  revs = connection.list_applications()
128
125
  table = _app_rev_table(revs)
@@ -142,9 +139,7 @@ def _add_list_rev_parser(subparsers, parents):
142
139
 
143
140
 
144
141
  def _scale(args):
145
- from fal.sdk import FalServerlessClient
146
-
147
- client = FalServerlessClient(args.host)
142
+ client = get_client(args.host, args.team)
148
143
  with client.connect() as connection:
149
144
  if (
150
145
  args.keep_alive is None
@@ -240,9 +235,7 @@ def _add_scale_parser(subparsers, parents):
240
235
 
241
236
 
242
237
  def _set_rev(args):
243
- from fal.sdk import FalServerlessClient
244
-
245
- client = FalServerlessClient(args.host)
238
+ client = get_client(args.host, args.team)
246
239
  with client.connect() as connection:
247
240
  connection.create_alias(args.app_name, args.app_rev, args.auth)
248
241
 
@@ -277,9 +270,7 @@ def _add_set_rev_parser(subparsers, parents):
277
270
  def _runners(args):
278
271
  from rich.table import Table
279
272
 
280
- from fal.sdk import FalServerlessClient
281
-
282
- client = FalServerlessClient(args.host)
273
+ client = get_client(args.host, args.team)
283
274
  with client.connect() as connection:
284
275
  runners = connection.list_alias_runners(alias=args.app_name)
285
276
 
@@ -350,9 +341,7 @@ def _add_runners_parser(subparsers, parents):
350
341
 
351
342
 
352
343
  def _delete(args):
353
- from fal.sdk import FalServerlessClient
354
-
355
- client = FalServerlessClient(args.host)
344
+ client = get_client(args.host, args.team)
356
345
  with client.connect() as connection:
357
346
  connection.delete_alias(args.app_name)
358
347
 
@@ -373,9 +362,7 @@ def _add_delete_parser(subparsers, parents):
373
362
 
374
363
 
375
364
  def _delete_rev(args):
376
- from fal.sdk import FalServerlessClient
377
-
378
- client = FalServerlessClient(args.host)
365
+ client = get_client(args.host, args.team)
379
366
  with client.connect() as connection:
380
367
  connection.delete_application(args.app_rev)
381
368
 
@@ -2,11 +2,38 @@ from fal.auth import USER, login, logout
2
2
 
3
3
 
4
4
  def _login(args):
5
+ from rich.prompt import Prompt
6
+
7
+ from fal.config import Config
8
+
5
9
  login()
10
+ teams = [team["nickname"].lower() for team in USER.teams]
11
+ if not teams:
12
+ return
13
+
14
+ team = Prompt.ask(
15
+ "\nPlease choose a team account to use or leave blank to "
16
+ "use your personal account:",
17
+ choices=teams,
18
+ default=None,
19
+ )
20
+ with Config().edit() as config:
21
+ if team:
22
+ args.console.print(
23
+ f"Setting team to [cyan]{team}[/]. "
24
+ "You can change this later with [bold]fal team set[/]."
25
+ )
26
+ config.set("team", team)
27
+ else:
28
+ config.unset("team")
6
29
 
7
30
 
8
31
  def _logout(args):
32
+ from fal.config import Config
33
+
9
34
  logout()
35
+ with Config().edit() as config:
36
+ config.unset("team")
10
37
 
11
38
 
12
39
  def _whoami(args):
@@ -1,12 +1,11 @@
1
1
  from fal.sdk import KeyScope
2
2
 
3
+ from ._utils import get_client
3
4
  from .parser import FalClientParser
4
5
 
5
6
 
6
7
  def _create(args):
7
- from fal.sdk import FalServerlessClient
8
-
9
- client = FalServerlessClient(args.host)
8
+ client = get_client(args.host, args.team)
10
9
  with client.connect() as connection:
11
10
  parsed_scope = KeyScope(args.scope)
12
11
  result = connection.create_user_key(parsed_scope, args.desc)
@@ -43,15 +42,13 @@ def _add_create_parser(subparsers, parents):
43
42
  def _list(args):
44
43
  from rich.table import Table
45
44
 
46
- from fal.sdk import FalServerlessClient
47
-
48
- client = FalServerlessClient(args.host)
49
45
  table = Table()
50
46
  table.add_column("Key ID")
51
47
  table.add_column("Created At")
52
48
  table.add_column("Scope")
53
49
  table.add_column("Description")
54
50
 
51
+ client = get_client(args.host, args.team)
55
52
  with client.connect() as connection:
56
53
  keys = connection.list_user_keys()
57
54
  for key in keys:
@@ -77,9 +74,7 @@ def _add_list_parser(subparsers, parents):
77
74
 
78
75
 
79
76
  def _revoke(args):
80
- from fal.sdk import FalServerlessClient
81
-
82
- client = FalServerlessClient(args.host)
77
+ client = get_client(args.host, args.team)
83
78
  with client.connect() as connection:
84
79
  connection.revoke_user_key(args.key_id)
85
80
 
@@ -18,6 +18,7 @@ from . import (
18
18
  run,
19
19
  runners,
20
20
  secrets,
21
+ teams,
21
22
  )
22
23
  from .debug import debugtools, get_debug_parser
23
24
  from .parser import FalParser, FalParserExit
@@ -55,6 +56,7 @@ def _get_main_parser() -> argparse.ArgumentParser:
55
56
  doctor,
56
57
  create,
57
58
  runners,
59
+ teams,
58
60
  ]:
59
61
  cmd.add_parser(subparsers, parents)
60
62
 
@@ -99,3 +99,7 @@ class FalClientParser(FalParser):
99
99
  default=GRPC_HOST,
100
100
  help=argparse.SUPPRESS,
101
101
  )
102
+ self.add_argument(
103
+ "--team",
104
+ help="The team to use.",
105
+ )
@@ -4,13 +4,12 @@ from fal.config import Config
4
4
 
5
5
 
6
6
  def _list(args):
7
- config = Config()
8
-
9
7
  table = Table()
10
8
  table.add_column("Default")
11
9
  table.add_column("Profile")
12
10
  table.add_column("Settings")
13
11
 
12
+ config = Config()
14
13
  for profile in config.profiles():
15
14
  table.add_row(
16
15
  "*" if profile == config._profile else "",
@@ -22,28 +21,24 @@ def _list(args):
22
21
 
23
22
 
24
23
  def _set(args):
25
- config = Config()
26
- config.set_internal("profile", args.PROFILE)
27
- args.console.print(f"Default profile set to [cyan]{args.PROFILE}[/].")
28
- config.profile = args.PROFILE
29
- if not config.get("key"):
30
- args.console.print(
31
- "No key set for profile. Use [bold]fal profile key[/] to set a key."
32
- )
33
- config.save()
24
+ with Config().edit() as config:
25
+ config.set_internal("profile", args.PROFILE)
26
+ args.console.print(f"Default profile set to [cyan]{args.PROFILE}[/].")
27
+ config.profile = args.PROFILE
28
+ if not config.get("key"):
29
+ args.console.print(
30
+ "No key set for profile. Use [bold]fal profile key[/] to set a key."
31
+ )
34
32
 
35
33
 
36
34
  def _unset(args):
37
- config = Config()
38
- config.set_internal("profile", None)
39
- args.console.print("Default profile unset.")
40
- config.profile = None
41
- config.save()
35
+ with Config().edit() as config:
36
+ config.set_internal("profile", None)
37
+ args.console.print("Default profile unset.")
38
+ config.profile = None
42
39
 
43
40
 
44
41
  def _key_set(args):
45
- config = Config()
46
-
47
42
  while True:
48
43
  key = input("Enter the key: ")
49
44
  if ":" in key:
@@ -52,25 +47,25 @@ def _key_set(args):
52
47
  "[red]Invalid key. The key must be in the format [bold]key:value[/].[/]"
53
48
  )
54
49
 
55
- config.set("key", key)
56
- args.console.print(f"Key set for profile [cyan]{config.profile}[/].")
57
- config.save()
50
+ with Config().edit() as config:
51
+ config.set("key", key)
52
+ args.console.print(f"Key set for profile [cyan]{config.profile}[/].")
58
53
 
59
54
 
60
55
  def _delete(args):
61
- config = Config()
62
- if config.profile == args.PROFILE:
63
- config.set_internal("profile", None)
56
+ with Config().edit() as config:
57
+ if config.profile == args.PROFILE:
58
+ config.set_internal("profile", None)
64
59
 
65
- config.delete(args.PROFILE)
66
- args.console.print(f"Profile [cyan]{args.PROFILE}[/] deleted.")
67
- config.save()
60
+ config.delete(args.PROFILE)
61
+ args.console.print(f"Profile [cyan]{args.PROFILE}[/] deleted.")
68
62
 
69
63
 
70
64
  def add_parser(main_subparsers, parents):
71
65
  auth_help = "Profile management."
72
66
  parser = main_subparsers.add_parser(
73
67
  "profile",
68
+ aliases=["profiles"],
74
69
  description=auth_help,
75
70
  help=auth_help,
76
71
  parents=parents,
@@ -1,10 +1,9 @@
1
+ from ._utils import get_client
1
2
  from .parser import FalClientParser
2
3
 
3
4
 
4
5
  def _kill(args):
5
- from fal.sdk import FalServerlessClient
6
-
7
- client = FalServerlessClient(args.host)
6
+ client = get_client(args.host, args.team)
8
7
  with client.connect() as connection:
9
8
  connection.kill_runner(args.id)
10
9
 
@@ -1,10 +1,9 @@
1
+ from ._utils import get_client
1
2
  from .parser import DictAction, FalClientParser
2
3
 
3
4
 
4
5
  def _set(args):
5
- from fal.sdk import FalServerlessClient
6
-
7
- client = FalServerlessClient(args.host)
6
+ client = get_client(args.host, args.team)
8
7
  with client.connect() as connection:
9
8
  for name, value in args.secrets.items():
10
9
  connection.set_secret(name, value)
@@ -34,13 +33,11 @@ def _add_set_parser(subparsers, parents):
34
33
  def _list(args):
35
34
  from rich.table import Table
36
35
 
37
- from fal.sdk import FalServerlessClient
38
-
39
36
  table = Table()
40
37
  table.add_column("Secret Name")
41
38
  table.add_column("Created At")
42
39
 
43
- client = FalServerlessClient(args.host)
40
+ client = get_client(args.host, args.team)
44
41
  with client.connect() as connection:
45
42
  for secret in connection.list_secrets():
46
43
  table.add_row(secret.name, str(secret.created_at))
@@ -60,9 +57,7 @@ def _add_list_parser(subparsers, parents):
60
57
 
61
58
 
62
59
  def _unset(args):
63
- from fal.sdk import FalServerlessClient
64
-
65
- client = FalServerlessClient(args.host)
60
+ client = get_client(args.host, args.team)
66
61
  with client.connect() as connection:
67
62
  connection.delete_secret(args.secret)
68
63
 
@@ -0,0 +1,89 @@
1
+ def _list(args):
2
+ from rich.table import Table
3
+
4
+ from fal.auth import USER
5
+ from fal.config import Config
6
+
7
+ table = Table()
8
+ table.add_column("Default")
9
+ table.add_column("Team")
10
+ table.add_column("Full Name")
11
+ table.add_column("ID")
12
+
13
+ default_team = Config().get("team")
14
+
15
+ for team in USER.teams:
16
+ default = default_team and default_team.lower() == team["nickname"].lower()
17
+ table.add_row(
18
+ "*" if default else "", team["nickname"], team["full_name"], team["user_id"]
19
+ )
20
+
21
+ args.console.print(table)
22
+
23
+
24
+ def _set(args):
25
+ from fal.config import Config
26
+ from fal.sdk import USER
27
+
28
+ team = args.team.lower()
29
+ for team_info in USER.teams:
30
+ if team_info["nickname"].lower() == team:
31
+ break
32
+ else:
33
+ raise ValueError(f"Team {args.team} not found")
34
+
35
+ with Config().edit() as config:
36
+ config.set("team", team)
37
+
38
+
39
+ def _unset(args):
40
+ from fal.config import Config
41
+
42
+ with Config().edit() as config:
43
+ config.unset("team")
44
+
45
+
46
+ def add_parser(main_subparsers, parents):
47
+ teams_help = "Manage teams."
48
+ parser = main_subparsers.add_parser(
49
+ "teams",
50
+ aliases=["team"],
51
+ description=teams_help,
52
+ help=teams_help,
53
+ parents=parents,
54
+ )
55
+
56
+ subparsers = parser.add_subparsers(
57
+ title="Commands",
58
+ metavar="command",
59
+ dest="cmd",
60
+ required=True,
61
+ )
62
+
63
+ list_help = "List teams."
64
+ list_parser = subparsers.add_parser(
65
+ "list",
66
+ description=list_help,
67
+ help=list_help,
68
+ parents=parents,
69
+ )
70
+ list_parser.set_defaults(func=_list)
71
+
72
+ set_help = "Set the current team."
73
+ set_parser = subparsers.add_parser(
74
+ "set",
75
+ description=set_help,
76
+ help=set_help,
77
+ parents=parents,
78
+ )
79
+ set_parser.add_argument("team", help="The team to set.")
80
+ set_parser.set_defaults(func=_set)
81
+
82
+ unset_help = "Unset the current team."
83
+ unset_parser = subparsers.add_parser(
84
+ "unset",
85
+ description=unset_help,
86
+ help=unset_help,
87
+ parents=parents,
88
+ )
89
+ unset_parser.set_defaults(func=_unset)
@@ -1,7 +1,9 @@
1
1
  import os
2
+ from contextlib import contextmanager
2
3
  from typing import Dict, List, Optional
3
4
 
4
- SETTINGS_SECTION = "__internal__"
5
+ SETTINGS_SECTION = "__internal__" # legacy
6
+ DEFAULT_PROFILE = "default"
5
7
 
6
8
 
7
9
  class Config:
@@ -23,9 +25,9 @@ class Config:
23
25
  except FileNotFoundError:
24
26
  self._config = {}
25
27
 
26
- profile = os.getenv("FAL_PROFILE")
27
- if not profile:
28
- profile = self.get_internal("profile")
28
+ profile = (
29
+ os.getenv("FAL_PROFILE") or self.get_internal("profile") or DEFAULT_PROFILE
30
+ )
29
31
 
30
32
  self.profile = profile
31
33
 
@@ -66,6 +68,12 @@ class Config:
66
68
 
67
69
  self._config[self.profile][key] = value
68
70
 
71
+ def unset(self, key: str) -> None:
72
+ if not self.profile:
73
+ raise ValueError("No profile set.")
74
+
75
+ del self._config[self.profile][key]
76
+
69
77
  def get_internal(self, key: str) -> Optional[str]:
70
78
  if SETTINGS_SECTION not in self._config:
71
79
  self._config[SETTINGS_SECTION] = {}
@@ -83,3 +91,8 @@ class Config:
83
91
 
84
92
  def delete(self, profile: str) -> None:
85
93
  del self._config[profile]
94
+
95
+ @contextmanager
96
+ def edit(self):
97
+ yield self
98
+ self.save()
@@ -129,12 +129,22 @@ class FalServerlessKeyCredentials(Credentials):
129
129
  @dataclass
130
130
  class AuthenticatedCredentials(Credentials):
131
131
  user = USER
132
+ team_id: str | None = None
132
133
 
133
134
  def to_grpc(self) -> grpc.ChannelCredentials:
134
- return grpc.composite_channel_credentials(
135
+ creds = [
135
136
  self.server_credentials.to_grpc(),
136
137
  grpc.access_token_call_credentials(USER.access_token),
137
- )
138
+ ]
139
+
140
+ if self.team_id:
141
+ creds.append(
142
+ grpc.metadata_call_credentials(
143
+ _GRPCMetadata("fal-user-id", self.team_id)
144
+ )
145
+ )
146
+
147
+ return grpc.composite_channel_credentials(*creds)
138
148
 
139
149
  def to_headers(self) -> dict[str, str]:
140
150
  token = USER.bearer_token
@@ -158,16 +168,23 @@ def get_agent_credentials(original_credentials: Credentials) -> Credentials:
158
168
  return original_credentials
159
169
 
160
170
 
161
- def get_default_credentials() -> Credentials:
171
+ def get_default_credentials(team: str | None = None) -> Credentials:
172
+ from fal.config import Config
173
+
162
174
  if flags.AUTH_DISABLED:
163
175
  return Credentials()
164
176
 
165
177
  key_creds = key_credentials()
166
178
  if key_creds:
167
179
  logger.debug("Using key credentials")
180
+ if team:
181
+ raise ValueError("Using explicit team with key credentials is not allowed")
168
182
  return FalServerlessKeyCredentials(key_creds[0], key_creds[1])
169
183
  else:
170
- return AuthenticatedCredentials()
184
+ config = Config()
185
+ team = team or config.get("team")
186
+ team_id = USER.get_team(team)["user_id"] if team else None
187
+ return AuthenticatedCredentials(team_id=team_id)
171
188
 
172
189
 
173
190
  @dataclass
File without changes
File without changes
File without changes
File without changes
File without changes