salesforce-data-customcode 6.0.4.dev2__tar.gz → 6.0.4.dev3__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 (98) hide show
  1. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/PKG-INFO +26 -9
  2. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/README.md +25 -7
  3. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/pyproject.toml +2 -2
  4. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/client.py +31 -1
  5. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/file/path/default.py +27 -30
  6. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/LICENSE.txt +0 -0
  7. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/__init__.py +0 -0
  8. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/auth.py +0 -0
  9. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/cli.py +0 -0
  10. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/cmd.py +0 -0
  11. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/common_config.py +0 -0
  12. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/config.py +0 -0
  13. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/config.yaml +0 -0
  14. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/constants.py +0 -0
  15. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/credentials.py +0 -0
  16. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/deploy.py +0 -0
  17. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/einstein_platform_client.py +0 -0
  18. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/einstein_platform_config.py +0 -0
  19. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/einstein_predictions/__init__.py +0 -0
  20. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/einstein_predictions/base.py +0 -0
  21. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/einstein_predictions/impl/default.py +0 -0
  22. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/einstein_predictions/types.py +0 -0
  23. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/einstein_predictions_config.py +0 -0
  24. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/file/__init__.py +0 -0
  25. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/file/base.py +0 -0
  26. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/file/path/__init__.py +0 -0
  27. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/function/__init__.py +0 -0
  28. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/function/base.py +0 -0
  29. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/function/feature_types/__init__.py +0 -0
  30. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/function/feature_types/chunking.py +0 -0
  31. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/function/runtime.py +0 -0
  32. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/function_utils.py +0 -0
  33. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/io/__init__.py +0 -0
  34. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/io/base.py +0 -0
  35. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/io/reader/__init__.py +0 -0
  36. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/io/reader/base.py +0 -0
  37. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/io/reader/query_api.py +0 -0
  38. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/io/reader/sf_cli.py +0 -0
  39. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/io/reader/utils.py +0 -0
  40. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/io/writer/__init__.py +0 -0
  41. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/io/writer/base.py +0 -0
  42. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/io/writer/csv.py +0 -0
  43. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/io/writer/print.py +0 -0
  44. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/llm_gateway/__init__.py +0 -0
  45. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/llm_gateway/base.py +0 -0
  46. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/llm_gateway/default.py +0 -0
  47. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/llm_gateway/errors.py +0 -0
  48. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/llm_gateway/spark_base.py +0 -0
  49. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/llm_gateway/spark_default.py +0 -0
  50. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/llm_gateway/types/__init__.py +0 -0
  51. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/llm_gateway/types/generate_text_request.py +0 -0
  52. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/llm_gateway/types/generate_text_request_builder.py +0 -0
  53. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/llm_gateway/types/generate_text_response.py +0 -0
  54. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/llm_gateway/types/generate_text_response_builder.py +0 -0
  55. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/llm_gateway_config.py +0 -0
  56. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/mixin.py +0 -0
  57. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/py.typed +0 -0
  58. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/run.py +0 -0
  59. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/scan.py +0 -0
  60. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/spark/__init__.py +0 -0
  61. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/spark/base.py +0 -0
  62. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/spark/default.py +0 -0
  63. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/template.py +0 -0
  64. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/__init__.py +0 -0
  65. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/.devcontainer/devcontainer.json +0 -0
  66. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/Dockerfile.dependencies +0 -0
  67. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/README.md +0 -0
  68. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/build_native_dependencies.sh +0 -0
  69. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/chunking/payload/config.json +0 -0
  70. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/chunking/payload/entrypoint.py +0 -0
  71. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/chunking/requirements.txt +0 -0
  72. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/example/chunking_with_llm/config.json +0 -0
  73. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/example/chunking_with_llm/entrypoint.py +0 -0
  74. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/example/chunking_with_llm/files/chunking_prompt.txt +0 -0
  75. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/example/chunking_with_llm/tests/test.json +0 -0
  76. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/example/chunking_with_prediction/config.json +0 -0
  77. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/example/chunking_with_prediction/entrypoint.py +0 -0
  78. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/example/chunking_with_prediction/tests/test.json +0 -0
  79. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/payload/config.json +0 -0
  80. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/payload/entrypoint.py +0 -0
  81. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/payload/utility.py +0 -0
  82. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/requirements-dev.txt +0 -0
  83. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/function/requirements.txt +0 -0
  84. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/script/.devcontainer/devcontainer.json +0 -0
  85. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/script/Dockerfile +0 -0
  86. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/script/Dockerfile.dependencies +0 -0
  87. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/script/README.md +0 -0
  88. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/script/account.ipynb +0 -0
  89. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/script/build_native_dependencies.sh +0 -0
  90. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/script/examples/employee_hierarchy/employee_data.csv +0 -0
  91. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/script/examples/employee_hierarchy/entrypoint.py +0 -0
  92. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/script/jupyterlab.sh +0 -0
  93. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/script/payload/config.json +0 -0
  94. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/script/payload/entrypoint.py +0 -0
  95. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/script/requirements-dev.txt +0 -0
  96. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/templates/script/requirements.txt +0 -0
  97. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/token_provider.py +0 -0
  98. {salesforce_data_customcode-6.0.4.dev2 → salesforce_data_customcode-6.0.4.dev3}/src/datacustomcode/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: salesforce-data-customcode
