streamwright 0.1.6__tar.gz → 0.1.8__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.
- {streamwright-0.1.6/src/streamwright.egg-info → streamwright-0.1.8}/PKG-INFO +11 -6
- {streamwright-0.1.6 → streamwright-0.1.8}/README.md +7 -3
- {streamwright-0.1.6 → streamwright-0.1.8}/pyproject.toml +5 -3
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/cli.py +63 -18
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/validation/schema.py +94 -5
- {streamwright-0.1.6 → streamwright-0.1.8/src/streamwright.egg-info}/PKG-INFO +11 -6
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright.egg-info/SOURCES.txt +1 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright.egg-info/entry_points.txt +1 -0
- streamwright-0.1.8/tests/test_cli_subcommands.py +72 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/tests/test_validation.py +9 -1
- {streamwright-0.1.6 → streamwright-0.1.8}/LICENSE +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/setup.cfg +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/setup.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/__init__.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/catalog.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/config/__init__.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/config/inputs.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/config/loader.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/config/reader.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/engine/__init__.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/engine/queries.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/engine/runner.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/engine/sql.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/net/__init__.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/net/downloads.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/net/encoding.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/net/http.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/net/paginators.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/outputs/__init__.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/outputs/dlt_output.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/outputs/exporter.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/outputs/output.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/outputs/staging.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/outputs/warehouse.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/runtime/__init__.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/runtime/components.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/runtime/logs.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/runtime/templates.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/runtime/testing.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/validation/__init__.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/validation/engine.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright/core/validation/source.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright.egg-info/dependency_links.txt +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright.egg-info/requires.txt +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/src/streamwright.egg-info/top_level.txt +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/tests/test_catalog.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/tests/test_connectors.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/tests/test_dlt_output.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/tests/test_exact_decimals.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/tests/test_exporter.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/tests/test_http_engine.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/tests/test_logging.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/tests/test_output_formats.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/tests/test_runner.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/tests/test_sql.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/tests/test_transform.py +0 -0
- {streamwright-0.1.6 → streamwright-0.1.8}/tests/test_warehouse_output.py +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: streamwright
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: Declarative data connectors — pull from APIs, files and databases with YAML, shape with SQL, write anywhere.
|
|
5
5
|
Author-email: Karthick Jaganathan <kjaganathan.sde@gmail.com>
|
|
6
6
|
License: Apache-2.0
|
|
7
|
-
Project-URL: Homepage, https://
|
|
8
|
-
Project-URL: Repository, https://github.com/karthick-jaganathan/streamwright
|
|
7
|
+
Project-URL: Homepage, https://streamwright.web.app
|
|
9
8
|
Project-URL: Documentation, https://streamwright.web.app/docs/
|
|
9
|
+
Project-URL: Repository, https://github.com/karthick-jaganathan/streamwright
|
|
10
|
+
Project-URL: Issue Tracker, https://github.com/karthick-jaganathan/streamwright/issues
|
|
10
11
|
Keywords: data,pipeline,etl,connector,singer,yaml,duckdb,rest-api
|
|
11
12
|
Classifier: Development Status :: 3 - Alpha
|
|
12
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -44,9 +45,12 @@ Each stream follows a three-stage pipeline:
|
|
|
44
45
|
|
|
45
46
|
## CLI Commands
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- **`streamwright
|
|
48
|
+
All commands can be invoked interchangeably using `streamwright` or the shorthand `sw` alias (e.g. `sw run`, `sw validate`, `sw connectors`, `sw mcp`).
|
|
49
|
+
|
|
50
|
+
- **`streamwright run`** (or **`sw run`**) — Execute a source and stream records to the configured destination.
|
|
51
|
+
- **`streamwright validate`** (or **`sw validate`**) — Validate source definitions, authentication specifications, and schemas without executing network requests.
|
|
52
|
+
- **`streamwright connectors`** (or **`sw connectors`**) — Browse, inspect, and install connectors from the StreamWright Hub.
|
|
53
|
+
- **`streamwright mcp`** (or **`sw mcp`**) — Launch the Model Context Protocol (MCP) server for AI coding assistants.
|
|
50
54
|
|
|
51
55
|
## Source Structure
|
|
52
56
|
|
|
@@ -120,3 +124,4 @@ streamwright run streamwright/examples/sources/readers/files_demo \
|
|
|
120
124
|
- **Full documentation:** https://streamwright.web.app/docs/
|
|
121
125
|
- **Architecture Guide:** https://streamwright.web.app/docs/architecture/
|
|
122
126
|
- **Connectors & Readers:** https://streamwright.web.app/docs/connectors/
|
|
127
|
+
- **MCP Server Guide:** https://streamwright.web.app/docs/ai/mcp/
|
|
@@ -16,9 +16,12 @@ Each stream follows a three-stage pipeline:
|
|
|
16
16
|
|
|
17
17
|
## CLI Commands
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- **`streamwright
|
|
19
|
+
All commands can be invoked interchangeably using `streamwright` or the shorthand `sw` alias (e.g. `sw run`, `sw validate`, `sw connectors`, `sw mcp`).
|
|
20
|
+
|
|
21
|
+
- **`streamwright run`** (or **`sw run`**) — Execute a source and stream records to the configured destination.
|
|
22
|
+
- **`streamwright validate`** (or **`sw validate`**) — Validate source definitions, authentication specifications, and schemas without executing network requests.
|
|
23
|
+
- **`streamwright connectors`** (or **`sw connectors`**) — Browse, inspect, and install connectors from the StreamWright Hub.
|
|
24
|
+
- **`streamwright mcp`** (or **`sw mcp`**) — Launch the Model Context Protocol (MCP) server for AI coding assistants.
|
|
22
25
|
|
|
23
26
|
## Source Structure
|
|
24
27
|
|
|
@@ -92,3 +95,4 @@ streamwright run streamwright/examples/sources/readers/files_demo \
|
|
|
92
95
|
- **Full documentation:** https://streamwright.web.app/docs/
|
|
93
96
|
- **Architecture Guide:** https://streamwright.web.app/docs/architecture/
|
|
94
97
|
- **Connectors & Readers:** https://streamwright.web.app/docs/connectors/
|
|
98
|
+
- **MCP Server Guide:** https://streamwright.web.app/docs/ai/mcp/
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "streamwright"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.8"
|
|
8
8
|
description = "Declarative data connectors — pull from APIs, files and databases with YAML, shape with SQL, write anywhere."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
@@ -36,13 +36,15 @@ dependencies = [
|
|
|
36
36
|
dlt = ["dlt>=1.0"]
|
|
37
37
|
|
|
38
38
|
[project.urls]
|
|
39
|
-
Homepage = "https://
|
|
40
|
-
Repository = "https://github.com/karthick-jaganathan/streamwright"
|
|
39
|
+
Homepage = "https://streamwright.web.app"
|
|
41
40
|
Documentation = "https://streamwright.web.app/docs/"
|
|
41
|
+
Repository = "https://github.com/karthick-jaganathan/streamwright"
|
|
42
|
+
"Issue Tracker" = "https://github.com/karthick-jaganathan/streamwright/issues"
|
|
42
43
|
|
|
43
44
|
[project.scripts]
|
|
44
45
|
# `streamwright run`, `streamwright validate`, `streamwright connectors` - one CLI (see streamwright.core.cli)
|
|
45
46
|
streamwright = "streamwright.core.cli:main"
|
|
47
|
+
sw = "streamwright.core.cli:main"
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
[tool.setuptools]
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# **************************************************************************/
|
|
17
17
|
|
|
18
18
|
"""
|
|
19
|
-
The `streamwright` command.
|
|
19
|
+
The `streamwright` command (shorthand: `sw`).
|
|
20
20
|
|
|
21
21
|
streamwright run SOURCE [--config FILE] [--set NAME=VALUE] [--secrets FILE] [--state FILE] [--stream NAME] [--output ...]
|
|
22
22
|
[--file-name TEMPLATE] [--allow-connector NAME] [--summary FILE] [logging options]
|
|
@@ -25,6 +25,7 @@ The `streamwright` command.
|
|
|
25
25
|
streamwright connectors list | search WORDS | install KEY [KEY ...] [--yes] [--hub-url URL]
|
|
26
26
|
(the catalog; install resolves every KEY, then runs one pip install)
|
|
27
27
|
streamwright validate PATH... (streamwright-validate, plus the checks of the installed connectors and query builders)
|
|
28
|
+
streamwright mcp [--transport stdio|sse] (Model Context Protocol server for AI assistants)
|
|
28
29
|
|
|
29
30
|
Logging options (streamwright run and streamwright validate): --log-level LEVEL, --log NAME=LEVEL, --log-format text|json,
|
|
30
31
|
--log-config FILE, --log-max-chars N. Loggers: streamwright.source (progress, summaries, warnings), streamwright.network (INFO: a
|
|
@@ -240,12 +241,18 @@ def _parser():
|
|
|
240
241
|
_add_log_options(run)
|
|
241
242
|
|
|
242
243
|
conn = commands.add_parser("connectors",
|
|
243
|
-
help="list,
|
|
244
|
+
help="list, find or install connectors (bare: the installed connectors and their "
|
|
244
245
|
"SDK loggers)")
|
|
245
|
-
conn.add_argument("
|
|
246
|
-
help="list
|
|
246
|
+
conn.add_argument("-ls", "--list", action="store_true", dest="flag_list",
|
|
247
|
+
help="list catalog and installed connectors")
|
|
248
|
+
conn.add_argument("-i", "--install", nargs="+", dest="flag_install", metavar="KEY",
|
|
249
|
+
help="install one or more connector keys")
|
|
250
|
+
conn.add_argument("-s", "-f", "--search", "--find", nargs="+", dest="flag_search", metavar="TEXT",
|
|
251
|
+
help="find/search connectors in the catalog by keyword")
|
|
252
|
+
conn.add_argument("action", nargs="?", metavar="{ls,i,find}",
|
|
253
|
+
help="ls (catalog + installed), -i KEY (install), or find TEXT (search catalog)")
|
|
247
254
|
conn.add_argument("query", nargs="*", metavar="KEY|TEXT",
|
|
248
|
-
help="connector keys (install: one or more) or words
|
|
255
|
+
help="connector keys (install: one or more) or search words")
|
|
249
256
|
conn.add_argument("--yes", action="store_true", help="skip the confirmation prompt when installing")
|
|
250
257
|
conn.add_argument("--hub-url", metavar="URL",
|
|
251
258
|
help="the connector hub (default: $STREAMWRIGHT_HUB_URL, else the live StreamWright hub; when it is "
|
|
@@ -254,6 +261,9 @@ def _parser():
|
|
|
254
261
|
validate = commands.add_parser("validate", help="check configuration files: streamwright-validate, plus the checks of "
|
|
255
262
|
"the installed connectors and query builders", add_help=False)
|
|
256
263
|
validate.add_argument("arguments", nargs=argparse.REMAINDER)
|
|
264
|
+
|
|
265
|
+
mcp_cmd = commands.add_parser("mcp", help="run the StreamWright Model Context Protocol (MCP) server", add_help=False)
|
|
266
|
+
mcp_cmd.add_argument("arguments", nargs=argparse.REMAINDER)
|
|
257
267
|
return parser
|
|
258
268
|
|
|
259
269
|
|
|
@@ -558,19 +568,42 @@ def _connector_lines():
|
|
|
558
568
|
|
|
559
569
|
|
|
560
570
|
def _connectors_command(args):
|
|
561
|
-
"""`streamwright connectors`: bare = installed list;
|
|
562
|
-
|
|
571
|
+
"""`streamwright connectors`: bare = installed list; ls/i/find use the catalog (the hub)."""
|
|
572
|
+
if getattr(args, "flag_list", False):
|
|
573
|
+
action = "ls"
|
|
574
|
+
query = []
|
|
575
|
+
elif getattr(args, "flag_install", None):
|
|
576
|
+
action = "install"
|
|
577
|
+
query = args.flag_install
|
|
578
|
+
elif getattr(args, "flag_search", None):
|
|
579
|
+
action = "find"
|
|
580
|
+
query = args.flag_search
|
|
581
|
+
else:
|
|
582
|
+
action = getattr(args, "action", None)
|
|
583
|
+
query = getattr(args, "query", [])
|
|
584
|
+
|
|
563
585
|
if action is None:
|
|
564
586
|
lines = _connector_lines()
|
|
565
|
-
print("\n".join(lines) if lines else "no connectors installed (e.g. streamwright connectors
|
|
587
|
+
print("\n".join(lines) if lines else "no connectors installed (e.g. streamwright connectors -i postgres)")
|
|
566
588
|
return 0
|
|
567
|
-
|
|
568
|
-
|
|
589
|
+
|
|
590
|
+
norm_action = action.lower()
|
|
591
|
+
if norm_action in ("ls", "list", "-ls", "--list"):
|
|
592
|
+
from streamwright.core import catalog
|
|
569
593
|
lines = catalog.listing(args.hub_url)
|
|
570
594
|
print("\n".join(lines) if lines else "the catalog is empty")
|
|
571
595
|
return 0
|
|
572
|
-
|
|
573
|
-
|
|
596
|
+
|
|
597
|
+
if norm_action in ("i", "install", "-i", "--install"):
|
|
598
|
+
from streamwright.core import catalog
|
|
599
|
+
if not query:
|
|
600
|
+
sys.stderr.write("streamwright: 'connectors -i' needs at least one connector KEY\n")
|
|
601
|
+
return 2
|
|
602
|
+
return catalog.install_many(query, assume_yes=args.yes, hub_url=args.hub_url)
|
|
603
|
+
|
|
604
|
+
if norm_action in ("find", "search", "s", "-s", "-f", "--find", "--search"):
|
|
605
|
+
from streamwright.core import catalog
|
|
606
|
+
text = " ".join(query)
|
|
574
607
|
hits = catalog.search(text, args.hub_url)
|
|
575
608
|
if not hits:
|
|
576
609
|
print("no connectors match %r" % text)
|
|
@@ -578,12 +611,17 @@ def _connectors_command(args):
|
|
|
578
611
|
for key, entry in sorted(hits.items()):
|
|
579
612
|
print(" %-16s %-10s %s" % (key, entry.get("trust", "community"), entry.get("summary", "")))
|
|
580
613
|
return 0
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
614
|
+
|
|
615
|
+
# If action was not a reserved verb, treat it as a direct search query (e.g. `sw connectors ads`)
|
|
616
|
+
from streamwright.core import catalog
|
|
617
|
+
text = " ".join([action] + query)
|
|
618
|
+
hits = catalog.search(text, args.hub_url)
|
|
619
|
+
if not hits:
|
|
620
|
+
print("no connectors match %r" % text)
|
|
621
|
+
return 0
|
|
622
|
+
for key, entry in sorted(hits.items()):
|
|
623
|
+
print(" %-16s %-10s %s" % (key, entry.get("trust", "community"), entry.get("summary", "")))
|
|
624
|
+
return 0
|
|
587
625
|
|
|
588
626
|
|
|
589
627
|
def main(argv=None):
|
|
@@ -601,6 +639,13 @@ def main(argv=None):
|
|
|
601
639
|
non_strict_codes=("connector-not-installed",))
|
|
602
640
|
finally:
|
|
603
641
|
setup.close()
|
|
642
|
+
if argv[:1] == ["mcp"]:
|
|
643
|
+
try:
|
|
644
|
+
from streamwright_mcp.cli import main as mcp_main
|
|
645
|
+
except ImportError:
|
|
646
|
+
sys.stderr.write("streamwright: 'streamwright-mcp' is not installed. Install it with: pip install streamwright-mcp\n")
|
|
647
|
+
return 1
|
|
648
|
+
return mcp_main(argv[1:]) or 0
|
|
604
649
|
args = _parser().parse_args(argv)
|
|
605
650
|
if args.command == "connectors":
|
|
606
651
|
return _connectors_command(args)
|
|
@@ -16,12 +16,101 @@
|
|
|
16
16
|
# **************************************************************************/
|
|
17
17
|
|
|
18
18
|
"""
|
|
19
|
-
Specification of the StreamWright source
|
|
19
|
+
Specification and single source of truth for the StreamWright source and stream declarative format (`kind: source`).
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
Defines structural schemas, valid keys, data types, authentication schemes, transformation contracts,
|
|
22
|
+
and export definitions for streamwright-validate, streamwright-mcp, and AI runtime assistants.
|
|
23
|
+
|
|
24
|
+
### 1. `source.yaml` Schema Structure
|
|
25
|
+
```yaml
|
|
26
|
+
kind: source # Required: must be 'source'
|
|
27
|
+
name: my_pipeline # Required: snake_case identifier
|
|
28
|
+
description: "Pipeline purpose" # Optional: text description
|
|
29
|
+
|
|
30
|
+
spec: # Declares runtime parameters and credentials
|
|
31
|
+
config: # Visible parameters passed via CLI (--set) or environment
|
|
32
|
+
tenant_id: {type: string, required: true}
|
|
33
|
+
batch_limit: {type: integer, default: 500}
|
|
34
|
+
start_date: {type: date, required: false}
|
|
35
|
+
secrets: # Redacted credentials from STREAMWRIGHT_SECRET_<NAME> or --secrets
|
|
36
|
+
api_key: {type: string, required: true}
|
|
37
|
+
|
|
38
|
+
auth: # Authentication provider or protocol
|
|
39
|
+
provider: <connector_name> # e.g. restapi, postgres, s3, google_ads
|
|
40
|
+
# Standard HTTP Auth Types:
|
|
41
|
+
# - bearer: {token: "{{ secrets.token }}"}
|
|
42
|
+
# - api_key: {name: "X-Api-Key", value: "{{ secrets.key }}", in: "header" | "query"}
|
|
43
|
+
# - basic: {username: "user", password: "{{ secrets.password }}"}
|
|
44
|
+
# - oauth2_refresh_token: {token_url, client_id, client_secret, refresh_token}
|
|
45
|
+
|
|
46
|
+
http: # Optional HTTP defaults shared by all streams
|
|
47
|
+
base_url: "https://api.example.com"
|
|
48
|
+
headers: {Accept: "application/json"}
|
|
49
|
+
params_encoding: plain # "plain" | "dotted"
|
|
50
|
+
paginator: # "offset" | "cursor" | "page"
|
|
51
|
+
type: cursor
|
|
52
|
+
token_path: next_cursor
|
|
53
|
+
param: cursor
|
|
54
|
+
records: {path: data} # JMESPath/key to extract record array from envelope
|
|
55
|
+
rate_limit: {requests: 10, per: 1s}
|
|
56
|
+
retry: {codes: [429, 500, 502, 503, 504], max_attempts: 5, backoff: exponential}
|
|
57
|
+
|
|
58
|
+
# Streams can be declared inline under `streams:` or as individual YAML files in `streams/<name>.yaml`.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 2. `streams/<name>.yaml` Schema Structure
|
|
62
|
+
```yaml
|
|
63
|
+
description: "Stream documentation"
|
|
64
|
+
|
|
65
|
+
# Optional partitioning (parallel or sequential chunk execution)
|
|
66
|
+
# partitions:
|
|
67
|
+
# - {name: account_id, values: "{{ config.account_ids }}"}
|
|
68
|
+
# - {name: child_id, from_stream: parent_stream, field: id}
|
|
69
|
+
|
|
70
|
+
# Optional incremental state sync
|
|
71
|
+
# incremental:
|
|
72
|
+
# cursor_field: updated_at
|
|
73
|
+
# start: "2026-01-01T00:00:00Z"
|
|
74
|
+
# window: 1d
|
|
75
|
+
# lookback: 1h
|
|
76
|
+
|
|
77
|
+
requests: # 1 or more extraction requests
|
|
78
|
+
# HTTP extraction:
|
|
79
|
+
- name: raw_items
|
|
80
|
+
http:
|
|
81
|
+
path: /v1/items
|
|
82
|
+
method: GET
|
|
83
|
+
params: {status: active}
|
|
84
|
+
|
|
85
|
+
# Or Connector SDK extraction:
|
|
86
|
+
# - name: raw_records
|
|
87
|
+
# sdk: postgres
|
|
88
|
+
# service: database
|
|
89
|
+
# method: query
|
|
90
|
+
# arguments:
|
|
91
|
+
# query: "SELECT * FROM orders WHERE created_at >= $since"
|
|
92
|
+
# params: {since: "{{ window.start }}"}
|
|
93
|
+
|
|
94
|
+
transform: # Relational DuckDB SQL transformations
|
|
95
|
+
mode: page # "page" (streamed per page) or "run" (accumulated whole run)
|
|
96
|
+
steps:
|
|
97
|
+
- name: cleaned_items
|
|
98
|
+
select: |
|
|
99
|
+
SELECT
|
|
100
|
+
record->>'id' AS item_id,
|
|
101
|
+
record->>'name' AS item_name,
|
|
102
|
+
(record->>'price')::DOUBLE AS price
|
|
103
|
+
FROM raw_items
|
|
104
|
+
|
|
105
|
+
export: # Final output datasets and identity contracts
|
|
106
|
+
items:
|
|
107
|
+
step: cleaned_items
|
|
108
|
+
primary_key: [item_id]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 3. Programmatic Schemas
|
|
112
|
+
Use `json_schema()` and `stream_json_schema()` to get JSON Schema Draft 7 specifications
|
|
113
|
+
for validating source and stream configurations programmatically.
|
|
25
114
|
"""
|
|
26
115
|
|
|
27
116
|
__all__ = ["VERSION", "SUPPORTED_VERSIONS", "KIND", "json_schema", "stream_json_schema", "split_outside_quotes"]
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: streamwright
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: Declarative data connectors — pull from APIs, files and databases with YAML, shape with SQL, write anywhere.
|
|
5
5
|
Author-email: Karthick Jaganathan <kjaganathan.sde@gmail.com>
|
|
6
6
|
License: Apache-2.0
|
|
7
|
-
Project-URL: Homepage, https://
|
|
8
|
-
Project-URL: Repository, https://github.com/karthick-jaganathan/streamwright
|
|
7
|
+
Project-URL: Homepage, https://streamwright.web.app
|
|
9
8
|
Project-URL: Documentation, https://streamwright.web.app/docs/
|
|
9
|
+
Project-URL: Repository, https://github.com/karthick-jaganathan/streamwright
|
|
10
|
+
Project-URL: Issue Tracker, https://github.com/karthick-jaganathan/streamwright/issues
|
|
10
11
|
Keywords: data,pipeline,etl,connector,singer,yaml,duckdb,rest-api
|
|
11
12
|
Classifier: Development Status :: 3 - Alpha
|
|
12
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -44,9 +45,12 @@ Each stream follows a three-stage pipeline:
|
|
|
44
45
|
|
|
45
46
|
## CLI Commands
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- **`streamwright
|
|
48
|
+
All commands can be invoked interchangeably using `streamwright` or the shorthand `sw` alias (e.g. `sw run`, `sw validate`, `sw connectors`, `sw mcp`).
|
|
49
|
+
|
|
50
|
+
- **`streamwright run`** (or **`sw run`**) — Execute a source and stream records to the configured destination.
|
|
51
|
+
- **`streamwright validate`** (or **`sw validate`**) — Validate source definitions, authentication specifications, and schemas without executing network requests.
|
|
52
|
+
- **`streamwright connectors`** (or **`sw connectors`**) — Browse, inspect, and install connectors from the StreamWright Hub.
|
|
53
|
+
- **`streamwright mcp`** (or **`sw mcp`**) — Launch the Model Context Protocol (MCP) server for AI coding assistants.
|
|
50
54
|
|
|
51
55
|
## Source Structure
|
|
52
56
|
|
|
@@ -120,3 +124,4 @@ streamwright run streamwright/examples/sources/readers/files_demo \
|
|
|
120
124
|
- **Full documentation:** https://streamwright.web.app/docs/
|
|
121
125
|
- **Architecture Guide:** https://streamwright.web.app/docs/architecture/
|
|
122
126
|
- **Connectors & Readers:** https://streamwright.web.app/docs/connectors/
|
|
127
|
+
- **MCP Server Guide:** https://streamwright.web.app/docs/ai/mcp/
|
|
@@ -40,6 +40,7 @@ src/streamwright/core/validation/engine.py
|
|
|
40
40
|
src/streamwright/core/validation/schema.py
|
|
41
41
|
src/streamwright/core/validation/source.py
|
|
42
42
|
tests/test_catalog.py
|
|
43
|
+
tests/test_cli_subcommands.py
|
|
43
44
|
tests/test_connectors.py
|
|
44
45
|
tests/test_dlt_output.py
|
|
45
46
|
tests/test_exact_decimals.py
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
from streamwright.core import cli
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_cli_help():
|
|
9
|
+
result = subprocess.run([sys.executable, "-m", "streamwright.core.cli", "--help"],
|
|
10
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
11
|
+
universal_newlines=True)
|
|
12
|
+
assert result.returncode == 0
|
|
13
|
+
assert "mcp" in result.stdout
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_cli_mcp_delegation():
|
|
17
|
+
from unittest.mock import MagicMock
|
|
18
|
+
mock_mcp = MagicMock(return_value=0)
|
|
19
|
+
mock_mod = MagicMock(main=mock_mcp)
|
|
20
|
+
with patch.dict(sys.modules, {"streamwright_mcp": MagicMock(), "streamwright_mcp.cli": mock_mod}):
|
|
21
|
+
code = cli.main(["mcp", "--help"])
|
|
22
|
+
assert code == 0
|
|
23
|
+
mock_mcp.assert_called_once_with(["--help"])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_cli_mcp_not_installed(capsys):
|
|
27
|
+
with patch.dict(sys.modules, {"streamwright_mcp.cli": None}):
|
|
28
|
+
with patch("builtins.__import__", side_effect=ImportError("No module named 'streamwright_mcp'")):
|
|
29
|
+
code = cli.main(["mcp"])
|
|
30
|
+
assert code == 1
|
|
31
|
+
captured = capsys.readouterr()
|
|
32
|
+
assert "'streamwright-mcp' is not installed" in captured.err
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_cli_connectors_help():
|
|
36
|
+
result = subprocess.run([sys.executable, "-m", "streamwright.core.cli", "connectors", "--help"],
|
|
37
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
38
|
+
universal_newlines=True)
|
|
39
|
+
assert result.returncode == 0
|
|
40
|
+
assert "-ls" in result.stdout
|
|
41
|
+
assert "-i" in result.stdout
|
|
42
|
+
assert "-s" in result.stdout or "find" in result.stdout
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_cli_connectors_variants(monkeypatch, capsys):
|
|
46
|
+
from streamwright.core import catalog
|
|
47
|
+
fake_catalog = {
|
|
48
|
+
"postgres": {"title": "Postgres", "summary": "Postgres DB", "trust": "official"}
|
|
49
|
+
}
|
|
50
|
+
monkeypatch.setattr(catalog, "connectors", lambda hub_url=None: fake_catalog)
|
|
51
|
+
monkeypatch.setattr(catalog, "listing", lambda hub_url=None: ["catalog-listing-item"])
|
|
52
|
+
|
|
53
|
+
# Test ls subcommand
|
|
54
|
+
code = cli.main(["connectors", "ls"])
|
|
55
|
+
assert code == 0
|
|
56
|
+
assert "catalog-listing-item" in capsys.readouterr().out
|
|
57
|
+
|
|
58
|
+
# Test -ls flag
|
|
59
|
+
code = cli.main(["connectors", "-ls"])
|
|
60
|
+
assert code == 0
|
|
61
|
+
assert "catalog-listing-item" in capsys.readouterr().out
|
|
62
|
+
|
|
63
|
+
# Test find subcommand
|
|
64
|
+
code = cli.main(["connectors", "find", "postgres"])
|
|
65
|
+
assert code == 0
|
|
66
|
+
assert "postgres" in capsys.readouterr().out
|
|
67
|
+
|
|
68
|
+
# Test -s flag
|
|
69
|
+
code = cli.main(["connectors", "-s", "postgres"])
|
|
70
|
+
assert code == 0
|
|
71
|
+
assert "postgres" in capsys.readouterr().out
|
|
72
|
+
|
|
@@ -376,7 +376,15 @@ def test_templates_are_not_allowed_in_names_and_records():
|
|
|
376
376
|
# * ----
|
|
377
377
|
|
|
378
378
|
def test_auth_checks():
|
|
379
|
-
|
|
379
|
+
from streamwright.core.runtime.components import Connector, register, unregister
|
|
380
|
+
class MockSdkValConnector(Connector):
|
|
381
|
+
name = "mock_sdk_val"
|
|
382
|
+
transport = "sdk"
|
|
383
|
+
register(MockSdkValConnector())
|
|
384
|
+
try:
|
|
385
|
+
assert codes(check(lambda d: d["auth"].update(provider="mock_sdk_val"))) == [(ERROR, "bad-value")]
|
|
386
|
+
finally:
|
|
387
|
+
unregister("mock_sdk_val")
|
|
380
388
|
assert codes(check(lambda d: d["auth"].update(type="bearr"))) == [(ERROR, "bad-value")]
|
|
381
389
|
issues = check(lambda d: d.update(auth={"type": "oauth2_refresh_token", "token_url": "https://x/token",
|
|
382
390
|
"client_id": "id", "client_secret": "s3cret",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|