tinybird-cli 5.10.1.dev0__tar.gz → 5.10.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/PKG-INFO +17 -1
  2. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/__cli__.py +2 -2
  3. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/client.py +5 -3
  4. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/config.py +10 -8
  5. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/datafile.py +16 -9
  6. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/sql.py +4 -4
  7. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/sql_template.py +8 -0
  8. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/branch.py +12 -11
  9. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/cicd.py +1 -1
  10. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/common.py +32 -31
  11. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/connection.py +11 -10
  12. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/fmt.py +5 -4
  13. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +2 -0
  14. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +3 -0
  15. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tornado_template.py +3 -3
  16. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird_cli.egg-info/PKG-INFO +17 -1
  17. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird_cli.egg-info/requires.txt +1 -0
  18. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/setup.cfg +0 -0
  19. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/ch_utils/constants.py +0 -0
  20. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/ch_utils/engine.py +0 -0
  21. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/check_pypi.py +0 -0
  22. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/connectors.py +0 -0
  23. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/context.py +0 -0
  24. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/datatypes.py +0 -0
  25. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/feedback_manager.py +0 -0
  26. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/git_settings.py +0 -0
  27. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/sql_template_fmt.py +0 -0
  28. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/sql_toolset.py +0 -0
  29. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/syncasync.py +0 -0
  30. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli.py +0 -0
  31. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/auth.py +0 -0
  32. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/cli.py +0 -0
  33. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/config.py +0 -0
  34. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/datasource.py +0 -0
  35. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/exceptions.py +0 -0
  36. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/job.py +0 -0
  37. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/pipe.py +0 -0
  38. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/regions.py +0 -0
  39. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/tag.py +0 -0
  40. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/telemetry.py +0 -0
  41. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/test.py +0 -0
  42. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/token.py +0 -0
  43. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/workspace.py +0 -0
  44. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  45. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird_cli.egg-info/SOURCES.txt +0 -0
  46. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird_cli.egg-info/dependency_links.txt +0 -0
  47. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird_cli.egg-info/entry_points.txt +0 -0
  48. {tinybird-cli-5.10.1.dev0 → tinybird-cli-5.10.2}/tinybird_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tinybird-cli
3
- Version: 5.10.1.dev0
3
+ Version: 5.10.2
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -18,9 +18,25 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
18
18
  Changelog
19
19
  ----------
20
20
 
