salesforce-data-customcode 6.0.4.dev2__py3-none-any.whl → 6.0.4.dev3__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.
- datacustomcode/client.py +31 -1
- datacustomcode/file/path/default.py +27 -30
- {salesforce_data_customcode-6.0.4.dev2.dist-info → salesforce_data_customcode-6.0.4.dev3.dist-info}/METADATA +26 -9
- {salesforce_data_customcode-6.0.4.dev2.dist-info → salesforce_data_customcode-6.0.4.dev3.dist-info}/RECORD +7 -7
- {salesforce_data_customcode-6.0.4.dev2.dist-info → salesforce_data_customcode-6.0.4.dev3.dist-info}/WHEEL +0 -0
- {salesforce_data_customcode-6.0.4.dev2.dist-info → salesforce_data_customcode-6.0.4.dev3.dist-info}/entry_points.txt +0 -0
- {salesforce_data_customcode-6.0.4.dev2.dist-info → salesforce_data_customcode-6.0.4.dev3.dist-info}/licenses/LICENSE.txt +0 -0
datacustomcode/client.py
CHANGED
|
@@ -289,8 +289,38 @@ class Client:
|
|
|
289
289
|
return self._writer.write_to_dmo(name, dataframe, write_mode, **kwargs) # type: ignore[no-any-return]
|
|
290
290
|
|
|
291
291
|
def find_file_path(self, file_name: str) -> Path:
|
|
292
|
-
"""
|
|
292
|
+
"""Resolve a bundled file shipped in the package to an absolute path.
|
|
293
|
+
|
|
294
|
+
Resolution order (first existing path wins):
|
|
295
|
+
|
|
296
|
+
1. ``$LIBRARY_PATH/<file_folder>/<file_name>`` then
|
|
297
|
+
``$LIBRARY_PATH/<file_name>`` — when the ``LIBRARY_PATH`` environment
|
|
298
|
+
variable is set. The Data Cloud runtime sets this to the directory
|
|
299
|
+
containing the extracted package.
|
|
300
|
+
2. ``<code_package>/<file_folder>/<file_name>`` relative to the current
|
|
301
|
+
working directory — the default ``payload/files/<file_name>`` layout
|
|
302
|
+
used by ``datacustomcode run`` from a project root.
|
|
303
|
+
3. ``<config_dir>/<file_folder>/<file_name>`` where ``<config_dir>`` is
|
|
304
|
+
the directory containing the nearest ``config.json`` discoverable
|
|
305
|
+
by walking the cwd subtree.
|
|
306
|
+
|
|
307
|
+
``LIBRARY_PATH`` must point to the directory that *contains*
|
|
308
|
+
``files/`` — i.e., the package root, the same directory that holds
|
|
309
|
+
``config.json`` and ``entrypoint.py``. See ``Bundled file resolution``
|
|
310
|
+
in the ``readme.md`` for more details.
|
|
293
311
|
|
|
312
|
+
Args:
|
|
313
|
+
file_name: A file under the package's ``files/`` folder. Relative
|
|
314
|
+
subpaths (e.g., ``"file/data2.csv"``) are supported.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
A ``pathlib.Path`` that exists.
|
|
318
|
+
|
|
319
|
+
Raises:
|
|
320
|
+
FileNotFoundError: If the file does not exist at any of the
|
|
321
|
+
resolution-order locations. The message lists every candidate
|
|
322
|
+
path that was tried.
|
|
323
|
+
"""
|
|
294
324
|
return self._file.find_file_path(file_name) # type: ignore[no-any-return]
|
|
295
325
|
|
|
296
326
|
def llm_gateway_generate_text(
|
|
@@ -16,7 +16,7 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import os
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from typing import Optional
|
|
19
|
+
from typing import Iterator, Optional
|
|
20
20
|
|
|
21
21
|
from datacustomcode.file.base import BaseDataAccessLayer
|
|
22
22
|
|
|
@@ -66,7 +66,7 @@ class DefaultFindFilePath(BaseDataAccessLayer):
|
|
|
66
66
|
file_name: The name of the file to open
|
|
67
67
|
|
|
68
68
|
Returns:
|
|
69
|
-
A file path
|
|
69
|
+
A file path that exists
|
|
70
70
|
|
|
71
71
|
Raises:
|
|
72
72
|
FileNotFoundError: If the file cannot be found
|
|
@@ -74,46 +74,40 @@ class DefaultFindFilePath(BaseDataAccessLayer):
|
|
|
74
74
|
if not file_name:
|
|
75
75
|
raise ValueError("file_name cannot be empty")
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
tried: list[Path] = []
|
|
78
|
+
for candidate in self._candidate_paths(file_name):
|
|
79
|
+
tried.append(candidate)
|
|
80
|
+
if candidate.exists():
|
|
81
|
+
return candidate
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
raise FileNotFoundError(
|
|
84
|
+
f"File '{file_name}' not found in any search location. "
|
|
85
|
+
f"Tried: {[str(p) for p in tried]}"
|
|
86
|
+
)
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _resolve_file_path(self, file_name: str) -> Path:
|
|
87
|
-
"""Resolve the full path to a file.
|
|
88
|
+
def _candidate_paths(self, file_name: str) -> Iterator[Path]:
|
|
89
|
+
"""Yield candidate paths for ``file_name`` in resolution order.
|
|
88
90
|
|
|
89
91
|
Args:
|
|
90
92
|
file_name: The name of the file to resolve
|
|
91
93
|
|
|
92
94
|
Returns:
|
|
93
|
-
|
|
95
|
+
An iterator of candidate paths
|
|
94
96
|
"""
|
|
95
|
-
#
|
|
97
|
+
# 1. $LIBRARY_PATH/<file_folder>/<file_name>, then $LIBRARY_PATH/<file_name>
|
|
96
98
|
env_path = os.getenv(self.DEFAULT_ENV_VAR)
|
|
97
99
|
if env_path:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return file_path
|
|
100
|
+
yield Path(env_path) / self.file_folder / file_name
|
|
101
|
+
yield Path(env_path) / file_name
|
|
101
102
|
|
|
102
|
-
#
|
|
103
|
+
# 2. <code_package>/<file_folder>/<file_name> relative to cwd
|
|
103
104
|
if self._code_package_exists():
|
|
104
|
-
|
|
105
|
-
if file_path.exists():
|
|
106
|
-
return file_path
|
|
105
|
+
yield self._get_code_package_file_path(file_name)
|
|
107
106
|
|
|
108
|
-
#
|
|
107
|
+
# 3. <config_dir>/<file_folder>/<file_name> via config.json discovery
|
|
109
108
|
config_path = self._find_config_file()
|
|
110
|
-
if config_path:
|
|
111
|
-
|
|
112
|
-
if file_path.exists():
|
|
113
|
-
return file_path
|
|
114
|
-
|
|
115
|
-
# Return the file name as a Path if not found in any location
|
|
116
|
-
return Path(file_name)
|
|
109
|
+
if config_path is not None:
|
|
110
|
+
yield self._get_config_based_file_path(file_name, config_path)
|
|
117
111
|
|
|
118
112
|
def _code_package_exists(self) -> bool:
|
|
119
113
|
"""Check if the default code package directory exists.
|
|
@@ -146,6 +140,10 @@ class DefaultFindFilePath(BaseDataAccessLayer):
|
|
|
146
140
|
def _get_config_based_file_path(self, file_name: str, config_path: Path) -> Path:
|
|
147
141
|
"""Get the file path relative to the config file location.
|
|
148
142
|
|
|
143
|
+
Anchors on the directory containing the discovered ``config.json`` so a
|
|
144
|
+
package found by walking up from cwd resolves files relative to its own
|
|
145
|
+
root, not the caller's cwd.
|
|
146
|
+
|
|
149
147
|
Args:
|
|
150
148
|
file_name: The name of the file
|
|
151
149
|
config_path: The path to the config file
|
|
@@ -153,8 +151,7 @@ class DefaultFindFilePath(BaseDataAccessLayer):
|
|
|
153
151
|
Returns:
|
|
154
152
|
The full path to the file
|
|
155
153
|
"""
|
|
156
|
-
|
|
157
|
-
return Path(relative_path)
|
|
154
|
+
return config_path.parent / self.file_folder / file_name
|
|
158
155
|
|
|
159
156
|
def _find_file_in_tree(self, filename: str, search_path: Path) -> Optional[Path]:
|
|
160
157
|
"""Find a file within a directory tree.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: salesforce-data-customcode
|
|
3
|
-
Version: 6.0.4.
|
|
3
|
+
Version: 6.0.4.dev3
|
|
4
4
|
Summary: Data Cloud Custom Code SDK
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE.txt
|
|
@@ -15,7 +15,6 @@ Requires-Dist: click (>=8.1.8,<9.0.0)
|
|
|
15
15
|
Requires-Dist: loguru (>=0.7.3,<0.8.0)
|
|
16
16
|
Requires-Dist: numpy
|
|
17
17
|
Requires-Dist: pandas
|
|
18
|
-
Requires-Dist: pipreqs
|
|
19
18
|
Requires-Dist: pydantic (==2.13.1)
|
|
20
19
|
Requires-Dist: pyspark (==3.5.1)
|
|
21
20
|
Requires-Dist: pyyaml (>=6.0,<7.0)
|
|
@@ -150,8 +149,8 @@ The SDK automatically handles all dependency packaging for Data Cloud deployment
|
|
|
150
149
|
├── payload
|
|
151
150
|
│ ├── config.json
|
|
152
151
|
│ ├── entrypoint.py
|
|
153
|
-
├── files
|
|
154
|
-
│ ├── data.csv
|
|
152
|
+
│ ├── files
|
|
153
|
+
│ │ ├── data.csv
|
|
155
154
|
```
|
|
156
155
|
|
|
157
156
|
## py-files directory
|
|
@@ -163,10 +162,10 @@ Your Python dependencies can be packaged as .py files, .zip archives (containing
|
|
|
163
162
|
├── payload
|
|
164
163
|
│ ├── config.json
|
|
165
164
|
│ ├── entrypoint.py
|
|
166
|
-
├── py-files
|
|
167
|
-
│ ├── moduleA
|
|
168
|
-
│ │ ├── __init__.py
|
|
169
|
-
│ │ ├── moduleA.py
|
|
165
|
+
│ ├── py-files
|
|
166
|
+
│ │ ├── moduleA
|
|
167
|
+
│ │ │ ├── __init__.py
|
|
168
|
+
│ │ │ ├── moduleA.py
|
|
170
169
|
```
|
|
171
170
|
|
|
172
171
|
## API
|
|
@@ -174,7 +173,7 @@ Your Python dependencies can be packaged as .py files, .zip archives (containing
|
|
|
174
173
|
Your entry point script will define logic using the `Client` object which wraps data access layers.
|
|
175
174
|
|
|
176
175
|
You should only need the following methods:
|
|
177
|
-
* `find_file_path(file_name)`
|
|
176
|
+
* `find_file_path(file_name)` – Resolve a bundled file (placed under `payload/files/`) to a `pathlib.Path` that exists. Works the same locally and inside Data Cloud — see [Bundled file resolution](#bundled-file-resolution) below for the full lookup order. Raises `FileNotFoundError` if the file isn't found.
|
|
178
177
|
* `read_dlo(name)` – Read from a Data Lake Object by name
|
|
179
178
|
* `read_dmo(name)` – Read from a Data Model Object by name
|
|
180
179
|
* `write_to_dlo(name, spark_dataframe, write_mode)` – Write to a Data Model Object by name with a Spark dataframe
|
|
@@ -195,6 +194,24 @@ client.write_to_dlo('output_DLO')
|
|
|
195
194
|
> [!WARNING]
|
|
196
195
|
> Currently we only support reading from DMOs and writing to DMOs or reading from DLOs and writing to DLOs, but they cannot mix.
|
|
197
196
|
|
|
197
|
+
### Bundled file resolution
|
|
198
|
+
|
|
199
|
+
Place bundled files (CSVs, prompt files, etc.) under `payload/files/`. The same `client.find_file_path("data.csv")` call resolves consistently across all three runtimes:
|
|
200
|
+
|
|
201
|
+
- `datacustomcode run` (local) → `<cwd>/payload/files/data.csv`
|
|
202
|
+
- Data Cloud script package → `$LIBRARY_PATH/files/data.csv`
|
|
203
|
+
- Data Cloud function package → `$LIBRARY_PATH/files/data.csv`
|
|
204
|
+
|
|
205
|
+
Resolution order (first existing path wins):
|
|
206
|
+
|
|
207
|
+
1. `$LIBRARY_PATH/files/<file_name>`, then `$LIBRARY_PATH/<file_name>` — when `LIBRARY_PATH` is set. Data Cloud sets this for you to the package root.
|
|
208
|
+
2. `payload/files/<file_name>` relative to the current working directory.
|
|
209
|
+
3. `<config_dir>/files/<file_name>` where `<config_dir>` is the directory of the nearest `config.json` discoverable by walking down from cwd.
|
|
210
|
+
|
|
211
|
+
If none of these exist, `find_file_path` raises `FileNotFoundError` with the list of paths it tried.
|
|
212
|
+
|
|
213
|
+
`$LIBRARY_PATH` is set automatically to the root of the package at runtime inside Data Cloud.
|
|
214
|
+
|
|
198
215
|
|
|
199
216
|
## CLI
|
|
200
217
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
datacustomcode/__init__.py,sha256=qx-X3U9lU_791ZbAxhGqQeyDw6eqTi9-E9AbYnwJfxU,2071
|
|
2
2
|
datacustomcode/auth.py,sha256=fpSjhIBdv9trC8yq2vuljAix_Euu-4Ah7HDCGhYjOxI,8309
|
|
3
3
|
datacustomcode/cli.py,sha256=i7hPoQqJskTmx3Odz2wVgkxBnf_PFaIawI12FXhaRUE,13264
|
|
4
|
-
datacustomcode/client.py,sha256=
|
|
4
|
+
datacustomcode/client.py,sha256=fc3JmDbAFcTT-yP_k_0jNiGqNp_KwZaIpEsI-NPxNcw,15115
|
|
5
5
|
datacustomcode/cmd.py,sha256=ZMs46aydJw2EaU26JgCtZmnqESQFHvvaJz10hnjZTBk,3537
|
|
6
6
|
datacustomcode/common_config.py,sha256=SAUnxj3kqmOeWwPmFoYq4tuxokMgURVm4QwcGi-avL4,1928
|
|
7
7
|
datacustomcode/config.py,sha256=2Pk61ieQsEWSxKDxlS66_rliKE8iX0j-9LweEm0aaGo,4072
|
|
@@ -19,7 +19,7 @@ datacustomcode/einstein_predictions_config.py,sha256=oxKgp2u7t5SdHVsasBBxsvd3KAf
|
|
|
19
19
|
datacustomcode/file/__init__.py,sha256=gamfOD1VnAtEslRBpqh-yKiVjkG_wYWYdSatGIfsN-w,621
|
|
20
20
|
datacustomcode/file/base.py,sha256=2IUlQufPvpHccgyoeZlKhcktO4pS4NKdha03Whq33Zk,745
|
|
21
21
|
datacustomcode/file/path/__init__.py,sha256=gamfOD1VnAtEslRBpqh-yKiVjkG_wYWYdSatGIfsN-w,621
|
|
22
|
-
datacustomcode/file/path/default.py,sha256=
|
|
22
|
+
datacustomcode/file/path/default.py,sha256=oG5S3JMnJ_c1ewUx5kWfEmJnMVXsu6AnwsBxlriw6Kk,5705
|
|
23
23
|
datacustomcode/function/__init__.py,sha256=oag8FevDxbx1vR0tFhtxPd5SvmUEYDj8esRo4v2qu3Q,749
|
|
24
24
|
datacustomcode/function/base.py,sha256=H_i80GcsFScvHJJ6bYLry5phTpsEU1KubpR8D3qnsjo,691
|
|
25
25
|
datacustomcode/function/feature_types/__init__.py,sha256=gamfOD1VnAtEslRBpqh-yKiVjkG_wYWYdSatGIfsN-w,621
|
|
@@ -92,8 +92,8 @@ datacustomcode/templates/script/requirements-dev.txt,sha256=OWwuy1awesqOZOS3Zizm
|
|
|
92
92
|
datacustomcode/templates/script/requirements.txt,sha256=qJbqzs5z0Hrx590U3T7dHq_m8UQEx2TdhgrJSz-QHIQ,40
|
|
93
93
|
datacustomcode/token_provider.py,sha256=kGPxdxGZEr6VFknW7jFb9P6wOvRaRo-sfdBN2cUjEZY,6792
|
|
94
94
|
datacustomcode/version.py,sha256=9LlbVrzwBvut1L308QbwNBgxdQk5CrghH39JARVSxms,989
|
|
95
|
-
salesforce_data_customcode-6.0.4.
|
|
96
|
-
salesforce_data_customcode-6.0.4.
|
|
97
|
-
salesforce_data_customcode-6.0.4.
|
|
98
|
-
salesforce_data_customcode-6.0.4.
|
|
99
|
-
salesforce_data_customcode-6.0.4.
|
|
95
|
+
salesforce_data_customcode-6.0.4.dev3.dist-info/METADATA,sha256=Tg_qVJWkMhhj1pz1hSHwVpLh00hppX1cDMkmts7ycJM,23234
|
|
96
|
+
salesforce_data_customcode-6.0.4.dev3.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
|
|
97
|
+
salesforce_data_customcode-6.0.4.dev3.dist-info/entry_points.txt,sha256=WpQ94UB7UuRCYGOLtJV3vAgm1tl7iVf43VYRWIuNu5Y,74
|
|
98
|
+
salesforce_data_customcode-6.0.4.dev3.dist-info/licenses/LICENSE.txt,sha256=iOi8EmQpfkFhMENi7VYtkl2EqS14pEOccoXHiW2dyPU,11443
|
|
99
|
+
salesforce_data_customcode-6.0.4.dev3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|