Flowfile 0.3.5__py3-none-any.whl → 0.3.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (145) hide show
  1. flowfile/__init__.py +27 -6
  2. flowfile/api.py +1 -0
  3. flowfile/web/__init__.py +2 -2
  4. flowfile/web/static/assets/CloudConnectionManager-2dfdce2f.css +86 -0
  5. flowfile/web/static/assets/CloudConnectionManager-c20a740f.js +783 -0
  6. flowfile/web/static/assets/CloudStorageReader-29d14fcc.css +143 -0
  7. flowfile/web/static/assets/CloudStorageReader-960b400a.js +437 -0
  8. flowfile/web/static/assets/CloudStorageWriter-49c9a4b2.css +138 -0
  9. flowfile/web/static/assets/CloudStorageWriter-e3decbdd.js +430 -0
  10. flowfile/web/static/assets/{CrossJoin-dfcf7351.js → CrossJoin-d67e2405.js} +8 -8
  11. flowfile/web/static/assets/{DatabaseConnectionSettings-b2afb1d7.js → DatabaseConnectionSettings-a81e0f7e.js} +2 -2
  12. flowfile/web/static/assets/{DatabaseManager-824a49b2.js → DatabaseManager-9ea35e84.js} +2 -2
  13. flowfile/web/static/assets/{DatabaseReader-a48124d8.js → DatabaseReader-9578bfa5.js} +9 -9
  14. flowfile/web/static/assets/{DatabaseWriter-b47cbae2.js → DatabaseWriter-19531098.js} +9 -9
  15. flowfile/web/static/assets/{ExploreData-fdfc45a4.js → ExploreData-40476474.js} +47141 -43697
  16. flowfile/web/static/assets/{ExternalSource-861b0e71.js → ExternalSource-2297ef96.js} +6 -6
  17. flowfile/web/static/assets/{Filter-f87bb897.js → Filter-f211c03a.js} +8 -8
  18. flowfile/web/static/assets/{Formula-b8cefc31.css → Formula-29f19d21.css} +10 -0
  19. flowfile/web/static/assets/{Formula-1e2ed720.js → Formula-4207ea31.js} +75 -9
  20. flowfile/web/static/assets/{FuzzyMatch-b6cc4fdd.js → FuzzyMatch-bf120df0.js} +9 -9
  21. flowfile/web/static/assets/{GraphSolver-6a371f4c.js → GraphSolver-5bb7497a.js} +5 -5
  22. flowfile/web/static/assets/{GroupBy-f7b7f472.js → GroupBy-92c81b65.js} +6 -6
  23. flowfile/web/static/assets/{Join-eec38203.js → Join-4e49a274.js} +23 -15
  24. flowfile/web/static/assets/{Join-41c0f331.css → Join-f45eff22.css} +20 -20
  25. flowfile/web/static/assets/{ManualInput-9aaa46fb.js → ManualInput-90998ae8.js} +106 -34
  26. flowfile/web/static/assets/{ManualInput-ac7b9972.css → ManualInput-a71b52c6.css} +29 -17
  27. flowfile/web/static/assets/{Output-3b2ca045.js → Output-81e3e917.js} +4 -4
  28. flowfile/web/static/assets/{Pivot-a4f5d88f.js → Pivot-a3419842.js} +6 -6
  29. flowfile/web/static/assets/{PolarsCode-49ce444f.js → PolarsCode-72710deb.js} +6 -6
  30. flowfile/web/static/assets/{Read-07acdc9a.js → Read-c4059daf.js} +6 -6
  31. flowfile/web/static/assets/{RecordCount-6a21da56.js → RecordCount-c2b5e095.js} +5 -5
  32. flowfile/web/static/assets/{RecordId-949bdc17.js → RecordId-10baf191.js} +6 -6
  33. flowfile/web/static/assets/{Sample-7afca6e1.js → Sample-3ed9a0ae.js} +5 -5
  34. flowfile/web/static/assets/{SecretManager-b41c029d.js → SecretManager-0d49c0e8.js} +2 -2
  35. flowfile/web/static/assets/{Select-32b28406.js → Select-8a02a0b3.js} +8 -8
  36. flowfile/web/static/assets/{SettingsSection-a0f15a05.js → SettingsSection-4c0f45f5.js} +1 -1
  37. flowfile/web/static/assets/{Sort-fc6ba0e2.js → Sort-f55c9f9d.js} +6 -6
  38. flowfile/web/static/assets/{TextToRows-23127596.js → TextToRows-5dbc2145.js} +8 -8
  39. flowfile/web/static/assets/{UnavailableFields-c42880a3.js → UnavailableFields-a1768e52.js} +2 -2
  40. flowfile/web/static/assets/{Union-39eecc6c.js → Union-f2aefdc9.js} +5 -5
  41. flowfile/web/static/assets/{Unique-a0e8fe61.js → Unique-46b250da.js} +8 -8
  42. flowfile/web/static/assets/{Unpivot-1e2d43f0.js → Unpivot-25ac84cc.js} +5 -5
  43. flowfile/web/static/assets/api-6ef0dcef.js +80 -0
  44. flowfile/web/static/assets/{api-44ca9e9c.js → api-a0abbdc7.js} +1 -1
  45. flowfile/web/static/assets/cloud_storage_reader-aa1415d6.png +0 -0
  46. flowfile/web/static/assets/{designer-267d44f1.js → designer-13eabd83.js} +36 -34
  47. flowfile/web/static/assets/{documentation-6c0810a2.js → documentation-b87e7f6f.js} +1 -1
  48. flowfile/web/static/assets/{dropDown-52790b15.js → dropDown-13564764.js} +1 -1
  49. flowfile/web/static/assets/{fullEditor-e272b506.js → fullEditor-fd2cd6f9.js} +2 -2
  50. flowfile/web/static/assets/{genericNodeSettings-4bdcf98e.js → genericNodeSettings-71e11604.js} +3 -3
  51. flowfile/web/static/assets/{index-e235a8bc.js → index-f6c15e76.js} +59 -22
  52. flowfile/web/static/assets/{nodeTitle-fc3fc4b7.js → nodeTitle-988d9efe.js} +3 -3
  53. flowfile/web/static/assets/{secretApi-cdc2a3fd.js → secretApi-dd636aa2.js} +1 -1
  54. flowfile/web/static/assets/{selectDynamic-96aa82cd.js → selectDynamic-af36165e.js} +3 -3
  55. flowfile/web/static/assets/{vue-codemirror.esm-25e75a08.js → vue-codemirror.esm-2847001e.js} +2 -1
  56. flowfile/web/static/assets/{vue-content-loader.es-6c4b1c24.js → vue-content-loader.es-0371da73.js} +1 -1
  57. flowfile/web/static/index.html +1 -1
  58. {flowfile-0.3.5.dist-info → flowfile-0.3.7.dist-info}/METADATA +9 -4
  59. {flowfile-0.3.5.dist-info → flowfile-0.3.7.dist-info}/RECORD +131 -124
  60. {flowfile-0.3.5.dist-info → flowfile-0.3.7.dist-info}/entry_points.txt +2 -0
  61. flowfile_core/__init__.py +3 -0
  62. flowfile_core/auth/jwt.py +39 -0
  63. flowfile_core/configs/node_store/nodes.py +9 -6
  64. flowfile_core/configs/settings.py +6 -5
  65. flowfile_core/database/connection.py +63 -15
  66. flowfile_core/database/init_db.py +0 -1
  67. flowfile_core/database/models.py +49 -2
  68. flowfile_core/flowfile/code_generator/code_generator.py +472 -17
  69. flowfile_core/flowfile/connection_manager/models.py +1 -1
  70. flowfile_core/flowfile/database_connection_manager/db_connections.py +216 -2
  71. flowfile_core/flowfile/extensions.py +1 -1
  72. flowfile_core/flowfile/flow_data_engine/cloud_storage_reader.py +259 -0
  73. flowfile_core/flowfile/flow_data_engine/create/funcs.py +19 -8
  74. flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +1062 -311
  75. flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +12 -2
  76. flowfile_core/flowfile/flow_data_engine/fuzzy_matching/settings_validator.py +1 -1
  77. flowfile_core/flowfile/flow_data_engine/join/__init__.py +2 -1
  78. flowfile_core/flowfile/flow_data_engine/join/utils.py +25 -0
  79. flowfile_core/flowfile/flow_data_engine/polars_code_parser.py +3 -1
  80. flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +29 -22
  81. flowfile_core/flowfile/flow_data_engine/utils.py +1 -40
  82. flowfile_core/flowfile/flow_graph.py +718 -253
  83. flowfile_core/flowfile/flow_graph_utils.py +2 -2
  84. flowfile_core/flowfile/flow_node/flow_node.py +563 -117
  85. flowfile_core/flowfile/flow_node/models.py +154 -20
  86. flowfile_core/flowfile/flow_node/schema_callback.py +3 -2
  87. flowfile_core/flowfile/handler.py +2 -33
  88. flowfile_core/flowfile/manage/open_flowfile.py +1 -2
  89. flowfile_core/flowfile/sources/external_sources/__init__.py +0 -2
  90. flowfile_core/flowfile/sources/external_sources/factory.py +4 -7
  91. flowfile_core/flowfile/util/calculate_layout.py +0 -2
  92. flowfile_core/flowfile/utils.py +35 -26
  93. flowfile_core/main.py +35 -15
  94. flowfile_core/routes/cloud_connections.py +77 -0
  95. flowfile_core/routes/logs.py +2 -7
  96. flowfile_core/routes/public.py +1 -0
  97. flowfile_core/routes/routes.py +130 -90
  98. flowfile_core/routes/secrets.py +72 -14
  99. flowfile_core/schemas/__init__.py +8 -0
  100. flowfile_core/schemas/cloud_storage_schemas.py +215 -0
  101. flowfile_core/schemas/input_schema.py +121 -71
  102. flowfile_core/schemas/output_model.py +19 -3
  103. flowfile_core/schemas/schemas.py +150 -12
  104. flowfile_core/schemas/transform_schema.py +175 -35
  105. flowfile_core/utils/utils.py +40 -1
  106. flowfile_core/utils/validate_setup.py +41 -0
  107. flowfile_frame/__init__.py +9 -1
  108. flowfile_frame/cloud_storage/frame_helpers.py +39 -0
  109. flowfile_frame/cloud_storage/secret_manager.py +73 -0
  110. flowfile_frame/expr.py +28 -1
  111. flowfile_frame/expr.pyi +76 -61
  112. flowfile_frame/flow_frame.py +481 -208
  113. flowfile_frame/flow_frame.pyi +140 -91
  114. flowfile_frame/flow_frame_methods.py +160 -22
  115. flowfile_frame/group_frame.py +3 -0
  116. flowfile_frame/utils.py +25 -3
  117. flowfile_worker/external_sources/s3_source/main.py +216 -0
  118. flowfile_worker/external_sources/s3_source/models.py +142 -0
  119. flowfile_worker/funcs.py +51 -6
  120. flowfile_worker/models.py +22 -2
  121. flowfile_worker/routes.py +40 -38
  122. flowfile_worker/utils.py +1 -1
  123. test_utils/s3/commands.py +46 -0
  124. test_utils/s3/data_generator.py +292 -0
  125. test_utils/s3/demo_data_generator.py +186 -0
  126. test_utils/s3/fixtures.py +214 -0
  127. flowfile/web/static/assets/AirbyteReader-1ac35765.css +0 -314
  128. flowfile/web/static/assets/AirbyteReader-e08044e5.js +0 -922
  129. flowfile/web/static/assets/dropDownGeneric-60f56a8a.js +0 -72
  130. flowfile/web/static/assets/dropDownGeneric-895680d6.css +0 -10
  131. flowfile_core/flowfile/sources/external_sources/airbyte_sources/airbyte.py +0 -159
  132. flowfile_core/flowfile/sources/external_sources/airbyte_sources/models.py +0 -172
  133. flowfile_core/flowfile/sources/external_sources/airbyte_sources/settings.py +0 -173
  134. flowfile_core/schemas/defaults.py +0 -9
  135. flowfile_core/schemas/external_sources/airbyte_schemas.py +0 -20
  136. flowfile_core/schemas/models.py +0 -193
  137. flowfile_worker/external_sources/airbyte_sources/cache_manager.py +0 -161
  138. flowfile_worker/external_sources/airbyte_sources/main.py +0 -89
  139. flowfile_worker/external_sources/airbyte_sources/models.py +0 -133
  140. flowfile_worker/external_sources/airbyte_sources/settings.py +0 -0
  141. {flowfile-0.3.5.dist-info → flowfile-0.3.7.dist-info}/LICENSE +0 -0
  142. {flowfile-0.3.5.dist-info → flowfile-0.3.7.dist-info}/WHEEL +0 -0
  143. {flowfile_core/flowfile/sources/external_sources/airbyte_sources → flowfile_frame/cloud_storage}/__init__.py +0 -0
  144. {flowfile_core/schemas/external_sources → flowfile_worker/external_sources/s3_source}/__init__.py +0 -0
  145. {flowfile_worker/external_sources/airbyte_sources → test_utils/s3}/__init__.py +0 -0
@@ -1,5 +1,10 @@
1
- # app_routes/secrets.py
1
+ """
2
+ Manages CRUD (Create, Read, Update, Delete) operations for secrets.
2
3
 
4
+ This router provides secure endpoints for creating, retrieving, and deleting
5
+ sensitive credentials for the authenticated user. Secrets are encrypted before
6
+ being stored and are associated with the user's ID.
7
+ """
3
8
  import os
4
9
  from typing import List
5
10
 
@@ -10,20 +15,31 @@ from flowfile_core.auth.jwt import get_current_active_user
10
15
  from flowfile_core.auth.models import Secret, SecretInput
11
16
  from flowfile_core.database import models as db_models
12
17
  from flowfile_core.database.connection import get_db
13
- from flowfile_core.secret_manager.secret_manager import encrypt_secret, store_secret, delete_secret as delete_secret_action
18
+ from flowfile_core.secret_manager.secret_manager import store_secret, delete_secret as delete_secret_action
14
19
 
15
20
  router = APIRouter(dependencies=[Depends(get_current_active_user)])
16
21
 
17
22
 
18
- # Get all secrets for current user
19
23
  @router.get("/secrets", response_model=List[Secret])
20
24
  async def get_secrets(current_user=Depends(get_current_active_user), db: Session = Depends(get_db)):
