awx-zipline-ai 0.0.32__py3-none-any.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.
Files changed (96) hide show
  1. __init__.py +0 -0
  2. agent/__init__.py +1 -0
  3. agent/constants.py +15 -0
  4. agent/ttypes.py +1684 -0
  5. ai/__init__.py +0 -0
  6. ai/chronon/__init__.py +0 -0
  7. ai/chronon/airflow_helpers.py +248 -0
  8. ai/chronon/cli/__init__.py +0 -0
  9. ai/chronon/cli/compile/__init__.py +0 -0
  10. ai/chronon/cli/compile/column_hashing.py +336 -0
  11. ai/chronon/cli/compile/compile_context.py +173 -0
  12. ai/chronon/cli/compile/compiler.py +183 -0
  13. ai/chronon/cli/compile/conf_validator.py +742 -0
  14. ai/chronon/cli/compile/display/__init__.py +0 -0
  15. ai/chronon/cli/compile/display/class_tracker.py +102 -0
  16. ai/chronon/cli/compile/display/compile_status.py +95 -0
  17. ai/chronon/cli/compile/display/compiled_obj.py +12 -0
  18. ai/chronon/cli/compile/display/console.py +3 -0
  19. ai/chronon/cli/compile/display/diff_result.py +111 -0
  20. ai/chronon/cli/compile/fill_templates.py +35 -0
  21. ai/chronon/cli/compile/parse_configs.py +134 -0
  22. ai/chronon/cli/compile/parse_teams.py +242 -0
  23. ai/chronon/cli/compile/serializer.py +109 -0
  24. ai/chronon/cli/compile/version_utils.py +42 -0
  25. ai/chronon/cli/git_utils.py +145 -0
  26. ai/chronon/cli/logger.py +59 -0
  27. ai/chronon/constants.py +3 -0
  28. ai/chronon/group_by.py +692 -0
  29. ai/chronon/join.py +580 -0
  30. ai/chronon/logger.py +23 -0
  31. ai/chronon/model.py +40 -0
  32. ai/chronon/query.py +126 -0
  33. ai/chronon/repo/__init__.py +39 -0
  34. ai/chronon/repo/aws.py +284 -0
  35. ai/chronon/repo/cluster.py +136 -0
  36. ai/chronon/repo/compile.py +62 -0
  37. ai/chronon/repo/constants.py +164 -0
  38. ai/chronon/repo/default_runner.py +269 -0
  39. ai/chronon/repo/explore.py +418 -0
  40. ai/chronon/repo/extract_objects.py +134 -0
  41. ai/chronon/repo/gcp.py +586 -0
  42. ai/chronon/repo/gitpython_utils.py +15 -0
  43. ai/chronon/repo/hub_runner.py +261 -0
  44. ai/chronon/repo/hub_uploader.py +109 -0
  45. ai/chronon/repo/init.py +60 -0
  46. ai/chronon/repo/join_backfill.py +119 -0
  47. ai/chronon/repo/run.py +296 -0
  48. ai/chronon/repo/serializer.py +133 -0
  49. ai/chronon/repo/team_json_utils.py +46 -0
  50. ai/chronon/repo/utils.py +481 -0
  51. ai/chronon/repo/zipline.py +35 -0
  52. ai/chronon/repo/zipline_hub.py +277 -0
  53. ai/chronon/resources/__init__.py +0 -0
  54. ai/chronon/resources/gcp/__init__.py +0 -0
  55. ai/chronon/resources/gcp/group_bys/__init__.py +0 -0
  56. ai/chronon/resources/gcp/group_bys/test/__init__.py +0 -0
  57. ai/chronon/resources/gcp/group_bys/test/data.py +30 -0
  58. ai/chronon/resources/gcp/joins/__init__.py +0 -0
  59. ai/chronon/resources/gcp/joins/test/__init__.py +0 -0
  60. ai/chronon/resources/gcp/joins/test/data.py +26 -0
  61. ai/chronon/resources/gcp/sources/__init__.py +0 -0
  62. ai/chronon/resources/gcp/sources/test/__init__.py +0 -0
  63. ai/chronon/resources/gcp/sources/test/data.py +26 -0
  64. ai/chronon/resources/gcp/teams.py +58 -0
  65. ai/chronon/source.py +86 -0
  66. ai/chronon/staging_query.py +226 -0
  67. ai/chronon/types.py +58 -0
  68. ai/chronon/utils.py +510 -0
  69. ai/chronon/windows.py +48 -0
  70. awx_zipline_ai-0.0.32.dist-info/METADATA +197 -0
  71. awx_zipline_ai-0.0.32.dist-info/RECORD +96 -0
  72. awx_zipline_ai-0.0.32.dist-info/WHEEL +5 -0
  73. awx_zipline_ai-0.0.32.dist-info/entry_points.txt +2 -0
  74. awx_zipline_ai-0.0.32.dist-info/top_level.txt +4 -0
  75. gen_thrift/__init__.py +0 -0
  76. gen_thrift/api/__init__.py +1 -0
  77. gen_thrift/api/constants.py +15 -0
  78. gen_thrift/api/ttypes.py +3754 -0
  79. gen_thrift/common/__init__.py +1 -0
  80. gen_thrift/common/constants.py +15 -0
  81. gen_thrift/common/ttypes.py +1814 -0
  82. gen_thrift/eval/__init__.py +1 -0
  83. gen_thrift/eval/constants.py +15 -0
  84. gen_thrift/eval/ttypes.py +660 -0
  85. gen_thrift/fetcher/__init__.py +1 -0
  86. gen_thrift/fetcher/constants.py +15 -0
  87. gen_thrift/fetcher/ttypes.py +127 -0
  88. gen_thrift/hub/__init__.py +1 -0
  89. gen_thrift/hub/constants.py +15 -0
  90. gen_thrift/hub/ttypes.py +1109 -0
  91. gen_thrift/observability/__init__.py +1 -0
  92. gen_thrift/observability/constants.py +15 -0
  93. gen_thrift/observability/ttypes.py +2355 -0
  94. gen_thrift/planner/__init__.py +1 -0
  95. gen_thrift/planner/constants.py +15 -0
  96. gen_thrift/planner/ttypes.py +1967 -0
