rushti 2.3.0__tar.gz → 2.3.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.
- {rushti-2.3.0/src/rushti.egg-info → rushti-2.3.2}/PKG-INFO +6 -1
- {rushti-2.3.0 → rushti-2.3.2}/README.md +5 -0
- {rushti-2.3.0 → rushti-2.3.2}/pyproject.toml +1 -1
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/__init__.py +1 -1
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/app_paths.py +22 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/cli.py +17 -2
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/build.py +12 -6
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/resume.py +5 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/tasks/__init__.py +12 -3
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/execution.py +2 -4
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/tm1_objects.py +87 -11
- {rushti-2.3.0 → rushti-2.3.2/src/rushti.egg-info}/PKG-INFO +6 -1
- {rushti-2.3.0 → rushti-2.3.2}/LICENSE +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/setup.cfg +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/_shlex_utils.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/checkpoint.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/__init__.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/db.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/stats/__init__.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/stats/analyze.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/stats/export.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/stats/list.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/stats/optimize.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/stats/visualize.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/tasks/expand.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/tasks/export.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/tasks/push.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/tasks/validate.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/commands/tasks/visualize.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/contention_analyzer.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/dag.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/dashboard.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/db_admin.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/exclusive.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/logging.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/logging_setup.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/messages.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/optimization_report.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/optimizer.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/parsing.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/results_writer.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/settings.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/stats/__init__.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/stats/dynamodb.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/stats/paths.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/stats/repository.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/stats/signature.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/stats/sqlite.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/task.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/taskfile.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/taskfile_ops.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/templates/__init__.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/templates/visualization.html +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/tm1_build.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/tm1_integration.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/utils.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti/visualization_template.py +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti.egg-info/SOURCES.txt +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti.egg-info/dependency_links.txt +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti.egg-info/entry_points.txt +0 -0
- {rushti-2.3.0 → rushti-2.3.2}/src/rushti.egg-info/requires.txt +0 -0
- {rushti-2.3.0 → rushti-2.3.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.3.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -109,6 +109,11 @@ user = admin
|
|
|
109
109
|
password = apple
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
+
RushTI looks for `config.ini` under `config/` (or `RUSHTI_DIR`). To point a
|
|
113
|
+
single run at a different file — e.g. a `config.ini` shared with other tm1py
|
|
114
|
+
utilities — pass `--config PATH` to any TM1-connecting command (`run`, `build`,
|
|
115
|
+
`tasks`, `resume`).
|
|
116
|
+
|
|
112
117
|
**2. Create a task file**
|
|
113
118
|
|
|
114
119
|
```json
|
|
@@ -65,6 +65,11 @@ user = admin
|
|
|
65
65
|
password = apple
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
RushTI looks for `config.ini` under `config/` (or `RUSHTI_DIR`). To point a
|
|
69
|
+
single run at a different file — e.g. a `config.ini` shared with other tm1py
|
|
70
|
+
utilities — pass `--config PATH` to any TM1-connecting command (`run`, `build`,
|
|
71
|
+
`tasks`, `resume`).
|
|
72
|
+
|
|
68
73
|
**2. Create a task file**
|
|
69
74
|
|
|
70
75
|
```json
|
|
@@ -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.3.
|
|
8
|
+
__version__ = "2.3.2"
|
|
9
9
|
__app_name__ = "RushTI"
|
|
10
10
|
|
|
11
11
|
# Core exports for programmatic use
|
|
@@ -6,6 +6,7 @@ and tracks which legacy paths have been used so a deprecation warning
|
|
|
6
6
|
can be emitted once logging is initialized.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import argparse
|
|
9
10
|
import logging
|
|
10
11
|
import os
|
|
11
12
|
|
|
@@ -15,6 +16,7 @@ __all__ = [
|
|
|
15
16
|
"CURRENT_DIRECTORY",
|
|
16
17
|
"resolve_config_path",
|
|
17
18
|
"log_legacy_path_warnings",
|
|
19
|
+
"add_config_arg",
|
|
18
20
|
]
|
|
19
21
|
|
|
20
22
|
|
|
@@ -73,6 +75,26 @@ def resolve_config_path(filename: str, warn_on_legacy: bool = True, cli_path: st
|
|
|
73
75
|
return new_path
|
|
74
76
|
|
|
75
77
|
|
|
78
|
+
def add_config_arg(parser: argparse.ArgumentParser) -> None:
|
|
79
|
+
"""Add the ``--config`` argument to a parser.
|
|
80
|
+
|
|
81
|
+
Long-form only (``-c`` is taken by ``resume --checkpoint``). Relocates
|
|
82
|
+
only ``config.ini`` (TM1 connection parameters); ``settings.ini`` and
|
|
83
|
+
``logging_config.ini`` keep their own resolution. The resolved value is
|
|
84
|
+
fed to :func:`resolve_config_path` as ``cli_path``.
|
|
85
|
+
"""
|
|
86
|
+
parser.add_argument(
|
|
87
|
+
"--config",
|
|
88
|
+
dest="config",
|
|
89
|
+
default=None,
|
|
90
|
+
metavar="FILE",
|
|
91
|
+
help=(
|
|
92
|
+
"Path to a config.ini file (TM1 connection parameters). "
|
|
93
|
+
"Overrides RUSHTI_DIR/default location."
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
76
98
|
def log_legacy_path_warnings(logger: logging.Logger) -> None:
|
|
77
99
|
"""Log deprecation warnings for any legacy paths that were used.
|
|
78
100
|
|
|
@@ -19,6 +19,7 @@ from pathlib import Path
|
|
|
19
19
|
from TM1py.Utils import integerize_version
|
|
20
20
|
|
|
21
21
|
from rushti.app_paths import (
|
|
22
|
+
add_config_arg,
|
|
22
23
|
log_legacy_path_warnings,
|
|
23
24
|
resolve_config_path,
|
|
24
25
|
)
|
|
@@ -273,6 +274,9 @@ Configuration:
|
|
|
273
274
|
# Add taskfile source arguments
|
|
274
275
|
add_taskfile_source_args(parser, required=False, include_settings=True)
|
|
275
276
|
|
|
277
|
+
# Add --config (relocates config.ini only)
|
|
278
|
+
add_config_arg(parser)
|
|
279
|
+
|
|
276
280
|
parser.add_argument(
|
|
277
281
|
"--max-workers",
|
|
278
282
|
"-w",
|
|
@@ -426,6 +430,7 @@ def parse_named_arguments(argv: list):
|
|
|
426
430
|
"workflow": workflow,
|
|
427
431
|
"log_level": args.log_level,
|
|
428
432
|
"detailed_results": args.detailed_results,
|
|
433
|
+
"config": args.config,
|
|
429
434
|
}
|
|
430
435
|
|
|
431
436
|
return tasks_file_path, cli_args
|
|
@@ -471,6 +476,7 @@ def parse_arguments(argv: list):
|
|
|
471
476
|
"exclusive": None,
|
|
472
477
|
"no_checkpoint": False,
|
|
473
478
|
"log_level": None, # Not supported in positional style
|
|
479
|
+
"config": None, # Legacy positional style does not support --config
|
|
474
480
|
}
|
|
475
481
|
return tasks_file, cli_args
|
|
476
482
|
|
|
@@ -626,6 +632,14 @@ Use '{APP_NAME} <command> --help' for command-specific options and examples.
|
|
|
626
632
|
if resume_context:
|
|
627
633
|
cli_args.update(resume_context)
|
|
628
634
|
|
|
635
|
+
# Resolve config.ini location, threading the resolved path explicitly into
|
|
636
|
+
# the TM1 connection layer (CLI --config > RUSHTI_DIR > legacy CWD > config/).
|
|
637
|
+
# Fail fast with a clean message (no traceback) on an explicit bad path.
|
|
638
|
+
try:
|
|
639
|
+
config_path = resolve_config_path("config.ini", cli_path=cli_args.get("config"))
|
|
640
|
+
except FileNotFoundError:
|
|
641
|
+
sys.exit(f"RushTI: --config file not found: {cli_args.get('config')}")
|
|
642
|
+
|
|
629
643
|
# Apply log level override if specified
|
|
630
644
|
apply_log_level(cli_args.get("log_level"))
|
|
631
645
|
|
|
@@ -645,7 +659,7 @@ Use '{APP_NAME} <command> --help' for command-specific options and examples.
|
|
|
645
659
|
try:
|
|
646
660
|
from rushti.tm1_integration import read_taskfile_from_tm1, connect_to_tm1_instance
|
|
647
661
|
|
|
648
|
-
tm1_source = connect_to_tm1_instance(tm1_instance,
|
|
662
|
+
tm1_source = connect_to_tm1_instance(tm1_instance, config_path)
|
|
649
663
|
try:
|
|
650
664
|
# Resolve execution mode for TM1 read (affects predecessor parsing)
|
|
651
665
|
tm1_mode = "opt" if cli_args.get("execution_mode") == ExecutionMode.OPT else "norm"
|
|
@@ -752,6 +766,7 @@ Use '{APP_NAME} <command> --help' for command-specific options and examples.
|
|
|
752
766
|
tasks_file_path=tasks_file_path_for_services if not tm1_taskfile else None,
|
|
753
767
|
workflow=workflow,
|
|
754
768
|
exclusive=exclusive_mode,
|
|
769
|
+
config_path=config_path,
|
|
755
770
|
tm1_instances=tm1_instances_needed,
|
|
756
771
|
)
|
|
757
772
|
|
|
@@ -1106,7 +1121,7 @@ Use '{APP_NAME} <command> --help' for command-specific options and examples.
|
|
|
1106
1121
|
)
|
|
1107
1122
|
tm1_upload = connect_to_tm1_instance(
|
|
1108
1123
|
upload_instance,
|
|
1109
|
-
|
|
1124
|
+
config_path,
|
|
1110
1125
|
)
|
|
1111
1126
|
try:
|
|
1112
1127
|
results_df = build_results_dataframe(
|
|
@@ -10,6 +10,7 @@ import sys
|
|
|
10
10
|
|
|
11
11
|
from TM1py import TM1Service
|
|
12
12
|
|
|
13
|
+
from rushti.app_paths import add_config_arg, resolve_config_path
|
|
13
14
|
from rushti.logging_setup import add_log_level_arg, apply_log_level
|
|
14
15
|
from rushti.settings import load_settings
|
|
15
16
|
from rushti.tm1_build import build_logging_objects, get_build_status
|
|
@@ -25,8 +26,6 @@ def run_build_command(argv: list) -> None:
|
|
|
25
26
|
|
|
26
27
|
:param argv: Command line arguments
|
|
27
28
|
"""
|
|
28
|
-
from rushti.cli import CONFIG
|
|
29
|
-
|
|
30
29
|
parser = argparse.ArgumentParser(
|
|
31
30
|
prog=f"{APP_NAME} build",
|
|
32
31
|
description="Create TM1 dimensions and cube for RushTI execution logging.",
|
|
@@ -60,6 +59,7 @@ Examples:
|
|
|
60
59
|
metavar="FILE",
|
|
61
60
|
help="Path to settings.ini file",
|
|
62
61
|
)
|
|
62
|
+
add_config_arg(parser)
|
|
63
63
|
add_log_level_arg(parser)
|
|
64
64
|
|
|
65
65
|
args = parser.parse_args(argv[2:]) # Skip "rushti" and "build"
|
|
@@ -68,16 +68,22 @@ Examples:
|
|
|
68
68
|
# Load settings
|
|
69
69
|
settings = load_settings(args.settings_file)
|
|
70
70
|
|
|
71
|
+
# Resolve config.ini location (CLI --config > RUSHTI_DIR > legacy CWD > config/)
|
|
72
|
+
try:
|
|
73
|
+
config_path = resolve_config_path("config.ini", cli_path=args.config)
|
|
74
|
+
except FileNotFoundError:
|
|
75
|
+
sys.exit(f"RushTI: --config file not found: {args.config}")
|
|
76
|
+
|
|
71
77
|
# Load config to get TM1 connection details
|
|
72
|
-
if not os.path.isfile(
|
|
73
|
-
print(f"Error: {
|
|
78
|
+
if not os.path.isfile(config_path):
|
|
79
|
+
print(f"Error: {config_path} does not exist")
|
|
74
80
|
sys.exit(1)
|
|
75
81
|
|
|
76
82
|
config = configparser.ConfigParser()
|
|
77
|
-
config.read(
|
|
83
|
+
config.read(config_path, encoding="utf-8")
|
|
78
84
|
|
|
79
85
|
if args.tm1_instance not in config.sections():
|
|
80
|
-
print(f"Error: Instance '{args.tm1_instance}' not found in {
|
|
86
|
+
print(f"Error: Instance '{args.tm1_instance}' not found in {config_path}")
|
|
81
87
|
print(f"Available instances: {', '.join(config.sections())}")
|
|
82
88
|
sys.exit(1)
|
|
83
89
|
|
|
@@ -10,6 +10,7 @@ import sys
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Optional
|
|
12
12
|
|
|
13
|
+
from rushti.app_paths import add_config_arg
|
|
13
14
|
from rushti.checkpoint import find_checkpoint_for_taskfile, load_checkpoint
|
|
14
15
|
from rushti.logging_setup import add_log_level_arg, apply_log_level
|
|
15
16
|
from rushti.settings import load_settings
|
|
@@ -86,6 +87,7 @@ Examples:
|
|
|
86
87
|
default=False,
|
|
87
88
|
help="Force resume even if checkpoint doesn't match taskfile",
|
|
88
89
|
)
|
|
90
|
+
add_config_arg(parser)
|
|
89
91
|
add_log_level_arg(parser)
|
|
90
92
|
|
|
91
93
|
args = parser.parse_args(argv[2:])
|
|
@@ -200,6 +202,9 @@ Examples:
|
|
|
200
202
|
if args.settings_file:
|
|
201
203
|
resume_argv.extend(["--settings", args.settings_file])
|
|
202
204
|
|
|
205
|
+
if args.config:
|
|
206
|
+
resume_argv.extend(["--config", args.config])
|
|
207
|
+
|
|
203
208
|
if args.force:
|
|
204
209
|
resume_argv.append("--force")
|
|
205
210
|
|
|
@@ -9,8 +9,9 @@ Subcommands:
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import argparse
|
|
12
|
+
import sys
|
|
12
13
|
|
|
13
|
-
from rushti.app_paths import resolve_config_path
|
|
14
|
+
from rushti.app_paths import add_config_arg, resolve_config_path
|
|
14
15
|
from rushti.commands.tasks.expand import handle_tasks_expand
|
|
15
16
|
from rushti.commands.tasks.export import handle_tasks_export
|
|
16
17
|
from rushti.commands.tasks.push import handle_tasks_push
|
|
@@ -98,6 +99,7 @@ Examples:
|
|
|
98
99
|
help="[TM1 sources only] 'norm' for wait-based sequencing, 'opt' for explicit predecessors. "
|
|
99
100
|
"Ignored for file sources (auto-detected). Default: opt",
|
|
100
101
|
)
|
|
102
|
+
add_config_arg(export_parser)
|
|
101
103
|
add_log_level_arg(export_parser)
|
|
102
104
|
|
|
103
105
|
# --- push subcommand ---
|
|
@@ -122,6 +124,7 @@ Examples:
|
|
|
122
124
|
help="[TM1 sources only] 'norm' for wait-based sequencing, 'opt' for explicit predecessors. "
|
|
123
125
|
"Ignored for file sources (auto-detected). Default: opt",
|
|
124
126
|
)
|
|
127
|
+
add_config_arg(push_parser)
|
|
125
128
|
add_log_level_arg(push_parser)
|
|
126
129
|
|
|
127
130
|
# --- expand subcommand ---
|
|
@@ -155,6 +158,7 @@ Examples:
|
|
|
155
158
|
help="[TM1 sources only] 'norm' for wait-based sequencing, 'opt' for explicit predecessors. "
|
|
156
159
|
"Ignored for file sources (auto-detected). Default: opt",
|
|
157
160
|
)
|
|
161
|
+
add_config_arg(expand_parser)
|
|
158
162
|
add_log_level_arg(expand_parser)
|
|
159
163
|
|
|
160
164
|
# --- visualize subcommand ---
|
|
@@ -188,6 +192,7 @@ Examples:
|
|
|
188
192
|
help="[TM1 sources only] 'norm' for wait-based sequencing, 'opt' for explicit predecessors. "
|
|
189
193
|
"Ignored for file sources (auto-detected). Default: opt",
|
|
190
194
|
)
|
|
195
|
+
add_config_arg(visualize_parser)
|
|
191
196
|
add_log_level_arg(visualize_parser)
|
|
192
197
|
|
|
193
198
|
# --- validate subcommand ---
|
|
@@ -219,14 +224,18 @@ Examples:
|
|
|
219
224
|
help="[TM1 sources only] 'norm' for wait-based sequencing, 'opt' for explicit predecessors. "
|
|
220
225
|
"Ignored for file sources (auto-detected). Default: opt",
|
|
221
226
|
)
|
|
227
|
+
add_config_arg(validate_parser)
|
|
222
228
|
add_log_level_arg(validate_parser)
|
|
223
229
|
|
|
224
230
|
# Parse arguments (skip script name and 'tasks' command)
|
|
225
231
|
args = parser.parse_args(argv[2:])
|
|
226
232
|
apply_log_level(args.log_level)
|
|
227
233
|
|
|
228
|
-
# Resolve config
|
|
229
|
-
|
|
234
|
+
# Resolve config.ini location (CLI --config > RUSHTI_DIR > legacy CWD > config/)
|
|
235
|
+
try:
|
|
236
|
+
config_path = resolve_config_path("config.ini", cli_path=args.config)
|
|
237
|
+
except FileNotFoundError:
|
|
238
|
+
sys.exit(f"RushTI: --config file not found: {args.config}")
|
|
230
239
|
|
|
231
240
|
# Dispatch to appropriate handler based on subcommand
|
|
232
241
|
if args.subcommand == "export":
|
|
@@ -148,14 +148,12 @@ def setup_tm1_services(
|
|
|
148
148
|
:param tasks_file_path: Path to the tasks file (can be None if tm1_instances provided)
|
|
149
149
|
:param workflow: Workflow identifier for session context
|
|
150
150
|
:param exclusive: Whether running in exclusive mode
|
|
151
|
-
:param config_path:
|
|
151
|
+
:param config_path: Path to config.ini file (resolved by the caller; required)
|
|
152
152
|
:param tm1_instances: Optional set of TM1 instance names (used when reading from TM1)
|
|
153
153
|
:return: Dictionary server_names and TM1py.TM1Service instances pairs
|
|
154
154
|
"""
|
|
155
155
|
if config_path is None:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
config_path = CONFIG
|
|
156
|
+
raise ValueError("config_path is required")
|
|
159
157
|
|
|
160
158
|
config_file = config_path
|
|
161
159
|
if not os.path.isfile(config_file):
|
|
@@ -150,6 +150,8 @@ nProcessReturnCode = 0;
|
|
|
150
150
|
nErrors = 0;
|
|
151
151
|
nMetadataRecordCount= 0;
|
|
152
152
|
nDataRecordCount = 0;
|
|
153
|
+
nHeaderChecked = 0;
|
|
154
|
+
nDataHeaderChecked = 0;
|
|
153
155
|
|
|
154
156
|
### Process Specific Constants
|
|
155
157
|
cFileSrc = pSourceFile;
|
|
@@ -220,7 +222,9 @@ CubeSetLogChanges( cCubeTgt, 0 );
|
|
|
220
222
|
If( nErrors = 0 );
|
|
221
223
|
DatasourceType = 'CHARACTERDELIMITED';
|
|
222
224
|
DatasourceNameForServer = cFileSrc;
|
|
223
|
-
|
|
225
|
+
# 0 (not 1): the header row is consumed as the first record so the
|
|
226
|
+
# metadata tab can validate it against the expected columns (issue #169).
|
|
227
|
+
DatasourceASCIIHeaderRecords = 0;
|
|
224
228
|
DatasourceASCIIDelimiter=',';
|
|
225
229
|
DatasourceASCIIDecimalSeparator='.';
|
|
226
230
|
DatasourceASCIIThousandSeparator='';
|
|
@@ -290,6 +294,8 @@ nProcessReturnCode = 0;
|
|
|
290
294
|
nErrors = 0;
|
|
291
295
|
nMetadataRecordCount= 0;
|
|
292
296
|
nDataRecordCount = 0;
|
|
297
|
+
nHeaderChecked = 0;
|
|
298
|
+
nDataHeaderChecked = 0;
|
|
293
299
|
|
|
294
300
|
### Process Specific Constants
|
|
295
301
|
cFileSrc = pSourceFile;
|
|
@@ -356,7 +362,9 @@ EndIf;
|
|
|
356
362
|
If( nErrors = 0 );
|
|
357
363
|
DatasourceType = 'CHARACTERDELIMITED';
|
|
358
364
|
DatasourceNameForServer = cFileSrc;
|
|
359
|
-
|
|
365
|
+
# 0 (not 1): the header row is consumed as the first record so the
|
|
366
|
+
# metadata tab can validate it against the expected columns (issue #169).
|
|
367
|
+
DatasourceASCIIHeaderRecords = 0;
|
|
360
368
|
DatasourceASCIIDelimiter=',';
|
|
361
369
|
DatasourceASCIIDecimalSeparator='.';
|
|
362
370
|
DatasourceASCIIThousandSeparator='';
|
|
@@ -366,10 +374,17 @@ EndIf;
|
|
|
366
374
|
#################################################################################################
|
|
367
375
|
"""
|
|
368
376
|
|
|
369
|
-
|
|
377
|
+
# Marker pair TM1 Architect uses to inject auto-generated variable/parameter
|
|
378
|
+
# code at the top of a tab. Kept as the first lines of every generated
|
|
379
|
+
# procedure so the process round-trips cleanly if later opened in Architect.
|
|
380
|
+
_GENERATED_STATEMENTS_MARKER = """#****Begin: Generated Statements***
|
|
370
381
|
#****End: Generated Statements****
|
|
382
|
+
"""
|
|
371
383
|
|
|
372
|
-
|
|
384
|
+
# Body of the metadata tab. The header-validation preamble is generated from
|
|
385
|
+
# PROCESS_VARIABLES and prepended in ``build_metadata_procedure`` so it stays
|
|
386
|
+
# in lockstep with the CSV column contract (see issue #169).
|
|
387
|
+
_PROCESS_METADATA_BODY = r"""If( pLogOutput >= 1 );
|
|
373
388
|
nMetadataRecordCount = nMetadataRecordCount + 1;
|
|
374
389
|
EndIf;
|
|
375
390
|
|
|
@@ -388,10 +403,9 @@ if (DimensionElementExists(pRunId_Dim, vrun_id) = 0);
|
|
|
388
403
|
DimensionElementInsertDirect(pRunId_Dim, '', vrun_id, 'N');
|
|
389
404
|
endif;"""
|
|
390
405
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
If( pLogOutput >= 1 );
|
|
406
|
+
# Body of the data tab. The header-skip preamble is prepended in
|
|
407
|
+
# ``build_data_procedure`` so the CSV header row is not written to the cube.
|
|
408
|
+
_PROCESS_DATA_BODY = r"""If( pLogOutput >= 1 );
|
|
395
409
|
nDataRecordCount = nDataRecordCount + 1;
|
|
396
410
|
EndIf;
|
|
397
411
|
|
|
@@ -544,8 +558,9 @@ PROCESS_PARAMETERS = [
|
|
|
544
558
|
# declaration list. Must match the column order produced by
|
|
545
559
|
# ``rushti.tm1_integration.upload_results_to_tm1`` (which inserts
|
|
546
560
|
# ``workflow`` and ``run_id`` ahead of the columns built by
|
|
547
|
-
# ``build_results_dataframe``). Mismatched ordering
|
|
548
|
-
# the
|
|
561
|
+
# ``build_results_dataframe``). Mismatched ordering scrambles the loaded
|
|
562
|
+
# payload; the metadata-tab header check generated from this list turns a
|
|
563
|
+
# stale-process mismatch into a hard error instead (see issue #169).
|
|
549
564
|
PROCESS_VARIABLES = [
|
|
550
565
|
"vworkflow",
|
|
551
566
|
"vrun_id",
|
|
@@ -571,12 +586,73 @@ PROCESS_VARIABLES = [
|
|
|
571
586
|
"vsucceed_on_minor_errors",
|
|
572
587
|
]
|
|
573
588
|
|
|
589
|
+
|
|
590
|
+
def build_metadata_procedure() -> str:
|
|
591
|
+
"""Assemble the ``}rushti.load.results`` metadata tab.
|
|
592
|
+
|
|
593
|
+
Prepends a header-validation preamble generated from ``PROCESS_VARIABLES``
|
|
594
|
+
so it stays in lockstep with the CSV column contract. The loader maps CSV
|
|
595
|
+
columns to variables positionally; with ``asciiHeaderRecords=0`` the CSV
|
|
596
|
+
header row is the first source record, so validating each column name
|
|
597
|
+
against the variable it feeds makes a process left stale by an in-place
|
|
598
|
+
rushti upgrade fail loudly instead of silently writing values to the wrong
|
|
599
|
+
measures (issue #169). Re-run ``rushti build`` to refresh a stale process.
|
|
600
|
+
"""
|
|
601
|
+
checks = "\n".join(
|
|
602
|
+
f" If( {var} @<> '{var[1:]}' );\n"
|
|
603
|
+
f" sHeaderError = sHeaderError | ' {var[1:]}';\n"
|
|
604
|
+
f" EndIf;"
|
|
605
|
+
for var in PROCESS_VARIABLES
|
|
606
|
+
)
|
|
607
|
+
preamble = f"""### Validate the CSV header against the columns this process expects.
|
|
608
|
+
### The loader maps columns to variables positionally, so a process built by
|
|
609
|
+
### an older rushti scrambles measures (issue #169). Fail loudly on mismatch
|
|
610
|
+
### and skip the header so it is not loaded as data.
|
|
611
|
+
If( nHeaderChecked = 0 );
|
|
612
|
+
nHeaderChecked = 1;
|
|
613
|
+
sHeaderError = '';
|
|
614
|
+
{checks}
|
|
615
|
+
If( Trim( sHeaderError ) @<> '' );
|
|
616
|
+
nErrors = nErrors + 1;
|
|
617
|
+
sMessage = Expand( 'Results CSV header does not match the columns }}rushti.load.results expects (mismatched:%sHeaderError% ). This process is out of date with the rushti version that produced the file - re-run: rushti build --tm1-instance <instance>.' );
|
|
618
|
+
LogOutput( cMsgErrorLevel, Expand( cMsgErrorContent ) );
|
|
619
|
+
ProcessQuit;
|
|
620
|
+
EndIf;
|
|
621
|
+
ItemSkip;
|
|
622
|
+
EndIf;
|
|
623
|
+
|
|
624
|
+
"""
|
|
625
|
+
return _GENERATED_STATEMENTS_MARKER + "\n" + preamble + _PROCESS_METADATA_BODY
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def build_data_procedure() -> str:
|
|
629
|
+
"""Assemble the ``}rushti.load.results`` data tab.
|
|
630
|
+
|
|
631
|
+
Skips the CSV header row (validated in the metadata tab) so it is not
|
|
632
|
+
written to the cube, then delegates to the static body. See issue #169.
|
|
633
|
+
"""
|
|
634
|
+
preamble = """### Skip the CSV header row (asciiHeaderRecords=0; validated in the
|
|
635
|
+
### metadata tab). See issue #169.
|
|
636
|
+
If( nDataHeaderChecked = 0 );
|
|
637
|
+
nDataHeaderChecked = 1;
|
|
638
|
+
ItemSkip;
|
|
639
|
+
EndIf;
|
|
640
|
+
|
|
641
|
+
"""
|
|
642
|
+
return _GENERATED_STATEMENTS_MARKER + "\n" + preamble + _PROCESS_DATA_BODY
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
PROCESS_METADATA = build_metadata_procedure()
|
|
646
|
+
PROCESS_DATA = build_data_procedure()
|
|
647
|
+
|
|
574
648
|
PROCESS_DATASOURCE = {
|
|
575
649
|
"Type": "ASCII",
|
|
576
650
|
"asciiDecimalSeparator": ".",
|
|
577
651
|
"asciiDelimiterChar": ",",
|
|
578
652
|
"asciiDelimiterType": "Character",
|
|
579
|
-
|
|
653
|
+
# 0: the header row is validated (not skipped) by the metadata tab; see
|
|
654
|
+
# the DatasourceASCIIHeaderRecords note in the prolog and issue #169.
|
|
655
|
+
"asciiHeaderRecords": 0,
|
|
580
656
|
"asciiQuoteCharacter": '"',
|
|
581
657
|
"asciiThousandSeparator": ",",
|
|
582
658
|
"dataSourceNameForClient": "",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rushti
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -109,6 +109,11 @@ user = admin
|
|
|
109
109
|
password = apple
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
+
RushTI looks for `config.ini` under `config/` (or `RUSHTI_DIR`). To point a
|
|
113
|
+
single run at a different file — e.g. a `config.ini` shared with other tm1py
|
|
114
|
+
utilities — pass `--config PATH` to any TM1-connecting command (`run`, `build`,
|
|
115
|
+
`tasks`, `resume`).
|
|
116
|
+
|
|
112
117
|
**2. Create a task file**
|
|
113
118
|
|
|
114
119
|
```json
|
|
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
|
|
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
|
|
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
|