pytrilogy 0.3.142__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- LICENSE.md +19 -0
- _preql_import_resolver/__init__.py +5 -0
- _preql_import_resolver/_preql_import_resolver.cp312-win_amd64.pyd +0 -0
- pytrilogy-0.3.142.dist-info/METADATA +555 -0
- pytrilogy-0.3.142.dist-info/RECORD +200 -0
- pytrilogy-0.3.142.dist-info/WHEEL +4 -0
- pytrilogy-0.3.142.dist-info/entry_points.txt +2 -0
- pytrilogy-0.3.142.dist-info/licenses/LICENSE.md +19 -0
- trilogy/__init__.py +16 -0
- trilogy/ai/README.md +10 -0
- trilogy/ai/__init__.py +19 -0
- trilogy/ai/constants.py +92 -0
- trilogy/ai/conversation.py +107 -0
- trilogy/ai/enums.py +7 -0
- trilogy/ai/execute.py +50 -0
- trilogy/ai/models.py +34 -0
- trilogy/ai/prompts.py +100 -0
- trilogy/ai/providers/__init__.py +0 -0
- trilogy/ai/providers/anthropic.py +106 -0
- trilogy/ai/providers/base.py +24 -0
- trilogy/ai/providers/google.py +146 -0
- trilogy/ai/providers/openai.py +89 -0
- trilogy/ai/providers/utils.py +68 -0
- trilogy/authoring/README.md +3 -0
- trilogy/authoring/__init__.py +148 -0
- trilogy/constants.py +113 -0
- trilogy/core/README.md +52 -0
- trilogy/core/__init__.py +0 -0
- trilogy/core/constants.py +6 -0
- trilogy/core/enums.py +443 -0
- trilogy/core/env_processor.py +120 -0
- trilogy/core/environment_helpers.py +320 -0
- trilogy/core/ergonomics.py +193 -0
- trilogy/core/exceptions.py +123 -0
- trilogy/core/functions.py +1227 -0
- trilogy/core/graph_models.py +139 -0
- trilogy/core/internal.py +85 -0
- trilogy/core/models/__init__.py +0 -0
- trilogy/core/models/author.py +2669 -0
- trilogy/core/models/build.py +2521 -0
- trilogy/core/models/build_environment.py +180 -0
- trilogy/core/models/core.py +501 -0
- trilogy/core/models/datasource.py +322 -0
- trilogy/core/models/environment.py +751 -0
- trilogy/core/models/execute.py +1177 -0
- trilogy/core/optimization.py +251 -0
- trilogy/core/optimizations/__init__.py +12 -0
- trilogy/core/optimizations/base_optimization.py +17 -0
- trilogy/core/optimizations/hide_unused_concept.py +47 -0
- trilogy/core/optimizations/inline_datasource.py +102 -0
- trilogy/core/optimizations/predicate_pushdown.py +245 -0
- trilogy/core/processing/README.md +94 -0
- trilogy/core/processing/READMEv2.md +121 -0
- trilogy/core/processing/VIRTUAL_UNNEST.md +30 -0
- trilogy/core/processing/__init__.py +0 -0
- trilogy/core/processing/concept_strategies_v3.py +508 -0
- trilogy/core/processing/constants.py +15 -0
- trilogy/core/processing/discovery_node_factory.py +451 -0
- trilogy/core/processing/discovery_utility.py +548 -0
- trilogy/core/processing/discovery_validation.py +167 -0
- trilogy/core/processing/graph_utils.py +43 -0
- trilogy/core/processing/node_generators/README.md +9 -0
- trilogy/core/processing/node_generators/__init__.py +31 -0
- trilogy/core/processing/node_generators/basic_node.py +160 -0
- trilogy/core/processing/node_generators/common.py +268 -0
- trilogy/core/processing/node_generators/constant_node.py +38 -0
- trilogy/core/processing/node_generators/filter_node.py +315 -0
- trilogy/core/processing/node_generators/group_node.py +213 -0
- trilogy/core/processing/node_generators/group_to_node.py +117 -0
- trilogy/core/processing/node_generators/multiselect_node.py +205 -0
- trilogy/core/processing/node_generators/node_merge_node.py +653 -0
- trilogy/core/processing/node_generators/recursive_node.py +88 -0
- trilogy/core/processing/node_generators/rowset_node.py +165 -0
- trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +261 -0
- trilogy/core/processing/node_generators/select_merge_node.py +748 -0
- trilogy/core/processing/node_generators/select_node.py +95 -0
- trilogy/core/processing/node_generators/synonym_node.py +98 -0
- trilogy/core/processing/node_generators/union_node.py +91 -0
- trilogy/core/processing/node_generators/unnest_node.py +182 -0
- trilogy/core/processing/node_generators/window_node.py +201 -0
- trilogy/core/processing/nodes/README.md +28 -0
- trilogy/core/processing/nodes/__init__.py +179 -0
- trilogy/core/processing/nodes/base_node.py +519 -0
- trilogy/core/processing/nodes/filter_node.py +75 -0
- trilogy/core/processing/nodes/group_node.py +194 -0
- trilogy/core/processing/nodes/merge_node.py +420 -0
- trilogy/core/processing/nodes/recursive_node.py +46 -0
- trilogy/core/processing/nodes/select_node_v2.py +242 -0
- trilogy/core/processing/nodes/union_node.py +53 -0
- trilogy/core/processing/nodes/unnest_node.py +62 -0
- trilogy/core/processing/nodes/window_node.py +56 -0
- trilogy/core/processing/utility.py +823 -0
- trilogy/core/query_processor.py +596 -0
- trilogy/core/statements/README.md +35 -0
- trilogy/core/statements/__init__.py +0 -0
- trilogy/core/statements/author.py +536 -0
- trilogy/core/statements/build.py +0 -0
- trilogy/core/statements/common.py +20 -0
- trilogy/core/statements/execute.py +155 -0
- trilogy/core/table_processor.py +66 -0
- trilogy/core/utility.py +8 -0
- trilogy/core/validation/README.md +46 -0
- trilogy/core/validation/__init__.py +0 -0
- trilogy/core/validation/common.py +161 -0
- trilogy/core/validation/concept.py +146 -0
- trilogy/core/validation/datasource.py +227 -0
- trilogy/core/validation/environment.py +73 -0
- trilogy/core/validation/fix.py +256 -0
- trilogy/dialect/__init__.py +32 -0
- trilogy/dialect/base.py +1392 -0
- trilogy/dialect/bigquery.py +308 -0
- trilogy/dialect/common.py +147 -0
- trilogy/dialect/config.py +144 -0
- trilogy/dialect/dataframe.py +50 -0
- trilogy/dialect/duckdb.py +231 -0
- trilogy/dialect/enums.py +147 -0
- trilogy/dialect/metadata.py +173 -0
- trilogy/dialect/mock.py +190 -0
- trilogy/dialect/postgres.py +117 -0
- trilogy/dialect/presto.py +110 -0
- trilogy/dialect/results.py +89 -0
- trilogy/dialect/snowflake.py +129 -0
- trilogy/dialect/sql_server.py +137 -0
- trilogy/engine.py +48 -0
- trilogy/execution/config.py +75 -0
- trilogy/executor.py +568 -0
- trilogy/hooks/__init__.py +4 -0
- trilogy/hooks/base_hook.py +40 -0
- trilogy/hooks/graph_hook.py +139 -0
- trilogy/hooks/query_debugger.py +166 -0
- trilogy/metadata/__init__.py +0 -0
- trilogy/parser.py +10 -0
- trilogy/parsing/README.md +21 -0
- trilogy/parsing/__init__.py +0 -0
- trilogy/parsing/common.py +1069 -0
- trilogy/parsing/config.py +5 -0
- trilogy/parsing/exceptions.py +8 -0
- trilogy/parsing/helpers.py +1 -0
- trilogy/parsing/parse_engine.py +2813 -0
- trilogy/parsing/render.py +769 -0
- trilogy/parsing/trilogy.lark +540 -0
- trilogy/py.typed +0 -0
- trilogy/render.py +42 -0
- trilogy/scripts/README.md +9 -0
- trilogy/scripts/__init__.py +0 -0
- trilogy/scripts/agent.py +41 -0
- trilogy/scripts/agent_info.py +303 -0
- trilogy/scripts/common.py +355 -0
- trilogy/scripts/dependency/Cargo.lock +617 -0
- trilogy/scripts/dependency/Cargo.toml +39 -0
- trilogy/scripts/dependency/README.md +131 -0
- trilogy/scripts/dependency/build.sh +25 -0
- trilogy/scripts/dependency/src/directory_resolver.rs +177 -0
- trilogy/scripts/dependency/src/lib.rs +16 -0
- trilogy/scripts/dependency/src/main.rs +770 -0
- trilogy/scripts/dependency/src/parser.rs +435 -0
- trilogy/scripts/dependency/src/preql.pest +208 -0
- trilogy/scripts/dependency/src/python_bindings.rs +303 -0
- trilogy/scripts/dependency/src/resolver.rs +716 -0
- trilogy/scripts/dependency/tests/base.preql +3 -0
- trilogy/scripts/dependency/tests/cli_integration.rs +377 -0
- trilogy/scripts/dependency/tests/customer.preql +6 -0
- trilogy/scripts/dependency/tests/main.preql +9 -0
- trilogy/scripts/dependency/tests/orders.preql +7 -0
- trilogy/scripts/dependency/tests/test_data/base.preql +9 -0
- trilogy/scripts/dependency/tests/test_data/consumer.preql +1 -0
- trilogy/scripts/dependency.py +323 -0
- trilogy/scripts/display.py +512 -0
- trilogy/scripts/environment.py +46 -0
- trilogy/scripts/fmt.py +32 -0
- trilogy/scripts/ingest.py +471 -0
- trilogy/scripts/ingest_helpers/__init__.py +1 -0
- trilogy/scripts/ingest_helpers/foreign_keys.py +123 -0
- trilogy/scripts/ingest_helpers/formatting.py +93 -0
- trilogy/scripts/ingest_helpers/typing.py +161 -0
- trilogy/scripts/init.py +105 -0
- trilogy/scripts/parallel_execution.py +713 -0
- trilogy/scripts/plan.py +189 -0
- trilogy/scripts/run.py +63 -0
- trilogy/scripts/serve.py +140 -0
- trilogy/scripts/serve_helpers/__init__.py +41 -0
- trilogy/scripts/serve_helpers/file_discovery.py +142 -0
- trilogy/scripts/serve_helpers/index_generation.py +206 -0
- trilogy/scripts/serve_helpers/models.py +38 -0
- trilogy/scripts/single_execution.py +131 -0
- trilogy/scripts/testing.py +119 -0
- trilogy/scripts/trilogy.py +68 -0
- trilogy/std/__init__.py +0 -0
- trilogy/std/color.preql +3 -0
- trilogy/std/date.preql +13 -0
- trilogy/std/display.preql +18 -0
- trilogy/std/geography.preql +22 -0
- trilogy/std/metric.preql +15 -0
- trilogy/std/money.preql +67 -0
- trilogy/std/net.preql +14 -0
- trilogy/std/ranking.preql +7 -0
- trilogy/std/report.preql +5 -0
- trilogy/std/semantic.preql +6 -0
- trilogy/utility.py +34 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from trilogy.authoring import (
|
|
5
|
+
DataType,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
# Rich type detection mappings
|
|
9
|
+
RICH_TYPE_PATTERNS: dict[str, dict[str, Any]] = {
|
|
10
|
+
"geography": {
|
|
11
|
+
"latitude": {
|
|
12
|
+
"patterns": [r"(?:^|_)lat(?:$|_)", r"(?:^|_)latitude(?:$|_)"],
|
|
13
|
+
"import": "std.geography",
|
|
14
|
+
"type_name": "latitude",
|
|
15
|
+
"base_type": DataType.FLOAT,
|
|
16
|
+
},
|
|
17
|
+
"longitude": {
|
|
18
|
+
"patterns": [
|
|
19
|
+
r"(?:^|_)lon(?:$|_)",
|
|
20
|
+
r"(?:^|_)lng(?:$|_)",
|
|
21
|
+
r"(?:^|_)long(?:$|_)",
|
|
22
|
+
r"(?:^|_)longitude(?:$|_)",
|
|
23
|
+
],
|
|
24
|
+
"import": "std.geography",
|
|
25
|
+
"type_name": "longitude",
|
|
26
|
+
"base_type": DataType.FLOAT,
|
|
27
|
+
},
|
|
28
|
+
"city": {
|
|
29
|
+
"patterns": [r"(?:^|_)city(?:$|_)"],
|
|
30
|
+
"import": "std.geography",
|
|
31
|
+
"type_name": "city",
|
|
32
|
+
"base_type": DataType.STRING,
|
|
33
|
+
},
|
|
34
|
+
"country": {
|
|
35
|
+
"patterns": [r"(?:^|_)country(?:$|_)"],
|
|
36
|
+
"import": "std.geography",
|
|
37
|
+
"type_name": "country",
|
|
38
|
+
"base_type": DataType.STRING,
|
|
39
|
+
},
|
|
40
|
+
"country_code": {
|
|
41
|
+
"patterns": [r"country_code", r"countrycode"],
|
|
42
|
+
"import": "std.geography",
|
|
43
|
+
"type_name": "country_code",
|
|
44
|
+
"base_type": DataType.STRING,
|
|
45
|
+
},
|
|
46
|
+
"us_state": {
|
|
47
|
+
"patterns": [r"(?:^|_)state(?:$|_)", r"us_state"],
|
|
48
|
+
"import": "std.geography",
|
|
49
|
+
"type_name": "us_state",
|
|
50
|
+
"base_type": DataType.STRING,
|
|
51
|
+
},
|
|
52
|
+
"us_zip_code": {
|
|
53
|
+
"patterns": [r"(?:^|_)zip(?:$|_)", r"zipcode", r"zip_code", r"postal_code"],
|
|
54
|
+
"import": "std.geography",
|
|
55
|
+
"type_name": "us_zip_code",
|
|
56
|
+
"base_type": DataType.STRING,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
"net": {
|
|
60
|
+
"email_address": {
|
|
61
|
+
"patterns": [r"(?:^|_)email(?:$|_)", r"email_address"],
|
|
62
|
+
"import": "std.net",
|
|
63
|
+
"type_name": "email_address",
|
|
64
|
+
"base_type": DataType.STRING,
|
|
65
|
+
},
|
|
66
|
+
"url": {
|
|
67
|
+
"patterns": [r"(?:^|_)url(?:$|_)", r"(?:^|_)website(?:$|_)"],
|
|
68
|
+
"import": "std.net",
|
|
69
|
+
"type_name": "url",
|
|
70
|
+
"base_type": DataType.STRING,
|
|
71
|
+
},
|
|
72
|
+
"ipv4_address": {
|
|
73
|
+
"patterns": [r"(?:^|_)ip(?:$|_)", r"(?:^|_)ipv4(?:$|_)", r"ip_address"],
|
|
74
|
+
"import": "std.net",
|
|
75
|
+
"type_name": "ipv4_address",
|
|
76
|
+
"base_type": DataType.STRING,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def detect_rich_type(
|
|
83
|
+
column_name: str, base_datatype: DataType
|
|
84
|
+
) -> tuple[str, str] | tuple[None, None]:
|
|
85
|
+
"""Detect if a column name matches a rich type pattern.
|
|
86
|
+
|
|
87
|
+
Returns: (import_path, type_name) or (None, None) if no match
|
|
88
|
+
|
|
89
|
+
Note: When multiple patterns match, the one with the longest matched
|
|
90
|
+
string is preferred to ensure more specific matches win.
|
|
91
|
+
"""
|
|
92
|
+
column_lower = column_name.lower()
|
|
93
|
+
|
|
94
|
+
# Collect all matches and sort by matched string length (longest first) to prefer more specific matches
|
|
95
|
+
matches = []
|
|
96
|
+
|
|
97
|
+
for _, types in RICH_TYPE_PATTERNS.items():
|
|
98
|
+
for _, config in types.items():
|
|
99
|
+
# Only consider if base types match
|
|
100
|
+
if config["base_type"] != base_datatype:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# Check if any pattern matches
|
|
104
|
+
for pattern in config["patterns"]:
|
|
105
|
+
match = re.search(pattern, column_lower)
|
|
106
|
+
if match:
|
|
107
|
+
# Store match with the length of the matched string for sorting
|
|
108
|
+
matched_length = len(match.group())
|
|
109
|
+
matches.append(
|
|
110
|
+
(matched_length, config["import"], config["type_name"])
|
|
111
|
+
)
|
|
112
|
+
break # Only need one match per type
|
|
113
|
+
|
|
114
|
+
# Return the most specific match (longest matched string)
|
|
115
|
+
if matches:
|
|
116
|
+
matches.sort(reverse=True) # Sort by matched string length descending
|
|
117
|
+
return str(matches[0][1]), str(matches[0][2])
|
|
118
|
+
|
|
119
|
+
return None, None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def infer_datatype_from_sql_type(sql_type: str) -> DataType:
|
|
123
|
+
"""Infer Trilogy datatype from SQL type string."""
|
|
124
|
+
sql_type_lower = sql_type.lower()
|
|
125
|
+
|
|
126
|
+
# Integer types
|
|
127
|
+
if any(
|
|
128
|
+
t in sql_type_lower
|
|
129
|
+
for t in ["int", "integer", "smallint", "tinyint", "mediumint"]
|
|
130
|
+
):
|
|
131
|
+
return DataType.INTEGER
|
|
132
|
+
if any(t in sql_type_lower for t in ["bigint", "long", "int64"]):
|
|
133
|
+
return DataType.BIGINT
|
|
134
|
+
|
|
135
|
+
# Numeric/decimal types
|
|
136
|
+
if any(t in sql_type_lower for t in ["numeric", "decimal", "money"]):
|
|
137
|
+
return DataType.NUMERIC
|
|
138
|
+
if any(t in sql_type_lower for t in ["float", "double", "real", "float64"]):
|
|
139
|
+
return DataType.FLOAT
|
|
140
|
+
|
|
141
|
+
# String types
|
|
142
|
+
if any(
|
|
143
|
+
t in sql_type_lower
|
|
144
|
+
for t in ["char", "varchar", "text", "string", "clob", "nchar", "nvarchar"]
|
|
145
|
+
):
|
|
146
|
+
return DataType.STRING
|
|
147
|
+
|
|
148
|
+
# Boolean
|
|
149
|
+
if any(t in sql_type_lower for t in ["bool", "boolean", "bit"]):
|
|
150
|
+
return DataType.BOOL
|
|
151
|
+
|
|
152
|
+
# Date/Time types
|
|
153
|
+
if "timestamp" in sql_type_lower:
|
|
154
|
+
return DataType.TIMESTAMP
|
|
155
|
+
if "datetime" in sql_type_lower:
|
|
156
|
+
return DataType.DATETIME
|
|
157
|
+
if "date" in sql_type_lower:
|
|
158
|
+
return DataType.DATE
|
|
159
|
+
|
|
160
|
+
# Default to string for unknown types
|
|
161
|
+
return DataType.STRING
|
trilogy/scripts/init.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Init command for Trilogy CLI - creates a new default workspace."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from click import argument, pass_context
|
|
6
|
+
|
|
7
|
+
from trilogy.scripts.display import print_error, print_info, print_success
|
|
8
|
+
|
|
9
|
+
# Default hello world script content
|
|
10
|
+
HELLO_WORLD_SCRIPT = """# Welcome to Trilogy!
|
|
11
|
+
# This is a simple example script to get you started.
|
|
12
|
+
|
|
13
|
+
# Define a simple concept
|
|
14
|
+
key user_id int;
|
|
15
|
+
|
|
16
|
+
# Create a sample datasource
|
|
17
|
+
datasource users (
|
|
18
|
+
user_id
|
|
19
|
+
)
|
|
20
|
+
grain (user_id)
|
|
21
|
+
query '''
|
|
22
|
+
SELECT 1 as user_id
|
|
23
|
+
UNION ALL
|
|
24
|
+
SELECT 2 as user_id
|
|
25
|
+
UNION ALL
|
|
26
|
+
SELECT 3 as user_id
|
|
27
|
+
''';
|
|
28
|
+
|
|
29
|
+
# Query the data
|
|
30
|
+
SELECT
|
|
31
|
+
user_id
|
|
32
|
+
;
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# Default trilogy.toml content
|
|
36
|
+
DEFAULT_CONFIG = """# Trilogy Configuration File
|
|
37
|
+
# Learn more at: https://github.com/trilmhmogy-data/pytrilogy
|
|
38
|
+
|
|
39
|
+
[engine]
|
|
40
|
+
# Default dialect for execution
|
|
41
|
+
# dialect = "duck_db"
|
|
42
|
+
|
|
43
|
+
# Max parallelism for multi-script execution
|
|
44
|
+
# parallelism = 3
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
[setup]
|
|
48
|
+
# Startup scripts to run before execution
|
|
49
|
+
# trilogy = []
|
|
50
|
+
# sql = []
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@argument("path", type=str, required=False, default=".")
|
|
55
|
+
@pass_context
|
|
56
|
+
def init(ctx, path: str):
|
|
57
|
+
"""Create a new default Trilogy workspace.
|
|
58
|
+
|
|
59
|
+
Initializes a new workspace with default configuration and structure:
|
|
60
|
+
- trilogy.toml: Configuration file
|
|
61
|
+
- raw/: Directory for raw data models
|
|
62
|
+
- derived/: Directory for derived data models
|
|
63
|
+
- jobs/: Directory for job scripts
|
|
64
|
+
- hello_world.preql: Example script
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
path: Path where the workspace should be created (default: current directory)
|
|
68
|
+
"""
|
|
69
|
+
workspace_path = Path(path).resolve()
|
|
70
|
+
|
|
71
|
+
print_info(f"Initializing Trilogy workspace at: {workspace_path}")
|
|
72
|
+
|
|
73
|
+
# Check if path already has trilogy files
|
|
74
|
+
if (workspace_path / "trilogy.toml").exists():
|
|
75
|
+
print_error(
|
|
76
|
+
f"Workspace already initialized at {workspace_path} (trilogy.toml exists)"
|
|
77
|
+
)
|
|
78
|
+
raise SystemExit(1)
|
|
79
|
+
|
|
80
|
+
# Create base directory if it doesn't exist
|
|
81
|
+
workspace_path.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
|
|
83
|
+
# Create subdirectories
|
|
84
|
+
subdirs = ["raw", "derived", "jobs"]
|
|
85
|
+
for subdir in subdirs:
|
|
86
|
+
subdir_path = workspace_path / subdir
|
|
87
|
+
subdir_path.mkdir(exist_ok=True)
|
|
88
|
+
print_info(f"Created directory: {subdir}/")
|
|
89
|
+
|
|
90
|
+
# Create trilogy.toml
|
|
91
|
+
config_path = workspace_path / "trilogy.toml"
|
|
92
|
+
config_path.write_text(DEFAULT_CONFIG)
|
|
93
|
+
print_info("Created configuration: trilogy.toml")
|
|
94
|
+
|
|
95
|
+
# Create hello_world.preql
|
|
96
|
+
hello_world_path = workspace_path / "hello_world.preql"
|
|
97
|
+
hello_world_path.write_text(HELLO_WORLD_SCRIPT)
|
|
98
|
+
print_info("Created example script: hello_world.preql")
|
|
99
|
+
|
|
100
|
+
print_success(
|
|
101
|
+
f"\nWorkspace initialized successfully!\n\n"
|
|
102
|
+
f"Get started with:\n"
|
|
103
|
+
f" cd {workspace_path.name if path != '.' else workspace_path}\n"
|
|
104
|
+
f" trilogy unit hello_world.preql\n"
|
|
105
|
+
)
|