tinybird 0.0.1.dev307__py3-none-any.whl → 0.0.1.dev309__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.

Potentially problematic release.


This version of tinybird might be problematic. Click here for more details.

tinybird/tb/__cli__.py CHANGED
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
4
  __url__ = 'https://www.tinybird.co/docs/forward/commands'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '0.0.1.dev307'
8
- __revision__ = '060369b'
7
+ __version__ = '0.0.1.dev309'
8
+ __revision__ = '8448176'
@@ -35,9 +35,10 @@ def build(ctx: click.Context, watch: bool) -> None:
35
35
  project: Project = ctx.ensure_object(dict)["project"]
36
36
  tb_client: TinyB = ctx.ensure_object(dict)["client"]
37
37
  config: Dict[str, Any] = ctx.ensure_object(dict)["config"]
38
+ is_environment = bool(ctx.ensure_object(dict)["environment"])
38
39
 
39
40
  # TODO: Explain that you can use custom environments too once they are open for everyone
40
- if obj["env"] == "cloud" and not obj["environment"]:
41
+ if obj["env"] == "cloud" and not is_environment:
41
42
  raise click.ClickException(FeedbackManager.error_build_only_supported_in_local())
42
43
 
43
44
  if project.has_deeper_level():
@@ -50,13 +51,20 @@ def build(ctx: click.Context, watch: bool) -> None:
50
51
  )
51
52
 
52
53
  click.echo(FeedbackManager.highlight_building_project())
53
- process(project=project, tb_client=tb_client, watch=False, config=config)
54
+ process(project=project, tb_client=tb_client, watch=False, config=config, is_environment=is_environment)
54
55
  if watch:
55
56
  run_watch(
56
57
  project=project,
57
58
  tb_client=tb_client,
58
59
  config=config,
59
- process=partial(process, project=project, tb_client=tb_client, watch=True, config=config),
60
+ process=partial(
61
+ process,
62
+ project=project,
63
+ tb_client=tb_client,
64
+ watch=True,
65
+ config=config,
66
+ is_environment=is_environment,
67
+ ),
60
68
  )
61
69
 
62
70
 
@@ -66,9 +74,9 @@ def build(ctx: click.Context, watch: bool) -> None:
66
74
  @click.pass_context
67
75
  def dev(ctx: click.Context, data_origin: str, ui: bool) -> None:
68
76
  obj: Dict[str, Any] = ctx.ensure_object(dict)
69
-
77
+ is_environment = bool(ctx.ensure_object(dict)["environment"])
70
78
  # TODO: Explain that you can use custom environments too once they are open for everyone
71
- if obj["env"] == "cloud" and not obj["environment"]:
79
+ if obj["env"] == "cloud" and not is_environment:
72
80
  raise click.ClickException(FeedbackManager.error_build_only_supported_in_local())
73
81
 
74
82
  if data_origin == "cloud":
@@ -87,12 +95,26 @@ def dev(ctx: click.Context, data_origin: str, ui: bool) -> None:
87
95
  time.sleep(0.5)
88
96
 
89
97
  click.echo(FeedbackManager.highlight_building_project())
90
- process(project=project, tb_client=tb_client, watch=True, config=config, build_status=build_status)
98
+ process(
99
+ project=project,
100
+ tb_client=tb_client,
101
+ watch=True,
102
+ config=config,
103
+ build_status=build_status,
104
+ is_environment=is_environment,
105
+ )
91
106
  run_watch(
92
107
  project=project,
93
108
  tb_client=tb_client,
94
109
  config=config,
95
- process=partial(process, project=project, tb_client=tb_client, build_status=build_status, config=config),
110
+ process=partial(
111
+ process,
112
+ project=project,
113
+ tb_client=tb_client,
114
+ build_status=build_status,
115
+ config=config,
116
+ is_environment=is_environment,
117
+ ),
96
118
  )
97
119
 
98
120
 
@@ -33,15 +33,17 @@ def process(
33
33
  exit_on_error: bool = True,
34
34
  load_fixtures: bool = True,
35
35
  project_with_vendors: Optional[Project] = None,
36
+ is_environment: bool = False,
36
37
  ) -> Optional[str]:
37
38
  time_start = time.time()
38
39
 
39
40
  # Build vendored workspaces before build
40
- if not project_with_vendors:
41
+ if not project_with_vendors and not is_environment:
41
42
  build_vendored_workspaces(project=project, tb_client=tb_client, config=config)
42
43
 
43
44
  # Ensure SHARED_WITH workspaces exist before build
44
- build_shared_with_workspaces(project=project, tb_client=tb_client, config=config)
45
+ if not is_environment:
46
+ build_shared_with_workspaces(project=project, tb_client=tb_client, config=config)
45
47
 
46
48
  build_failed = False
47
49
  build_error: Optional[str] = None
@@ -669,363 +669,450 @@ def datasource_create(
669
669
  gcs: bool,
670
670
  kafka: bool,
671
671
  ):
672
- project: Project = ctx.ensure_object(dict)["project"]
673
- client: TinyB = ctx.ensure_object(dict)["client"]
674
- config = ctx.ensure_object(dict)["config"]
675
- env: str = ctx.ensure_object(dict)["env"]
672
+ wizard_data: dict[str, str | bool | float] = {
673
+ "wizard": "datasource_create",
674
+ "current_step": "start",
675
+ }
676
+ start_time = time.time()
676
677
 
