tinybird 0.0.1.dev77__tar.gz → 0.0.1.dev79__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.

Potentially problematic release.


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

Files changed (103) hide show
  1. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/PKG-INFO +1 -1
  2. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/config.py +5 -3
  3. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/__cli__.py +2 -2
  4. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/auth.py +2 -2
  5. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/build.py +2 -1
  6. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/cli.py +4 -4
  7. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/config.py +51 -34
  8. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/create.py +28 -24
  9. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/build_pipe.py +6 -135
  10. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/playground.py +5 -0
  11. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/deployment.py +9 -1
  12. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/local_common.py +1 -1
  13. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/login.py +3 -1
  14. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/playground.py +4 -4
  15. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/project.py +5 -7
  16. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/shell.py +3 -3
  17. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/test.py +1 -1
  18. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird.egg-info/PKG-INFO +1 -1
  19. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/setup.cfg +0 -0
  20. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/__cli__.py +0 -0
  21. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/ch_utils/constants.py +0 -0
  22. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/ch_utils/engine.py +0 -0
  23. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/check_pypi.py +0 -0
  24. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/client.py +0 -0
  25. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/connectors.py +0 -0
  26. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/context.py +0 -0
  27. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/datafile.py +0 -0
  28. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/datatypes.py +0 -0
  29. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/feedback_manager.py +0 -0
  30. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/git_settings.py +0 -0
  31. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/prompts.py +0 -0
  32. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/sql.py +0 -0
  33. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/sql_template.py +0 -0
  34. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/sql_template_fmt.py +0 -0
  35. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/sql_toolset.py +0 -0
  36. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/syncasync.py +0 -0
  37. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/cli.py +0 -0
  38. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/cicd.py +0 -0
  39. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/common.py +0 -0
  40. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/copy.py +0 -0
  41. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/build.py +0 -0
  42. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/build_common.py +0 -0
  43. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  44. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/common.py +0 -0
  45. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/diff.py +0 -0
  46. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/exceptions.py +0 -0
  47. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/fixture.py +0 -0
  48. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/format_common.py +0 -0
  49. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  50. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  51. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/parse_datasource.py +0 -0
  52. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/parse_pipe.py +0 -0
  53. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  54. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datafile/pull.py +0 -0
  55. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/datasource.py +0 -0
  56. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/endpoint.py +0 -0
  57. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/exceptions.py +0 -0
  58. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/feedback_manager.py +0 -0
  59. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/fmt.py +0 -0
  60. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/job.py +0 -0
  61. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/llm.py +0 -0
  62. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/llm_utils.py +0 -0
  63. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/local.py +0 -0
  64. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/materialization.py +0 -0
  65. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/mock.py +0 -0
  66. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/pipe.py +0 -0
  67. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/regions.py +0 -0
  68. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/table.py +0 -0
  69. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/tag.py +0 -0
  70. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/telemetry.py +0 -0
  71. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  72. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
  73. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/token.py +0 -0
  74. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/watch.py +0 -0
  75. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/workspace.py +0 -0
  76. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb/modules/workspace_members.py +0 -0
  77. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli.py +0 -0
  78. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/auth.py +0 -0
  79. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/branch.py +0 -0
  80. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/cicd.py +0 -0
  81. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/cli.py +0 -0
  82. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/common.py +0 -0
  83. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/config.py +0 -0
  84. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/connection.py +0 -0
  85. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/datasource.py +0 -0
  86. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/exceptions.py +0 -0
  87. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/fmt.py +0 -0
  88. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/job.py +0 -0
  89. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/pipe.py +0 -0
  90. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/regions.py +0 -0
  91. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/tag.py +0 -0
  92. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/telemetry.py +0 -0
  93. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/test.py +0 -0
  94. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  95. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  96. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/workspace.py +0 -0
  97. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  98. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird/tornado_template.py +0 -0
  99. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird.egg-info/SOURCES.txt +0 -0
  100. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird.egg-info/dependency_links.txt +0 -0
  101. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird.egg-info/entry_points.txt +0 -0
  102. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird.egg-info/requires.txt +0 -0
  103. {tinybird-0.0.1.dev77 → tinybird-0.0.1.dev79}/tinybird.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev77
3
+ Version: 0.0.1.dev79
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -68,14 +68,15 @@ LEGACY_HOSTS = {
68
68
  }
69
69
 
70
70
 
71
- async def get_config(host: str, token: Optional[str], semver: Optional[str] = None) -> Dict[str, Any]:
71
+ async def get_config(
72
+ host: str, token: Optional[str], semver: Optional[str] = None, config_file: Optional[str] = None
73
+ ) -> Dict[str, Any]:
72
74
  if host:
73
75
  host = host.rstrip("/")
