tinybird-cli 1.2.1.dev0__tar.gz → 1.2.1.dev2__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 (45) hide show
  1. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/PKG-INFO +11 -1
  2. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/__cli__.py +2 -2
  3. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/client.py +1 -0
  4. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/datafile.py +73 -25
  5. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/sql_template.py +139 -23
  6. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/cli.py +48 -29
  7. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/test.py +1 -0
  8. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird_cli.egg-info/PKG-INFO +11 -1
  9. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/setup.cfg +0 -0
  10. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/ch_utils/constants.py +0 -0
  11. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/ch_utils/engine.py +0 -0
  12. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/check_pypi.py +0 -0
  13. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/config.py +0 -0
  14. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/connector_settings.py +0 -0
  15. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/connectors.py +0 -0
  16. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/context.py +0 -0
  17. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/datatypes.py +0 -0
  18. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/feedback_manager.py +0 -0
  19. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/sql.py +0 -0
  20. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/sql_template_fmt.py +0 -0
  21. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/sql_toolset.py +0 -0
  22. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/syncasync.py +0 -0
  23. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli.py +0 -0
  24. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/auth.py +0 -0
  25. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/branch.py +0 -0
  26. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/cicd.py +0 -0
  27. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/common.py +0 -0
  28. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/config.py +0 -0
  29. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/connection.py +0 -0
  30. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/datasource.py +0 -0
  31. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/exceptions.py +0 -0
  32. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/job.py +0 -0
  33. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/pipe.py +0 -0
  34. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/telemetry.py +0 -0
  35. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  36. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  37. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/token.py +0 -0
  38. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/workspace.py +0 -0
  39. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  40. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird/tornado_template.py +0 -0
  41. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird_cli.egg-info/SOURCES.txt +0 -0
  42. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird_cli.egg-info/dependency_links.txt +0 -0
  43. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird_cli.egg-info/entry_points.txt +0 -0
  44. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird_cli.egg-info/requires.txt +0 -0
  45. {tinybird-cli-1.2.1.dev0 → tinybird-cli-1.2.1.dev2}/tinybird_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tinybird-cli
3
- Version: 1.2.1.dev0
3
+ Version: 1.2.1.dev2
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://docs.tinybird.co/cli.html
6
6
  Author: Tinybird
@@ -19,6 +19,16 @@ Changelog
19
19
 
20
20
  ---------
21
21
 
22
+ 1.2.1.dev2
23
+ ************
24
+
25
+ - `Changed` Internal change
26
+
27
+ 1.2.1.dev1
28
+ ************
29
+
30
+ - `Fixed` Skip `regression.yaml` in `tb test run` since it's reserved for regression tests
31
+
22
32
  1.2.0
23
33
  ************
24
34
 
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
4
  __url__ = 'https://docs.tinybird.co/cli.html'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '1.2.1.dev0'
8
- __revision__ = 'd687b0c'
7
+ __version__ = '1.2.1.dev2'
8
+ __revision__ = '06bf61e'
@@ -588,6 +588,7 @@ class TinyB(object):
588
588
  params = {}
589
589
  if pipeline:
590
590
  params = {"pipeline": pipeline}
591
+ params.update({"release_replacements": "true"})
591
592
 
592
593
  if len(sql) > TinyB.MAX_GET_LENGTH:
593
594
  return await self._req(f"/v0/sql?{urlencode(params)}", data=sql, method="POST")
@@ -23,7 +23,7 @@ from operator import itemgetter
23
23
  import shlex
24
24
  import sys
25
25
  import re
26
- from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple, Union, cast, Set
26
+ from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple, Union, cast
27
27
  from enum import Enum
28
28
  from mypy_extensions import VarArg, KwArg
29
29
  import difflib
@@ -2297,6 +2297,7 @@ async def new_pipe(
2297
2297
  tests_check_requests_from_branch: bool = False,
2298
2298
  config: Any = None,
2299
2299
  fork_downstream: Optional[bool] = False,
2300
+ fork: Optional[bool] = False,
2300
2301
  ): # noqa: C901
2301
2302
  # TODO use tb_client instead of calling the urls directly.
2302
2303
  host = tb_client.host
