salesforce-data-customcode 0.1.23__tar.gz → 0.1.25__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.
Files changed (68) hide show
  1. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/PKG-INFO +1 -1
  2. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/pyproject.toml +1 -1
  3. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/cli.py +30 -13
  4. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/deploy.py +86 -2
  5. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/run.py +4 -2
  6. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/LICENSE.txt +0 -0
  7. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/README.md +0 -0
  8. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/__init__.py +0 -0
  9. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/auth.py +0 -0
  10. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/client.py +0 -0
  11. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/cmd.py +0 -0
  12. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/config.py +0 -0
  13. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/config.yaml +0 -0
  14. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/credentials.py +0 -0
  15. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/file/__init__.py +0 -0
  16. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/file/base.py +0 -0
  17. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/file/path/__init__.py +0 -0
  18. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/file/path/default.py +0 -0
  19. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/io/__init__.py +0 -0
  20. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/io/base.py +0 -0
  21. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/io/reader/__init__.py +0 -0
  22. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/io/reader/base.py +0 -0
  23. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/io/reader/query_api.py +0 -0
  24. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/io/reader/sf_cli.py +0 -0
  25. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/io/reader/utils.py +0 -0
  26. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/io/writer/__init__.py +0 -0
  27. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/io/writer/base.py +0 -0
  28. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/io/writer/csv.py +0 -0
  29. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/io/writer/print.py +0 -0
  30. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/mixin.py +0 -0
  31. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/proxy/__init__.py +0 -0
  32. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/proxy/base.py +0 -0
  33. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/proxy/client/LocalProxyClientProvider.py +0 -0
  34. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/proxy/client/__init__.py +0 -0
  35. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/proxy/client/base.py +0 -0
  36. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/py.typed +0 -0
  37. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/scan.py +0 -0
  38. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/spark/__init__.py +0 -0
  39. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/spark/base.py +0 -0
  40. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/spark/default.py +0 -0
  41. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/template.py +0 -0
  42. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/function/.devcontainer/devcontainer.json +0 -0
  43. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/function/Dockerfile +0 -0
  44. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/function/Dockerfile.dependencies +0 -0
  45. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/function/README.md +0 -0
  46. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/function/account.ipynb +0 -0
  47. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/function/build_native_dependencies.sh +0 -0
  48. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/function/examples/employee_hierarchy/employee_data.csv +0 -0
  49. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/function/examples/employee_hierarchy/entrypoint.py +0 -0
  50. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/function/jupyterlab.sh +0 -0
  51. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/function/payload/config.json +0 -0
  52. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/function/payload/entrypoint.py +0 -0
  53. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/function/requirements-dev.txt +0 -0
  54. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/function/requirements.txt +0 -0
  55. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/script/.devcontainer/devcontainer.json +0 -0
  56. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/script/Dockerfile +0 -0
  57. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/script/Dockerfile.dependencies +0 -0
  58. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/script/README.md +0 -0
  59. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/script/account.ipynb +0 -0
  60. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/script/build_native_dependencies.sh +0 -0
  61. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/script/examples/employee_hierarchy/employee_data.csv +0 -0
  62. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/script/examples/employee_hierarchy/entrypoint.py +0 -0
  63. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/script/jupyterlab.sh +0 -0
  64. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/script/payload/config.json +0 -0
  65. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/script/payload/entrypoint.py +0 -0
  66. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/script/requirements-dev.txt +0 -0
  67. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/templates/script/requirements.txt +0 -0
  68. {salesforce_data_customcode-0.1.23 → salesforce_data_customcode-0.1.25}/src/datacustomcode/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: salesforce-data-customcode
3
- Version: 0.1.23
3
+ Version: 0.1.25
4
4
  Summary: Data Cloud Custom Code SDK
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE.txt
@@ -18,7 +18,7 @@ license = "Apache-2.0"
18
18
  name = "salesforce-data-customcode"
19
19
  readme = "README.md"
20
20
  requires-python = ">=3.10,<3.12"
21
- version = "0.1.23"
21
+ version = "0.1.25"
22
22
 
23
23
  [tool.black]