74
76
 
75
- config_file = Path(getcwd()) / ".tinyb"
76
77
  config = {}
77
78
  try:
78
- async with aiofiles.open(config_file) as file:
79
+ async with aiofiles.open(config_file or Path(getcwd()) / ".tinyb") as file:
79
80
  res = await file.read()
80
81
  config = json.loads(res)
81
82
  except OSError:
@@ -89,6 +90,7 @@ async def get_config(host: str, token: Optional[str], semver: Optional[str] = No
89
90
  config["semver"] = semver or config.get("semver", None)
90
91
  config["host"] = host or config.get("host", DEFAULT_API_HOST)
91
92
  config["workspaces"] = config.get("workspaces", [])
93
+ config["cwd"] = config.get("cwd", getcwd())
92
94
  return config
93
95
 
94
96
 
@@ -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__ = '0.0.1.dev77'
8
- __revision__ = '44da91f'
7
+ __version__ = '0.0.1.dev79'
8
+ __revision__ = 'f126631'
@@ -116,7 +116,7 @@ async def auth_login(host: Optional[str], token: Optional[str]) -> None:
116
116
  Alternatively, tb will use the authentication token found in the environment
117
117
  variable TB_USER_TOKEN. This method is most suitable for "headless" use of tb
118
118
  such as in automations or in a CI environment."""
119
- config = CLIConfig.get_global_config()
119
+ config = CLIConfig.get_project_config()
120
120
 
121
121
  if host:
122
122
  config.set_host(host)
@@ -146,7 +146,7 @@ async def auth_login(host: Optional[str], token: Optional[str]) -> None:
146
146
  @coro
147
147
  async def auth_logout() -> None:
148
148
  """Remove authentication from Tinybird."""
149
- conf = CLIConfig.get_global_config()
149
+ conf = CLIConfig.get_project_config()
150
150
  conf.set_user_token(None)
151
151
  conf.persist_to_file()
152
152
 
@@ -137,7 +137,8 @@ def build_project(project: Project, tb_client: TinyB, file_changed: Optional[str
137
137
  error_msg = None
138
138
  for build_error in build_errors:
139
139
  filename_bit = f"{build_error.get('filename', '')}"
140
- error_msg = ((filename_bit + "\n\n") if filename_bit else "") + build_error.get("error")
140
+ error_bit = build_error.get("error") or build_error.get("message") or ""
141
+ error_msg = ((filename_bit + "\n\n") if filename_bit else "") + error_bit
141
142
  error = error_msg or "Unknown build error"
142
143
  else:
143
144
  error = f"Unknown build result. Error: {result.get('error')}"
@@ -75,7 +75,6 @@ async def cli(
75
75
  """
76
76
  Use `OBFUSCATE_REGEX_PATTERN` and `OBFUSCATE_PATTERN_SEPARATOR` environment variables to define a regex pattern and a separator (in case of a single string with multiple regex) to obfuscate secrets in the CLI output.
77
77
  """
78
- project = Project(folder=os.getcwd())
79
78
  # We need to unpatch for our tests not to break
80
79
  if show_tokens or not cloud or ctx.invoked_subcommand == "build" or build:
81
80
  __unpatch_click_output()
@@ -88,7 +87,9 @@ async def cli(
88
87
  if debug:
89
88
  logging.basicConfig(level=logging.DEBUG)
90
89
 
91
- config_temp = CLIConfig.get_project_config(str(project.path))
90
+ config_temp = CLIConfig.get_project_config()
91
+ project = Project(folder=config_temp.cwd)
92
+
92
93
  if token:
93
94
  config_temp.set_token(token)
94
95
  if host:
@@ -106,10 +107,9 @@ async def cli(
106
107
  if not host and "TB_HOST" in os.environ:
107
108
  host = os.environ.get("TB_HOST", "")
108
109
 
109
- config = await get_config(host, token)
110
+ config = await get_config(host, token, config_file=config_temp._path)
110
111
  client = _get_tb_client(config.get("token", None), config["host"])
111
112
 
112
- config["path"] = str(project.path)
113
113
  # If they have passed a token or host as paramter and it's different that record in .tinyb, refresh the workspace id
114
114
  if token or host:
115
115
  try:
@@ -70,6 +70,7 @@ class CLIConfig:
70
70
  "token": "TB_TOKEN",
71
71
  "user_token": "TB_USER_TOKEN",
72
72
  "host": "TB_HOST",
73
+ "cwd": "TB_CWD",
73
74
  }
74
75
 
75
76
  DEFAULTS: Dict[str, str] = {"host": DEFAULT_API_HOST if not FeatureFlags.is_localhost() else DEFAULT_LOCALHOST}
@@ -77,9 +78,9 @@ class CLIConfig:
77
78
  _global: Optional["CLIConfig"] = None
78
79
  _projects: Dict[str, "CLIConfig"] = {}
79
80
 
80
- def __init__(self, path: Optional[str] = None, parent: Optional["CLIConfig"] = None) -> None:
81
- self._path = path
82
- self._parent = parent
81
+ def __init__(self, path: Optional[str] = None) -> None:
82
+ self.cwd = os.getcwd()
83
+ self._path = path or self.get_tinyb_file() or os.path.join(os.getcwd(), ".tinyb")
83
84
  self._values: Dict[str, ConfigValue] = {}
84
85
  self._values["version"] = ConfigValue("version", CURRENT_VERSION, ConfigValueOrigin.DEFAULT)
85
86
  self._workspaces: List[Dict[str, Any]] = []
@@ -94,7 +95,7 @@ class CLIConfig:
94
95
 
95
96
  def to_dict(self) -> Dict[str, Any]:
96
97
  """Helper to ease"""
97
- result: Dict[str, Any] = self._parent.to_dict() if self._parent else {}
98
+ result: Dict[str, Any] = {}
98
99
  result.update(dict((v.name, deepcopy(v.value)) for v in self._values.values()))
99
100
  return result
100
101
 
@@ -107,8 +108,6 @@ class CLIConfig:
107
108
  """
108
109
  if key in self._values:
109
110
  return self._values[key].value
110
- if self._parent:
111
- return self._parent[key]
112
111
  raise KeyError(key)
113
112
 
114
113
  def __setitem__(self, key: str, value: Any) -> None:
@@ -126,18 +125,16 @@ class CLIConfig:
126
125
  def get_value_origin(self, key: str) -> ConfigValueOrigin:
127
126
  if key in self._values:
128
127
  return self._values[key].origin
129
- if self._parent:
130
- return self._parent.get_value_origin(key)
131
128
  else:
132
129
  return ConfigValueOrigin.NONE
133
130
 
134
- def persist_to_file(self, override_with_values: Optional["CLIConfig"] = None) -> None:
131
+ def persist_to_file(self, override_with_path: Optional[str] = None) -> None:
132
+ if override_with_path:
133
+ self._path = override_with_path
134
+
135
135
  if not self._path:
136
136
  raise ValueError("Cannot persist configuration: `path` is None")
137
137
 
138
- if override_with_values:
139
- self.update(override_with_values)
140
-
141
138
  os.makedirs(os.path.dirname(self._path), exist_ok=True)
142
139
  values = dict(v.as_tuple() for v in self._values.values())
143
140
  write_json_file(values, self._path)
@@ -149,6 +146,9 @@ class CLIConfig:
149
146
  values: Dict[str, Any] = json.loads(file.read())
150
147
  for k, v in values.items():
151
148
  self[k] = v
149
+ if "cwd" in values:
150
+ self.set_cwd(values["cwd"])
151
+
152
152
  return True
153
153
  except OSError:
154
154
  return False
@@ -211,6 +211,9 @@ class CLIConfig:
211
211
  netloc: str = url_info.netloc.decode() if isinstance(url_info.netloc, bytes) else url_info.netloc
212
212
  self["host"] = f"{scheme}://{netloc}"
213
213
 
214
+ def set_cwd(self, cwd: str) -> None:
215
+ self.cwd = os.path.join(os.path.dirname(self._path) if self._path else os.getcwd(), cwd)
216
+
214
217
  def get_host(self, use_defaults_if_needed: bool = False) -> Optional[str]:
215
218
  result: Optional[str] = self.get("host", None)
216
219
  if result:
@@ -288,41 +291,55 @@ class CLIConfig:
288
291
 
289
292
  return result
290
293
 
291
- def spawn(self) -> "CLIConfig":
292
- return CLIConfig(path=None, parent=self)
293
-
294
294
  def update(self, other: Union["CLIConfig", Dict[str, Any]]):
295
295
  values = other if isinstance(other, dict) else other._values
296
296
  for k, v in values.items():
297
297
  self[k] = v
298
298
 
299
299
  @staticmethod
300
- def get_global_config(_path: Optional[str] = None) -> "CLIConfig":
301
- """Returns the user-specific config.
300
+ def get_tinyb_file() -> Optional[str]:
301
+ """Find the .tinyb file by traversing up directories from start_dir, stopping at user's home directory.
302
302
 
303
- The data is cached between calls, so feel free to use it freely instead
304
- of saving a reference.
303
+ Args:
304
+ start_dir: Directory to start searching from. Defaults to current working directory.
305
305
 
306
- Note: the `_path` argument is mainly intended to help during testing.
306
+ Returns:
307
+ The path to the .tinyb file if found, current working directory otherwise.
307
308
  """
308
- path: Optional[str] = _path or os.environ.get("XDG_CONFIG_HOME", None)
309
- if not path:
310
- path = os.path.join(os.environ.get("HOME", "~"), ".config")
311
- path = os.path.join(path, APP_CONFIG_NAME, ".tinyb")
312
- exists_path = os.path.exists(path)
313
- if not exists_path:
314
- path = os.path.join(os.getcwd(), ".tinyb")
315
- return CLIConfig(path, parent=None)
309
+ current_dir = os.getcwd()
310
+ path = os.path.join(current_dir, ".tinyb")
311
+ if os.path.isfile(path):
312
+ return path
313
+
314
+ home_dir = os.path.expanduser("~")
315
+
316
+ while current_dir != os.path.dirname(current_dir): # Stop at root directory
317
+ # Stop if we reach home directory
318
+ if current_dir == home_dir:
319
+ tinyb_path = os.path.join(current_dir, ".tinyb")
320
+ return tinyb_path if os.path.isfile(tinyb_path) else None
321
+
322
+ tinyb_path = os.path.join(current_dir, ".tinyb")
323
+ if os.path.isfile(tinyb_path):
324
+ return tinyb_path
325
+
326
+ # Stop if we would go beyond home directory
327
+ parent_dir = os.path.dirname(current_dir)
328
+ if not current_dir.startswith(home_dir) or not parent_dir.startswith(home_dir):
329
+ return None
330
+
331
+ current_dir = parent_dir
332
+ return None
316
333
 
317
334
  @staticmethod
318
- def get_project_config(working_dir: Optional[str] = None) -> "CLIConfig":
319
- """Returns the project-specific config located at `working_dir` (defaults to `os.getcwd()`)
335
+ def get_project_config() -> "CLIConfig":
336
+ """Returns the project-specific config by finding the closest .tinyb file in the directory tree.
320
337
 
321
- The data is cached between calls, given the same `working_dir`.
338
+ Returns:
339
+ A CLIConfig instance with the project configuration.
322
340
  """
323
- working_dir = working_dir or os.getcwd()
324
- path: str = os.path.join(working_dir, ".tinyb")
325
- return CLIConfig(path, parent=CLIConfig.get_global_config())
341
+ tinyb_file = CLIConfig.get_tinyb_file()
342
+ return CLIConfig(tinyb_file)
326
343
 
327
344
  @staticmethod
328
345
  def reset() -> None:
@@ -1,6 +1,5 @@
1
1
  import os
2
2
  import re
3
- from os import getcwd
4
3
  from pathlib import Path
5
4
  from typing import Optional
6
5
 
@@ -34,20 +33,35 @@ from tinybird.tb.modules.project import Project
34
33
  help="Prompt to be used to create the project",
35
34
  )
