QuLab 2.10.10__tar.gz → 2.10.12__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.
- {qulab-2.10.10 → qulab-2.10.12}/PKG-INFO +2 -1
- {qulab-2.10.10 → qulab-2.10.12}/QuLab.egg-info/PKG-INFO +2 -1
- {qulab-2.10.10 → qulab-2.10.12}/QuLab.egg-info/SOURCES.txt +0 -4
- {qulab-2.10.10 → qulab-2.10.12}/QuLab.egg-info/requires.txt +1 -0
- {qulab-2.10.10 → qulab-2.10.12}/pyproject.toml +1 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/cli/commands.py +2 -1
- {qulab-2.10.10 → qulab-2.10.12}/qulab/executor/cli.py +15 -2
- {qulab-2.10.10 → qulab-2.10.12}/qulab/scan/curd.py +1 -1
- {qulab-2.10.10 → qulab-2.10.12}/qulab/scan/scan.py +1 -1
- {qulab-2.10.10 → qulab-2.10.12}/qulab/scan/utils.py +1 -2
- qulab-2.10.12/qulab/version.py +1 -0
- qulab-2.10.10/qulab/dicttree.py +0 -523
- qulab-2.10.10/qulab/expression.py +0 -827
- qulab-2.10.10/qulab/version.py +0 -1
- qulab-2.10.10/tests/test_dicttree.py +0 -163
- qulab-2.10.10/tests/test_expression.py +0 -171
- {qulab-2.10.10 → qulab-2.10.12}/LICENSE +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/MANIFEST.in +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/QuLab.egg-info/dependency_links.txt +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/QuLab.egg-info/entry_points.txt +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/QuLab.egg-info/top_level.txt +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/README.md +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/__main__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/cli/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/cli/config.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/cli/decorators.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/executor/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/executor/analyze.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/executor/load.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/executor/registry.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/executor/schedule.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/executor/storage.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/executor/template.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/executor/utils.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/monitor/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/monitor/__main__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/monitor/config.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/monitor/dataset.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/monitor/event_queue.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/monitor/mainwindow.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/monitor/monitor.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/monitor/ploter.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/monitor/qt_compat.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/monitor/toolbar.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/scan/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/scan/models.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/scan/optimize.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/scan/query.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/scan/record.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/scan/server.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/scan/space.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/__main__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/backend/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/backend/redis.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/base_dataset.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/chunk.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/dataset.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/file.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/models/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/models/base.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/models/config.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/models/file.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/models/ipy.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/models/models.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/models/record.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/models/report.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/models/tag.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/storage/storage.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/chat.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/device/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/device/basedevice.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/device/loader.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/device/utils.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/drivers/FakeInstrument.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/drivers/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/ipy_events.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/net/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/net/bencoder.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/net/cli.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/net/dhcp.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/net/dhcpd.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/net/kad.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/net/kcp.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/net/nginx.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/progress.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/rpc/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/rpc/client.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/rpc/exceptions.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/rpc/msgpack.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/rpc/msgpack.pyi +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/rpc/router.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/rpc/rpc.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/rpc/serialize.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/rpc/server.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/rpc/socket.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/rpc/utils.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/rpc/worker.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/sys/rpc/zmq_socket.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/tools/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/tools/connection_helper.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/typing.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/utils.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/visualization/__init__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/visualization/__main__.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/visualization/_autoplot.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/visualization/plot_circ.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/visualization/plot_layout.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/visualization/plot_seq.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/visualization/qdat.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/visualization/rot3d.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/qulab/visualization/widgets.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/setup.cfg +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/setup.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/src/qulab.h +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/tests/test_kad.py +0 -0
- {qulab-2.10.10 → qulab-2.10.12}/tests/test_scan.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: QuLab
|
3
|
-
Version: 2.10.
|
3
|
+
Version: 2.10.12
|
4
4
|
Summary: contral instruments and manage data
|
5
5
|
Author-email: feihoo87 <feihoo87@gmail.com>
|
6
6
|
Maintainer-email: feihoo87 <feihoo87@gmail.com>
|
@@ -40,6 +40,7 @@ Requires-Dist: pyperclip>=1.8.2
|
|
40
40
|
Requires-Dist: pyzmq>=25.1.0
|
41
41
|
Requires-Dist: qlisp>=1.1.4
|
42
42
|
Requires-Dist: qlispc>=1.1.8
|
43
|
+
Requires-Dist: qlispreg>=0.0.1
|
43
44
|
Requires-Dist: scipy>=1.0.0
|
44
45
|
Requires-Dist: scikit-optimize>=0.9.0
|
45
46
|
Requires-Dist: SQLAlchemy>=2.0.19
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: QuLab
|
3
|
-
Version: 2.10.
|
3
|
+
Version: 2.10.12
|
4
4
|
Summary: contral instruments and manage data
|
5
5
|
Author-email: feihoo87 <feihoo87@gmail.com>
|
6
6
|
Maintainer-email: feihoo87 <feihoo87@gmail.com>
|
@@ -40,6 +40,7 @@ Requires-Dist: pyperclip>=1.8.2
|
|
40
40
|
Requires-Dist: pyzmq>=25.1.0
|
41
41
|
Requires-Dist: qlisp>=1.1.4
|
42
42
|
Requires-Dist: qlispc>=1.1.8
|
43
|
+
Requires-Dist: qlispreg>=0.0.1
|
43
44
|
Requires-Dist: scipy>=1.0.0
|
44
45
|
Requires-Dist: scikit-optimize>=0.9.0
|
45
46
|
Requires-Dist: SQLAlchemy>=2.0.19
|
@@ -11,8 +11,6 @@ QuLab.egg-info/requires.txt
|
|
11
11
|
QuLab.egg-info/top_level.txt
|
12
12
|
qulab/__init__.py
|
13
13
|
qulab/__main__.py
|
14
|
-
qulab/dicttree.py
|
15
|
-
qulab/expression.py
|
16
14
|
qulab/typing.py
|
17
15
|
qulab/utils.py
|
18
16
|
qulab/version.py
|
@@ -110,7 +108,5 @@ qulab/visualization/qdat.py
|
|
110
108
|
qulab/visualization/rot3d.py
|
111
109
|
qulab/visualization/widgets.py
|
112
110
|
src/qulab.h
|
113
|
-
tests/test_dicttree.py
|
114
|
-
tests/test_expression.py
|
115
111
|
tests/test_kad.py
|
116
112
|
tests/test_scan.py
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import click
|
2
2
|
|
3
|
-
from ..executor.cli import create, get, maintain, reproduce, run, set
|
3
|
+
from ..executor.cli import boot, create, get, maintain, reproduce, run, set
|
4
4
|
from ..monitor.__main__ import main as monitor
|
5
5
|
from ..scan.server import server
|
6
6
|
from ..sys.net.cli import dht
|
@@ -28,3 +28,4 @@ cli.add_command(reproduce)
|
|
28
28
|
cli.add_command(create)
|
29
29
|
cli.add_command(set)
|
30
30
|
cli.add_command(get)
|
31
|
+
cli.add_command(boot)
|
@@ -20,7 +20,7 @@ from .utils import workflow_template
|
|
20
20
|
|
21
21
|
|
22
22
|
@logger.catch(reraise=True)
|
23
|
-
def
|
23
|
+
def run_script(script_path):
|
24
24
|
"""Run a script in a new terminal."""
|
25
25
|
import subprocess
|
26
26
|
import sys
|
@@ -81,7 +81,7 @@ def command_option(command_name):
|
|
81
81
|
sys.path.insert(0, code)
|
82
82
|
bootstrap = kwargs.pop('bootstrap')
|
83
83
|
if bootstrap is not None:
|
84
|
-
|
84
|
+
run_script(bootstrap)
|
85
85
|
return func(*args, **kwargs)
|
86
86
|
|
87
87
|
return wrapper
|
@@ -163,6 +163,19 @@ def get(key, api):
|
|
163
163
|
click.echo(reg.get(key))
|
164
164
|
|
165
165
|
|
166
|
+
@click.command()
|
167
|
+
@click.option('--bootstrap',
|
168
|
+
'-b',
|
169
|
+
default=lambda: get_config_value("bootstrap", Path),
|
170
|
+
help='The path of the bootstrap.')
|
171
|
+
def boot(bootstrap):
|
172
|
+
"""
|
173
|
+
Run a bootstrap script.
|
174
|
+
"""
|
175
|
+
if bootstrap is not None:
|
176
|
+
run_script(bootstrap)
|
177
|
+
|
178
|
+
|
166
179
|
@click.command()
|
167
180
|
@click.argument('workflow')
|
168
181
|
@click.option('--plot', '-p', is_flag=True, help='Plot the report.')
|
@@ -4,10 +4,10 @@ from datetime import date, datetime, timezone
|
|
4
4
|
from pathlib import Path
|
5
5
|
from typing import Sequence, Type, Union
|
6
6
|
|
7
|
+
from qlispreg.dicttree import foldDict
|
7
8
|
from sqlalchemy.orm import Query, Session, aliased
|
8
9
|
from sqlalchemy.orm.exc import NoResultFound
|
9
10
|
from sqlalchemy.orm.session import Session
|
10
|
-
from ..dicttree import foldDict
|
11
11
|
|
12
12
|
from .models import (Cell, Comment, Config, InputText, Notebook, Record,
|
13
13
|
Report, Sample, Tag, utcnow)
|
@@ -17,8 +17,8 @@ from typing import Any, Awaitable, Callable, Iterable
|
|
17
17
|
import dill
|
18
18
|
import numpy as np
|
19
19
|
import zmq
|
20
|
+
from qlispreg.expression import Env, Expression, Symbol
|
20
21
|
|
21
|
-
from ..expression import Env, Expression, Symbol
|
22
22
|
from ..sys.rpc.zmq_socket import ZMQContextManager
|
23
23
|
from .optimize import NgOptimizer
|
24
24
|
from .record import Record
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "2.10.12"
|
qulab-2.10.10/qulab/dicttree.py
DELETED
@@ -1,523 +0,0 @@
|
|
1
|
-
import copy
|
2
|
-
import math
|
3
|
-
import operator
|
4
|
-
import pickle
|
5
|
-
import sys
|
6
|
-
from typing import Any, Generator
|
7
|
-
|
8
|
-
|
9
|
-
class Singleton(type):
|
10
|
-
_instances = {}
|
11
|
-
|
12
|
-
def __call__(cls, *args, **kwargs):
|
13
|
-
if cls not in cls._instances:
|
14
|
-
cls._instances[cls] = super(Singleton,
|
15
|
-
cls).__call__(*args, **kwargs)
|
16
|
-
return cls._instances[cls]
|
17
|
-
|
18
|
-
|
19
|
-
class _SELF(metaclass=Singleton):
|
20
|
-
__slots__ = ()
|
21
|
-
|
22
|
-
def __repr__(self):
|
23
|
-
return "self"
|
24
|
-
|
25
|
-
|
26
|
-
class _NOTSET(metaclass=Singleton):
|
27
|
-
__slots__ = ()
|
28
|
-
|
29
|
-
def __repr__(self):
|
30
|
-
return 'N/A'
|
31
|
-
|
32
|
-
|
33
|
-
class _UNKNOW(metaclass=Singleton):
|
34
|
-
__slots__ = ()
|
35
|
-
|
36
|
-
def __repr__(self) -> str:
|
37
|
-
return "Unknow"
|
38
|
-
|
39
|
-
|
40
|
-
class _DELETE(metaclass=Singleton):
|
41
|
-
__slots__ = ()
|
42
|
-
|
43
|
-
def __repr__(self):
|
44
|
-
return 'Delete'
|
45
|
-
|
46
|
-
|
47
|
-
SELF = _SELF()
|
48
|
-
NOTSET = _NOTSET()
|
49
|
-
UNKNOW = _UNKNOW()
|
50
|
-
DELETE = _DELETE()
|
51
|
-
|
52
|
-
|
53
|
-
def flattenDictIter(d: dict,
|
54
|
-
prefix: list = []
|
55
|
-
) -> Generator[tuple[str, Any], None, None]:
|
56
|
-
for k in d:
|
57
|
-
if isinstance(d[k], dict):
|
58
|
-
yield from flattenDictIter(d[k], prefix=[*prefix, k])
|
59
|
-
else:
|
60
|
-
yield '.'.join(prefix + [k]), d[k]
|
61
|
-
|
62
|
-
|
63
|
-
def flattenDict(d: dict[str, Any]) -> dict[str, Any]:
|
64
|
-
return {k: v for k, v in flattenDictIter(d)}
|
65
|
-
|
66
|
-
|
67
|
-
def foldDict(d: dict[str, Any]) -> dict[str, Any]:
|
68
|
-
ret = {}
|
69
|
-
|
70
|
-
for k, v in d.items():
|
71
|
-
keys = k.split('.')
|
72
|
-
d = ret
|
73
|
-
parent = None
|
74
|
-
|
75
|
-
for key in keys[:-1]:
|
76
|
-
if not isinstance(d, dict):
|
77
|
-
parent[0][parent[1]] = {SELF: d}
|
78
|
-
d = parent[0][parent[1]]
|
79
|
-
if key not in d:
|
80
|
-
d[key] = dict()
|
81
|
-
parent = d, key
|
82
|
-
d = d[key]
|
83
|
-
if not isinstance(d, dict):
|
84
|
-
parent[0][parent[1]] = {SELF: d}
|
85
|
-
d = parent[0][parent[1]]
|
86
|
-
if keys[-1] in d and isinstance(d[keys[-1]], dict):
|
87
|
-
d[keys[-1]][SELF] = v
|
88
|
-
else:
|
89
|
-
d[keys[-1]] = v
|
90
|
-
return ret
|
91
|
-
|
92
|
-
|
93
|
-
class Update():
|
94
|
-
__slots__ = ('o', 'n')
|
95
|
-
|
96
|
-
def __init__(self, o, n):
|
97
|
-
self.o = o
|
98
|
-
self.n = n
|
99
|
-
|
100
|
-
def __repr__(self):
|
101
|
-
return f"Update: {self.o!r} ==> {self.n!r}"
|
102
|
-
|
103
|
-
def __eq__(self, o: object) -> bool:
|
104
|
-
return isinstance(o, Update) and self.n == o.n
|
105
|
-
|
106
|
-
|
107
|
-
class Create():
|
108
|
-
__slots__ = ('n', 'replace')
|
109
|
-
|
110
|
-
def __init__(self, n, replace=False):
|
111
|
-
"""
|
112
|
-
Create a new node
|
113
|
-
|
114
|
-
:param n: the new node
|
115
|
-
:param replace: if True, replace the node if it already exists
|
116
|
-
"""
|
117
|
-
self.n = n
|
118
|
-
self.replace = replace
|
119
|
-
|
120
|
-
def __repr__(self):
|
121
|
-
if self.replace:
|
122
|
-
return f"Replace: {self.n!r}"
|
123
|
-
else:
|
124
|
-
return f"Create: {self.n!r}"
|
125
|
-
|
126
|
-
def __eq__(self, o: object) -> bool:
|
127
|
-
return isinstance(
|
128
|
-
o, Create) and self.n == o.n and self.replace == o.replace
|
129
|
-
|
130
|
-
|
131
|
-
def _eq(a, b):
|
132
|
-
import numpy as np
|
133
|
-
|
134
|
-
if isinstance(a, np.ndarray):
|
135
|
-
return np.array_equal(a, np.asarray(b))
|
136
|
-
|
137
|
-
try:
|
138
|
-
return a == b
|
139
|
-
except:
|
140
|
-
pass
|
141
|
-
if isinstance(a, (list, tuple)):
|
142
|
-
return len(a) == len(b) and all(_eq(a[i], b[i]) for i in range(len(a)))
|
143
|
-
if isinstance(a, dict):
|
144
|
-
return set(a.keys()) == set(b.keys()) and all(
|
145
|
-
_eq(a[k], b[k]) for k in a)
|
146
|
-
|
147
|
-
try:
|
148
|
-
return pickle.dumps(a) == pickle.dumps(b)
|
149
|
-
except:
|
150
|
-
return False
|
151
|
-
|
152
|
-
|
153
|
-
def diff(d1: dict, d2: dict) -> dict:
|
154
|
-
"""
|
155
|
-
Compute the difference between two dictionaries
|
156
|
-
|
157
|
-
Args:
|
158
|
-
d1: the original dictionary
|
159
|
-
d2: the new dictionary
|
160
|
-
|
161
|
-
Returns:
|
162
|
-
a dictionary containing the difference between d1 and d2
|
163
|
-
"""
|
164
|
-
ret = {}
|
165
|
-
for k in d2:
|
166
|
-
if k in d1:
|
167
|
-
if isinstance(d2[k], type(d1[k])) and _eq(d1[k], d2[k]):
|
168
|
-
pass
|
169
|
-
elif isinstance(d1[k], dict) and isinstance(d2[k], dict):
|
170
|
-
ret[k] = diff(d1[k], d2[k])
|
171
|
-
else:
|
172
|
-
ret[k] = Update(d1[k], d2[k])
|
173
|
-
else:
|
174
|
-
ret[k] = Create(d2[k])
|
175
|
-
for k in d1:
|
176
|
-
if k not in d2:
|
177
|
-
ret[k] = DELETE
|
178
|
-
return ret
|
179
|
-
|
180
|
-
|
181
|
-
def patch(source, diff, in_place=False):
|
182
|
-
"""
|
183
|
-
Patch a dictionary with a diff
|
184
|
-
|
185
|
-
Args:
|
186
|
-
source: the original dictionary
|
187
|
-
diff: the diff
|
188
|
-
in_place: if True, patch the source dictionary in place
|
189
|
-
|
190
|
-
Returns:
|
191
|
-
the patched dictionary
|
192
|
-
"""
|
193
|
-
if in_place:
|
194
|
-
ret = source
|
195
|
-
else:
|
196
|
-
ret = copy.copy(source)
|
197
|
-
for k, v in diff.items():
|
198
|
-
if isinstance(v, dict):
|
199
|
-
ret[k] = patch(source[k], v, in_place=in_place)
|
200
|
-
else:
|
201
|
-
if isinstance(v, Update):
|
202
|
-
ret[k] = v.n
|
203
|
-
elif isinstance(v, Create):
|
204
|
-
if v.replace or k not in ret:
|
205
|
-
ret[k] = v.n
|
206
|
-
else:
|
207
|
-
update_tree(ret[k], v.n)
|
208
|
-
elif v is DELETE:
|
209
|
-
del ret[k]
|
210
|
-
else:
|
211
|
-
raise ValueError(f"Unsupported patch: {v!r}")
|
212
|
-
return ret
|
213
|
-
|
214
|
-
|
215
|
-
def merge(diff1, diff2, origin=None):
|
216
|
-
"""
|
217
|
-
Merge two diffs
|
218
|
-
|
219
|
-
Args:
|
220
|
-
diff1: the first diff
|
221
|
-
diff2: the second diff
|
222
|
-
origin: the original dictionary
|
223
|
-
|
224
|
-
Returns:
|
225
|
-
the merged diff
|
226
|
-
"""
|
227
|
-
if origin is not None:
|
228
|
-
updated = patch(patch(origin, diff1), diff2)
|
229
|
-
return diff(origin, updated)
|
230
|
-
|
231
|
-
ret = {}
|
232
|
-
for k, v in diff1.items():
|
233
|
-
if k in diff2:
|
234
|
-
v2 = diff2[k]
|
235
|
-
if isinstance(v, dict) and isinstance(v2, dict):
|
236
|
-
d = merge(v, v2)
|
237
|
-
if d:
|
238
|
-
ret[k] = d
|
239
|
-
else:
|
240
|
-
if isinstance(v, Update) and isinstance(v2, Update):
|
241
|
-
ret[k] = Update(v.o, v2.n)
|
242
|
-
elif isinstance(v, Create) and isinstance(v2, dict):
|
243
|
-
ret[k] = Create(patch(copy.copy(v.n), v2, True))
|
244
|
-
elif isinstance(v, Create) and isinstance(v2, Update):
|
245
|
-
ret[k] = Create(v2.n)
|
246
|
-
elif isinstance(v, Create) and v2 is DELETE:
|
247
|
-
pass
|
248
|
-
elif v2 is DELETE:
|
249
|
-
ret[k] = DELETE
|
250
|
-
elif v is DELETE and isinstance(v2, Create):
|
251
|
-
if isinstance(v2.n, dict):
|
252
|
-
ret[k] = Create(v2.n, replace=True)
|
253
|
-
else:
|
254
|
-
ret[k] = Update(UNKNOW, v2.n)
|
255
|
-
elif isinstance(v2, Create) and v2.replace:
|
256
|
-
ret[k] = v2
|
257
|
-
else:
|
258
|
-
raise ValueError(f"Unsupported merge: {v!r} {v2!r}")
|
259
|
-
else:
|
260
|
-
ret[k] = v
|
261
|
-
for k, v in diff2.items():
|
262
|
-
if k not in diff1:
|
263
|
-
ret[k] = v
|
264
|
-
return ret
|
265
|
-
|
266
|
-
|
267
|
-
def print_diff(d, limit=None, offset=0, file=sys.stdout, ignores=None):
|
268
|
-
"""
|
269
|
-
Print a diff
|
270
|
-
|
271
|
-
Args:
|
272
|
-
d: the diff
|
273
|
-
limit: the maximum number of lines to print
|
274
|
-
offset: the offset of the first line
|
275
|
-
file: the file to print to
|
276
|
-
ignores: ignore keys starting with this prefix
|
277
|
-
"""
|
278
|
-
count = 0
|
279
|
-
for i, (k, v) in enumerate(flattenDictIter(d)):
|
280
|
-
if count >= offset:
|
281
|
-
if ignores is not None and k.startswith(ignores):
|
282
|
-
continue
|
283
|
-
print(f"{k:40}", v, file=file)
|
284
|
-
count += 1
|
285
|
-
if limit is not None and count >= limit:
|
286
|
-
break
|
287
|
-
|
288
|
-
|
289
|
-
def update_tree(result, updates):
|
290
|
-
for k, v in updates.items():
|
291
|
-
if isinstance(v, dict):
|
292
|
-
if k not in result or not isinstance(result[k], dict):
|
293
|
-
result[k] = {}
|
294
|
-
update_tree(result[k], v)
|
295
|
-
else:
|
296
|
-
result[k] = v
|
297
|
-
return result
|
298
|
-
|
299
|
-
|
300
|
-
def queryref_tree(q: str, keys, dct, prefix=[], chain=None):
|
301
|
-
q = q.removeprefix('$')
|
302
|
-
if q.startswith('.'):
|
303
|
-
while q.startswith('.'):
|
304
|
-
keys.pop()
|
305
|
-
q = q[1:]
|
306
|
-
q = '.'.join(keys + [q])
|
307
|
-
|
308
|
-
if chain is None:
|
309
|
-
chain = [q]
|
310
|
-
elif q in chain:
|
311
|
-
raise KeyError(f'Circular reference: {chain+[q]}')
|
312
|
-
|
313
|
-
return query_tree(q, dct, prefix=prefix, eval=True, chain=chain + [q])
|
314
|
-
|
315
|
-
|
316
|
-
def query_tree(q, dct, prefix=[], eval=True, chain=None):
|
317
|
-
ret = dct
|
318
|
-
keys = q.split('.')
|
319
|
-
for i, key in enumerate(keys):
|
320
|
-
if key not in ret:
|
321
|
-
return (NOTSET, '.'.join(prefix + keys[:i + 1]))
|
322
|
-
ret = ret[key]
|
323
|
-
if isinstance(ret, str) and eval:
|
324
|
-
if ret.startswith('$'):
|
325
|
-
return queryref_tree(ret, keys, dct, prefix=prefix, chain=chain)
|
326
|
-
elif ret.startswith('&'):
|
327
|
-
return eval_expr(ret[1:], Env(dct,
|
328
|
-
keys,
|
329
|
-
prefix=prefix,
|
330
|
-
chain=chain))
|
331
|
-
return ret
|
332
|
-
|
333
|
-
|
334
|
-
class Env():
|
335
|
-
|
336
|
-
def __init__(self, dct=None, keys=None, prefix=[], chain=None):
|
337
|
-
self.dct = dct if dct is not None else {}
|
338
|
-
self.keys = keys if keys is not None else []
|
339
|
-
self.chain = chain
|
340
|
-
self.prefix = prefix
|
341
|
-
|
342
|
-
def get(self, name):
|
343
|
-
return queryref_tree(name,
|
344
|
-
self.keys,
|
345
|
-
self.dct,
|
346
|
-
prefix=self.prefix,
|
347
|
-
chain=self.chain)
|
348
|
-
|
349
|
-
|
350
|
-
internal_functions = {
|
351
|
-
'**': operator.pow,
|
352
|
-
'+': operator.add,
|
353
|
-
'-': operator.sub,
|
354
|
-
'*': operator.mul,
|
355
|
-
'/': operator.truediv,
|
356
|
-
'//': operator.floordiv,
|
357
|
-
'%': operator.mod,
|
358
|
-
'&': operator.and_,
|
359
|
-
'|': operator.or_,
|
360
|
-
'^': operator.xor,
|
361
|
-
'~': operator.invert,
|
362
|
-
'<<': operator.lshift,
|
363
|
-
'>>': operator.rshift,
|
364
|
-
'getitem': operator.getitem,
|
365
|
-
'contains': operator.contains,
|
366
|
-
'not': operator.not_,
|
367
|
-
'getattr': getattr,
|
368
|
-
'abs': abs,
|
369
|
-
'min': min,
|
370
|
-
'max': max,
|
371
|
-
'sum': sum,
|
372
|
-
'len': len,
|
373
|
-
'any': any,
|
374
|
-
'all': all,
|
375
|
-
'ord': ord,
|
376
|
-
'chr': chr,
|
377
|
-
'bin': bin,
|
378
|
-
'oct': oct,
|
379
|
-
'hex': hex,
|
380
|
-
'int': int,
|
381
|
-
'round': round,
|
382
|
-
'real': lambda x: x.real,
|
383
|
-
'imag': lambda x: x.imag,
|
384
|
-
'sqrt': math.sqrt,
|
385
|
-
'sin': math.sin,
|
386
|
-
'cos': math.cos,
|
387
|
-
'tan': math.tan,
|
388
|
-
'asin': math.asin,
|
389
|
-
'acos': math.acos,
|
390
|
-
'atan': math.atan,
|
391
|
-
'atan2': math.atan2,
|
392
|
-
'sinh': math.sinh,
|
393
|
-
'cosh': math.cosh,
|
394
|
-
'tanh': math.tanh,
|
395
|
-
'asinh': math.asinh,
|
396
|
-
'acosh': math.acosh,
|
397
|
-
'atanh': math.atanh,
|
398
|
-
'ceil': math.ceil,
|
399
|
-
'floor': math.floor,
|
400
|
-
'trunc': math.trunc,
|
401
|
-
'log10': math.log10,
|
402
|
-
'log': math.log,
|
403
|
-
'exp': math.exp,
|
404
|
-
}
|
405
|
-
|
406
|
-
|
407
|
-
def eval_expr(expression, env=None, functions=None):
|
408
|
-
from pyparsing import (Combine, Forward, Literal, MatchFirst, ParseResults,
|
409
|
-
Regex, Suppress, Word, ZeroOrMore, alphanums,
|
410
|
-
alphas, delimitedList, infixNotation, oneOf,
|
411
|
-
opAssoc, pyparsing_common)
|
412
|
-
if functions is None:
|
413
|
-
functions = internal_functions
|
414
|
-
|
415
|
-
def lookup(name):
|
416
|
-
return env.get(name)
|
417
|
-
|
418
|
-
def apply(fun, *args):
|
419
|
-
return functions[fun](*args)
|
420
|
-
|
421
|
-
def eval_expr(t):
|
422
|
-
if not isinstance(t, ParseResults):
|
423
|
-
return t
|
424
|
-
if t[0] == '-' and len(t) == 2:
|
425
|
-
return -t[1]
|
426
|
-
return apply(t[1], t[0], t[2])
|
427
|
-
|
428
|
-
# Define pyparsing grammar
|
429
|
-
expr = Forward()
|
430
|
-
|
431
|
-
complex_ = (pyparsing_common.number +
|
432
|
-
'j').setParseAction(lambda s, l, t: complex(t[0]))
|
433
|
-
number = pyparsing_common.number | complex_
|
434
|
-
|
435
|
-
const = oneOf('pi e').setParseAction(lambda s, l, t: {
|
436
|
-
'pi': math.pi,
|
437
|
-
'e': math.e
|
438
|
-
}.get(t[0]))
|
439
|
-
|
440
|
-
identifier = Word(alphas + '_', alphanums + '_')
|
441
|
-
dollar = Literal('$')
|
442
|
-
dot_sequence = Regex(r'\.{1,}')
|
443
|
-
attr = Combine(Literal('.') + identifier)
|
444
|
-
attr_chain = ZeroOrMore(attr)
|
445
|
-
dollar_named_chain = Combine(dollar + identifier + attr_chain)
|
446
|
-
dollar_dotN_chain = Combine(dollar + dot_sequence + identifier +
|
447
|
-
attr_chain)
|
448
|
-
dollar_simple = Combine(dollar + identifier)
|
449
|
-
|
450
|
-
variable = MatchFirst(
|
451
|
-
[dollar_dotN_chain, dollar_named_chain,
|
452
|
-
dollar_simple]).setParseAction(lambda s, l, t: lookup(t[0]))
|
453
|
-
|
454
|
-
bracket = Suppress('(') + expr + Suppress(')')
|
455
|
-
bracket.setParseAction(lambda s, l, t: t[0])
|
456
|
-
|
457
|
-
operand = const | variable | number | bracket
|
458
|
-
|
459
|
-
func = pyparsing_common.identifier
|
460
|
-
func_call = func + Suppress("(") + delimitedList(expr) + Suppress(")")
|
461
|
-
func_call.setParseAction(lambda s, l, t: apply(t[0], *t[1:]))
|
462
|
-
|
463
|
-
term = operand | func_call
|
464
|
-
|
465
|
-
expr << infixNotation(term, [
|
466
|
-
(Literal('**'), 2, opAssoc.RIGHT),
|
467
|
-
(Literal('~'), 1, opAssoc.RIGHT),
|
468
|
-
(Literal('-'), 1, opAssoc.RIGHT),
|
469
|
-
(oneOf('* / // %'), 2, opAssoc.LEFT),
|
470
|
-
(oneOf('+ -'), 2, opAssoc.LEFT),
|
471
|
-
(oneOf('>> <<'), 2, opAssoc.LEFT),
|
472
|
-
(Literal('&'), 2, opAssoc.LEFT),
|
473
|
-
(Literal('^'), 2, opAssoc.LEFT),
|
474
|
-
(Literal('|'), 2, opAssoc.LEFT),
|
475
|
-
])
|
476
|
-
|
477
|
-
expr.setParseAction(lambda s, l, t: eval_expr(t[0]))
|
478
|
-
|
479
|
-
parsed = expr.parseString(expression, parseAll=True)
|
480
|
-
return parsed[0]
|
481
|
-
|
482
|
-
|
483
|
-
def sorted_tree(dct, *, keys=None):
|
484
|
-
if keys is None or callable(keys):
|
485
|
-
key = keys
|
486
|
-
elif isinstance(keys, list):
|
487
|
-
key = keys[0]
|
488
|
-
if len(keys) > 1:
|
489
|
-
keys = keys[1:]
|
490
|
-
else:
|
491
|
-
keys = None
|
492
|
-
elif (isinstance(keys, tuple) and len(keys) == 2
|
493
|
-
and isinstance(keys[1], dict)):
|
494
|
-
key = keys[0]
|
495
|
-
keys = keys[1]
|
496
|
-
else:
|
497
|
-
raise Exception(f"Unsupported keys: {keys!r}")
|
498
|
-
|
499
|
-
if isinstance(dct, dict):
|
500
|
-
if isinstance(keys, dict):
|
501
|
-
default = keys.get('default', None)
|
502
|
-
return {
|
503
|
-
k: sorted_tree(dct[k], keys=keys.get(k, default))
|
504
|
-
for k in sorted(dct.keys(), key=key)
|
505
|
-
}
|
506
|
-
else:
|
507
|
-
return {
|
508
|
-
k: sorted_tree(dct[k], keys=keys)
|
509
|
-
for k in sorted(dct.keys(), key=key)
|
510
|
-
}
|
511
|
-
elif isinstance(dct, set):
|
512
|
-
if isinstance(keys, dict):
|
513
|
-
default = keys.get('default', None)
|
514
|
-
return set([
|
515
|
-
sorted_tree(v, keys=keys.get(v, default))
|
516
|
-
for v in sorted(list(dct), key=key)
|
517
|
-
])
|
518
|
-
else:
|
519
|
-
return set([
|
520
|
-
sorted_tree(v, keys=keys) for v in sorted(list(dct), key=key)
|
521
|
-
])
|
522
|
-
else:
|
523
|
-
return dct
|