25
+ """Retrieves all secret names for the currently authenticated user.
26
+
27
+ Note: This endpoint returns the secret names and metadata but does not
28
+ expose the decrypted secret values.
29
+
30
+ Args:
31
+ current_user: The authenticated user object, injected by FastAPI.
32
+ db: The database session, injected by FastAPI.
33
+
34
+ Returns:
35
+ A list of `Secret` objects, each containing the name and encrypted value.
36
+ """
21
37
  user_id = current_user.id
22
38
 
23
39
  # Get secrets from database
24
40
  db_secrets = db.query(db_models.Secret).filter(db_models.Secret.user_id == user_id).all()
25
41
 
26
- # Decrypt secrets
42
+ # Prepare response model (without decrypting)
27
43
  secrets = []
28
44
  for db_secret in db_secrets:
29
45
  secrets.append(Secret(
@@ -35,13 +51,27 @@ async def get_secrets(current_user=Depends(get_current_active_user), db: Session
35
51
  return secrets
36
52
 
37
53
 
38
- # Create a new secret
39
54
  @router.post("/secrets", response_model=Secret)
40
55
  async def create_secret(secret: SecretInput, current_user=Depends(get_current_active_user),
41
- db: Session = Depends(get_db)):
42
- print('current_user', current_user)
56
+ db: Session = Depends(get_db)) -> Secret:
57
+ """Creates a new secret for the authenticated user.
58
+
59
+ The secret value is encrypted before being stored in the database. A secret
60
+ name must be unique for a given user.
61
+
62
+ Args:
63
+ secret: A `SecretInput` object containing the name and plaintext value of the secret.
64
+ current_user: The authenticated user object, injected by FastAPI.
65
+ db: The database session, injected by FastAPI.
66
+
67
+ Raises:
68
+ HTTPException: 400 if a secret with the same name already exists for the user.
69
+
70
+ Returns:
71
+ A `Secret` object containing the name and the *encrypted* value.
72
+ """
43
73
  # Get user ID
44
- user_id = 1 if os.environ.get("FLOWFILE_MODE") == "electron" or 1 == 1 else current_user.id
74
+ user_id = 1 if os.environ.get("FLOWFILE_MODE") == "electron" else current_user.id
45
75
 
46
76
  existing_secret = db.query(db_models.Secret).filter(
47
77
  db_models.Secret.user_id == user_id,
@@ -51,13 +81,30 @@ async def create_secret(secret: SecretInput, current_user=Depends(get_current_ac
51
81
  if existing_secret:
52
82
  raise HTTPException(status_code=400, detail="Secret with this name already exists")
53
83
 
54
- encrypted_value = store_secret(db, secret, user_id).encrypted_value
55
- return Secret(name=secret.name, value=encrypted_value, user_id=str(user_id))
84
+ # The store_secret function handles encryption and DB storage
85
+ stored_secret = store_secret(db, secret, user_id)
86
+ return Secret(name=stored_secret.name, value=stored_secret.encrypted_value, user_id=str(user_id))
56
87
 
57
88
 
58
- # Get a specific secret by name
59
89
  @router.get("/secrets/{secret_name}", response_model=Secret)
60
- async def get_secret(secret_name: str, current_user=Depends(get_current_active_user), db: Session = Depends(get_db)):
90
+ async def get_secret(secret_name: str,
91
+ current_user=Depends(get_current_active_user), db: Session = Depends(get_db)) -> Secret:
92
+ """Retrieves a specific secret by name for the authenticated user.
93
+
94
+ Note: This endpoint returns the secret name and metadata but does not
95
+ expose the decrypted secret value.
96
+
97
+ Args:
98
+ secret_name: The name of the secret to retrieve.
99
+ current_user: The authenticated user object, injected by FastAPI.
100
+ db: The database session, injected by FastAPI.
101
+
102
+ Raises:
103
+ HTTPException: 404 if the secret is not found.
104
+
105
+ Returns:
106
+ A `Secret` object containing the name and encrypted value.
107
+ """
61
108
  # Get user ID
62
109
  user_id = 1 if os.environ.get("FLOWFILE_MODE") == "electron" else current_user.id
63
110
 
@@ -78,8 +125,19 @@ async def get_secret(secret_name: str, current_user=Depends(get_current_active_u
78
125
 
79
126
 
80
127
  @router.delete("/secrets/{secret_name}", status_code=204)
81
- async def delete_secret(secret_name: str, current_user=Depends(get_current_active_user), db: Session = Depends(get_db)):
128
+ async def delete_secret(secret_name: str, current_user=Depends(get_current_active_user),
129
+ db: Session = Depends(get_db)) -> None:
130
+ """Deletes a secret by name for the authenticated user.
131
+
132
+ Args:
133
+ secret_name: The name of the secret to delete.
134
+ current_user: The authenticated user object, injected by FastAPI.
135
+ db: The database session, injected by FastAPI.
136
+
137
+ Returns:
138
+ An empty response with a 204 No Content status code upon success.
139
+ """
82
140
  # Get user ID
83
- user_id = 1 if os.environ.get("FLOWFILE_MODE") == "electron" or 1 == 1 else current_user.id
141
+ user_id = 1 if os.environ.get("FLOWFILE_MODE") == "electron" else current_user.id
84
142
  delete_secret_action(db, secret_name, user_id)
85
143
  return None
@@ -0,0 +1,8 @@
1
+ from flowfile_core.schemas import input_schema as node_interface, transform_schema as transformation_settings
2
+ from flowfile_core.schemas.schemas import FlowSettings, FlowInformation
3
+ from flowfile_core.schemas.input_schema import RawData
4
+
5
+
6
+ __all__ = [
7
+ "transformation_settings", "node_interface", "FlowSettings", "FlowInformation", "RawData"
8
+ ]
@@ -0,0 +1,215 @@
1
+ """Cloud storage connection schemas for S3, ADLS, and other cloud providers."""
2
+
3
+ from typing import Optional, Literal
4
+ import polars as pl
5
+ import base64
6
+
7
+ from pydantic import BaseModel, SecretStr, field_validator, Field
8
+
9
+ from flowfile_core.secret_manager.secret_manager import encrypt_secret
10
+
11
+ CloudStorageType = Literal["s3", "adls", "gcs"]
12
+ AuthMethod = Literal["access_key", "iam_role", "service_principal", "managed_identity", "sas_token", "aws-cli", "env_vars"]
13
+
14
+
15
+ def encrypt_for_worker(secret_value: SecretStr|None) -> str|None:
16
+ """
17
+ Encrypts a secret value for use in worker contexts.
18
+ This is a placeholder function that simulates encryption.
19
+ In practice, you would use a secure encryption method.
20
+ """
21
+ if secret_value is not None:
22
+ return encrypt_secret(secret_value.get_secret_value())
23
+
24
+
25
+ class AuthSettingsInput(BaseModel):
26
+ """
27
+ The information needed for the user to provide the details that are needed to provide how to connect to the
28
+ Cloud provider
29
+ """
30
+ storage_type: CloudStorageType
31
+ auth_method: AuthMethod
32
+ connection_name: Optional[str] = "None" # This is the reference to the item we will fetch that contains the data
33
+
34
+
35
+ class FullCloudStorageConnectionWorkerInterface(AuthSettingsInput):
36
+ """Internal model with decrypted secrets"""
37
+
38
+ # AWS S3
39
+ aws_region: Optional[str] = None
40
+ aws_access_key_id: Optional[str] = None
41
+ aws_secret_access_key: Optional[str] = None
42
+ aws_role_arn: Optional[str] = None
43
+ aws_allow_unsafe_html: Optional[bool] = None
44
+ aws_session_token: Optional[str] = None
45
+
46
+ # Azure ADLS
47
+ azure_account_name: Optional[str] = None
48
+ azure_account_key: Optional[str] = None
49
+ azure_tenant_id: Optional[str] = None
50
+ azure_client_id: Optional[str] = None
51
+ azure_client_secret: Optional[str] = None
52
+
53
+ # Common
54
+ endpoint_url: Optional[str] = None
55
+ verify_ssl: bool = True
56
+
57
+
58
+ class FullCloudStorageConnection(AuthSettingsInput):
59
+ """Internal model with decrypted secrets"""
60
+
61
+ # AWS S3
62
+ aws_region: Optional[str] = None
63
+ aws_access_key_id: Optional[str] = None
64
+ aws_secret_access_key: Optional[SecretStr] = None
65
+ aws_role_arn: Optional[str] = None
66
+ aws_allow_unsafe_html: Optional[bool] = None
67
+ aws_session_token: Optional[SecretStr] = None
68
+
69
+ # Azure ADLS
70
+ azure_account_name: Optional[str] = None
71
+ azure_account_key: Optional[SecretStr] = None
72
+ azure_tenant_id: Optional[str] = None
73
+ azure_client_id: Optional[str] = None
74
+ azure_client_secret: Optional[SecretStr] = None
75
+
76
+ # Common
77
+ endpoint_url: Optional[str] = None
78
+ verify_ssl: bool = True
79
+
80
+ def get_worker_interface(self) -> "FullCloudStorageConnectionWorkerInterface":
81
+ """
82
+ Convert to a public interface model without secrets.
83
+ """
84
+ return FullCloudStorageConnectionWorkerInterface(
85
+ storage_type=self.storage_type,
86
+ auth_method=self.auth_method,
87
+ connection_name=self.connection_name,
88
+ aws_allow_unsafe_html=self.aws_allow_unsafe_html,
89
+ aws_secret_access_key=encrypt_for_worker(self.aws_secret_access_key),
90
+ aws_region=self.aws_region,
91
+ aws_access_key_id=self.aws_access_key_id,
92
+ aws_role_arn=self.aws_role_arn,
93
+ aws_session_token=encrypt_for_worker(self.aws_session_token),
94
+ azure_account_name=self.azure_account_name,
95
+ azure_tenant_id=self.azure_tenant_id,
96
+ azure_account_key=encrypt_for_worker(self.azure_account_key),
97
+ azure_client_id=self.azure_client_id,
98
+ azure_client_secret=encrypt_for_worker(self.azure_client_secret),
99
+ endpoint_url=self.endpoint_url,
100
+ verify_ssl=self.verify_ssl
101
+ )
102
+
103
+
104
+ class FullCloudStorageConnectionInterface(AuthSettingsInput):
105
+ """API response model - no secrets exposed"""
106
+
107
+ # Public fields only
108
+ aws_allow_unsafe_html: Optional[bool] = None
109
+ aws_region: Optional[str] = None
110
+ aws_access_key_id: Optional[str] = None
111
+ aws_role_arn: Optional[str] = None
112
+ azure_account_name: Optional[str] = None
113
+ azure_tenant_id: Optional[str] = None
114
+ azure_client_id: Optional[str] = None
115
+ endpoint_url: Optional[str] = None
116
+ verify_ssl: bool = True
117
+
118
+
119
+ class CloudStorageSettings(BaseModel):
120
+ """Settings for cloud storage nodes in the visual designer"""
121
+
122
+ auth_mode: AuthMethod = "auto"
123
+ connection_name: Optional[str] = None # Required only for 'reference' mode
124
+ resource_path: str # s3://bucket/path/to/file.csv
125
+
126
+ @field_validator("auth_mode", mode="after")
127
+ def validate_auth_requirements(cls, v, values):
128
+ data = values.data
129
+ if v == "reference" and not data.get("connection_name"):
130
+ raise ValueError("connection_name required when using reference mode")
131
+ return v
132
+
133
+
134
+ class CloudStorageReadSettings(CloudStorageSettings):
135
+ """Settings for reading from cloud storage"""
136
+
137
+ scan_mode: Literal["single_file", "directory"] = "single_file"
138
+ file_format: Literal["csv", "parquet", "json", "delta", "iceberg"] = "parquet"
139
+ # CSV specific options
140
+ csv_has_header: Optional[bool] = True
141
+ csv_delimiter: Optional[str] = ","
142
+ csv_encoding: Optional[str] = "utf8"
143
+ # Deltalake specific settings
144
+ delta_version: Optional[int] = None
145
+
146
+
147
+ class CloudStorageReadSettingsInternal(BaseModel):
148
+ read_settings: CloudStorageReadSettings
149
+ connection: FullCloudStorageConnection
150
+
151
+
152
+ class WriteSettingsWorkerInterface(BaseModel):
153
+ """Settings for writing to cloud storage"""
154
+ resource_path: str # s3://bucket/path/to/file.csv
155
+
156
+ write_mode: Literal["overwrite", "append"] = "overwrite"
157
+ file_format: Literal["csv", "parquet", "json", "delta"] = "parquet"
158
+
159
+ parquet_compression: Literal["snappy", "gzip", "brotli", "lz4", "zstd"] = "snappy"
160
+
161
+ csv_delimiter: str = ","
162
+ csv_encoding: str = "utf8"
163
+
164
+
165
+ class CloudStorageWriteSettings(CloudStorageSettings, WriteSettingsWorkerInterface):
166
+ """Settings for writing to cloud storage"""
167
+ pass
168
+
169
+ def get_write_setting_worker_interface(self) -> WriteSettingsWorkerInterface:
170
+ """
171
+ Convert to a worker interface model without secrets.
172
+ """
173
+ return WriteSettingsWorkerInterface(
174
+ resource_path=self.resource_path,
175
+ write_mode=self.write_mode,
176
+ file_format=self.file_format,
177
+ parquet_compression=self.parquet_compression,
178
+ csv_delimiter=self.csv_delimiter,
179
+ csv_encoding=self.csv_encoding
180
+ )
181
+
182
+
183
+ class CloudStorageWriteSettingsInternal(BaseModel):
184
+ write_settings: CloudStorageWriteSettings
185
+ connection: FullCloudStorageConnection
186
+
187
+
188
+ class CloudStorageWriteSettingsWorkerInterface(BaseModel):
189
+ """Settings for writing to cloud storage in worker context"""
190
+ operation: str
191
+ write_settings: WriteSettingsWorkerInterface
192
+ connection: FullCloudStorageConnectionWorkerInterface
193
+ flowfile_flow_id: int = 1
194
+ flowfile_node_id: int | str = -1
195
+
196
+
197
+ def get_cloud_storage_write_settings_worker_interface(
198
+ write_settings: CloudStorageWriteSettings,
199
+ connection: FullCloudStorageConnection,
200
+ lf: pl.LazyFrame,
201
+ flowfile_flow_id: int = 1,
202
+ flowfile_node_id: int | str = -1,
203
+ ) -> CloudStorageWriteSettingsWorkerInterface:
204
+ """
205
+ Convert to a worker interface model with hashed secrets.
206
+ """
207
+ operation = base64.b64encode(lf.serialize()).decode()
208
+
209
+ return CloudStorageWriteSettingsWorkerInterface(
210
+ operation=operation,
211
+ write_settings=write_settings.get_write_setting_worker_interface(),
212
+ connection=connection.get_worker_interface(),
213
+ flowfile_flow_id=flowfile_flow_id, # Default value, can be overridden
214
+ flowfile_node_id=flowfile_node_id # Default value, can be overridden
215
+ )