@@ -0,0 +1,481 @@
1
+ import functools
2
+ import json
3
+ import os
4
+ import re
5
+ import subprocess
6
+ import time
7
+ import xml.etree.ElementTree as ET
8
+ from datetime import datetime, timedelta
9
+ from enum import Enum
10
+
11
+ from click import style
12
+
13
+ from ai.chronon.cli.compile.parse_teams import EnvOrConfigAttribute
14
+ from ai.chronon.logger import get_logger
15
+ from ai.chronon.repo.constants import (
16
+ APP_NAME_TEMPLATE,
17
+ SCALA_VERSION_FOR_SPARK,
18
+ SUPPORTED_SPARK,
19
+ )
20
+
21
+ LOG = get_logger()
22
+
23
+
24
+ class JobType(Enum):
25
+ SPARK = "spark"
26
+ FLINK = "flink"
27
+
28
+
29
+ def retry_decorator(retries=3, backoff=20):
30
+ def wrapper(func):
31
+ def wrapped(*args, **kwargs):
32
+ attempt = 0
33
+ while attempt <= retries:
34
+ try:
35
+ return func(*args, **kwargs)
36
+ except Exception as e:
37
+ attempt += 1
38
+ LOG.exception(e)
39
+ sleep_time = attempt * backoff
40
+ LOG.info(
41
+ "[{}] Retry: {} out of {}/ Sleeping for {}".format(
42
+ func.__name__, attempt, retries, sleep_time
43
+ )
44
+ )
45
+ time.sleep(sleep_time)
46
+ return func(*args, **kwargs)
47
+
48
+ return wrapped
49
+
50
+ return wrapper
51
+
52
+
53
+ def get_environ_arg(env_name, ignoreError=False) -> str:
54
+ value = os.environ.get(env_name)
55
+ if not value and not ignoreError:
56
+ raise ValueError(f"Please set {env_name} environment variable")
57
+ return value
58
+
59
+
60
+ def get_customer_warehouse_bucket() -> str:
61
+ return f"zipline-warehouse-{get_customer_id()}"
62
+
63
+
64
+ def get_customer_id() -> str:
65
+ return get_environ_arg("CUSTOMER_ID")
66
+
67
+
68
+ def extract_filename_from_path(path):
69
+ return path.split("/")[-1]
70
+
71
+
72
+ def check_call(cmd):
73
+ LOG.info("Running command: " + cmd)
74
+ return subprocess.check_call(cmd.split(), bufsize=0)
75
+
76
+
77
+ def check_output(cmd):
78
+ LOG.info("Running command: " + cmd)
79
+ return subprocess.check_output(cmd.split(), bufsize=0).strip()
80
+
81
+
82
+ def custom_json(conf):
83
+ """Extract the json stored in customJson for a conf."""
84
+ if conf.get("metaData", {}).get("customJson"):
85
+ return json.loads(conf["metaData"]["customJson"])
86
+ return {}
87
+
88
+
89
+ def download_only_once(url, path, skip_download=False):
90
+ if skip_download:
91
+ LOG.info("Skipping download of " + path)
92
+ return
93
+ should_download = True
94
+ path = path.strip()
95
+ if os.path.exists(path):
96
+ content_output = check_output("curl -sI " + url).decode("utf-8")
97
+ content_length = re.search("(content-length:\\s)(\\d+)", content_output.lower())
98
+ remote_size = int(content_length.group().split()[-1])
99
+ local_size = int(check_output("wc -c " + path).split()[0])
100
+ LOG.info(
101
+ """Files sizes of {url} vs. {path}
102
+ Remote size: {remote_size}
103
+ Local size : {local_size}""".format(**locals())
104
+ )
105
+ if local_size == remote_size:
106
+ LOG.info("Sizes match. Assuming it's already downloaded.")
107
+ should_download = False
108
+ if should_download:
109
+ LOG.info("Different file from remote at local: " + path + ". Re-downloading..")
110
+ check_call("curl {} -o {} --connect-timeout 10".format(url, path))
111
+ else:
112
+ LOG.info("No file at: " + path + ". Downloading..")
113
+ check_call("curl {} -o {} --connect-timeout 10".format(url, path))
114
+
115
+
116
+ # NOTE: this is only for the open source chronon. For the internal zipline version, we have a different jar to download.
117
+ @retry_decorator(retries=3, backoff=50)
118
+ def download_jar(
119
+ version,
120
+ jar_type="uber",
121
+ release_tag=None,
122
+ spark_version="2.4.0",
123
+ skip_download=False,
124
+ ):
125
+ assert spark_version in SUPPORTED_SPARK, (
126
+ f"Received unsupported spark version {spark_version}. "
127
+ f"Supported spark versions are {SUPPORTED_SPARK}"
128
+ )
129
+ scala_version = SCALA_VERSION_FOR_SPARK[spark_version]
130
+ maven_url_prefix = os.environ.get("CHRONON_MAVEN_MIRROR_PREFIX", None)
131
+ default_url_prefix = "https://s01.oss.sonatype.org/service/local/repositories/public/content"
132
+ url_prefix = maven_url_prefix if maven_url_prefix else default_url_prefix
133
+ base_url = "{}/ai/chronon/spark_{}_{}".format(url_prefix, jar_type, scala_version)
134
+ LOG.info("Downloading jar from url: " + base_url)
135
+ jar_path = os.environ.get("CHRONON_DRIVER_JAR", None)
136
+ if jar_path is None:
137
+ if version == "latest":
138
+ version = None
139
+ if version is None:
140
+ metadata_content = check_output("curl -s {}/maven-metadata.xml".format(base_url))
141
+ meta_tree = ET.fromstring(metadata_content)
142
+ versions = [
143
+ node.text
144
+ for node in meta_tree.findall("./versioning/versions/")
145
+ if re.search(
146
+ r"^\d+\.\d+\.\d+{}$".format(
147
+ r"\_{}\d*".format(release_tag) if release_tag else ""
148
+ ),
149
+ node.text,
150
+ )
151
+ ]
152
+ version = versions[-1]
153
+ jar_url = (
154
+ "{base_url}/{version}/spark_{jar_type}_{scala_version}-{version}-assembly.jar".format(
155
+ base_url=base_url,
156
+ version=version,
157
+ scala_version=scala_version,
158
+ jar_type=jar_type,
159
+ )
160
+ )
161
+ jar_path = os.path.join("/tmp", extract_filename_from_path(jar_url))
162
+ download_only_once(jar_url, jar_path, skip_download)
163
+ return jar_path
164
+
165
+
166
+ def get_teams_json_file_path(repo_path):
167
+ return os.path.join(repo_path, "teams.json")
168
+
169
+
170
+ def get_teams_py_file_path(repo_path):
171
+ return os.path.join(repo_path, "teams.py")
172
+
173
+
174
+ def set_runtime_env_v3(params, conf):
175
+ effective_mode = params.get("mode")
176
+
177
+ runtime_env = {"APP_NAME": params.get("app_name")}
178
+
179
+ if params.get("repo") and conf and effective_mode:
180
+ # get the conf file
181
+ conf_path = os.path.join(params["repo"], conf)
182
+ if os.path.isfile(conf_path):
183
+ with open(conf_path, "r") as infile:
184
+ conf_json = json.load(infile)
185
+ metadata = (
186
+ conf_json.get("metaData", {}) or conf_json
187
+ ) # user may just pass metadata as the entire json
188
+ env = metadata.get("executionInfo", {}).get("env", {})
189
+ runtime_env.update(
190
+ env.get(EnvOrConfigAttribute.ENV, {}).get(effective_mode, {})
191
+ or env.get("common", {})
192
+ )
193
+ # Also set APP_NAME
194
+ try:
195
+ _, conf_type, team, _ = conf.split("/")[-4:]
196
+ if not team:
197
+ team = "default"
198
+ # context is the environment in which the job is running, which is provided from the args,
199
+ # default to be dev.
200
+ if params["env"]:
201
+ context = params["env"]
202
+ else:
203
+ context = "dev"
204
+ LOG.info(f"Context: {context} -- conf_type: {conf_type} -- team: {team}")
205
+
206
+ runtime_env["APP_NAME"] = APP_NAME_TEMPLATE.format(
207
+ mode=effective_mode,
208
+ conf_type=conf_type,
209
+ context=context,
210
+ name=conf_json["metaData"]["name"],
211
+ )
212
+ except Exception:
213
+ LOG.warn(
214
+ "Failed to set APP_NAME due to invalid conf path: {}, please ensure to supply the "
215
+ "relative path to zipline/ folder".format(conf)
216
+ )
217
+ else:
218
+ if not params.get("app_name") and not os.environ.get("APP_NAME"):
219
+ # Provide basic app_name when no conf is defined.
220
+ # Modes like metadata-upload and metadata-export can rely on conf-type or folder rather than a conf.
221
+ runtime_env["APP_NAME"] = "_".join(
222
+ [k for k in ["chronon", effective_mode.replace("-", "_")] if k is not None]
223
+ )
224
+ for key, value in runtime_env.items():
225
+ if key not in os.environ and value is not None:
226
+ LOG.info(f"Setting to environment: {key}={value}")
227
+ print(f"Setting to environment: {key}={value}")
228
+ os.environ[key] = value
229
+
230
+
231
+ # TODO: delete this when we cutover
232
+ def set_runtime_env(params):
233
+ """
234
+ Setting the runtime environment variables.
235
+ These are extracted from the common env, the team env and the common env.
236
+ In order to use the environment variables defined in the configs as overrides for the args in the cli this method
237
+ needs to be run before the runner and jar downloads.
238
+
239
+ The order of priority is:
240
+ - Environment variables existing already.
241
+ - Environment variables derived from args (like app_name)
242
+ - conf.metaData.modeToEnvMap for the mode (set on config)
243
+ - team's dev environment for each mode set on teams.json
244
+ - team's prod environment for each mode set on teams.json
245
+ - default team environment per context and mode set on teams.json
246
+ - Common Environment set in teams.json
247
+ """
248
+
249
+ environment = {
250
+ "common_env": {},
251
+ "conf_env": {},
252
+ "default_env": {},
253
+ "team_env": {},
254
+ "production_team_env": {},
255
+ "cli_args": {},
256
+ }
257
+
258
+ conf_type = None
259
+ # Normalize modes that are effectively replacement of each other (streaming/local-streaming/streaming-client)
260
+ effective_mode = params["mode"]
261
+ if effective_mode and "streaming" in effective_mode:
262
+ effective_mode = "streaming"
263
+ if params["repo"]:
264
+ # Break if teams.json and teams.py exists
265
+ teams_json_file = get_teams_json_file_path(params["repo"])
266
+ teams_py_file = get_teams_py_file_path(params["repo"])
267
+
268
+ if os.path.exists(teams_json_file) and os.path.exists(teams_py_file):
269
+ raise ValueError("Both teams.json and teams.py exist. Please only use teams.py.")
270
+
271
+ if os.path.exists(teams_json_file):
272
+ set_runtime_env_teams_json(environment, params, effective_mode, teams_json_file)
273
+ if params["app_name"]:
274
+ environment["cli_args"]["APP_NAME"] = params["app_name"]
275
+ else:
276
+ if not params["app_name"] and not environment["cli_args"].get("APP_NAME"):
277
+ # Provide basic app_name when no conf is defined.
278
+ # Modes like metadata-upload and metadata-export can rely on conf-type or folder rather than a conf.
279
+ environment["cli_args"]["APP_NAME"] = "_".join(
280
+ [
281
+ k
282
+ for k in [
283
+ "chronon",
284
+ conf_type,
285
+ (params["mode"].replace("-", "_") if params["mode"] else None),
286
+ ]
287
+ if k is not None
288
+ ]
289
+ )
290
+
291
+ # Adding these to make sure they are printed if provided by the environment.
292
+ environment["cli_args"]["CHRONON_DRIVER_JAR"] = params["chronon_jar"]
293
+ environment["cli_args"]["CHRONON_ONLINE_JAR"] = params["online_jar"]
294
+ environment["cli_args"]["CHRONON_ONLINE_CLASS"] = params["online_class"]
295
+ order = [
296
+ "conf_env",
297
+ "team_env", # todo: team_env maybe should be below default/common_env
298
+ "production_team_env",
299
+ "default_env",
300
+ "common_env",
301
+ "cli_args",
302
+ ]
303
+ LOG.info("Setting env variables:")
304
+ for key in os.environ:
305
+ if any([key in (environment.get(set_key, {}) or {}) for set_key in order]):
306
+ LOG.info(f"From <environment> found {key}={os.environ[key]}")
307
+ for set_key in order:
308
+ for key, value in (environment.get(set_key, {}) or {}).items():
309
+ if key not in os.environ and value is not None:
310
+ LOG.info(f"From <{set_key}> setting {key}={value}")
311
+ os.environ[key] = value
312
+
313
+
314
+ # TODO: delete this when we cutover
315
+ def set_runtime_env_teams_json(environment, params, effective_mode, teams_json_file):
316
+ if os.path.exists(teams_json_file):
317
+ with open(teams_json_file, "r") as infile:
318
+ teams_json = json.load(infile)
319
+ # we should have a fallback if user wants to set to something else `default`
320
+ environment["common_env"] = teams_json.get("default", {}).get("common_env", {})
321
+ if params["conf"] and effective_mode:
322
+ try:
323
+ _, conf_type, team, _ = params["conf"].split("/")[-4:]
324
+ except Exception as e:
325
+ LOG.error(
326
+ "Invalid conf path: {}, please ensure to supply the relative path to zipline/ folder".format(
327
+ params["conf"]
328
+ )
329
+ )
330
+ raise e
331
+ if not team:
332
+ team = "default"
333
+ # context is the environment in which the job is running, which is provided from the args,
334
+ # default to be dev.
335
+ if params["env"]:
336
+ context = params["env"]
337
+ else:
338
+ context = "dev"
339
+ LOG.info(f"Context: {context} -- conf_type: {conf_type} -- team: {team}")
340
+ conf_path = os.path.join(params["repo"], params["conf"])
341
+ if os.path.isfile(conf_path):
342
+ with open(conf_path, "r") as conf_file:
343
+ conf_json = json.load(conf_file)
344
+
345
+ new_env = (
346
+ conf_json.get("metaData")
347
+ .get("executionInfo", {})
348
+ .get("env", {})
349
+ .get(effective_mode, {})
350
+ )
351
+
352
+ old_env = (
353
+ conf_json.get("metaData").get("modeToEnvMap", {}).get(effective_mode, {})
354
+ )
355
+
356
+ environment["conf_env"] = new_env if new_env else old_env
357
+
358
+ # Load additional args used on backfill.
359
+ if custom_json(conf_json) and effective_mode in [
360
+ "backfill",
361
+ "backfill-left",
362
+ "backfill-final",
363
+ ]:
364
+ environment["conf_env"]["CHRONON_CONFIG_ADDITIONAL_ARGS"] = " ".join(
365
+ custom_json(conf_json).get("additional_args", [])
366
+ )
367
+ environment["cli_args"]["APP_NAME"] = APP_NAME_TEMPLATE.format(
368
+ mode=effective_mode,
369
+ conf_type=conf_type,
370
+ context=context,
371
+ name=conf_json["metaData"]["name"],
372
+ )
373
+ environment["team_env"] = teams_json[team].get(context, {}).get(effective_mode, {})
374
+ # fall-back to prod env even in dev mode when dev env is undefined.
375
+ environment["production_team_env"] = (
376
+ teams_json[team].get("production", {}).get(effective_mode, {})
377
+ )
378
+ # By default use production env.
379
+ environment["default_env"] = (
380
+ teams_json.get("default", {}).get("production", {}).get(effective_mode, {})
381
+ )
382
+ environment["cli_args"]["CHRONON_CONF_PATH"] = conf_path
383
+ if params["app_name"]:
384
+ environment["cli_args"]["APP_NAME"] = params["app_name"]
385
+ else:
386
+ if not params["app_name"] and not environment["cli_args"].get("APP_NAME"):
387
+ # Provide basic app_name when no conf is defined.
388
+ # Modes like metadata-upload and metadata-export can rely on conf-type or folder rather than a conf.
389
+ environment["cli_args"]["APP_NAME"] = "_".join(
390
+ [
391
+ k
392
+ for k in [
393
+ "chronon",
394
+ conf_type,
395
+ params["mode"].replace("-", "_") if params["mode"] else None,
396
+ ]
397
+ if k is not None
398
+ ]
399
+ )
400
+
401
+ # Adding these to make sure they are printed if provided by the environment.
402
+ environment["cli_args"]["CHRONON_DRIVER_JAR"] = params["chronon_jar"]
403
+ environment["cli_args"]["CHRONON_ONLINE_JAR"] = params["online_jar"]
404
+ environment["cli_args"]["CHRONON_ONLINE_CLASS"] = params["online_class"]
405
+ order = [
406
+ "conf_env",
407
+ "team_env",
408
+ "production_team_env",
409
+ "default_env",
410
+ "common_env",
411
+ "cli_args",
412
+ ]
413
+ LOG.info("Setting env variables:")
414
+ for key in os.environ:
415
+ if any([key in environment[set_key] for set_key in order]):
416
+ LOG.info(f"From <environment> found {key}={os.environ[key]}")
417
+ for set_key in order:
418
+ for key, value in environment[set_key].items():
419
+ if key not in os.environ and value is not None:
420
+ LOG.info(f"From <{set_key}> setting {key}={value}")
421
+ os.environ[key] = value
422
+
423
+
424
+ def split_date_range(start_date, end_date, parallelism):
425
+ start_date = datetime.strptime(start_date, "%Y-%m-%d")
426
+ end_date = datetime.strptime(end_date, "%Y-%m-%d")
427
+ if start_date > end_date:
428
+ raise ValueError("Start date should be earlier than end date")
429
+ total_days = (end_date - start_date).days + 1 # +1 to include the end_date in the range
430
+
431
+ # Check if parallelism is greater than total_days
432
+ if parallelism > total_days:
433
+ raise ValueError("Parallelism should be less than or equal to total days")
434
+
435
+ split_size = total_days // parallelism
436
+ date_ranges = []
437
+
438
+ for i in range(parallelism):
439
+ split_start = start_date + timedelta(days=i * split_size)
440
+ if i == parallelism - 1:
441
+ split_end = end_date
442
+ else:
443
+ split_end = split_start + timedelta(days=split_size - 1)
444
+ date_ranges.append((split_start.strftime("%Y-%m-%d"), split_end.strftime("%Y-%m-%d")))
445
+ return date_ranges
446
+
447
+
448
+ def get_metadata_name_from_conf(repo_path, conf_path):
449
+ with open(os.path.join(repo_path, conf_path), "r") as conf_file:
450
+ data = json.load(conf_file)
451
+ return data.get("metaData", {}).get("name", None)
452
+
453
+
454
+ def handle_conf_not_found(log_error=True, callback=None):
455
+ def wrapper(func):
456
+ @functools.wraps(func)
457
+ def wrapped(*args, **kwargs):
458
+ try:
459
+ return func(*args, **kwargs)
460
+ except FileNotFoundError as e:
461
+ if log_error:
462
+ print(style(f"File not found in {func.__name__}: {e}", fg="red"))
463
+ if callback:
464
+ callback(*args, **kwargs)
465
+ return
466
+
467
+ return wrapped
468
+
469
+ return wrapper
470
+
471
+
472
+ def print_possible_confs(conf, repo, *args, **kwargs):
473
+ conf_location = os.path.join(repo, conf)
474
+ conf_dirname = os.path.dirname(conf_location)
475
+ if os.path.exists(conf_dirname):
476
+ print(
477
+ f"Possible confs from {style(conf_dirname, fg='yellow')}: \n -",
478
+ "\n - ".join([name for name in os.listdir(conf_dirname)]),
479
+ )
480
+ else:
481
+ print(f"Directory does not exist: {style(conf_dirname, fg='yellow')}")
@@ -0,0 +1,35 @@
1
+ from importlib.metadata import PackageNotFoundError
2
+ from importlib.metadata import version as ver
3
+
4
+ import click
5
+
6
+ from ai.chronon.cli.compile.display.console import console
7
+ from ai.chronon.repo.compile import compile
8
+ from ai.chronon.repo.hub_runner import hub
9
+ from ai.chronon.repo.init import main as init_main
10
+ from ai.chronon.repo.run import main as run_main
11
+
12
+
13
+ def _set_package_version():
14
+ try:
15
+ package_version = ver("zipline-ai")
16
+ except PackageNotFoundError:
17
+ console.print("No package found. Continuing with the latest version.")
18
+ package_version = "latest"
19
+ return package_version
20
+
21
+
22
+ @click.group(
23
+ help="The Zipline CLI. A tool for compiling and running Zipline pipelines. For more information, see: https://zipline.ai/docs"
24
+ )
25
+ @click.version_option(version=_set_package_version())
26
+ @click.pass_context
27
+ def zipline(ctx):
28
+ ctx.ensure_object(dict)
29
+ ctx.obj["version"] = _set_package_version()
30
+
31
+
32
+ zipline.add_command(compile)
33
+ zipline.add_command(run_main)
34
+ zipline.add_command(init_main)
35
+ zipline.add_command(hub)