tinybird 0.0.1.dev308__py3-none-any.whl → 0.0.1.dev310__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 +2 -2
- tinybird/tb/modules/datasource.py +393 -306
- tinybird/tb/modules/dev_server.py +17 -3
- {tinybird-0.0.1.dev308.dist-info → tinybird-0.0.1.dev310.dist-info}/METADATA +1 -1
- {tinybird-0.0.1.dev308.dist-info → tinybird-0.0.1.dev310.dist-info}/RECORD +8 -8
- {tinybird-0.0.1.dev308.dist-info → tinybird-0.0.1.dev310.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev308.dist-info → tinybird-0.0.1.dev310.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev308.dist-info → tinybird-0.0.1.dev310.dist-info}/top_level.txt +0 -0
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.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '0.0.1.dev310'
|
|
8
|
+
__revision__ = '8861705'
|
|
@@ -669,363 +669,450 @@ def datasource_create(
|
|
|
669
669
|
gcs: bool,
|
|
670
670
|
kafka: bool,
|
|
671
671
|
):
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
|
678
|
-
|
|
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
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
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
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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
|
-
|
|
753
|
-
|
|
754
|
-
return None
|
|
763
|
+
if datasource_type_index == 0:
|
|
764
|
+
click.echo(FeedbackManager.warning(message="Datasource type selection cancelled by user"))
|
|
755
765
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
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
|
-
|
|
770
|
-
|
|
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
|
-
|
|
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.
|
|
793
|
-
message="
|
|
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
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
if
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
)
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
-
|
|
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
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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
|
-
|
|
871
|
-
|
|
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"?
|
|
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
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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
|
-
|
|
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
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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
|
-
|
|
938
|
-
|
|
939
|
-
kafka_connection_files = project.get_kafka_connection_files()
|
|
963
|
+
if name == default_name:
|
|
964
|
+
wizard_data["used_default_name"] = True
|
|
940
965
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
FeedbackManager.
|
|
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
|
-
|
|
947
|
-
|
|
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
|
-
|
|
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
|
-
|
|
994
|
+
if name == default_name:
|
|
995
|
+
wizard_data["used_default_name"] = True
|
|
957
996
|
|
|
958
|
-
if
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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
|
-
|
|
1024
|
+
topics = client.kafka_list_topics(connection_id) if connection_id else []
|
|
973
1025
|
except Exception:
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
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
|
-
|
|
1043
|
+
group_id = generate_kafka_group_id(topic)
|
|
979
1044
|
|
|
980
|
-
|
|
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
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
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
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
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
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
datasources_path.
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
ds_file.
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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():
|
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Callable, Optional
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
|
+
import requests
|
|
9
10
|
|
|
10
11
|
from tinybird.tb.client import TinyB
|
|
11
12
|
from tinybird.tb.modules.common import sys_exit
|
|
@@ -51,8 +52,21 @@ class DevServer(http.server.HTTPServer):
|
|
|
51
52
|
)
|
|
52
53
|
except OSError as e:
|
|
53
54
|
if e.errno == 48: # Address already in use
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
dev_server_already_running = False
|
|
56
|
+
try:
|
|
57
|
+
response = requests.get(f"http://localhost:{port}")
|
|
58
|
+
if response.status_code == 200 and "Tinybird Dev Server" in response.text:
|
|
59
|
+
dev_server_already_running = True
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
if dev_server_already_running:
|
|
63
|
+
message = f"Dev server is already running on http://localhost:{port}. Skipping..."
|
|
64
|
+
click.echo(FeedbackManager.warning(message=message))
|
|
65
|
+
sys_exit("dev_server_already_running", message)
|
|
66
|
+
else:
|
|
67
|
+
message = f"Port {port} is already in use. Check if another instance of the server is running or release the port."
|
|
68
|
+
click.echo(FeedbackManager.error(message=message))
|
|
69
|
+
sys_exit("port_in_use", message)
|
|
56
70
|
else:
|
|
57
71
|
click.echo(FeedbackManager.error_exception(error=e))
|
|
58
72
|
sys_exit("server_error", str(e))
|
|
@@ -240,7 +254,7 @@ def start_server(project: Project, tb_client: TinyB, process: Callable, build_st
|
|
|
240
254
|
"""
|
|
241
255
|
|
|
242
256
|
try:
|
|
243
|
-
click.echo(FeedbackManager.highlight(message="
|
|
257
|
+
click.echo(FeedbackManager.highlight(message="» Starting Tinybird dev server..."))
|
|
244
258
|
|
|
245
259
|
# Create and start the server
|
|
246
260
|
server = DevServer(process, project, build_status, tb_client)
|
|
@@ -18,7 +18,7 @@ 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=
|
|
21
|
+
tinybird/tb/__cli__.py,sha256=PsaXQzmkEgsfLDtULkAsUwi2_fl1jOdNztKb1bHvnR0,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
|
|
@@ -32,11 +32,11 @@ 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=
|
|
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
|
|
39
|
-
tinybird/tb/modules/dev_server.py,sha256=
|
|
39
|
+
tinybird/tb/modules/dev_server.py,sha256=4PzRoE47vrHJ2ccdA57fd03r4KDgCz4EcayW52gZlA0,10881
|
|
40
40
|
tinybird/tb/modules/endpoint.py,sha256=ksRj6mfDb9Xv63PhTkV_uKSosgysHElqagg3RTt21Do,11958
|
|
41
41
|
tinybird/tb/modules/environment.py,sha256=z99321d_imk3eLJx9JNZIWMVcaRPUNixUV8pom4OL5o,5724
|
|
42
42
|
tinybird/tb/modules/exceptions.py,sha256=_1BHy0OixEkeF0fOCu1_1Pj8pJS4BNfUyucqCViJGTw,5958
|
|
@@ -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.
|
|
126
|
-
tinybird-0.0.1.
|
|
127
|
-
tinybird-0.0.1.
|
|
128
|
-
tinybird-0.0.1.
|
|
129
|
-
tinybird-0.0.1.
|
|
125
|
+
tinybird-0.0.1.dev310.dist-info/METADATA,sha256=8rqBQagupBkyWHMUi5ITIFqonzEIFUXPRVTFKSpkRKM,1881
|
|
126
|
+
tinybird-0.0.1.dev310.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
127
|
+
tinybird-0.0.1.dev310.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
|
|
128
|
+
tinybird-0.0.1.dev310.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
|
|
129
|
+
tinybird-0.0.1.dev310.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|