677
- if env == "cloud":
678
- raise CLIDatasourceException(
679
- FeedbackManager.error(message="`tb datasource create` is not available against Tinybird Cloud.")
680
- )
678
+ if name:
679
+ wizard_data["datasource_name"] = name
681
680
 
682
- datasource_types = {
683
- "blank": ("Blank", "Create an empty one"),
684
- "local_file": ("Local file", "A local file"),
685
- "remote_url": ("Remote URL", "A remote file"),
686
- "s3": ("S3", "Files on S3"),
687
- "gcs": ("GCS", "Files on GCS"),
688
- "kafka": ("Kafka", "Connect a Kafka topic"),
689
- "prompt": ("Prompt", "Create a datasource from a prompt"),
690
- }
691
- datasource_type: Optional[str] = None
692
- connection_file: Optional[str] = None
693
- ds_content = """SCHEMA >
681
+ try:
682
+ project: Project = ctx.ensure_object(dict)["project"]
683
+ client: TinyB = ctx.ensure_object(dict)["client"]
684
+ config = ctx.ensure_object(dict)["config"]
685
+ env: str = ctx.ensure_object(dict)["env"]
686
+
687
+ if env == "cloud":
688
+ raise CLIDatasourceException(
689
+ FeedbackManager.error(message="`tb datasource create` is not available against Tinybird Cloud.")
690
+ )
691
+
692
+ datasource_types = {
693
+ "blank": ("Blank", "Create an empty one"),
694
+ "local_file": ("Local file", "A local file"),
695
+ "remote_url": ("Remote URL", "A remote file"),
696
+ "s3": ("S3", "Files on S3"),
697
+ "gcs": ("GCS", "Files on GCS"),
698
+ "kafka": ("Kafka", "Connect a Kafka topic"),
699
+ "prompt": ("Prompt", "Create a datasource from a prompt"),
700
+ }
701
+ datasource_type: Optional[str] = None
702
+ connection_file: Optional[str] = None
703
+ ds_content = """SCHEMA >
694
704
  `data` String `json:$`
695
705
  """
696
- wizard_mode = True
697
- if file:
698
- datasource_type = "local_file"
699
- wizard_mode = False
700
- elif url:
701
- datasource_type = "remote_url"
702
- wizard_mode = False
703
- elif blank:
704
- datasource_type = "blank"
705
- wizard_mode = False
706
- elif connection:
707
- connection_files = project.get_connection_files()
708
- connection_file = next((f for f in connection_files if f.endswith(f"{connection}.connection")), None)
709
- if connection_file:
710
- connection_content = Path(connection_file).read_text()
711
- if project.is_kafka_connection(connection_content):
712
- datasource_type = "kafka"
713
- elif project.is_s3_connection(connection_content):
714
- datasource_type = "s3"
715
- elif project.is_gcs_connection(connection_content):
716
- datasource_type = "gcs"
717
- elif s3:
718
- datasource_type = "s3"
719
- wizard_mode = False
720
- elif gcs:
721
- datasource_type = "gcs"
722
- wizard_mode = False
723
- elif kafka:
724
- datasource_type = "kafka"
725
- wizard_mode = False
726
- elif prompt:
727
- datasource_type = "prompt"
728
- wizard_mode = False
729
-
730
- if datasource_type is None:
731
- click.echo(
732
- FeedbackManager.highlight(
733
- message="? This command will create the schema (.datasource) for your data. Choose where from:"
706
+ wizard_mode = True
707
+ if file:
708
+ datasource_type = "local_file"
709
+ wizard_mode = False
710
+ elif url:
711
+ datasource_type = "remote_url"
712
+ wizard_mode = False
713
+ elif blank:
714
+ datasource_type = "blank"
715
+ wizard_mode = False
716
+ elif connection:
717
+ connection_files = project.get_connection_files()
718
+ connection_file = next((f for f in connection_files if f.endswith(f"{connection}.connection")), None)
719
+ if connection_file:
720
+ connection_content = Path(connection_file).read_text()
721
+ if project.is_kafka_connection(connection_content):
722
+ datasource_type = "kafka"
723
+ elif project.is_s3_connection(connection_content):
724
+ datasource_type = "s3"
725
+ elif project.is_gcs_connection(connection_content):
726
+ datasource_type = "gcs"
727
+ elif s3:
728
+ datasource_type = "s3"
729
+ wizard_mode = False
730
+ elif gcs:
731
+ datasource_type = "gcs"
732
+ wizard_mode = False
733
+ elif kafka:
734
+ datasource_type = "kafka"
735
+ wizard_mode = False
736
+ elif prompt:
737
+ datasource_type = "prompt"
738
+ wizard_mode = False
739
+
740
+ if datasource_type is None:
741
+ wizard_data["current_step"] = "select_datasource_origin"
742
+ click.echo(
743
+ FeedbackManager.highlight(
744
+ message="? This command will create the schema (.datasource) for your data. Choose where from:"
745
+ )
734
746
  )
735
- )
736
- datasource_type_index = -1
747
+ datasource_type_index = -1
737
748
 
738
- dt_keys = list(datasource_types.keys())
739
- while datasource_type_index == -1:
740
- for index, key in enumerate(dt_keys):
749
+ dt_keys = list(datasource_types.keys())
750
+ while datasource_type_index == -1:
751
+ for index, key in enumerate(dt_keys):
752
+ click.echo(
753
+ f" [{index + 1}] {FeedbackManager.bold(message=datasource_types[key][0])}: {datasource_types[key][1]}"
754
+ )
755
+ click.echo(FeedbackManager.gray(message="\nFiles can be either NDJSON, CSV or Parquet."))
741
756
  click.echo(
742
- f" [{index + 1}] {FeedbackManager.bold(message=datasource_types[key][0])}: {datasource_types[key][1]}"
743
- )
744
- click.echo(FeedbackManager.gray(message="\nFiles can be either NDJSON, CSV or Parquet."))
745
- click.echo(
746
- FeedbackManager.gray(
747
- message=("Tip: Run `tb datasource create --file | --url | --connection` to skip this step.")
757
+ FeedbackManager.gray(
758
+ message=("Tip: Run `tb datasource create --file | --url | --connection` to skip this step.")
759
+ )
748
760
  )
749
- )
750
- datasource_type_index = click.prompt("\nSelect option", default=1)
761
+ datasource_type_index = click.prompt("\nSelect option", default=1)
751
762
 
752
- if datasource_type_index == 0:
753
- click.echo(FeedbackManager.warning(message="Datasource type selection cancelled by user"))
754
- return None
763
+ if datasource_type_index == 0:
764
+ click.echo(FeedbackManager.warning(message="Datasource type selection cancelled by user"))
755
765
 
756
- try:
757
- datasource_type = dt_keys[int(datasource_type_index) - 1]
758
- except Exception:
759
- datasource_type_index = -1
766
+ wizard_data["exit_reason"] = "user_cancelled_type_selection"
767
+ wizard_data["duration_seconds"] = round(time.time() - start_time, 2)
768
+ add_telemetry_event("system_info", **wizard_data)
769
+ return None
760
770
 
761
- if not datasource_type:
762
- click.echo(
763
- FeedbackManager.error(
764
- message=f"Invalid option: {datasource_type_index}. Please select a valid option from the list above."
765
- )
766
- )
767
- return
771
+ try:
772
+ datasource_type = dt_keys[int(datasource_type_index) - 1]
773
+ except Exception:
774
+ datasource_type_index = -1
768
775
 
769
- if datasource_type == "prompt":
770
- click.echo(FeedbackManager.gray(message="\n» Creating .datasource file..."))
771
- if not config.get("user_token"):
772
- raise Exception("This action requires authentication. Run 'tb login' first.")
776
+ if datasource_type:
777
+ wizard_data["datasource_type"] = datasource_type
773
778
 
774
- instructions = (
775
- "Create or update a Tinybird datasource (.datasource file) for this project. "
776
- "Do not generate mock data or append data; those steps will run later programmatically."
777
- )
778
- if name:
779
- instructions += f" Name the datasource '{name}'."
780
-
781
- created_resources = create_resources_from_prompt(
782
- config,
783
- project,
784
- prompt,
785
- feature="tb_datasource_create",
786
- instructions=instructions,
787
- )
788
- if any(path.suffix == ".datasource" for path in created_resources):
789
- click.echo(FeedbackManager.success(message="✓ .datasource created!"))
790
- else:
779
+ if not datasource_type:
791
780
  click.echo(
792
- FeedbackManager.gray(
793
- message=" No new datasource file detected. Existing resources may have been updated instead."
781
+ FeedbackManager.error(
782
+ message=f"Invalid option: {datasource_type_index}. Please select a valid option from the list above."
794
783
  )
795
784
  )
796
- return
797
-
798
- connection_required = datasource_type in ("kafka", "s3", "gcs")
799
-
800
- if connection_required:
801
-
802
- def get_connection_files():
803
- connection_files = []
804
- if datasource_type == "kafka":
805
- connection_files = project.get_kafka_connection_files()
806
- elif datasource_type == "s3":
807
- connection_files = project.get_s3_connection_files()
808
- elif datasource_type == "gcs":
809
- connection_files = project.get_gcs_connection_files()
810
- return connection_files
811
-
812
- connection_files = get_connection_files()
813
-
814
- click.echo(FeedbackManager.gray(message="\n» Selecting connection..."))
815
- connection_name = ""
816
- topics: List[str] = []
817
- if len(connection_files) == 0:
818
- click.echo(FeedbackManager.error(message=f"✗ No {datasource_types[datasource_type][0]} connections found."))
819
- if click.confirm(
820
- FeedbackManager.highlight(
821
- message=f"\n? Do you want to create a {datasource_types[datasource_type][0]} connection? [Y/n]"
822
- ),
823
- show_default=False,
824
- default=True,
825
- ):
826
- if datasource_type != "kafka":
827
- click.echo(FeedbackManager.gray(message="\n» Creating .connection file..."))
828
- default_connection_name = f"{datasource_type}_{generate_short_id()}"
829
- connection_name = click.prompt(
830
- FeedbackManager.highlight(message=f"? Connection name [{default_connection_name}]"),
831
- show_default=False,
832
- default=default_connection_name,
785
+
786
+ wizard_data["exit_reason"] = "invalid_type_selection"
787
+ wizard_data["duration_seconds"] = round(time.time() - start_time, 2)
788
+ add_telemetry_event("system_info", **wizard_data)
789
+ return
790
+
791
+ if datasource_type == "prompt":
792
+ click.echo(FeedbackManager.gray(message="\n» Creating .datasource file..."))
793
+ if not config.get("user_token"):
794
+ raise Exception("This action requires authentication. Run 'tb login' first.")
795
+
796
+ instructions = (
797
+ "Create or update a Tinybird datasource (.datasource file) for this project. "
798
+ "Do not generate mock data or append data; those steps will run later programmatically."
799
+ )
800
+ if not prompt:
801
+ wizard_data["current_step"] = "enter_prompt"
802
+ prompt = click.prompt(FeedbackManager.highlight(message="? Enter your prompt"))
803
+ wizard_data["prompt"] = prompt
804
+
805
+ if name:
806
+ instructions += f" Name the datasource '{name}'."
807
+
808
+ created_resources = create_resources_from_prompt(
809
+ config,
810
+ project,
811
+ prompt,
812
+ feature="tb_datasource_create",
813
+ instructions=instructions,
814
+ )
815
+ if any(path.suffix == ".datasource" for path in created_resources):
816
+ click.echo(FeedbackManager.success(message=" .datasource created!"))
817
+ else:
818
+ click.echo(
819
+ FeedbackManager.gray(
820
+ message="△ No new datasource file detected. Existing resources may have been updated instead."
833
821
  )
822
+ )
823
+
824
+ wizard_data["current_step"] = "completed"
825
+ wizard_data["duration_seconds"] = round(time.time() - start_time, 2)
826
+ add_telemetry_event("system_info", **wizard_data)
827
+ return
828
+
829
+ connection_required = datasource_type in ("kafka", "s3", "gcs")
830
+
831
+ if connection_required:
832
+ wizard_data["current_step"] = "select_connection"
833
+
834
+ def get_connection_files():
835
+ connection_files = []
834
836
  if datasource_type == "kafka":
835
- (
836
- connection_name,
837
- bootstrap_servers,
838
- key,
839
- secret,
840
- schema_registry_url,
841
- auto_offset_reset,
842
- sasl_mechanism,
843
- security_protocol,
844
- topics,
845
- ) = connection_create_kafka(ctx)
837
+ connection_files = project.get_kafka_connection_files()
846
838
  elif datasource_type == "s3":
847
- generate_aws_iamrole_connection_file_with_secret(
848
- connection_name,
849
- service="s3",
850
- role_arn_secret_name="S3_ARN",
851
- region="eu-west-1",
852
- folder=project.folder,
853
- with_default_secret=True,
854
- )
839
+ connection_files = project.get_s3_connection_files()
855
840
  elif datasource_type == "gcs":
856
- generate_gcs_connection_file_with_secrets(
857
- connection_name,
858
- service="gcs",
859
- svc_account_creds="GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON",
860
- folder=project.folder,
861
- )
862
- if datasource_type != "kafka":
863
- click.echo(FeedbackManager.info_file_created(file=f"connections/{connection_name}.connection"))
864
- click.echo(FeedbackManager.success(message="✓ .connection created!"))
865
- connection_files = get_connection_files()
866
- else:
867
- click.echo(FeedbackManager.info(message=f"→ Run `tb connection create {datasource_type}` to add one."))
868
- return
841
+ connection_files = project.get_gcs_connection_files()
842
+ return connection_files
843
+
844
+ connection_files = get_connection_files()
869
845
 
870
- if not connection_file:
871
- if len(connection_files) > 1:
846
+ click.echo(FeedbackManager.gray(message="\n» Selecting connection..."))
847
+ connection_name = ""
848
+ topics: List[str] = []
849
+ if len(connection_files) == 0:
872
850
  click.echo(
851
+ FeedbackManager.error(message=f"✗ No {datasource_types[datasource_type][0]} connections found.")
852
+ )
853
+ if click.confirm(
873
854
  FeedbackManager.highlight(
874
- message=f"? Multiple {datasource_types[datasource_type][0]} connections found. Please select one:"
855
+ message=f"\n? Do you want to create a {datasource_types[datasource_type][0]} connection? [Y/n]"
856
+ ),
857
+ show_default=False,
858
+ default=True,
859
+ ):
860
+ wizard_data["created_new_connection"] = True
861
+ if datasource_type != "kafka":
862
+ click.echo(FeedbackManager.gray(message="\n» Creating .connection file..."))
863
+ default_connection_name = f"{datasource_type}_{generate_short_id()}"
864
+ connection_name = click.prompt(
865
+ FeedbackManager.highlight(message=f"? Connection name [{default_connection_name}]"),
866
+ show_default=False,
867
+ default=default_connection_name,
868
+ )
869
+ wizard_data["connection_name"] = connection_name
870
+ if datasource_type == "kafka":
871
+ (
872
+ connection_name,
873
+ bootstrap_servers,
874
+ key,
875
+ secret,
876
+ schema_registry_url,
877
+ auto_offset_reset,
878
+ sasl_mechanism,
879
+ security_protocol,
880
+ topics,
881
+ ) = connection_create_kafka(ctx)
882
+ elif datasource_type == "s3":
883
+ generate_aws_iamrole_connection_file_with_secret(
884
+ connection_name,
885
+ service="s3",
886
+ role_arn_secret_name="S3_ARN",
887
+ region="eu-west-1",
888
+ folder=project.folder,
889
+ with_default_secret=True,
890
+ )
891
+ elif datasource_type == "gcs":
892
+ generate_gcs_connection_file_with_secrets(
893
+ connection_name,
894
+ service="gcs",
895
+ svc_account_creds="GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON",
896
+ folder=project.folder,
897
+ )
898
+ if datasource_type != "kafka":
899
+ click.echo(FeedbackManager.info_file_created(file=f"connections/{connection_name}.connection"))
900
+ click.echo(FeedbackManager.success(message="✓ .connection created!"))
901
+ connection_files = get_connection_files()
902
+ else:
903
+ click.echo(
904
+ FeedbackManager.info(message=f"→ Run `tb connection create {datasource_type}` to add one.")
875
905
  )
876
- )
877
- connection_index = -1
878
- while connection_index == -1:
879
- for index, conn_file in enumerate(connection_files):
880
- conn_path = Path(conn_file)
881
- click.echo(f" [{index + 1}] {conn_path.stem}")
882
- connection_index = click.prompt("\nSelect option", default=1)
883
- try:
884
- connection_file = connection_files[int(connection_index) - 1]
885
- connection_path = Path(connection_file)
886
- connection = connection_path.stem
887
- except Exception:
888
- connection_index = -1
889
- else:
890
- connection_file = connection_files[0]
891
- connection_path = Path(connection_file)
892
- connection = connection_path.stem
893
- click.echo(FeedbackManager.info(message=f"Using connection: {connection}"))
894
-
895
- click.echo(FeedbackManager.gray(message="\n» Creating .datasource file..."))
896
-
897
- if datasource_type == "local_file":
898
- if not file:
899
- file = click.prompt(FeedbackManager.highlight(message="? Path"))
900
- if file.startswith("~"):
901
- file = os.path.expanduser(file)
902
-
903
- folder_path = project.path
904
- path = folder_path / file
905
- if not path.exists():
906
- path = Path(file)
907
-
908
- data_format = path.suffix.lstrip(".")
909
- ds_content = analyze_file(str(path), client, format=data_format)
910
- default_name = normalize_datasource_name(path.stem)
911
- name = name or click.prompt(
912
- FeedbackManager.highlight(message=f"? Data source name [{default_name}]"),
913
- default=default_name,
914
- show_default=False,
915
- )
906
+ wizard_data["exit_reason"] = "user_declined_connection_creation"
907
+ wizard_data["duration_seconds"] = round(time.time() - start_time, 2)
908
+ add_telemetry_event("system_info", **wizard_data)
909
+ return
910
+
911
+ if not connection_file:
912
+ if len(connection_files) > 1:
913
+ wizard_data["selected_connection_from_multiple"] = True
914
+ click.echo(
915
+ FeedbackManager.highlight(
916
+ message=f"? Multiple {datasource_types[datasource_type][0]} connections found. Please select one:"
917
+ )
918
+ )
919
+ connection_index = -1
920
+ while connection_index == -1:
921
+ for index, conn_file in enumerate(connection_files):
922
+ conn_path = Path(conn_file)
923
+ click.echo(f" [{index + 1}] {conn_path.stem}")
924
+ connection_index = click.prompt("\nSelect option", default=1)
925
+ try:
926
+ connection_file = connection_files[int(connection_index) - 1]
927
+ connection_path = Path(connection_file)
928
+ connection = connection_path.stem
929
+ except Exception:
930
+ connection_index = -1
931
+ else:
932
+ connection_file = connection_files[0]
933
+ connection_path = Path(connection_file)
934
+ connection = connection_path.stem
935
+ click.echo(FeedbackManager.info(message=f"Using connection: {connection}"))
936
+ wizard_data["connection_name"] = connection
916
937
 
917
- if datasource_type == "remote_url":
918
- if not url:
919
- url = click.prompt(FeedbackManager.highlight(message="? URL"))
920
- format = url.split(".")[-1]
921
- ds_content = analyze_file(url, client, format)
922
- default_name = normalize_datasource_name(Path(url).stem)
923
- name = name or click.prompt(
924
- FeedbackManager.highlight(message=f"? Data source name [{default_name}]"),
925
- default=default_name,
926
- show_default=False,
927
- )
938
+ click.echo(FeedbackManager.gray(message="\n» Creating .datasource file..."))
928
939
 
929
- if datasource_type not in ("remote_url", "local_file"):
930
- default_name = f"ds_{generate_short_id()}"
931
- name = name or click.prompt(
932
- FeedbackManager.highlight(message=f"? Data source name [{default_name}]"),
933
- default=default_name,
934
- show_default=False,
935
- )
940
+ if datasource_type == "local_file":
941
+ wizard_data["current_step"] = "file_input"
942
+ if not file:
943
+ file = click.prompt(FeedbackManager.highlight(message="? Path"))
944
+ if file.startswith("~"):
945
+ file = os.path.expanduser(file)
946
+
947
+ folder_path = project.path
948
+ path = folder_path / file
949
+ if not path.exists():
950
+ path = Path(file)
951
+
952
+ data_format = path.suffix.lstrip(".")
953
+ ds_content = analyze_file(str(path), client, format=data_format)
954
+ default_name = normalize_datasource_name(path.stem)
955
+ wizard_data["current_step"] = "enter_name"
956
+ name = name or click.prompt(
957
+ FeedbackManager.highlight(message=f"? Data source name [{default_name}]"),
958
+ default=default_name,
959
+ show_default=False,
960
+ )
961
+ wizard_data["datasource_name"] = name
936
962
 
937
- if datasource_type == "kafka":
938
- connections = client.connections("kafka")
939
- kafka_connection_files = project.get_kafka_connection_files()
963
+ if name == default_name:
964
+ wizard_data["used_default_name"] = True
940
965
 
941
- # if we have no topics from before and no connections, we need to build the project
942
- if len(topics) == 0 and len(kafka_connection_files) != len(connections):
943
- click.echo(
944
- FeedbackManager.error(message=f" Some {datasource_types[datasource_type][0]} connections are missing.")
966
+ if datasource_type == "remote_url":
967
+ wizard_data["current_step"] = "file_input"
968
+ if not url:
969
+ url = click.prompt(FeedbackManager.highlight(message="? URL"))
970
+ format = url.split(".")[-1]
971
+ ds_content = analyze_file(url, client, format)
972
+ default_name = normalize_datasource_name(Path(url).stem)
973
+ wizard_data["current_step"] = "enter_name"
974
+ name = name or click.prompt(
975
+ FeedbackManager.highlight(message=f"? Data source name [{default_name}]"),
976
+ default=default_name,
977
+ show_default=False,
945
978
  )
946
- if click.confirm(
947
- FeedbackManager.highlight(message="? Do you want to build your project before continue? [Y/n]"),
979
+ wizard_data["datasource_name"] = name
980
+
981
+ if name == default_name:
982
+ wizard_data["used_default_name"] = True
983
+
984
+ if datasource_type not in ("remote_url", "local_file"):
985
+ wizard_data["current_step"] = "enter_name"
986
+ default_name = f"ds_{generate_short_id()}"
987
+ name = name or click.prompt(
988
+ FeedbackManager.highlight(message=f"? Data source name [{default_name}]"),
989
+ default=default_name,
948
990
  show_default=False,
949
- default=True,
950
- ):
951
- click.echo(FeedbackManager.gray(message="» Building project..."))
952
- build_project(project=project, tb_client=client, watch=False, config=config, silent=True)
953
- click.echo(FeedbackManager.success(message="✓ Build completed!"))
954
- connections = client.connections("kafka")
991
+ )
992
+ wizard_data["datasource_name"] = name
955
993
 
956
- connection_id = next((c["id"] for c in connections if c["name"] == connection), connection)
994
+ if name == default_name:
995
+ wizard_data["used_default_name"] = True
957
996
 
958
- if not topics:
959
- try:
960
- topics = client.kafka_list_topics(connection_id) if connection_id else []
961
- except Exception:
962
- topics = []
963
-
964
- if len(topics) > 1:
965
- click.echo(FeedbackManager.highlight(message="? Multiple topics found. Please select one:"))
966
- topic_index = -1
967
- while topic_index == -1:
968
- for index, topic in enumerate(topics):
969
- click.echo(f" [{index + 1}] {topic}")
970
- topic_index = click.prompt("\nSelect option", default=1)
997
+ if datasource_type == "kafka":
998
+ wizard_data["current_step"] = "kafka_configuration"
999
+ connections = client.connections("kafka")
1000
+ kafka_connection_files = project.get_kafka_connection_files()
1001
+
1002
+ # if we have no topics from before and no connections, we need to build the project
1003
+ if len(topics) == 0 and len(kafka_connection_files) != len(connections):
1004
+ click.echo(
1005
+ FeedbackManager.error(
1006
+ message=f"✗ Some {datasource_types[datasource_type][0]} connections are missing."
1007
+ )
1008
+ )
1009
+ if click.confirm(
1010
+ FeedbackManager.highlight(message="? Do you want to build your project before continue? [Y/n]"),
1011
+ show_default=False,
1012
+ default=True,
1013
+ ):
1014
+ wizard_data["chose_to_build_project"] = True
1015
+ click.echo(FeedbackManager.gray(message="» Building project..."))
1016
+ build_project(project=project, tb_client=client, watch=False, config=config, silent=True)
1017
+ click.echo(FeedbackManager.success(message="✓ Build completed!"))
1018
+ connections = client.connections("kafka")
1019
+
1020
+ connection_id = next((c["id"] for c in connections if c["name"] == connection), connection)
1021
+
1022
+ if not topics:
971
1023
  try:
972
- topic = topics[int(topic_index) - 1]
1024
+ topics = client.kafka_list_topics(connection_id) if connection_id else []
973
1025
  except Exception:
974
- topic_index = -1
975
- else:
976
- topic = topics[0] if len(topics) > 0 else "topic_0"
1026
+ topics = []
1027
+
1028
+ if len(topics) > 1:
1029
+ wizard_data["selected_topic_from_multiple"] = True
1030
+ click.echo(FeedbackManager.highlight(message="? Multiple topics found. Please select one:"))
1031
+ topic_index = -1
1032
+ while topic_index == -1:
1033
+ for index, topic in enumerate(topics):
1034
+ click.echo(f" [{index + 1}] {topic}")
1035
+ topic_index = click.prompt("\nSelect option", default=1)
1036
+ try:
1037
+ topic = topics[int(topic_index) - 1]
1038
+ except Exception:
1039
+ topic_index = -1
1040
+ else:
1041
+ topic = topics[0] if len(topics) > 0 else "topic_0"
977
1042
 
978
- group_id = generate_kafka_group_id(topic)
1043
+ group_id = generate_kafka_group_id(topic)
979
1044
 
980
- ds_content += f"""
1045
+ ds_content += f"""
981
1046
  KAFKA_CONNECTION_NAME {connection}
982
1047
  KAFKA_TOPIC {topic}
983
1048
  KAFKA_GROUP_ID {group_id}
984
1049
  """
985
1050
 
986
- if datasource_type == "s3":
987
- if not connection:
988
- connections = client.connections("s3")
989
- connection = next((c["name"] for c in connections if c["name"] == connection), connection)
990
- ds_content += f"""
1051
+ if datasource_type == "s3":
1052
+ if not connection:
1053
+ connections = client.connections("s3")
1054
+ connection = next((c["name"] for c in connections if c["name"] == connection), connection)
1055
+ ds_content += f"""
991
1056
  IMPORT_CONNECTION_NAME "{connection}"
992
1057
  IMPORT_BUCKET_URI "s3://my-bucket/*.csv"
993
1058
  IMPORT_SCHEDULE "@auto"
994
1059
  """
995
1060
 
996
- if datasource_type == "gcs":
997
- if not connection:
998
- connections = client.connections("gcs")
999
- connection = next((c["name"] for c in connections if c["name"] == connection), connection)
1000
- ds_content += f"""
1061
+ if datasource_type == "gcs":
1062
+ if not connection:
1063
+ connections = client.connections("gcs")
1064
+ connection = next((c["name"] for c in connections if c["name"] == connection), connection)
1065
+ ds_content += f"""
1001
1066
  IMPORT_CONNECTION_NAME "{connection}"
1002
1067
  IMPORT_BUCKET_URI "gs://my-bucket/*.csv"
1003
1068
  IMPORT_SCHEDULE "@auto"
1004
1069
  """
1005
1070
 
1006
- click.echo(FeedbackManager.info(message=f"/datasources/{name}.datasource"))
1007
- datasources_path = project.path / "datasources"
1008
- if not datasources_path.exists():
1009
- datasources_path.mkdir()
1010
- ds_file = datasources_path / f"{name}.datasource"
1011
- if not ds_file.exists():
1012
- ds_file.touch()
1013
- ds_file.write_text(ds_content)
1014
- click.echo(FeedbackManager.success(message="✓ .datasource created!"))
1015
-
1016
- if wizard_mode:
1017
- last_tip_message = "\nTip: To skip the interactive prompts, pass flags to this command, e.g."
1018
- last_tip_command = ""
1019
- if datasource_type == "local_file":
1020
- last_tip_command = f"`tb datasource create --file {file} --name {name}`."
1021
- elif datasource_type == "remote_url":
1022
- last_tip_command = f"`tb datasource create --url {url} --name {name}`."
1023
- elif datasource_type == "blank":
1024
- last_tip_command = f"`tb datasource create --blank --name {name}`."
1025
- elif datasource_type in ("s3", "gcs", "kafka"):
1026
- last_tip_command = f"`tb datasource create --{datasource_type} --name {name} --connection {connection}`."
1027
-
1028
- click.echo(FeedbackManager.gray(message=(f"{last_tip_message} {last_tip_command}")))
1071
+ wizard_data["current_step"] = "create_datasource_file"
1072
+ click.echo(FeedbackManager.info(message=f"/datasources/{name}.datasource"))
1073
+ datasources_path = project.path / "datasources"
1074
+ if not datasources_path.exists():
1075
+ datasources_path.mkdir()
1076
+ ds_file = datasources_path / f"{name}.datasource"
1077
+ if not ds_file.exists():
1078
+ ds_file.touch()
1079
+ ds_file.write_text(ds_content)
1080
+ click.echo(FeedbackManager.success(message="✓ .datasource created!"))
1081
+
1082
+ if wizard_mode:
1083
+ last_tip_message = "\nTip: To skip the interactive prompts, pass flags to this command, e.g."
1084
+ last_tip_command = ""
1085
+ if datasource_type == "local_file":
1086
+ last_tip_command = f"`tb datasource create --file {file} --name {name}`."
1087
+ elif datasource_type == "remote_url":
1088
+ last_tip_command = f"`tb datasource create --url {url} --name {name}`."
1089
+ elif datasource_type == "blank":
1090
+ last_tip_command = f"`tb datasource create --blank --name {name}`."
1091
+ elif datasource_type in ("s3", "gcs", "kafka"):
1092
+ last_tip_command = (
1093
+ f"`tb datasource create --{datasource_type} --name {name} --connection {connection}`."
1094
+ )
1095
+
1096
+ click.echo(FeedbackManager.gray(message=(f"{last_tip_message} {last_tip_command}")))
1097
+
1098
+ wizard_data["current_step"] = "completed"
1099
+ wizard_data["duration_seconds"] = round(time.time() - start_time, 2)
1100
+ add_telemetry_event("system_info", **wizard_data)
1101
+
1102
+ except Exception as e:
1103
+ wizard_data["duration_seconds"] = round(time.time() - start_time, 2)
1104
+
1105
+ current_exception: Optional[BaseException] = e
1106
+ while current_exception:
1107
+ if isinstance(current_exception, KeyboardInterrupt):
1108
+ wizard_data["exit_reason"] = "user_interrupted"
1109
+ add_telemetry_event("system_info", **wizard_data)
1110
+ raise
1111
+ current_exception = current_exception.__cause__ or current_exception.__context__
1112
+
1113
+ wizard_data["error_message"] = str(e)
1114
+ add_telemetry_event("wizard_error", **wizard_data)
1115
+ raise
1029
1116
 
1030
1117
 
1031
1118
  def generate_short_id():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev307
3
+ Version: 0.0.1.dev309
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -18,13 +18,13 @@ tinybird/datafile/exceptions.py,sha256=8rw2umdZjtby85QbuRKFO5ETz_eRHwUY5l7eHsy1w
18
18
  tinybird/datafile/parse_connection.py,sha256=tRyn2Rpr1TeWet5BXmMoQgaotbGdYep1qiTak_OqC5E,1825
19
19
  tinybird/datafile/parse_datasource.py,sha256=ssW8QeFSgglVFi3sDZj_HgkJiTJ2069v2JgqnH3CkDE,1825
20
20
  tinybird/datafile/parse_pipe.py,sha256=8e9LMecSQVWHC4AXf8cdxoQ1nxUR4fTObYxTctO_EXQ,3816
21
- tinybird/tb/__cli__.py,sha256=sGbfRkjI4JkVGzo0JQPjoiJ4aJczl7ueJiW4NshluI4,247
21
+ tinybird/tb/__cli__.py,sha256=lIikCt72JkWrUE9mRsfWt_yj_YkKZFDenadAR1hcQ8o,247
22
22
  tinybird/tb/check_pypi.py,sha256=Gp0HkHHDFMSDL6nxKlOY51z7z1Uv-2LRexNTZSHHGmM,552