3
- Version: 6.0.4.dev2
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)` - Returns a file path
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
 
@@ -124,8 +124,8 @@ The SDK automatically handles all dependency packaging for Data Cloud deployment
124
124
  ├── payload
125
125
  │ ├── config.json
126
126
  │ ├── entrypoint.py
127
- ├── files
128
- │ ├── data.csv
127
+ ├── files
128
+ ├── data.csv
129
129
  ```
130
130
 
131
131
  ## py-files directory
@@ -137,10 +137,10 @@ Your Python dependencies can be packaged as .py files, .zip archives (containing
137
137
  ├── payload
138
138
  │ ├── config.json
139
139
  │ ├── entrypoint.py
140
- ├── py-files
141
- │ ├── moduleA
142
- │ │ ├── __init__.py
143
- │ │ ├── moduleA.py
140
+ ├── py-files
141
+ ├── moduleA
142
+ │ │ ├── __init__.py
143
+ │ │ ├── moduleA.py
144
144
  ```
145
145
 
146
146
  ## API
@@ -148,7 +148,7 @@ Your Python dependencies can be packaged as .py files, .zip archives (containing
148
148
  Your entry point script will define logic using the `Client` object which wraps data access layers.
149
149
 
150
150
  You should only need the following methods:
151
- * `find_file_path(file_name)` - Returns a file path
151
+ * `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.
152
152
  * `read_dlo(name)` – Read from a Data Lake Object by name
153
153
  * `read_dmo(name)` – Read from a Data Model Object by name
154
154
  * `write_to_dlo(name, spark_dataframe, write_mode)` – Write to a Data Model Object by name with a Spark dataframe
@@ -169,6 +169,24 @@ client.write_to_dlo('output_DLO')
169
169
  > [!WARNING]
170
170
  > Currently we only support reading from DMOs and writing to DMOs or reading from DLOs and writing to DLOs, but they cannot mix.
171
171
 
172
+ ### Bundled file resolution
173
+
174
+ 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:
175
+
176
+ - `datacustomcode run` (local) → `<cwd>/payload/files/data.csv`
177
+ - Data Cloud script package → `$LIBRARY_PATH/files/data.csv`
178
+ - Data Cloud function package → `$LIBRARY_PATH/files/data.csv`
179
+
180
+ Resolution order (first existing path wins):
181
+
182
+ 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.
183
+ 2. `payload/files/<file_name>` relative to the current working directory.
184
+ 3. `<config_dir>/files/<file_name>` where `<config_dir>` is the directory of the nearest `config.json` discoverable by walking down from cwd.
185
+
186
+ If none of these exist, `find_file_path` raises `FileNotFoundError` with the list of paths it tried.
187
+
188
+ `$LIBRARY_PATH` is set automatically to the root of the package at runtime inside Data Cloud.
189
+
172
190
 
173
191
  ## CLI
174
192
 
@@ -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 = "6.0.4.dev2"
21
+ version = "6.0.4.dev3"
22
22
 
23
23
  [tool.black]
24
24
  exclude = '''
@@ -101,7 +101,6 @@ click = "^8.1.8"
101
101
  loguru = "^0.7.3"
102
102
  numpy = "*"
103
103
  pandas = "*"
104
- pipreqs = "*"
105
104
  pydantic = "2.13.1"
106
105
  pyspark = "3.5.1"
107
106
  python = ">=3.10,<3.12"
@@ -115,6 +114,7 @@ build = "*"
115
114
  coverage = ">=7.0.0,<8.0.0"
116
115
  ipykernel = "^6.29.5"
117
116
  mypy = "*"
117
+ pipreqs = "*"
118
118
  poetry-dynamic-versioning = {extras = ["plugin"], version = "^1.8.2"}
119
119
  pre-commit = "*"
120
120
  pytest = "*"
@@ -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
- """Return a file path"""
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
- file_path = self._resolve_file_path(file_name)
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
- if not file_path.exists():
80
- raise FileNotFoundError(
81
- f"File '{file_name}' not found in any search location"
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
- return file_path
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
- The full path to the file
95
+ An iterator of candidate paths
94
96
  """
95
- # First check if environment variable is set
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
- file_path = Path(env_path) / file_name
99
- if file_path.exists():
100
- return file_path
100
+ yield Path(env_path) / self.file_folder / file_name
101
+ yield Path(env_path) / file_name
101
102
 
102
- # First try the default code package location
103
+ # 2. <code_package>/<file_folder>/<file_name> relative to cwd
103
104
  if self._code_package_exists():
104
- file_path = self._get_code_package_file_path(file_name)
105
- if file_path.exists():
106
- return file_path
105
+ yield self._get_code_package_file_path(file_name)
107
106
 
108
- # Fall back to config.json-based location
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
- file_path = self._get_config_based_file_path(file_name, config_path)
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
- relative_path = f"{self.file_folder}/{file_name}"
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.