@@ -2382,7 +2383,7 @@ async def new_pipe(
2382
2383
  if populate_subset:
2383
2384
  params["populate_subset"] = populate_subset
2384
2385
  params["unlink_on_populate_error"] = "true" if unlink_on_populate_error else "false"
2385
- params["branch_mode"] = "fork" if fork_downstream else "None"
2386
+ params["branch_mode"] = "fork" if fork_downstream or fork else "None"
2386
2387
 
2387
2388
  body = {"name": p["name"], "description": p.get("description", "")}
2388
2389
 
@@ -2610,6 +2611,7 @@ async def new_ds(
2610
2611
  skip_confirmation: bool = False,
2611
2612
  current_ws=None,
2612
2613
  fork_downstream: Optional[bool] = False,
2614
+ fork: Optional[bool] = False,
2613
2615
  git_release: Optional[bool] = False,
2614
2616
  ):
2615
2617
  ds_name = ds["params"]["name"]
@@ -2641,9 +2643,9 @@ async def new_ds(
2641
2643
  except DoesNotExistException:
2642
2644
  datasource_exists = False
2643
2645
 
2644
- if not datasource_exists or fork_downstream:
2646
+ if not datasource_exists or fork_downstream or fork:
2645
2647
  params = ds["params"]
2646
- params["branch_mode"] = "fork" if fork_downstream else "None"
2648
+ params["branch_mode"] = "fork" if fork_downstream or fork else "None"
2647
2649
 
2648
2650
  try:
2649
2651
  if (
@@ -2719,7 +2721,7 @@ async def new_ds(
2719
2721
  ds["params"]["schema"].replace(" ", "") != existing_ds["schema"]["sql_schema"].replace(" ", "")
2720
2722
  ):
2721
2723
  new_schema = ds["params"]["schema"]
2722
- if new_description or new_schema or new_ttl and not fork_downstream:
2724
+ if new_description or new_schema or new_ttl and (not fork_downstream or not fork):
2723
2725
  alter_response = await client.alter_datasource(
2724
2726
  ds_name, new_schema=new_schema, description=new_description, ttl=new_ttl, dry_run=True
2725
2727
  )
@@ -2899,6 +2901,7 @@ async def exec_file(
2899
2901
  tests_check_requests_from_branch: bool = False,
2900
2902
  current_ws: Optional[Dict[str, Any]] = None,
2901
2903
  fork_downstream: Optional[bool] = False,
2904
+ fork: Optional[bool] = False,
2902
2905
  git_release: Optional[bool] = False,
2903
2906
  ):
2904
2907
  if debug:
@@ -2929,6 +2932,7 @@ async def exec_file(
2929
2932
  override_datasource=override_datasource,
2930
2933
  tests_check_requests_from_branch=tests_check_requests_from_branch,
2931
2934
  fork_downstream=fork_downstream,
2935
+ fork=fork,
2932
2936
  )
2933
2937
  elif r["resource"] == "datasources":
2934
2938
  await new_ds(
@@ -2939,6 +2943,7 @@ async def exec_file(
2939
2943
  skip_confirmation=skip_confirmation,
2940
2944
  current_ws=current_ws,
2941
2945
  fork_downstream=fork_downstream,
2946
+ fork=fork,
2942
2947
  git_release=git_release,
2943
2948
  )
2944
2949
  elif r["resource"] == "tokens":
@@ -3280,38 +3285,48 @@ async def build_graph(
3280
3285
 
3281
3286
  return downstream_dependency_graph
3282
3287
 
3283
- def update_dep_map_recursively(dep_map, downstream_dep_map, all_resources, to_run, key, external_visitor):
3288
+ def update_dep_map_recursively(
3289
+ dep_map, downstream_dep_map, all_resources, to_run, dep_map_keys, key=None, visited=None
3290
+ ):
3284
3291
  """
3285
3292
  Given a downstream_dep_map obtained from create_downstream_dependency_graph this function updates each node recursively to complete the downstream dependency graph for each node
3286
3293
  """
3294
+ if not visited:
3295
+ visited = list()
3296
+ if not key and len(dep_map_keys) == 0:
3297
+ return
3298
+ if not key:
3299
+ key = dep_map_keys.pop()
3287
3300
  if key not in dep_map:
3288
3301
  dep_map[key] = set()
3289
- elif key in external_visitor:
3290
- return
3291
3302
  else:
3292
- external_visitor.add(key)
3303
+ visited.append(key)
3304
+ return
3293
3305
 
3294
3306
  for dep in downstream_dep_map.get(key, {}):
3295
3307
  if dep not in downstream_dep_map:
3296
3308
  continue
3297
3309
  to_run[dep] = all_resources.get(dep)
3298
- update_dep_map_recursively(dep_map, downstream_dep_map, all_resources, to_run, dep, external_visitor)
3310
+ update_dep_map_recursively(
3311
+ dep_map, downstream_dep_map, all_resources, to_run, dep_map_keys, key=dep, visited=visited
3312
+ )
3299
3313
  dep_map[key].update(downstream_dep_map[dep])
3300
3314
  dep_map[key].update({dep})
3301
3315
  try:
3302
3316
  dep_map[key].remove(key)
3303
3317
  except KeyError:
3304
- return
3318
+ pass
3305
3319
 
3306
3320
  to_run[key] = all_resources.get(key)
3321
+ update_dep_map_recursively(
3322
+ dep_map, downstream_dep_map, all_resources, to_run, dep_map_keys, key=None, visited=visited
3323
+ )
3307
3324
 
3308
3325
  new_dep_map = dep_map
3309
3326
  if all_dep_map and fork_downstream:
3310
3327
  downstream_dep_map = create_downstream_dependency_graph(all_dep_map, all_resources)
3311
3328
  new_dep_map = {}
3312
- external_visitor: Set[str] = set()
3313
- for key in list(dep_map.keys()):
3314
- update_dep_map_recursively(new_dep_map, downstream_dep_map, all_resources, to_run, key, external_visitor)
3329
+ update_dep_map_recursively(new_dep_map, downstream_dep_map, all_resources, to_run, list(set(dep_map.keys())))
3315
3330
 
3316
3331
  return to_run, new_dep_map
3317
3332
 
@@ -3396,6 +3411,7 @@ async def folder_push(
3396
3411
  config: Optional[Dict[str, Any]] = None,
3397
3412
  user_token: Optional[str] = None,
3398
3413
  fork_downstream: Optional[bool] = False,
3414
+ fork: Optional[bool] = False,
3399
3415
  is_internal: Optional[bool] = False,
3400
3416
  ): # noqa: C901
3401
3417
  workspaces: List[Dict[str, Any]] = (await tb_client.user_workspaces_and_branches()).get("workspaces", [])
@@ -3535,6 +3551,7 @@ async def folder_push(
3535
3551
  latest_datasource_versions: Dict[str, Any],
3536
3552
  dry_run: bool,
3537
3553
  fork_downstream: Optional[bool] = False,
3554
+ fork: Optional[bool] = False,
3538
3555
  ):
3539
3556
  if name in to_run:
3540
3557
  if not dry_run:
@@ -3583,6 +3600,7 @@ async def folder_push(
3583
3600
  tests_check_requests_from_branch,
3584
3601
  current_ws,
3585
3602
  fork_downstream,
3603
+ fork,
3586
3604
  git_release,
3587
3605
  )
3588
3606
  if not run_tests:
@@ -3632,6 +3650,7 @@ async def folder_push(
3632
3650
  async def push_files(dry_run: bool = False):
3633
3651
  endpoints_dep_map = dict()
3634
3652
  pipes_dep_map = dict()
3653
+ mat_dep_map = dict()
3635
3654
  processed = set()
3636
3655
 
3637
3656
  groups = [group for group in toposort(dep_map)]
@@ -3643,24 +3662,44 @@ async def folder_push(
3643
3662
  continue
3644
3663
  # FIXME: name in dep_map?? that's wrong it should be there
3645
3664
  if fork_downstream and not is_datasource(to_run.get(name)) and name in dep_map:
3646
- pipes_dep_map[name] = dep_map[name]
3647
- continue
3648
- await push(name, to_run, resource_versions, latest_datasource_versions, dry_run, fork_downstream)
3665
+ if is_materialized(to_run.get(name)):
3666
+ mat_dep_map[name] = dep_map[name]
3667
+ continue
3668
+ else:
3669
+ pipes_dep_map[name] = dep_map[name]
3670
+ continue
3671
+ await push(name, to_run, resource_versions, latest_datasource_versions, dry_run, fork_downstream, fork)
3649
3672
  processed.add(name)
3650
3673
 
3674
+ # push endpoints with no dependencies or dependencies with other endpoints
3675
+ groups = [group for group in toposort(endpoints_dep_map)]
3676
+ for group in groups:
3677
+ for name in group:
3678
+ if name not in processed and not is_materialized(to_run[name]):
3679
+ await push(
3680
+ name, to_run, resource_versions, latest_datasource_versions, dry_run, fork_downstream, fork
3681
+ )
3682
+ processed.add(name)
3683
+
3684
+ # materialized pipes at the end
3651
3685
  if fork_downstream:
3652
- for group in toposort(pipes_dep_map):
3686
+ groups = [group for group in toposort(pipes_dep_map)]
3687
+ for group in groups:
3653
3688
  for name in group:
3654
- if name not in processed:
3689
+ if name not in processed and not is_materialized(to_run[name]):
3655
3690
  await push(
3656
3691
  name, to_run, resource_versions, latest_datasource_versions, dry_run, fork_downstream
3657
3692
  )
3693
+ processed.add(name)
3658
3694
 
3659
- # push endpoints with no dependencies or dependencies with other endpoints
3660
- for group in toposort(endpoints_dep_map):
3661
- for name in group:
3662
- if name not in processed:
3663
- await push(name, to_run, resource_versions, latest_datasource_versions, dry_run, fork_downstream)
3695
+ groups = [group for group in toposort(mat_dep_map)]
3696
+ for group in groups:
3697
+ for name in group:
3698
+ if name not in processed:
3699
+ await push(
3700
+ name, to_run, resource_versions, latest_datasource_versions, dry_run, fork_downstream
3701
+ )
3702
+ processed.add(name)
3664
3703
 
3665
3704
  if deployment.is_git_release:
3666
3705
  deployment.deploying_dry_run()
@@ -4365,6 +4404,15 @@ def is_endpoint(resource: Dict[str, Any]) -> bool:
4365
4404
  return False
4366
4405
 
4367
4406
 
4407
+ def is_materialized(resource: Dict[str, Any]) -> bool:
4408
+ if not resource:
4409
+ return False
4410
+ is_materialized = any(
4411
+ [node.get("params", {}).get("type", None) == "materialized" for node in resource.get("nodes", []) or []]
4412
+ )
4413
+ return is_materialized
4414
+
4415
+
4368
4416
  def is_endpoint_with_no_dependencies(resource: Dict[str, Any], dep_map: Dict[str, Any], to_run: Dict[str, Any]) -> bool:
4369
4417
  if not resource or resource.get("resource") == "datasources":
4370
4418
  return False
@@ -4388,7 +4436,7 @@ def is_endpoint_with_no_dependencies(resource: Dict[str, Any], dep_map: Dict[str
4388
4436
  deps = dep_map.get(resource["resource_name"], None)
4389
4437
  for dep in deps:
4390
4438
  r = to_run.get(dep, None)
4391
- if is_endpoint(r):
4439
+ if is_endpoint(r) or is_materialized(r):
4392
4440
  return False
4393
4441
 
4394
4442
  return True
@@ -423,7 +423,14 @@ def day_diff(d0, d1, default=None):
423
423
  )
424
424
 
425
425
 
426
- def date_diff_in_days(d0, d1, date_format: str = "%Y-%m-%d", default=None):
426
+ def date_diff_in_days(
427
+ d0: Union[Placeholder, str],
428
+ d1: Union[Placeholder, str],
429
+ date_format: str = "%Y-%m-%d",
430
+ default=None,
431
+ backup_date_format=None,
432
+ none_if_error=False,
433
+ ):
427
434
  """
428
435
  >>> date_diff_in_days('2019-01-01', '2019-01-01')
429
436
  0
@@ -441,13 +448,27 @@ def date_diff_in_days(d0, d1, date_format: str = "%Y-%m-%d", default=None):
441
448
  0
442
449
  >>> date_diff_in_days(Placeholder(), '')
443
450
  0
451
+ >>> date_diff_in_days('2019-01-01', '2019/01/01', backup_date_format='%Y/%m/%d')
452
+ 0
453
+ >>> date_diff_in_days('2019-01-01', '2019/01/04', backup_date_format='%Y/%m/%d')
454
+ 3
455
+ >>> date_diff_in_days('2019/01/04', '2019-01-01', backup_date_format='%Y/%m/%d')
456
+ 3
457
+ >>> date_diff_in_days('2019-02-01T20:00:00z', '2019-02-15', '%Y-%m-%dT%H:%M:%Sz', backup_date_format='%Y-%m-%d')
458
+ 13
459
+ >>> date_diff_in_days('2019-02-01 20:00:00', '2019-02-15', '%Y-%m-%dT%H:%M:%Sz', backup_date_format='%Y-%m-%d', none_if_error=True) is None
460
+ True
461
+ >>> date_diff_in_days('2019-01-01', '2019-00-02', none_if_error=True) is None
462
+ True
463
+ >>> date_diff_in_days('2019-01-01 00:00:00', '2019-01-02 00:00:00', none_if_error=True) is None
464
+ True
444
465
  """
445
466
  if isinstance(d0, Placeholder) or isinstance(d1, Placeholder):
446
467
  if default:
447
468
  return default
448
469
  return 0
449
470
  try:
450
- return __date_diff(d0, d1, date_format, "days")
471
+ return __date_diff(d0, d1, date_format, backup_date_format, "days", none_if_error)
451
472
  except Exception:
452
473
  raise SQLTemplateException(
453
474
  "invalid date format in function `date_diff_in_days`, it must be ISO format date YYYY-MM-DD, e.g. 2018-09-26",
@@ -456,7 +477,12 @@ def date_diff_in_days(d0, d1, date_format: str = "%Y-%m-%d", default=None):
456
477
 
457
478
 
458
479
  def date_diff_in_hours(
459
- d0: Union[Placeholder, str], d1: Union[Placeholder, str], date_format: str = "%Y-%m-%d %H:%M:%S", default=None
480
+ d0: Union[Placeholder, str],
481
+ d1: Union[Placeholder, str],
482
+ date_format: str = "%Y-%m-%d %H:%M:%S",
483
+ default=None,
484
+ backup_date_format=None,
485
+ none_if_error=False,
460
486
  ):
461
487
  """
462
488
  >>> date_diff_in_hours('2022-12-19T18:42:23.521Z', '2022-12-19T18:42:23.521Z', date_format='%Y-%m-%dT%H:%M:%S.%fz')
@@ -471,13 +497,33 @@ def date_diff_in_hours(
471
497
  0
472
498
  >>> date_diff_in_hours(Placeholder(), '')
473
499
  0
500
+ >>> date_diff_in_hours('2022-12-19T03:22:12.102Z', '2022-12-19', date_format='%Y-%m-%dT%H:%M:%S.%fz', backup_date_format='%Y-%m-%d')
501
+ 3
502
+ >>> date_diff_in_hours('2022-12-19', '2022-12-19', '%Y-%m-%dT%H:%M:%Sz', backup_date_format='%Y-%m-%d')
503
+ 0
504
+ >>> date_diff_in_hours('2022-12-19', '2022-12-18', '%Y-%m-%dT%H:%M:%Sz', backup_date_format='%Y-%m-%d')
505
+ 24
506
+ >>> date_diff_in_hours('2022-12-19', '2022-12-19 02:01:00', backup_date_format='%Y-%m-%d')
507
+ 2
508
+ >>> date_diff_in_hours('2022-25-19T00:00:03.521Z', '2022-12-19', date_format='%Y-%m-%dT%H:%M:%S.%fz', backup_date_format='%Y-%m-%d', none_if_error=True) is None
509
+ True
510
+ >>> date_diff_in_hours('2022-25-19 00:00:03', '2022-12-19', date_format='%Y-%m-%dT%H:%M:%S.%fz', backup_date_format='%Y-%m-%d', none_if_error=True) is None
511
+ True
512
+ >>> date_diff_in_hours('2022-12-19', '2022-25-19', '%Y-%m-%dT%H:%M:%Sz', backup_date_format='%Y-%m-%d', none_if_error=True) is None
513
+ True
514
+ >>> date_diff_in_hours('2022-12-19', '2022-25-19 00:01:00', backup_date_format='%Y-%m-%d', none_if_error=True) is None
515
+ True
516
+ >>> date_diff_in_hours('2022-12-32 18:42:22', '2022-12-19 18:42:22', none_if_error=True) is None
517
+ True
518
+ >>> date_diff_in_hours('2022-12-18T18:42:22Z', '2022-12-19T18:42:22Z', none_if_error=True) is None
519
+ True
474
520
  """
475
521
  if isinstance(d0, Placeholder) or isinstance(d1, Placeholder):
476
522
  if default:
477
523
  return default
478
524
  return 0
479
525
  try:
480
- return __date_diff(d0, d1, date_format, "hours")
526
+ return __date_diff(d0, d1, date_format, backup_date_format, "hours", none_if_error)
481
527
  except Exception:
482
528
  raise SQLTemplateException(
483
529
  "invalid date_format in function `date_diff_in_hours`, defaults to YYYY-MM-DD hh:mm:ss. Or %Y-%m-%d %H:%M:%S [.ssssss]Z, e.g. ms: 2022-12-19T18:42:22.591Z s:2022-12-19T18:42:22Z",
@@ -486,7 +532,12 @@ def date_diff_in_hours(
486
532
 
487
533
 
488
534
  def date_diff_in_minutes(
489
- d0: Union[Placeholder, str], d1: Union[Placeholder, str], date_format: str = "%Y-%m-%d %H:%M:%S", default=None
535
+ d0: Union[Placeholder, str],
536
+ d1: Union[Placeholder, str],
537
+ date_format: str = "%Y-%m-%d %H:%M:%S",
538
+ default=None,
539
+ backup_date_format=None,
540
+ none_if_error=False,
490
541
  ):
491
542
  """
492
543
  >>> date_diff_in_minutes('2022-12-19T18:42:23.521Z', '2022-12-19T18:42:23.521Z', date_format='%Y-%m-%dT%H:%M:%S.%fz')
@@ -501,13 +552,33 @@ def date_diff_in_minutes(
501
552
  0
502
553
  >>> date_diff_in_minutes(Placeholder(), '')
503
554
  0
555
+ >>> date_diff_in_minutes('2022-12-19T03:22:12.102Z', '2022-12-19', date_format='%Y-%m-%dT%H:%M:%S.%fz', backup_date_format='%Y-%m-%d')
556
+ 202
557
+ >>> date_diff_in_minutes('2022-12-19', '2022-12-19', '%Y-%m-%dT%H:%M:%Sz', backup_date_format='%Y-%m-%d')
558
+ 0
559
+ >>> date_diff_in_minutes('2022-12-19', '2022-12-18', '%Y-%m-%dT%H:%M:%Sz', backup_date_format='%Y-%m-%d')
560
+ 1440
561
+ >>> date_diff_in_minutes('2022-12-19', '2022-12-19 00:01:00', backup_date_format='%Y-%m-%d')
562
+ 1
563
+ >>> date_diff_in_minutes('2022-25-19T00:00:03.521Z', '2022-12-19', date_format='%Y-%m-%dT%H:%M:%S.%fz', backup_date_format='%Y-%m-%d', none_if_error=True) is None
564
+ True
565
+ >>> date_diff_in_minutes('2022-12-19', '2022-25-19', '%Y-%m-%dT%H:%M:%Sz', backup_date_format='%Y-%m-%d', none_if_error=True) is None
566
+ True
567
+ >>> date_diff_in_minutes('2022-12-19', '2022-25-19 00:01:00', backup_date_format='%Y-%m-%d', none_if_error=True) is None
568
+ True
569
+ >>> date_diff_in_minutes('2022-25-19T00:00:03.521Z', '2022-12-19 00:23:12', date_format='%Y-%m-%dT%H:%M:%S.%fz', backup_date_format='%Y-%m-%d', none_if_error=True) is None
570
+ True
571
+ >>> date_diff_in_minutes('2022-12-14 18:42:22', '2022/12/19 18:42:22', none_if_error=True) is None
572
+ True
573
+ >>> date_diff_in_minutes('2022-12-14 18:42:22', '2022/12/19 18:42:22', date_format='%Y/%m/%dT%H:%M:%S.%fz', none_if_error=True) is None
574
+ True
504
575
  """
505
576
  if isinstance(d0, Placeholder) or isinstance(d1, Placeholder):
506
577
  if default:
507
578
  return default
508
579
  return 0
509
580
  try:
510
- return __date_diff(d0, d1, date_format, "minutes")
581
+ return __date_diff(d0, d1, date_format, backup_date_format, "minutes", none_if_error)
511
582
  except Exception:
512
583
  raise SQLTemplateException(
513
584
  "invalid date_format in function `date_diff_in_seconds`, defaults to YYYY-MM-DD hh:mm:ss. Or %Y-%m-%d %H:%M:%S [.ssssss]Z, e.g. ms: 2022-12-19T18:42:22.591Z s:2022-12-19T18:42:22Z",
@@ -516,7 +587,12 @@ def date_diff_in_minutes(
516
587
 
517
588
 
518
589
  def date_diff_in_seconds(
519
- d0: Union[Placeholder, str], d1: Union[Placeholder, str], date_format: str = "%Y-%m-%d %H:%M:%S", default=None
590
+ d0: Union[Placeholder, str],
591
+ d1: Union[Placeholder, str],
592
+ date_format: str = "%Y-%m-%d %H:%M:%S",
593
+ default=None,
594
+ backup_date_format=None,
595
+ none_if_error=False,
520
596
  ):
521
597
  """
522
598
  >>> date_diff_in_seconds('2022-12-19T18:42:23.521Z', '2022-12-19T18:42:23.521Z', date_format='%Y-%m-%dT%H:%M:%S.%fz')
@@ -537,13 +613,33 @@ def date_diff_in_seconds(
537
613
  0
538
614
  >>> date_diff_in_seconds(Placeholder(), '')
539
615
  0
616
+ >>> date_diff_in_seconds('2022-12-19T00:00:03.521Z', '2022-12-19', date_format='%Y-%m-%dT%H:%M:%S.%fz', backup_date_format='%Y-%m-%d')
617
+ 3
618
+ >>> date_diff_in_seconds('2022-12-19', '2022-12-19', '%Y-%m-%dT%H:%M:%Sz', backup_date_format='%Y-%m-%d')
619
+ 0
620
+ >>> date_diff_in_seconds('2022-12-19', '2022-12-19 00:01:00', backup_date_format='%Y-%m-%d')
621
+ 60
622
+ >>> date_diff_in_seconds('2022-25-19T00:00:03.521Z', '2022-12-19', date_format='%Y-%m-%dT%H:%M:%S.%fz', backup_date_format='%Y-%m-%d', none_if_error=True) is None
623
+ True
624
+ >>> date_diff_in_seconds('2022-12-19', '2022-25-19', '%Y-%m-%dT%H:%M:%Sz', backup_date_format='%Y-%m-%d', none_if_error=True) is None
625
+ True
626
+ >>> date_diff_in_seconds('2022-12-19', '2022-25-19 00:01:00', backup_date_format='%Y-%m-%d', none_if_error=True) is None
627
+ True
628
+ >>> date_diff_in_seconds('2022-10-19T00:00:03.521Z', '2022/12/19', date_format='%Y-%m-%dT%H:%M:%S.%fz', backup_date_format='%Y-%m-%d', none_if_error=True) is None
629
+ True
630
+ >>> date_diff_in_seconds('2022-10-19 00:00:03', '2022-10-19 00:05:03', date_format='%Y-%m-%dT%H:%M:%S.%fz', none_if_error=True) is None
631
+ True
632
+ >>> date_diff_in_seconds('2022/12/19 00:00:03', '2022-10-19 00:05:03', none_if_error=True) is None
633
+ True
634
+ >>> date_diff_in_seconds('2022-25-19 00:00:03', '2022-10-19 00:05:03', none_if_error=True) is None
635
+ True
540
636
  """
541
637
  if isinstance(d0, Placeholder) or isinstance(d1, Placeholder):
542
638
  if default:
543
639
  return default
544
640
  return 0
545
641
  try:
546
- return __date_diff(d0, d1, date_format, "seconds")
642
+ return __date_diff(d0, d1, date_format, backup_date_format, "seconds", none_if_error)
547
643
  except Exception:
548
644
  raise SQLTemplateException(
549
645
  "invalid date_format in function `date_diff_in_seconds`, defaults to YYYY-MM-DD hh:mm:ss. Or %Y-%m-%d %H:%M:%S [.ssssss]Z, e.g. ms: 2022-12-19T18:42:22.591Z s:2022-12-19T18:42:22Z",
@@ -551,32 +647,52 @@ def date_diff_in_seconds(
551
647
  )
552
648
 
553
649
 
554
- def __date_diff(d0, d1, date_format: str = "%Y-%m-%d %H:%M:%S", unit: str = "seconds", default=None):
555
- if isinstance(d0, Placeholder) or isinstance(d1, Placeholder):
556
- if default:
557
- return default
558
- return 0
650
+ def __date_diff(
651
+ d0: Union[Placeholder, str],
652
+ d1: Union[Placeholder, str],
653
+ date_format: str = "%Y-%m-%d %H:%M:%S",
654
+ backup_date_format=None,
655
+ unit: str = "seconds",
656
+ none_if_error=False,
657
+ ):
559
658
  try:
659
+ formatted_d0 = _parse_datetime(d0, date_format, backup_date_format)
660
+ formatted_d1 = _parse_datetime(d1, date_format, backup_date_format)
661
+ diff = abs(formatted_d1 - formatted_d0).total_seconds()
662
+
560
663
  if unit == "days":
561
- return int(
562
- abs((datetime.strptime(d1, date_format) - datetime.strptime(d0, date_format)).total_seconds()) / 86400
563
- )
664
+ return int(diff / 86400)
564
665
  elif unit == "hours":
565
- return int(
566
- abs((datetime.strptime(d1, date_format) - datetime.strptime(d0, date_format)).total_seconds()) / 3600
567
- )
666
+ return int(diff / 3600)
568
667
  elif unit == "minutes":
569
- return int(
570
- abs((datetime.strptime(d1, date_format) - datetime.strptime(d0, date_format)).total_seconds()) / 60
571
- )
668
+ return int(diff / 60)
572
669
  else:
573
- return int(abs((datetime.strptime(d1, date_format) - datetime.strptime(d0, date_format)).total_seconds()))
670
+ return int(diff)
574
671
  except Exception:
672
+ if none_if_error:
673
+ return None
674
+
575
675
  raise SQLTemplateException(
576
676
  "invalid date_format in function date_diff_* function", documentation="/cli/advanced-templates.html"
577
677
  )
578
678
 
579
679
 
680
+ def _parse_datetime(date_string, date_format, backup_date_format=None):
681
+ formats = [date_format]
682
+ if backup_date_format:
683
+ formats.append(backup_date_format)
684
+
685
+ for fmt in formats:
686
+ try:
687
+ return datetime.strptime(date_string, fmt)
688
+ except ValueError:
689
+ continue
690
+
691
+ raise SQLTemplateException(
692
+ "invalid date_format in function date_diff_* function", documentation="/cli/advanced-templates.html"
693
+ )
694
+
695
+
580
696
  function_list = {
581
697
  "columns": columns,
582
698
  "table": table,
@@ -1542,6 +1542,13 @@ async def prompt(_ctx: Context) -> None:
1542
1542
  help="Creates new versions of the dependent downstream resources to the changed resources.",
1543
1543
  hidden=True,
1544
1544
  )
1545
+ @click.option(
1546
+ "--fork",
1547
+ is_flag=True,
1548
+ default=False,
1549
+ help="Creates new versions of the changed resources. Use --fork-downstream to fork also the downstream dependencies of the changed resources.",
1550
+ hidden=True,
1551
+ )
1545
1552
  @click.option(
1546
1553
  "--is-internal",
1547
1554
  is_flag=True,
@@ -1577,6 +1584,7 @@ async def deploy(
1577
1584
  folder: str,
1578
1585
  user_token: Optional[str],
1579
1586
  fork_downstream: bool,
1587
+ fork: bool,
1580
1588
  is_internal: bool,
1581
1589
  only_changes: bool,
1582
1590
  ) -> None:
@@ -1584,32 +1592,43 @@ async def deploy(
1584
1592
 
1585
1593
  ignore_sql_errors = FeatureFlags.ignore_sql_errors()
1586
1594
  context.disable_template_security_validation.set(True)
1587
- await folder_push(
1588
- tb_client=create_tb_client(ctx),
1589
- dry_run=dry_run,
1590
- check=False,
1591
- push_deps=True,
1592
- debug=debug,
1593
- force=True,
1594
- git_release=True,
1595
- only_changes=only_changes,
1596
- override_datasource=override_datasource,
1597
- populate=populate,
1598
- populate_subset=subset,
1599
- populate_condition=sql_condition,
1600
- unlink_on_populate_error=unlink_on_populate_error,
1601
- upload_fixtures=fixtures,
1602
- wait=wait,
1603
- ignore_sql_errors=ignore_sql_errors,
1604
- skip_confirmation=yes,
1605
- workspace_map=dict(workspace_map),
1606
- workspace_lib_paths=workspace,
1607
- timeout=timeout,
1608
- run_tests=False,
1609
- folder=folder,
1610
- config=ctx.ensure_object(dict)["config"],
1611
- user_token=user_token,
1612
- fork_downstream=fork_downstream,
1613
- is_internal=is_internal,
1614
- )
1615
- return
1595
+ try:
1596
+ await folder_push(
1597
+ tb_client=create_tb_client(ctx),
1598
+ dry_run=dry_run,
1599
+ check=False,
1600
+ push_deps=True,
1601
+ debug=debug,
1602
+ force=True,
1603
+ git_release=True,
1604
+ only_changes=only_changes,
1605
+ override_datasource=override_datasource,
1606
+ populate=populate,
1607
+ populate_subset=subset,
1608
+ populate_condition=sql_condition,
1609
+ unlink_on_populate_error=unlink_on_populate_error,
1610
+ upload_fixtures=fixtures,
1611
+ wait=wait,
1612
+ ignore_sql_errors=ignore_sql_errors,
1613
+ skip_confirmation=yes,
1614
+ workspace_map=dict(workspace_map),
1615
+ workspace_lib_paths=workspace,
1616
+ timeout=timeout,
1617
+ run_tests=False,
1618
+ folder=folder,
1619
+ config=ctx.ensure_object(dict)["config"],
1620
+ user_token=user_token,
1621
+ fork_downstream=fork_downstream,
1622
+ fork=fork,
1623
+ is_internal=is_internal,
1624
+ )
1625
+ except click.ClickException as e:
1626
+ # custom exceptions coming from the API for the git workflow
1627
+ # FIXME: change this in the API
1628
+ if "--override-datasource" in str(e):
1629
+ new_exception = type(e)(
1630
+ f"{str(e).replace('. If you want to try to force override the Materialized View, please use the `--override-datasource` flag', '')} If you are creating a new Release update the related .datasource files and use --fork-downstream"
1631
+ )
1632
+ raise new_exception
1633
+ else:
1634
+ raise e
@@ -41,6 +41,7 @@ async def test_run(ctx: click.Context, file: Tuple[str, ...], verbose: bool, onl
41
41
  raise e
42
42
 
43
43
  file_list: Iterable[str] = file if len(file) > 0 else glob.glob("./tests/**/*.y*ml", recursive=True)
44
+ file_list = [f for f in file_list if not f.endswith("regression.yaml")]
44
45
  for test_file in file_list:
45
46
  try:
46
47
  test_result = await run_test_file(tb_client, test_file)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tinybird-cli
3
- Version: 1.2.1.dev0
3
+ Version: 1.2.1.dev2
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://docs.tinybird.co/cli.html
6
6
  Author: Tinybird
@@ -19,6 +19,16 @@ Changelog
19
19
 
20
20
  ---------
21
21
 
22
+ 1.2.1.dev2
23
+ ************
24
+
25
+ - `Changed` Internal change
26
+
27
+ 1.2.1.dev1
28
+ ************
29
+
30
+ - `Fixed` Skip `regression.yaml` in `tb test run` since it's reserved for regression tests
31
+
22
32
  1.2.0
23
33
  ************
24
34