vgi-python 0.37.2__py3-none-any.whl → 0.38.0__py3-none-any.whl

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.
@@ -123,7 +123,7 @@ class RequiredAttachOptions:
123
123
 
124
124
  # No class-level assignment: there is no default to fall back on, so the
125
125
  # caller has to supply it.
126
- api_key: Annotated[str, AttachOption(desc="API key", required=True)]
126
+ api_key: Annotated[str, AttachOption(desc="API key", required=True, secret=True)]
127
127
  region: Annotated[str, AttachOption(desc="Region")] = "us-east-1"
128
128
 
129
129
 
@@ -10,7 +10,7 @@ holds the machinery both share; the two public modules subclass it so their
10
10
  names and behaviour are unchanged.
11
11
 
12
12
  A subclass may widen its wire format with additional columns — `AttachOption`
13
- adds ``required`` — via :meth:`_SpecBase._extra_row` /
13
+ adds ``required`` and ``secret`` — via :meth:`_SpecBase._extra_row` /
14
14
  :meth:`_SpecBase._extra_kwargs` and :meth:`_DescriptorBase.extra_spec_kwargs`.
15
15
  Because both directions read by column name, the widening is compatible in both
16
16
  directions: a reader ignores columns it doesn't know, and a reader whose peer
@@ -9,6 +9,14 @@ from session-level Settings resent on every call).
9
9
  The declaration mirrors ``vgi.catalog.setting.Setting`` — same Arrow IPC spec
10
10
  format, same Python type → Arrow mapping, same extractor shape — so both share
11
11
  the machinery in ``vgi.catalog._descriptor_spec``.
12
+
13
+ Credential options (API keys, tokens, passwords) MUST be declared
14
+ ``secret=True``. Clients and the DuckDB extension then mask the value, keep it
15
+ out of result-cache keys and logs, and can supply it from a ``vgi_attach``
16
+ DuckDB secret instead of the ``ATTACH`` text::
17
+
18
+ CREATE SECRET (TYPE vgi_attach, SCOPE 'https://worker.example.com', api_key '...');
19
+ ATTACH 'mydb' AS mydb (TYPE vgi, LOCATION 'https://worker.example.com');
12
20
  """
13
21
 
14
22
  from dataclasses import dataclass
@@ -42,18 +50,31 @@ class AttachOptionSpec(_SpecBase):
42
50
  rather than surfacing a failure that reads like an empty catalog.
43
51
  Mutually exclusive with ``default`` — an option that falls back to
44
52
  a value is by definition satisfiable without the caller.
45
- ARROW_SCHEMA: The shared four columns plus ``required``.
53
+ secret: The option carries a credential (an API key, token or
54
+ password). Clients and the DuckDB extension mask its value, keep it
55
+ out of result-cache keys and logs, and can supply it from a
56
+ ``vgi_attach`` DuckDB secret
57
+ (``CREATE SECRET (TYPE vgi_attach, SCOPE '<worker url>', api_key '...')``)
58
+ rather than the ``ATTACH`` text. Credential options MUST set this.
59
+ Compatible with ``required``. Allowed alongside a ``default``, but a
60
+ secret option should normally have none: a default credential is
61
+ advertised in plain text to every client at discovery.
62
+ ARROW_SCHEMA: The shared four columns plus ``required`` and ``secret``.
46
63
 
47
64
  """
48
65
 
49
66
  required: bool = False
67
+ secret: bool = False
50
68
 
51
69
  ARROW_SCHEMA: ClassVar[pa.Schema] = pa.schema(
52
70
  [
53
71
  *_SpecBase.ARROW_SCHEMA,
54
- # Nullable, and appended last: a peer that predates this column
72
+ # Nullable, and appended after the shared columns: a peer that predates this column
55
73
  # reads the batch by name and simply doesn't see it.
56
74
  pa.field("required", pa.bool_(), nullable=True),
75
+ # Same rules as ``required``: nullable, appended after it, and an
76
+ # absent column reads as False.
77
+ pa.field("secret", pa.bool_(), nullable=True),
57
78
  ]
58
79
  )