21
+ 5.10.2
22
+ ***********
23
+
24
+ - `Changed` Bump tinybird-cli version in requirements.txt when running `tb init --git`
25
+ - `Changed` Use `aiofiles` for async operations
26
+ - `Fixed` `TAGS` format was missing quotes in `tb pull``
27
+ - `Fixed` some internal typing issues from the CLI. Something internal that we don't include in the changelog of the CLI.
28
+
29
+ 5.10.1
30
+ ***********
31
+
32
+ - `Fixed` Internal Error while pushing S3 Data Source which provided the wrong cron expression to the backend.
33
+
21
34
  5.10.0
22
35
  ***********
23
36
 
37
+ - `Added` support for Kafka Data Sources with CA certificate.
38
+ - `Fixed` Correctly parse lambda expressions in indexes
39
+ - `Changed` Upgrade clickhouse-toolset to 0.32.dev0
24
40
  - `Added` new "File not found" error to `tb check` when including files from missing paths.
25
41
  - `Added` support for Kafka Data Sources with CA certificate.
26
42
  - `Changed` Upgrade clickhouse-toolset to 0.32.dev0
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
4
  __url__ = 'https://www.tinybird.co/docs/cli/introduction.html'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '5.10.1.dev0'
8
- __revision__ = 'f8aa90b'
7
+ __version__ = '5.10.2'
8
+ __revision__ = '5dafd46'
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
  from typing import Any, Callable, Dict, List, Mapping, Optional, Set, Union
7
7
  from urllib.parse import quote, urlencode
8
8
 
9
+ import aiofiles
9
10
  import requests
10
11
  import requests.adapters
11
12
  from requests import Response
@@ -421,11 +422,12 @@ class TinyB(object):
421
422
  for option in list(replace_options):
422
423
  params[option] = "true"
423
424
 
424
- with open(file, "rb") as content:
425
+ async with aiofiles.open(file, "rb") as content:
426
+ file_content = await content.read()
425
427
  if format == "csv":
426
- files = {"csv": ("csv", content)}
428
+ files = {"csv": ("csv", file_content)}
427
429
  else:
428
- files = {"ndjson": ("ndjson", content)}
430
+ files = {"ndjson": ("ndjson", file_content)}
429
431
 
430
432
  res = await self._req(
431
433
  f"v0/datasources?{urlencode(params, safe='')}",
@@ -3,6 +3,7 @@ from os import environ, getcwd
3
3
  from pathlib import Path
4
4
  from typing import Any, Dict, Optional
5
5
 
6
+ import aiofiles
6
7
  import click
7
8
 
8
9
  from tinybird import __cli__
@@ -39,6 +40,8 @@ LEGACY_HOSTS = {
39
40
  "https://api.wadus2.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus2",
40
41
  "https://api.wadus3.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus3",
41
42
  "https://api.wadus4.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus4",
43
+ "https://api.wadus5.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus5",
44
+ "https://api.wadus6.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus6",
42
45
  "https://ui.tinybird.co": "https://app.tinybird.co/gcp/europe-west3",
43
46
  "https://ui.us-east.tinybird.co": "https://app.tinybird.co/gcp/us-east4",
44
47
  "https://ui.us-east.aws.tinybird.co": "https://app.tinybird.co/aws/us-east-1",
@@ -53,12 +56,8 @@ LEGACY_HOSTS = {
53
56
  "https://inditex-pro.tinybird.co": "https://app.inditex.tinybird.co/gcp/inditex-pro",
54
57
  "https://ui.split.tinybird.co": "https://app.tinybird.co/aws/split-us-east",
55
58
  "https://ui.split.us-west-2.aws.tinybird.co": "https://app.tinybird.co/aws/split-us-west-2",
56
- "https://ui.split.azure.tinybird.co": "https://app.tinybird.co/azure/split-us-east",
57
- "https://ui.split-westeurope.azure.tinybird.co": "https://app.tinybird.co/azure/split-westeurope",
58
59
  "https://api.split.tinybird.co": "https://app.tinybird.co/aws/split-us-east",
59
60
  "https://api.split.us-west-2.aws.tinybird.co": "https://app.tinybird.co/aws/split-us-west-2",
60
- "https://api.split.azure.tinybird.co": "https://app.tinybird.co/azure/split-us-east",
61
- "https://api.split-westeurope.azure.tinybird.co": "https://app.tinybird.co/azure/split-westeurope",
62
61
  "https://ui.wadus1.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus1",
63
62
  "https://ui.wadus2.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus2",
64
63
  "https://ui.wadus3.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus3",
@@ -69,6 +68,8 @@ LEGACY_HOSTS = {
69
68
  "https://ui.wadus2.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus2",
70
69
  "https://ui.wadus3.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus3",
71
70
  "https://ui.wadus4.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus4",
71
+ "https://ui.wadus5.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus5",
72
+ "https://ui.wadus6.aws.tinybird.co": "https://app.wadus.tinybird.co/aws/wadus6",
72
73
  }
73
74
 
74
75
 
@@ -79,8 +80,9 @@ async def get_config(host: str, token: Optional[str], semver: Optional[str] = No
79
80
  config_file = Path(getcwd()) / ".tinyb"
80
81
  config = {}
81
82
  try:
82
- with open(config_file) as file:
83
- config = json.loads(file.read())
83
+ async with aiofiles.open(config_file) as file:
84
+ res = await file.read()
85
+ config = json.loads(res)
84
86
  except IOError:
85
87
  pass
86
88
  except json.decoder.JSONDecodeError:
@@ -97,8 +99,8 @@ async def get_config(host: str, token: Optional[str], semver: Optional[str] = No
97
99
 
98
100
  async def write_config(config: Dict[str, Any], dest_file: str = ".tinyb"):
99
101
  config_file = Path(getcwd()) / dest_file
100
- with open(config_file, "w") as file:
101
- file.write(json.dumps(config, indent=4, sort_keys=True))
102
+ async with aiofiles.open(config_file, "w") as file:
103
+ await file.write(json.dumps(config, indent=4, sort_keys=True))
102
104
 
103
105
 
104
106
  def get_display_host(ui_host: str):
@@ -5,6 +5,7 @@ Datafile is like a Dockerfile but to describe ETL processes
5
5
  from asyncio import Semaphore, gather
6
6
  from datetime import datetime
7
7
 
8
+ import aiofiles
8
9
  from requests import Response
9
10
 
10
11
  try:
@@ -143,6 +144,7 @@ class CopyModes:
143
144
  PREVIEW_CONNECTOR_SERVICES = ["s3", "s3_iamrole", "gcs"]
144
145
 
145
146
  ON_DEMAND = "@on-demand"
147
+ DEFAULT_CRON_PERIOD: int = 60
146
148
 
147
149
 
148
150
  class ImportReplacements:
@@ -1538,12 +1540,17 @@ async def process_file(
1538
1540
 
1539
1541
  if cron == ON_DEMAND_CRON:
1540
1542
  import_params["import_schedule"] = ON_DEMAND_CRON_EXPECTED_BY_THE_API
1541
- if cron == AUTO_CRON and current_ws:
1542
- workspaces = (await tb_client.user_workspaces()).get("workspaces", [])
1543
- rate_limits: Dict[str, Dict[str, int]] = next(
1544
- (w.get("rate_limits", {}) for w in workspaces if w["id"] == current_ws["id"]), {}
1545
- )
1546
- period: int = rate_limits.get("api_datasources_create_append_replace", {}).get("period", 60)
1543
+ if cron == AUTO_CRON:
1544
+ period: int = DEFAULT_CRON_PERIOD
1545
+
1546
+ if current_ws:
1547
+ workspaces = (await tb_client.user_workspaces()).get("workspaces", [])
1548
+ workspace_rate_limits: Dict[str, Dict[str, int]] = next(
1549
+ (w.get("rate_limits", {}) for w in workspaces if w["id"] == current_ws["id"]), {}
1550
+ )
1551
+ period = workspace_rate_limits.get("api_datasources_create_append_replace", {}).get(
1552
+ "period", DEFAULT_CRON_PERIOD
1553
+ )
1547
1554
 
1548
1555
  def seconds_to_cron_expression(seconds: int) -> str:
1549
1556
  minutes = seconds // 60
@@ -4945,7 +4952,7 @@ def format_shared_with(file_parts: List[str], doc: Datafile) -> List[str]:
4945
4952
 
4946
4953
  def format_tags(file_parts: List[str], doc: Datafile) -> List[str]:
4947
4954
  if doc.filtering_tags:
4948
- file_parts.append(f'TAGS {", ".join(doc.filtering_tags)}')
4955
+ file_parts.append(f'TAGS "{", ".join(doc.filtering_tags)}"')
4949
4956
  file_parts.append(DATAFILE_NEW_LINE)
4950
4957
  file_parts.append(DATAFILE_NEW_LINE)
4951
4958
  return file_parts
@@ -5233,7 +5240,7 @@ async def folder_pull(
5233
5240
  if verbose:
5234
5241
  click.echo(FeedbackManager.info_writing_resource(resource=f))
5235
5242
  if not f.exists() or force:
5236
- with open(f, "w") as fd:
5243
+ async with aiofiles.open(f, "w") as fd:
5237
5244
  # versions are a client only thing so
5238
5245
  # datafiles from the server do not contains information about versions
5239
5246
  if k["version"] >= 0:
@@ -5247,7 +5254,7 @@ async def folder_pull(
5247
5254
  m = match.split("__v")[0]
5248
5255
  if m in resources or m in resource_names:
5249
5256
  resource_to_write = resource_to_write.replace(match, m)
5250
- fd.write(resource_to_write)
5257
+ await fd.write(resource_to_write)
5251
5258
  else:
5252
5259
  if verbose:
5253
5260
  click.echo(FeedbackManager.info_skip_already_exists())
@@ -51,16 +51,16 @@ class TableProjection:
51
51
  def to_sql(self):
52
52
  return f"PROJECTION {self.to_datafile()}"
53
53
 
54
- def add_index_sql(self):
54
+ def add_projection_sql(self):
55
55
  return f"ADD {self.to_sql()}"
56
56
 
57
- def drop_index_sql(self):
57
+ def drop_projection_sql(self):
58
58
  return f"DROP PROJECTION IF EXISTS {self.name}"
59
59
 
60
- def materialize_index_sql(self):
60
+ def materialize_projection_sql(self):
61
61
  return f"MATERIALIZE PROJECTION IF EXISTS {self.name}"
62
62
 
63
- def clear_index_sql(self):
63
+ def clear_projection_sql(self):
64
64
  return f"CLEAR PROJECTION IF EXISTS {self.name}"
65
65
 
66
66
 
@@ -1629,6 +1629,8 @@ def get_var_data(content, node_id=None):
1629
1629
  # It will be overriden in later definitions or left as is otherwise.
1630
1630
  # args[0] check is used to avoid adding unnamed parameters found in
1631
1631
  # templates like: `split_to_array('')`
1632
+ if len(args) and isinstance(args[0], list):
1633
+ raise ValueError(f'"{args[0]}" can not be used as a variable name')
1632
1634
  if len(args) > 0 and args[0] not in vars and args[0]:
1633
1635
  vars[args[0]] = {
1634
1636
  "type": "String",
@@ -1640,6 +1642,8 @@ def get_var_data(content, node_id=None):
1640
1642
  default = kwargs.get("default", args[2] if len(args) > 2 and args[2] else None)
1641
1643
  kwargs["default"] = check_default_value(default)
1642
1644
  if len(args):
1645
+ if isinstance(args[0], list):
1646
+ raise ValueError(f'"{args[0]}" can not be used as a variable name')
1643
1647
  vars[args[0]] = {
1644
1648
  "type": f"Array({args[1]})" if len(args) > 1 else "Array(String)",
1645
1649
  **kwargs,
@@ -1652,11 +1656,15 @@ def get_var_data(content, node_id=None):
1652
1656
  default = kwargs.get("default", args[1] if len(args) > 1 else None)
1653
1657
  kwargs["default"] = check_default_value(default)
1654
1658
  try:
1659
+ if isinstance(args[0], list):
1660
+ raise ValueError(f'"{args[0]}" can not be used as a variable name')
1655
1661
  vars[args[0]] = {"type": func, **kwargs}
1656
1662
  if "default" in kwargs:
1657
1663
  kwargs["default"] = check_default_value(kwargs["default"])
1658
1664
  except TypeError as e:
1659
1665
  logging.exception(f"pipe parsing problem {content} (node '{node_id}'): {e}")
1666
+ except ValueError:
1667
+ raise
1660
1668
  except Exception as e:
1661
1669
  # if we find a problem parsing, let the parsing continue
1662
1670
  logging.exception(f"pipe parsing problem {content} (node: '{node_id}'): {e}")
@@ -7,6 +7,7 @@ import os
7
7
  from os import getcwd
8
8
  from typing import List, Optional, Tuple
9
9
 
10
+ import aiofiles
10
11
  import click
11
12
  import yaml
12
13
 
@@ -79,8 +80,8 @@ async def print_releases(config: CLIConfig):
79
80
  async def release_generate(semver: str) -> None:
80
81
  click.echo(FeedbackManager.warning_deprecated_releases())
81
82
  if os.path.exists(".tinyenv"):
82
- with open(".tinyenv", "r") as env_file:
83
- lines = env_file.readlines()
83
+ async with aiofiles.open(".tinyenv", "r") as env_file:
84
+ lines = await env_file.readlines()
84
85
 
85
86
  updated_lines = []
86
87
  for line in lines:
@@ -89,18 +90,18 @@ async def release_generate(semver: str) -> None:
89
90
  else:
90
91
  updated_lines.append(line)
91
92
 
92
- with open(".tinyenv", "w") as env_file:
93
- env_file.writelines(updated_lines)
93
+ async with aiofiles.open(".tinyenv", "w") as env_file:
94
+ await env_file.writelines(updated_lines)
94
95
  else:
95
- with open(".tinyenv", "w") as env_file:
96
- env_file.write(f"VERSION={semver}\n")
96
+ async with aiofiles.open(".tinyenv", "w") as env_file:
97
+ await env_file.write(f"VERSION={semver}\n")
97
98
 
98
99
  deploy_dir = os.path.join("deploy", semver)
99
100
  os.makedirs(deploy_dir, exist_ok=True)
100
101
 
101
102
  deploy_file = os.path.join(deploy_dir, "deploy.sh")
102
- with open(deploy_file, "w") as deploy:
103
- deploy.write(
103
+ async with aiofiles.open(deploy_file, "w") as deploy:
104
+ await deploy.write(
104
105
  """\
105
106
  #!/bin/bash
106
107
  set -euxo pipefail
@@ -112,8 +113,8 @@ set -euxo pipefail
112
113
  os.chmod(deploy_file, 0o755)
113
114
 
114
115
  post_deploy_file = os.path.join(deploy_dir, "postdeploy.sh")
115
- with open(post_deploy_file, "w") as post_deploy:
116
- post_deploy.write(
116
+ async with aiofiles.open(post_deploy_file, "w") as post_deploy:
117
+ await post_deploy.write(
117
118
  """\
118
119
  #!/bin/bash
119
120
  set -euxo pipefail
@@ -550,7 +551,7 @@ async def regression_tests(
550
551
 
551
552
  if filename:
552
553
  try:
553
- with open(filename, "r") as file:
554
+ with open(filename, "r") as file: # noqa: ASYNC230
554
555
  regression_tests_commands = yaml.safe_load(file)
555
556
  except Exception as exc:
556
557
  raise CLIBranchException(FeedbackManager.error_regression_yaml_not_valid(filename=filename, error=exc))
@@ -17,7 +17,7 @@ class Provider(Enum):
17
17
 
18
18
  WORKFLOW_VERSION = "v3.1.0"
19
19
 
20
- DEFAULT_REQUIREMENTS_FILE = "tinybird-cli>=4,<5"
20
+ DEFAULT_REQUIREMENTS_FILE = "tinybird-cli>=5,<6"
21
21
 
22
22
  GITHUB_CI_YML = """
23
23
  ##################################################
@@ -21,6 +21,7 @@ from pathlib import Path
21
21
  from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Union
22
22
  from urllib.parse import urlparse
23
23
 
24
+ import aiofiles
24
25
  import click
25
26
  import click.formatting
26
27
  import humanfriendly
@@ -212,7 +213,7 @@ class AliasedGroup(click.Group):
212
213
  def resolve_command(self, ctx, args):
213
214
  # always return the command's name, not the alias
214
215
  _, cmd, args = super().resolve_command(ctx, args)
215
- return cmd.name, cmd, args
216
+ return cmd.name, cmd, args # type: ignore[union-attr]
216
217
 
217
218
 
218
219
  class CatchAuthExceptions(AliasedGroup):
@@ -310,13 +311,13 @@ async def _analyze(filename: str, client: TinyB, format: str, connector: Optiona
310
311
  if parsed.scheme in ("http", "https"):
311
312
  meta = await client.datasource_analyze(filename)
312
313
  else:
313
- with open(filename, "rb") as file:
314
+ async with aiofiles.open(filename, "rb") as file:
314
315
  # We need to read the whole file in binary for Parquet, while for the
315
316
  # others we just read 1KiB
316
317
  if format == "parquet":
317
- data = file.read()
318
+ data = await file.read()
318
319
  else:
319
- data = file.read(1024 * 1024)
320
+ data = await file.read(1024 * 1024)
320
321
 
321
322
  meta = await client.datasource_analyze_file(data)
322
323
  else:
@@ -360,8 +361,8 @@ async def folder_init(
360
361
  base = Path(".")
361
362
  f = base / (".tinyenv")
362
363
  if not f.exists() or force:
363
- with open(".tinyenv", "w") as file:
364
- file.write(DEFAULT_TINYENV_FILE)
364
+ async with aiofiles.open(".tinyenv", "w") as file:
365
+ await file.write(DEFAULT_TINYENV_FILE)
365
366
  click.echo(FeedbackManager.info_file_created(file=".tinyenv"))
366
367
  else:
367
368
  click.echo(FeedbackManager.info_dottinyenv_already_exists())
@@ -369,8 +370,8 @@ async def folder_init(
369
370
  base = Path(".")
370
371
  f = base / ("requirements.txt")
371
372
  if not f.exists() or force:
372
- with open("requirements.txt", "w") as file:
373
- file.write(DEFAULT_REQUIREMENTS_FILE)
373
+ async with aiofiles.open("requirements.txt", "w") as file:
374
+ await file.write(DEFAULT_REQUIREMENTS_FILE)
374
375
  click.echo(FeedbackManager.info_file_created(file="requirements.txt"))
375
376
 
376
377
  base = Path("scripts")
@@ -378,15 +379,15 @@ async def folder_init(
378
379
  base = Path()
379
380
  f = base / ("exec_test.sh")
380
381
  if not f.exists() or force:
381
- with open(f"{f}", "w") as t_file:
382
- t_file.write(EXEC_TEST_SH)
382
+ async with aiofiles.open(f"{f}", "w") as t_file:
383
+ await t_file.write(EXEC_TEST_SH)
383
384
  click.echo(FeedbackManager.info_file_created(file="scripts/exec_test.sh"))
384
385
  chmod(f, 0o755)
385
386
 
386
387
  f = base / ("append_fixtures.sh")
387
388
  if not f.exists() or force:
388
- with open(f"{f}", "w") as t_file:
389
- t_file.write(APPEND_FIXTURES_SH)
389
+ async with aiofiles.open(f"{f}", "w") as t_file:
390
+ await t_file.write(APPEND_FIXTURES_SH)
390
391
  click.echo(FeedbackManager.info_file_created(file="scripts/append_fixtures.sh"))
391
392
  chmod(f, 0o755)
392
393
 
@@ -395,8 +396,8 @@ async def folder_init(
395
396
  base = Path()
396
397
  f = base / ("example.yml")
397
398
  if not base.exists() or force:
398
- with open(f"{f}", "w") as t_file:
399
- t_file.write(
399
+ async with aiofiles.open(f"{f}", "w") as t_file:
400
+ await t_file.write(
400
401
  """
401
402
  ##############################################################################################################################
402
403
  ### Visit https://www.tinybird.co/docs/production/implementing-test-strategies.html#data-quality-tests ###
@@ -431,8 +432,8 @@ async def folder_init(
431
432
 
432
433
  f = base / ("regression.yaml")
433
434
  if not base.exists() or force:
434
- with open(f"{f}", "w") as t_file:
435
- t_file.write(
435
+ async with aiofiles.open(f"{f}", "w") as t_file:
436
+ await t_file.write(
436
437
  """
437
438
  ############################################################################################################################
438
439
  ### Visit https://www.tinybird.co/docs/production/implementing-test-strategies.html#regression-tests ###
@@ -1071,22 +1072,22 @@ async def push_data(
1071
1072
  raise e
1072
1073
 
1073
1074
  def cb(res):
1074
- if cb.First:
1075
+ if cb.First: # type: ignore[attr-defined]
1075
1076
  blocks_to_process = len([x for x in res["block_log"] if x["status"] == "idle"])
1076
1077
  if blocks_to_process:
1077
- cb.bar = click.progressbar(label=FeedbackManager.info_progress_blocks(), length=blocks_to_process)
1078
- cb.bar.update(0)
1079
- cb.First = False
1080
- cb.blocks_to_process = blocks_to_process
1078
+ cb.bar = click.progressbar(label=FeedbackManager.info_progress_blocks(), length=blocks_to_process) # type: ignore[attr-defined]
1079
+ cb.bar.update(0) # type: ignore[attr-defined]
1080
+ cb.First = False # type: ignore[attr-defined]
1081
+ cb.blocks_to_process = blocks_to_process # type: ignore[attr-defined]
1081
1082
  else:
1082
1083
  done = len([x for x in res["block_log"] if x["status"] == "done"])
1083
- if done * 2 > cb.blocks_to_process:
1084
- cb.bar.label = FeedbackManager.info_progress_current_blocks()
1085
- cb.bar.update(done - cb.prev_done)
1086
- cb.prev_done = done
1084
+ if done * 2 > cb.blocks_to_process: # type: ignore[attr-defined]
1085
+ cb.bar.label = FeedbackManager.info_progress_current_blocks() # type: ignore[attr-defined]
1086
+ cb.bar.update(done - cb.prev_done) # type: ignore[attr-defined]
1087
+ cb.prev_done = done # type: ignore[attr-defined]
1087
1088
 
1088
- cb.First = True # type: ignore
1089
- cb.prev_done = 0 # type: ignore
1089
+ cb.First = True # type: ignore[attr-defined]
1090
+ cb.prev_done = 0 # type: ignore[attr-defined]
1090
1091
 
1091
1092
  click.echo(FeedbackManager.info_starting_import_process())
1092
1093
 
@@ -1257,9 +1258,9 @@ def validate_kafka_bootstrap_servers(host_and_port):
1257
1258
  if len(parts) > 2:
1258
1259
  raise CLIException(FeedbackManager.error_kafka_bootstrap_server())
1259
1260
  host = parts[0]
1260
- port = parts[1] if len(parts) == 2 else "9092"
1261
+ port_str = parts[1] if len(parts) == 2 else "9092"
1261
1262
  try:
1262
- port = int(port)
1263
+ port = int(port_str)
1263
1264
  except Exception:
1264
1265
  raise CLIException(FeedbackManager.error_kafka_bootstrap_server())
1265
1266
  with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
@@ -2047,8 +2048,8 @@ async def create_aws_iamrole_connection(client: TinyB, service: str, connection_
2047
2048
  FeedbackManager.error_connection_create(connection_name=connection_name, error=str(e))
2048
2049
  )
2049
2050
 
2050
- with open(conn_file_path, "w") as f:
2051
- f.write(
2051
+ async with aiofiles.open(conn_file_path, "w") as f:
2052
+ await f.write(
2052
2053
  f"""TYPE {service}
2053
2054
 
2054
2055
  """
@@ -8,6 +8,7 @@ from os import getcwd
8
8
  from pathlib import Path
9
9
  from typing import Any, Dict, List, Optional
10
10
 
11
+ import aiofiles
11
12
  import click
12
13
  from click import Context
13
14
 
@@ -336,8 +337,8 @@ async def connection_create_snowflake(
336
337
  account, username, password, warehouse, role, connection_name, integration_name, stage_name
337
338
  )
338
339
 
339
- with open(conn_file_path, "w") as f:
340
- f.write(
340
+ async with aiofiles.open(conn_file_path, "w") as f:
341
+ await f.write(
341
342
  f"""TYPE snowflake
342
343
 
343
344
  USERNAME='{username}'
@@ -389,8 +390,8 @@ async def connection_create_bigquery(ctx: Context, no_validate: bool) -> None:
389
390
  click.echo(FeedbackManager.error_bigquery_improper_permissions())
390
391
 
391
392
  if connection_created:
392
- with open(Path(getcwd(), "bigquery.connection"), "w") as f:
393
- f.write("TYPE bigquery\n")
393
+ async with aiofiles.open(Path(getcwd(), "bigquery.connection"), "w") as f:
394
+ await f.write("TYPE bigquery\n")
394
395
  click.echo(FeedbackManager.success_connection_created(id="bigquery"))
395
396
 
396
397
 
@@ -528,8 +529,8 @@ async def connection_create_s3(
528
529
  click.echo("** Creating connection...")
529
530
  _ = await client.connection_create(params)
530
531
 
531
- with open(conn_file_path, "w") as f:
532
- f.write(
532
+ async with aiofiles.open(conn_file_path, "w") as f:
533
+ await f.write(
533
534
  """TYPE s3
534
535
 
535
536
  """
@@ -598,8 +599,8 @@ async def connection_create_gcs_hmac(
598
599
  FeedbackManager.error_connection_create(connection_name=connection_name, error=str(e))
599
600
  )
600
601
 
601
- with open(conn_file_path, "w") as f:
602
- f.write(
602
+ async with aiofiles.open(conn_file_path, "w") as f:
603
+ await f.write(
603
604
  """TYPE gcs_hmac
604
605
 
605
606
  """
@@ -700,8 +701,8 @@ async def connection_create_gcs(
700
701
  FeedbackManager.error_connection_create(connection_name=connection_name, error=str(e))
701
702
  )
702
703
 
703
- with open(conn_file_path, "w") as f:
704
- f.write(
704
+ async with aiofiles.open(conn_file_path, "w") as f:
705
+ await f.write(
705
706
  """TYPE {service}
706
707
 
707
708
  """
@@ -3,6 +3,7 @@ import sys
3
3
  from pathlib import Path
4
4
  from typing import List, Optional
5
5
 
6
+ import aiofiles
6
7
  import click
7
8
  from click import Context
8
9
 
@@ -60,8 +61,8 @@ async def fmt(
60
61
  if diff:
61
62
  result = result.rstrip("\n")
62
63
  lines_fmt = [f"{line}\n" for line in result.split("\n")]
63
- with open(filename, "r") as file:
64
- lines_file = file.readlines()
64
+ async with aiofiles.open(filename, "r") as file:
65
+ lines_file = await file.readlines()
65
66
  diff_result = difflib.unified_diff(
66
67
  lines_file, lines_fmt, fromfile=f"{Path(filename).name} local", tofile="fmt datafile"
67
68
  )
@@ -77,8 +78,8 @@ async def fmt(
77
78
  return None
78
79
 
79
80
  if yes or click.confirm(FeedbackManager.prompt_override_local_file(name=filename)):
80
- with open(f"{filename}", "w") as file:
81
- file.write(result)
81
+ async with aiofiles.open(f"{filename}", "w") as file:
82
+ await file.write(result)
82
83
 
83
84
  click.echo(FeedbackManager.success_generated_local_file(file=filename))
84
85
 
@@ -5,6 +5,7 @@ from typing import Any, Dict, Iterable, List, Optional
5
5
  import click
6
6
  import yaml
7
7
  from humanfriendly.tables import format_smart_table
8
+ from typing_extensions import override
8
9
 
9
10
  from tinybird.client import TinyB
10
11
  from tinybird.feedback_manager import FeedbackManager
@@ -117,6 +118,7 @@ class TestResult:
117
118
  return PASS_OVER_TIME
118
119
  return PASS
119
120
 
121
+ @override
120
122
  def __dict__(self):
121
123
  return {
122
124
  "name": self.name,
@@ -3,6 +3,8 @@ from collections import namedtuple
3
3
  from json import JSONEncoder
4
4
  from typing import Optional
5
5
 
6
+ from typing_extensions import override
7
+
6
8
 
7
9
  class MyJSONEncoder(JSONEncoder):
8
10
  # def default(self, in_obj):
@@ -54,6 +56,7 @@ class DataUnitTest:
54
56
  def __str__(self):
55
57
  return json.dumps(dict(self), ensure_ascii=False)
56
58
 
59
+ @override
57
60
  def __dict__(self):
58
61
  return dict(self)
59
62
 
@@ -205,7 +205,7 @@ import posixpath
205
205
  import re
206
206
  import threading
207
207
  from io import StringIO
208
- from typing import Union
208
+ from typing import Dict, Union
209
209
 
210
210
  from tornado import escape
211
211
  from tornado.log import app_log
@@ -356,7 +356,7 @@ class Template(object):
356
356
  buffer = StringIO()
357
357
  try:
358
358
  # named_blocks maps from names to _NamedBlock objects
359
- named_blocks = {}
359
+ named_blocks: Dict[str, _NamedBlock] = {}
360
360
  ancestors = self._get_ancestors(loader)
361
361
  ancestors.reverse()
362
362
  for ancestor in ancestors:
@@ -1163,7 +1163,7 @@ def check_valid_expr(expr):
1163
1163
  elif isinstance(expr, ast.Subscript):
1164
1164
  check_valid_expr(expr.value)
1165
1165
  if isinstance(expr.slice, ast.Index):
1166
- check_valid_expr(expr.slice.value)
1166
+ check_valid_expr(expr.slice.value) # type: ignore[attr-defined]
1167
1167
  elif isinstance(expr.slice, ast.Slice):
1168
1168
  if expr.slice.lower is not None:
1169
1169
  check_valid_expr(expr.slice.lower)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tinybird-cli
3
- Version: 5.10.1.dev0
3
+ Version: 5.10.2
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -18,9 +18,25 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
18
18
  Changelog
19
19
  ----------
20
20
 
21
+ 5.10.2
22
+ ***********
23
+
24
+ - `Changed` Bump tinybird-cli version in requirements.txt when running `tb init --git`
25
+ - `Changed` Use `aiofiles` for async operations
26
+ - `Fixed` `TAGS` format was missing quotes in `tb pull``
27
+ - `Fixed` some internal typing issues from the CLI. Something internal that we don't include in the changelog of the CLI.
28
+
29
+ 5.10.1
30
+ ***********
31
+
32
+ - `Fixed` Internal Error while pushing S3 Data Source which provided the wrong cron expression to the backend.
33
+
21
34
  5.10.0
22
35
  ***********
23
36
 
37
+ - `Added` support for Kafka Data Sources with CA certificate.
38
+ - `Fixed` Correctly parse lambda expressions in indexes
39
+ - `Changed` Upgrade clickhouse-toolset to 0.32.dev0
24
40
  - `Added` new "File not found" error to `tb check` when including files from missing paths.
25
41
  - `Added` support for Kafka Data Sources with CA certificate.
26
42
  - `Changed` Upgrade clickhouse-toolset to 0.32.dev0
@@ -1,3 +1,4 @@
1
+ aiofiles==24.1.0
1
2
  click<8.2,>=8.1.6
2
3
  clickhouse-toolset==0.32.dev0
3
4
  colorama==0.4.6