tinybird 0.0.1.dev269__py3-none-any.whl → 0.0.1.dev271__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/agent/agent.py +19 -1
- tinybird/tb/modules/agent/prompts.py +104 -21
- tinybird/tb/modules/agent/tools/secret.py +113 -0
- tinybird/tb/modules/agent/utils.py +1 -0
- tinybird/tb/modules/deployment.py +82 -2
- tinybird/tb/modules/deployment_common.py +4 -3
- tinybird/tb/modules/project.py +21 -0
- {tinybird-0.0.1.dev269.dist-info → tinybird-0.0.1.dev271.dist-info}/METADATA +1 -1
- {tinybird-0.0.1.dev269.dist-info → tinybird-0.0.1.dev271.dist-info}/RECORD +13 -12
- {tinybird-0.0.1.dev269.dist-info → tinybird-0.0.1.dev271.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev269.dist-info → tinybird-0.0.1.dev271.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev269.dist-info → tinybird-0.0.1.dev271.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.dev271'
|
|
8
|
+
__revision__ = 'aeab6b9'
|
|
@@ -27,7 +27,12 @@ from tinybird.tb.modules.agent.memory import (
|
|
|
27
27
|
save_messages,
|
|
28
28
|
)
|
|
29
29
|
from tinybird.tb.modules.agent.models import create_model
|
|
30
|
-
from tinybird.tb.modules.agent.prompts import
|
|
30
|
+
from tinybird.tb.modules.agent.prompts import (
|
|
31
|
+
agent_system_prompt,
|
|
32
|
+
load_custom_project_rules,
|
|
33
|
+
resources_prompt,
|
|
34
|
+
secrets_prompt,
|
|
35
|
+
)
|
|
31
36
|
from tinybird.tb.modules.agent.testing_agent import TestingAgent
|
|
32
37
|
from tinybird.tb.modules.agent.tools.analyze import analyze_file, analyze_url
|
|
33
38
|
from tinybird.tb.modules.agent.tools.append import append_file, append_url
|
|
@@ -40,6 +45,7 @@ from tinybird.tb.modules.agent.tools.get_endpoint_stats import get_endpoint_stat
|
|
|
40
45
|
from tinybird.tb.modules.agent.tools.get_openapi_definition import get_openapi_definition
|
|
41
46
|
from tinybird.tb.modules.agent.tools.mock import mock
|
|
42
47
|
from tinybird.tb.modules.agent.tools.plan import plan
|
|
48
|
+
from tinybird.tb.modules.agent.tools.secret import create_or_update_secrets
|
|
43
49
|
from tinybird.tb.modules.agent.utils import AgentRunCancelled, TinybirdAgentContext, show_confirmation, show_input
|
|
44
50
|
from tinybird.tb.modules.build_common import process as build_process
|
|
45
51
|
from tinybird.tb.modules.common import _analyze, _get_tb_client, echo_safe_humanfriendly_tables_format_pretty_table
|
|
@@ -51,6 +57,7 @@ from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
|
51
57
|
from tinybird.tb.modules.login_common import login
|
|
52
58
|
from tinybird.tb.modules.mock_common import append_mock_data, create_mock_data
|
|
53
59
|
from tinybird.tb.modules.project import Project
|
|
60
|
+
from tinybird.tb.modules.secret_common import load_secrets
|
|
54
61
|
from tinybird.tb.modules.test_common import run_tests as run_tests_common
|
|
55
62
|
|
|
56
63
|
|
|
@@ -108,6 +115,12 @@ class TinybirdAgent:
|
|
|
108
115
|
takes_ctx=True,
|
|
109
116
|
),
|
|
110
117
|
Tool(diff_resource, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
|
|
118
|
+
Tool(
|
|
119
|
+
create_or_update_secrets,
|
|
120
|
+
docstring_format="google",
|
|
121
|
+
require_parameter_descriptions=True,
|
|
122
|
+
takes_ctx=True,
|
|
123
|
+
),
|
|
111
124
|
],
|
|
112
125
|
history_processors=[compact_messages],
|
|
113
126
|
)
|
|
@@ -207,6 +220,10 @@ class TinybirdAgent:
|
|
|
207
220
|
def get_project_files(ctx: RunContext[TinybirdAgentContext]) -> str:
|
|
208
221
|
return resources_prompt(self.project)
|
|
209
222
|
|
|
223
|
+
@self.agent.instructions
|
|
224
|
+
def get_secrets(ctx: RunContext[TinybirdAgentContext]) -> str:
|
|
225
|
+
return secrets_prompt(self.project)
|
|
226
|
+
|
|
210
227
|
def add_message(self, message: ModelMessage) -> None:
|
|
211
228
|
self.messages.append(message)
|
|
212
229
|
|
|
@@ -251,6 +268,7 @@ class TinybirdAgent:
|
|
|
251
268
|
local_host=local_client.host,
|
|
252
269
|
local_token=local_client.token,
|
|
253
270
|
run_id=run_id,
|
|
271
|
+
load_secrets_local=partial(load_secrets, project=project, client=local_client),
|
|
254
272
|
)
|
|
255
273
|
|
|
256
274
|
def run(self, user_prompt: str, config: dict[str, Any]) -> None:
|
|
@@ -5,16 +5,12 @@ from typing import Any
|
|
|
5
5
|
from pydantic_ai import format_as_xml
|
|
6
6
|
|
|
7
7
|
from tinybird.prompts import (
|
|
8
|
-
connection_instructions,
|
|
9
8
|
copy_pipe_instructions,
|
|
10
9
|
datasource_example,
|
|
11
10
|
datasource_instructions,
|
|
12
|
-
gcs_connection_example,
|
|
13
|
-
kafka_connection_example,
|
|
14
11
|
materialized_pipe_instructions,
|
|
15
12
|
pipe_example,
|
|
16
13
|
pipe_instructions,
|
|
17
|
-
s3_connection_example,
|
|
18
14
|
sink_pipe_instructions,
|
|
19
15
|
)
|
|
20
16
|
from tinybird.tb.modules.project import Project
|
|
@@ -25,18 +21,20 @@ When asked to create a plan, you MUST respond with this EXACT format and NOTHING
|
|
|
25
21
|
Plan description: [One sentence describing what will be built]
|
|
26
22
|
|
|
27
23
|
Steps:
|
|
28
|
-
1.
|
|
29
|
-
2.
|
|
30
|
-
3.
|
|
31
|
-
4.
|
|
32
|
-
5. Materialized
|
|
33
|
-
6.
|
|
34
|
-
7.
|
|
35
|
-
8.
|
|
36
|
-
9.
|
|
24
|
+
1. Create secrets: [key1, key2, ...] - Create all required secrets in .env.local in one step
|
|
25
|
+
2. Connection: [name] - [description]
|
|
26
|
+
3. Datasource: [name] - [description] - Depends on: [connection_name (optional)]
|
|
27
|
+
4. Endpoint: [name] - [description] - Depends on: [resources]
|
|
28
|
+
5. Materialized pipe: [name] - [description] - Depends on: [resources]
|
|
29
|
+
6. Materialized datasource: [name] - [description] - Depends on: [resources]
|
|
30
|
+
7. Sink: [name] - [description] - Depends on: [resources]
|
|
31
|
+
8. Copy: [name] - [description] - Depends on: [resources]
|
|
32
|
+
9. Generate mock data: [datasource_name]
|
|
33
|
+
10. Append existing fixture: [fixture_pathname] - Target: [datasource_name]
|
|
37
34
|
|
|
38
35
|
<dev_notes>
|
|
39
36
|
You can skip steps where resources will not be created or updated.
|
|
37
|
+
Always add 'Create secrets' as the FIRST step if any secrets/environment variables are required for the implementation. This step should include ALL required secrets at once.
|
|
40
38
|
Always add 'Generate mock data' step if a landing datasource was created without providing a fixture file.
|
|
41
39
|
Always add 'Append existing fixture' step if a landing datasource was created after providing a fixture file.
|
|
42
40
|
Solve the specific user request, do not add extra steps that are not related to the user request.
|
|
@@ -103,8 +101,6 @@ sql_instructions = """
|
|
|
103
101
|
- ONLY SELECT statements are allowed in any sql query.
|
|
104
102
|
- When using functions try always ClickHouse functions first, then SQL functions.
|
|
105
103
|
- Parameters are never quoted in any case.
|
|
106
|
-
- Use the following syntax in the SQL section for the iceberg table function: iceberg('s3://bucket/path/to/table', {{tb_secret('aws_access_key_id')}}, {{tb_secret('aws_secret_access_key')}})
|
|
107
|
-
- Use the following syntax in the SQL section for the postgres table function: postgresql('host:port', 'database', 'table', {{tb_secret('db_username')}}, {{tb_secret('db_password')}}), 'schema')
|
|
108
104
|
</sql_instructions>
|
|
109
105
|
"""
|
|
110
106
|
|
|
@@ -158,6 +154,29 @@ def resources_prompt(project: Project) -> str:
|
|
|
158
154
|
return resources_content + "\n" + fixture_content
|
|
159
155
|
|
|
160
156
|
|
|
157
|
+
def secrets_prompt(project: Project) -> str:
|
|
158
|
+
"""Generate a prompt showing available secrets from .env.local file."""
|
|
159
|
+
secrets = project.get_secrets()
|
|
160
|
+
|
|
161
|
+
if not secrets:
|
|
162
|
+
return "# Environment variables from .env.local:\nNo secrets found in .env.local file"
|
|
163
|
+
|
|
164
|
+
secrets_content = "# Environment variables from .env.local:\n"
|
|
165
|
+
secrets_list = []
|
|
166
|
+
|
|
167
|
+
for key, value in sorted(secrets.items()):
|
|
168
|
+
secret = {
|
|
169
|
+
"key": key,
|
|
170
|
+
"value": value,
|
|
171
|
+
}
|
|
172
|
+
secrets_list.append(secret)
|
|
173
|
+
|
|
174
|
+
if secrets_list:
|
|
175
|
+
secrets_content = format_as_xml(secrets_list, root_tag="secrets", item_tag="secret")
|
|
176
|
+
|
|
177
|
+
return secrets_content
|
|
178
|
+
|
|
179
|
+
|
|
161
180
|
def tests_files_prompt(project: Project) -> str:
|
|
162
181
|
files = project.get_project_files()
|
|
163
182
|
test_files = project.get_test_files()
|
|
@@ -640,6 +659,72 @@ Do not add additional code explanation summary unless requested by the user. Aft
|
|
|
640
659
|
IMPORTANT: DO NOT ADD ANY COMMENTS unless asked by the user.
|
|
641
660
|
"""
|
|
642
661
|
|
|
662
|
+
secrets_instructions = """
|
|
663
|
+
# Working with secrets:
|
|
664
|
+
- The syntax to use a secret is `{{ tb_secret("SECRET_NAME", "DEFAULT_VALUE_OPTIONAL") }}`.
|
|
665
|
+
- Secrets are used for sensitive credentials in the following cases:
|
|
666
|
+
- Connection files
|
|
667
|
+
- Pipe files in the SQL section (Remember to add `%` on top of the query to make it dynamic)
|
|
668
|
+
- All credentials needed to access Postgres and Iceberg external tables
|
|
669
|
+
- Do NOT use dynamic parameters instead of secrets, in the cases where secrets are needed.
|
|
670
|
+
- Secrets in pipe files do not allow default values.
|
|
671
|
+
- Secrets in connection files allow default values.
|
|
672
|
+
"""
|
|
673
|
+
|
|
674
|
+
external_tables_instructions = """
|
|
675
|
+
# Querying external tables:
|
|
676
|
+
When users ask to query a Postgres or Iceberg table, do not create connections, just use the following syntax in the SQL section:
|
|
677
|
+
## Iceberg table example:
|
|
678
|
+
```sql
|
|
679
|
+
FROM iceberg('s3://bucket/path/to/table', {{tb_secret('aws_access_key_id')}}, {{tb_secret('aws_secret_access_key')}})
|
|
680
|
+
```
|
|
681
|
+
## Postgres table example:
|
|
682
|
+
```sql
|
|
683
|
+
FROM postgresql({{ tb_secret("db_host_port") }}, 'database', 'table', {{tb_secret('db_username')}}, {{tb_secret('db_password')}}), 'schema_optional')
|
|
684
|
+
```
|
|
685
|
+
<dev_notes>
|
|
686
|
+
- Do not split the host and port in multiple secrets, use the secret as a whole.
|
|
687
|
+
</dev_notes>
|
|
688
|
+
"""
|
|
689
|
+
|
|
690
|
+
connection_instructions = """
|
|
691
|
+
# Working with connections files:
|
|
692
|
+
- Content cannot be empty.
|
|
693
|
+
- The connection names must be unique.
|
|
694
|
+
- No indentation is allowed for property names
|
|
695
|
+
- We support kafka, gcs and s3 connections for now
|
|
696
|
+
- If a user asks for a non supported connection type, just say that it is not supported and do not try to create it.
|
|
697
|
+
|
|
698
|
+
## Kafka connection example:
|
|
699
|
+
```
|
|
700
|
+
TYPE kafka
|
|
701
|
+
KAFKA_BOOTSTRAP_SERVERS {{ tb_secret("PRODUCTION_KAFKA_SERVERS", "localhost:9092") }}
|
|
702
|
+
KAFKA_SECURITY_PROTOCOL SASL_SSL
|
|
703
|
+
KAFKA_SASL_MECHANISM PLAIN
|
|
704
|
+
KAFKA_KEY {{ tb_secret("PRODUCTION_KAFKA_USERNAME", "") }}
|
|
705
|
+
KAFKA_SECRET {{ tb_secret("PRODUCTION_KAFKA_PASSWORD", "") }}
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
## S3 connection example:
|
|
709
|
+
```
|
|
710
|
+
TYPE s3
|
|
711
|
+
S3_REGION {{ tb_secret("PRODUCTION_S3_REGION", "") }}
|
|
712
|
+
S3_ARN {{ tb_secret("PRODUCTION_S3_ARN", "") }}
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
## GCS service account connection example:
|
|
716
|
+
```
|
|
717
|
+
TYPE gcs
|
|
718
|
+
GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON {{ tb_secret("PRODUCTION_GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON", "") }}
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
## GCS HMAC connection example:
|
|
722
|
+
```
|
|
723
|
+
TYPE gcs
|
|
724
|
+
GCS_HMAC_ACCESS_ID {{ tb_secret("gcs_hmac_access_id") }}
|
|
725
|
+
GCS_HMAC_SECRET {{ tb_secret("gcs_hmac_secret") }}
|
|
726
|
+
```
|
|
727
|
+
"""
|
|
643
728
|
|
|
644
729
|
agent_system_prompt = f"""
|
|
645
730
|
You are a Tinybird Code, an agentic CLI that can help users to work with Tinybird.
|
|
@@ -720,13 +805,11 @@ IMPORTANT: Every time you finish a plan and start a new resource creation or upd
|
|
|
720
805
|
{sql_agent_instructions}
|
|
721
806
|
{sql_instructions}
|
|
722
807
|
|
|
723
|
-
|
|
724
|
-
{connection_instructions}
|
|
808
|
+
{secrets_instructions}
|
|
725
809
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
GCS: {gcs_connection_example}
|
|
810
|
+
{external_tables_instructions}
|
|
811
|
+
|
|
812
|
+
{connection_instructions}
|
|
730
813
|
|
|
731
814
|
{explore_data_instructions}
|
|
732
815
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from pydantic_ai import RunContext
|
|
5
|
+
|
|
6
|
+
from tinybird.tb.modules.agent.utils import (
|
|
7
|
+
AgentRunCancelled,
|
|
8
|
+
TinybirdAgentContext,
|
|
9
|
+
create_terminal_box,
|
|
10
|
+
show_confirmation,
|
|
11
|
+
show_input,
|
|
12
|
+
)
|
|
13
|
+
from tinybird.tb.modules.exceptions import CLIBuildException
|
|
14
|
+
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_or_update_secrets(ctx: RunContext[TinybirdAgentContext], secrets: dict[str, str]) -> str:
|
|
18
|
+
"""Creates or updates multiple secrets in the .env.local file in the project folder.
|
|
19
|
+
|
|
20
|
+
This function will:
|
|
21
|
+
1. Check if .env.local exists in the project folder, create it if it doesn't
|
|
22
|
+
2. Create or update all specified secret keys with their given values
|
|
23
|
+
3. Handle existing keys by updating their values
|
|
24
|
+
4. Show a single confirmation for all secrets
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
secrets (dict[str, str]): Dictionary of environment variable keys and values. Required.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
str: Confirmation message about the secrets creation/update.
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
ctx.deps.thinking_animation.stop()
|
|
34
|
+
|
|
35
|
+
env_file_path = Path(ctx.deps.folder) / ".env.local"
|
|
36
|
+
|
|
37
|
+
# Read existing content if file exists
|
|
38
|
+
existing_content = ""
|
|
39
|
+
if env_file_path.exists():
|
|
40
|
+
existing_content = env_file_path.read_text()
|
|
41
|
+
|
|
42
|
+
# Parse existing environment variables
|
|
43
|
+
env_vars = {}
|
|
44
|
+
for line in existing_content.splitlines():
|
|
45
|
+
line = line.strip()
|
|
46
|
+
if line and not line.startswith("#") and "=" in line:
|
|
47
|
+
k, v = line.split("=", 1)
|
|
48
|
+
env_vars[k.strip()] = v.strip()
|
|
49
|
+
|
|
50
|
+
# Update or add the new secrets
|
|
51
|
+
new_keys = []
|
|
52
|
+
updated_keys = []
|
|
53
|
+
for key, value in secrets.items():
|
|
54
|
+
if key in env_vars:
|
|
55
|
+
updated_keys.append(key)
|
|
56
|
+
else:
|
|
57
|
+
new_keys.append(key)
|
|
58
|
+
env_vars[key] = value
|
|
59
|
+
|
|
60
|
+
# Generate new content
|
|
61
|
+
new_content = "\n".join([f"{k}={v}" for k, v in sorted(env_vars.items())])
|
|
62
|
+
if new_content:
|
|
63
|
+
new_content += "\n"
|
|
64
|
+
|
|
65
|
+
# Show preview
|
|
66
|
+
action = "Update" if env_file_path.exists() and updated_keys else "Create"
|
|
67
|
+
if new_keys and updated_keys:
|
|
68
|
+
action = "Create/Update"
|
|
69
|
+
|
|
70
|
+
preview_content = create_terminal_box(new_content, title=".env.local")
|
|
71
|
+
click.echo(preview_content)
|
|
72
|
+
|
|
73
|
+
confirmation = show_confirmation(
|
|
74
|
+
title=f"{action} {len(secrets)} secret(s) in .env.local?",
|
|
75
|
+
skip_confirmation=ctx.deps.dangerously_skip_permissions,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if confirmation == "review":
|
|
79
|
+
feedback = show_input(ctx.deps.workspace_name)
|
|
80
|
+
ctx.deps.thinking_animation.start()
|
|
81
|
+
return f"User did not confirm the secret changes and gave the following feedback: {feedback}"
|
|
82
|
+
|
|
83
|
+
# Write the file
|
|
84
|
+
action = "Updating" if env_file_path.exists() and updated_keys else "Creating"
|
|
85
|
+
click.echo(FeedbackManager.highlight(message=f"» {action} secrets in .env.local..."))
|
|
86
|
+
env_file_path.write_text(new_content)
|
|
87
|
+
ctx.deps.load_secrets_local()
|
|
88
|
+
ctx.deps.build_project(test=False, silent=True, load_fixtures=False)
|
|
89
|
+
|
|
90
|
+
# Generate success message
|
|
91
|
+
result_parts = []
|
|
92
|
+
if new_keys:
|
|
93
|
+
result_parts.append(f"created {len(new_keys)} new secret(s): {', '.join(new_keys)}")
|
|
94
|
+
if updated_keys:
|
|
95
|
+
result_parts.append(f"updated {len(updated_keys)} existing secret(s): {', '.join(updated_keys)}")
|
|
96
|
+
|
|
97
|
+
result_msg = " and ".join(result_parts)
|
|
98
|
+
ctx.deps.thinking_animation.start()
|
|
99
|
+
|
|
100
|
+
return f"Successfully {result_msg} in .env.local. Project built successfully."
|
|
101
|
+
|
|
102
|
+
except AgentRunCancelled as e:
|
|
103
|
+
raise e
|
|
104
|
+
except CLIBuildException as e:
|
|
105
|
+
ctx.deps.thinking_animation.stop()
|
|
106
|
+
click.echo(FeedbackManager.error(message=e))
|
|
107
|
+
ctx.deps.thinking_animation.start()
|
|
108
|
+
return f"Error building project: {e}. If the error is related to another resource, fix it and try again."
|
|
109
|
+
except Exception as e:
|
|
110
|
+
ctx.deps.thinking_animation.stop()
|
|
111
|
+
click.echo(FeedbackManager.error(message=f"Error managing secrets: {e}"))
|
|
112
|
+
ctx.deps.thinking_animation.start()
|
|
113
|
+
return f"Error creating/updating secrets: {e}"
|
|
@@ -61,6 +61,7 @@ class TinybirdAgentContext(BaseModel):
|
|
|
61
61
|
get_connection_datafile_cloud: Callable[..., str]
|
|
62
62
|
get_connection_datafile_local: Callable[..., str]
|
|
63
63
|
run_tests: Callable[..., Optional[str]]
|
|
64
|
+
load_secrets_local: Callable[..., None]
|
|
64
65
|
dangerously_skip_permissions: bool
|
|
65
66
|
token: str
|
|
66
67
|
user_token: str
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import datetime, timedelta, timezone
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Any, Dict, Optional
|
|
6
6
|
|
|
@@ -405,4 +405,84 @@ def create_deployment_cmd(
|
|
|
405
405
|
click.echo(FeedbackManager.success(message="Template downloaded successfully"))
|
|
406
406
|
client = ctx.ensure_object(dict)["client"]
|
|
407
407
|
config: Dict[str, Any] = ctx.ensure_object(dict)["config"]
|
|
408
|
-
|
|
408
|
+
is_web_analytics_starter_kit = bool(template and "web-analytics-starter-kit" in template)
|
|
409
|
+
create_deployment(
|
|
410
|
+
project,
|
|
411
|
+
client,
|
|
412
|
+
config,
|
|
413
|
+
wait,
|
|
414
|
+
auto,
|
|
415
|
+
verbose,
|
|
416
|
+
check,
|
|
417
|
+
allow_destructive_operations,
|
|
418
|
+
ingest_hint=not is_web_analytics_starter_kit,
|
|
419
|
+
)
|
|
420
|
+
show_web_analytics_starter_kit_hints(client, is_web_analytics_starter_kit)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def show_web_analytics_starter_kit_hints(client, is_web_analytics_starter_kit: bool) -> None:
|
|
424
|
+
try:
|
|
425
|
+
if not is_web_analytics_starter_kit:
|
|
426
|
+
return
|
|
427
|
+
|
|
428
|
+
from tinybird.tb.modules.cli import __unpatch_click_output
|
|
429
|
+
|
|
430
|
+
__unpatch_click_output()
|
|
431
|
+
tokens = client.tokens()
|
|
432
|
+
tracker_token = next((token for token in tokens if token["name"] == "tracker"), None)
|
|
433
|
+
if tracker_token:
|
|
434
|
+
click.echo(FeedbackManager.highlight(message="» Ingest data using the script below:"))
|
|
435
|
+
click.echo(
|
|
436
|
+
FeedbackManager.info(
|
|
437
|
+
message=f"""
|
|
438
|
+
<script
|
|
439
|
+
defer
|
|
440
|
+
src="https://unpkg.com/@tinybirdco/flock.js"
|
|
441
|
+
data-token="{tracker_token["token"]}"
|
|
442
|
+
data-host="{client.host}"
|
|
443
|
+
></script>
|
|
444
|
+
"""
|
|
445
|
+
)
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
try:
|
|
449
|
+
ttl = timedelta(days=365 * 10)
|
|
450
|
+
expiration_time = int((ttl + datetime.now(timezone.utc)).timestamp())
|
|
451
|
+
datasources = client.datasources()
|
|
452
|
+
pipes = client.pipes()
|
|
453
|
+
|
|
454
|
+
scopes = []
|
|
455
|
+
for res in pipes:
|
|
456
|
+
scope_data = {
|
|
457
|
+
"type": "PIPES:READ",
|
|
458
|
+
"resource": res["name"],
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
scopes.append(scope_data)
|
|
462
|
+
|
|
463
|
+
for res in datasources:
|
|
464
|
+
scope_data = {
|
|
465
|
+
"type": "DATASOURCES:READ",
|
|
466
|
+
"resource": res["name"],
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
scopes.append(scope_data)
|
|
470
|
+
|
|
471
|
+
response = client.create_jwt_token("web_analytics_starter_kit_jwt", expiration_time, scopes)
|
|
472
|
+
click.echo(FeedbackManager.highlight(message="» Open this URL in your browser to see the dashboard:\n"))
|
|
473
|
+
click.echo(
|
|
474
|
+
FeedbackManager.info(
|
|
475
|
+
message=f"https://analytics.tinybird.co?token={response['token']}&host={client.host}"
|
|
476
|
+
)
|
|
477
|
+
)
|
|
478
|
+
except Exception:
|
|
479
|
+
dashboard_token = next((token for token in tokens if token["name"] == "dashboard"), None)
|
|
480
|
+
if dashboard_token:
|
|
481
|
+
click.echo(FeedbackManager.highlight(message="» Open this URL in your browser to see the dashboard:\n"))
|
|
482
|
+
click.echo(
|
|
483
|
+
FeedbackManager.info(
|
|
484
|
+
message=f"https://analytics.tinybird.co?token={dashboard_token['token']}&host={client.host}"
|
|
485
|
+
)
|
|
486
|
+
)
|
|
487
|
+
except Exception:
|
|
488
|
+
pass
|
|
@@ -66,7 +66,7 @@ def api_post(
|
|
|
66
66
|
|
|
67
67
|
# TODO(eclbg): This logic should be in the server, and there should be a dedicated endpoint for promoting a deployment
|
|
68
68
|
# potato
|
|
69
|
-
def promote_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
|
|
69
|
+
def promote_deployment(host: Optional[str], headers: dict, wait: bool, ingest_hint: Optional[bool] = True) -> None:
|
|
70
70
|
TINYBIRD_API_URL = f"{host}/v1/deployments"
|
|
71
71
|
result = api_fetch(TINYBIRD_API_URL, headers)
|
|
72
72
|
|
|
@@ -123,7 +123,7 @@ def promote_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
|
|
|
123
123
|
break
|
|
124
124
|
|
|
125
125
|
time.sleep(5)
|
|
126
|
-
if last_deployment.get("id") == "0":
|
|
126
|
+
if last_deployment.get("id") == "0" and ingest_hint:
|
|
127
127
|
# This is the first deployment, so we prompt the user to ingest data
|
|
128
128
|
click.echo(
|
|
129
129
|
FeedbackManager.info(
|
|
@@ -197,6 +197,7 @@ def create_deployment(
|
|
|
197
197
|
verbose: bool = False,
|
|
198
198
|
check: Optional[bool] = None,
|
|
199
199
|
allow_destructive_operations: Optional[bool] = None,
|
|
200
|
+
ingest_hint: Optional[bool] = True,
|
|
200
201
|
) -> None:
|
|
201
202
|
# TODO: This code is duplicated in build_server.py
|
|
202
203
|
# Should be refactored to be shared
|
|
@@ -349,7 +350,7 @@ def create_deployment(
|
|
|
349
350
|
click.echo(FeedbackManager.info(message="✓ Deployment is ready"))
|
|
350
351
|
|
|
351
352
|
if auto:
|
|
352
|
-
promote_deployment(client.host, HEADERS, wait=wait)
|
|
353
|
+
promote_deployment(client.host, HEADERS, wait=wait, ingest_hint=ingest_hint)
|
|
353
354
|
|
|
354
355
|
|
|
355
356
|
def _build_data_movement_message(kind: str, source_mv_name: Optional[str]) -> str:
|
tinybird/tb/modules/project.py
CHANGED
|
@@ -82,6 +82,27 @@ class Project:
|
|
|
82
82
|
test_files.append(test_file)
|
|
83
83
|
return test_files
|
|
84
84
|
|
|
85
|
+
def get_secrets(self) -> dict[str, str]:
|
|
86
|
+
"""Load secrets from .env.local file in the project folder."""
|
|
87
|
+
secrets: dict[str, str] = {}
|
|
88
|
+
env_file_path = Path(self.folder) / ".env.local"
|
|
89
|
+
|
|
90
|
+
if not env_file_path.exists():
|
|
91
|
+
return secrets
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
content = env_file_path.read_text()
|
|
95
|
+
for line in content.splitlines():
|
|
96
|
+
line = line.strip()
|
|
97
|
+
if line and not line.startswith("#") and "=" in line:
|
|
98
|
+
key, value = line.split("=", 1)
|
|
99
|
+
secrets[key.strip()] = value.strip()
|
|
100
|
+
except Exception:
|
|
101
|
+
# If there's any error reading the file, return empty dict
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
return secrets
|
|
105
|
+
|
|
85
106
|
def get_resource_path(self, resource_name: str, resource_type: str) -> str:
|
|
86
107
|
full_path = next(
|
|
87
108
|
(p for p in self.get_project_files() if p.endswith("/" + resource_name + f".{resource_type}")), ""
|
|
@@ -17,7 +17,7 @@ tinybird/datafile/exceptions.py,sha256=8rw2umdZjtby85QbuRKFO5ETz_eRHwUY5l7eHsy1w
|
|
|
17
17
|
tinybird/datafile/parse_connection.py,sha256=tRyn2Rpr1TeWet5BXmMoQgaotbGdYep1qiTak_OqC5E,1825
|
|
18
18
|
tinybird/datafile/parse_datasource.py,sha256=ssW8QeFSgglVFi3sDZj_HgkJiTJ2069v2JgqnH3CkDE,1825
|
|
19
19
|
tinybird/datafile/parse_pipe.py,sha256=xf4m0Tw44QWJzHzAm7Z7FwUoUUtr7noMYjU1NiWnX0k,3880
|
|
20
|
-
tinybird/tb/__cli__.py,sha256=
|
|
20
|
+
tinybird/tb/__cli__.py,sha256=hE21EHSWlTInYkHEIdOVARPEKkTP_6FJafe-wwskyxc,247
|
|
21
21
|
tinybird/tb/check_pypi.py,sha256=Gp0HkHHDFMSDL6nxKlOY51z7z1Uv-2LRexNTZSHHGmM,552
|
|
22
22
|
tinybird/tb/cli.py,sha256=FdDFEIayjmsZEVsVSSvRiVYn_FHOVg_zWQzchnzfWho,1008
|
|
23
23
|
tinybird/tb/client.py,sha256=OVNlCXI16kpD-Ph8BDhkyKbR68TdZcR_j4ng91Iiybg,54706
|
|
@@ -32,8 +32,8 @@ tinybird/tb/modules/connection.py,sha256=-MY56NUAai6EMC4-wpi7bT0_nz_SA8QzTmHkV7H
|
|
|
32
32
|
tinybird/tb/modules/copy.py,sha256=dPZkcIDvxjJrlQUIvToO0vsEEEs4EYumbNV77-BzNoU,4404
|
|
33
33
|
tinybird/tb/modules/create.py,sha256=pJxHXG69c9Z_21s-7VuJ3RZOF_nJU51LEwiAkvI3dZY,23251
|
|
34
34
|
tinybird/tb/modules/datasource.py,sha256=pae-ENeHYIF1HHYRSOziFC-2FPLUFa0KS60YpdlKCS8,41725
|
|
35
|
-
tinybird/tb/modules/deployment.py,sha256=
|
|
36
|
-
tinybird/tb/modules/deployment_common.py,sha256=
|
|
35
|
+
tinybird/tb/modules/deployment.py,sha256=v0layOmG0IMnuXc3RT39mpGfa5M8yPlrL9F089fJFCo,15964
|
|
36
|
+
tinybird/tb/modules/deployment_common.py,sha256=gP2jYdRw8aehfKIHVQaS3DoS7sA43I-sQkLykcL83jE,19193
|
|
37
37
|
tinybird/tb/modules/deprecations.py,sha256=rrszC1f_JJeJ8mUxGoCxckQTJFBCR8wREf4XXXN-PRc,4507
|
|
38
38
|
tinybird/tb/modules/dev_server.py,sha256=57FCKuWpErwYUYgHspYDkLWEm9F4pbvVOtMrFXX1fVU,10129
|
|
39
39
|
tinybird/tb/modules/endpoint.py,sha256=ksRj6mfDb9Xv63PhTkV_uKSosgysHElqagg3RTt21Do,11958
|
|
@@ -54,7 +54,7 @@ tinybird/tb/modules/mock.py,sha256=ET8sRpmXnQsd2sSJXH_KCdREU1_XQgkORru6T357Akc,3
|
|
|
54
54
|
tinybird/tb/modules/mock_common.py,sha256=72yKp--Zo40hrycUtiajSRW2BojOsgOZFqUorQ_KQ3w,2279
|
|
55
55
|
tinybird/tb/modules/open.py,sha256=LYiuO8Z1I9O_v6pv58qpUCWFD6BT00BdeO21fRa4I4Y,1315
|
|
56
56
|
tinybird/tb/modules/pipe.py,sha256=xPKtezhnWZ6k_g82r4XpgKslofhuIxb_PvynH4gdUzI,2393
|
|
57
|
-
tinybird/tb/modules/project.py,sha256=
|
|
57
|
+
tinybird/tb/modules/project.py,sha256=_P6wUOFTY87ixE06SjfYIROMoVr6oipi6qHu-RAHLD8,7356
|
|
58
58
|
tinybird/tb/modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
59
59
|
tinybird/tb/modules/secret.py,sha256=9BIdh2PZDAbY2wRbf4ZDvkEltygztz1RMxgDmY1D0LI,3521
|
|
60
60
|
tinybird/tb/modules/secret_common.py,sha256=HyCLAI9WniDLwfK6SAb7ZUWorWjtf8j_GghlaTaos_I,1829
|
|
@@ -69,7 +69,7 @@ tinybird/tb/modules/watch.py,sha256=No0bK1M1_3CYuMaIgylxf7vYFJ72lTJe3brz6xQ-mJo,
|
|
|
69
69
|
tinybird/tb/modules/workspace.py,sha256=Q_8HcxMsNg8QG9aBlwcWS2umrDP5IkTIHqqz3sfmGuc,11341
|
|
70
70
|
tinybird/tb/modules/workspace_members.py,sha256=5JdkJgfuEwbq-t6vxkBhYwgsiTDxF790wsa6Xfif9nk,8608
|
|
71
71
|
tinybird/tb/modules/agent/__init__.py,sha256=i3oe3vDIWWPaicdCM0zs7D7BJ1W0k7th93ooskHAV00,54
|
|
72
|
-
tinybird/tb/modules/agent/agent.py,sha256=
|
|
72
|
+
tinybird/tb/modules/agent/agent.py,sha256=4nLBw3Z9uyl1SrziUN9i6IDlGIER2oU5tW_jLJd7MwY,29935
|
|
73
73
|
tinybird/tb/modules/agent/animations.py,sha256=4WOC5_2BracttmMCrV0H91tXfWcUzQHBUaIJc5FA7tE,3490
|
|
74
74
|
tinybird/tb/modules/agent/banner.py,sha256=l6cO5Fi7lbVKp-GsBP8jf3IkjOWxg2jpAt9NBCy0WR8,4085
|
|
75
75
|
tinybird/tb/modules/agent/command_agent.py,sha256=NTzgb9qnuG-gDpk87VijKs1UUMukJPaJI5UiZtRWUoQ,2864
|
|
@@ -77,9 +77,9 @@ tinybird/tb/modules/agent/compactor.py,sha256=BK5AxZFhrp3xWnsRnYaleiYoIWtVNc-_m6
|
|
|
77
77
|
tinybird/tb/modules/agent/explore_agent.py,sha256=HkzKmggfSMz7S3RSeKnZXufq-z_U0tTQJpF7JfNIaGQ,3504
|
|
78
78
|
tinybird/tb/modules/agent/memory.py,sha256=vBewB_64L_wHoT4tLT6UX2uxcHwSY880QZ26F9rPqXs,3793
|
|
79
79
|
tinybird/tb/modules/agent/models.py,sha256=IAxqlnHy8c2OeSnDrrSp2Mg38W9_r0GsDM87Wv-YNfM,925
|
|
80
|
-
tinybird/tb/modules/agent/prompts.py,sha256=
|
|
80
|
+
tinybird/tb/modules/agent/prompts.py,sha256=0ApEajQhiPUNmsW9XmaHaUTwUaj5IePryp5FmJ_GiMY,34561
|
|
81
81
|
tinybird/tb/modules/agent/testing_agent.py,sha256=mjR9OJ_KzGnjCnjRxp5-sf01LSDkO4-QqknTbkZ-R-Q,2752
|
|
82
|
-
tinybird/tb/modules/agent/utils.py,sha256=
|
|
82
|
+
tinybird/tb/modules/agent/utils.py,sha256=0hYhpJkmqmtcAuE4R96kzkhLN4LfvpJkxqaghLRclao,31045
|
|
83
83
|
tinybird/tb/modules/agent/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
84
84
|
tinybird/tb/modules/agent/tools/analyze.py,sha256=CR5LXg4fou-zYEksqnjpJ0icvxJVoKnTctoI1NRvqCM,3873
|
|
85
85
|
tinybird/tb/modules/agent/tools/append.py,sha256=xZVNkES0-qGjUrOfMLTn3kZ8JBm2OSMfLk_IZwUTRUo,6558
|
|
@@ -95,6 +95,7 @@ tinybird/tb/modules/agent/tools/mock.py,sha256=RvdsKIr0vKEs91GuK5vKg0fDj8SI-cdcX
|
|
|
95
95
|
tinybird/tb/modules/agent/tools/plan.py,sha256=2KHLNkr2f1RfkbAR4mCVsv94LGosXd8-ky7v6BB1OtQ,985
|
|
96
96
|
tinybird/tb/modules/agent/tools/request_endpoint.py,sha256=fK8qeCVWQvSbqVyuE71Yvm5-EFTvsDYADpKHmwblbUI,3356
|
|
97
97
|
tinybird/tb/modules/agent/tools/run_command.py,sha256=XjPDTTzkba9GOQBDiSTwddluyXkguVhxvXnRaC8m-Zc,1657
|
|
98
|
+
tinybird/tb/modules/agent/tools/secret.py,sha256=UbF9YIW4zh5qdF7qCeMhbhsDt_2qdjjntJE1e8HSUG0,4292
|
|
98
99
|
tinybird/tb/modules/agent/tools/test.py,sha256=CbGak_coopCTtqHoPWy-BwgLMIyEyeO34NTNkv18au4,6041
|
|
99
100
|
tinybird/tb/modules/datafile/build.py,sha256=NFKBrusFLU0WJNCXePAFWiEDuTaXpwc0lHlOQWEJ43s,51117
|
|
100
101
|
tinybird/tb/modules/datafile/build_common.py,sha256=2yNdxe49IMA9wNvl25NemY2Iaz8L66snjOdT64dm1is,4511
|
|
@@ -116,8 +117,8 @@ tinybird/tb_cli_modules/config.py,sha256=IsgdtFRnUrkY8-Zo32lmk6O7u3bHie1QCxLwgp4
|
|
|
116
117
|
tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
|
|
117
118
|
tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
118
119
|
tinybird/tb_cli_modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09K1A,10449
|
|
119
|
-
tinybird-0.0.1.
|
|
120
|
-
tinybird-0.0.1.
|
|
121
|
-
tinybird-0.0.1.
|
|
122
|
-
tinybird-0.0.1.
|
|
123
|
-
tinybird-0.0.1.
|
|
120
|
+
tinybird-0.0.1.dev271.dist-info/METADATA,sha256=CSddJZlg5oPjFuHA3lXFCB_D6GBoexmShKIi4qZY500,1763
|
|
121
|
+
tinybird-0.0.1.dev271.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
122
|
+
tinybird-0.0.1.dev271.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
|
|
123
|
+
tinybird-0.0.1.dev271.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
|
|
124
|
+
tinybird-0.0.1.dev271.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|