rushti 2.2.0__tar.gz → 2.2.2__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 (62) hide show
  1. {rushti-2.2.0/src/rushti.egg-info → rushti-2.2.2}/PKG-INFO +1 -1
  2. {rushti-2.2.0 → rushti-2.2.2}/pyproject.toml +1 -1
  3. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/__init__.py +1 -1
  4. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/cli.py +43 -63
  5. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/tasks/__init__.py +3 -3
  6. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/tasks/push.py +12 -2
  7. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/settings.py +71 -5
  8. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/taskfile.py +2 -0
  9. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/tm1_build.py +11 -4
  10. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/tm1_objects.py +181 -3
  11. {rushti-2.2.0 → rushti-2.2.2/src/rushti.egg-info}/PKG-INFO +1 -1
  12. {rushti-2.2.0 → rushti-2.2.2}/LICENSE +0 -0
  13. {rushti-2.2.0 → rushti-2.2.2}/README.md +0 -0
  14. {rushti-2.2.0 → rushti-2.2.2}/setup.cfg +0 -0
  15. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/_shlex_utils.py +0 -0
  16. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/app_paths.py +0 -0
  17. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/checkpoint.py +0 -0
  18. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/__init__.py +0 -0
  19. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/build.py +0 -0
  20. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/db.py +0 -0
  21. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/resume.py +0 -0
  22. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/stats/__init__.py +0 -0
  23. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/stats/analyze.py +0 -0
  24. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/stats/export.py +0 -0
  25. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/stats/list.py +0 -0
  26. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/stats/optimize.py +0 -0
  27. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/stats/visualize.py +0 -0
  28. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/tasks/expand.py +0 -0
  29. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/tasks/export.py +0 -0
  30. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/tasks/validate.py +0 -0
  31. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/commands/tasks/visualize.py +0 -0
  32. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/contention_analyzer.py +0 -0
  33. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/dag.py +0 -0
  34. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/dashboard.py +0 -0
  35. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/db_admin.py +0 -0
  36. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/exclusive.py +0 -0
  37. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/execution.py +0 -0
  38. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/logging.py +0 -0
  39. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/logging_setup.py +0 -0
  40. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/messages.py +0 -0
  41. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/optimization_report.py +0 -0
  42. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/optimizer.py +0 -0
  43. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/parsing.py +0 -0
  44. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/results_writer.py +0 -0
  45. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/stats/__init__.py +0 -0
  46. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/stats/dynamodb.py +0 -0
  47. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/stats/paths.py +0 -0
  48. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/stats/repository.py +0 -0
  49. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/stats/signature.py +0 -0
  50. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/stats/sqlite.py +0 -0
  51. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/task.py +0 -0
  52. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/taskfile_ops.py +0 -0
  53. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/templates/__init__.py +0 -0
  54. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/templates/visualization.html +0 -0
  55. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/tm1_integration.py +0 -0
  56. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/utils.py +0 -0
  57. {rushti-2.2.0 → rushti-2.2.2}/src/rushti/visualization_template.py +0 -0
  58. {rushti-2.2.0 → rushti-2.2.2}/src/rushti.egg-info/SOURCES.txt +0 -0
  59. {rushti-2.2.0 → rushti-2.2.2}/src/rushti.egg-info/dependency_links.txt +0 -0
  60. {rushti-2.2.0 → rushti-2.2.2}/src/rushti.egg-info/entry_points.txt +0 -0
  61. {rushti-2.2.0 → rushti-2.2.2}/src/rushti.egg-info/requires.txt +0 -0
  62. {rushti-2.2.0 → rushti-2.2.2}/src/rushti.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rushti
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: RushTI - Parallel TM1 TurboIntegrator Process Execution
5
5
  Author-email: nicolasbisurgi <nbisurgi@cubewise.com>, Marius Wirtz <mwirtz@cubewise.com>
6
6
  Maintainer: Cubewise CODE
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rushti"
7
- version = "2.2.0"
7
+ version = "2.2.2"
8
8
  description = "RushTI - Parallel TM1 TurboIntegrator Process Execution"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -5,7 +5,7 @@ This package provides tools for executing TM1 TI processes in parallel
5
5
  with dependency management, checkpoint/resume support, and execution logging.
