tlpytools 0.2.4__tar.gz → 0.2.5__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 (60) hide show
  1. {tlpytools-0.2.4 → tlpytools-0.2.5}/.env.example +3 -2
  2. {tlpytools-0.2.4 → tlpytools-0.2.5}/.github/copilot-instructions.md +2 -2
  3. {tlpytools-0.2.4 → tlpytools-0.2.5}/.github/workflows/deploy.yml +1 -1
  4. {tlpytools-0.2.4 → tlpytools-0.2.5}/CHANGELOG.md +23 -0
  5. {tlpytools-0.2.4 → tlpytools-0.2.5}/DEVELOPER_GUIDE.md +2 -2
  6. {tlpytools-0.2.4 → tlpytools-0.2.5}/PKG-INFO +2 -1
  7. {tlpytools-0.2.4 → tlpytools-0.2.5}/pyproject.toml +2 -1
  8. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/__init__.py +1 -1
  9. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/orca/orchestrator.py +34 -158
  10. {tlpytools-0.2.4 → tlpytools-0.2.5}/tests/test_orca_unit.py +40 -6
  11. {tlpytools-0.2.4 → tlpytools-0.2.5}/uv.lock +36 -32
  12. {tlpytools-0.2.4 → tlpytools-0.2.5}/.github/skills/README.md +0 -0
  13. {tlpytools-0.2.4 → tlpytools-0.2.5}/.github/skills/prepare-official-release/SKILL.md +0 -0
  14. {tlpytools-0.2.4 → tlpytools-0.2.5}/.github/workflows/code-formatting-pr.yml +0 -0
  15. {tlpytools-0.2.4 → tlpytools-0.2.5}/.github/workflows/code-formatting.yml +0 -0
  16. {tlpytools-0.2.4 → tlpytools-0.2.5}/.github/workflows/test.yml +0 -0
  17. {tlpytools-0.2.4 → tlpytools-0.2.5}/.gitignore +0 -0
  18. {tlpytools-0.2.4 → tlpytools-0.2.5}/.pre-commit-config.yaml +0 -0
  19. {tlpytools-0.2.4 → tlpytools-0.2.5}/.python-version +0 -0
  20. {tlpytools-0.2.4 → tlpytools-0.2.5}/ENVIRONMENT_VARIABLES.md +0 -0
  21. {tlpytools-0.2.4 → tlpytools-0.2.5}/Makefile +0 -0
  22. {tlpytools-0.2.4 → tlpytools-0.2.5}/README.md +0 -0
  23. {tlpytools-0.2.4 → tlpytools-0.2.5}/docs/adls_server.md +0 -0
  24. {tlpytools-0.2.4 → tlpytools-0.2.5}/docs/azure_credential_manager.md +0 -0
  25. {tlpytools-0.2.4 → tlpytools-0.2.5}/docs/diagrams/README.md +0 -0
  26. {tlpytools-0.2.4 → tlpytools-0.2.5}/docs/diagrams/orca_cloud_workflow.drawio +0 -0
  27. {tlpytools-0.2.4 → tlpytools-0.2.5}/docs/diagrams/orca_detailed_steps.drawio +0 -0
  28. {tlpytools-0.2.4 → tlpytools-0.2.5}/docs/diagrams/orca_file_structure.drawio +0 -0
  29. {tlpytools-0.2.4 → tlpytools-0.2.5}/docs/diagrams/orca_local_workflow.drawio +0 -0
  30. {tlpytools-0.2.4 → tlpytools-0.2.5}/docs/diagrams/orca_main_features.drawio +0 -0
  31. {tlpytools-0.2.4 → tlpytools-0.2.5}/docs/unified_logging.md +0 -0
  32. {tlpytools-0.2.4 → tlpytools-0.2.5}/requirements.txt +0 -0
  33. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/examples/batchtask.py +0 -0
  34. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/examples/data_files/example.csv +0 -0
  35. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/examples/mydataproject.py +0 -0
  36. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/examples/readwritedata.py +0 -0
  37. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/examples/unified_logging.py +0 -0
  38. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/adls_server.py +0 -0
  39. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/azure_credential.py +0 -0
  40. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/config.py +0 -0
  41. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/data.py +0 -0
  42. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/data_store.py +0 -0
  43. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/env_config.py +0 -0
  44. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/log.py +0 -0
  45. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/orca/README.md +0 -0
  46. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/orca/__init__.py +0 -0
  47. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/orca/__main__.py +0 -0
  48. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/orca/batch_task_runner.py +0 -0
  49. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/orca/cli.py +0 -0
  50. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/orca/orca_model_config_barebone.yaml +0 -0
  51. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/orca/orca_model_config_example.yaml +0 -0
  52. {tlpytools-0.2.4 → tlpytools-0.2.5}/src/tlpytools/sql_server.py +0 -0
  53. {tlpytools-0.2.4 → tlpytools-0.2.5}/tests/README.md +0 -0
  54. {tlpytools-0.2.4 → tlpytools-0.2.5}/tests/__init__.py +0 -0
  55. {tlpytools-0.2.4 → tlpytools-0.2.5}/tests/conftest.py +0 -0
  56. {tlpytools-0.2.4 → tlpytools-0.2.5}/tests/run_tests.py +0 -0
  57. {tlpytools-0.2.4 → tlpytools-0.2.5}/tests/test_azure_credential.py +0 -0
  58. {tlpytools-0.2.4 → tlpytools-0.2.5}/tests/test_basic.py +0 -0
  59. {tlpytools-0.2.4 → tlpytools-0.2.5}/tests/test_core.py +0 -0
  60. {tlpytools-0.2.4 → tlpytools-0.2.5}/tests/test_orca_integration.py +0 -0
@@ -7,15 +7,16 @@
7
7
  # Copy this file to .env and modify the values as needed
8
8
 
9
9
  # Required Environment Variables for Azure Batch
10
- BATCH_ACCOUNT_ENDPOINT=https://your-batch-account.your-region.batch.azure.com
10
+ BATCH_ACCOUNT_ENDPOINT=your-batch-account.your-region.batch.azure.com
11
11
  BATCH_API_VERSION=2024-07-01.20.0
12
12
  IMAGE_REGISTRY_ENDPOINT=your-registry.azurecr.io
13
13
  AZURE_SUBSCRIPTION_ID=your-subscription-id
14
14
  AZURE_RESOURCE_GROUP=your-resource-group
15
15
  MANAGED_IDENTITY_NAME=your-managed-identity
16
16
 
17
- # Azure Data Lake Storage URL
17
+ # Azure Data Lake Storage
18
18
  ORCA_ADLS_URL=yourstorageaccount.dfs.core.windows.net
19
+ ORCA_ADLS_CONTAINER=yourcontainer
19
20
 
20
21
  # Azure SQL Server URI
21
22
  TLPT_AZURE_SQL_URI=yoursever.database.windows.net
@@ -98,8 +98,8 @@ Interpretation for agent PRs:
98
98
 
99
99
  Observed result:
100
100
  - Build succeeds and produces:
101
- - `dist/tlpytools-0.2.4.tar.gz`
102
- - `dist/tlpytools-0.2.4-py3-none-any.whl`
101
+ - `dist/tlpytools-0.2.5.tar.gz`
102
+ - `dist/tlpytools-0.2.5-py3-none-any.whl`
103
103
 
104
104
  ### 5) Run
105
105
  - ORCA CLI (module form):
@@ -4,7 +4,7 @@ on:
4
4
  workflow_dispatch:
5
5
  inputs:
6
6
  version:
7
- description: 'Version number to release (e.g., 0.2.4)'
7
+ description: 'Version number to release (e.g., 0.2.5)'
8
8
  required: true
9
9
  type: string
10
10
  prerelease:
@@ -2,6 +2,29 @@
2
2
 
3
3
  All notable changes to TLPyTools will be documented in this file.
4
4
 
5
+ ## [0.2.5]
6
+
7
+ ### Changed
8
+ - **Dependency update:**
9
+ - Add matplotlib as a dependency with version specifications for compatibility
10
+ - **ADLS configuration moved to environment variables:**
11
+ - `adls_url` and `adls_container` are no longer read from `operational_mode.cloud` in YAML config
12
+ - Both are now read exclusively from `ORCA_ADLS_URL` and `ORCA_ADLS_CONTAINER` environment variables; an error is raised at runtime if either is missing
13
+ - `adls_folder` is now derived solely from the `project_folder` parameter passed to `OrcaOrchestrator`; the `operational_mode.cloud` YAML subsection has been removed from the default config template
14
+ - **Error dump restricted to cloud production mode:**
15
+ - Error dumps are now only created when `operational_mode.type` is `cloud_production`; they are silently skipped in `local_testing` mode regardless of the `create_error_dump` config setting
16
+ - **Error dump operations log removed:**
17
+ - `create_error_dump` no longer writes a CSV operations log file; a single `logger.info` message reports the dump path instead
18
+ - The `export_log` parameter has been removed from `create_error_dump`
19
+ - **Improved model run completion reporting in `print_execution_summary`:**
20
+ - `[ERROR] MODEL RUN FAILED` — run ended with an error
21
+ - `[OK] MODEL RUN COMPLETED` — all iterations and steps completed successfully
22
+ - `[OK] MODEL RUN IS PARTIALLY COMPLETED WITH NO ERRORS` — run stopped cleanly before all iterations were done (e.g. after `run_one_step` or `run_one_iteration`)
23
+
24
+ ### Fixed
25
+ - **Unit test for ADLS config now validates `.env.example`:**
26
+ - `test_get_adls_config` parses `.env.example` at test time and asserts that `ORCA_ADLS_URL` and `ORCA_ADLS_CONTAINER` are present; the test fails if either variable is removed from the example file
27
+
5
28
  ## [0.2.4]
6
29
 
7
30
  ### Changed
@@ -200,7 +200,7 @@ make check-all
200
200
  4. Click **"Run workflow"** button
201
201
  5. Fill in the required information:
202
202
  - **Branch**: Select `main` (or your release branch)
203
- - **Version number**: Enter the new version (e.g., `0.2.4`)
203
+ - **Version number**: Enter the new version (e.g., `0.2.5`)
204
204
  - **Is this a pre-release?**: Check if this is a pre-release version
205
205
 
206
206
  #### 3. Monitor the Release
@@ -219,7 +219,7 @@ After the workflow completes:
219
219
 