23
23
  tinybird/tb/cli.py,sha256=Os4uApzYYVPXcYFdaReQs9E12q9DOuYOUVFoOgt7i7U,1047
24
24
  tinybird/tb/client.py,sha256=VYbllzIsmBS4b56Ix-vsyLbhpbe7MVRxR1sIo6xU2qA,53544
25
25
  tinybird/tb/config.py,sha256=XIKju3xxpo40oQ48zDtSv0vgUrBZBHSmHJi4SHksjXE,5447
26
- tinybird/tb/modules/build.py,sha256=gnZoqxEzFDc8OKVzmKIaesy3tat0lbJ3E8JKmsKTutw,8321
27
- tinybird/tb/modules/build_common.py,sha256=zHqnzEdJvEIlzDGEL5HqcUXInuKLqYG6hPCdB6f6PMA,20064
26
+ tinybird/tb/modules/build.py,sha256=mVWpMPn198qxzVlkzWjTFaHKGBt_ZWuPZZz8YvO5wk4,8817
27
+ tinybird/tb/modules/build_common.py,sha256=jY0aomUjDXHx0zDmR26C4JpBLJWZ_m5MSt6edEGfgjE,20152
28
28
  tinybird/tb/modules/cicd.py,sha256=0KLKccha9IP749QvlXBmzdWv1On3mFwMY4DUcJlBxiE,7326
29
29
  tinybird/tb/modules/cli.py,sha256=YsnFZT3WbeFX-zhlB9t10czHKzRJBoutzLZZe1OqUCw,21240
30
30
  tinybird/tb/modules/common.py,sha256=vSfyJCdmoUvO1B_JmODqKeMYQy4VXOYrFhBue758VMw,88275
@@ -32,7 +32,7 @@ tinybird/tb/modules/config.py,sha256=gK7rgaWTDd4ZKCrNEg_Uemr26EQjqWt6TjyQKujxOws
32
32
  tinybird/tb/modules/connection.py,sha256=axp8Fny1_4PSLJGN4UF6WygyRbQtM3Lbt6thxHKTxzw,17790
33
33
  tinybird/tb/modules/copy.py,sha256=dPZkcIDvxjJrlQUIvToO0vsEEEs4EYumbNV77-BzNoU,4404
34
34
  tinybird/tb/modules/create.py,sha256=j0WvO6LxuGW1ZOYVuvR1FxFls2oMmKePYNTRJMSXfuU,20135
35
- tinybird/tb/modules/datasource.py,sha256=O-w4qwhAIitD_SLVdxkpknckliRp_xMswdB8n8Ohxo0,42335
35
+ tinybird/tb/modules/datasource.py,sha256=pExdYfLQ5e88RqgJ-dbd30k1TJ4LDb_POtSc7fnlJDA,47365
36
36
  tinybird/tb/modules/deployment.py,sha256=v0layOmG0IMnuXc3RT39mpGfa5M8yPlrL9F089fJFCo,15964
37
37
  tinybird/tb/modules/deployment_common.py,sha256=68qoCoOQFqRFyvxWwYmKiuVb37z7txBhEvzSOJGUHMc,20664
38
38
  tinybird/tb/modules/deprecations.py,sha256=rrszC1f_JJeJ8mUxGoCxckQTJFBCR8wREf4XXXN-PRc,4507
@@ -122,8 +122,8 @@ tinybird/tb_cli_modules/config.py,sha256=IsgdtFRnUrkY8-Zo32lmk6O7u3bHie1QCxLwgp4
122
122
  tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
123
123
  tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
124
124
  tinybird/tb_cli_modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09K1A,10449
125
- tinybird-0.0.1.dev307.dist-info/METADATA,sha256=F1RA-wrl0jqMAo128B0tMdVBrCTW5zEC1EulkwKSyHo,1881
126
- tinybird-0.0.1.dev307.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
127
- tinybird-0.0.1.dev307.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
128
- tinybird-0.0.1.dev307.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
129
- tinybird-0.0.1.dev307.dist-info/RECORD,,
125
+ tinybird-0.0.1.dev309.dist-info/METADATA,sha256=89ZaXaJwYZLry_lgBwPIqrvc3aTAEKn1HosDiz23EI0,1881
126
+ tinybird-0.0.1.dev309.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
127
+ tinybird-0.0.1.dev309.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
128
+ tinybird-0.0.1.dev309.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
129
+ tinybird-0.0.1.dev309.dist-info/RECORD,,