6
6
  """
7
7
 
8
- __version__ = "2.2.0"
8
+ __version__ = "2.2.2"
9
9
  __app_name__ = "RushTI"
10
10
 
11
11
  # Core exports for programmatic use
@@ -29,7 +29,7 @@ from rushti.logging_setup import (
29
29
  )
30
30
  from rushti.results_writer import create_results_file
31
31
  from rushti.task import ExecutionMode
32
- from rushti.settings import load_settings, get_effective_settings
32
+ from rushti.settings import get_effective_settings, load_settings, resolve_tm1_instance
33
33
  from rushti.taskfile import (
34
34
  detect_file_type,
35
35
  parse_json_taskfile,
@@ -629,42 +629,6 @@ Use '{APP_NAME} <command> --help' for command-specific options and examples.
629
629
  # Load settings from settings.ini
630
630
  settings = load_settings(cli_args.get("settings_file"))
631
631
 
632
- # Apply CLI overrides to settings
633
- settings = get_effective_settings(settings, cli_args=cli_args)
634
-
635
- if settings.tm1_integration.detailed_results:
636
- logging.info(
637
- "Detailed results enabled: one cube row per executed TI. "
638
- "If you upgraded RushTI from an earlier release, run "
639
- "'rushti build --tm1-instance <instance>' once to add the "
640
- "'original_task_id' measure to the rushti_measure dimension. "
641
- "The build is non-destructive — existing cube data is preserved."
642
- )
643
-
644
- # Extract final values (CLI overrides settings.ini)
645
- max_workers = (
646
- cli_args["max_workers"]
647
- if cli_args.get("max_workers") is not None
648
- else settings.defaults.max_workers
649
- )
650
- process_execution_retries = (
651
- cli_args["retries"] if cli_args.get("retries") is not None else settings.defaults.retries
652
- )
653
- result_file = (
654
- cli_args["output_file"]
655
- if cli_args.get("output_file") is not None
656
- else settings.defaults.result_file
657
- )
658
- # Resolve result file path relative to application directory (skip if empty/None)
659
- if result_file:
660
- from rushti.utils import resolve_app_path
661
-
662
- result_file = resolve_app_path(result_file)
663
-
664
- logger.debug(
665
- f"Effective settings: max_workers={max_workers}, retries={process_execution_retries}, result_file={result_file}"
666
- )
667
-
668
632
  # Handle TM1 source if specified
669
633
  tm1_instance = cli_args.get("tm1_instance")
670
634
  workflow = cli_args.get("workflow")
@@ -701,6 +665,7 @@ Use '{APP_NAME} <command> --help' for command-specific options and examples.
701
665
 
702
666
  # Determine workflow and exclusive mode from task file
703
667
  # Parse task file early to get metadata for session context
668
+ taskfile_preview = None
704
669
  if tm1_taskfile:
705
670
  # Using TM1 source
706
671
  file_type = "json" # TM1 taskfiles are treated as JSON format
@@ -727,6 +692,36 @@ Use '{APP_NAME} <command> --help' for command-specific options and examples.
727
692
  logger.error(str(e))
728
693
  sys.exit(1)
729
694
 
695
+ # Merge taskfile JSON settings (tier 2) into the effective settings
696
+ # before any setting is read. CLI args overlay JSON, which overlays
697
+ # settings.ini, which overlays built-in defaults.
698
+ effective_taskfile = tm1_taskfile or taskfile_preview
699
+ json_settings_dict = effective_taskfile.settings.to_dict() if effective_taskfile else {}
700
+ settings = get_effective_settings(settings, json_settings=json_settings_dict, cli_args=cli_args)
701
+
702
+ if settings.tm1_integration.detailed_results:
703
+ logging.info(
704
+ "Detailed results enabled: one cube row per executed TI. "
705
+ "If you upgraded RushTI from an earlier release, run "
706
+ "'rushti build --tm1-instance <instance>' once to add the "
707
+ "'original_task_id' measure to the rushti_measure dimension. "
708
+ "The build is non-destructive — existing cube data is preserved."
709
+ )
710
+
711
+ # Extract effective execution params (CLI > taskfile JSON > settings.ini > defaults)
712
+ max_workers = settings.defaults.max_workers
713
+ process_execution_retries = settings.defaults.retries
714
+ result_file = settings.defaults.result_file
715
+ if result_file:
716
+ from rushti.utils import resolve_app_path
717
+
718
+ result_file = resolve_app_path(result_file)
719
+
720
+ logger.debug(
721
+ f"Effective settings: max_workers={max_workers}, "
722
+ f"retries={process_execution_retries}, result_file={result_file}"
723
+ )
724
+
730
725
  # Apply exclusive from settings.ini if not set via CLI or JSON
731
726
  if exclusive_mode is None:
732
727
  exclusive_mode = settings.exclusive_mode.enabled
@@ -824,16 +819,6 @@ Use '{APP_NAME} <command> --help' for command-specific options and examples.
824
819
  )
825
820
  dag.validate()
826
821
  taskfile = tm1_taskfile
827
- # Apply settings from TM1 taskfile (CLI still overrides)
828
- if taskfile.settings.max_workers and cli_args.get("max_workers") is None:
829
- max_workers = taskfile.settings.max_workers
830
- if taskfile.settings.retries is not None and cli_args.get("retries") is None:
831
- process_execution_retries = taskfile.settings.retries
832
- if taskfile.settings.result_file and cli_args.get("output_file") is None:
833
- result_file = resolve_app_path(taskfile.settings.result_file)
834
- logger.debug(
835
- f"Applied TM1 taskfile settings: max_workers={max_workers}, retries={process_execution_retries}"
836
- )
837
822
  else:
838
823
  # Build DAG from file with dependency validation
839
824
  dag_result = build_dag(
@@ -846,16 +831,6 @@ Use '{APP_NAME} <command> --help' for command-specific options and examples.
846
831
  taskfile = None
847
832
  if isinstance(dag_result, tuple):
848
833
  dag, taskfile = dag_result
849
- # Apply JSON settings (CLI still overrides)
850
- if taskfile.settings.max_workers and cli_args.get("max_workers") is None:
851
- max_workers = taskfile.settings.max_workers
852
- if taskfile.settings.retries is not None and cli_args.get("retries") is None:
853
- process_execution_retries = taskfile.settings.retries
854
- if taskfile.settings.result_file and cli_args.get("output_file") is None:
855
- result_file = resolve_app_path(taskfile.settings.result_file)
856
- logger.debug(
857
- f"Applied JSON settings: max_workers={max_workers}, retries={process_execution_retries}"
858
- )
859
834
  else:
860
835
  dag = dag_result
861
836
  # TXT source: convert to Taskfile for archiving
@@ -1117,12 +1092,15 @@ Use '{APP_NAME} <command> --help' for command-specific options and examples.
1117
1092
  assign_unique_task_ids,
1118
1093
  )
1119
1094
 
1120
- # CLI --tm1-instance wins over default_tm1_instance for every
1121
- # TM1 operation in this run (taskfile read, results push,
1122
- # auto_load_results). Silent precedence — no extra warning
1123
- # when CLI overrides default.
1124
- upload_instance = tm1_instance or settings.tm1_integration.default_tm1_instance
1095
+ upload_instance, source_tier = resolve_tm1_instance(
1096
+ tm1_instance, settings, json_settings_dict
1097
+ )
1125
1098
  if upload_instance:
1099
+ logger.info(
1100
+ "Result upload target: %s (source: %s)",
1101
+ upload_instance,
1102
+ source_tier,
1103
+ )
1126
1104
  tm1_upload = connect_to_tm1_instance(
1127
1105
  upload_instance,
1128
1106
  CONFIG,
@@ -1173,7 +1151,9 @@ Use '{APP_NAME} <command> --help' for command-specific options and examples.
1173
1151
  tm1_upload.logout()
1174
1152
  else:
1175
1153
  logger.warning(
1176
- "push_results enabled but default_tm1_instance not configured"
1154
+ "push_results enabled but no TM1 instance configured "
1155
+ "(CLI --tm1-instance, taskfile settings.tm1_instance, "
1156
+ "or settings.ini tm1_instance)."
1177
1157
  )
1178
1158
  except Exception as e:
1179
1159
  logger.warning(f"Failed to upload results to TM1 (non-blocking): {e}")
@@ -108,10 +108,10 @@ Examples:
108
108
  add_taskfile_source_args(push_parser, required=False, include_settings=True)
109
109
  push_parser.add_argument(
110
110
  "--target-tm1-instance",
111
- dest="target_tm1_instance",
112
- default=None,
111
+ dest="tm1_instance",
112
+ default=argparse.SUPPRESS,
113
113
  metavar="INSTANCE",
114
- help="Target TM1 instance for push (defaults to --tm1-instance if loading from TM1)",
114
+ help="[DEPRECATED] Alias for --tm1-instance. Will be removed in a future major version.",
115
115
  )
116
116
  push_parser.add_argument(
117
117
  "--mode",
@@ -1,9 +1,12 @@
1
1
  """rushti tasks push — upload JSON taskfile to TM1 as a file."""
2
2
 
3
+ import logging
3
4
  import os
4
5
  import sys
5
6
  from pathlib import Path
6
7
 
8
+ logger = logging.getLogger(__name__)
9
+
7
10
 
8
11
  def handle_tasks_push(args, config_path: str) -> None:
9
12
  """Handle tasks push action.