36
35
  @click.option("--rows", type=int, default=10, help="Number of events to send")
37
- @click.option("--source", type=str, default="tb", help="Source of the command")
36
+ @click.option("--folder", type=str, default=None, help="Folder to create the project in")
37
+ @click.option("--agent", type=str, default="cursor", help="Agent to use for rules")
38
38
  @click.pass_context
39
39
  @coro
40
- async def create(ctx: click.Context, data: Optional[str], prompt: Optional[str], rows: int, source: str) -> None:
40
+ async def create(
41
+ ctx: click.Context, data: Optional[str], prompt: Optional[str], rows: int, folder: Optional[str], agent: str
42
+ ) -> None:
41
43
  """Initialize a new project."""
42
- project: Project = ctx.ensure_object(dict)["project"]
43
44
  local_client: TinyB = ctx.ensure_object(dict)["client"]
44
- folder = getcwd()
45
- folder_path = Path(folder)
45
+ project: Project = ctx.ensure_object(dict)["project"]
46
+ config = CLIConfig.get_project_config()
47
+
48
+ # If folder is provided, rewrite the config and project folder
49
+ if folder:
50
+ config.set_cwd(folder)
51
+ config.persist_to_file()
52
+ project.folder = folder
53
+
54
+ root_folder = os.getcwd()
55
+ if config._path:
56
+ root_folder = os.path.dirname(config._path)
57
+
58
+ folder = project.folder
59
+ folder_path = project.path
60
+
46
61
  if not folder_path.exists():
47
62
  folder_path.mkdir()
48
63
 
49
64
  try:
50
- config = CLIConfig.get_project_config(str(project.path))
51
65
  tb_client = config.get_client()
52
66
  user_token: Optional[str] = None
53
67
  created = False
@@ -75,16 +89,16 @@ async def create(ctx: click.Context, data: Optional[str], prompt: Optional[str],
75
89
  click.echo(FeedbackManager.highlight(message="\n» Creating resources..."))
76
90
  result = await create_resources(local_client, tb_client, user_token, data, prompt, folder)
77
91
  click.echo(FeedbackManager.success(message="✓ Done!\n"))
78
- created = True
79
- if not already_has_cicd(folder):
92
+
93
+ if not already_has_cicd(root_folder):
80
94
  click.echo(FeedbackManager.highlight(message="\n» Creating CI/CD files for GitHub and GitLab..."))
81
- init_git(folder)
82
- await init_cicd(data_project_dir=os.path.relpath(folder))
95
+ init_git(root_folder)
96
+ await init_cicd(root_folder, data_project_dir=os.path.relpath(folder))
83
97
  click.echo(FeedbackManager.success(message="✓ Done!\n"))
84
- created = True
85
- if not already_has_cursor_rules(folder):
98
+
99
+ if not already_has_cursor_rules(root_folder):
86
100
  click.echo(FeedbackManager.highlight(message="\n» Creating .cursorrules..."))
87
- create_rules(folder, source, "cursor")
101
+ create_rules(root_folder, "tb", agent)
88
102
  click.echo(FeedbackManager.success(message="✓ Done!\n"))
89
103
  created = True
90
104
  if should_generate_fixtures(result):
@@ -333,13 +347,3 @@ def get_context() -> str:
333
347
  def save_context(prompt: str, feedback: str):
334
348
  context_file = get_context_file()
335
349
  context_file.write_text(f"- {prompt}\n{feedback}")
336
-
337
-
338
- @cli.command("rules")
339
- @click.option("--agent", type=str, default="cursor", help="Agent to use for rules")
340
- @click.option("--source", type=str, default="tb", help="Source of the command")
341
- @click.pass_context
342
- def rules(ctx: click.Context, agent: str, source: str):
343
- """Create agent rules for the project."""
344
- project: Project = ctx.ensure_object(dict)["project"]
345
- create_rules(str(project.path), source, agent)
@@ -5,18 +5,17 @@ from urllib.parse import urlencode
5
5
 
6
6
  import click
7
7
  import requests
8
- from croniter import croniter
9
8
 
10
- from tinybird.client import DoesNotExistException, TinyB
11
- from tinybird.tb.modules.common import requests_delete, requests_get
9
+ from tinybird.client import TinyB
10
+ from tinybird.tb.modules.common import requests_get
12
11
  from tinybird.tb.modules.config import CLIConfig
13
- from tinybird.tb.modules.datafile.common import ON_DEMAND, CopyModes, CopyParameters, PipeNodeTypes, PipeTypes
14
- from tinybird.tb.modules.exceptions import CLIPipeException
12
+ from tinybird.tb.modules.datafile.common import PipeNodeTypes
15
13
  from tinybird.tb.modules.feedback_manager import FeedbackManager
16
14
 
17
15
 
18
16
  async def new_pipe(
19
17
  p,
18
+ config: CLIConfig,
20
19
  tb_client: TinyB,
21
20
  force: bool = False,
22
21
  check: bool = True,
@@ -39,13 +38,11 @@ async def new_pipe(
39
38
  tests_validate_processed_bytes: bool = False,
40
39
  override_datasource: bool = False,
41
40
  tests_check_requests_from_branch: bool = False,
42
- config: Any = None,
43
41
  fork_downstream: Optional[bool] = False,
44
42
  fork: Optional[bool] = False,
45
43
  ):
46
44
  # TODO use tb_client instead of calling the urls directly.
47
45
  host = tb_client.host
48
- config = CLIConfig().get_project_config()
49
46
  token = config.get_user_token()
50
47
 
51
48
  headers = {"Authorization": f"Bearer {token}"}
@@ -101,8 +98,8 @@ async def new_pipe(
101
98
 
102
99
  try:
103
100
  user_client = deepcopy(tb_client)
104
- config = CLIConfig().get_project_config()
105
- user_client.token = config.get_user_token()
101
+ config = CLIConfig.get_project_config()
102
+ user_client.token = config.get_user_token() or ""
106
103
  params["workspace_id"] = config.get("id", None)
107
104
  body["name"] = p["name"] + "__tb__playground"
108
105
 
@@ -182,132 +179,6 @@ async def get_token_from_main_branch(branch_tb_client: TinyB) -> Optional[str]:
182
179
  return token_from_main_branch
183
180
 
184
181
 
185
- async def check_materialized(pipe, host, token, cl, override_datasource=False, current_pipe=None):
186
- checker_pipe = deepcopy(pipe)
187
- checker_pipe["name"] = f"{checker_pipe['name']}__checker"
188
- headers = {"Authorization": f"Bearer {token}"}
189
-
190
- if current_pipe:
191
- from_copy_to_materialized = current_pipe["type"] == "copy"
192
- if from_copy_to_materialized:
193
- await cl.pipe_remove_copy(current_pipe["id"], current_pipe["copy_node"])
194
-
195
- materialized_node = None
196
- for node in checker_pipe["nodes"]:
197
- if node["params"]["type"] == "materialized":
198
- materialized_node = deepcopy(node)
199
- materialized_node["params"]["override_datasource"] = "true" if override_datasource else "false"
200
- node["params"]["type"] = "standard"
201
-
202
- try:
203
- pipe_created = False
204
- await new_pipe(
205
- checker_pipe, cl, force=True, check=False, populate=False, skip_tokens=True, ignore_sql_errors=False
206
- )
207
- pipe_created = True
208
- response = await cl.analyze_pipe_node(checker_pipe["name"], materialized_node, dry_run="true")
209
- if response.get("warnings"):
210
- show_materialized_view_warnings(response["warnings"])
211
-
212
- except Exception as e:
213
- raise click.ClickException(FeedbackManager.error_while_check_materialized(error=str(e)))
214
- finally:
215
- if pipe_created:
216
- r = await requests_delete(f"{host}/v0/pipes/{checker_pipe['name']}", headers=headers)
217
- if r.status_code != 204:
218
- click.echo(FeedbackManager.warning_check_pipe(content=r.content))
219
-
220
-
221
- async def check_copy_pipe(pipe, copy_node, tb_client: TinyB):
222
- target_datasource = copy_node["params"].get("target_datasource", None)
223
- if not target_datasource:
224
- raise CLIPipeException(FeedbackManager.error_creating_copy_pipe_target_datasource_required())
225
-
226
- try:
227
- await tb_client.get_datasource(target_datasource)
228
- except DoesNotExistException:
229
- raise CLIPipeException(
230
- FeedbackManager.error_creating_copy_pipe_target_datasource_not_found(target_datasource=target_datasource)
231
- )
232
- except Exception as e:
233
- raise CLIPipeException(FeedbackManager.error_exception(error=e))
234
-
235
- schedule_cron = copy_node["params"].get(CopyParameters.COPY_SCHEDULE, None)
236
- is_valid_cron = not schedule_cron or (
237
- schedule_cron and (schedule_cron == ON_DEMAND or croniter.is_valid(schedule_cron))
238
- )
239
-
240
- if not is_valid_cron:
241
- raise CLIPipeException(FeedbackManager.error_creating_copy_pipe_invalid_cron(schedule_cron=schedule_cron))
242
-
243
- mode = copy_node["params"].get("mode", CopyModes.APPEND)
244
- is_valid_mode = CopyModes.is_valid(mode)
245
-
246
- if not is_valid_mode:
247
- raise CLIPipeException(FeedbackManager.error_creating_copy_pipe_invalid_mode(mode=mode))
248
-
249
- if not pipe:
250
- return
251
-
252
- pipe_name = pipe["name"]
253
- pipe_type = pipe["type"]
254
-
255
- if pipe_type == PipeTypes.ENDPOINT:
256
- await tb_client.pipe_remove_endpoint(pipe_name, pipe["endpoint"])
257
-
258
- if pipe_type == PipeTypes.DATA_SINK:
259
- await tb_client.pipe_remove_sink(pipe_name, pipe["sink_node"])
260
-
261
- if pipe_type == PipeTypes.STREAM:
262
- await tb_client.pipe_remove_stream(pipe_name, pipe["stream_node"])
263
-
264
-
265
- async def check_sink_pipe(pipe, sink_node, tb_client: TinyB):
266
- if not sink_node["export_params"]:
267
- return
268
-
269
- if not pipe:
270
- return
271
-
272
- pipe_name = pipe["name"]
273
- pipe_type = pipe["type"]
274
-
275
- schedule_cron = sink_node["export_params"].get("schedule_cron", "")
276
- is_valid_cron = not schedule_cron or (schedule_cron and croniter.is_valid(schedule_cron))
277
-
278
- if not is_valid_cron:
279
- raise CLIPipeException(FeedbackManager.error_creating_sink_pipe_invalid_cron(schedule_cron=schedule_cron))
280
-
281
- if pipe_type == PipeTypes.ENDPOINT:
282
- await tb_client.pipe_remove_endpoint(pipe_name, pipe["endpoint"])
283
-
284
- if pipe_type == PipeTypes.COPY:
285
- await tb_client.pipe_remove_copy(pipe_name, pipe["copy_node"])
286
-
287
- if pipe_type == PipeTypes.STREAM:
288
- await tb_client.pipe_remove_stream(pipe_name, pipe["stream_node"])
289
-
290
-
291
- async def check_stream_pipe(pipe, stream_node, tb_client: TinyB):
292
- if not stream_node["params"]:
293
- return
294
-
295
- if not pipe:
296
- return
297
-
298
- pipe_name = pipe["name"]
299
- pipe_type = pipe["type"]
300
-
301
- if pipe_type == PipeTypes.ENDPOINT:
302
- await tb_client.pipe_remove_endpoint(pipe_name, pipe["endpoint"])
303
-
304
- if pipe_type == PipeTypes.COPY:
305
- await tb_client.pipe_remove_copy(pipe_name, pipe["copy_node"])
306
-
307
- if pipe_type == PipeTypes.DATA_SINK:
308
- await tb_client.pipe_remove_sink(pipe_name, pipe["sink_node"])
309
-
310
-
311
182
  def show_materialized_view_warnings(warnings):
312
183
  """
313
184
  >>> show_materialized_view_warnings([{'code': 'SIM', 'weight': 1}])
@@ -15,6 +15,7 @@ from tinybird.client import TinyB
15
15
  from tinybird.sql import parse_table_structure, schema_to_sql_columns
16
16
  from tinybird.sql_template import get_used_tables_in_template, render_sql_template
17
17
  from tinybird.tb.modules.common import get_ca_pem_content
18
+ from tinybird.tb.modules.config import CLIConfig
18
19
  from tinybird.tb.modules.datafile.build_datasource import is_datasource
19
20
  from tinybird.tb.modules.datafile.build_pipe import (
20
21
  get_target_materialized_data_source_name,
@@ -48,6 +49,7 @@ from tinybird.tb.modules.project import Project
48
49
 
49
50
  async def folder_playground(
50
51
  project: Project,
52
+ config: CLIConfig,
51
53
  tb_client: TinyB,
52
54
  filenames: Optional[List[str]] = None,
53
55
  is_internal: bool = False,
@@ -163,6 +165,7 @@ async def folder_playground(
163
165
  try:
164
166
  await exec_file(
165
167
  to_run[name],
168
+ config,
166
169
  tb_client,
167
170
  force,
168
171
  check,
@@ -390,6 +393,7 @@ async def name_matches_existing_resource(resource: str, name: str, tb_client: Ti
390
393
 
391
394
  async def exec_file(
392
395
  r: Dict[str, Any],
396
+ config: CLIConfig,
393
397
  tb_client: TinyB,
394
398
  force: bool,
395
399
  check: bool,
@@ -426,6 +430,7 @@ async def exec_file(
426
430
  if r["resource"] == "pipes":
427
431
  await new_pipe(
428
432
  r,
433
+ config,
429
434
  tb_client,
430
435
  force,
431
436
  check,
@@ -343,7 +343,15 @@ def create_deployment(
343
343
 
344
344
  feedback = result.get("deployment", {}).get("feedback", [])
345
345
  for f in feedback:
346
- click.echo(FeedbackManager.warning(message=f"△ {f.get('level')}: {f.get('resource')}: {f.get('message')}"))
346
+ if f.get("level", "").upper() == "ERROR":
347
+ feedback_func = FeedbackManager.error
348
+ feedback_icon = ""
349
+ else:
350
+ feedback_func = FeedbackManager.warning
351
+ feedback_icon = "△ "
352
+ resource = f.get("resource")
353
+ resource_bit = f"{resource}: " if resource else ""
354
+ click.echo(feedback_func(message=f"{feedback_icon}{f.get('level')}: {resource_bit}{f.get('message')}"))
347
355
 
348
356
  deploy_errors = result.get("deployment", {}).get("errors")
349
357
  for deploy_error in deploy_errors:
@@ -27,7 +27,7 @@ async def get_tinybird_local_config(config_obj: Dict[str, Any], build: bool = Fa
27
27
  It uses the tokens from tinybird local
28
28
  """
29
29
  path = config_obj.get("path")
30
- config = CLIConfig.get_project_config(path)
30
+ config = CLIConfig.get_project_config()
31
31
 
32
32
  try:
33
33
  # ruff: noqa: ASYNC210
@@ -1,4 +1,5 @@
1
1
  import http.server
2
+ import os
2
3
  import socketserver
3
4
  import threading
4
5
  import time
@@ -159,7 +160,8 @@ async def login(host: str, auth_host: str, workspace: str):
159
160
  if k in ws:
160
161
  cli_config[k] = ws[k]
161
162
 
162
- cli_config.persist_to_file()
163
+ path = os.path.join(os.getcwd(), ".tinyb")
164
+ cli_config.persist_to_file(override_with_path=path)
163
165
  click.echo(FeedbackManager.gray(message="\nWorkspace: ") + FeedbackManager.info(message=ws["name"]))
164
166
  click.echo(FeedbackManager.gray(message="User: ") + FeedbackManager.info(message=ws["user_email"]))
165
167
  click.echo(FeedbackManager.gray(message="Host: ") + FeedbackManager.info(message=host))
@@ -62,6 +62,7 @@ def playground(
62
62
  """Build the project in Tinybird Local."""
63
63
  project: Project = ctx.ensure_object(dict)["project"]
64
64
  tb_client: TinyB = ctx.ensure_object(dict)["client"]
65
+ config: CLIConfig = ctx.ensure_object(dict)["config"]
65
66
  context.disable_template_security_validation.set(True)
66
67
 
67
68
  async def process(filenames: List[str], watch: bool = False):
@@ -69,11 +70,11 @@ def playground(
69
70
  if len(datafiles) > 0:
70
71
  check_filenames(filenames=datafiles)
71
72
  await folder_playground(
72
- project, tb_client, filenames=datafiles, is_internal=False, current_ws=None, local_ws=None
73
+ project, config, tb_client, filenames=datafiles, is_internal=False, current_ws=None, local_ws=None
73
74
  )
74
75
  if len(filenames) > 0 and watch:
75
76
  filename = filenames[0]
76
- await build_and_print_resource(tb_client, filename)
77
+ await build_and_print_resource(config, tb_client, filename)
77
78
 
78
79
  datafiles = project.get_project_files()
79
80
  filenames = datafiles
@@ -112,11 +113,10 @@ def playground(
112
113
  shell.run()
113
114
 
114
115
 
115
- async def build_and_print_resource(tb_client: TinyB, filename: str):
116
+ async def build_and_print_resource(config: CLIConfig, tb_client: TinyB, filename: str):
116
117
  resource_path = Path(filename)
117
118
  name = resource_path.stem
118
119
  playground_name = name if filename.endswith(".pipe") else None
119
- config = CLIConfig().get_project_config()
120
120
  user_client = deepcopy(tb_client)
121
121
  user_client.token = config.get_user_token() or ""
122
122
  cli_params = {}
@@ -1,10 +1,8 @@
1
1
  import glob
2
- import os
3
2
  import re
4
3
  from pathlib import Path
5
4
  from typing import Dict, List, Optional
6
5
 
7
- from tinybird.tb.modules.config import CLIConfig
8
6
  from tinybird.tb.modules.datafile.common import Datafile
9
7
  from tinybird.tb.modules.datafile.parse_datasource import parse_datasource
10
8
  from tinybird.tb.modules.datafile.parse_pipe import parse_pipe
@@ -13,12 +11,12 @@ from tinybird.tb.modules.datafile.parse_pipe import parse_pipe
13
11
  class Project:
14
12
  extensions = ("datasource", "pipe")
15
13
 
16
- def __init__(self, folder: Optional[str] = None):
17
- self.folder = folder or os.getcwd()
18
- self.path = Path(self.folder)
14
+ def __init__(self, folder: str):
15
+ self.folder = folder
19
16
 
20
- def get_config(self) -> CLIConfig:
21
- return CLIConfig.get_project_config(self.folder)
17
+ @property
18
+ def path(self) -> Path:
19
+ return Path(self.folder)
22
20
 
23
21
  @property
24
22
  def vendor_path(self) -> str:
@@ -340,12 +340,12 @@ class Shell:
340
340
 
341
341
  def print_table_formatted(res: dict, name: str):
342
342
  data = []
343
- limit = 5
344
- for d in res["data"][:5]:
343
+ limit = 20
344
+ for d in res["data"][:limit]:
345
345
  data.append(d.values())
346
346
  meta = res["meta"]
347
- row_count = res.get("rows", 0)
348
347
  stats = res.get("statistics", {})
348
+ row_count = stats.get("rows_read", 0)
349
349
  elapsed = stats.get("elapsed", 0)
350
350
  cols = len(meta)
351
351
  try:
@@ -75,6 +75,7 @@ async def test_create(ctx: click.Context, name_or_filename: str, prompt: str) ->
75
75
  """
76
76
  project: Project = ctx.ensure_object(dict)["project"]
77
77
  client: TinyB = ctx.ensure_object(dict)["client"]
78
+ config: CLIConfig = ctx.ensure_object(dict)["config"]
78
79
  root_path = Path(project.folder)
79
80
  folder = project.folder
80
81
  try:
@@ -106,7 +107,6 @@ async def test_create(ctx: click.Context, name_or_filename: str, prompt: str) ->
106
107
  content=pipe_content,
107
108
  parameters=parameters or "No parameters",
108
109
  )
109
- config = CLIConfig.get_project_config(folder)
110
110
  user_token = config.get_user_token()
111
111
  if not user_token:
112
112
  raise CLIException(FeedbackManager.error(message="No user token found"))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev77
3
+ Version: 0.0.1.dev79
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
File without changes