fal 0.11.3__tar.gz → 0.11.4__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 (139) hide show
  1. {fal-0.11.3 → fal-0.11.4}/PKG-INFO +1 -1
  2. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/__init__.py +3 -1
  3. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/grouped_usage_detail.py +6 -6
  4. {fal-0.11.3 → fal-0.11.4}/pyproject.toml +1 -1
  5. {fal-0.11.3 → fal-0.11.4}/setup.py +1 -1
  6. {fal-0.11.3 → fal-0.11.4}/src/fal/__init__.py +2 -28
  7. {fal-0.11.3 → fal-0.11.4}/src/fal/api.py +5 -1
  8. fal-0.11.4/src/fal/app.py +162 -0
  9. {fal-0.11.3 → fal-0.11.4}/src/fal/cli.py +34 -42
  10. {fal-0.11.3 → fal-0.11.4}/src/fal/exceptions/_base.py +1 -1
  11. {fal-0.11.3 → fal-0.11.4}/src/fal/exceptions/auth.py +1 -1
  12. {fal-0.11.3 → fal-0.11.4}/README.md +0 -0
  13. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/__init__.py +0 -0
  14. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/__init__.py +0 -0
  15. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/admin/__init__.py +0 -0
  16. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/admin/get_usage_per_user.py +0 -0
  17. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/admin/handle_user_lock.py +0 -0
  18. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/applications/__init__.py +0 -0
  19. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/applications/app_metadata.py +0 -0
  20. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/applications/get_status_applications_app_user_id_app_alias_or_id_status_get.py +0 -0
  21. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/__init__.py +0 -0
  22. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/delete_payment_method.py +0 -0
  23. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/get_setup_intent_key.py +0 -0
  24. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_details.py +0 -0
  25. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_invoices.py +0 -0
  26. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_payment_methods.py +0 -0
  27. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_price.py +0 -0
  28. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/get_user_spending.py +0 -0
  29. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/handle_stripe_webhook.py +0 -0
  30. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/upcoming_invoice.py +0 -0
  31. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/billing/update_customer_budget.py +0 -0
  32. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/__init__.py +0 -0
  33. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/check_dir_hash.py +0 -0
  34. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/delete.py +0 -0
  35. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/download.py +0 -0
  36. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/file_exists.py +0 -0
  37. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/list_directory.py +0 -0
  38. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/list_root.py +0 -0
  39. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/upload_from_url.py +0 -0
  40. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/files/upload_local_file.py +0 -0
  41. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/health/__init__.py +0 -0
  42. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/health/check.py +0 -0
  43. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/keys/__init__.py +0 -0
  44. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/keys/create_key.py +0 -0
  45. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/keys/delete_key.py +0 -0
  46. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/keys/list_keys.py +0 -0
  47. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/logs/__init__.py +0 -0
  48. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/logs/list_since.py +0 -0
  49. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/requests/__init__.py +0 -0
  50. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/requests/requests.py +0 -0
  51. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/storage/__init__.py +0 -0
  52. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/storage/get_file_link.py +0 -0
  53. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/storage/initiate_upload.py +0 -0
  54. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/storage/upload_file.py +0 -0
  55. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/tokens/__init__.py +0 -0
  56. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/tokens/create_token.py +0 -0
  57. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/usage/__init__.py +0 -0
  58. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/usage/get_custom_usage_per_machine.py +0 -0
  59. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/usage/get_gateway_request_stats.py +0 -0
  60. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/usage/get_gateway_request_stats_by_time.py +0 -0
  61. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/usage/get_shared_usage_per_app.py +0 -0
  62. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/usage/get_usage_records.py +0 -0
  63. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/usage/per_machine_usage.py +0 -0
  64. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/api/usage/per_machine_usage_details.py +0 -0
  65. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/client.py +0 -0
  66. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/errors.py +0 -0
  67. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/app_metadata_response_app_metadata.py +0 -0
  68. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/body_create_token.py +0 -0
  69. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/body_upload_file.py +0 -0
  70. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/body_upload_local_file.py +0 -0
  71. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/customer_details.py +0 -0
  72. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/file_spec.py +0 -0
  73. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/gateway_stats_by_time.py +0 -0
  74. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/gateway_usage_stats.py +0 -0
  75. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/get_gateway_request_stats_by_time_response_get_gateway_request_stats_by_time.py +0 -0
  76. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/handle_stripe_webhook_response_handle_stripe_webhook.py +0 -0
  77. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/hash_check.py +0 -0
  78. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/http_validation_error.py +0 -0
  79. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/initiate_upload_info.py +0 -0
  80. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/invoice.py +0 -0
  81. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/invoice_item.py +0 -0
  82. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/key_scope.py +0 -0
  83. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/log_entry.py +0 -0
  84. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/log_entry_labels.py +0 -0
  85. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/new_user_key.py +0 -0
  86. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/payment_method.py +0 -0
  87. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/persisted_usage_record.py +0 -0
  88. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/persisted_usage_record_meta.py +0 -0
  89. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/presigned_upload_url.py +0 -0
  90. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/request_io.py +0 -0
  91. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/request_io_json_input.py +0 -0
  92. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/request_io_json_output.py +0 -0
  93. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/run_type.py +0 -0
  94. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/stats_timeframe.py +0 -0
  95. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/status.py +0 -0
  96. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/status_health.py +0 -0
  97. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/uploaded_file_result.py +0 -0
  98. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/url_file_upload.py +0 -0
  99. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/usage_per_machine_type.py +0 -0
  100. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/usage_per_user.py +0 -0
  101. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/usage_run_detail.py +0 -0
  102. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/user_key_info.py +0 -0
  103. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/models/validation_error.py +0 -0
  104. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/py.typed +0 -0
  105. {fal-0.11.3 → fal-0.11.4}/openapi-fal-rest/openapi_fal_rest/types.py +0 -0
  106. {fal-0.11.3 → fal-0.11.4}/src/fal/_serialization.py +0 -0
  107. {fal-0.11.3 → fal-0.11.4}/src/fal/apps.py +0 -0
  108. {fal-0.11.3 → fal-0.11.4}/src/fal/auth/__init__.py +0 -0
  109. {fal-0.11.3 → fal-0.11.4}/src/fal/auth/auth0.py +0 -0
  110. {fal-0.11.3 → fal-0.11.4}/src/fal/auth/local.py +0 -0
  111. {fal-0.11.3 → fal-0.11.4}/src/fal/console/__init__.py +0 -0
  112. {fal-0.11.3 → fal-0.11.4}/src/fal/console/icons.py +0 -0
  113. {fal-0.11.3 → fal-0.11.4}/src/fal/console/ux.py +0 -0
  114. {fal-0.11.3 → fal-0.11.4}/src/fal/env.py +0 -0
  115. {fal-0.11.3 → fal-0.11.4}/src/fal/exceptions/__init__.py +0 -0
  116. {fal-0.11.3 → fal-0.11.4}/src/fal/exceptions/handlers.py +0 -0
  117. {fal-0.11.3 → fal-0.11.4}/src/fal/flags.py +0 -0
  118. {fal-0.11.3 → fal-0.11.4}/src/fal/logging/__init__.py +0 -0
  119. {fal-0.11.3 → fal-0.11.4}/src/fal/logging/datadog.py +0 -0
  120. {fal-0.11.3 → fal-0.11.4}/src/fal/logging/isolate.py +0 -0
  121. {fal-0.11.3 → fal-0.11.4}/src/fal/logging/style.py +0 -0
  122. {fal-0.11.3 → fal-0.11.4}/src/fal/logging/trace.py +0 -0
  123. {fal-0.11.3 → fal-0.11.4}/src/fal/logging/user.py +0 -0
  124. {fal-0.11.3 → fal-0.11.4}/src/fal/rest_client.py +0 -0
  125. {fal-0.11.3 → fal-0.11.4}/src/fal/sdk.py +0 -0
  126. {fal-0.11.3 → fal-0.11.4}/src/fal/sync.py +0 -0
  127. {fal-0.11.3 → fal-0.11.4}/src/fal/toolkit/__init__.py +0 -0
  128. {fal-0.11.3 → fal-0.11.4}/src/fal/toolkit/exceptions.py +0 -0
  129. {fal-0.11.3 → fal-0.11.4}/src/fal/toolkit/file/__init__.py +0 -0
  130. {fal-0.11.3 → fal-0.11.4}/src/fal/toolkit/file/file.py +0 -0
  131. {fal-0.11.3 → fal-0.11.4}/src/fal/toolkit/file/providers/fal.py +0 -0
  132. {fal-0.11.3 → fal-0.11.4}/src/fal/toolkit/file/providers/gcp.py +0 -0
  133. {fal-0.11.3 → fal-0.11.4}/src/fal/toolkit/file/providers/r2.py +0 -0
  134. {fal-0.11.3 → fal-0.11.4}/src/fal/toolkit/file/types.py +0 -0
  135. {fal-0.11.3 → fal-0.11.4}/src/fal/toolkit/image/__init__.py +0 -0
  136. {fal-0.11.3 → fal-0.11.4}/src/fal/toolkit/image/image.py +0 -0
  137. {fal-0.11.3 → fal-0.11.4}/src/fal/toolkit/mainify.py +0 -0
  138. {fal-0.11.3 → fal-0.11.4}/src/fal/toolkit/utils/__init__.py +0 -0
  139. {fal-0.11.3 → fal-0.11.4}/src/fal/toolkit/utils/download_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fal
