stores 0.1.7.dev6__py3-none-any.whl → 0.1.8.dev2__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.
- stores/format.py +15 -9
- stores/indexes/base_index.py +12 -9
- stores/indexes/remote_index.py +7 -2
- stores/indexes/venv_utils.py +140 -116
- {stores-0.1.7.dev6.dist-info → stores-0.1.8.dev2.dist-info}/METADATA +1 -1
- {stores-0.1.7.dev6.dist-info → stores-0.1.8.dev2.dist-info}/RECORD +8 -8
- {stores-0.1.7.dev6.dist-info → stores-0.1.8.dev2.dist-info}/WHEEL +0 -0
- {stores-0.1.7.dev6.dist-info → stores-0.1.8.dev2.dist-info}/licenses/LICENSE +0 -0
stores/format.py
CHANGED
@@ -61,7 +61,7 @@ def get_type_repr(typ: Type | GenericAlias) -> list[str]:
|
|
61
61
|
return [type_mappings[typ.__name__]]
|
62
62
|
|
63
63
|
|
64
|
-
def get_type_schema(typ: Type | GenericAlias):
|
64
|
+
def get_type_schema(typ: Type | GenericAlias, provider: ProviderFormat):
|
65
65
|
origin = get_origin(typ)
|
66
66
|
args = get_args(typ)
|
67
67
|
|
@@ -77,24 +77,27 @@ def get_type_schema(typ: Type | GenericAlias):
|
|
77
77
|
schema["enum"] = [v.value for v in typ]
|
78
78
|
elif isinstance(typ, type) and typ.__class__.__name__ == "_TypedDictMeta":
|
79
79
|
hints = get_type_hints(typ)
|
80
|
-
schema["properties"] = {
|
81
|
-
|
80
|
+
schema["properties"] = {
|
81
|
+
k: get_type_schema(v, provider) for k, v in hints.items()
|
82
|
+
}
|
83
|
+
if provider != ProviderFormat.GOOGLE_GEMINI:
|
84
|
+
schema["additionalProperties"] = False
|
82
85
|
schema["required"] = list(hints.keys())
|
83
86
|
elif origin in (list, List) or typ is dict:
|
84
87
|
if args:
|
85
|
-
schema["items"] = get_type_schema(args[0])
|
88
|
+
schema["items"] = get_type_schema(args[0], provider)
|
86
89
|
else:
|
87
90
|
raise TypeError("Insufficient argument type information")
|
88
91
|
elif origin in (dict, Dict) or typ is dict:
|
89
92
|
raise TypeError("Insufficient argument type information")
|
90
93
|
elif origin in (tuple, Tuple) or typ is tuple:
|
91
94
|
if args:
|
92
|
-
schema["items"] = get_type_schema(args[0])
|
95
|
+
schema["items"] = get_type_schema(args[0], provider)
|
93
96
|
else:
|
94
97
|
raise TypeError("Insufficient argument type information")
|
95
98
|
elif origin is Union or origin is T.UnionType:
|
96
99
|
for arg in args:
|
97
|
-
subschema = get_type_schema(arg)
|
100
|
+
subschema = get_type_schema(arg, provider)
|
98
101
|
del subschema["type"]
|
99
102
|
schema = {
|
100
103
|
**schema,
|
@@ -103,14 +106,17 @@ def get_type_schema(typ: Type | GenericAlias):
|
|
103
106
|
|
104
107
|
# Un-nest single member type lists since Gemini does not accept list of types
|
105
108
|
# Optional for OpenAI or Anthropic
|
106
|
-
if schema["type"]
|
107
|
-
schema["type"]
|
109
|
+
if schema["type"]:
|
110
|
+
if len(schema["type"]) == 1:
|
111
|
+
schema["type"] = schema["type"][0]
|
112
|
+
elif len(schema["type"]) > 1 and provider == ProviderFormat.GOOGLE_GEMINI:
|
113
|
+
schema["type"] = schema["type"][0]
|
108
114
|
|
109
115
|
return schema
|
110
116
|
|
111
117
|
|
112
118
|
def get_param_schema(param: inspect.Parameter, provider: ProviderFormat):
|
113
|
-
param_schema = get_type_schema(param.annotation)
|
119
|
+
param_schema = get_type_schema(param.annotation, provider)
|
114
120
|
|
115
121
|
if param_schema["type"] is None:
|
116
122
|
raise TypeError(f"Unsupported type: {param.annotation.__name__}")
|
stores/indexes/base_index.py
CHANGED
@@ -9,7 +9,6 @@ from typing import (
|
|
9
9
|
Callable,
|
10
10
|
List,
|
11
11
|
Literal,
|
12
|
-
Optional,
|
13
12
|
Tuple,
|
14
13
|
Union,
|
15
14
|
get_args,
|
@@ -98,7 +97,7 @@ def _handle_non_string_literal(annotation: type):
|
|
98
97
|
return list[new_annotation], {"item": literal_map}
|
99
98
|
if origin is Union or origin is UnionType:
|
100
99
|
union_literal_maps = {}
|
101
|
-
argtype_args = [a for a in get_args(annotation)
|
100
|
+
argtype_args = [a for a in get_args(annotation)]
|
102
101
|
new_union, literal_map = _handle_non_string_literal(argtype_args[0])
|
103
102
|
union_literal_maps[new_union.__name__] = literal_map
|
104
103
|
for child_argtype in argtype_args[1:]:
|
@@ -198,12 +197,12 @@ def wrap_tool(tool: Callable):
|
|
198
197
|
# Process args with default values: make sure type includes None
|
199
198
|
new_annotation = argtype
|
200
199
|
if new_annotation is Parameter.empty:
|
201
|
-
new_annotation =
|
200
|
+
new_annotation = type(new_arg.default) | None
|
202
201
|
origin = get_origin(new_annotation)
|
203
202
|
if origin not in [Union, UnionType] or NoneType not in get_args(
|
204
203
|
new_annotation
|
205
204
|
):
|
206
|
-
new_annotation =
|
205
|
+
new_annotation = new_annotation | None
|
207
206
|
new_arg = new_arg.replace(
|
208
207
|
default=None,
|
209
208
|
kind=Parameter.POSITIONAL_OR_KEYWORD,
|
@@ -402,13 +401,17 @@ class BaseIndex:
|
|
402
401
|
# Handle sync
|
403
402
|
yield tool_fn(**kwargs)
|
404
403
|
|
405
|
-
def parse_and_execute(self, msg: str):
|
404
|
+
def parse_and_execute(self, msg: str, collect_results=False):
|
406
405
|
toolcall = llm_parse_json(msg, keys=["toolname", "kwargs"])
|
407
|
-
return self.execute(
|
406
|
+
return self.execute(
|
407
|
+
toolcall.get("toolname"), toolcall.get("kwargs"), collect_results
|
408
|
+
)
|
408
409
|
|
409
|
-
async def
|
410
|
+
async def aparse_and_execute(self, msg: str, collect_results=False):
|
410
411
|
toolcall = llm_parse_json(msg, keys=["toolname", "kwargs"])
|
411
|
-
return await self.aexecute(
|
412
|
+
return await self.aexecute(
|
413
|
+
toolcall.get("toolname"), toolcall.get("kwargs"), collect_results
|
414
|
+
)
|
412
415
|
|
413
416
|
def stream_parse_and_execute(self, msg: str):
|
414
417
|
toolcall = llm_parse_json(msg, keys=["toolname", "kwargs"])
|
@@ -416,7 +419,7 @@ class BaseIndex:
|
|
416
419
|
|
417
420
|
async def astream_parse_and_execute(self, msg: str):
|
418
421
|
toolcall = llm_parse_json(msg, keys=["toolname", "kwargs"])
|
419
|
-
async for value in self.
|
422
|
+
async for value in self.astream_execute(
|
420
423
|
toolcall.get("toolname"), toolcall.get("kwargs")
|
421
424
|
):
|
422
425
|
yield value
|
stores/indexes/remote_index.py
CHANGED
@@ -8,7 +8,7 @@ from pathlib import Path
|
|
8
8
|
from typing import Optional
|
9
9
|
|
10
10
|
import requests
|
11
|
-
from git import Repo
|
11
|
+
from git import GitCommandError, Repo
|
12
12
|
|
13
13
|
from stores.constants import VENV_NAME
|
14
14
|
from stores.indexes.base_index import BaseIndex
|
@@ -44,6 +44,8 @@ def lookup_index(index_id: str, index_version: str | None = None):
|
|
44
44
|
)
|
45
45
|
if response.ok:
|
46
46
|
return response.json()
|
47
|
+
else:
|
48
|
+
raise ValueError(f"Index {index_id} not found in database")
|
47
49
|
|
48
50
|
|
49
51
|
class RemoteIndex(BaseIndex):
|
@@ -88,7 +90,10 @@ class RemoteIndex(BaseIndex):
|
|
88
90
|
if not repo_url:
|
89
91
|
# Otherwise, assume index references a GitHub repo
|
90
92
|
repo_url = f"https://github.com/{index_id}.git"
|
91
|
-
|
93
|
+
try:
|
94
|
+
repo = Repo.clone_from(repo_url, self.index_folder)
|
95
|
+
except GitCommandError as e:
|
96
|
+
raise ValueError(f"Index {index_id} not found") from e
|
92
97
|
if commit_like:
|
93
98
|
repo.git.checkout(commit_like)
|
94
99
|
|
stores/indexes/venv_utils.py
CHANGED
@@ -327,8 +327,7 @@ def parse_tool_signature(
|
|
327
327
|
if signature_dict.get("isasyncgenfunction"):
|
328
328
|
|
329
329
|
async def func_handler(*args, **kwargs):
|
330
|
-
|
331
|
-
async for value in run_remote_tool(
|
330
|
+
async for value in run_remote_tool_async(
|
332
331
|
tool_id=signature_dict["tool_id"],
|
333
332
|
index_folder=index_folder,
|
334
333
|
args=args,
|
@@ -338,6 +337,7 @@ def parse_tool_signature(
|
|
338
337
|
stream=True,
|
339
338
|
):
|
340
339
|
yield value
|
340
|
+
|
341
341
|
elif signature_dict.get("isgeneratorfunction"):
|
342
342
|
|
343
343
|
def func_handler(*args, **kwargs):
|
@@ -347,7 +347,7 @@ def parse_tool_signature(
|
|
347
347
|
def run():
|
348
348
|
async def runner():
|
349
349
|
try:
|
350
|
-
async for item in
|
350
|
+
async for item in run_remote_tool_async(
|
351
351
|
tool_id=signature_dict["tool_id"],
|
352
352
|
index_folder=index_folder,
|
353
353
|
args=args,
|
@@ -381,26 +381,63 @@ def parse_tool_signature(
|
|
381
381
|
elif signature_dict.get("iscoroutinefunction"):
|
382
382
|
|
383
383
|
async def func_handler(*args, **kwargs):
|
384
|
-
|
385
|
-
|
384
|
+
result = []
|
385
|
+
async for item in run_remote_tool_async(
|
386
386
|
tool_id=signature_dict["tool_id"],
|
387
387
|
index_folder=index_folder,
|
388
388
|
args=args,
|
389
389
|
kwargs=kwargs,
|
390
390
|
venv=venv,
|
391
391
|
env_var=env_var,
|
392
|
-
|
392
|
+
stream=True,
|
393
|
+
):
|
394
|
+
result.append(item)
|
395
|
+
return result[-1] if result else None
|
393
396
|
else:
|
394
397
|
|
395
|
-
def
|
396
|
-
|
398
|
+
async def func_handler_async_fallback(*args, **kwargs):
|
399
|
+
result = []
|
400
|
+
async for item in run_remote_tool_async(
|
397
401
|
tool_id=signature_dict["tool_id"],
|
398
402
|
index_folder=index_folder,
|
399
403
|
args=args,
|
400
404
|
kwargs=kwargs,
|
401
405
|
venv=venv,
|
402
406
|
env_var=env_var,
|
403
|
-
|
407
|
+
stream=True,
|
408
|
+
):
|
409
|
+
result.append(item)
|
410
|
+
return result[-1] if result else None
|
411
|
+
|
412
|
+
def func_handler(*args, **kwargs):
|
413
|
+
coro = func_handler_async_fallback(*args, **kwargs)
|
414
|
+
try:
|
415
|
+
# Check if we're in an async context
|
416
|
+
asyncio.get_running_loop()
|
417
|
+
in_async = True
|
418
|
+
except RuntimeError:
|
419
|
+
in_async = False
|
420
|
+
|
421
|
+
if not in_async:
|
422
|
+
# Safe to run directly
|
423
|
+
return asyncio.run(coro)
|
424
|
+
|
425
|
+
q = queue.Queue()
|
426
|
+
|
427
|
+
def runner():
|
428
|
+
try:
|
429
|
+
result = asyncio.run(coro)
|
430
|
+
q.put(result)
|
431
|
+
except Exception as e:
|
432
|
+
q.put(e)
|
433
|
+
|
434
|
+
t = threading.Thread(target=runner)
|
435
|
+
t.start()
|
436
|
+
result = q.get()
|
437
|
+
t.join()
|
438
|
+
if isinstance(result, Exception):
|
439
|
+
raise result
|
440
|
+
return result
|
404
441
|
|
405
442
|
# Reconstruct signature from list of args
|
406
443
|
params = []
|
@@ -426,92 +463,7 @@ def parse_tool_signature(
|
|
426
463
|
return func
|
427
464
|
|
428
465
|
|
429
|
-
|
430
|
-
def run_remote_tool(
|
431
|
-
tool_id: str,
|
432
|
-
index_folder: os.PathLike,
|
433
|
-
args: list | None = None,
|
434
|
-
kwargs: dict | None = None,
|
435
|
-
venv: str = VENV_NAME,
|
436
|
-
env_var: dict | None = None,
|
437
|
-
stream: bool = False,
|
438
|
-
):
|
439
|
-
args = args or []
|
440
|
-
kwargs = kwargs or {}
|
441
|
-
env_var = env_var or {}
|
442
|
-
|
443
|
-
module_name = ".".join(tool_id.split(".")[:-1])
|
444
|
-
tool_name = tool_id.split(".")[-1]
|
445
|
-
payload = json.dumps(
|
446
|
-
{
|
447
|
-
"args": args,
|
448
|
-
"kwargs": kwargs,
|
449
|
-
}
|
450
|
-
).encode("utf-8")
|
451
|
-
|
452
|
-
# We use sockets to pass function output
|
453
|
-
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
454
|
-
listener.bind(("localhost", 0))
|
455
|
-
listener.listen(1)
|
456
|
-
_, port = listener.getsockname()
|
457
|
-
|
458
|
-
result_data = {}
|
459
|
-
|
460
|
-
def handle_connection_sync():
|
461
|
-
conn, _ = listener.accept()
|
462
|
-
with conn:
|
463
|
-
buffer = ""
|
464
|
-
while True:
|
465
|
-
chunk = conn.recv(4096).decode("utf-8")
|
466
|
-
if not chunk:
|
467
|
-
break
|
468
|
-
buffer += chunk
|
469
|
-
while "\n" in buffer:
|
470
|
-
line, buffer = buffer.split("\n", 1)
|
471
|
-
if not line.strip():
|
472
|
-
continue
|
473
|
-
msg = json.loads(line)
|
474
|
-
if msg.get("ok") and "stream" in msg:
|
475
|
-
result_data.setdefault("stream", []).append(msg["stream"])
|
476
|
-
elif msg.get("ok") and "result" in msg:
|
477
|
-
result_data["result"] = msg["result"]
|
478
|
-
elif "error" in msg:
|
479
|
-
result_data["error"] = msg["error"]
|
480
|
-
elif msg.get("done"):
|
481
|
-
return
|
482
|
-
|
483
|
-
async def handle_connection_async():
|
484
|
-
loop = asyncio.get_running_loop()
|
485
|
-
conn, _ = await loop.sock_accept(listener)
|
486
|
-
conn.setblocking(False)
|
487
|
-
buffer = ""
|
488
|
-
try:
|
489
|
-
while True:
|
490
|
-
chunk = await loop.sock_recv(conn, 4096)
|
491
|
-
if not chunk:
|
492
|
-
break
|
493
|
-
buffer += chunk.decode("utf-8")
|
494
|
-
while "\n" in buffer:
|
495
|
-
line, buffer = buffer.split("\n", 1)
|
496
|
-
if not line.strip():
|
497
|
-
continue
|
498
|
-
msg = json.loads(line)
|
499
|
-
if msg.get("ok") and "stream" in msg:
|
500
|
-
yield msg["stream"]
|
501
|
-
elif msg.get("ok") and "result" in msg:
|
502
|
-
yield msg["result"]
|
503
|
-
elif "error" in msg:
|
504
|
-
raise RuntimeError(f"Subprocess error:\n{msg['error']}")
|
505
|
-
elif msg.get("done"):
|
506
|
-
return
|
507
|
-
finally:
|
508
|
-
conn.close()
|
509
|
-
|
510
|
-
if not stream:
|
511
|
-
thread = threading.Thread(target=lambda: handle_connection_sync())
|
512
|
-
thread.start()
|
513
|
-
|
514
|
-
runner = f"""
|
466
|
+
tool_runner = """
|
515
467
|
import asyncio, inspect, json, socket, sys, traceback
|
516
468
|
sys.path.insert(0, "{index_folder}")
|
517
469
|
|
@@ -560,26 +512,98 @@ finally:
|
|
560
512
|
pass
|
561
513
|
"""
|
562
514
|
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
515
|
+
|
516
|
+
# TODO: Sanitize tool_id, args, and kwargs
|
517
|
+
async def run_remote_tool_async(
|
518
|
+
tool_id: str,
|
519
|
+
index_folder: os.PathLike,
|
520
|
+
args: list | None = None,
|
521
|
+
kwargs: dict | None = None,
|
522
|
+
venv: str = VENV_NAME,
|
523
|
+
env_var: dict | None = None,
|
524
|
+
stream: bool = True,
|
525
|
+
):
|
526
|
+
args = args or []
|
527
|
+
kwargs = kwargs or {}
|
528
|
+
env_var = env_var or {}
|
529
|
+
|
530
|
+
module_name = ".".join(tool_id.split(".")[:-1])
|
531
|
+
tool_name = tool_id.split(".")[-1]
|
532
|
+
payload = json.dumps({"args": args, "kwargs": kwargs}).encode("utf-8")
|
533
|
+
|
534
|
+
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
535
|
+
listener.bind(("localhost", 0))
|
536
|
+
listener.listen(1)
|
537
|
+
listener.setblocking(False)
|
538
|
+
_, port = listener.getsockname()
|
539
|
+
|
540
|
+
loop = asyncio.get_running_loop()
|
541
|
+
conn_task = loop.create_task(loop.sock_accept(listener))
|
542
|
+
|
543
|
+
runner = tool_runner.format(
|
544
|
+
index_folder=index_folder,
|
545
|
+
port=port,
|
546
|
+
module_name=module_name,
|
547
|
+
tool_name=tool_name,
|
548
|
+
)
|
549
|
+
|
550
|
+
proc = await asyncio.create_subprocess_exec(
|
551
|
+
get_python_command(Path(index_folder) / venv),
|
552
|
+
"-c",
|
553
|
+
runner,
|
554
|
+
stdin=asyncio.subprocess.PIPE,
|
555
|
+
stdout=asyncio.subprocess.DEVNULL,
|
556
|
+
stderr=asyncio.subprocess.PIPE,
|
568
557
|
env=env_var or None,
|
569
558
|
)
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
559
|
+
|
560
|
+
try:
|
561
|
+
proc.stdin.write(payload)
|
562
|
+
await proc.stdin.drain()
|
563
|
+
proc.stdin.close()
|
564
|
+
|
565
|
+
conn, _ = await conn_task
|
566
|
+
conn.setblocking(False)
|
567
|
+
|
568
|
+
buffer = ""
|
569
|
+
result = None
|
570
|
+
while True:
|
571
|
+
chunk = await loop.sock_recv(conn, 4096)
|
572
|
+
if not chunk:
|
573
|
+
break
|
574
|
+
buffer += chunk.decode("utf-8")
|
575
|
+
while "\n" in buffer:
|
576
|
+
line, buffer = buffer.split("\n", 1)
|
577
|
+
if not line.strip():
|
578
|
+
continue
|
579
|
+
msg = json.loads(line)
|
580
|
+
|
581
|
+
if msg.get("ok") and "stream" in msg:
|
582
|
+
if stream:
|
583
|
+
yield msg["stream"]
|
584
|
+
else:
|
585
|
+
result = msg["stream"]
|
586
|
+
elif msg.get("ok") and "result" in msg:
|
587
|
+
result = msg["result"]
|
588
|
+
elif "error" in msg:
|
589
|
+
raise RuntimeError(f"Subprocess error:\n{msg['error']}")
|
590
|
+
elif "done" in msg and result is not None:
|
591
|
+
yield result
|
592
|
+
return
|
593
|
+
|
594
|
+
except asyncio.CancelledError:
|
595
|
+
proc.kill()
|
596
|
+
await proc.wait()
|
597
|
+
raise
|
598
|
+
finally:
|
599
|
+
try:
|
600
|
+
conn.close()
|
601
|
+
except Exception:
|
602
|
+
pass
|
603
|
+
try:
|
604
|
+
listener.close()
|
605
|
+
except Exception:
|
606
|
+
pass
|
607
|
+
if proc.returncode is None:
|
608
|
+
proc.kill()
|
609
|
+
await proc.wait()
|
@@ -1,15 +1,15 @@
|
|
1
1
|
stores/__init__.py,sha256=KYpKkNrMLx6ssVUbxHnn9wFBq5F5KnaFchcimIfDf9g,186
|
2
2
|
stores/constants.py,sha256=7WqFmoGCtmUKHA5WHxOJvvK7g-yYu_KGoqnuVFADNao,57
|
3
|
-
stores/format.py,sha256=
|
3
|
+
stores/format.py,sha256=L6DaRklE0CpUzMoVTckliaP-M5UrF6gW8nEG_m4y21M,8089
|
4
4
|
stores/parse.py,sha256=HYPNPzQod2vpu1Cln7yQ8aVkZT1Mw2IN0sZ2A1DIaqE,4967
|
5
5
|
stores/utils.py,sha256=GPWT6lCoGobwP3PlEOHyJfKyd0dobamjyErcR7lgm7M,242
|
6
6
|
stores/indexes/__init__.py,sha256=s-RNqml8uGREQhxwSdDoxcbcxeD8soB9BcL5dBKsQfI,215
|
7
|
-
stores/indexes/base_index.py,sha256=
|
7
|
+
stores/indexes/base_index.py,sha256=oaLN_Tk6PdoTSlRdGsd2wTvBVQ4S3ylV8rbIVSSNkEQ,15608
|
8
8
|
stores/indexes/index.py,sha256=Cub5mtnYGipHfPR8BexJYRSKfuJmcGPp0B3ou2bGNqs,2901
|
9
9
|
stores/indexes/local_index.py,sha256=Gg9LkEo1L0_NZZYPItsF_-1y6nFP369C3QlPvPyvPx8,3708
|
10
|
-
stores/indexes/remote_index.py,sha256
|
11
|
-
stores/indexes/venv_utils.py,sha256=
|
12
|
-
stores-0.1.
|
13
|
-
stores-0.1.
|
14
|
-
stores-0.1.
|
15
|
-
stores-0.1.
|
10
|
+
stores/indexes/remote_index.py,sha256=kbyfjqeOgH5IgC0gYfoq79loFM1AeOCCYcsKimudZAg,3666
|
11
|
+
stores/indexes/venv_utils.py,sha256=oN0LMQxeZQvVlkqsu3vrshaYaSmSDE-VOFSmMKc8QeI,18822
|
12
|
+
stores-0.1.8.dev2.dist-info/METADATA,sha256=xsNHI0bP0mF7StNBmRniznrzUFFsiPMptKqSmO8pWko,3081
|
13
|
+
stores-0.1.8.dev2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
14
|
+
stores-0.1.8.dev2.dist-info/licenses/LICENSE,sha256=VTidYE7_Dam0Dwyq095EhhDIqi47g03oVpLAHQgKws0,1066
|
15
|
+
stores-0.1.8.dev2.dist-info/RECORD,,
|
File without changes
|
File without changes
|