cicwave 0.7.0__tar.gz → 0.7.2__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.
- {cicwave-0.7.0/src/cicwave.egg-info → cicwave-0.7.2}/PKG-INFO +1 -1
- {cicwave-0.7.0 → cicwave-0.7.2}/pyproject.toml +1 -1
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave/cli.py +27 -5
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave/wave_pg.py +60 -10
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave/wavefiles.py +12 -0
- {cicwave-0.7.0 → cicwave-0.7.2/src/cicwave.egg-info}/PKG-INFO +1 -1
- {cicwave-0.7.0 → cicwave-0.7.2}/LICENSE +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/README.md +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/setup.cfg +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave/__init__.py +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave/analysis.py +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave/apisource.py +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave/command.py +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave/mcp_server.py +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave/ngraw.py +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave/pivot.py +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave/stdf.py +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave/theme.py +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave.egg-info/SOURCES.txt +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave.egg-info/dependency_links.txt +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave.egg-info/entry_points.txt +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave.egg-info/requires.txt +0 -0
- {cicwave-0.7.0 → cicwave-0.7.2}/src/cicwave.egg-info/top_level.txt +0 -0
|
@@ -30,6 +30,7 @@ import os
|
|
|
30
30
|
import re
|
|
31
31
|
import sys
|
|
32
32
|
import glob as _glob
|
|
33
|
+
import urllib.parse
|
|
33
34
|
import click
|
|
34
35
|
from .command import setup_logging
|
|
35
36
|
|
|
@@ -111,6 +112,23 @@ def _expand_glob_patterns(files, patterns):
|
|
|
111
112
|
return tuple(out)
|
|
112
113
|
|
|
113
114
|
|
|
115
|
+
def _spec_path_of(path):
|
|
116
|
+
"""Lower-cased path part of *path*, with any URL query string cut off."""
|
|
117
|
+
if _URL_RE.match(path):
|
|
118
|
+
return urllib.parse.urlsplit(path).path.lower()
|
|
119
|
+
return path.lower()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _spec_ref(path):
|
|
123
|
+
"""How a spec is recorded for a session: absolute path, or URL as-is.
|
|
124
|
+
|
|
125
|
+
``os.path.abspath`` on an ``http://`` URL yields a local path that
|
|
126
|
+
names nothing, so a session written from a URL spec could not be
|
|
127
|
+
read back. Mirrors what ``PgWaveWindow.openSourceSpec`` does.
|
|
128
|
+
"""
|
|
129
|
+
return path if _URL_RE.match(path) else os.path.abspath(path)
|
|
130
|
+
|
|
131
|
+
|
|
114
132
|
def _is_source_spec_file(path):
|
|
115
133
|
"""True for a YAML/JSON pivot spec that fetches its own data.
|
|
116
134
|
|
|
@@ -126,8 +144,11 @@ def _is_source_spec_file(path):
|
|
|
126
144
|
"""
|
|
127
145
|
#- Extension check first, so the pandas/Qt import chain behind
|
|
128
146
|
#- apisource is paid for only when a YAML argument is actually given,
|
|
129
|
-
#- and so a plain data URL is never fetched twice.
|
|
130
|
-
|
|
147
|
+
#- and so a plain data URL is never fetched twice. Test it against
|
|
148
|
+
#- the URL's path, not the whole URL: a generated spec is served at
|
|
149
|
+
#- something like `/spec.yaml?dut=X`, and the rest of cicwave already
|
|
150
|
+
#- ignores the query string when resolving a format.
|
|
151
|
+
if not _spec_path_of(path).endswith((".yaml", ".yml")):
|
|
131
152
|
return False
|
|
132
153
|
from .apisource import is_source_spec_file
|
|
133
154
|
return is_source_spec_file(path)
|
|
@@ -199,7 +220,7 @@ def _run_catalog_spec(pivot_spec, spec, x, pivot_info_flag,
|
|
|
199
220
|
c.win.applySession(session_path)
|
|
200
221
|
c.win.browser.openLazyFrame(
|
|
201
222
|
os.path.basename(pivot_spec), groups, catalog.load_group,
|
|
202
|
-
pivot_spec_path=
|
|
223
|
+
pivot_spec_path=_spec_ref(pivot_spec))
|
|
203
224
|
c.run()
|
|
204
225
|
|
|
205
226
|
|
|
@@ -309,7 +330,7 @@ def _run_wave_pg(files, x, sheet, pivot_spec=None,
|
|
|
309
330
|
name = "pivot(%s)" % os.path.basename(f)
|
|
310
331
|
c.openDataFrame(
|
|
311
332
|
pivoted, name,
|
|
312
|
-
pivot_spec_path=
|
|
333
|
+
pivot_spec_path=_spec_ref(pivot_spec),
|
|
313
334
|
original_path=(
|
|
314
335
|
f if _URL_RE.match(f) else os.path.abspath(f)),
|
|
315
336
|
source_spec=from_source)
|
|
@@ -382,7 +403,8 @@ def main(files, globs, x, sheet, fmt, pivot, pivot_info, session, export,
|
|
|
382
403
|
advanced visualization of simulation data.
|
|
383
404
|
|
|
384
405
|
Supports: .raw, .csv, .tsv, .xlsx, .json, .parquet, .feather, .npz,
|
|
385
|
-
.h5, .pkl, .vcd (digital), .iqvsa (LitePoint), .stdf (ATE),
|
|
406
|
+
.h5, .pkl, .vcd (digital), .iqvsa (LitePoint), .stdf (ATE),
|
|
407
|
+
.u32 (raw counter captures), and more.
|
|
386
408
|
|
|
387
409
|
\b
|
|
388
410
|
URL sources:
|
|
@@ -10,6 +10,7 @@ import math
|
|
|
10
10
|
import os
|
|
11
11
|
import sys
|
|
12
12
|
import re
|
|
13
|
+
from urllib.parse import urlsplit
|
|
13
14
|
import numpy as np
|
|
14
15
|
import pandas as pd
|
|
15
16
|
from importlib.metadata import version as _pkg_version
|
|
@@ -3460,10 +3461,13 @@ class PgWaveWindow(QMainWindow):
|
|
|
3460
3461
|
"""
|
|
3461
3462
|
from .apisource import is_source_spec_file
|
|
3462
3463
|
|
|
3463
|
-
|
|
3464
|
+
#- Match on the path part: a spec served by an API can carry a
|
|
3465
|
+
#- query string, which the rest of cicwave already ignores when
|
|
3466
|
+
#- resolving a format.
|
|
3467
|
+
probe = (urlsplit(path).path if _is_url(path) else path).lower()
|
|
3468
|
+
if probe.endswith('.cicwave.yaml'):
|
|
3464
3469
|
self.applySession(path)
|
|
3465
|
-
elif
|
|
3466
|
-
and is_source_spec_file(path)):
|
|
3470
|
+
elif probe.endswith(('.yaml', '.yml')) and is_source_spec_file(path):
|
|
3467
3471
|
self.openSourceSpec(path)
|
|
3468
3472
|
else:
|
|
3469
3473
|
self.browser.openFile(path)
|
|
@@ -4017,6 +4021,14 @@ class PgWaveWindow(QMainWindow):
|
|
|
4017
4021
|
if getattr(wave, 'show_as_digital', False):
|
|
4018
4022
|
wave_dict['digital'] = True
|
|
4019
4023
|
wave_dict['digital_format'] = wave.digital_format
|
|
4024
|
+
#- A catalog's waves exist only once their group has been
|
|
4025
|
+
#- fetched, so the session has to say which one to ask
|
|
4026
|
+
#- for; without it the wave is simply not found on load.
|
|
4027
|
+
group = None
|
|
4028
|
+
if hasattr(wave.wfile, 'groupOf'):
|
|
4029
|
+
group = wave.wfile.groupOf(wave.key)
|
|
4030
|
+
if group is not None:
|
|
4031
|
+
wave_dict['group'] = group
|
|
4020
4032
|
waves.append(wave_dict)
|
|
4021
4033
|
plot_dict = {'name': tab_name, 'waves': waves}
|
|
4022
4034
|
if widget.custom_xlabel:
|
|
@@ -4063,11 +4075,18 @@ class PgWaveWindow(QMainWindow):
|
|
|
4063
4075
|
return
|
|
4064
4076
|
session = self._build_session()
|
|
4065
4077
|
session_dir = os.path.dirname(os.path.abspath(fname))
|
|
4078
|
+
|
|
4079
|
+
def _portable(ref):
|
|
4080
|
+
#- A URL is already portable; only a local path is worth
|
|
4081
|
+
#- rewriting relative to where the session is being saved.
|
|
4082
|
+
return ref if _is_url(ref) else os.path.relpath(ref, session_dir)
|
|
4083
|
+
|
|
4066
4084
|
for fe in session.get('files', []):
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4085
|
+
#- A spec that fetches its own data is stored as 'source'
|
|
4086
|
+
#- and has no 'path' at all, so key by what is there.
|
|
4087
|
+
for key in ('path', 'source', 'pivot'):
|
|
4088
|
+
if key in fe:
|
|
4089
|
+
fe[key] = _portable(fe[key])
|
|
4071
4090
|
|
|
4072
4091
|
with open(fname, 'w') as fh:
|
|
4073
4092
|
yaml.dump(session, fh, default_flow_style=False, sort_keys=False)
|
|
@@ -4097,15 +4116,29 @@ class PgWaveWindow(QMainWindow):
|
|
|
4097
4116
|
fpath = os.path.normpath(os.path.join(session_dir, fpath))
|
|
4098
4117
|
fmt = fe.get('format')
|
|
4099
4118
|
pivot_path = fpath if from_source else fe.get('pivot')
|
|
4100
|
-
if pivot_path and not
|
|
4119
|
+
if (pivot_path and not _is_url(pivot_path)
|
|
4120
|
+
and not os.path.isabs(pivot_path)):
|
|
4101
4121
|
pivot_path = os.path.normpath(
|
|
4102
4122
|
os.path.join(session_dir, pivot_path))
|
|
4103
4123
|
|
|
4104
4124
|
try:
|
|
4105
4125
|
if pivot_path:
|
|
4106
|
-
from .apisource import
|
|
4126
|
+
from .apisource import (
|
|
4127
|
+
LazyCatalog, has_catalog, load_flat_frame)
|
|
4107
4128
|
from .pivot import load_spec, apply_pivot
|
|
4108
4129
|
spec = load_spec(pivot_path)
|
|
4130
|
+
spec_ref = (pivot_path if _is_url(pivot_path)
|
|
4131
|
+
else os.path.abspath(pivot_path))
|
|
4132
|
+
if from_source and has_catalog(spec):
|
|
4133
|
+
#- Same as opening the spec directly: the groups
|
|
4134
|
+
#- are named now and each is fetched when it is
|
|
4135
|
+
#- plotted. Fetching the lot is what a catalog
|
|
4136
|
+
#- exists to avoid, session or not.
|
|
4137
|
+
catalog = LazyCatalog(spec)
|
|
4138
|
+
self.browser.openLazyFrame(
|
|
4139
|
+
os.path.basename(fpath), catalog.load(),
|
|
4140
|
+
catalog.load_group, pivot_spec_path=spec_ref)
|
|
4141
|
+
continue
|
|
4109
4142
|
xaxis = self.browser.xaxis or spec.get('columns', '')
|
|
4110
4143
|
if not self.browser.xaxis and spec.get('columns'):
|
|
4111
4144
|
self.browser.xaxis = spec['columns']
|
|
@@ -4116,7 +4149,7 @@ class PgWaveWindow(QMainWindow):
|
|
|
4116
4149
|
name = "pivot(%s)" % os.path.basename(fpath)
|
|
4117
4150
|
self.browser.openDataFrame(
|
|
4118
4151
|
pivoted, name,
|
|
4119
|
-
pivot_spec_path=
|
|
4152
|
+
pivot_spec_path=spec_ref,
|
|
4120
4153
|
original_path=(fpath if _is_url(fpath)
|
|
4121
4154
|
else os.path.abspath(fpath)),
|
|
4122
4155
|
source_spec=from_source)
|
|
@@ -4141,6 +4174,10 @@ class PgWaveWindow(QMainWindow):
|
|
|
4141
4174
|
for wd in pd.get('waves', []):
|
|
4142
4175
|
wave_name = wd.get('name')
|
|
4143
4176
|
style = wd.get('style', 'Lines')
|
|
4177
|
+
#- A wave from a catalog group is not there until that
|
|
4178
|
+
#- group is fetched; one request, not the whole catalog.
|
|
4179
|
+
if wd.get('group'):
|
|
4180
|
+
self._fetch_session_group(wd['group'])
|
|
4144
4181
|
wave = self._find_wave(wave_name)
|
|
4145
4182
|
if wave:
|
|
4146
4183
|
bits = wd.get('twos_complement_bits')
|
|
@@ -4189,6 +4226,19 @@ class PgWaveWindow(QMainWindow):
|
|
|
4189
4226
|
xv = math.log10(xv)
|
|
4190
4227
|
p._set_cursor(which[-1], xv)
|
|
4191
4228
|
|
|
4229
|
+
def _fetch_session_group(self, group):
|
|
4230
|
+
"""Fetch a catalog group a session's plot needs, if still pending."""
|
|
4231
|
+
for wf in self.browser.files.values():
|
|
4232
|
+
if hasattr(wf, 'isPendingGroup') and wf.isPendingGroup(group):
|
|
4233
|
+
try:
|
|
4234
|
+
wf.loadGroup(group)
|
|
4235
|
+
except Exception as e:
|
|
4236
|
+
QMessageBox.critical(
|
|
4237
|
+
self, "Fetch",
|
|
4238
|
+
"Failed to fetch %s:\n%s" % (group, e))
|
|
4239
|
+
self.browser._fill_waves()
|
|
4240
|
+
return
|
|
4241
|
+
|
|
4192
4242
|
def _find_wave(self, name):
|
|
4193
4243
|
"""Look up a PgWave by column name across all loaded files."""
|
|
4194
4244
|
for fkey, wf in self.browser.files.items():
|
|
@@ -470,6 +470,9 @@ class WaveFile():
|
|
|
470
470
|
self._group_loader = group_loader
|
|
471
471
|
self._pending_groups = list(groups)
|
|
472
472
|
self._loaded_groups = set()
|
|
473
|
+
#- Which group produced each wave, so a session can name the one
|
|
474
|
+
#- group it has to fetch to get a plotted wave back.
|
|
475
|
+
self._group_of = dict()
|
|
473
476
|
self._remote = (not self._virtual) and _is_url(fname)
|
|
474
477
|
self._remote_bytes = None
|
|
475
478
|
self._remote_content_type = None
|
|
@@ -1056,10 +1059,19 @@ class WaveFile():
|
|
|
1056
1059
|
if c not in x_columns
|
|
1057
1060
|
and (self._df is None or c not in self._df.columns)]
|
|
1058
1061
|
self._merge_group_frame(frame)
|
|
1062
|
+
#- Every column the fetch brought in, not just the plottable
|
|
1063
|
+
#- ones: a session may name the group's x column too, and it is
|
|
1064
|
+
#- just as absent until the group is fetched.
|
|
1065
|
+
for c in frame.columns:
|
|
1066
|
+
self._group_of.setdefault(c, name)
|
|
1059
1067
|
for wave in list(self.waves.values()):
|
|
1060
1068
|
wave.reload()
|
|
1061
1069
|
return added
|
|
1062
1070
|
|
|
1071
|
+
def groupOf(self, name):
|
|
1072
|
+
"""The catalog group *name* came from, or None if it stands alone."""
|
|
1073
|
+
return self._group_of.get(name)
|
|
1074
|
+
|
|
1063
1075
|
def _merge_group_frame(self, frame):
|
|
1064
1076
|
"""Append a fetched group's rows, widening the frame as needed.
|
|
1065
1077
|
|
|
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
|