3
- Version: 0.11.3
3
+ Version: 0.11.4
4
4
  Summary: fal is an easy-to-use Serverless Python Framework
5
5
  Author: Features & Labels
6
6
  Author-email: hello@fal.ai
@@ -12,7 +12,9 @@ from .get_gateway_request_stats_by_time_response_get_gateway_request_stats_by_ti
12
12
  GetGatewayRequestStatsByTimeResponseGetGatewayRequestStatsByTime,
13
13
  )
14
14
  from .grouped_usage_detail import GroupedUsageDetail
15
- from .handle_stripe_webhook_response_handle_stripe_webhook import HandleStripeWebhookResponseHandleStripeWebhook
15
+ from .handle_stripe_webhook_response_handle_stripe_webhook import (
16
+ HandleStripeWebhookResponseHandleStripeWebhook,
17
+ )
16
18
  from .hash_check import HashCheck
17
19
  from .http_validation_error import HTTPValidationError
18
20
  from .initiate_upload_info import InitiateUploadInfo
@@ -12,14 +12,14 @@ class GroupedUsageDetail:
12
12
  model_id (str):
13
13
  machine_type (str):
14
14
  request_count (int):
15
- avg_duration (float):
15
+ median_duration (float):
16
16
  total_duration (float):
17
17
  """
18
18
 
19
19
  model_id: str
20
20
  machine_type: str
21
21
  request_count: int
22
- avg_duration: float
22
+ median_duration: float
23
23
  total_duration: float
24
24
  additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
25
25
 
@@ -27,7 +27,7 @@ class GroupedUsageDetail:
27
27
  model_id = self.model_id
28
28
  machine_type = self.machine_type
29
29
  request_count = self.request_count
30
- avg_duration = self.avg_duration
30
+ median_duration = self.median_duration
31
31
  total_duration = self.total_duration
32
32
 
33
33
  field_dict: Dict[str, Any] = {}
@@ -37,7 +37,7 @@ class GroupedUsageDetail:
37
37
  "model_id": model_id,
38
38
  "machine_type": machine_type,
39
39
  "request_count": request_count,
40
- "avg_duration": avg_duration,
40
+ "median_duration": median_duration,
41
41
  "total_duration": total_duration,
42
42
  }
43
43
  )
@@ -53,7 +53,7 @@ class GroupedUsageDetail:
53
53
 
54
54
  request_count = d.pop("request_count")
55
55
 
56
- avg_duration = d.pop("avg_duration")
56
+ median_duration = d.pop("median_duration")
57
57
 
58
58
  total_duration = d.pop("total_duration")
59
59
 
@@ -61,7 +61,7 @@ class GroupedUsageDetail:
61
61
  model_id=model_id,
62
62
  machine_type=machine_type,
63
63
  request_count=request_count,
64
- avg_duration=avg_duration,
64
+ median_duration=median_duration,
65
65
  total_duration=total_duration,
66
66
  )
67
67
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "fal"
3
- version = "0.11.3"
3
+ version = "0.11.4"
4
4
  description = "fal is an easy-to-use Serverless Python Framework"
5
5
  authors = ["Features & Labels <hello@fal.ai>"]
6
6
  readme = "README.md"
@@ -82,7 +82,7 @@ entry_points = \
82
82
 
83
83
  setup_kwargs = {
84
84
  'name': 'fal',
85
- 'version': '0.11.3',
85
+ 'version': '0.11.4',
86
86
  'description': 'fal is an easy-to-use Serverless Python Framework',
87
87
  'long_description': '# fal\n\nfal is a serverless Python runtime that lets you run and scale code in the cloud with no infra management.\n\nWith fal, you can build pipelines, serve ML models and scale them up to many users. You scale down to 0 when you don\'t use any resources.\n\n## Quickstart\n\nFirst, you need to install the `fal` package. You can do so using pip:\n```shell\npip install fal\n```\n\nThen you need to authenticate:\n```shell\nfal auth login\n```\n\nYou can also use fal keys that you can get from [our dashboard](https://fal.ai/dashboard/keys).\n\nNow can use the fal package in your Python scripts as follows:\n\n```py\nimport fal\n\n@fal.function(\n "virtualenv",\n requirements=["pyjokes"],\n)\ndef tell_joke() -> str:\n import pyjokes\n\n joke = pyjokes.get_joke()\n return joke\n\nprint("Joke from the clouds: ", tell_joke())\n```\n\nA new virtual environment will be created by fal in the cloud and the set of requirements that we passed will be installed as soon as this function is called. From that point on, our code will be executed as if it were running locally, and the joke prepared by the pyjokes library will be returned.\n\n## Next steps\n\nIf you would like to find out more about the capabilities of fal, check out to the [docs](https://fal.ai/docs). You can learn more about persistent storage, function caches and deploying your functions as API endpoints.\n',
88
88
  'author': 'Features & Labels',
@@ -6,6 +6,7 @@ from fal import apps
6
6
  from fal.api import FalServerlessHost, LocalHost, cached
7
7
  from fal.api import function
8
8
  from fal.api import function as isolated
9
+ from fal.app import App, endpoint, wrap_app
9
10
  from fal.sdk import FalServerlessKeyCredentials
10
11
  from fal.sync import sync_dir
11
12
 
@@ -15,35 +16,8 @@ serverless = FalServerlessHost()
15
16
  # DEPRECATED - use serverless instead
16
17
  cloud = FalServerlessHost()
17
18
 
18
- DBT_FAL_IMPORT_NOTICE = """
19
- The dbt tool `fal` and `dbt-fal` adapter have been merged into a single tool.
20
- Please import from the `fal.dbt` module instead.
21
- Running `pip install dbt-fal` will install the new tool and the adapter alongside.
22
- Then import from the `fal.dbt` module like
23
-
24
- from fal.dbt import {name}
25
-
26
- """
27
-
28
-
29
- # Avoid printing on non-direct imports
30
- def __getattr__(name: str):
31
- if name in (
32
- "NodeStatus",
33
- "FalDbt",
34
- "DbtModel",
35
- "DbtSource",
36
- "DbtTest",
37
- "DbtGenericTest",
38
- "DbtSingularTest",
39
- "Context",
40
- "CurrentModel",
41
- ):
42
- raise ImportError(DBT_FAL_IMPORT_NOTICE.format(name=name))
43
-
44
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
45
-
46
19
 
20
+ # NOTE: This makes `import fal.dbt` import the `dbt-fal` module and `import fal` import the `fal` module
47
21
  # NOTE: taken from dbt-core: https://github.com/dbt-labs/dbt-core/blob/ac539fd5cf325cfb5315339077d03399d575f570/core/dbt/adapters/__init__.py#L1-L7
48
22
  # N.B.
49
23
  # This will add to the package’s __path__ all subdirectories of directories on sys.path named after the package which effectively combines both modules into a single namespace (dbt.adapters)
@@ -552,6 +552,7 @@ def function(
552
552
  exposed_port: int | None = None,
553
553
  max_concurrency: int | None = None,
554
554
  # FalServerlessHost options
555
+ metadata: dict[str, Any] | None = None,
555
556
  machine_type: str = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
556
557
  keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
557
558
  max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
@@ -576,6 +577,7 @@ def function(
576
577
  exposed_port: int | None = None,
577
578
  max_concurrency: int | None = None,
578
579
  # FalServerlessHost options
580
+ metadata: dict[str, Any] | None = None,
579
581
  machine_type: str = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
580
582
  keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
581
583
  max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
@@ -652,6 +654,7 @@ def function(
652
654
  exposed_port: int | None = None,
653
655
  max_concurrency: int | None = None,
654
656
  # FalServerlessHost options
657
+ metadata: dict[str, Any] | None = None,
655
658
  machine_type: str = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
656
659
  keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
657
660
  max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
@@ -681,6 +684,7 @@ def function(
681
684
  exposed_port: int | None = None,
682
685
  max_concurrency: int | None = None,
683
686
  # FalServerlessHost options
687
+ metadata: dict[str, Any] | None = None,
684
688
  machine_type: str = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
685
689
  keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
686
690
  max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
@@ -788,7 +792,7 @@ class ServeWrapper:
788
792
  if "properties" in schema:
789
793
  mark_order(schema, "properties")
790
794
 
791
- for key in spec["components"].get("schemas") or {}:
795
+ for key in spec.get("components", {}).get("schemas") or {}:
792
796
  order_schema_object(spec["components"]["schemas"][key])
793
797
 
794
798
  return spec
@@ -0,0 +1,162 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import os
5
+ import fal.api
6
+ from fal.toolkit import mainify
7
+ from fastapi import FastAPI
8
+ from typing import Any, NamedTuple, Callable, TypeVar, ClassVar
9
+ from fal.logging import get_logger
10
+
11
+ EndpointT = TypeVar("EndpointT", bound=Callable[..., Any])
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ def wrap_app(cls: type[App], **kwargs) -> fal.api.IsolatedFunction:
16
+ def initialize_and_serve():
17
+ app = cls()
18
+ app.serve()
19
+
20
+ try:
21
+ app = cls(_allow_init=True)
22
+ metadata = app.openapi()
23
+ except Exception as exc:
24
+ logger.warning("Failed to build OpenAPI specification for %s", cls.__name__)
25
+ metadata = {}
26
+
27
+ wrapper = fal.api.function(
28
+ "virtualenv",
29
+ requirements=cls.requirements,
30
+ machine_type=cls.machine_type,
31
+ **cls.host_kwargs,
32
+ **kwargs,
33
+ metadata=metadata,
34
+ serve=True,
35
+ )
36
+ return wrapper(initialize_and_serve).on(
37
+ serve=False,
38
+ exposed_port=8080,
39
+ )
40
+
41
+
42
+ @mainify
43
+ class RouteSignature(NamedTuple):
44
+ path: str
45
+
46
+
47
+ @mainify
48
+ class App:
49
+ requirements: ClassVar[list[str]] = []
50
+ machine_type: ClassVar[str] = "S"
51
+ host_kwargs: ClassVar[dict[str, Any]] = {}
52
+
53
+ def __init_subclass__(cls, **kwargs):
54
+ cls.host_kwargs = kwargs
55
+
56
+ if cls.__init__ is not App.__init__:
57
+ raise ValueError(
58
+ "App classes should not override __init__ directly. "
59
+ "Use setup() instead."
60
+ )
61
+
62
+ def __init__(self, *, _allow_init: bool = False):
63
+ if not _allow_init and not os.getenv("IS_ISOLATE_AGENT"):
64
+ raise NotImplementedError(
65
+ "Running apps through SDK is not implemented yet."
66
+ )
67
+
68
+ def setup(self):
69
+ """Setup the application before serving."""
70
+
71
+ def serve(self) -> None:
72
+ import uvicorn
73
+
74
+ app = self._build_app()
75
+ self.setup()
76
+ uvicorn.run(app, host="0.0.0.0", port=8080)
77
+
78
+ def _build_app(self) -> FastAPI:
79
+ from fastapi import FastAPI
80
+ from fastapi.middleware.cors import CORSMiddleware
81
+
82
+ _app = FastAPI()
83
+
84
+ _app.add_middleware(
85
+ CORSMiddleware,
86
+ allow_credentials=True,
87
+ allow_headers=("*"),
88
+ allow_methods=("*"),
89
+ allow_origins=("*"),
90
+ )
91
+
92
+ routes: dict[RouteSignature, Callable[..., Any]] = {
93
+ signature: endpoint
94
+ for _, endpoint in inspect.getmembers(self, inspect.ismethod)
95
+ if (signature := getattr(endpoint, "route_signature", None))
96
+ }
97
+ if not routes:
98
+ raise ValueError("An application must have at least one route!")
99
+
100
+ for signature, endpoint in routes.items():
101
+ _app.add_api_route(
102
+ signature.path,
103
+ endpoint,
104
+ name=endpoint.__name__,
105
+ methods=["POST"],
106
+ )
107
+
108
+ return _app
109
+
110
+ def openapi(self) -> dict[str, Any]:
111
+ """
112
+ Build the OpenAPI specification for the served function.
113
+ Attach needed metadata for a better integration to fal.
114
+ """
115
+ app = self._build_app()
116
+ spec = app.openapi()
117
+ self._mark_order_openapi(spec)
118
+ return spec
119
+
120
+ def _mark_order_openapi(self, spec: dict[str, Any]):
121
+ """
122
+ Add x-fal-order-* keys to the OpenAPI specification to help the rendering of UI.
123
+
124
+ NOTE: We rely on the fact that fastapi and Python dicts keep the order of properties.
125
+ """
126
+
127
+ def mark_order(obj: dict[str, Any], key: str):
128
+ obj[f"x-fal-order-{key}"] = list(obj[key].keys())
129
+
130
+ mark_order(spec, "paths")
131
+
132
+ def order_schema_object(schema: dict[str, Any]):
133
+ """
134
+ Mark the order of properties in the schema object.
135
+ They can have 'allOf', 'properties' or '$ref' key.
136
+ """
137
+ if "allOf" in schema:
138
+ for sub_schema in schema["allOf"]:
139
+ order_schema_object(sub_schema)
140
+ if "properties" in schema:
141
+ mark_order(schema, "properties")
142
+
143
+ for key in spec["components"].get("schemas") or {}:
144
+ order_schema_object(spec["components"]["schemas"][key])
145
+
146
+ return spec
147
+
148
+
149
+ @mainify
150
+ def endpoint(path: str) -> Callable[[EndpointT], EndpointT]:
151
+ """Designate the decorated function as an application endpoint."""
152
+
153
+ def marker_fn(callable: EndpointT) -> EndpointT:
154
+ if hasattr(callable, "route_signature"):
155
+ raise ValueError(
156
+ f"Can't set multiple routes for the same function: {callable.__name__}"
157
+ )
158
+
159
+ callable.route_signature = RouteSignature(path=path) # type: ignore
160
+ return callable
161
+
162
+ return marker_fn
@@ -9,7 +9,7 @@ from uuid import uuid4
9
9
 
10
10
  import click
11
11
  import fal.auth as auth
12
- import grpc
12
+ import fal
13
13
  from fal import api, sdk
14
14
  from fal.console import console
15
15
  from fal.exceptions import ApplicationExceptionHandler
@@ -145,15 +145,6 @@ def auth_cli():
145
145
  @auth_cli.command(name="login")
146
146
  def auth_login():
147
147
  auth.login()
148
- try:
149
- client = sdk.FalServerlessClient(f"{DEFAULT_HOST}:{DEFAULT_PORT}")
150
- with client.connect() as connection:
151
- connection.list_aliases()
152
- except grpc.RpcError as e:
153
- if "Insufficient permissions" in e.details():
154
- console.print(e.details())
155
- else:
156
- raise e
157
148
 
158
149
 
159
150
  @auth_cli.command(name="logout")
@@ -244,6 +235,28 @@ def function_cli(ctx, host: str, port: str):
244
235
  ctx.obj = api.FalServerlessHost(f"{host}:{port}")
245
236
 
246
237
 
238
+ def load_function_from(
239
+ host: api.FalServerlessHost,
240
+ file_path: str,
241
+ function_name: str,
242
+ ) -> api.IsolatedFunction:
243
+ import runpy
244
+
245
+ module = runpy.run_path(file_path)
246
+ if function_name not in module:
247
+ raise api.FalServerlessError(f"Function '{function_name}' not found in module")
248
+
249
+ target = module[function_name]
250
+ if isinstance(target, type) and issubclass(target, fal.App):
251
+ target = fal.wrap_app(target, host=host)
252
+
253
+ if not isinstance(target, api.IsolatedFunction):
254
+ raise api.FalServerlessError(
255
+ f"Function '{function_name}' is not a fal.function or a fal.App"
256
+ )
257
+ return target
258
+
259
+
247
260
  @function_cli.command("serve")
248
261
  @click.option("--alias", default=None)
249
262
  @click.option(
@@ -262,15 +275,9 @@ def register_application(
262
275
  alias: str | None,
263
276
  auth_mode: ALIAS_AUTH_TYPE,
264
277
  ):
265
- import runpy
266
-
267
278
  user_id = _get_user_id()
268
279
 
269
- module = runpy.run_path(file_path)
270
- if function_name not in module:
271
- raise api.FalServerlessError(f"Function '{function_name}' not found in module")
272
-
273
- isolated_function: api.IsolatedFunction = module[function_name]
280
+ isolated_function = load_function_from(host, file_path, function_name)
274
281
  gateway_options = isolated_function.options.gateway
275
282
  if "serve" not in gateway_options and "exposed_port" not in gateway_options:
276
283
  raise api.FalServerlessError(
@@ -289,7 +296,7 @@ def register_application(
289
296
  options=isolated_function.options,
290
297
  application_name=alias,
291
298
  application_auth_mode=auth_mode,
292
- metadata={},
299
+ metadata=isolated_function.options.host.get("metadata", {}),
293
300
  )
294
301
 
295
302
  if id:
@@ -307,6 +314,15 @@ def register_application(
307
314
  console.print(f"URL: https://{user_id}-{id}.{gateway_host}")
308
315
 
309
316
 
317
+ @function_cli.command("run")
318
+ @click.argument("file_path", required=True)
319
+ @click.argument("function_name", required=True)
320
+ @click.pass_obj
321
+ def run(host: api.FalServerlessHost, file_path: str, function_name: str):
322
+ isolated_function = load_function_from(host, file_path, function_name)
323
+ isolated_function()
324
+
325
+
310
326
  @function_cli.command("logs")
311
327
  @click.option("--lines", default=100)
312
328
  @click.option("--url", default=None)
@@ -520,30 +536,6 @@ def remove_http_and_port_from_url(url):
520
536
  return url
521
537
 
522
538
 
523
- # dbt-fal commands to be errored out
524
- DBT_FAL_COMMAND_NOTICE = """
525
- The dbt tool `fal` and `dbt-fal` adapter have been merged into a single tool.
526
- Please use the new `dbt-fal` command line tool instead.
527
- Running `pip install dbt-fal` will install the new tool and the adapter alongside.
528
- Then run your command like
529
-
530
- dbt-fal <command>
531
-
532
- """
533
-
534
-
535
- @cli.command("run", context_settings={"ignore_unknown_options": True})
536
- @click.argument("any", nargs=-1, type=click.UNPROCESSED)
537
- def dbt_run(any):
538
- raise click.BadArgumentUsage(DBT_FAL_COMMAND_NOTICE)
539
-
540
-
541
- @cli.command("flow", context_settings={"ignore_unknown_options": True})
542
- @click.argument("any", nargs=-1, type=click.UNPROCESSED)
543
- def dbt_flow(any):
544
- raise click.BadArgumentUsage(DBT_FAL_COMMAND_NOTICE)
545
-
546
-
547
539
  def _get_user_id() -> str:
548
540
  try:
549
541
  user_details_response = get_user_details.sync_detailed(
@@ -14,4 +14,4 @@ class FalServerlessException(Exception):
14
14
  super().__init__(message)
15
15
 
16
16
  def __str__(self) -> str:
17
- return self.message
17
+ return self.message + (f"\nHint: {self.hint}" if self.hint else "")
@@ -9,5 +9,5 @@ class UnauthenticatedException(FalServerlessException):
9
9
  def __init__(self) -> None:
10
10
  super().__init__(
11
11
  message="You must be authenticated.",
12
- hint="Login via `fal auth login`",
12
+ hint="Login via `fal auth login` or make sure to setup fal keys correctly.",
13
13
  )
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