From 1c23a79095c60644f9a5a40637c9af954ba51021 Mon Sep 17 00:00:00 2001 From: Rafa Moreno Date: Mon, 9 Mar 2026 14:47:56 +0100 Subject: [PATCH] docs(readme): port ts readme structure to python api surface --- README.md | 1037 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 834 insertions(+), 203 deletions(-) diff --git a/README.md b/README.md index cef0a1b..555c50d 100644 --- a/README.md +++ b/README.md @@ -1,288 +1,919 @@ # tinybird-sdk (Python) -Python SDK for Tinybird with a TypeScript-SDK-like surface. +> **Note:** This package is experimental. APIs may change between versions. -It includes: +A Python SDK for defining Tinybird resources with a TypeScript-SDK-like workflow. +Define your datasources, pipes, and queries in Python and sync them directly to Tinybird. -- A low-level API wrapper (`TinybirdApi` / `createTinybirdApi`) -- A high-level runtime client (`TinybirdClient` / `createClient`) -- Schema DSL (`t`, `p`, `engine`, `defineDatasource`, `definePipe`, `defineProject`) -- Resource generation helpers (`generateResources`, `build`, `buildFromInclude`) -- API helpers for branches, workspaces, tokens, regions, dashboard URLs, and local mode +## Installation + +```bash +pip install tinybird-sdk +``` ## Requirements - Python `>=3.11` -- [`uv`](https://docs.astral.sh/uv/) for environment and dependency management +- Server-side usage only (do not expose Tinybird credentials in browser code) -## Setup with uv +## Quick Start -From the repository root: +### 1. Initialize your project ```bash -cd python -uv sync --dev +tinybird init +``` + +This creates: +- `tinybird.config.json` - Configuration file +- `lib/datasources.py` - Define your datasources +- `lib/pipes.py` - Define your pipes/endpoints +- `lib/client.py` - Your Tinybird client module + +### 2. Configure your token + +Create a `.env.local` file: + +```env +TINYBIRD_TOKEN=p.your_token_here +``` + +### 3. Define your datasources + +```python +# lib/datasources.py +from tinybird_sdk import define_datasource, t, engine + +page_views = define_datasource( + "page_views", + { + "description": "Page view tracking data", + "schema": { + "timestamp": t.date_time(), + "pathname": t.string(), + "session_id": t.string(), + "country": t.string().low_cardinality().nullable(), + }, + "engine": engine.merge_tree( + { + "sorting_key": ["pathname", "timestamp"], + } + ), + }, +) +``` + +### 4. Define your endpoints + +```python +# lib/pipes.py +from tinybird_sdk import define_endpoint, node, p, t + +top_pages = define_endpoint( + "top_pages", + { + "description": "Get the most visited pages", + "params": { + "start_date": p.date_time(), + "end_date": p.date_time(), + "limit": p.int32().optional(10), + }, + "nodes": [ + node( + { + "name": "aggregated", + "sql": """ + SELECT pathname, count() AS views + FROM page_views + WHERE timestamp >= {{DateTime(start_date)}} + AND timestamp <= {{DateTime(end_date)}} + GROUP BY pathname + ORDER BY views DESC + LIMIT {{Int32(limit, 10)}} + """, + } + ) + ], + "output": { + "pathname": t.string(), + "views": t.uint64(), + }, + }, +) +``` + +### 5. Create your client + +```python +# lib/client.py +from tinybird_sdk import Tinybird +from .datasources import page_views +from .pipes import top_pages + +tinybird = Tinybird( + { + "datasources": {"page_views": page_views}, + "pipes": {"top_pages": top_pages}, + } +) + +__all__ = ["tinybird", "page_views", "top_pages"] ``` -This creates `.venv` and installs the package in editable mode with dev dependencies. +### 6. Optional: use a stable local import path -## Development Commands +In larger applications, keep a single module (for example `lib/client.py`) and import from there: -Run from `python/`: +```python +from lib.client import tinybird +``` + +### 7. Start development ```bash -# Run test suite -uv run pytest +tinybird dev +``` + +This watches your schema files and syncs changes to Tinybird. -# Quick syntax validation -uv run python -m compileall -q src +### 8. Use the client -# Build wheel/sdist -uv build +```python +from lib.client import tinybird + +# Ingest one row +tinybird.page_views.ingest( + { + "timestamp": "2024-01-15 10:30:00", + "pathname": "/home", + "session_id": "abc123", + "country": "US", + } +) + +# Query endpoint +result = tinybird.top_pages.query( + { + "start_date": "2024-01-01 00:00:00", + "end_date": "2024-01-31 23:59:59", + "limit": 5, + } +) ``` -## Quick Start +### 9. Manage datasource rows -### 1) Low-level API wrapper +```python +from lib.client import tinybird -Use this when you want direct HTTP-like operations. +# Datasource accessors support: ingest, append, replace, delete, truncate + +tinybird.page_views.ingest( + { + "timestamp": "2024-01-15 10:30:00", + "pathname": "/pricing", + "session_id": "session_123", + "country": "US", + } +) + +tinybird.page_views.append( + { + "url": "https://example.com/page_views.csv", + } +) + +tinybird.page_views.replace( + { + "url": "https://example.com/page_views_full_snapshot.csv", + } +) + +tinybird.page_views.delete( + { + "delete_condition": "country = 'XX'", + } +) + +tinybird.page_views.delete( + { + "delete_condition": "country = 'XX'", + "dry_run": True, + } +) + +tinybird.page_views.truncate() +``` + +## Public Tinybird API (Optional) + +If you want a low-level API wrapper decoupled from the high-level client layer, +use `create_tinybird_api()` directly with `base_url` and `token`: ```python -from tinybird_sdk import createTinybirdApi +from tinybird_sdk import create_tinybird_api -api = createTinybirdApi({ - "baseUrl": "https://api.tinybird.co", - "token": "p.your_token", -}) +api = create_tinybird_api( + { + "base_url": "https://api.tinybird.co", + "token": "p.your_token", + } +) # Query endpoint pipe -result = api.query("top_pages", {"limit": 10}) +top_pages = api.query( + "top_pages", + { + "start_date": "2024-01-01", + "end_date": "2024-01-31", + "limit": 5, + }, +) -# Ingest event -api.ingest("events", { - "timestamp": "2026-01-01T00:00:00Z", - "event_name": "page_view", -}) +# Ingest one row +api.ingest( + "events", + { + "timestamp": "2024-01-15 10:30:00", + "event_name": "page_view", + "pathname": "/home", + }, +) -# Execute SQL -rows = api.sql("SELECT 1 AS value") +# Ingest retry behavior (disabled by default): +# - 429 retries use Retry-After / X-RateLimit-Reset headers. +# - 503 retries use SDK default exponential backoff. +api.ingest( + "events", + { + "timestamp": "2024-01-15 10:31:00", + "event_name": "button_click", + "pathname": "/pricing", + }, + { + "max_retries": 3, + }, +) + +# Import rows from URL/file +api.append_datasource( + "events", + { + "url": "https://example.com/events.csv", + }, +) + +# Delete rows matching a SQL condition +api.delete_datasource( + "events", + { + "delete_condition": "event_name = 'test'", + }, +) + +# Delete dry run +api.delete_datasource( + "events", + { + "delete_condition": "event_name = 'test'", + "dry_run": True, + }, +) + +# Truncate datasource +api.truncate_datasource("events") + +# Execute raw SQL +sql_result = api.sql("SELECT count() AS total FROM events") + +# Optional per-request token override +workspace_response = api.request_json( + "/v1/workspace", + token="p.branch_or_jwt_token", +) ``` -### 2) High-level runtime client +This Tinybird API is standalone and can be used without `create_client()` or `Tinybird(...)`. + +## JWT Token Creation -Use this when you want a more SDK-oriented client with datasource namespace operations. +Create short-lived JWT tokens for secure scoped access to Tinybird resources. ```python -from tinybird_sdk import createClient +from datetime import datetime, timedelta, timezone + +from tinybird_sdk import create_client + +client = create_client( + { + "base_url": "https://api.tinybird.co", + "token": "p.your_admin_token", + } +) + +result = client.tokens.create_jwt( + { + "name": "user_123_session", + "expires_at": datetime.now(tz=timezone.utc) + timedelta(hours=1), + "scopes": [ + { + "type": "PIPES:READ", + "resource": "user_dashboard", + "fixed_params": {"user_id": 123}, + } + ], + "limits": {"rps": 10}, + } +) + +jwt_token = result["token"] +``` + +### Scope Types + +| Scope | Description | +|-------|-------------| +| `PIPES:READ` | Read access to a specific pipe endpoint | +| `DATASOURCES:READ` | Read access to a datasource (with optional `filter`) | +| `DATASOURCES:APPEND` | Append access to a datasource | + +### Scope Options + +- **`fixed_params`**: For pipes, embed parameters that cannot be overridden by the caller. +- **`filter`**: For datasources, append a SQL WHERE clause (for example, `"org_id = 'acme'"`). + +## CLI Commands + +This package installs `tinybird` as a runtime dependency. +`tinybird generate` is handled by this SDK; other commands are delegated to the Tinybird CLI. + +### `tinybird init` + +Initialize a new Tinybird project: + +```bash +tinybird init +tinybird init --force +tinybird init --skip-login +``` + +### `tinybird migrate` + +Migrate local Tinybird datafiles (`.datasource`, `.pipe`, `.connection`) into a Python definitions file. + +```bash +tinybird migrate "tinybird/**/*.datasource" "tinybird/**/*.pipe" "tinybird/**/*.connection" +tinybird migrate tinybird/legacy --out ./tinybird.migration.py +tinybird migrate tinybird --dry-run +``` + +### `tinybird dev` + +```bash +tinybird dev +tinybird dev --local +tinybird dev --branch +``` + +### `tinybird build` + +```bash +tinybird build +tinybird build --dry-run +tinybird build --local +tinybird build --branch +``` + +### `tinybird deploy` + +```bash +tinybird deploy +tinybird deploy --check +tinybird deploy --allow-destructive-operations +``` + +### `tinybird pull` + +```bash +tinybird pull +tinybird pull --output-dir ./tinybird-datafiles +tinybird pull --force +``` + +### `tinybird login` + +```bash +tinybird login +``` + +### `tinybird branch` + +```bash +tinybird branch list +tinybird branch status +tinybird branch delete +``` + +### `tinybird info` + +```bash +tinybird info +tinybird info --json +``` + +## Configuration + +Create a `tinybird.config.json` (or `tinybird.config.py` / `tinybird_config.py` for dynamic logic) in your project root: + +```json +{ + "include": [ + "lib/*.py", + "tinybird/**/*.datasource", + "tinybird/**/*.pipe", + "tinybird/**/*.connection" + ], + "token": "${TINYBIRD_TOKEN}", + "base_url": "https://api.tinybird.co", + "dev_mode": "branch" +} +``` + +You can mix Python files with raw `.datasource`, `.pipe`, and `.connection` files for incremental migration. +`include` supports glob patterns. -client = createClient({ - "baseUrl": "https://api.tinybird.co", - "token": "p.your_token", - "devMode": False, -}) +### Config File Formats -client.query("top_pages", {"limit": 5}) -client.ingest("events", {"event_name": "click"}) +Supported config files (search order): -client.datasources.append("events", { - "url": "https://example.com/events.csv" -}) +| File | Description | +|------|-------------| +| `tinybird.config.py` | Python config with dynamic logic | +| `tinybird_config.py` | Python config alias | +| `tinybird.config.json` | JSON config (default for new projects) | +| `tinybird.json` | Legacy JSON config | -client.datasources.delete("events", { - "deleteCondition": "event_name = 'test'", - "dryRun": True, -}) +For Python configs, export one of: +- `config` dict +- `CONFIG` dict +- `default` dict +- `get_config()` returning a dict -context = client.getContext() +Example: + +```python +# tinybird.config.py +config = { + "include": ["lib/*.py"], + "token": "${TINYBIRD_TOKEN}", + "base_url": "https://api.tinybird.co", + "dev_mode": "branch", +} ``` -### 3) Schema DSL + project client +### Configuration Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `include` | `list[str]` | *required* | File paths or glob patterns for Python and raw datafiles | +| `token` | `str` | *required* | API token; supports `${ENV_VAR}` interpolation | +| `base_url` | `str` | `"https://api.tinybird.co"` | Tinybird API URL | +| `dev_mode` | `"branch"` \| `"local"` | `"branch"` | Development mode | + +### Local Development Mode + +Use a local Tinybird container for development without affecting cloud workspaces: + +1. Start the local container: + ```bash + docker run -d -p 7181:7181 --name tinybird-local tinybirdco/tinybird-local:latest + ``` + +2. Configure your project: + ```json + { + "dev_mode": "local" + } + ``` + + Or use CLI flag: + ```bash + tinybird dev --local + ``` + +## Defining Resources -Use this to define datasources/pipes as code and expose a structured client. +### Connections ```python -from tinybird_sdk import ( - t, - p, - node, - engine, - defineDatasource, - defineEndpoint, - createTinybirdClient, -) - -page_views = defineDatasource("page_views", { - "schema": { - "timestamp": t.dateTime(), - "pathname": t.string(), - "country": t.string().nullable().lowCardinality(), +from tinybird_sdk import define_gcs_connection, define_kafka_connection, define_s3_connection, secret + +events_kafka = define_kafka_connection( + "events_kafka", + { + "bootstrap_servers": "kafka.example.com:9092", + "security_protocol": "SASL_SSL", + "sasl_mechanism": "PLAIN", + "key": secret("KAFKA_KEY"), + "secret": secret("KAFKA_SECRET"), }, - "engine": engine.mergeTree({ - "sortingKey": ["pathname", "timestamp"], - }), -}) - -top_pages = defineEndpoint("top_pages", { - "params": { - "limit": p.int32().optional(10), - }, - "nodes": [ - node({ - "name": "endpoint", - "sql": """ - SELECT pathname, count() AS views - FROM page_views - GROUP BY pathname - ORDER BY views DESC - LIMIT {{Int32(limit, 10)}} - """, - }) - ], - "output": { - "pathname": t.string(), - "views": t.uint64(), +) + +landing_s3 = define_s3_connection( + "landing_s3", + { + "region": "us-east-1", + "arn": "arn:aws:iam::123456789012:role/tinybird-s3-access", }, -}) +) -tinybird = createTinybirdClient({ - "datasources": {"page_views": page_views}, - "pipes": {"top_pages": top_pages}, -}) +landing_gcs = define_gcs_connection( + "landing_gcs", + { + "service_account_credentials_json": secret("GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON"), + }, +) ``` -## SDK API Shape +### Datasources -### Top-level imports +```python +from tinybird_sdk import define_datasource, engine, t + +events = define_datasource( + "events", + { + "description": "Event tracking data", + "schema": { + "timestamp": t.date_time(), + "event_name": t.string().low_cardinality(), + "user_id": t.string().nullable(), + "properties": t.string(), + }, + "engine": engine.merge_tree( + { + "sorting_key": ["event_name", "timestamp"], + "partition_key": "toYYYYMM(timestamp)", + "ttl": "timestamp + INTERVAL 90 DAY", + } + ), + }, +) +``` + +### Endpoints (API pipes) ```python -from tinybird_sdk import ( - # Runtime clients - TinybirdClient, - createClient, - TinybirdApi, - createTinybirdApi, - - # Schema DSL - t, - p, - engine, - defineDatasource, - definePipe, - defineEndpoint, - defineMaterializedView, - defineCopyPipe, - defineProject, - createTinybirdClient, - node, - sql, - - # Tokens - createJWT, -) -``` - -### Common runtime methods - -`TinybirdApi`: - -- `query(endpointName, params=None, options=None)` -- `ingest(datasourceName, event, options=None)` -- `ingestBatch(datasourceName, events, options=None)` -- `sql(sql, options=None)` -- `appendDatasource(datasourceName, options, apiOptions=None)` -- `deleteDatasource(datasourceName, options, apiOptions=None)` -- `truncateDatasource(datasourceName, options=None, apiOptions=None)` -- `createToken(body, options=None)` - -`TinybirdClient`: - -- `query(pipeName, params=None, options=None)` -- `ingest(datasourceName, event, options=None)` -- `ingestBatch(datasourceName, events, options=None)` -- `sql(sql, options=None)` -- `getContext()` -- `datasources.ingest/append/replace/delete/truncate(...)` -- `tokens.createJWT(...)` - -## Generation and Build Workflows - -Generate Tinybird resource content from Python project definitions: +from tinybird_sdk import define_endpoint, node, p, t + +top_events = define_endpoint( + "top_events", + { + "description": "Get the most frequent events", + "params": { + "start_date": p.date_time(), + "end_date": p.date_time(), + "limit": p.int32().optional(10), + }, + "nodes": [ + node( + { + "name": "aggregated", + "sql": """ + SELECT event_name, count() AS event_count + FROM events + WHERE timestamp >= {{DateTime(start_date)}} + AND timestamp <= {{DateTime(end_date)}} + GROUP BY event_name + ORDER BY event_count DESC + LIMIT {{Int32(limit, 10)}} + """, + } + ) + ], + "output": { + "event_name": t.string(), + "event_count": t.uint64(), + }, + }, +) +``` + +### Internal Pipes (not exposed as API) ```python -from tinybird_sdk import defineProject, generateResources +from tinybird_sdk import define_pipe, node, p + +filtered_events = define_pipe( + "filtered_events", + { + "description": "Filter events by date range", + "params": { + "start_date": p.date_time(), + "end_date": p.date_time(), + }, + "nodes": [ + node( + { + "name": "filtered", + "sql": """ + SELECT * FROM events + WHERE timestamp >= {{DateTime(start_date)}} + AND timestamp <= {{DateTime(end_date)}} + """, + } + ) + ], + }, +) +``` -project = defineProject({ - "datasources": {"page_views": page_views}, - "pipes": {"top_pages": top_pages}, -}) +### Materialized Views -resources = generateResources(project) +```python +from tinybird_sdk import define_datasource, define_materialized_view, engine, node, t + +daily_stats = define_datasource( + "daily_stats", + { + "schema": { + "date": t.date(), + "pathname": t.string(), + "views": t.simple_aggregate_function("sum", t.uint64()), + "unique_sessions": t.aggregate_function("uniq", t.string()), + }, + "engine": engine.aggregating_merge_tree({"sorting_key": ["date", "pathname"]}), + }, +) -for ds in resources.datasources: - print(ds.name, ds.content) +daily_stats_mv = define_materialized_view( + "daily_stats_mv", + { + "datasource": daily_stats, + "nodes": [ + node( + { + "name": "aggregate", + "sql": """ + SELECT + toDate(timestamp) AS date, + pathname, + count() AS views, + uniqState(session_id) AS unique_sessions + FROM page_views + GROUP BY date, pathname + """, + } + ) + ], + }, +) ``` -Build from a Python schema module file: +### Copy Pipes ```python -from tinybird_sdk.generator import build +from tinybird_sdk import define_copy_pipe, node + +# Scheduled copy pipe +daily_snapshot = define_copy_pipe( + "daily_snapshot", + { + "datasource": events, + "copy_schedule": "0 0 * * *", + "copy_mode": "append", + "nodes": [ + node( + { + "name": "snapshot", + "sql": """ + SELECT today() AS snapshot_date, event_name, count() AS events + FROM events + WHERE toDate(timestamp) = today() - 1 + GROUP BY event_name + """, + } + ) + ], + }, +) -result = build({ - "schemaPath": "src/tinybird_schema.py", # must export `project` or `tinybird_project` -}) +# On-demand copy pipe +manual_report = define_copy_pipe( + "manual_report", + { + "datasource": events, + "copy_schedule": "@on-demand", + "copy_mode": "replace", + "nodes": [ + node( + { + "name": "report", + "sql": "SELECT * FROM events WHERE timestamp >= now() - interval 7 day", + } + ) + ], + }, +) +``` + +### Sink Pipes + +Use sink pipes to publish query results to external systems. +The SDK supports Kafka and S3 sinks. + +```python +from tinybird_sdk import define_sink_pipe, node + +# Kafka sink +kafka_events_sink = define_sink_pipe( + "kafka_events_sink", + { + "sink": { + "connection": events_kafka, + "topic": "events_export", + "schedule": "@on-demand", + }, + "nodes": [ + node( + { + "name": "publish", + "sql": "SELECT timestamp, payload FROM kafka_events", + } + ) + ], + }, +) -print(result.stats) +# S3 sink +s3_events_sink = define_sink_pipe( + "s3_events_sink", + { + "sink": { + "connection": landing_s3, + "bucket_uri": "s3://my-bucket/exports/", + "file_template": "events_{date}", + "format": "csv", + "schedule": "@once", + "strategy": "create_new", + "compression": "gzip", + }, + "nodes": [ + node( + { + "name": "export", + "sql": "SELECT timestamp, session_id FROM s3_landing", + } + ) + ], + }, +) ``` -Deploy generated resources using API layer helpers: +### Static Tokens ```python -from tinybird_sdk.api.build import buildToTinybird +from tinybird_sdk import define_datasource, define_endpoint, define_token, node, t + +app_token = define_token("app_read") +ingest_token = define_token("ingest_token") + +events = define_datasource( + "events", + { + "schema": { + "timestamp": t.date_time(), + "event_name": t.string(), + }, + "tokens": [ + {"token": app_token, "scope": "READ"}, + {"token": ingest_token, "scope": "APPEND"}, + ], + }, +) -response = buildToTinybird( - {"baseUrl": "https://api.tinybird.co", "token": "p.your_token"}, - resources, +top_events = define_endpoint( + "top_events", + { + "nodes": [node({"name": "endpoint", "sql": "SELECT * FROM events LIMIT 10"})], + "output": {"timestamp": t.date_time(), "event_name": t.string()}, + "tokens": [{"token": app_token, "scope": "READ"}], + }, ) ``` -## Token and Preview Behavior +## Type Validators + +Use `t.*` to define column types: + +```python +from tinybird_sdk import t + +schema = { + # Strings + "name": t.string(), + "id": t.uuid(), + "code": t.fixed_string(3), + + # Numbers + "count": t.int32(), + "amount": t.float64(), + "big_number": t.uint64(), + "price": t.decimal(10, 2), + + # Date/Time + "created_at": t.date_time(), + "updated_at": t.date_time64(3), + "birth_date": t.date(), + + # Boolean + "is_active": t.bool(), + + # Complex types + "tags": t.array(t.string()), + "metadata": t.map(t.string(), t.string()), + + # Aggregate functions + "total": t.simple_aggregate_function("sum", t.uint64()), + "unique_users": t.aggregate_function("uniq", t.string()), + + # Modifiers + "optional_field": t.string().nullable(), + "category": t.string().low_cardinality(), + "status": t.string().default("pending"), +} +``` + +## Parameter Validators + +Use `p.*` to define query parameters: + +```python +from tinybird_sdk import p + +params = { + "start_date": p.date_time(), + "user_id": p.string(), + + "limit": p.int32().optional(10), + "offset": p.int32().optional(0), + + "status": p.string().optional("active").describe("Filter by status"), +} +``` + +## Engine Configurations + +```python +from tinybird_sdk import engine + +engine.merge_tree( + { + "sorting_key": ["user_id", "timestamp"], + "partition_key": "toYYYYMM(timestamp)", + "ttl": "timestamp + INTERVAL 90 DAY", + } +) + +engine.replacing_merge_tree( + { + "sorting_key": ["id"], + "ver": "updated_at", + } +) -Preview token resolution supports these environment conventions: +engine.summing_merge_tree( + { + "sorting_key": ["date", "category"], + "columns": ["count", "total"], + } +) -- `TINYBIRD_BRANCH_TOKEN` (highest priority) -- `TINYBIRD_TOKEN` -- CI/preview branch env detection (`VERCEL_*`, `GITHUB_*`, `CI_*`, etc.) +engine.aggregating_merge_tree( + { + "sorting_key": ["date"], + } +) +``` -Helpers: +## Python App Integration -- `isPreviewEnvironment()` -- `getPreviewBranchName()` -- `resolveToken(...)` -- `clearTokenCache()` +For Python web apps (FastAPI, Django, Flask), keep Tinybird definitions and client in a dedicated module and import that module from your app services. -## Error Types +The CLI automatically loads `.env.local` and `.env` files in project root when resolving configuration. -Main SDK errors: +## Schema Inference Helpers -- `TinybirdApiError`: HTTP/API-level errors from low-level wrapper -- `TinybirdError`: high-level client errors -- `TokenApiError`, `BranchApiError`, `WorkspaceApiError`, `ResourceApiError`, `RegionsApiError`, `LocalApiError` +The `tinybird_sdk.infer` module can inspect datasource and pipe definitions: -## Project Layout +```python +from tinybird_sdk.infer import infer_output_schema, infer_params_schema, infer_row_schema -```text -python/ - src/tinybird_sdk/ - api/ - client/ - schema/ - generator/ - codegen/ - infer/ - cli/ - tests/ +row_schema = infer_row_schema(page_views) +params_schema = infer_params_schema(top_pages) +output_schema = infer_output_schema(top_pages) ``` -## Status +## License -This package is parity-oriented and actively evolving. API details may still be refined as more TS test contracts are ported. +MIT