@@ -11,6 +14,13 @@ def handle_tasks_push(args, config_path: str) -> None:
11
14
  :param args: Parsed arguments
12
15
  :param config_path: Path to config.ini
13
16
  """
17
+ if "--target-tm1-instance" in sys.argv[1:]:
18
+ logger.warning(
19
+ "DEPRECATION: --target-tm1-instance is deprecated. "
20
+ "Use --tm1-instance instead. The deprecated flag will continue "
21
+ "to work but may be removed in a future major version."
22
+ )
23
+
14
24
  if not args.taskfile_path:
15
25
  print("Error: --tasks is required for --push (must be a local JSON file)")
16
26
  sys.exit(1)
@@ -19,9 +29,9 @@ def handle_tasks_push(args, config_path: str) -> None:
19
29
  print(f"Error: Input file not found: {args.taskfile_path}")
20
30
  sys.exit(1)
21
31
 
22
- target_instance = args.target_tm1_instance or args.tm1_instance
32
+ target_instance = args.tm1_instance
23
33
  if not target_instance:
24
- print("Error: --tm1-instance or --target-tm1-instance is required for --push")
34
+ print("Error: --tm1-instance is required for --push")
25
35
  sys.exit(1)
26
36
 
27
37
  try:
@@ -69,6 +69,7 @@ class TM1IntegrationSettings:
69
69
  push_results: bool = False
70
70
  auto_load_results: bool = False
71
71
  detailed_results: bool = False
72
+ tm1_instance: Optional[str] = None
72
73
  default_tm1_instance: Optional[str] = None
73
74
  default_rushti_cube: str = "rushti"
74
75
  default_workflow_dim: str = "rushti_workflow"
@@ -155,6 +156,7 @@ SETTINGS_SCHEMA = {
155
156
  "push_results": bool,
156
157
  "auto_load_results": bool,
157
158
  "detailed_results": bool,
159
+ "tm1_instance": str,
158
160
  "default_tm1_instance": str,
159
161
  "default_rushti_cube": str,
160
162
  "default_workflow_dim": str,
@@ -365,9 +367,67 @@ def load_settings(settings_path: Optional[str] = None) -> Settings:
365
367
  # Resolve paths relative to application directory
366
368
  settings.resume.checkpoint_dir = resolve_app_path(settings.resume.checkpoint_dir)
367
369
 
370
+ if not settings.tm1_integration.tm1_instance and settings.tm1_integration.default_tm1_instance:
371
+ logger.warning(
372
+ "DEPRECATION: [tm1_integration].default_tm1_instance is deprecated. "
373
+ "Rename to tm1_instance. The deprecated key will continue to work "
374
+ "but may be removed in a future major version."
375
+ )
376
+
368
377
  return settings
369
378
 
370
379
 
380
+ def resolve_tm1_instance(
381
+ tm1_instance: Optional[str],
382
+ settings: Settings,
383
+ json_settings: Optional[Dict[str, Any]] = None,
384
+ ) -> tuple[Optional[str], str]:
385
+ """Resolve the effective TM1 instance for results-push, with source tier.
386
+
387
+ Walks the 4-tier precedence chain (highest to lowest), returning the
388
+ first non-empty value alongside a label identifying the tier:
389
+
390
+ 1. CLI argument (``tm1_instance``) → label ``"cli"``
391
+ 2. Taskfile JSON ``settings.tm1_instance`` → label ``"taskfile"``
392
+ 3. ``settings.ini [tm1_integration].tm1_instance`` → label
393
+ ``"settings.tm1_instance"``
394
+ 4. ``settings.ini [tm1_integration].default_tm1_instance`` (deprecated)
395
+ → label ``"settings.default_tm1_instance"``
396
+
397
+ Empty strings at any tier are treated as unset (fall through).
398
+
399
+ ``json_settings`` is the taskfile JSON ``settings`` dict (as returned by
400
+ ``TaskfileSettings.to_dict``). It is consulted separately so the
401
+ resolver can distinguish tier 2 from tier 3 — by the time
402
+ ``get_effective_settings`` has been applied, the JSON-sourced
403
+ ``tm1_instance`` has already overwritten the settings.ini value in the
404
+ merged ``Settings`` object, and the provenance is otherwise lost.
405
+
406
+ :param tm1_instance: Value from the CLI (e.g. ``run --tm1-instance``).
407
+ :param settings: Settings object (post-merge).
408
+ :param json_settings: Taskfile JSON settings dict; tier 2 lookup.
409
+ :return: ``(value, source_label)``; ``(None, "none")`` when all tiers
410
+ are empty.
411
+ """
412
+ if tm1_instance:
413
+ return tm1_instance, "cli"
414
+
415
+ if json_settings:
416
+ taskfile_value = json_settings.get("tm1_instance")
417
+ if taskfile_value:
418
+ return taskfile_value, "taskfile"
419
+
420
+ settings_value = settings.tm1_integration.tm1_instance
421
+ if settings_value:
422
+ return settings_value, "settings.tm1_instance"
423
+
424
+ deprecated_value = settings.tm1_integration.default_tm1_instance
425
+ if deprecated_value:
426
+ return deprecated_value, "settings.default_tm1_instance"
427
+
428
+ return None, "none"
429
+
430
+
371
431
  def get_effective_settings(
372
432
  settings: Settings,
373
433
  cli_args: Optional[Dict[str, Any]] = None,
@@ -419,14 +479,20 @@ def _apply_json_settings(settings: Settings, json_settings: Dict[str, Any]) -> N
419
479
  "push_results": ("tm1_integration", "push_results"),
420
480
  "auto_load_results": ("tm1_integration", "auto_load_results"),
421
481
  "detailed_results": ("tm1_integration", "detailed_results"),
482
+ "tm1_instance": ("tm1_integration", "tm1_instance"),
422
483
  }
423
484
 
424
485
  for json_key, (section, attr) in json_to_settings.items():
425
- if json_key in json_settings and json_settings[json_key] is not None:
426
- section_obj = getattr(settings, section)
427
- value = json_settings[json_key]
428
- setattr(section_obj, attr, value)
429
- logger.debug(f"JSON override: {section}.{attr} = {value}")
486
+ if json_key not in json_settings:
487
+ continue
488
+ value = json_settings[json_key]
489
+ if value is None:
490
+ continue
491
+ if isinstance(value, str) and value == "":
492
+ continue
493
+ section_obj = getattr(settings, section)
494
+ setattr(section_obj, attr, value)
495
+ logger.debug(f"JSON override: {section}.{attr} = {value}")
430
496
 
431
497
 
432
498
  def _apply_cli_args(settings: Settings, cli_args: Dict[str, Any]) -> None:
@@ -83,6 +83,7 @@ class TaskfileSettings:
83
83
  optimization_algorithm: Optional[str] = None
84
84
  push_results: Optional[bool] = None
85
85
  auto_load_results: Optional[bool] = None
86
+ tm1_instance: Optional[str] = None
86
87
  stage_order: Optional[List[str]] = None
87
88
  stage_workers: Optional[Dict[str, int]] = None
88
89
 
@@ -103,6 +104,7 @@ class TaskfileSettings:
103
104
  optimization_algorithm=data.get("optimization_algorithm"),
104
105
  push_results=data.get("push_results"),
105
106
  auto_load_results=data.get("auto_load_results"),
107
+ tm1_instance=data.get("tm1_instance"),
106
108
  stage_order=data.get("stage_order"),
107
109
  stage_workers=data.get("stage_workers"),
108
110
  )
@@ -32,16 +32,19 @@ from TM1py.Objects import (
32
32
  Process,
33
33
  Subset,
34
34
  )
35
+ from TM1py.Utils import integerize_version
35
36
 
36
37
  from rushti.tm1_objects import (
37
38
  MEASURE_ATTRIBUTES,
38
39
  MEASURE_ELEMENTS,
39
40
  PROCESS_DATA,
40
41
  PROCESS_DATASOURCE,
41
- PROCESS_EPILOG,
42
+ PROCESS_EPILOG_V11,
43
+ PROCESS_EPILOG_V12,
42
44
  PROCESS_METADATA,
43
45
  PROCESS_PARAMETERS,
44
- PROCESS_PROLOG,
46
+ PROCESS_PROLOG_V11,
47
+ PROCESS_PROLOG_V12,
45
48
  PROCESS_VARIABLES,
46
49
  RUN_ID_SEED_ELEMENTS,
47
50
  SAMPLE_DATA,
@@ -558,12 +561,16 @@ def _create_process(tm1: TM1Service, process_name: str, force: bool = False) ->
558
561
  logger.info(f"Replacing existing process: {process_name}")
559
562
  tm1.processes.delete(process_name)
560
563
 
564
+ is_v12 = integerize_version(tm1.version, 2) >= 12
565
+ prolog = PROCESS_PROLOG_V12 if is_v12 else PROCESS_PROLOG_V11
566
+ epilog = PROCESS_EPILOG_V12 if is_v12 else PROCESS_EPILOG_V11
567
+
561
568
  process = Process(
562
569
  name=process_name,
563
- prolog_procedure=PROCESS_PROLOG,
570
+ prolog_procedure=prolog,
564
571
  metadata_procedure=PROCESS_METADATA,
565
572
  data_procedure=PROCESS_DATA,
566
- epilog_procedure=PROCESS_EPILOG,
573
+ epilog_procedure=epilog,
567
574
  datasource_type=PROCESS_DATASOURCE["Type"],
568
575
  datasource_ascii_decimal_separator=PROCESS_DATASOURCE["asciiDecimalSeparator"],
569
576
  datasource_ascii_delimiter_char=PROCESS_DATASOURCE["asciiDelimiterChar"],
@@ -10,8 +10,14 @@ Object Definitions:
10
10
  - TASK_ID_ELEMENT_COUNT: Number of elements to generate for task_id dimension (1..N)
11
11
  - RUN_ID_SEED_ELEMENTS: Seed elements for run_id dimension
12
12
  - WORKFLOW_SEED_ELEMENTS: Seed elements for workflow dimension
13
- - PROCESS_DEFINITION: Full TI process definition for }rushti.load.results
13
+ - PROCESS_PROLOG_V11 / PROCESS_PROLOG_V12: Prolog body per TM1 major version
14
+ - PROCESS_EPILOG_V11 / PROCESS_EPILOG_V12: Epilog body per TM1 major version
14
15
  - SAMPLE_DATA: Sample cube data for demonstration taskfiles
16
+
17
+ The v12 variants drop calls to functions that were removed or are unsupported
18
+ on TM1 v12 (``CubeGetLogChanges`` / ``CubeSetLogChanges`` and
19
+ ``ExecuteCommand``). Source-file cleanup on v12 is handled by the TM1-native
20
+ ``ASCIIDelete`` instead of shelling out to ``cmd /c del``.
15
21
  """
16
22
 
17
23
  # ---------------------------------------------------------------------------
@@ -85,7 +91,7 @@ WORKFLOW_SEED_ELEMENTS = ["Sample_Stage_Mode", "Sample_Optimal_Mode"]
85
91
  # TI Process: }rushti.load.results
86
92
  # ---------------------------------------------------------------------------
87
93
 
88
- PROCESS_PROLOG = r"""#################################################################################################
94
+ PROCESS_PROLOG_V11 = r"""#################################################################################################
89
95
  ##~~Join the bedrock TM1 community on GitHub https://github.com/cubewise-code/bedrock Ver 4.0~~##
90
96
  #################################################################################################
91
97
 
@@ -208,6 +214,142 @@ CubeSetLogChanges( cCubeTgt, 0 );
208
214
  #Region - DataSource
209
215
 
210
216
 
217
+ ### Assign data source
218
+ If( nErrors = 0 );
219
+ DatasourceType = 'CHARACTERDELIMITED';
220
+ DatasourceNameForServer = cFileSrc;
221
+ DatasourceASCIIHeaderRecords = 1;
222
+ DatasourceASCIIDelimiter=',';
223
+ DatasourceASCIIDecimalSeparator='.';
224
+ DatasourceASCIIThousandSeparator='';
225
+ EndIf;
226
+
227
+ #EndRegion - DataSource
228
+ #################################################################################################
229
+ """
230
+
231
+ # v12 prolog: identical to v11 except the CubeGetLogChanges / CubeSetLogChanges
232
+ # block is removed. Those functions are unsupported on TM1 v12 and would cause
233
+ # the TI to fail at compile time.
234
+ PROCESS_PROLOG_V12 = r"""#################################################################################################
235
+ ##~~Join the bedrock TM1 community on GitHub https://github.com/cubewise-code/bedrock Ver 4.0~~##
236
+ #################################################################################################
237
+
238
+ #****Begin: Generated Statements***
239
+ #****End: Generated Statements****
240
+
241
+ #################################################################################################
242
+ ### CHANGE HISTORY:
243
+ ### MODIFICATION DATE CHANGED BY COMMENT
244
+ ### 2026-01-15 Nicolas Bisurgi Creation of Process
245
+ ### YYYY-MM-DD Developer Name Reason for modification here
246
+ #################################################################################################
247
+ #Region @DOC
248
+ # Description:
249
+ # This process will load RushTI result files into a TM1 cube
250
+
251
+ # Use case:
252
+ # Storing RushTI execution stats in a TM1 Cube
253
+
254
+ # Note:
255
+ # * This process assumes a there is a rushti compliant cube and dimensions. If you don't have it
256
+ # * please run: rushti.py build --tm1-instance tm1srv01
257
+ #EndRegion @DOC
258
+ #################################################################################################
259
+
260
+ #################################################################################################
261
+ #Region Process Declarations
262
+ ### Process Parameters
263
+ # a short description of what the process does goes here in cAction variable, e.g. "copied data from cube A to cube B". This will be written to the message log if pLogOutput=1
264
+ cAction = 'loading rushti stats into ' | pTargetCube;
265
+ cParamArray = '';
266
+ # to use the parameter array remove the line above and uncomment the line below, adding the needed parameters in the provided format
267
+ #cParamArray = 'pLogOutput:%pLogOutput%, pTemp:%pTemp%';
268
+
269
+ ### Global Variables
270
+ StringGlobalVariable('sProcessReturnCode');
271
+ NumericGlobalVariable('nProcessReturnCode');
272
+
273
+ ### Standard Constants
274
+ cThisProcName = GetProcessName();
275
+ cUserName = TM1User();
276
+ cTimeStamp = TimSt( Now, '\Y\m\d\h\i\s' );
277
+ cRandomInt = NumberToString( INT( RAND( ) * 1000 ));
278
+ cTempObjName = Expand('%cThisProcName%_%cTimeStamp%_%cRandomInt%');
279
+ cViewClr = '}bedrock_clear_' | cTempObjName;
280
+ cViewSrc = '}bedrock_source_' | cTempObjName;
281
+ cMsgErrorLevel = 'ERROR';
282
+ cMsgErrorContent = 'Process:%cThisProcName% ErrorMsg:%sMessage%';
283
+ cLogInfo = 'Process:%cThisProcName% run with parameters %cParamArray%';
284
+ sDelimEleStart = '¦';
285
+ sDelimDim = '&';
286
+ sDelimEle = '+';
287
+ nProcessReturnCode = 0;
288
+ nErrors = 0;
289
+ nMetadataRecordCount= 0;
290
+ nDataRecordCount = 0;
291
+
292
+ ### Process Specific Constants
293
+ cFileSrc = pSourceFile;
294
+ cCubeTgt = pTargetCube;
295
+
296
+ #EndRegion Process Declarations
297
+ #################################################################################################
298
+
299
+ ### LogOutput parameters
300
+ IF( pLogoutput = 1 );
301
+ LogOutput('INFO', Expand( cLogInfo ) );
302
+ ENDIF;
303
+
304
+ #################################################################################################
305
+ #Region Validate Parameters
306
+
307
+ # pLogOutput
308
+ If( pLogOutput >= 1 );
309
+ pLogOutput = 1;
310
+ Else;
311
+ pLogOutput = 0;
312
+ EndIf;
313
+
314
+ # Validate source file
315
+ If( Trim( cFileSrc ) @= '' );
316
+ nErrors = nErrors + 1;
317
+ sMessage = 'No source cfileube specified.';
318
+ LogOutput( cMsgErrorLevel, Expand( cMsgErrorContent ) );
319
+ ElseIf( FileExists( cFileSrc ) = 0 );
320
+ nErrors = nErrors + 1;
321
+ sMessage = Expand( 'Invalid source file specified: %cFileSrc%.');
322
+ LogOutput( cMsgErrorLevel, Expand( cMsgErrorContent ) );
323
+ EndIf;
324
+
325
+ # Validate target cube
326
+ If( Trim( cCubeTgt ) @= '' );
327
+ nErrors = nErrors + 1;
328
+ sMessage = 'No target cube specified.';
329
+ LogOutput( cMsgErrorLevel, Expand( cMsgErrorContent ) );
330
+ ElseIf( CubeExists( cCubeTgt ) = 0 );
331
+ nErrors = nErrors + 1;
332
+ sMessage = Expand( 'Invalid target cube specified: %cCubeTgt%.');
333
+ LogOutput( cMsgErrorLevel, Expand( cMsgErrorContent ) );
334
+ EndIf;
335
+
336
+ # If any parameters fail validation then set data source of process to null and go directly to epilog
337
+ If( nErrors > 0 );
338
+ DataSourceType = 'NULL';
339
+ If( pStrictErrorHandling = 1 );
340
+ ProcessQuit;
341
+ Else;
342
+ ProcessBreak;
343
+ EndIf;
344
+ EndIf;
345
+
346
+ #################################################################################################
347
+ #EndRegion Validate Parameters
348
+
349
+ #################################################################################################
350
+ #Region - DataSource
351
+
352
+
211
353
  ### Assign data source
212
354
  If( nErrors = 0 );
213
355
  DatasourceType = 'CHARACTERDELIMITED';
@@ -271,7 +413,7 @@ CellPutS(vsucceed_on_minor_errors, pTargetCube, vworkflow, vrun_id, vtask_id, 's
271
413
  CellPutS(voriginal_task_id, pTargetCube, vworkflow, vrun_id, vtask_id, 'original_task_id');
272
414
  """
273
415
 
274
- PROCESS_EPILOG = r"""#################################################################################################
416
+ PROCESS_EPILOG_V11 = r"""#################################################################################################
275
417
  ##~~Join the bedrock TM1 community on GitHub https://github.com/cubewise-code/bedrock Ver 4.0~~##
276
418
  #################################################################################################
277
419
 
@@ -308,6 +450,42 @@ EndIf;
308
450
 
309
451
  ### End Epilog ###"""
310
452
 
453
+ # v12 epilog: source-file cleanup uses TM1-native ASCIIDelete (no shell-out),
454
+ # and the CubeSetLogChanges restore is omitted because the v12 prolog does not
455
+ # toggle transaction logging.
456
+ PROCESS_EPILOG_V12 = r"""#################################################################################################
457
+ ##~~Join the bedrock TM1 community on GitHub https://github.com/cubewise-code/bedrock Ver 4.0~~##
458
+ #################################################################################################
459
+
460
+ #****Begin: Generated Statements***
461
+ #****End: Generated Statements****
462
+
463
+ ### If required delete the source file
464
+ if(pDeleteSourceFile=1);
465
+ Sleep(1000);
466
+ ASCIIDelete(pSourceFile);
467
+ endif;
468
+
469
+ ### Return code & final error message handling
470
+ If( nErrors > 0 );
471
+ sMessage = 'the process incurred at least 1 error. Please see above lines in this file for more details.';
472
+ nProcessReturnCode = 0;
473
+ LogOutput( cMsgErrorLevel, Expand( cMsgErrorContent ) );
474
+ sProcessReturnCode = Expand( '%sProcessReturnCode% Process:%cThisProcName% completed with errors. Check tm1server.log for details.' );
475
+ If( pStrictErrorHandling = 1 );
476
+ ProcessQuit;
477
+ EndIf;
478
+ Else;
479
+ sProcessAction = Expand( 'Process:%cThisProcName% successfully %cAction%. %nDataRecordCount% records processed.' );
480
+ sProcessReturnCode = Expand( '%sProcessReturnCode% %sProcessAction%' );
481
+ nProcessReturnCode = 1;
482
+ If( pLogoutput = 1 );
483
+ LogOutput('INFO', Expand( sProcessAction ) );
484
+ EndIf;
485
+ EndIf;
486
+
487
+ ### End Epilog ###"""
488
+
311
489
  PROCESS_PARAMETERS = [
312
490
  {
313
491
  "Name": "pSourceFile",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rushti
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: RushTI - Parallel TM1 TurboIntegrator Process Execution
5
5
  Author-email: nicolasbisurgi <nbisurgi@cubewise.com>, Marius Wirtz <mwirtz@cubewise.com>
6
6
  Maintainer: Cubewise CODE
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes