vgi-python 0.44.0__py3-none-any.whl → 0.45.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.
@@ -193,7 +193,7 @@ def main() -> None:
193
193
  "--identity",
194
194
  envvar="VGI_FIXTURE_IDENTITY",
195
195
  help=(
196
- "Host vgi_rpc.Identity.v1 with the vgi-rpc conformance fixture policy and its "
196
+ "Host vgi_rpc.Identity.v2 with the vgi-rpc conformance fixture policy and its "
197
197
  "spoofable X-Conformance-Principal header authentication. Test-only; for "
198
198
  "vgi-rpc-test-hosted --identity."
199
199
  ),
@@ -1,6 +1,6 @@
1
1
  # Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
2
 
3
- """Opt the HTTP fixture into ``vgi_rpc.Identity.v1`` with the conformance policy.
3
+ """Opt the HTTP fixture into ``vgi_rpc.Identity.v2`` with the conformance policy.
4
4
 
5
5
  Enabled by ``vgi-fixture-http --identity`` (or ``VGI_FIXTURE_IDENTITY=1``) so
6
6
  the cross-SDK hosted-protocols group can run its identity cases with
vgi/attach_ticket.py CHANGED
@@ -3,7 +3,7 @@
3
3
  r"""Attach tickets: a user's ATTACH, sealed so a runner can replay it later as that user.
4
4
 
5
5
  A ticket is the *what* half of an unattended session; a sealed grant
6
- (``vgi_rpc.Identity.v1`` ``issue_grant``) is the *who*. While the user is
6
+ (``vgi_rpc.Identity.v2`` ``issue_grant``) is the *who*. While the user is
7
7
  attached and logged in, the client asks the worker to seal the options it
8
8
  attached with -- secret ones included -- into a ticket only this worker can
9
9
  open. Later any runner holding the user's grant attaches with the single option
@@ -43,7 +43,6 @@ from __future__ import annotations
43
43
 
44
44
  import base64
45
45
  import dataclasses
46
- import math
47
46
  import os
48
47
  import re
49
48
  import secrets
@@ -51,6 +50,7 @@ import struct
51
50
  import sys
52
51
  import time
53
52
  from collections.abc import Mapping, Sequence
53
+ from datetime import UTC, datetime
54
54
  from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Protocol
55
55
 
56
56
  import pyarrow as pa
@@ -553,13 +553,20 @@ class AttachTicket(ArrowSerializableDataclass):
553
553
 
554
554
  Attributes:
555
555
  ticket: ``vgia1.`` text. Not a credential, but never logged.
556
- expires_at: Unix seconds after which the worker refuses it; ``+inf``
557
- when the worker sets no maximum lifetime.
556
+ expires_at: The UTC instant after which the worker refuses it, carried
557
+ as ``timestamp[us, tz="UTC"]``; ``None`` (null on the wire) when
558
+ the worker sets no maximum lifetime. Protocol ``1.0.0`` carried a
559
+ float64 of unix seconds with ``+inf`` for "no expiry".
558
560
 
559
561
  """
560
562
 
561
563
  ticket: str
562
- expires_at: float
564
+ expires_at: Annotated[datetime | None, ArrowType(pa.timestamp("us", tz="UTC"))]
565
+
566
+ def __post_init__(self) -> None:
567
+ """Refuse a naive ``expires_at``, which has no defined instant."""
568
+ if self.expires_at is not None and (self.expires_at.tzinfo is None or self.expires_at.utcoffset() is None):
569
+ raise ValueError("AttachTicket.expires_at must be timezone-aware (UTC) or None")
563
570
 
564
571
 
565
572
  class AttachTickets(Protocol):
@@ -571,7 +578,7 @@ class AttachTickets(Protocol):
571
578
  """
572
579
 
573
580
  protocol_name: ClassVar[str] = ATTACH_TICKETS_PROTOCOL_NAME
574
- protocol_version: ClassVar[str] = "1.0.0"
581
+ protocol_version: ClassVar[str] = "2.0.0"
575
582
 
576
583
  def seal_attach(self, request: SealAttachRequest) -> AttachTicket:
577
584
  """Seal the caller's attach of ``request.catalog_name`` into a ticket.
@@ -730,4 +737,6 @@ class AttachTicketsImpl:
730
737
  )
731
738
  except ValueError as exc:
732
739
  raise _invalid_request("seal_attach request is invalid", [("request", str(exc))]) from exc
733
- return AttachTicket(ticket=token, expires_at=math.inf if expires_at == 0 else float(expires_at))
740
+ return AttachTicket(
741
+ ticket=token, expires_at=None if expires_at == 0 else datetime.fromtimestamp(expires_at, tz=UTC)
742
+ )
vgi/auth.py CHANGED
@@ -24,7 +24,7 @@ from typing import TYPE_CHECKING, Any
24
24
 
25
25
  from vgi_rpc.rpc import AuthContext, CallContext
26
26
 
27
- # ``vgi_rpc.Identity.v1`` lives at the RPC layer as of vgi-rpc 0.46.0 -- it was
27
+ # ``vgi_rpc.Identity.v2`` lives at the RPC layer as of vgi-rpc 0.46.0 -- it was
28
28
  # an HTTP JSON route (``POST {prefix}/__introspect_token__``) through 0.45.x,
29
29
  # which meant it existed on exactly one transport. So this is no longer gated
30
30
  # on the ``http`` extra. Still not re-exported by ``vgi_rpc.rpc`` itself, so
vgi/meta_worker.py CHANGED
@@ -768,7 +768,7 @@ class MetaWorker:
768
768
  if len(impls) > 1:
769
769
  owners = ", ".join(sorted(owner for _, owner in impls.values()))
770
770
  raise ValueError(
771
- f"MetaWorker children {owners} each implement {hook}(). vgi_rpc.Identity.v1 is "
771
+ f"MetaWorker children {owners} each implement {hook}(). vgi_rpc.Identity.v2 is "
772
772
  f"hosted once per process, so at most one child may implement it."
773
773
  )
774
774
  return next(iter(impls.values()))[0] if impls else None
vgi/rpc_server.py CHANGED
@@ -16,7 +16,7 @@ What a server hosts, in reflection order:
16
16
  the order returned — on **every** transport.
17
17
  3. ``vgi_rpc.Reflection.v1`` — on every transport unless ``describe=False``
18
18
  (an HTTP operator choice, ``--no-describe``).
19
- 4. ``vgi_rpc.Identity.v1`` — HTTP only, and only when the worker overrides
19
+ 4. ``vgi_rpc.Identity.v2`` — HTTP only, and only when the worker overrides
20
20
  ``resolve_token`` and/or ``mint_grant`` (or grant keys are configured).
21
21
  5. ``vgi.attach_tickets.v1`` — HTTP only, and only when ``VGI_SIGNING_KEY`` is
22
22
  configured explicitly *and* the worker can issue grants. See
@@ -57,7 +57,7 @@ here rather than at each call site.
57
57
  _TRANSPORTS: frozenset[str] = frozenset({"pipe", "unix", "tcp", "iroh", "http"})
58
58
 
59
59
  #: Transports that authenticate their callers, and so may host
60
- #: ``vgi_rpc.Identity.v1``. Identity answers questions about *other*
60
+ #: ``vgi_rpc.Identity.v2``. Identity answers questions about *other*
61
61
  #: callers' credentials; on a transport with no caller identity there is no
62
62
  #: allowlist to enforce, so it is not hosted there.
63
63
  _IDENTITY_TRANSPORTS: frozenset[str] = frozenset({"http"})
@@ -188,7 +188,7 @@ def _validated_hosted_protocols(worker: Worker | MetaWorker, primary: type) -> t
188
188
  raise ValueError(
189
189
  f"{owner} entry {index} ({proto.__name__}) is named {name!r}, which claims the reserved "
190
190
  f"{RESERVED_PROTOCOL_PREFIX!r} prefix. Framework protocols are not supplied through this "
191
- f"hook: reflection is hosted automatically, and vgi_rpc.Identity.v1 is enabled by "
191
+ f"hook: reflection is hosted automatically, and vgi_rpc.Identity.v2 is enabled by "
192
192
  f"overriding resolve_token() and/or mint_grant()."
193
193
  )
194
194
  try:
@@ -216,7 +216,7 @@ def _build_identity(
216
216
  introspect_principals: Iterable[str] | None,
217
217
  grant_keys: GrantKeys | None = None,
218
218
  ) -> IdentityImpl | None:
219
- """Build the ``vgi_rpc.Identity.v1`` implementation, or ``None``.
219
+ """Build the ``vgi_rpc.Identity.v2`` implementation, or ``None``.
220
220
 
221
221
  ``None`` unless the worker class implements at least one of the two
222
222
  methods, and that is the point: ``RpcServer`` then does not host the
@@ -256,7 +256,7 @@ def _build_identity(
256
256
  return None
257
257
 
258
258
  # Not re-exported by ``vgi_rpc.rpc``, so the private module is the only
259
- # import path for the ``vgi_rpc.Identity.v1`` implementation helper.
259
+ # import path for the ``vgi_rpc.Identity.v2`` implementation helper.
260
260
  from vgi_rpc.rpc._token_identity import IdentityImpl
261
261
 
262
262
  return IdentityImpl(
@@ -326,7 +326,7 @@ def _resolve_introspect_principals(explicit: Iterable[str] | None) -> list[str]:
326
326
  if not principals:
327
327
  sys.stderr.write(
328
328
  "Error: this worker implements resolve_token(), which hosts the\n"
329
- " vgi_rpc.Identity.v1 protocol, but no introspector allowlist was\n"
329
+ " vgi_rpc.Identity.v2 protocol, but no introspector allowlist was\n"
330
330
  " configured. Set VGI_INTROSPECT_PRINCIPALS (comma-separated) or\n"
331
331
  " pass --introspect-principals.\n"
332
332
  "\n"
vgi/serve.py CHANGED
@@ -276,7 +276,7 @@ def create_app(
276
276
  escape, because bytes already uploaded cannot be un-uploaded.
277
277
  ``None`` (the default) means no cap.
278
278
  introspect_principals: Principals permitted to call
279
- ``vgi_rpc.Identity.v1``'s ``introspect_token``. Only consulted
279
+ ``vgi_rpc.Identity.v2``'s ``introspect_token``. Only consulted
280
280
  when the worker class overrides ``resolve_token``. ``None`` reads
281
281
  ``VGI_INTROSPECT_PRINCIPALS``.
282
282
  grant_keys: Sealed-grant configuration. ``None`` reads
vgi/worker.py CHANGED
@@ -1540,7 +1540,7 @@ class Worker:
1540
1540
  Each protocol needs a distinct wire name (``protocol_name:
1541
1541
  ClassVar[str]``). Names may not repeat, may not equal the worker's own
1542
1542
  protocol, and may not use the reserved ``vgi_rpc.`` prefix: reflection
1543
- is hosted automatically and ``vgi_rpc.Identity.v1`` is enabled by
1543
+ is hosted automatically and ``vgi_rpc.Identity.v2`` is enabled by
1544
1544
  overriding [`resolve_token`][vgi.worker.Worker.resolve_token] /
1545
1545
  [`mint_grant`][vgi.worker.Worker.mint_grant]. Violations are a startup
1546
1546
  error naming this method.
@@ -1558,7 +1558,7 @@ class Worker:
1558
1558
  def resolve_token(cls, token: str) -> TokenIdentity | None:
1559
1559
  """Resolve an opaque bearer credential to the identity it authenticates as.
1560
1560
 
1561
- Override to host ``vgi_rpc.Identity.v1``'s ``introspect_token``,
1561
+ Override to host ``vgi_rpc.Identity.v2``'s ``introspect_token``,
1562
1562
  which a reverse proxy calls when it terminates the only public
1563
1563
  listener and must know *which principal* a credential is before it can
1564
1564
  authorize anything. Until it is overridden the protocol is not hosted
@@ -1625,7 +1625,7 @@ class Worker:
1625
1625
  def mint_grant(cls, principal: str, purpose: str, scopes: list[str], ttl_seconds: int) -> IssuedGrant:
1626
1626
  """Mint a standing delegation credential for *principal*.
1627
1627
 
1628
- Override to host ``vgi_rpc.Identity.v1``'s ``issue_grant``. Like
1628
+ Override to host ``vgi_rpc.Identity.v2``'s ``issue_grant``. Like
1629
1629
  ``resolve_token``, the method is absent until it is overridden rather
1630
1630
  than hosted-and-refusing, so no worker grows a credential *issuer* by
1631
1631
  upgrading a dependency.
@@ -1668,7 +1668,9 @@ class Worker:
1668
1668
  never issue a longer one.
1669
1669
 
1670
1670
  Returns:
1671
- The minted ``IssuedGrant``. ``expires_at`` is a declaration rather
1671
+ The minted ``IssuedGrant``. ``expires_at`` is a timezone-aware UTC
1672
+ ``datetime`` (``timestamp[us, tz="UTC"]`` on the wire, under
1673
+ ``vgi_rpc.Identity.v2``), and a declaration rather
1672
1674
  than an enforcement: the real lifetime lives inside the opaque
1673
1675
  token, so a worker that states one has thought about one.
1674
1676
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vgi-python
3
- Version: 0.44.0
3
+ Version: 0.45.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
@@ -162,7 +162,7 @@ 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.51.1
165
+ Requires-Dist: vgi-rpc>=0.52.0
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'
@@ -6,8 +6,8 @@ vgi/aggregate_function.py,sha256=NYxudbvQ9ZLkP2w5hRdd4YgsGUZ_0cVfqqXnlmJPfls,273
6
6
  vgi/argument_spec.py,sha256=SADH97VlFu3asI8CWZ_I7XTuSxwqFciFgdupdAf-tkY,34231
7
7
  vgi/arguments.py,sha256=A_k9WLNJbNZD6E8AZt7lTjdlKkcXZA9_2-CTwS6ATtE,72633
8
8
  vgi/attach_header.py,sha256=sxpzCOVa5fl_-kQSCZgeUxS5I9Kuv8VCUjAOF5s1pr0,3782
9
- vgi/attach_ticket.py,sha256=MB0QV4FUYMHhnNIFzRo4zp4_mKJoezu06G8ZwgrgQzo,27623
10
- vgi/auth.py,sha256=qMhaLlQfn6F_ovMg4vwD8fl55THalWrSQ9A_V8arGDU,5195
9
+ vgi/attach_ticket.py,sha256=mqrtrwzRQnCBFQJkNPadxkLYnzfYo9P9Y48c-I6yhUE,28243
10
+ vgi/auth.py,sha256=KRGFQjYKes4-_kG6jWVIOrMUb9R3_d7ZHGCmVbK0So4,5195
11
11
  vgi/cache_control.py,sha256=IyBp9gZTCc4Y-4V7Rs05_o5lSEjbR2jt6cddXPU0dKw,7812
12
12
  vgi/copy_from_function.py,sha256=iSkR48b3W9IVQ-HY2I8-5W1QjTtWF7k1GkogHNE7ZMM,7114
13
13
  vgi/copy_to_function.py,sha256=8l0v_DyEF1EnFwinStrZDQ-yNFfGJsv3Mr5o2jG30f0,9279
@@ -20,26 +20,26 @@ vgi/function_storage_azure_sql.py,sha256=lALZK6FBU-dW4g-PEMtw-zysEfK1o_NNvTX3r6Y
20
20
  vgi/function_storage_cf_do.py,sha256=s5LRBLZPC6XOhLkKiXiF_OhW8GiN5zsZesb6rR_g6wY,29425
21
21
  vgi/invocation.py,sha256=GQ82v0TlryS0yNfhDjzBi5vNpRo1ApIYO0GwkUSxx_U,5713
22
22
  vgi/logging_config.py,sha256=KlrcX2psl9AZ3F-f8MeVSSr3OVQnoxCGdG2NhSdlQnc,12539
23
- vgi/meta_worker.py,sha256=jCourpEB__KsGxFwoIlMb8GogILbO-0RaSMBxTEmjoM,36277
23
+ vgi/meta_worker.py,sha256=7AAFLet2Bi3e5JqwtfsCpBp3a0g3ppf87yD_sdaUa2E,36277
24
24
  vgi/metadata.py,sha256=M8KZObX1wwCr2lPjgDL0-79gPbeM_NSOIqYCIAAck5M,74719
25
25
  vgi/otel.py,sha256=JlUDnk2c0UXSo_j1PJdS67Y--t55sXPg054ADuvHANc,15452
26
26
  vgi/profiling.py,sha256=rK7vij3oHhXC3JHI2BsMySsXh7_hrXi0aXpCW8iFSIc,3671
27
27
  vgi/protocol.py,sha256=J9vaxstGvxe4Vmlf_Aij9HkabGbdFx_nEsQI_m-V7ak,175438
28
28
  vgi/protocol_version.txt,sha256=yJuHwmLp1f_SDnOqx0oXcq3Mn1ohaCzDOtz1CG5f71w,6
29
29
  vgi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- vgi/rpc_server.py,sha256=C2rux8D50TkTn4DpU2-HlZRg3pB1ujPvTdYlxjOKiyo,16678
30
+ vgi/rpc_server.py,sha256=na6mheP80PFYt6qIPmi9NcMhotLcoTxZfqk3ZfNn1Sk,16678
31
31
  vgi/scalar_function.py,sha256=yTPrh7FQni6F6pEanQn70OiXHL2EREWUjliqKWyN7mM,53069
32
32
  vgi/schema_path.py,sha256=GVlkZlCUj7GbVhgiocSMC6Ff5iTJhKqiYE62hMIEKiI,2403
33
33
  vgi/schema_utils.py,sha256=zQCliUgIpO2u77Mhtgg3OFvOEDNgJopu6kj2fw8YP1I,7945
34
34
  vgi/secret_protocol.py,sha256=iCDl7nF7Nb4cJV4vpfbldunTDqDJWzCzS7uCmOACf7U,7151
35
35
  vgi/secret_service.py,sha256=r7qCzfnOMirf9wSCUe-mgFe3z6v9_cDRzixuSAZG_bs,8973
36
- vgi/serve.py,sha256=t4K80p05PCJlLirLdTg5y-bRsnjRdpihqTu-7fBh38U,64641
36
+ vgi/serve.py,sha256=2Xcw3Fj6mV2dcbDzpoW3PmlbjuBB106pJx32LD7R5bQ,64641
37
37
  vgi/split_token.py,sha256=tutcksj-hgWrLY7UW3sSqkyoaD-cMqNTOeff40MGl_g,10850
38
38
  vgi/table_buffering_function.py,sha256=SI6yLAcs8WBHqS6TeaZ92s-Q6GXn0ReROyQWDujw2iA,19554
39
39
  vgi/table_filter_pushdown.py,sha256=4Qo4OAOLjTYQ5pinNTOjLCDnb96OHqpcxJlbSJo3cJU,67144
40
40
  vgi/table_function.py,sha256=V_5qdMUQGoORiwF_xj0VhrnlytbUs5mdU7uhA_t8BYM,73638
41
41
  vgi/table_in_out_function.py,sha256=IIb9dOW6Dw74PQYg3cQQH5_iDdGLek8cAWZYgvwOu8c,19416
42
- vgi/worker.py,sha256=j_DNZ6YCu51P0Ibs-iPoF0NKfy0RwmnlLXosJaO037I,264903
42
+ vgi/worker.py,sha256=d9fSt34TkVHcakvKEglR8pswwKwBsUqj-ICbSthDQnY,265039
43
43
  vgi/write_results.py,sha256=-OrUcz6E9ahhC_pnKYAwIMR2LpapwQafLC4IOnPTmec,2851
44
44
  vgi/_test_fixtures/__init__.py,sha256=xQq-QQLk8USQ6TZHsPiPcZldNNUdcJ2gToXMPGzScLI,444
45
45
  vgi/_test_fixtures/attach_options.py,sha256=FPsXGmhA9sU0tkgtHAlSO7kUQgqHsaDwtC8jYNXDHko,12529
@@ -53,8 +53,8 @@ vgi/_test_fixtures/companion.py,sha256=Ya_rg2J4tXOYC7zBFOjTTh-4AYuRhJN5BT4tVZULQ
53
53
  vgi/_test_fixtures/copy_from.py,sha256=YAWametIhrKE_K_kRwx37QpKMY0yBWAordE5-cNVn4M,6134
54
54
  vgi/_test_fixtures/copy_to.py,sha256=fFHxPYVd0wfAfMEvpeBRijqUasBeoAGREY3kHmm_oK8,9667
55
55
  vgi/_test_fixtures/global_functions.py,sha256=G1h0w5o6bHMULDFLZuNjrZVrBbO6Cre6kTqTnQwTFmE,11251
56
- vgi/_test_fixtures/http_server.py,sha256=uLaNEFuh3iMud6VTLcnXz-pkV5xJ09wC6K2zkBEZ8eM,22008
57
- vgi/_test_fixtures/identity_fixture.py,sha256=wNTON3wpOGwH7HS9oCPb4Iav7GMYW0NlhtlZF1TL7aY,3318
56
+ vgi/_test_fixtures/http_server.py,sha256=23zmh3vObwHuFUgu--gTdacfufghZLQ_IYen-d-CY6k,22008
57
+ vgi/_test_fixtures/identity_fixture.py,sha256=bMZkYY4tLWceuFmYidfHZaexIcHzPKwxrrqE82z3XO4,3318
58
58
  vgi/_test_fixtures/nest_tensor.py,sha256=UjjW9UwipYG3ehQpZOm4hdzsafMyvVc0ZKJCrqiZTPU,24471
59
59
  vgi/_test_fixtures/orchard_catalog.py,sha256=8Uv-mKTIgl-OYU9JLgG23owlHGmgniyF2chPoKXGtuI,1643
60
60
  vgi/_test_fixtures/secret_cache.py,sha256=su4a90I-YHTfsmVPoJGyycTkEzT7GItreYZEfbXMp2o,9552
@@ -160,8 +160,8 @@ vgi/transactor/_duckdb_compat.py,sha256=sXVZ9JLKAQyGR1BjWczSwdQEavtr-TcZPoVZZnTr
160
160
  vgi/transactor/client.py,sha256=7DTeMksogsw6ANjQjGOPpKYrV76rg4_kGjktMJf54jg,4486
161
161
  vgi/transactor/protocol.py,sha256=v59IvrKnuvwOXvn_HEcBAbCzDVcx0akgKN0R1mChXg0,5034
162
162
  vgi/transactor/server.py,sha256=fJi72oaS8PiOI3rFW0MdetGBFEzFBkLdlDAMmbB50Uw,32663
163
- vgi_python-0.44.0.dist-info/METADATA,sha256=bAIZI0MpSUx1Y9v-UNNqzxhBvs6FTDK3sDBiAeYVVp0,25953
164
- vgi_python-0.44.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
165
- vgi_python-0.44.0.dist-info/entry_points.txt,sha256=3Kz1vgodw3pOL_xjtSyDB55-ZRy-U2X-X_Bdr582x0Q,165
166
- vgi_python-0.44.0.dist-info/licenses/LICENSE,sha256=pbJb4zZasP6n5ifEV81wFu017TarjydaYVmGbHcehtY,6103
167
- vgi_python-0.44.0.dist-info/RECORD,,
163
+ vgi_python-0.45.0.dist-info/METADATA,sha256=KMk45Pz3EhQ27X54ueT6sNxoGT4Pb-tvnfCRoDnHslU,25953
164
+ vgi_python-0.45.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
165
+ vgi_python-0.45.0.dist-info/entry_points.txt,sha256=3Kz1vgodw3pOL_xjtSyDB55-ZRy-U2X-X_Bdr582x0Q,165
166
+ vgi_python-0.45.0.dist-info/licenses/LICENSE,sha256=pbJb4zZasP6n5ifEV81wFu017TarjydaYVmGbHcehtY,6103
167
+ vgi_python-0.45.0.dist-info/RECORD,,