24
24
  exclude = '''
@@ -160,6 +160,11 @@ def zip(path: str, network: str):
160
160
  Choose based on your workload requirements.""",
161
161
  )
162
162
  @click.option("--function-invoke-opt")
163
+ @click.option(
164
+ "--sf-cli-org",
165
+ default=None,
166
+ help="SF CLI org alias or username. Fetches credentials via `sf org display`.",
167
+ )
163
168
  def deploy(
164
169
  path: str,
165
170
  name: str,
@@ -169,15 +174,19 @@ def deploy(
169
174
  profile: str,
170
175
  network: str,
171
176
  function_invoke_opt: str,
177
+ sf_cli_org: Optional[str],
172
178
  ):
173
179
  from datacustomcode.credentials import Credentials
174
- from datacustomcode.deploy import CodeExtensionMetadata, deploy_full
180
+ from datacustomcode.deploy import (
181
+ COMPUTE_TYPES,
182
+ AccessTokenResponse,
183
+ CodeExtensionMetadata,
184
+ _retrieve_access_token_from_sf_cli,
185
+ deploy_full,
186
+ )
175
187
 
176
188
  logger.debug("Deploying project")
177
189
 
178
- # Validate compute type
179
- from datacustomcode.deploy import COMPUTE_TYPES
180
-
181
190
  if cpu_size not in COMPUTE_TYPES.keys():
182
191
  click.secho(
183
192
  f"Error: Invalid CPU size '{cpu_size}'. "
@@ -208,15 +217,23 @@ def deploy(
208
217
  function_invoke_options = function_invoke_opt.split(",")
209
218
  metadata.functionInvokeOptions = function_invoke_options
210
219
 
211
- try:
212
- credentials = Credentials.from_available(profile=profile)
213
- except ValueError as e:
214
- click.secho(
215
- f"Error: {e}",
216
- fg="red",
217
- )
218
- raise click.Abort() from None
219
- deploy_full(path, metadata, credentials, network)
220
+ auth: Union[Credentials, AccessTokenResponse]
221
+ if sf_cli_org:
222
+ try:
223
+ auth = _retrieve_access_token_from_sf_cli(sf_cli_org)
224
+ except RuntimeError as e:
225
+ click.secho(f"Error: {e}", fg="red")
226
+ raise click.Abort() from None
227
+ else:
228
+ try:
229
+ auth = Credentials.from_available(profile=profile)
230
+ except ValueError as e:
231
+ click.secho(
232
+ f"Error: {e}",
233
+ fg="red",
234
+ )
235
+ raise click.Abort() from None
236
+ deploy_full(path, metadata, auth, network)
220
237
 
221
238
 
222
239
  @cli.command()
@@ -17,7 +17,9 @@ from __future__ import annotations
17
17
  from html import unescape
18
18
  import json
19
19
  import os
20
+ import re
20
21
  import shutil
22
+ import subprocess
21
23
  import tempfile
22
24
  import time
23
25
  from typing import (
@@ -57,6 +59,19 @@ COMPUTE_TYPES = {
57
59
  }
58
60
 
59
61
 
62
+ def _sanitize_api_name(name: str) -> str:
63
+ """Sanitize an API name to comply with Salesforce naming rules.
64
+
65
+ Replaces spaces and hyphens with underscores, removes invalid characters,
66
+ collapses consecutive underscores, and strips leading/trailing underscores.
67
+ """
68
+ sanitized = re.sub(r"[ \-]", "_", name)
69
+ sanitized = re.sub(r"[^\w]", "", sanitized)
70
+ sanitized = re.sub(r"_+", "_", sanitized)
71
+ sanitized = sanitized.strip("_")
72
+ return sanitized
73
+
74
+
60
75
  class CodeExtensionMetadata(BaseModel):
61
76
  name: str
62
77
  version: str
@@ -66,6 +81,24 @@ class CodeExtensionMetadata(BaseModel):
66
81
  functionInvokeOptions: Union[list[str], None] = None
67
82
 
68
83
  def __init__(self, **data):
84
+ name = data.get("name", "")
85
+ sanitized = _sanitize_api_name(name)
86
+ if sanitized != name:
87
+ logger.warning(f"API name '{name}' was sanitized to '{sanitized}'")
88
+ data["name"] = sanitized
89
+ if not sanitized:
90
+ raise ValueError(
91
+ f"API name '{name}' is invalid and could not be sanitized to a"
92
+ " valid name."
93
+ )
94
+ if not sanitized[0].isalpha():
95
+ raise ValueError(
96
+ f"API name '{sanitized}' must begin with a letter. "
97
+ "The name can only contain underscores and alphanumeric"
98
+ " characters, must begin with a letter, not include spaces,"
99
+ " not end with an underscore, and not contain two consecutive"
100
+ " underscores."
101
+ )
69
102
  super().__init__(**data)
70
103
 
71
104
 
@@ -156,6 +189,54 @@ def _retrieve_access_token(credentials: Credentials) -> AccessTokenResponse:
156
189
  return AccessTokenResponse(**response)
157
190
 
158
191
 
192
+ def _retrieve_access_token_from_sf_cli(sf_cli_org: str) -> AccessTokenResponse:
193
+ """Get an access token from the Salesforce CLI."""
194
+ try:
195
+ result = subprocess.run(
196
+ ["sf", "org", "display", "--target-org", sf_cli_org, "--json"],
197
+ capture_output=True,
198
+ text=True,
199
+ check=True,
200
+ timeout=30,
201
+ )
202
+ except FileNotFoundError as exc:
203
+ raise RuntimeError(
204
+ "The 'sf' command was not found. "
205
+ "Please install Salesforce CLI: https://developer.salesforce.com/tools/salesforcecli"
206
+ ) from exc
207
+ except subprocess.TimeoutExpired as exc:
208
+ raise RuntimeError(
209
+ f"'sf org display' timed out for org '{sf_cli_org}'"
210
+ ) from exc
211
+ except subprocess.CalledProcessError as exc:
212
+ raise RuntimeError(
213
+ f"'sf org display' failed for org '{sf_cli_org}'.\n"
214
+ f"Ensure the org is authenticated via 'sf org login web'.\n"
215
+ f"stderr: {exc.stderr.strip()}"
216
+ ) from exc
217
+
218
+ try:
219
+ data = json.loads(result.stdout)
220
+ except json.JSONDecodeError as exc:
221
+ raise RuntimeError(f"Failed to parse 'sf org display' output: {exc}") from exc
222
+
223
+ if data.get("status") != 0:
224
+ raise RuntimeError(
225
+ f"SF CLI error for org '{sf_cli_org}': "
226
+ f"{data.get('message', 'unknown error')}"
227
+ )
228
+
229
+ org_result = data.get("result", {})
230
+ access_token = org_result.get("accessToken")
231
+ instance_url = org_result.get("instanceUrl")
232
+ if not access_token or not instance_url:
233
+ raise RuntimeError(
234
+ f"'sf org display' did not return an access token or instance URL "
235
+ f"for org '{sf_cli_org}'"
236
+ )
237
+ return AccessTokenResponse(access_token=access_token, instance_url=instance_url)
238
+
239
+
159
240
  class CreateDeploymentResponse(BaseModel):
160
241
  fileUploadUrl: str
161
242
 
@@ -463,12 +544,15 @@ def zip(
463
544
  def deploy_full(
464
545
  directory: str,
465
546
  metadata: CodeExtensionMetadata,
466
- credentials: Credentials,
547
+ credentials: Union["Credentials", AccessTokenResponse],
467
548
  docker_network: str,
468
549
  callback=None,
469
550
  ) -> AccessTokenResponse:
470
551
  """Deploy a data transform in the DataCloud."""
471
- access_token = _retrieve_access_token(credentials)
552
+ if isinstance(credentials, AccessTokenResponse):
553
+ access_token = credentials
554
+ else:
555
+ access_token = _retrieve_access_token(credentials)
472
556
 
473
557
  # prepare payload
474
558
  config = get_config(directory)
@@ -25,7 +25,7 @@ from typing import (
25
25
  )
26
26
 
27
27
  from datacustomcode.config import config
28
- from datacustomcode.scan import get_package_type
28
+ from datacustomcode.scan import find_base_directory, get_package_type
29
29
 
30
30
 
31
31
  def _set_config_option(config_obj, key: str, value: str) -> None:
@@ -68,7 +68,9 @@ def run_entrypoint(
68
68
  f"config.json not found at {config_json_path}. config.json is required."
69
69
  )
70
70
 
71
- package_type = get_package_type(entrypoint_dir)
71
+ # SDK config (.datacustomcode_proj/sdk_config.json) is under project root
72
+ base_directory = find_base_directory(entrypoint)
73
+ package_type = get_package_type(base_directory)
72
74
 
73
75
  try:
74
76
  with open(config_json_path, "r") as f: