streamwright 0.1.2__tar.gz → 0.1.4__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.2/src/streamwright.egg-info → streamwright-0.1.4}/PKG-INFO +3 -2
- {streamwright-0.1.2 → streamwright-0.1.4}/README.md +2 -1
- {streamwright-0.1.2 → streamwright-0.1.4}/pyproject.toml +1 -1
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/catalog.py +50 -27
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/cli.py +10 -7
- {streamwright-0.1.2 → streamwright-0.1.4/src/streamwright.egg-info}/PKG-INFO +3 -2
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright.egg-info/SOURCES.txt +1 -0
- streamwright-0.1.4/tests/test_catalog.py +127 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/LICENSE +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/setup.cfg +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/setup.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/__init__.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/config/__init__.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/config/inputs.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/config/loader.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/config/reader.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/connectors.json +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/engine/__init__.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/engine/queries.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/engine/runner.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/engine/sql.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/net/__init__.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/net/downloads.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/net/http.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/outputs/__init__.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/outputs/dlt_output.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/outputs/exporter.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/outputs/output.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/outputs/staging.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/outputs/warehouse.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/runtime/__init__.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/runtime/components.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/runtime/logs.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/runtime/templates.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/runtime/testing.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/validation/__init__.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/validation/engine.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/validation/schema.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright/core/validation/source.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright.egg-info/dependency_links.txt +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright.egg-info/entry_points.txt +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright.egg-info/requires.txt +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/src/streamwright.egg-info/top_level.txt +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/tests/test_connectors.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/tests/test_dlt_output.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/tests/test_exact_decimals.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/tests/test_exporter.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/tests/test_logging.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/tests/test_output_formats.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/tests/test_runner.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/tests/test_sql.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/tests/test_transform.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/tests/test_validation.py +0 -0
- {streamwright-0.1.2 → streamwright-0.1.4}/tests/test_warehouse_output.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: streamwright
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Declarative data connectors — pull from APIs, files and databases with YAML, shape with SQL, write anywhere.
|
|
5
5
|
Author: Karthick Jaganathan
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -34,7 +34,7 @@ signing in, paging, retries, shaping the data with DuckDB SQL, and writing it ou
|
|
|
34
34
|
No per-connector code, and one command runs any source.
|
|
35
35
|
|
|
36
36
|
The `streamwright` package is the heart of it: the `streamwright` command plus the runtime for
|
|
37
|
-
`kind: source` sources.
|
|
37
|
+
`kind: source` sources.
|
|
38
38
|
|
|
39
39
|
- **`streamwright run`** executes a source · **`streamwright validate`** checks one · **`streamwright connectors`** browses and installs connectors.
|
|
40
40
|
- A source is a folder — `source.yaml` (`spec`, `auth`, `http`) and one file per stream in `streams/` — or a single YAML file.
|
|
@@ -54,6 +54,7 @@ connectors yet. Add the ones you need on demand:
|
|
|
54
54
|
streamwright connectors list # browse the hub
|
|
55
55
|
streamwright connectors install files # a reader (files, s3, gcs, postgres)
|
|
56
56
|
streamwright connectors install google_ads # an API connector (pulls in its SDK)
|
|
57
|
+
streamwright connectors install google_ads meta_ads microsoft_ads # several at once (one pip install)
|
|
57
58
|
```
|
|
58
59
|
|
|
59
60
|
## Quickstart
|
|
@@ -6,7 +6,7 @@ signing in, paging, retries, shaping the data with DuckDB SQL, and writing it ou
|
|
|
6
6
|
No per-connector code, and one command runs any source.
|
|
7
7
|
|
|
8
8
|
The `streamwright` package is the heart of it: the `streamwright` command plus the runtime for
|
|
9
|
-
`kind: source` sources.
|
|
9
|
+
`kind: source` sources.
|
|
10
10
|
|
|
11
11
|
- **`streamwright run`** executes a source · **`streamwright validate`** checks one · **`streamwright connectors`** browses and installs connectors.
|
|
12
12
|
- A source is a folder — `source.yaml` (`spec`, `auth`, `http`) and one file per stream in `streams/` — or a single YAML file.
|
|
@@ -26,6 +26,7 @@ connectors yet. Add the ones you need on demand:
|
|
|
26
26
|
streamwright connectors list # browse the hub
|
|
27
27
|
streamwright connectors install files # a reader (files, s3, gcs, postgres)
|
|
28
28
|
streamwright connectors install google_ads # an API connector (pulls in its SDK)
|
|
29
|
+
streamwright connectors install google_ads meta_ads microsoft_ads # several at once (one pip install)
|
|
29
30
|
```
|
|
30
31
|
|
|
31
32
|
## Quickstart
|
|
@@ -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.4"
|
|
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 = "Apache-2.0"
|
|
@@ -74,12 +74,13 @@ def installed():
|
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
def search(text, hub_url=None):
|
|
77
|
-
|
|
77
|
+
"""The catalog connectors whose key, title, summary or family contains every word of `text`."""
|
|
78
|
+
words = (text or "").lower().split()
|
|
78
79
|
hits = {}
|
|
79
80
|
for key, entry in connectors(hub_url).items():
|
|
80
81
|
haystack = " ".join([key, entry.get("title", ""), entry.get("summary", ""),
|
|
81
82
|
entry.get("family", "")]).lower()
|
|
82
|
-
if
|
|
83
|
+
if all(word in haystack for word in words):
|
|
83
84
|
hits[key] = entry
|
|
84
85
|
return hits
|
|
85
86
|
|
|
@@ -111,40 +112,62 @@ def listing(hub_url=None):
|
|
|
111
112
|
|
|
112
113
|
|
|
113
114
|
def install(key, assume_yes=False, hub_url=None, pip_args=()):
|
|
114
|
-
"""Resolve `key` in the catalog and install it (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
"""Resolve one connector `key` in the catalog and install it (see install_many)."""
|
|
116
|
+
return install_many([key], assume_yes=assume_yes, hub_url=hub_url, pip_args=pip_args)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def install_many(keys, assume_yes=False, hub_url=None, pip_args=()):
|
|
120
|
+
"""
|
|
121
|
+
Resolve connector `keys` in the catalog and install them with ONE pip command, so pip resolves their
|
|
122
|
+
dependencies together. Every key is checked first: an unknown key, or a connector without an installable
|
|
123
|
+
source, installs nothing. Image-distributed connectors are reported, not installed. Non-"official"
|
|
124
|
+
connectors need one confirmation for all of them (or `assume_yes`), and are refused non-interactively.
|
|
125
|
+
"""
|
|
126
|
+
keys = list(dict.fromkeys(keys)) # in order, without repeats
|
|
127
|
+
if not keys:
|
|
128
|
+
sys.stderr.write("streamwright: 'connectors install' needs at least one connector KEY\n")
|
|
129
|
+
return 2
|
|
130
|
+
catalog = connectors(hub_url)
|
|
131
|
+
unknown = [key for key in keys if key not in catalog]
|
|
132
|
+
if unknown:
|
|
133
|
+
sys.stderr.write("streamwright: unknown connector%s %s (try: streamwright connectors list)\n" % (
|
|
134
|
+
"s" if len(unknown) > 1 else "", ", ".join(repr(key) for key in unknown)))
|
|
118
135
|
return 2
|
|
119
|
-
inst = entry.get("install", {})
|
|
120
|
-
trust = entry.get("trust", "community")
|
|
121
136
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
specs, untrusted = [], []
|
|
138
|
+
for key in keys:
|
|
139
|
+
entry = catalog[key]
|
|
140
|
+
inst = entry.get("install", {})
|
|
141
|
+
if "image" in inst:
|
|
142
|
+
print("%r is distributed as an image, not a Python package:" % key)
|
|
143
|
+
print(" %s" % inst["image"])
|
|
144
|
+
print("Run it with the docker or k8s execution mode (it is not pip-installed).")
|
|
145
|
+
continue
|
|
146
|
+
spec = inst.get("pip") or inst.get("git")
|
|
147
|
+
if not spec:
|
|
148
|
+
sys.stderr.write("streamwright: connector %r has no installable source\n" % key)
|
|
149
|
+
return 2
|
|
150
|
+
specs.append(spec)
|
|
151
|
+
trust = entry.get("trust", "community")
|
|
152
|
+
if trust != "official":
|
|
153
|
+
untrusted.append((key, trust, _source_of(inst)))
|
|
154
|
+
if not specs:
|
|
126
155
|
return 0
|
|
127
156
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
sys.stderr.write("streamwright: connector %r has no installable source\n" % key)
|
|
131
|
-
return 2
|
|
132
|
-
|
|
133
|
-
if trust != "official" and not assume_yes:
|
|
134
|
-
source = _source_of(inst)
|
|
157
|
+
if untrusted and not assume_yes:
|
|
158
|
+
names = ", ".join("%s connector %r" % (trust, key) for key, trust, _ in untrusted)
|
|
135
159
|
if not sys.stdin.isatty():
|
|
136
|
-
sys.stderr.write(
|
|
137
|
-
|
|
138
|
-
% (trust, key, source))
|
|
160
|
+
sys.stderr.write("streamwright: refusing to install %s non-interactively; pass --yes to confirm (%s)\n"
|
|
161
|
+
% (names, "; ".join(source for _, _, source in untrusted)))
|
|
139
162
|
return 2
|
|
140
|
-
sys.stderr.write(
|
|
141
|
-
|
|
142
|
-
% (key, trust, source))
|
|
163
|
+
sys.stderr.write("\n WARNING: installing runs third-party code from:\n")
|
|
164
|
+
for key, trust, source in untrusted:
|
|
165
|
+
sys.stderr.write(" %s (%s): %s\n" % (key, trust, source))
|
|
143
166
|
answer = input(" Continue? [y/N] ").strip().lower()
|
|
144
167
|
if answer not in ("y", "yes"):
|
|
145
168
|
print("aborted")
|
|
146
169
|
return 1
|
|
147
170
|
|
|
148
|
-
cmd = [sys.executable, "-m", "pip", "install", *pip_args,
|
|
149
|
-
print("+ " + " ".join(cmd))
|
|
171
|
+
cmd = [sys.executable, "-m", "pip", "install", *pip_args, *specs]
|
|
172
|
+
print("+ " + " ".join(cmd), flush=True)
|
|
150
173
|
return subprocess.call(cmd)
|
|
@@ -22,6 +22,8 @@ The `streamwright` command.
|
|
|
22
22
|
[--file-name TEMPLATE] [--allow-connector NAME] [--summary FILE] [logging options]
|
|
23
23
|
(SOURCE: a source folder - source.yaml and streams/ - or a source file)
|
|
24
24
|
streamwright connectors (installed connectors, with the names of their SDK loggers)
|
|
25
|
+
streamwright connectors list | search WORDS | install KEY [KEY ...] [--yes] [--hub-url URL]
|
|
26
|
+
(the catalog; install resolves every KEY, then runs one pip install)
|
|
25
27
|
streamwright validate PATH... (streamwright-validate, plus the checks of the installed connectors and query builders)
|
|
26
28
|
|
|
27
29
|
Logging options (streamwright run and streamwright validate): --log-level LEVEL, --log NAME=LEVEL, --log-format text|json,
|
|
@@ -240,9 +242,9 @@ def _parser():
|
|
|
240
242
|
help="list, search or install connectors (bare: the installed connectors and their "
|
|
241
243
|
"SDK loggers)")
|
|
242
244
|
conn.add_argument("action", nargs="?", choices=["list", "search", "install"],
|
|
243
|
-
help="list (catalog + installed), search TEXT, or install KEY")
|
|
244
|
-
conn.add_argument("query", nargs="
|
|
245
|
-
help="
|
|
245
|
+
help="list (catalog + installed), search TEXT, or install KEY [KEY ...]")
|
|
246
|
+
conn.add_argument("query", nargs="*", metavar="KEY|TEXT",
|
|
247
|
+
help="connector keys (install: one or more) or words (search)")
|
|
246
248
|
conn.add_argument("--yes", action="store_true", help="skip the confirmation prompt when installing")
|
|
247
249
|
conn.add_argument("--hub-url", metavar="URL",
|
|
248
250
|
help="catalog URL to use (default: the bundled catalog, or $STREAMWRIGHT_HUB_URL)")
|
|
@@ -544,18 +546,19 @@ def _connectors_command(args):
|
|
|
544
546
|
print("\n".join(lines) if lines else "the catalog is empty")
|
|
545
547
|
return 0
|
|
546
548
|
if action == "search":
|
|
547
|
-
|
|
549
|
+
text = " ".join(args.query)
|
|
550
|
+
hits = catalog.search(text, args.hub_url)
|
|
548
551
|
if not hits:
|
|
549
|
-
print("no connectors match %r" %
|
|
552
|
+
print("no connectors match %r" % text)
|
|
550
553
|
return 0
|
|
551
554
|
for key, entry in sorted(hits.items()):
|
|
552
555
|
print(" %-16s %-10s %s" % (key, entry.get("trust", "community"), entry.get("summary", "")))
|
|
553
556
|
return 0
|
|
554
557
|
if action == "install":
|
|
555
558
|
if not args.query:
|
|
556
|
-
sys.stderr.write("streamwright: 'connectors install' needs
|
|
559
|
+
sys.stderr.write("streamwright: 'connectors install' needs at least one connector KEY\n")
|
|
557
560
|
return 2
|
|
558
|
-
return catalog.
|
|
561
|
+
return catalog.install_many(args.query, assume_yes=args.yes, hub_url=args.hub_url)
|
|
559
562
|
return 2
|
|
560
563
|
|
|
561
564
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: streamwright
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Declarative data connectors — pull from APIs, files and databases with YAML, shape with SQL, write anywhere.
|
|
5
5
|
Author: Karthick Jaganathan
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -34,7 +34,7 @@ signing in, paging, retries, shaping the data with DuckDB SQL, and writing it ou
|
|
|
34
34
|
No per-connector code, and one command runs any source.
|
|
35
35
|
|
|
36
36
|
The `streamwright` package is the heart of it: the `streamwright` command plus the runtime for
|
|
37
|
-
`kind: source` sources.
|
|
37
|
+
`kind: source` sources.
|
|
38
38
|
|
|
39
39
|
- **`streamwright run`** executes a source · **`streamwright validate`** checks one · **`streamwright connectors`** browses and installs connectors.
|
|
40
40
|
- A source is a folder — `source.yaml` (`spec`, `auth`, `http`) and one file per stream in `streams/` — or a single YAML file.
|
|
@@ -54,6 +54,7 @@ connectors yet. Add the ones you need on demand:
|
|
|
54
54
|
streamwright connectors list # browse the hub
|
|
55
55
|
streamwright connectors install files # a reader (files, s3, gcs, postgres)
|
|
56
56
|
streamwright connectors install google_ads # an API connector (pulls in its SDK)
|
|
57
|
+
streamwright connectors install google_ads meta_ads microsoft_ads # several at once (one pip install)
|
|
57
58
|
```
|
|
58
59
|
|
|
59
60
|
## Quickstart
|
|
@@ -38,6 +38,7 @@ src/streamwright/core/validation/__init__.py
|
|
|
38
38
|
src/streamwright/core/validation/engine.py
|
|
39
39
|
src/streamwright/core/validation/schema.py
|
|
40
40
|
src/streamwright/core/validation/source.py
|
|
41
|
+
tests/test_catalog.py
|
|
41
42
|
tests/test_connectors.py
|
|
42
43
|
tests/test_dlt_output.py
|
|
43
44
|
tests/test_exact_decimals.py
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""`streamwright connectors install KEY [KEY ...]` and search: resolution, consent and the single pip command."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from streamwright.core import catalog, cli
|
|
9
|
+
|
|
10
|
+
CATALOG = {
|
|
11
|
+
"files": {"title": "Files", "family": "readers", "summary": "Local files", "trust": "official",
|
|
12
|
+
"install": {"git": "git+https://example.test/sw.git@abc#subdirectory=connectors/readers/files"}},
|
|
13
|
+
"google_ads": {"title": "Google Ads", "family": "ads", "summary": "Google Ads reports", "trust": "official",
|
|
14
|
+
"install": {"git": "git+https://example.test/sw.git@abc#subdirectory=connectors/ads/google_ads"}},
|
|
15
|
+
"meta_ads": {"title": "Meta Ads", "family": "ads", "summary": "Meta Marketing insights", "trust": "official",
|
|
16
|
+
"install": {"pip": "streamwright-meta-ads>=0.1,<0.2"}},
|
|
17
|
+
"acme": {"title": "Acme", "family": "ads", "summary": "Acme reports", "trust": "community",
|
|
18
|
+
"install": {"git": "git+https://example.test/acme.git@def"}},
|
|
19
|
+
"imaged": {"title": "Imaged", "family": "readers", "summary": "An image", "trust": "official",
|
|
20
|
+
"install": {"image": "ghcr.io/org/imaged@sha256:" + "0" * 64}},
|
|
21
|
+
"empty": {"title": "Empty", "family": "readers", "summary": "No source", "trust": "official", "install": {}},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TTY(io.StringIO):
|
|
26
|
+
def __init__(self, tty):
|
|
27
|
+
super().__init__()
|
|
28
|
+
self.tty = tty
|
|
29
|
+
|
|
30
|
+
def isatty(self):
|
|
31
|
+
return self.tty
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def pip(monkeypatch):
|
|
36
|
+
"""The catalog above; records each pip command instead of running it."""
|
|
37
|
+
calls = []
|
|
38
|
+
monkeypatch.setattr(catalog, "connectors", lambda hub_url=None: CATALOG)
|
|
39
|
+
monkeypatch.setattr(catalog.subprocess, "call", lambda cmd: calls.append(cmd) or 0)
|
|
40
|
+
monkeypatch.setattr(sys, "stdin", TTY(False))
|
|
41
|
+
return calls
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def specs(cmd):
|
|
45
|
+
return cmd[cmd.index("install") + 1:]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_one_key_installs_its_source(pip):
|
|
49
|
+
assert catalog.install("files") == 0
|
|
50
|
+
assert [specs(cmd) for cmd in pip] == [[CATALOG["files"]["install"]["git"]]]
|
|
51
|
+
assert pip[0][:4] == [sys.executable, "-m", "pip", "install"]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_several_keys_install_with_one_pip_command_in_order_without_repeats(pip):
|
|
55
|
+
assert catalog.install_many(["google_ads", "meta_ads", "files", "google_ads"]) == 0
|
|
56
|
+
assert len(pip) == 1
|
|
57
|
+
assert specs(pip[0]) == [CATALOG["google_ads"]["install"]["git"], "streamwright-meta-ads>=0.1,<0.2",
|
|
58
|
+
CATALOG["files"]["install"]["git"]]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_an_unknown_key_installs_nothing(pip, capsys):
|
|
62
|
+
assert catalog.install_many(["google_ads", "nope", "nada"]) == 2
|
|
63
|
+
assert pip == []
|
|
64
|
+
assert "unknown connectors 'nope', 'nada'" in capsys.readouterr().err
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_a_connector_without_a_source_installs_nothing(pip, capsys):
|
|
68
|
+
assert catalog.install_many(["files", "empty"]) == 2
|
|
69
|
+
assert pip == []
|
|
70
|
+
assert "'empty' has no installable source" in capsys.readouterr().err
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_no_keys_is_an_error(pip, capsys):
|
|
74
|
+
assert catalog.install_many([]) == 2
|
|
75
|
+
assert pip == []
|
|
76
|
+
assert "at least one connector KEY" in capsys.readouterr().err
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_a_community_connector_is_refused_non_interactively_without_yes(pip, capsys):
|
|
80
|
+
assert catalog.install_many(["files", "acme"]) == 2
|
|
81
|
+
assert pip == []
|
|
82
|
+
assert "refusing to install community connector 'acme' non-interactively" in capsys.readouterr().err
|
|
83
|
+
assert catalog.install_many(["files", "acme"], assume_yes=True) == 0
|
|
84
|
+
assert specs(pip[0]) == [CATALOG["files"]["install"]["git"], CATALOG["acme"]["install"]["git"]]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_one_confirmation_covers_every_untrusted_connector(pip, monkeypatch, capsys):
|
|
88
|
+
monkeypatch.setattr(sys, "stdin", TTY(True))
|
|
89
|
+
answers = []
|
|
90
|
+
monkeypatch.setattr("builtins.input", lambda prompt: answers.append(prompt) or "n")
|
|
91
|
+
assert catalog.install_many(["acme", "files"]) == 1
|
|
92
|
+
assert pip == [] and len(answers) == 1
|
|
93
|
+
monkeypatch.setattr("builtins.input", lambda prompt: "y")
|
|
94
|
+
assert catalog.install_many(["acme", "files"]) == 0
|
|
95
|
+
assert len(pip) == 1
|
|
96
|
+
assert "acme (community): git: git+https://example.test/acme.git@def" in capsys.readouterr().err
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_images_are_reported_not_installed(pip, capsys):
|
|
100
|
+
assert catalog.install_many(["imaged"]) == 0
|
|
101
|
+
assert pip == []
|
|
102
|
+
assert catalog.install_many(["imaged", "files"]) == 0
|
|
103
|
+
assert specs(pip[0]) == [CATALOG["files"]["install"]["git"]]
|
|
104
|
+
assert "'imaged' is distributed as an image" in capsys.readouterr().out
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_search_matches_every_word(pip):
|
|
108
|
+
assert set(catalog.search("ads")) == {"google_ads", "meta_ads", "acme"}
|
|
109
|
+
assert set(catalog.search("google ads")) == {"google_ads"}
|
|
110
|
+
assert set(catalog.search("")) == set(CATALOG)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_cli_installs_several_connectors(pip):
|
|
114
|
+
assert cli.main(["connectors", "install", "google_ads", "meta_ads", "--yes"]) == 0
|
|
115
|
+
assert specs(pip[0]) == [CATALOG["google_ads"]["install"]["git"], "streamwright-meta-ads>=0.1,<0.2"]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_cli_install_needs_a_key(pip, capsys):
|
|
119
|
+
assert cli.main(["connectors", "install"]) == 2
|
|
120
|
+
assert pip == []
|
|
121
|
+
assert "at least one connector KEY" in capsys.readouterr().err
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_cli_search_joins_its_words(pip, capsys):
|
|
125
|
+
assert cli.main(["connectors", "search", "meta", "insights"]) == 0
|
|
126
|
+
out = capsys.readouterr().out
|
|
127
|
+
assert "meta_ads" in out and "google_ads" not in out
|
|
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
|