220
220
  ```bash
221
221
  # Verify the package is available on PyPI
222
- pip install tlpytools==0.2.4 # Replace with your version
222
+ pip install tlpytools==0.2.5 # Replace with your version
223
223
 
224
224
  # Test installation in a fresh environment
225
225
  python -c "import tlpytools; print(tlpytools.__version__)"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tlpytools
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: TransLink's Python Tools - A comprehensive toolkit for transportation modeling and forecasting
5
5
  Project-URL: Homepage, https://github.com/TransLinkForecasting/tlpytools
6
6
  Project-URL: Bug Tracker, https://github.com/TransLinkForecasting/tlpytools/issues
@@ -23,6 +23,7 @@ Requires-Dist: coverage>=7.10.6
23
23
  Requires-Dist: cryptography>=3.4.7
24
24
  Requires-Dist: cryptpandas>=0.1.1
25
25
  Requires-Dist: ipykernel>=7.2.0
26
+ Requires-Dist: matplotlib>=3.10.9
26
27
  Requires-Dist: notebook>=7.5.6
27
28
  Requires-Dist: numpy>=1.18
28
29
  Requires-Dist: openmatrix>=0.3
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "tlpytools"
7
- version = "0.2.4"
7
+ version = "0.2.5"
8
8
  description = "TransLink's Python Tools - A comprehensive toolkit for transportation modeling and forecasting"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10.0, <3.13"
@@ -26,6 +26,7 @@ classifiers = [
26
26
  # Runtime dependencies: these are installed when a user `pip install tlpytools`
27
27
  # and also when a developer runs `pip install -e .`
28
28
  dependencies = [
29
+ "matplotlib>=3.10.9",
29
30
  "pandas>=1.1",
30
31
  "numpy>=1.18",
31
32
  "sqlalchemy>=1.4",
@@ -2,7 +2,7 @@
2
2
  TLPyTools - A set of tools for building models at the TransLink Forecasting Team
3
3
  """
4
4
 
5
- __version__ = "0.2.4"
5
+ __version__ = "0.2.5"
6
6
 
7
7
  # Core modules that should always be available (minimal dependencies)
8
8
  from . import log
@@ -2144,7 +2144,6 @@ class OrcaScenario:
2144
2144
  iteration: int,
2145
2145
  error_message: str,
2146
2146
  include_all_scenario: bool = False,
2147
- export_log: bool = True,
2148
2147
  ) -> bool:
2149
2148
  """
2150
2149
  Create an error dump when a model execution fails.
@@ -2161,7 +2160,6 @@ class OrcaScenario:
2161
2160
  iteration: Current iteration number
2162
2161
  error_message: Error message describing the failure
2163
2162
  include_all_scenario: If True, includes entire scenario in error dump
2164
- export_log: Whether to export operation log
2165
2163
 
2166
2164
  Returns:
2167
2165
  bool: True if error dump was created successfully, False otherwise
@@ -2188,8 +2186,6 @@ class OrcaScenario:
2188
2186
  "dump_contents": [],
2189
2187
  }
2190
2188
 
2191
- operations = []
2192
-
2193
2189
  # 1. Copy the failed step directory if it exists
2194
2190
  step_dir = os.path.join(self.scenario_path, step_name)
2195
2191
  if os.path.exists(step_dir):
@@ -2205,26 +2201,6 @@ class OrcaScenario:
2205
2201
 
2206
2202
  if success:
2207
2203
  error_info["dump_contents"].append(f"{step_name}/")
2208
- operations.append(
2209
- {
2210
- "source": step_dir,
2211
- "target": step_dump_dir,
2212
- "type": "directory_copy",
2213
- "status": "success",
2214
- "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
2215
- }
2216
- )
2217
- else:
2218
- operations.append(
2219
- {
2220
- "source": step_dir,
2221
- "target": step_dump_dir,
2222
- "type": "directory_copy",
2223
- "status": "failed",
2224
- "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
2225
- "error": "Failed to copy step directory",
2226
- }
2227
- )
2228
2204
 
2229
2205
  # 2. Copy configuration files
2230
2206
  config_files = [
@@ -2243,25 +2219,11 @@ class OrcaScenario:
2243
2219
  try:
2244
2220
  shutil.copy2(config_path, target_path)
2245
2221
  error_info["dump_contents"].append(f"config/{config_file}")
2246
- operations.append(
2247
- {
2248
- "source": config_path,
2249
- "target": target_path,
2250
- "type": "file_copy",
2251
- "status": "success",
2252
- "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
2253
- }
2254
- )
2255
2222
  except Exception as e:
2256
- operations.append(
2257
- {
2258
- "source": config_path,
2259
- "target": target_path,
2260
- "type": "file_copy",
2261
- "status": "failed",
2262
- "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
2263
- "error": str(e),
2264
- }
2223
+ self.logger.warning(
2224
+ "Failed to copy config file %s to error dump: %s",
2225
+ config_file,
2226
+ e,
2265
2227
  )
2266
2228
 
2267
2229
  # 3. Copy log files
@@ -2276,25 +2238,9 @@ class OrcaScenario:
2276
2238
  try:
2277
2239
  shutil.copy2(log_file, target_path)
2278
2240
  error_info["dump_contents"].append(f"{LOGS_DIR}/{log_name}")
2279
- operations.append(
2280
- {
2281
- "source": log_file,
2282
- "target": target_path,
2283
- "type": "file_copy",
2284
- "status": "success",
2285
- "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
2286
- }
2287
- )
2288
2241
  except Exception as e:
2289
- operations.append(
2290
- {
2291
- "source": log_file,
2292
- "target": target_path,
2293
- "type": "file_copy",
2294
- "status": "failed",
2295
- "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
2296
- "error": str(e),
2297
- }
2242
+ self.logger.warning(
2243
+ "Failed to copy log file %s to error dump: %s", log_name, e
2298
2244
  )
2299
2245
 
2300
2246
  # 4. Copy outputs directory if it exists
@@ -2312,26 +2258,6 @@ class OrcaScenario:
2312
2258
 
2313
2259
  if success:
2314
2260
  error_info["dump_contents"].append(f"{OUTPUTS_DIR}/")
2315
- operations.append(
2316
- {
2317
- "source": outputs_dir,
2318
- "target": outputs_dump_dir,
2319
- "type": "directory_copy",
2320
- "status": "success",
2321
- "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
2322
- }
2323
- )
2324
- else:
2325
- operations.append(
2326
- {
2327
- "source": outputs_dir,
2328
- "target": outputs_dump_dir,
2329
- "type": "directory_copy",
2330
- "status": "failed",
2331
- "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
2332
- "error": "Failed to copy outputs directory",
2333
- }
2334
- )
2335
2261
 
2336
2262
  # 5. Optionally copy entire scenario (excluding the error dump itself)
2337
2263
  if include_all_scenario:
@@ -2348,26 +2274,6 @@ class OrcaScenario:
2348
2274
 
2349
2275
  if success:
2350
2276
  error_info["dump_contents"].append("full_scenario/")
2351
- operations.append(
2352
- {
2353
- "source": self.scenario_path,
2354
- "target": full_dump_dir,
2355
- "type": "full_scenario_copy",
2356
- "status": "success",
2357
- "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
2358
- }
2359
- )
2360
- else:
2361
- operations.append(
2362
- {
2363
- "source": self.scenario_path,
2364
- "target": full_dump_dir,
2365
- "type": "full_scenario_copy",
2366
- "status": "failed",
2367
- "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
2368
- "error": "Failed to copy full scenario",
2369
- }
2370
- )
2371
2277
 
2372
2278
  # 6. Save error information
2373
2279
  error_info_file = os.path.join(error_dump_dir, "error_info.json")
@@ -2395,23 +2301,8 @@ class OrcaScenario:
2395
2301
  error_dump_dir,
2396
2302
  )
2397
2303
 
2398
- # 8. Log all operations
2399
- if export_log and operations:
2400
- self.log_file_operations(
2401
- operations=operations,
2402
- log_dir=error_dump_base,
2403
- operation_type="error_dump",
2404
- export_log=export_log,
2405
- )
2406
-
2407
- # Summary logging
2408
- self.logger.info("Error dump completed successfully")
2409
- self.logger.debug(
2410
- "Error dump location: %s", zip_path if zip_success else error_dump_dir
2411
- )
2412
- self.logger.debug(
2413
- "Error dump contents: %s", ", ".join(error_info["dump_contents"])
2414
- )
2304
+ dump_location = zip_path if zip_success else error_dump_dir
2305
+ self.logger.info("Error dump created at: %s", dump_location)
2415
2306
 
2416
2307
  return True
2417
2308
 
@@ -2764,30 +2655,13 @@ class OrcaOrchestrator:
2764
2655
  raise ValueError(f"Invalid mode: {self.mode}")
2765
2656
 
2766
2657
  # Initialize state - for cloud mode, this will load the cloud-synchronized state
2767
- # Get project name from config or project_folder_override
2768
- # Priority: adls_folder in YAML > project_folder parameter > default
2769
- adls_folder_in_config = (
2770
- self.config.get("operational_mode", {}).get("cloud", {}).get("adls_folder")
2771
- )
2772
-
2773
- if adls_folder_in_config:
2774
- # Use adls_folder from YAML if already set
2775
- project_name = adls_folder_in_config
2776
- self.logger.debug("Using adls_folder from config: '%s'", project_name)
2777
- elif self.project_folder_override:
2778
- # Use project_folder parameter if provided and adls_folder not in YAML
2658
+ # project_folder parameter is the authoritative source for project name
2659
+ if self.project_folder_override:
2779
2660
  project_name = self.project_folder_override
2780
- # Automatically inject into config for consistency
2781
- if "operational_mode" not in self.config:
2782
- self.config["operational_mode"] = {}
2783
- if "cloud" not in self.config["operational_mode"]:
2784
- self.config["operational_mode"]["cloud"] = {}
2785
- self.config["operational_mode"]["cloud"]["adls_folder"] = project_name
2786
2661
  self.logger.debug(
2787
- "Set adls_folder from project_folder parameter: '%s'", project_name
2662
+ "Using project folder as project name: '%s'", project_name
2788
2663
  )
2789
2664
  else:
2790
- # Use default if neither is set
2791
2665
  project_name = "proj_unnamed"
2792
2666
  self.logger.debug("Using default project name: '%s'", project_name)
2793
2667
 
@@ -3926,10 +3800,10 @@ class OrcaOrchestrator:
3926
3800
  error_config = self.config.get("error_handling", {})
3927
3801
  create_dump = error_config.get("create_error_dump", True)
3928
3802
  include_full_scenario = error_config.get(
3929
- "include_all_files_in_dump", False
3803
+ "include_all_files_in_dump", True
3930
3804
  )
3931
3805
 
3932
- if create_dump:
3806
+ if create_dump and self.mode == "cloud_production":
3933
3807
  # Create error dump when command fails
3934
3808
  self.logger.info(
3935
3809
  "Creating error dump for failed step: %s", step_name
@@ -3939,7 +3813,6 @@ class OrcaOrchestrator:
3939
3813
  iteration=display_iteration,
3940
3814
  error_message=error_msg,
3941
3815
  include_all_scenario=include_full_scenario,
3942
- export_log=True,
3943
3816
  )
3944
3817
 
3945
3818
  if dump_success:
@@ -4290,26 +4163,19 @@ class OrcaOrchestrator:
4290
4163
 
4291
4164
  def _get_adls_config(self):
4292
4165
  """
4293
- Get ADLS configuration from config file.
4166
+ Get ADLS configuration from environment variables and the project_folder parameter.
4294
4167
 
4295
4168
  Returns:
4296
4169
  tuple: (adls_url, adls_container, adls_folder)
4297
4170
  """
4298
- cloud_config = self.config["operational_mode"].get("cloud", {})
4171
+ adls_base = get_env_var("ORCA_ADLS_URL", required=True)
4172
+ adls_url = f"https://{adls_base}"
4299
4173
 
4300
- adls_url = cloud_config.get("adls_url")
4301
- if adls_url is None:
4302
- adls_base = get_env_var("ORCA_ADLS_URL", required=True)
4303
- adls_url = f"https://{adls_base}"
4304
-
4305
- adls_container = cloud_config.get("adls_container", "raw")
4174
+ adls_container = get_env_var("ORCA_ADLS_CONTAINER", required=True)
4306
4175
 
4307
- # Use project_folder override if provided, otherwise use config value
4176
+ adls_folder = self.project_folder_override or "proj_unnamed"
4308
4177
  if self.project_folder_override:
4309
- adls_folder = self.project_folder_override
4310
4178
  self.logger.info("Using project folder override: %s", adls_folder)
4311
- else:
4312
- adls_folder = cloud_config.get("adls_folder", "proj_unnamed")
4313
4179
 
4314
4180
  return adls_url, adls_container, adls_folder
4315
4181
 
@@ -7030,8 +6896,18 @@ class OrcaOrchestrator:
7030
6896
  """
7031
6897
  Print a summary of the model execution results.
7032
6898
  """
6899
+ status = self.state.get("status") if self.state else None
6900
+ all_complete = self.state.is_all_iterations_complete() if self.state else False
6901
+
6902
+ if status == "error":
6903
+ header = "[ERROR] MODEL RUN FAILED"
6904
+ elif all_complete:
6905
+ header = "[OK] MODEL RUN COMPLETED"
6906
+ else:
6907
+ header = "[OK] MODEL RUN IS PARTIALLY COMPLETED WITH NO ERRORS"
6908
+
7033
6909
  print("\n" + "=" * 70)
7034
- print("[OK] MODEL EXECUTION COMPLETED")
6910
+ print(header)
7035
6911
  print("=" * 70)
7036
6912
 
7037
6913
  if self.state:
@@ -7911,6 +7787,7 @@ class OrcaUtil:
7911
7787
  # Required environment variables for cloud operations
7912
7788
  required_env_vars = [
7913
7789
  "ORCA_ADLS_URL",
7790
+ "ORCA_ADLS_CONTAINER",
7914
7791
  "BATCH_ACCOUNT_ENDPOINT",
7915
7792
  "IMAGE_REGISTRY_ENDPOINT",
7916
7793
  "AZURE_SUBSCRIPTION_ID",
@@ -7965,11 +7842,10 @@ class OrcaUtil:
7965
7842
  if tenant_name_output:
7966
7843
  print(f"[INFO] tenantDisplayName: {tenant_name_output}")
7967
7844
 
7968
- # Get cloud configuration from orchestrator
7969
- cloud_config = orchestrator.config.get("operational_mode", {}).get("cloud", {})
7970
- adls_url = cloud_config.get("adls_url", "")
7971
- adls_container = cloud_config.get("adls_container", "")
7972
- adls_folder = cloud_config.get("adls_folder", "")
7845
+ # Get cloud configuration from environment variables
7846
+ adls_url = os.getenv("ORCA_ADLS_URL", "")
7847
+ adls_container = os.getenv("ORCA_ADLS_CONTAINER", "")
7848
+ adls_folder = orchestrator.project_folder_override or ""
7973
7849
 
7974
7850
  # Print detailed configuration status
7975
7851
  print("\n" + "=" * 70)
@@ -530,14 +530,48 @@ class TestOrcaOrchestrator(unittest.TestCase):
530
530
  self.assertEqual(len(archive_files), 0)
531
531
 
532
532
  def test_get_adls_config(self):
533
- """Test ADLS config retrieval."""
534
- orchestrator = self._create_test_orchestrator()
533
+ """Test ADLS config retrieval.
534
+
535
+ Loads .env.example to verify it contains the required variables, then
536
+ uses those values to exercise _get_adls_config so the test breaks if
537
+ either the code or the example file drops a required variable.
538
+ """
539
+ env_example_path = os.path.join(
540
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
541
+ ".env.example",
542
+ )
543
+ self.assertTrue(
544
+ os.path.exists(env_example_path),
545
+ f".env.example not found at {env_example_path}",
546
+ )
547
+
548
+ # Parse .env.example into a dict (skip comments and blank lines)
549
+ env_vars = {}
550
+ with open(env_example_path) as f:
551
+ for line in f:
552
+ line = line.strip()
553
+ if line and not line.startswith("#") and "=" in line:
554
+ key, _, value = line.partition("=")
555
+ env_vars[key.strip()] = value.strip()
556
+
557
+ self.assertIn(
558
+ "ORCA_ADLS_URL", env_vars, "ORCA_ADLS_URL missing from .env.example"
559
+ )
560
+ self.assertIn(
561
+ "ORCA_ADLS_CONTAINER",
562
+ env_vars,
563
+ "ORCA_ADLS_CONTAINER missing from .env.example",
564
+ )
565
+
566
+ with patch.dict(os.environ, env_vars):
567
+ orchestrator = self._create_test_orchestrator()
568
+ orchestrator.project_folder_override = "test_folder"
535
569
 
536
- adls_url, adls_container, adls_folder = orchestrator.get_adls_config()
570
+ adls_url, adls_container, adls_folder = orchestrator.get_adls_config()
537
571
 
538
- self.assertEqual(adls_url, "https://test.dfs.core.windows.net")
539
- self.assertEqual(adls_container, "test")
540
- self.assertEqual(adls_folder, "test_folder")
572
+ self.assertEqual(adls_url, f"https://{env_vars['ORCA_ADLS_URL']}")
573
+ self.assertEqual(adls_container, env_vars["ORCA_ADLS_CONTAINER"])
574
+ self.assertEqual(adls_folder, "test_folder")
541
575
 
542
576
  def test_input_data_initialization(self):
543
577
  """Test that input_data configuration is properly loaded."""
@@ -1250,11 +1250,11 @@ wheels = [
1250
1250
 
1251
1251
  [[package]]
1252
1252
  name = "filelock"
1253
- version = "3.31.1"
1253
+ version = "3.32.0"
1254
1254
  source = { registry = "https://pypi.org/simple" }
1255
- sdist = { url = "https://files.pythonhosted.org/packages/53/55/1e19b2b56a24a4b94624f7e819e1bb87fa6c5609dbaf621df3aa6568a761/filelock-3.31.1.tar.gz", hash = "sha256:9e0c4e88ebe90833c1beafd3a547ccbc0bf7f491cd3858c3ec7aed63efe02163", size = 196656, upload-time = "2026-07-20T03:14:32.414Z" }
1255
+ sdist = { url = "https://files.pythonhosted.org/packages/c0/80/8232b582c4b318b817cf1274ba74976b07b34d35ef439b3eb948f98645a1/filelock-3.32.0.tar.gz", hash = "sha256:7be2ad23a14607ccc71808e68fe30848aeace7058ace17852f68e2a68e310402", size = 213757, upload-time = "2026-07-21T13:17:42.898Z" }
1256
1256
  wheels = [
1257
- { url = "https://files.pythonhosted.org/packages/88/61/df1d9db18f188d0ae648956a1decadc0e3b77d0571474370fd01f28a82b1/filelock-3.31.1-py3-none-any.whl", hash = "sha256:9ea33146c780161bf67cb20c7cb26b651566820d65ad8dfdd79422602a2dcfc0", size = 97189, upload-time = "2026-07-20T03:14:31.307Z" },
1257
+ { url = "https://files.pythonhosted.org/packages/06/79/b4c714bef36bc4ec2beeae1e0c124f0223888cd8c6feb1cdc56038116920/filelock-3.32.0-py3-none-any.whl", hash = "sha256:d396bea984af47333ef05e50eae7eff88c84256de6112aea0ec48a233c064fe3", size = 97732, upload-time = "2026-07-21T13:17:41.55Z" },
1258
1258
  ]
1259
1259
 
1260
1260
  [[package]]
@@ -1970,7 +1970,7 @@ wheels = [
1970
1970
 
1971
1971
  [[package]]
1972
1972
  name = "jupyterlab"
1973
- version = "4.6.1"
1973
+ version = "4.6.2"
1974
1974
  source = { registry = "https://pypi.org/simple" }
1975
1975
  dependencies = [
1976
1976
  { name = "async-lru" },
@@ -1987,10 +1987,11 @@ dependencies = [
1987
1987
  { name = "tomli", marker = "python_full_version < '3.11'" },
1988
1988
  { name = "tornado" },
1989
1989
  { name = "traitlets" },
1990
+ { name = "typing-extensions", marker = "python_full_version < '3.12'" },
1990
1991
  ]
1991
- sdist = { url = "https://files.pythonhosted.org/packages/bc/2a/d6af53bfd45a43a5bfe7e40ba47ee7a8921a807daf4bb708e3a295bbb54d/jupyterlab-4.6.1.tar.gz", hash = "sha256:75315982ed28427edaa62bb85eadb5105e4043a757643c910efd787fe6ed0837", size = 28179125, upload-time = "2026-06-29T12:48:45.402Z" }
1992
+ sdist = { url = "https://files.pythonhosted.org/packages/7a/7f/51c0c856ab286bdaf5709cf61ed13584ed9d4bee906479707da45b11b353/jupyterlab-4.6.2.tar.gz", hash = "sha256:e18ce8b34f3de350e93cd5b2c4f3ae884cbe266eb76bf5d6825a4ed34c13bcff", size = 28183650, upload-time = "2026-07-21T12:05:24.051Z" }
1992
1993
  wheels = [
1993
- { url = "https://files.pythonhosted.org/packages/5a/81/90ac6cc31d248e83a0d1eab343a5e6e68bc783d3f74fbe61640f42a61da4/jupyterlab-4.6.1-py3-none-any.whl", hash = "sha256:85a58546c831f3dce6cf919468c26874c9065e99c42279fb4abb8e1b552a98bb", size = 17164660, upload-time = "2026-06-29T12:48:41.21Z" },
1994
+ { url = "https://files.pythonhosted.org/packages/63/1f/e39b248c76bb3736bc05a9491a6aa1315414c32dea12f548ecc1b24c758e/jupyterlab-4.6.2-py3-none-any.whl", hash = "sha256:5964447036629adfcd3fc0969effc1da6f47d2cbd0a60b2c2eea7c31be0ec6a8", size = 17166703, upload-time = "2026-07-21T12:05:19.818Z" },
1994
1995
  ]
1995
1996
 
1996
1997
  [[package]]
@@ -3247,11 +3248,11 @@ wheels = [
3247
3248
 
3248
3249
  [[package]]
3249
3250
  name = "platformdirs"
3250
- version = "4.10.1"
3251
+ version = "4.11.0"
3251
3252
  source = { registry = "https://pypi.org/simple" }
3252
- sdist = { url = "https://files.pythonhosted.org/packages/52/cd/4f25b2f95b23f5d2c9c1fe43e49841bff5800562149b2666afc09309aa8f/platformdirs-4.10.1.tar.gz", hash = "sha256:ceab4084426fe6319ce18e86deada8ab1b7487c7aee7040c55e277c9ae793695", size = 31678, upload-time = "2026-07-18T03:53:43.808Z" }
3253
+ sdist = { url = "https://files.pythonhosted.org/packages/78/9b/560e4be8e26f6fd133a03630a8df0c663b9e8d61b4ade152b72005aec83b/platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", size = 31953, upload-time = "2026-07-21T13:09:36.565Z" }
3253
3254
  wheels = [
3254
- { url = "https://files.pythonhosted.org/packages/ec/73/6fd0bb9ce84138c3857f12e9de63bc901852975a092d545f18087a204aa2/platformdirs-4.10.1-py3-none-any.whl", hash = "sha256:0e4eff26be2d75293977f7cddc153fd9b8eaa7fb0c7b64ffe4076cb443117443", size = 22906, upload-time = "2026-07-18T03:53:42.576Z" },
3255
+ { url = "https://files.pythonhosted.org/packages/7d/68/d8d58938dfb1370b266a1a729e6d77a985be23689a0496498ee17b2cbf90/platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74", size = 23247, upload-time = "2026-07-21T13:09:35.422Z" },
3255
3256
  ]
3256
3257
 
3257
3258
  [[package]]
@@ -3278,30 +3279,30 @@ wheels = [
3278
3279
 
3279
3280
  [[package]]
3280
3281
  name = "polars"
3281
- version = "1.42.1"
3282
+ version = "1.43.0"
3282
3283
  source = { registry = "https://pypi.org/simple" }
3283
3284
  dependencies = [
3284
3285
  { name = "polars-runtime-32" },
3285
3286
  ]
3286
- sdist = { url = "https://files.pythonhosted.org/packages/27/99/fe77f10a13a778705ef05b499fc708c9a0b0a3680d9eb6bc6e1b6a6b9914/polars-1.42.1.tar.gz", hash = "sha256:2fe94f3059334650bd850ae19a9c165dcd5d9cb12cd95ea04de2201662e70e8a", size = 741532, upload-time = "2026-06-30T04:57:51.504Z" }
3287
+ sdist = { url = "https://files.pythonhosted.org/packages/66/5b/5d0f0aa53c6e9a8ecbc99ff502edcf9584e5d08ab34ea407c086999103d5/polars-1.43.0.tar.gz", hash = "sha256:bb2c67553e4968c18dfe268a88ff9a5790d5c2e0b7ea7efe97640b9a90438c88", size = 749537, upload-time = "2026-07-21T04:30:25.966Z" }
3287
3288
  wheels = [
3288
- { url = "https://files.pythonhosted.org/packages/be/6a/edd939cc6fa04b6415aaa9bf19720fc74ead81234b3d38542e0005816d4d/polars-1.42.1-py3-none-any.whl", hash = "sha256:3c0c65cdfa21a621650c4bdcbbccf93964d052fd766c3e70e84a55d961c259fd", size = 837622, upload-time = "2026-06-30T04:56:34.686Z" },
3289
+ { url = "https://files.pythonhosted.org/packages/76/28/a8eac2c1d1b2d2a4ba2eb745921616d863185d94b1afd91cbb07af9ef21a/polars-1.43.0-py3-none-any.whl", hash = "sha256:c49078b14e2d6b8ff5cc5b78b6d9638603ea5dffafb889d9204818822f55b813", size = 846493, upload-time = "2026-07-21T04:29:06.68Z" },
3289
3290
  ]
3290
3291
 
3291
3292
  [[package]]
3292
3293
  name = "polars-runtime-32"
3293
- version = "1.42.1"
3294
+ version = "1.43.0"
3294
3295
  source = { registry = "https://pypi.org/simple" }
3295
- sdist = { url = "https://files.pythonhosted.org/packages/1f/59/15bcc4dac380c6d63efa5446d8317f22671cbd6c9dadd576bd17a334c45a/polars_runtime_32-1.42.1.tar.gz", hash = "sha256:4d4809e1c1b9a6611f6944f27b24abea902b5159e6b6fa262fd716e947af5afd", size = 3045460, upload-time = "2026-06-30T04:57:52.866Z" }
3296
+ sdist = { url = "https://files.pythonhosted.org/packages/86/96/7e714cad082e9e6aaebb8886fcb1b0220d5c35149d4c8d3466bd7e7d581e/polars_runtime_32-1.43.0.tar.gz", hash = "sha256:5fb47a3a883402e62eab2fde5922f78c531d037aeece3640c15225f39228621e", size = 3090044, upload-time = "2026-07-21T04:30:27.185Z" }
3296
3297
  wheels = [
3297
- { url = "https://files.pythonhosted.org/packages/62/29/16ff6e4e91d71e530d3581f45e342a9cc35072ac6b31dcbc2fa33de2569e/polars_runtime_32-1.42.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bbdc26d68ee5b23b0ce227fa0599220aa35b77c826b6b0a6b2d8e7f6c1c36974", size = 53117325, upload-time = "2026-06-30T04:56:37.972Z" },
3298
- { url = "https://files.pythonhosted.org/packages/04/8e/4f8296fcfd1347f1351342fecf13bf2430d7efbae2f1f45964ec7930a99e/polars_runtime_32-1.42.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:f6c0288be940b607dc4a7476c01e67fb6bbee93f5f1dd42c64970274c71008ba", size = 47446251, upload-time = "2026-06-30T04:56:41.459Z" },
3299
- { url = "https://files.pythonhosted.org/packages/88/2e/0d66a7deadc453b890c3391034ca8ab4b05d0beaebbb92a7d65199fba61b/polars_runtime_32-1.42.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635d9dbcae2302ae223afb395d5cd220bffa61a53d0ab6871d17c8bc830101cf", size = 51359402, upload-time = "2026-06-30T04:56:44.595Z" },
3300
- { url = "https://files.pythonhosted.org/packages/5d/10/ffb85fa380bc9c9000dc35f40f44954dde49023018501c54faab94b3a39e/polars_runtime_32-1.42.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d059e8e53cc114ff82f9bd791fd341dc53534a2c745e6f6aa37594c3a93f01fe", size = 57302723, upload-time = "2026-06-30T04:56:47.609Z" },
3301
- { url = "https://files.pythonhosted.org/packages/c2/63/ca50adc62e44224ca5c622a842ba6f35ee87d1d40ef0df7ea2ed6c6edb08/polars_runtime_32-1.42.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f91f0b13588324905682809d270e1de5f1990c908721c8527657d77a044c9919", size = 51515673, upload-time = "2026-06-30T04:56:50.88Z" },
3302
- { url = "https://files.pythonhosted.org/packages/63/c3/08fbbf38deaa17bf34a601d327cb7451074098673c78b7c1a8538dde9794/polars_runtime_32-1.42.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b8bf972d99d48aaaa2582e2bce966a6f43bc815bd8725d15f5cab9e2fb15d17", size = 55217259, upload-time = "2026-06-30T04:56:53.834Z" },
3303
- { url = "https://files.pythonhosted.org/packages/d1/0e/51db89361668fe077a835fc579277f824ba526e7daf7b94d23d25439e0d0/polars_runtime_32-1.42.1-cp310-abi3-win_amd64.whl", hash = "sha256:e9364c26da389a8b7339e4d29e20a3d12af730247e6ed3b7804bddce2477f428", size = 52715432, upload-time = "2026-06-30T04:56:57.109Z" },
3304
- { url = "https://files.pythonhosted.org/packages/76/c5/2fb8592d691bd114de25d9c84300b23541dca7060eac11d7b4bed0327786/polars_runtime_32-1.42.1-cp310-abi3-win_arm64.whl", hash = "sha256:7051226e6b42ffc395a7a9190377cd28649fbfb991b8f85c6271f4e1cfb736fb", size = 46718300, upload-time = "2026-06-30T04:56:59.855Z" },
3298
+ { url = "https://files.pythonhosted.org/packages/0f/14/9b1f5eb1c5104ba1ceb380a7308ffcd979f3ce1df86e76293062698f2ff1/polars_runtime_32-1.43.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6707193d30a7135bce0424304f76d8145270527444097548e794ad8d26823b70", size = 53059463, upload-time = "2026-07-21T04:29:09.194Z" },
3299
+ { url = "https://files.pythonhosted.org/packages/ca/14/73d77d1c0c928eb599d9516d874af0cd2b6225201e1327a6c4857e6776d0/polars_runtime_32-1.43.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:78ca2f97740b2a6beb36eb112749280b5e08750c60f53c08d2feffebdba9d35a", size = 47499586, upload-time = "2026-07-21T04:29:13.229Z" },
3300
+ { url = "https://files.pythonhosted.org/packages/80/b1/98278fa796f93d0975fd3fe1d4ab4031707d4a9f1da44c21c29996b62c73/polars_runtime_32-1.43.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa99bb2c7ee0a9392ae50b350af0ed17acf0519d15c75fe223021798566174", size = 51326702, upload-time = "2026-07-21T04:29:16.084Z" },
3301
+ { url = "https://files.pythonhosted.org/packages/ab/7d/24ae73389aac03296925973c4e2cbe2e4982e859b9fad69eb1a72b9026fa/polars_runtime_32-1.43.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ecc8feaf04de5989a29245885921db612ef1ce9065e5cb6ec37495acfa55bba", size = 57266705, upload-time = "2026-07-21T04:29:19.288Z" },
3302
+ { url = "https://files.pythonhosted.org/packages/5c/49/2026be1f7b51242ad62e08b20728462558e161fb84b564e5b986f2b38664/polars_runtime_32-1.43.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:01e1471a5ee161a969c7a96991d8e5d20b97a0b7fe075df9c7a01f52a49c5ac2", size = 51484129, upload-time = "2026-07-21T04:29:22.639Z" },
3303
+ { url = "https://files.pythonhosted.org/packages/db/e2/047c7695f08a9b18614c0e1ec5e70a754455542a21a6d52955f7f05c6268/polars_runtime_32-1.43.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9cd8b813afe67d59e87027dedcaf5c6a06fe602472fd74e1a49f4bafea47259c", size = 55169401, upload-time = "2026-07-21T04:29:25.902Z" },
3304
+ { url = "https://files.pythonhosted.org/packages/92/dc/bfd2533c487563c7a21ab7d7af3d78f820b0236e14dc5ee63d46188cd275/polars_runtime_32-1.43.0-cp310-abi3-win_amd64.whl", hash = "sha256:41a75fb3cb4cc574eb21801383578f75cfc374597c22322ef457ab7bac8a3301", size = 52541527, upload-time = "2026-07-21T04:29:29.162Z" },
3305
+ { url = "https://files.pythonhosted.org/packages/46/d7/5c47f1bf57479d1671669af9c421f9abf4986b2ddaa64c177244ff811de0/polars_runtime_32-1.43.0-cp310-abi3-win_arm64.whl", hash = "sha256:c285e598dd91e08560e519275b8b8108adbafb438d218a175ebe073dbc2027fb", size = 46552281, upload-time = "2026-07-21T04:29:32.224Z" },
3305
3306
  ]
3306
3307
 
3307
3308
  [[package]]
@@ -3840,15 +3841,15 @@ wheels = [
3840
3841
 
3841
3842
  [[package]]
3842
3843
  name = "python-discovery"
3843
- version = "1.4.4"
3844
+ version = "1.5.0"
3844
3845
  source = { registry = "https://pypi.org/simple" }
3845
3846
  dependencies = [
3846
3847
  { name = "filelock" },
3847
3848
  { name = "platformdirs" },
3848
3849
  ]
3849
- sdist = { url = "https://files.pythonhosted.org/packages/4c/81/58c70036dffeccb7fe7d79d6260c69f7a28272bbd3909c29a01ea9422744/python_discovery-1.4.4.tar.gz", hash = "sha256:5cad33982d412c1f3ffb8f9ca4ea292c9680bca3942451d30b69c37fce53a4a3", size = 72212, upload-time = "2026-07-08T23:06:50.691Z" }
3850
+ sdist = { url = "https://files.pythonhosted.org/packages/f1/51/276f964496a5714ab9f320896195639086881c2b39c03b5ad13de84acbb8/python_discovery-1.5.0.tar.gz", hash = "sha256:3e014c6327154d3dda27939a9a0dc9c5c000439f1906d3f303b48f984bd2ecef", size = 72483, upload-time = "2026-07-21T13:14:14.641Z" }
3850
3851
  wheels = [
3851
- { url = "https://files.pythonhosted.org/packages/9d/ae/84bc0d2440c95772272bb6f4b3d09ccf08b2898fce89b3d4f969a9fc74e9/python_discovery-1.4.4-py3-none-any.whl", hash = "sha256:abebe9120b43453b68c908acfb1e72a19d1a959ed2cb620ad38fc57d08056dbe", size = 34181, upload-time = "2026-07-08T23:06:49.402Z" },
3852
+ { url = "https://files.pythonhosted.org/packages/c7/7b/14882602ddee241d7984a742fcb423cb4a30fb0d6efc546ac3129fba475a/python_discovery-1.5.0-py3-none-any.whl", hash = "sha256:70c4fc61b4e7404e44f01d6fc44a715c4d685ca6cea83d295922f05891877c98", size = 34205, upload-time = "2026-07-21T13:14:13.398Z" },
3852
3853
  ]
3853
3854
 
3854
3855
  [[package]]
@@ -4495,11 +4496,11 @@ wheels = [
4495
4496
 
4496
4497
  [[package]]
4497
4498
  name = "soupsieve"
4498
- version = "2.9"
4499
+ version = "2.9.1"
4499
4500
  source = { registry = "https://pypi.org/simple" }
4500
- sdist = { url = "https://files.pythonhosted.org/packages/80/f1/93422647dd7e461f23d254e6b2bfa687a85b53aeb4903fcdbb74474d4584/soupsieve-2.9.tar.gz", hash = "sha256:acee8417325c5653e1377dc31eccad59eb82cbc65942afe6174c53b3aaad63fc", size = 122122, upload-time = "2026-07-19T01:35:18.425Z" }
4501
+ sdist = { url = "https://files.pythonhosted.org/packages/d9/38/e12680bbe6b4f8f3d17adcaf38d26850aa756c85cf4a80e79fc12a018fe8/soupsieve-2.9.1.tar.gz", hash = "sha256:c33e6605bbc71dd628b00c632d58ae607c22bade247e52553928f83bbb75b4ba", size = 122261, upload-time = "2026-07-21T16:57:17.452Z" }
4501
4502
  wheels = [
4502
- { url = "https://files.pythonhosted.org/packages/7b/d6/3185ab5ad1280319b31986898f3206dd7227cd75e293d4dba2a5e6bf27a0/soupsieve-2.9-py3-none-any.whl", hash = "sha256:a2b2c76d67df2382d245409fd71e321a571717e58463efa32ace87dcadac2c12", size = 37387, upload-time = "2026-07-19T01:35:17.106Z" },
4503
+ { url = "https://files.pythonhosted.org/packages/0f/2c/437fe806897c2d6cfdc3ee43a18da8bf8e568530a4ae9bac781541ca9896/soupsieve-2.9.1-py3-none-any.whl", hash = "sha256:4f4477399246b7a0c720a88ca2454b11cd6bb9ae4c9d170140786e916776c14c", size = 37404, upload-time = "2026-07-21T16:57:16.421Z" },
4503
4504
  ]
4504
4505
 
4505
4506
  [[package]]
@@ -4652,7 +4653,7 @@ wheels = [
4652
4653
 
4653
4654
  [[package]]
4654
4655
  name = "tlpytools"
4655
- version = "0.2.4"
4656
+ version = "0.2.5"
4656
4657
  source = { editable = "." }
4657
4658
  dependencies = [
4658
4659
  { name = "azure-core" },
@@ -4664,6 +4665,8 @@ dependencies = [
4664
4665
  { name = "cryptography" },
4665
4666
  { name = "cryptpandas" },
4666
4667
  { name = "ipykernel" },
4668
+ { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
4669
+ { name = "matplotlib", version = "3.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
4667
4670
  { name = "notebook" },
4668
4671
  { name = "numpy" },
4669
4672
  { name = "openmatrix" },
@@ -4746,6 +4749,7 @@ requires-dist = [
4746
4749
  { name = "geopandas", marker = "extra == 'dev'", specifier = ">=0.13.0" },
4747
4750
  { name = "ipykernel", specifier = ">=7.2.0" },
4748
4751
  { name = "kiwisolver", marker = "extra == 'dev'", specifier = ">=1.4.0" },
4752
+ { name = "matplotlib", specifier = ">=3.10.9" },
4749
4753
  { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.5.0" },
4750
4754
  { name = "notebook", specifier = ">=7.5.6" },
4751
4755
  { name = "numpy", specifier = ">=1.18" },
@@ -4925,7 +4929,7 @@ wheels = [
4925
4929
 
4926
4930
  [[package]]
4927
4931
  name = "virtualenv"
4928
- version = "21.6.1"
4932
+ version = "21.7.0"
4929
4933
  source = { registry = "https://pypi.org/simple" }
4930
4934
  dependencies = [
4931
4935
  { name = "distlib" },
@@ -4934,9 +4938,9 @@ dependencies = [
4934
4938
  { name = "python-discovery" },
4935
4939
  { name = "typing-extensions", marker = "python_full_version < '3.11'" },
4936
4940
  ]
4937
- sdist = { url = "https://files.pythonhosted.org/packages/34/d9/b477fddb68840b570af8b22afe9b035cbc277b5fb7b33dea390617a8b10f/virtualenv-21.6.1.tar.gz", hash = "sha256:15f978b7cd329f24855ff4a0c4b4899cc7678589f49adbdcbbb4d3232e641128", size = 5526620, upload-time = "2026-07-10T19:33:53.312Z" }
4941
+ sdist = { url = "https://files.pythonhosted.org/packages/fe/25/e367a7229b0914772ca8d81b41fde012d9feda68523b52644a571bb21ce8/virtualenv-21.7.0.tar.gz", hash = "sha256:7f9519b9432ff11b6e1a3e94061664efc2ff99ea21780e3cf4f6bd0a5da8b37c", size = 5527510, upload-time = "2026-07-21T13:12:14.109Z" }
4938
4942
  wheels = [
4939
- { url = "https://files.pythonhosted.org/packages/c1/7c/4e7225d46d634a0d8d534dd8a6ce0c319d09b4d0cf0337eb314ca4789d8c/virtualenv-21.6.1-py3-none-any.whl", hash = "sha256:afe991df855715a2b2f60edfcc0107ef95a79fdfd8cb4cdaa71603d1c12e463b", size = 5506392, upload-time = "2026-07-10T19:33:51.629Z" },
4943
+ { url = "https://files.pythonhosted.org/packages/a5/7a/ae29312b1e88a22e81f5d21fc11526d2a114089776c2550d2b205b6c2a47/virtualenv-21.7.0-py3-none-any.whl", hash = "sha256:a8370c1c5530fbabf955e40b8fbbc68a431648b10f9433faa587db30a06e51dd", size = 5507078, upload-time = "2026-07-21T13:12:12.136Z" },
4940
4944
  ]
4941
4945
 
4942
4946
  [[package]]
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