59
80
 
@@ -67,12 +88,16 @@ class AttachOptionSpec(_SpecBase):
67
88
  )
68
89
 
69
90
  def _extra_row(self) -> dict[str, Any]:
70
- return {"required": self.required}
91
+ return {"required": self.required, "secret": self.secret}
71
92
 
72
93
  @classmethod
73
94
  def _extra_kwargs(cls, row: dict[str, object]) -> dict[str, Any]:
74
- # Absent column (older peer) and explicit null both mean "not required".
75
- return {"required": bool(row.get("required") or False)}
95
+ # Absent column (older peer) and explicit null both mean "not required"
96
+ # / "not secret".
97
+ return {
98
+ "required": bool(row.get("required") or False),
99
+ "secret": bool(row.get("secret") or False),
100
+ }
76
101
 
77
102
 
78
103
  @dataclass
@@ -90,18 +115,26 @@ class AttachOption(_DescriptorBase):
90
115
 
91
116
  class AttachOptions:
92
117
  region: Annotated[str, AttachOption(desc="AWS region")] = "us-east-1"
93
- api_key: Annotated[str, AttachOption(desc="API key", required=True)]
118
+ api_key: Annotated[str, AttachOption(desc="API key", required=True, secret=True)]
119
+
120
+ Credential options (API keys, tokens, passwords) MUST be declared
121
+ ``secret=True``; see `AttachOptionSpec.secret`.
94
122
 
95
123
  Attributes:
96
124
  required: The caller must supply this option at ``ATTACH`` time.
125
+ secret: The option carries a credential. Clients and the DuckDB
126
+ extension mask it, keep it out of cache keys and logs, and can
127
+ supply it from a ``vgi_attach`` DuckDB secret. Should normally have
128
+ no default.
97
129
 
