noodle-bucket 2.7.3__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.
@@ -0,0 +1,3 @@
1
+ node: $Format:%H$
2
+ node-date: $Format:%cI$
3
+ describe-name: $Format:%(describe:tags=true,match=v[0-9]*)$
@@ -0,0 +1 @@
1
+ .git_archival.txt export-subst
@@ -0,0 +1,44 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+
4
+ # Distribution / packaging
5
+ uv.lock
6
+
7
+ # pyenv
8
+ .python-version
9
+
10
+ # ZSH autocomplete
11
+ bin/zsh/.zcompdump
12
+
13
+ example_file_store.db
14
+ example_*.bktgz
15
+ example_*.db
16
+
17
+ # test coverage
18
+ .coverage
19
+
20
+ dist/
21
+ dev-dist/
22
+ # Demo report written at the repo root; anchored so the pattern doesn't also
23
+ # exclude viewer/index.html (hatchling applies these patterns to builds).
24
+ /index.html
25
+
26
+ # macOS
27
+ .DS_Store
28
+ *.iconset/
29
+
30
+ # Node modules
31
+ electron/node_modules/
32
+
33
+ # VSCode settings (do not check in)
34
+ .vscode/settings.json
35
+
36
+ # Local Copilot instructions (do not check in)
37
+ .github/copilot-instructions.md
38
+ .vscode/settings.json
39
+
40
+ # Example coverage files (do not check in)
41
+ example*.bktgz
42
+
43
+ # Stress example local output
44
+ /output/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-2025 Noodle-Bytes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,115 @@
1
+ Metadata-Version: 2.5
2
+ Name: noodle-bucket
3
+ Version: 2.7.3
4
+ Summary: Functional coverage written in Python
5
+ Project-URL: Homepage, https://github.com/Noodle-Bytes/bucket
6
+ Project-URL: Documentation, https://noodle-bytes.github.io/bucket/docs/
7
+ Project-URL: Repository, https://github.com/Noodle-Bytes/bucket
8
+ Project-URL: Coverage viewer, https://noodle-bytes.github.io/bucket/
9
+ Project-URL: Issues, https://github.com/Noodle-Bytes/bucket/issues
10
+ Author-email: Stuart Alldred <stuartalldred@gmail.com>, Edward Kotarski <edktrsk@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: cocotb,functional-coverage,verification
14
+ Classifier: Development Status :: 5 - Production/Stable
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
24
+ Classifier: Topic :: Software Development :: Testing
25
+ Requires-Python: >=3.11
26
+ Requires-Dist: click>=8.1.3
27
+ Requires-Dist: pydantic>=2.8.2
28
+ Requires-Dist: rich>=13.3.4
29
+ Requires-Dist: sqlalchemy>=2.0.40
30
+ Provides-Extra: dev
31
+ Requires-Dist: mkdocs>=1.6.0; extra == 'dev'
32
+ Requires-Dist: pre-commit>=3.8.0; extra == 'dev'
33
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
34
+ Requires-Dist: pytest<9,>=8.0.1; extra == 'dev'
35
+ Description-Content-Type: text/markdown
36
+
37
+ <!--
38
+ ~ SPDX-License-Identifier: MIT
39
+ ~ Copyright (c) 2023-2026 Noodle-Bytes. All Rights Reserved
40
+ -->
41
+
42
+ <!--
43
+ ~ SPDX-License-Identifier: MIT
44
+ ~ Copyright (c) 2023-2024 Vypercore. All Rights Reserved
45
+ -->
46
+ <div align="center">
47
+ <img alt="Bucket logo" src="https://raw.githubusercontent.com/Noodle-Bytes/bucket/main/.github/images/Logo-192x192.png">
48
+ </div>
49
+
50
+ # Bucket
51
+
52
+
53
+ Bucket is a python library to create nicer functional coverpoints. There are several benefits writing the coverage in python, such as:
54
+
55
+ * Easier integration into cocotb and other python testbenches
56
+ * No commercial EDA license required
57
+ * Vendor independence - easily collect coverage from multiple tools/sources (eg. models, log parsers, etc)
58
+
59
+ ## Install
60
+
61
+ ```bash
62
+ pip install noodle-bucket
63
+ ```
64
+
65
+ This installs the Python library (`import bucket`) and the `bucket` CLI.
66
+ It is enough to write coverpoints, collect coverage, and export `.bktgz` /
67
+ SQL / JSON / console output. View results in the [hosted viewer](https://noodle-bytes.github.io/bucket/)
68
+ or the desktop app.
69
+
70
+ Generating standalone HTML (`bucket write html` / `bucket write report`)
71
+ needs a source checkout of this repository and Node.js; it is not part of
72
+ the pip package.
73
+
74
+ ## Documentation
75
+
76
+ The user documentation lives in the `docs/` directory and can be read directly on GitHub, starting from [`docs/index.md`](docs/index.md).
77
+
78
+ For a richer browsing experience, you can also run the MkDocs site locally:
79
+
80
+ ```bash
81
+ ./bin/shell
82
+ mkdocs serve
83
+ ```
84
+
85
+ Then open `http://127.0.0.1:8000/` in your browser.
86
+
87
+ ## Viewer
88
+
89
+ A copy of the viewer is hosted at: https://noodle-bytes.github.io/bucket/
90
+
91
+ ## Support
92
+
93
+ This library is being actively built, and is expected to change while it matures and key features are added.
94
+ We are not providing external support for use of this library, however we are aiming to make it as easy to use as possible.
95
+
96
+ As per the licence:
97
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
98
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
99
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
100
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
101
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
102
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
103
+ SOFTWARE.
104
+
105
+ ## Contributions
106
+
107
+ **We will be introducing a contributor licence agreement in the near future. In the meantime, if you want to contribute please get in touch.**
108
+
109
+ Please feel free to contribute to the project, following these guidelines:
110
+
111
+ * Please contribute by creating a fork and submitting a pull request.
112
+ * Pull requests should be as small as possible to resolve the issue they are trying to address.
113
+ * Pull requests must respect the goals of the library, as stated in the documentation.
114
+ * Pull requests should take care not to make performance worse except for cases which require bug fixes.
115
+ * Pull requests should update the documentation for any added/changed functionality.
@@ -0,0 +1,79 @@
1
+ <!--
2
+ ~ SPDX-License-Identifier: MIT
3
+ ~ Copyright (c) 2023-2026 Noodle-Bytes. All Rights Reserved
4
+ -->
5
+
6
+ <!--
7
+ ~ SPDX-License-Identifier: MIT
8
+ ~ Copyright (c) 2023-2024 Vypercore. All Rights Reserved
9
+ -->
10
+ <div align="center">
11
+ <img alt="Bucket logo" src="https://raw.githubusercontent.com/Noodle-Bytes/bucket/main/.github/images/Logo-192x192.png">
12
+ </div>
13
+
14
+ # Bucket
15
+
16
+
17
+ Bucket is a python library to create nicer functional coverpoints. There are several benefits writing the coverage in python, such as:
18
+
19
+ * Easier integration into cocotb and other python testbenches
20
+ * No commercial EDA license required
21
+ * Vendor independence - easily collect coverage from multiple tools/sources (eg. models, log parsers, etc)
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install noodle-bucket
27
+ ```
28
+
29
+ This installs the Python library (`import bucket`) and the `bucket` CLI.
30
+ It is enough to write coverpoints, collect coverage, and export `.bktgz` /
31
+ SQL / JSON / console output. View results in the [hosted viewer](https://noodle-bytes.github.io/bucket/)
32
+ or the desktop app.
33
+
34
+ Generating standalone HTML (`bucket write html` / `bucket write report`)
35
+ needs a source checkout of this repository and Node.js; it is not part of
36
+ the pip package.
37
+
38
+ ## Documentation
39
+
40
+ The user documentation lives in the `docs/` directory and can be read directly on GitHub, starting from [`docs/index.md`](docs/index.md).
41
+
42
+ For a richer browsing experience, you can also run the MkDocs site locally:
43
+
44
+ ```bash
45
+ ./bin/shell
46
+ mkdocs serve
47
+ ```
48
+
49
+ Then open `http://127.0.0.1:8000/` in your browser.
50
+
51
+ ## Viewer
52
+
53
+ A copy of the viewer is hosted at: https://noodle-bytes.github.io/bucket/
54
+
55
+ ## Support
56
+
57
+ This library is being actively built, and is expected to change while it matures and key features are added.
58
+ We are not providing external support for use of this library, however we are aiming to make it as easy to use as possible.
59
+
60
+ As per the licence:
61
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
62
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
63
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
64
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
65
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
66
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
67
+ SOFTWARE.
68
+
69
+ ## Contributions
70
+
71
+ **We will be introducing a contributor licence agreement in the near future. In the meantime, if you want to contribute please get in touch.**
72
+
73
+ Please feel free to contribute to the project, following these guidelines:
74
+
75
+ * Please contribute by creating a fork and submitting a pull request.
76
+ * Pull requests should be as small as possible to resolve the issue they are trying to address.
77
+ * Pull requests must respect the goals of the library, as stated in the documentation.
78
+ * Pull requests should take care not to make performance worse except for cases which require bug fixes.
79
+ * Pull requests should update the documentation for any added/changed functionality.
@@ -0,0 +1,25 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2023-2026 Noodle-Bytes. All Rights Reserved
3
+
4
+ from importlib.metadata import PackageNotFoundError
5
+ from importlib.metadata import version as _version
6
+
7
+ from . import rw
8
+ from .axisutils import AxisUtils
9
+ from .context import CoverageContext
10
+ from .covergroup import Covergroup
11
+ from .coverpoint import Coverpoint
12
+ from .covertop import Covertop
13
+
14
+ # PyPI distribution name. The import package remains `bucket`; `bucket` itself
15
+ # is already taken on PyPI (an unrelated Bitbucket CLI).
16
+ DIST_NAME = "noodle-bucket"
17
+
18
+ try:
19
+ __version__ = _version(DIST_NAME)
20
+ except PackageNotFoundError:
21
+ __version__ = "unknown"
22
+
23
+ version = __version__
24
+
25
+ assert all((CoverageContext, Covergroup, Coverpoint, Covertop, AxisUtils, rw))
@@ -0,0 +1,311 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2023-2026 Noodle-Bytes. All Rights Reserved
3
+
4
+ from pathlib import Path
5
+ from typing import Iterable
6
+
7
+ import click
8
+
9
+ from . import DIST_NAME
10
+ from .rw import (
11
+ ArchiveAccessor,
12
+ ConsoleWriter,
13
+ HTMLWriter,
14
+ JSONAccessor,
15
+ ReportWriter,
16
+ SQLAccessor,
17
+ )
18
+ from .rw.common import MergeReadout, Readout
19
+ from .rw.html import DEFAULT_WEB_PATH
20
+
21
+
22
+ @click.group()
23
+ @click.pass_context
24
+ @click.version_option(package_name=DIST_NAME, prog_name="bucket")
25
+ @click.option(
26
+ "--web-path",
27
+ help="Path to the web viewer (only used by html/report)",
28
+ default=DEFAULT_WEB_PATH,
29
+ show_default=True,
30
+ type=click.Path(path_type=Path),
31
+ )
32
+ def cli(ctx, web_path):
33
+ ctx.obj = {"web_path": web_path}
34
+
35
+
36
+ _VALID_READERS = ["sql", "json", "archive"]
37
+
38
+
39
+ def _split_spec(spec: str) -> tuple[str | None, str, str]:
40
+ """
41
+ Split a readout spec into its components.
42
+
43
+ Valid Formats:
44
+ - `<record>@<type>:<URI>`
45
+ - `<record>@:<URI>`
46
+ - `<type>:<URI>`
47
+ - `<URI>`
48
+
49
+ Returns: (record: str | None, type: str, uri: str)
50
+ """
51
+ record = None
52
+
53
+ # Get record if present
54
+ if "@" in spec:
55
+ record, spec = spec.split("@", 1)
56
+ record = int(record)
57
+
58
+ # Get type if present
59
+ if ":" in spec:
60
+ head, tail = spec.split(":", 1)
61
+ if head in _VALID_READERS:
62
+ typ, uri = head, tail
63
+ elif head == "":
64
+ # `<record>@:<URI>` form — type omitted, infer it from the URI
65
+ typ, uri = None, tail
66
+ else:
67
+ typ, uri = None, spec
68
+ else:
69
+ typ, uri = None, spec
70
+
71
+ # Infer type if missing
72
+ if typ is None:
73
+ if uri.endswith(".db"):
74
+ typ = "sql"
75
+ elif uri.endswith(".json"):
76
+ typ = "json"
77
+ elif uri.endswith(".bktgz"):
78
+ typ = "archive"
79
+ else:
80
+ raise ValueError(
81
+ f"Could not infer reader type from '{uri}'; please specify explicitly."
82
+ )
83
+
84
+ return record, typ, uri
85
+
86
+
87
+ def get_readouts_from_spec(*specs: str) -> Iterable[Readout]:
88
+ "Parse readout specs into Readout objects."
89
+ for spec in specs:
90
+ record, typ, uri = _split_spec(spec)
91
+
92
+ if typ == "sql":
93
+ sql_path = Path(uri)
94
+ assert sql_path.exists(), f"SQL path does not exist: {sql_path}"
95
+ assert sql_path.is_file(), f"SQL path is not a file: {sql_path}"
96
+
97
+ reader = SQLAccessor.File(sql_path).reader()
98
+
99
+ elif typ == "json":
100
+ json_path = Path(uri)
101
+ assert json_path.exists(), f"JSON path does not exist: {uri}"
102
+ assert json_path.is_file(), f"JSON path is not a file: {uri}"
103
+
104
+ reader = JSONAccessor(json_path).reader()
105
+
106
+ elif typ == "archive":
107
+ archive_path = Path(uri)
108
+ assert archive_path.exists(), f"Archive path does not exist: {uri}"
109
+ assert archive_path.is_file(), f"Archive path is not a file: {uri}"
110
+
111
+ reader = ArchiveAccessor(archive_path).reader()
112
+ else:
113
+ raise ValueError(f"Unknown reader type: {typ}")
114
+
115
+ if record is None:
116
+ yield from reader.read_all()
117
+ else:
118
+ yield reader.read(record)
119
+
120
+
121
+ @cli.group()
122
+ @click.pass_context
123
+ @click.option(
124
+ "--read",
125
+ "-r",
126
+ "readout_specs",
127
+ multiple=True,
128
+ type=str,
129
+ help=f"""Can be specified multiple times.
130
+ Valid Formats:
131
+ - `<record>@<type>:<URI>`
132
+ - `<record>@:<URI>`
133
+ - `<type>:<URI>`
134
+ - `<URI>`
135
+
136
+ If the <record> is omitted, all records from the source are read.
137
+ If the <type> is omitted, it is inferred from the URI extension.
138
+ <URI> is interpreted according to the <type> as follows:
139
+ - `sql`: path to an SQL database file
140
+ - `json`: path to a JSON file
141
+ - `archive`: path to a .bktgz archive
142
+
143
+ Valid <type> values are: {', '.join(_VALID_READERS)}.
144
+ """,
145
+ )
146
+ @click.option("--merge", "-m", is_flag=True, default=False, help="Merge all readouts.")
147
+ def write(ctx, readout_specs: Iterable[str], merge: bool):
148
+ ctx.obj = ctx.obj or {}
149
+ readouts = get_readouts_from_spec(*readout_specs)
150
+
151
+ if merge:
152
+ readouts = [MergeReadout(*readouts)]
153
+
154
+ ctx.obj["readouts"] = readouts
155
+
156
+
157
+ @write.command()
158
+ @click.pass_context
159
+ @click.option(
160
+ "--output",
161
+ "-o",
162
+ help="Path to output the JSON",
163
+ required=True,
164
+ type=click.Path(path_type=Path),
165
+ )
166
+ def json(ctx, output: Path):
167
+ readouts = ctx.obj["readouts"]
168
+ writer = JSONAccessor(output).writer()
169
+
170
+ for readout in readouts:
171
+ writer.write(readout)
172
+
173
+
174
+ @write.command()
175
+ @click.pass_context
176
+ @click.option(
177
+ "--output",
178
+ "-o",
179
+ help="Path to output the Archive",
180
+ required=True,
181
+ type=click.Path(path_type=Path),
182
+ )
183
+ def archive(ctx, output: Path):
184
+ readouts = ctx.obj["readouts"]
185
+ writer = ArchiveAccessor(output).writer()
186
+
187
+ for readout in readouts:
188
+ writer.write(readout)
189
+
190
+
191
+ @write.command()
192
+ @click.pass_context
193
+ @click.option(
194
+ "--output",
195
+ "-o",
196
+ help="Path to output the SQL",
197
+ required=True,
198
+ type=click.Path(path_type=Path),
199
+ )
200
+ def sql(ctx, output: Path):
201
+ readouts = ctx.obj["readouts"]
202
+ writer = SQLAccessor.File(output).writer()
203
+
204
+ for readout in readouts:
205
+ writer.write(readout)
206
+
207
+
208
+ @write.command()
209
+ @click.pass_context
210
+ @click.option(
211
+ "--output",
212
+ "-o",
213
+ help="Path to output the HTML report",
214
+ required=True,
215
+ type=click.Path(path_type=Path),
216
+ )
217
+ def html(ctx, output: Path):
218
+ readouts = ctx.obj["readouts"]
219
+ web_path = ctx.obj["web_path"]
220
+ try:
221
+ writer = HTMLWriter(web_path, output)
222
+ for readout in readouts:
223
+ writer.write(readout)
224
+ except RuntimeError as exc:
225
+ raise click.ClickException(str(exc)) from exc
226
+
227
+
228
+ @write.command()
229
+ @click.pass_context
230
+ @click.option(
231
+ "--output",
232
+ "-o",
233
+ help="Path to output the HTML coverage report",
234
+ required=True,
235
+ type=click.Path(path_type=Path),
236
+ )
237
+ @click.option(
238
+ "--max-axis-values",
239
+ "max_axis_values",
240
+ default=64,
241
+ type=click.IntRange(min=0),
242
+ help="Cap on listed values per axis; 0 means unlimited",
243
+ )
244
+ @click.option(
245
+ "--max-tier",
246
+ "max_tier",
247
+ default=None,
248
+ type=click.IntRange(min=0),
249
+ help="Only include coverpoints with tier <= this value",
250
+ )
251
+ @click.option(
252
+ "--tags",
253
+ default=None,
254
+ type=str,
255
+ help="Only include coverpoints with at least one of these comma-separated tags",
256
+ )
257
+ @click.option(
258
+ "--point",
259
+ default=None,
260
+ type=str,
261
+ help="Only include coverpoints matching this dotted-path glob, e.g. 'Pets.dogs*'",
262
+ )
263
+ def report(
264
+ ctx,
265
+ output: Path,
266
+ max_axis_values: int,
267
+ max_tier: int | None,
268
+ tags: str | None,
269
+ point: str | None,
270
+ ):
271
+ readouts = ctx.obj["readouts"]
272
+ web_path = ctx.obj["web_path"]
273
+ try:
274
+ writer = ReportWriter(
275
+ web_path,
276
+ output,
277
+ max_axis_values=max_axis_values,
278
+ max_tier=max_tier,
279
+ tags=[tag.strip() for tag in tags.split(",") if tag.strip()]
280
+ if tags
281
+ else None,
282
+ point=point,
283
+ )
284
+ # A single write: the writer is single-use and all readouts belong in
285
+ # one report document.
286
+ writer.write(list(readouts))
287
+ except RuntimeError as exc:
288
+ raise click.ClickException(str(exc)) from exc
289
+
290
+
291
+ @write.command()
292
+ @click.pass_context
293
+ @click.option("--axes/--no-axes", default=False)
294
+ @click.option("--goals/--no-goals", default=False)
295
+ @click.option("--points/--no-points", default=False)
296
+ @click.option("--summary/--no-summary", default=True)
297
+ def console(
298
+ ctx,
299
+ axes: bool,
300
+ goals: bool,
301
+ points: bool,
302
+ summary: bool,
303
+ ):
304
+ readouts = ctx.obj["readouts"]
305
+ writer = ConsoleWriter(axes=axes, goals=goals, points=points, summary=summary)
306
+ for readout in readouts:
307
+ writer.write(readout)
308
+
309
+
310
+ if __name__ == "__main__":
311
+ cli()