98
130
  """
99
131
 
100
132
  required: bool = False
133
+ secret: bool = False
101
134
 
102
135
  def extra_spec_kwargs(self) -> dict[str, Any]:
103
- """Carry ``required`` through to the `AttachOptionSpec`."""
104
- return {"required": self.required}
136
+ """Carry ``required`` and ``secret`` through to the `AttachOptionSpec`."""
137
+ return {"required": self.required, "secret": self.secret}
105
138
 
106
139
 
107
140
  class MissingAttachOptionsError(ValueError):
vgi/http/landing.html CHANGED
@@ -544,7 +544,8 @@ async function buildCatalog(info){
544
544
  name: s.name, type: typeStr(s.type),
545
545
  default: s.default == null ? '' : String(s.default),
546
546
  description: s.description || '',
547
- required: !!s.required
547
+ required: !!s.required,
548
+ secret: !!s.secret
548
549
  }));
549
550
  } catch (e) { /* keep the card; drop unreadable specs */ }
550
551
 
@@ -792,7 +793,8 @@ function attachOptionsHtml(c){
792
793
  '<table class="opts"><thead><tr><th>Option</th><th>Type</th><th>Default</th><th>Description</th></tr></thead><tbody>'+
793
794
  opts.map(o=>'<tr><td class="o-name">'+esc(o.name)+
794
795
  (o.required?'<span class="o-req" title="Must be supplied at ATTACH time">required</span>':'')+
795
- '</td><td class="o-type">'+esc(o.type)+'</td><td class="o-def">'+esc(o.default||'—')+'</td><td class="o-desc">'+esc(o.description||'')+'</td></tr>').join('')+
796
+ (o.secret?'<span class="o-req" title="A credential: supply it from a vgi_attach DuckDB secret rather than the ATTACH text">secret</span>':'')+
797
+ '</td><td class="o-type">'+esc(o.type)+'</td><td class="o-def">'+esc(o.secret&&o.default?'hidden':(o.default||'—'))+'</td><td class="o-desc">'+esc(o.description||'')+'</td></tr>').join('')+
796
798
  '</tbody></table></details>';
797
799
  }
798
800
  function renderConnect(){
vgi/http/worker_page.py CHANGED
@@ -584,10 +584,24 @@ def _build_attach_options_table(specs: tuple[AttachOptionSpec, ...]) -> str:
584
584
  "<table><tr><th>Name</th><th>Type</th><th>Default</th><th>Description</th></tr>",
585
585
  ]
586
586
  for spec in specs:
587
- default_str = _esc(repr(spec.default)) if spec.default is not None else "&mdash;"
587
+ if spec.default is None:
588
+ default_str = "&mdash;"
589
+ elif spec.secret:
590
+ # A credential's default is never echoed back onto the page.
591
+ default_str = "<em>hidden</em>"
592
+ else:
593
+ default_str = _esc(repr(spec.default))
588
594
  desc_str = _esc(spec.desc) if spec.desc else "&mdash;"
595
+ flags = ""
596
+ if spec.required:
597
+ flags += ' <span class="o-flag" title="Must be supplied at ATTACH time">required</span>'
598
+ if spec.secret:
599
+ flags += (
600
+ ' <span class="o-flag" title="A credential: supply it from a vgi_attach DuckDB secret '
601
+ 'rather than the ATTACH text">secret</span>'
602
+ )
589
603
  parts.append(
590
- f"<tr><td><code>{_esc(spec.name)}</code></td>"
604
+ f"<tr><td><code>{_esc(spec.name)}</code>{flags}</td>"
591
605
  f"<td><code>{_esc(str(spec.type))}</code></td>"
592
606
  f"<td>{default_str}</td>"
593
607
  f"<td>{desc_str}</td></tr>"
@@ -1230,6 +1244,9 @@ _PAGE_TEMPLATE = (
1230
1244
  font-weight: 600; border-bottom: 2px solid #e0dcd0; }}
1231
1245
  td {{ padding: 8px 10px; border-bottom: 1px solid #f0ece0; }}
1232
1246
  td code {{ font-size: 0.85em; }}
1247
+ .o-flag {{ font-size: 0.7em; font-weight: 600; text-transform: uppercase; margin-left: 4px;
1248
+ padding: 1px 5px; border-radius: 4px; background: #fdf0e3; color: #8a4b08;
1249
+ border: 1px solid #f0d5b4; }}
1233
1250
  .no-params {{ color: #6b6b5a; font-style: italic; font-size: 0.9em; }}
1234
1251
  .section-label {{ font-size: 0.8em; font-weight: 600; text-transform: uppercase;
1235
1252
  letter-spacing: 0.05em; color: #6b6b5a; margin-top: 14px;
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vgi-python
3
- Version: 0.37.2
3
+ Version: 0.38.0
4
4
  Summary: Vector Gateway Interface - Connect DuckDB to external programs via Apache Arrow
5
5
  Project-URL: Homepage, https://query.farm
6
6
  Project-URL: Repository, https://github.com/Query-farm/vgi-python
@@ -158,11 +158,11 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
158
158
  Classifier: Typing :: Typed
159
159
  Requires-Python: >=3.13
160
160
  Requires-Dist: click
161
- Requires-Dist: httpx2>=2.9.1
161
+ Requires-Dist: httpx2>=2.12.0
162
162
  Requires-Dist: platformdirs
163
163
  Requires-Dist: pyarrow
164
164
  Requires-Dist: typer>=0.9
165
- Requires-Dist: vgi-rpc>=0.47.0
165
+ Requires-Dist: vgi-rpc>=0.47.2
166
166
  Provides-Extra: azure
167
167
  Requires-Dist: azure-identity>=1.16.0; extra == 'azure'
168
168
  Requires-Dist: pymssql>=2.3.0; extra == 'azure'
@@ -235,6 +235,11 @@ Description-Content-Type: text/markdown
235
235
  <a href="LICENSE"><img src="https://img.shields.io/pypi/l/vgi-python" alt="License"></a>
236
236
  </p>
237
237
 
238
+ <p align="center">
239
+ <a href="https://youtu.be/J2E51PTZn6o"><img src="https://raw.githubusercontent.com/Query-farm/vgi-python/main/docs/assets/video-thumbnail.jpg" alt="Watch: VGI explained in 2½ minutes, on YouTube" width="640"></a><br>
240
+ <sub>▶ <a href="https://youtu.be/J2E51PTZn6o">Watch the 2½-minute overview</a></sub>
241
+ </p>
242
+
238
243
  ---
239
244
 
240
245
  ## See It in Action
@@ -39,7 +39,7 @@ vgi/table_in_out_function.py,sha256=F2QEtsbGEYHDVyQ4Cf9OCW7HlWXeqekAyx607foB-bs,
39
39
  vgi/worker.py,sha256=0ez46DE5tc1-78ll29lBP7KRKdDJnkOv89gdr4RjLrE,253453
40
40
  vgi/write_results.py,sha256=-OrUcz6E9ahhC_pnKYAwIMR2LpapwQafLC4IOnPTmec,2851
41
41
  vgi/_test_fixtures/__init__.py,sha256=xQq-QQLk8USQ6TZHsPiPcZldNNUdcJ2gToXMPGzScLI,444
42
- vgi/_test_fixtures/attach_options.py,sha256=ZNgNTNF1RymSfnU-uVIDTLvCpc283h6O8tZ1UuEa3Lc,12516
42
+ vgi/_test_fixtures/attach_options.py,sha256=FPsXGmhA9sU0tkgtHAlSO7kUQgqHsaDwtC8jYNXDHko,12529
43
43
  vgi/_test_fixtures/bad_enum.py,sha256=b8l-afmXgGR6iqgIUiVeqHu0C0F8Of8ahEmdQGtqd1g,2916
44
44
  vgi/_test_fixtures/bad_protocol.py,sha256=fPU7rpXEgBxRtPHfuCa0O2AHNRRbZDllLfydHsAKPEg,2992
45
45
  vgi/_test_fixtures/cancellable.py,sha256=hetTRLyT4kkazPNcxAdfzj0pYzEmegMksamJbR0w3Ho,12151
@@ -122,8 +122,8 @@ vgi/_test_fixtures/writable/generic.py,sha256=3UzNUly3DhDPz-Revq7GePi6pxbP3AZ7R0
122
122
  vgi/_test_fixtures/writable/table.py,sha256=j4hdzxva7XX_9MG68AJIdpLqC2SY9g-A6jy-eLL6Yzg,5735
123
123
  vgi/_test_fixtures/writable/worker.py,sha256=ti-YFqVdYTMPIvDC8iwvaZ_CYUFTIf9xod2gqc1ReBk,42956
124
124
  vgi/catalog/__init__.py,sha256=edc2wuvloEgY2WnnJNngV3xP0DN_p49SElYQanGzq4A,2629
125
- vgi/catalog/_descriptor_spec.py,sha256=fyeWOC3eN04UXDAOnxFeOS1VwGBWoBCMOzZfRYKPFH4,9773
126
- vgi/catalog/attach_option.py,sha256=4cgJZjTVCnFv8TrLYnfjy-XZLAx5ux4UisJRcBeXkfo,5735
125
+ vgi/catalog/_descriptor_spec.py,sha256=8F7QGHvs5uumkVqpWxgMZi_QuqobqTJwf2_xgyuPZh4,9788
126
+ vgi/catalog/attach_option.py,sha256=-f8a6MPOthTWGf_6cjOgnNwJtVH4rGdxLMOMduGSyxk,7641
127
127
  vgi/catalog/catalog_interface.py,sha256=-JdVTfkTOC2RYickktUiESF5UCuLVnPnLCUBQrTaN_4,158452
128
128
  vgi/catalog/descriptors.py,sha256=JvI0mqDy6DGmtnhZw-U2Z9ZROMjz4KejxGrw7XmNnGw,49362
129
129
  vgi/catalog/duckdb_statistics.py,sha256=Bzw4UGO-zdKrontaCcRV2palO8G3XLCWh-AhZozvQI8,16894
@@ -144,17 +144,17 @@ vgi/client/client.py,sha256=cvBQHzM2Z2lamC-fkBAvZD4HuRxB03aUkEKFVo_E9GU,167312
144
144
  vgi/client/errors.py,sha256=cSN65cyQMk3crwZCz1IdPErK3mtlEkTNpu-nuQUDOQE,1633
145
145
  vgi/http/__init__.py,sha256=hlOwOVcoZqmEKVGk7ganOdr5ryRSpEhLBF9sRD7BkYc,608
146
146
  vgi/http/demo_storage.py,sha256=830s-H61thozC3eEuqdnwCpYFfvoJRq9vjyuXa5OURo,8769
147
- vgi/http/landing.html,sha256=E3gRtGGYt9DIB1auhNogOOS6W3NdkLNRNxqn_L8OKHY,356356
147
+ vgi/http/landing.html,sha256=v7_5QRwjwVIX_WIKCx-dxZb4FoOF6_XN505__DyOuNw,356562
148
148
  vgi/http/landing_page.py,sha256=8dPnRWwao-Zn7SirkJ9KZoGVYFOj9G0kGCTEO0Yo2X4,4149
149
149
  vgi/http/vgi-client.js,sha256=dxJEzkpFp-XOAj1_z6inAhZyt2hYsjA9dI2US7k6r8I,560268
150
- vgi/http/worker_page.py,sha256=ILfIYLZZHS_rOE4y-EsYt14B0ytItiHLO5YtIbRCLUs,54872
150
+ vgi/http/worker_page.py,sha256=hQefuTk_P3O8IfrXY4QzWo8Q_06pjOQLtxjQPxUUCIs,55663
151
151
  vgi/transactor/__init__.py,sha256=Y3pHIQc5f6xy771J259oUZlAegklcHlIlTuinL3ENPQ,764
152
152
  vgi/transactor/_duckdb_compat.py,sha256=sXVZ9JLKAQyGR1BjWczSwdQEavtr-TcZPoVZZnTr3vQ,953
153
153
  vgi/transactor/client.py,sha256=7DTeMksogsw6ANjQjGOPpKYrV76rg4_kGjktMJf54jg,4486
154
154
  vgi/transactor/protocol.py,sha256=v59IvrKnuvwOXvn_HEcBAbCzDVcx0akgKN0R1mChXg0,5034
155
155
  vgi/transactor/server.py,sha256=nzsZQxJZdgappdYX8okrFIrjFtUbdM_Hhskl1fZ2nDY,32553
156
- vgi_python-0.37.2.dist-info/METADATA,sha256=fTpswGuWyykmygCUctNzcLQQfYdlu2sJ36Ol6UiS7F4,25617
157
- vgi_python-0.37.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
158
- vgi_python-0.37.2.dist-info/entry_points.txt,sha256=3Kz1vgodw3pOL_xjtSyDB55-ZRy-U2X-X_Bdr582x0Q,165
159
- vgi_python-0.37.2.dist-info/licenses/LICENSE,sha256=pbJb4zZasP6n5ifEV81wFu017TarjydaYVmGbHcehtY,6103
160
- vgi_python-0.37.2.dist-info/RECORD,,
156
+ vgi_python-0.38.0.dist-info/METADATA,sha256=BdmzjeWg-CTAbWvVahlskbm-aIC6Ky_-wpf-y9izq3w,25953
157
+ vgi_python-0.38.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
158
+ vgi_python-0.38.0.dist-info/entry_points.txt,sha256=3Kz1vgodw3pOL_xjtSyDB55-ZRy-U2X-X_Bdr582x0Q,165
159
+ vgi_python-0.38.0.dist-info/licenses/LICENSE,sha256=pbJb4zZasP6n5ifEV81wFu017TarjydaYVmGbHcehtY,6103
160
+ vgi_python-0.38.0.dist-info/RECORD,,