cometx 3.6.7__tar.gz → 3.6.8__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.
Files changed (58) hide show
  1. {cometx-3.6.7 → cometx-3.6.8}/PKG-INFO +1 -1
  2. {cometx-3.6.7 → cometx-3.6.8}/cometx/_version.py +1 -1
  3. {cometx-3.6.7 → cometx-3.6.8}/cometx/utils.py +71 -0
  4. cometx-3.6.8/cometx/views.py +877 -0
  5. {cometx-3.6.7 → cometx-3.6.8}/cometx.egg-info/PKG-INFO +1 -1
  6. {cometx-3.6.7 → cometx-3.6.8}/cometx.egg-info/SOURCES.txt +1 -0
  7. {cometx-3.6.7 → cometx-3.6.8}/LICENSE +0 -0
  8. {cometx-3.6.7 → cometx-3.6.8}/README.md +0 -0
  9. {cometx-3.6.7 → cometx-3.6.8}/cometx/__init__.py +0 -0
  10. {cometx-3.6.7 → cometx-3.6.8}/cometx/_typing.py +0 -0
  11. {cometx-3.6.7 → cometx-3.6.8}/cometx/_ui.py +0 -0
  12. {cometx-3.6.7 → cometx-3.6.8}/cometx/api.py +0 -0
  13. {cometx-3.6.7 → cometx-3.6.8}/cometx/audio.py +0 -0
  14. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/__init__.py +0 -0
  15. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin.py +0 -0
  16. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_app.py +0 -0
  17. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_gpu_app.py +0 -0
  18. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_gpu_report.py +0 -0
  19. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_growth_render.py +0 -0
  20. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_growth_report.py +0 -0
  21. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_optimizer_report.py +0 -0
  22. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_usage_report.py +0 -0
  23. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/admin_utils.py +0 -0
  24. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/config.py +0 -0
  25. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/copy.py +0 -0
  26. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/copy_utils.py +0 -0
  27. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/count.py +0 -0
  28. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/delete_assets.py +0 -0
  29. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/download.py +0 -0
  30. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/list_command.py +0 -0
  31. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/log.py +0 -0
  32. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/migrate_users.py +0 -0
  33. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/rename_duplicates.py +0 -0
  34. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/reproduce.py +0 -0
  35. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/smoke_test.py +0 -0
  36. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/update.py +0 -0
  37. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/upload_optimizer_experiments.py +0 -0
  38. {cometx-3.6.7 → cometx-3.6.8}/cometx/cli/utils.py +0 -0
  39. {cometx-3.6.7 → cometx-3.6.8}/cometx/framework/__init__.py +0 -0
  40. {cometx-3.6.7 → cometx-3.6.8}/cometx/framework/comet/__init__.py +0 -0
  41. {cometx-3.6.7 → cometx-3.6.8}/cometx/framework/comet/download_manager.py +0 -0
  42. {cometx-3.6.7 → cometx-3.6.8}/cometx/framework/neptune.py +0 -0
  43. {cometx-3.6.7 → cometx-3.6.8}/cometx/framework/wandb.py +0 -0
  44. {cometx-3.6.7 → cometx-3.6.8}/cometx/generate_utils.py +0 -0
  45. {cometx-3.6.7 → cometx-3.6.8}/cometx/magics.py +0 -0
  46. {cometx-3.6.7 → cometx-3.6.8}/cometx/optimizer.py +0 -0
  47. {cometx-3.6.7 → cometx-3.6.8}/cometx/panel_utils.py +0 -0
  48. {cometx-3.6.7 → cometx-3.6.8}/cometx/tools/__init__.py +0 -0
  49. {cometx-3.6.7 → cometx-3.6.8}/cometx/tools/dataset.py +0 -0
  50. {cometx-3.6.7 → cometx-3.6.8}/cometx/tools/pointcloud.py +0 -0
  51. {cometx-3.6.7 → cometx-3.6.8}/cometx.egg-info/dependency_links.txt +0 -0
  52. {cometx-3.6.7 → cometx-3.6.8}/cometx.egg-info/entry_points.txt +0 -0
  53. {cometx-3.6.7 → cometx-3.6.8}/cometx.egg-info/requires.txt +0 -0
  54. {cometx-3.6.7 → cometx-3.6.8}/cometx.egg-info/top_level.txt +0 -0
  55. {cometx-3.6.7 → cometx-3.6.8}/pyproject.toml +0 -0
  56. {cometx-3.6.7 → cometx-3.6.8}/setup.cfg +0 -0
  57. {cometx-3.6.7 → cometx-3.6.8}/setup.py +0 -0
  58. {cometx-3.6.7 → cometx-3.6.8}/tests/testlib.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cometx
3
- Version: 3.6.7
3
+ Version: 3.6.8
4
4
  Summary: Python tools for Comet
5
5
  Home-page: https://github.com/comet-ml/cometx/
6
6
  Author: cometx development team
@@ -12,5 +12,5 @@
12
12
  # the express permission of Comet ML Inc.
13
13
  # *******************************************************
14
14
 
15
- version_info = (3, 6, 7)
15
+ version_info = (3, 6, 8)
16
16
  __version__ = ".".join(map(str, version_info))
@@ -304,6 +304,77 @@ def get_unit_label_plural(unit):
304
304
  return labels.get(unit, unit.capitalize() + "s")
305
305
 
306
306
 
307
+ def resolve_workspace(workspace=None, api=None):
308
+ """
309
+ Return a workspace name, falling back to the account's default.
310
+
311
+ Args:
312
+ workspace: (str, optional) a workspace name; returned unchanged if given
313
+ api: (API, optional) an API instance to reuse; one is created if omitted
314
+
315
+ Returns: the given workspace, the configured default workspace, or -- if
316
+ neither is set and the account has exactly one workspace -- that one.
317
+
318
+ Raises:
319
+ ValueError: if no workspace was given, no default is configured, and the
320
+ account has several workspaces to choose between.
321
+ """
322
+ if workspace:
323
+ return workspace
324
+
325
+ if api is None:
326
+ from .api import API
327
+
328
+ api = API()
329
+
330
+ configured = api.get_default_workspace()
331
+ if configured:
332
+ return configured
333
+
334
+ workspaces = api.get_workspaces()
335
+ if len(workspaces) == 1:
336
+ return workspaces[0]
337
+
338
+ raise ValueError(
339
+ "Unable to pick a workspace: several are available -- %s"
340
+ % ", ".join(sorted(workspaces))
341
+ )
342
+
343
+
344
+ def get_project_url(workspace, project_name, api=None):
345
+ """
346
+ Return the URL of a project in the Comet UI.
347
+
348
+ Args:
349
+ workspace: (str) the workspace name
350
+ project_name: (str) the project name
351
+ api: (API, optional) an API instance to reuse; one is created if omitted
352
+
353
+ Returns: the project's URL, on whichever server the API is configured for.
354
+ """
355
+ if api is None:
356
+ from .api import API
357
+
358
+ api = API()
359
+
360
+ return "%s/%s/%s" % (api.server_url.rstrip("/"), workspace, project_name)
361
+
362
+
363
+ def get_first_experiment(api, workspace, project_name):
364
+ """
365
+ Return one APIExperiment from a project, for code that just needs an example.
366
+
367
+ Args:
368
+ api: (API) an API instance
369
+ workspace: (str) the workspace name
370
+ project_name: (str) the project name
371
+
372
+ Returns: the project's first APIExperiment, or None if the project is empty.
373
+ """
374
+ experiments = api.get_experiments(workspace, project_name)
375
+ return experiments[0] if experiments else None
376
+
377
+
307
378
  def remove_extra_slashes(path):
308
379
  if path:
309
380
  if path.startswith("/"):
@@ -0,0 +1,877 @@
1
+ # -*- coding: utf-8 -*-
2
+ # ****************************************
3
+ # __
4
+ # _________ ____ ___ ___ / /__ __
5
+ # / ___/ __ \/ __ `__ \/ _ \/ __/ |/_/
6
+ # / /__/ /_/ / / / / / / __/ /__> <
7
+ # \___/\____/_/ /_/ /_/\___/\__/_/|_|
8
+ #
9
+ #
10
+ # Copyright (c) 2026 Cometx Development
11
+ # Team. All rights reserved.
12
+ # ****************************************
13
+ """
14
+ cometx.views -- a builder library for Comet dashboard Views.
15
+
16
+ Views are the saved dashboards and per-experiment chart layouts in the Comet UI.
17
+ This module builds them programmatically: import the panel factories, compose
18
+ them into ``Section`` objects, and hand the result to ``build_view()`` (project
19
+ dashboards) or ``build_experiment_view()`` (per-experiment views).
20
+
21
+ from cometx.views import Section, build_view, line_panel, scalar_panel
22
+
23
+ view = build_view(
24
+ "My Dashboard",
25
+ sections=[
26
+ Section("At a glance", [scalar_panel("accuracy", aggregation="max")]),
27
+ Section("Curves", [line_panel(["loss", "val_loss"], smoothing=0.6)]),
28
+ ],
29
+ )
30
+ API().create_view(workspace, project_name, view)
31
+
32
+ The Comet SDK exposes four functions for working with saved dashboard views:
33
+
34
+ API.get_views(workspace, project_name) -> list[View]
35
+ API.create_view(workspace, project_name, v) -> View (project-level)
36
+ APIExperiment.get_views() -> list[View]
37
+ APIExperiment.create_view(view) -> View (experiment-level)
38
+
39
+ A ``View`` is mostly a carrier for the dashboard's serialized layout state, and
40
+ the interesting content lives in ``view.v3`` -- a dict of *sections*, each
41
+ holding *panels* plus a grid *layout*. This module provides typed-ish factories
42
+ for every panel type the dashboard supports, plus ``build_view()`` to assemble
43
+ sections into a ``View`` that the Comet UI renders correctly.
44
+
45
+ Schema notes (derived from views saved by the Comet UI):
46
+
47
+ v3 = {
48
+ "layout": [], # legacy mirror of section[0]; safe to leave empty
49
+ "globalConfig": {...}, # dashboard-wide x-axis / smoothing / sampling
50
+ "sections": [
51
+ {"id": "aB3xY9", "title": "Metrics", "autogenerated": False,
52
+ "expanded": True, "panels": [ ... ], "layout": [ ... ]},
53
+ ],
54
+ "autoGenerate": False, # True lets Comet append auto panels for new metrics
55
+ "isPersonalView": False,
56
+ }
57
+
58
+ chart_state ("dashboardChartState") is the pre-sections legacy format: the
59
+ same panel dicts bucketed by type. We emit it alongside v3 for compatibility
60
+ with older backends and with code that reads the legacy field.
61
+
62
+ query_state ("experimentQueryState") holds the filter/segment state.
63
+ table_state ("reactGridTableState") holds the experiment table config.
64
+
65
+ Experiment-level views (the chart-template scope, written by
66
+ ``APIExperiment.create_view``) use a *different* envelope: the sections live in
67
+ ``view.v2["panels"]`` and the display settings sit at the top of ``v2``, with
68
+ ``v3`` unused. ``build_view`` targets the project scope and
69
+ ``build_experiment_view`` the experiment scope; sections and panels are
70
+ identical between the two.
71
+
72
+ Nothing here calls the network; ``build_view`` returns a plain ``View`` that you
73
+ pass to ``API.create_view`` or ``APIExperiment.create_view``.
74
+ """
75
+
76
+ import json
77
+ import random
78
+ import string
79
+ from typing import Any, Dict, List, Optional, Sequence, Union
80
+
81
+ from comet_ml.api_objects.view import View
82
+
83
+ __all__ = [
84
+ "new_id",
85
+ "Section",
86
+ "build_view",
87
+ "build_experiment_view",
88
+ "line_panel",
89
+ "bar_panel",
90
+ "scatter_panel",
91
+ "parallel_panel",
92
+ "scalar_panel",
93
+ "image_panel",
94
+ "data_panel",
95
+ "custom_panel",
96
+ "python_panel",
97
+ "legend_key",
98
+ "global_config",
99
+ "table_state",
100
+ "query_state",
101
+ "rule",
102
+ "any_of",
103
+ "all_of",
104
+ "describe_view",
105
+ ]
106
+
107
+ # The dashboard grid is 6 units wide. A panel with w=2 is one third of a row.
108
+ GRID_WIDTH = 6
109
+
110
+ _ID_ALPHABET = string.ascii_letters + string.digits
111
+
112
+
113
+ def new_id(length: int = 6) -> str:
114
+ """Return a random id in the same style the UI generates ('F5ReNw')."""
115
+ return "".join(random.choice(_ID_ALPHABET) for _ in range(length))
116
+
117
+
118
+ # ---------------------------------------------------------------------------
119
+ # Legend keys
120
+ # ---------------------------------------------------------------------------
121
+
122
+
123
+ def legend_key(
124
+ value: str = "Name", label: Optional[str] = None, source: str = "log_other"
125
+ ) -> Dict[str, Any]:
126
+ """One entry of a chart's ``selectedLegendKeys``.
127
+
128
+ Common combinations:
129
+ legend_key("Name") # experiment name
130
+ legend_key("experimentTags", "Tags", "tags") # tags
131
+ legend_key("learning_rate", source="params") # a hyperparameter
132
+ """
133
+ return {"value": value, "label": label or value, "source": source}
134
+
135
+
136
+ DEFAULT_LEGEND = [legend_key("Name")]
137
+
138
+
139
+ # ---------------------------------------------------------------------------
140
+ # Panel factories. Each returns a v3 panel dict with a fresh chartId.
141
+ # ---------------------------------------------------------------------------
142
+
143
+
144
+ def line_panel(
145
+ y: Union[str, Sequence[str]],
146
+ x: str = "step",
147
+ name: str = "",
148
+ smoothing: float = 0,
149
+ transform_y: Optional[str] = None,
150
+ outliers: str = "show",
151
+ sample_size: int = 100,
152
+ legend: Optional[List[Dict[str, Any]]] = None,
153
+ locked: bool = False,
154
+ ) -> Dict[str, Any]:
155
+ """A line chart of one or more metrics over an x-axis.
156
+
157
+ Args:
158
+ y: metric name, or a list of metric names to overlay in one chart.
159
+ x: "step", "duration", "epoch", "wall", or any logged metric name.
160
+ transform_y: None, "log", "ln", "sqrt" -- y-axis transform.
161
+ smoothing: 0..1 exponential smoothing factor.
162
+ outliers: "show" or "hide".
163
+ sample_size: max points fetched per curve.
164
+ locked: True pins this panel's config against dashboard-level changes.
165
+ """
166
+ metric_names = [y] if isinstance(y, str) else list(y)
167
+ return {
168
+ "chartType": "line",
169
+ "chartId": new_id(),
170
+ "chartName": name,
171
+ "selectedXAxis": x,
172
+ "selectedYAxis": metric_names[0],
173
+ "selectedOutliers": outliers,
174
+ "selectedLegendKeys": legend or DEFAULT_LEGEND,
175
+ "transformX": None,
176
+ "transformY": transform_y,
177
+ "smoothingX": 0,
178
+ "smoothingY": smoothing,
179
+ "metricName": "",
180
+ "metricNames": metric_names,
181
+ "locked": locked,
182
+ "sampleSize": sample_size,
183
+ }
184
+
185
+
186
+ def bar_panel(
187
+ metric: str,
188
+ name: str = "",
189
+ aggregation: str = "last",
190
+ orientation: str = "v",
191
+ plot_type: str = "BAR",
192
+ group_by_aggregation: Optional[str] = None,
193
+ legend: Optional[List[Dict[str, Any]]] = None,
194
+ ) -> Dict[str, Any]:
195
+ """A bar chart of one metric, one bar per experiment.
196
+
197
+ Args:
198
+ aggregation: how to reduce a metric's history to one number --
199
+ "last", "min", "max", "average", "sum".
200
+ orientation: "v" (vertical bars) or "h".
201
+ plot_type: "BAR", "BOX", or "VIOLIN".
202
+ group_by_aggregation: if set (e.g. "mean"), bars are grouped and
203
+ aggregated instead of one-per-experiment.
204
+ """
205
+ return {
206
+ "chartType": "bar",
207
+ "chartId": new_id(),
208
+ "chartName": name,
209
+ "selectedXAxis": metric,
210
+ "metricName": metric,
211
+ "aggregationX": aggregation,
212
+ "orientation": orientation,
213
+ "plotType": plot_type,
214
+ "grouping": {
215
+ "enabled": group_by_aggregation is not None,
216
+ "plotType": plot_type,
217
+ "aggregation": group_by_aggregation or "mean",
218
+ },
219
+ "customRange": {"x": {"min": "", "max": ""}, "y": {"min": "", "max": ""}},
220
+ "selectedLegendKeys": legend or DEFAULT_LEGEND,
221
+ }
222
+
223
+
224
+ def scatter_panel(
225
+ x: str,
226
+ y: str,
227
+ z: Optional[str] = None,
228
+ name: str = "",
229
+ aggregation: str = "last",
230
+ metrics: Optional[List[str]] = None,
231
+ params: Optional[List[str]] = None,
232
+ outliers: str = "show",
233
+ sample_size: int = 100,
234
+ legend: Optional[List[Dict[str, Any]]] = None,
235
+ ) -> Dict[str, Any]:
236
+ """A 2D or 3D scatter plot; one point per experiment.
237
+
238
+ Axes may be metric names or hyperparameter names. Pass ``z`` for a 3D plot.
239
+ ``metrics``/``params`` declare which of the axes are metrics vs params; when
240
+ omitted every axis is treated as a metric.
241
+ """
242
+ axes = [a for a in (x, y, z) if a]
243
+ panel = {
244
+ "chartType": "scatter",
245
+ "chartId": new_id(),
246
+ "chartName": name,
247
+ "selectedXAxis": x,
248
+ "selectedYAxis": y,
249
+ "selectedOutliers": outliers,
250
+ "sampleSize": sample_size,
251
+ "selectedLegendKeys": legend or DEFAULT_LEGEND,
252
+ "aggregationX": aggregation,
253
+ "aggregationY": aggregation,
254
+ "metrics": metrics if metrics is not None else [],
255
+ "params": params or [],
256
+ "metricName": "",
257
+ "metricNames": axes if metrics is None else metrics,
258
+ "annotations": {
259
+ "MIN_Y": {"name": "MIN_Y", "label": "Min y-values", "checked": False},
260
+ "MAX_Y": {"name": "MAX_Y", "label": "Max y-values", "checked": False},
261
+ "AVG_Y": {"name": "AVG_Y", "label": "Avg y-values", "checked": False},
262
+ },
263
+ "customRange": {"x": {"min": "", "max": ""}, "y": {"min": "", "max": ""}},
264
+ "legendMode": "auto",
265
+ "locked": False,
266
+ "fromChart": False,
267
+ }
268
+ if z:
269
+ panel["selectedZAxis"] = z
270
+ panel["aggregationZ"] = aggregation
271
+ return panel
272
+
273
+
274
+ def parallel_panel(
275
+ params: Sequence[str],
276
+ metrics: Sequence[str] = (),
277
+ target: str = "",
278
+ name: str = "",
279
+ precision: str = ".3f",
280
+ ) -> Dict[str, Any]:
281
+ """A parallel-coordinates chart -- the standard hyperparameter sweep view.
282
+
283
+ Args:
284
+ params: hyperparameter names, drawn as axes left to right.
285
+ metrics: metric names, drawn as the right-most axes.
286
+ target: which axis colors the lines (usually a metric, e.g. "loss").
287
+ """
288
+ return {
289
+ "chartType": "parallel",
290
+ "chartId": new_id(),
291
+ "chartName": name,
292
+ "params": list(params),
293
+ "metrics": list(metrics),
294
+ "selectedTargetVariable": target or (list(metrics)[0] if metrics else ""),
295
+ "selectedDecimalPrecision": precision,
296
+ "locked": False,
297
+ }
298
+
299
+
300
+ def scalar_panel(
301
+ metric: str,
302
+ aggregation: str = "last",
303
+ name: str = "",
304
+ precision: int = 3,
305
+ description: Optional[str] = None,
306
+ ) -> Dict[str, Any]:
307
+ """A single big-number tile.
308
+
309
+ Args:
310
+ aggregation: "last", "min", "max", "average", "sum".
311
+ description: subtitle. Defaults to Comet's generated wording.
312
+ """
313
+ return {
314
+ "chartType": "scalar",
315
+ "chartId": new_id(),
316
+ "chartName": name,
317
+ "metricName": metric,
318
+ "paramName": "",
319
+ "aggregation": aggregation,
320
+ "precision": precision,
321
+ "description": description
322
+ or "The %s value of %s metric" % (aggregation, metric),
323
+ "isCustomDescription": description is not None,
324
+ "isNumber": True,
325
+ "locked": False,
326
+ }
327
+
328
+
329
+ def image_panel(
330
+ images: Sequence[str] = (),
331
+ step: Optional[int] = None,
332
+ name: str = "",
333
+ experiments_count: int = 25,
334
+ ) -> Dict[str, Any]:
335
+ """An image-grid panel showing logged images across experiments.
336
+
337
+ Args:
338
+ images: logged image file names to display. Empty means "all".
339
+ step: pin the grid to one step; None follows the dashboard.
340
+ """
341
+ return {
342
+ "chartType": "image",
343
+ "chartId": new_id(),
344
+ "chartName": name,
345
+ "images": list(images),
346
+ "step": step,
347
+ "experimentsCount": experiments_count,
348
+ }
349
+
350
+
351
+ def data_panel(
352
+ file_name: str,
353
+ name: str = "",
354
+ axis: int = 0,
355
+ join_type: str = "outer",
356
+ experiments_count: int = 25,
357
+ ) -> Dict[str, Any]:
358
+ """A table panel rendering a logged tabular asset (e.g. a .csv).
359
+
360
+ Args:
361
+ file_name: the logged asset's file name, as passed to log_table().
362
+ axis: 0 concatenates rows across experiments, 1 concatenates columns.
363
+ join_type: "outer" or "inner", when combining across experiments.
364
+ """
365
+ return {
366
+ "chartType": "data",
367
+ "chartId": new_id(),
368
+ "chartName": name,
369
+ "fileName": file_name,
370
+ "index": None,
371
+ "hasData": True,
372
+ "experimentsCount": experiments_count,
373
+ "axis": axis,
374
+ "joinType": join_type,
375
+ "columnFilters": None,
376
+ "columnState": None,
377
+ }
378
+
379
+
380
+ def custom_panel(
381
+ instance_id: str,
382
+ instance_name: str = "",
383
+ name: str = "",
384
+ default_config: str = "",
385
+ ) -> Dict[str, Any]:
386
+ """A JavaScript custom panel from the Panel gallery.
387
+
388
+ ``instance_id``/``instance_name`` identify an existing panel instance and
389
+ cannot be invented -- copy them off a panel already on a dashboard (scan
390
+ existing views with ``get_views()``) or from the panel's URL in the UI.
391
+ """
392
+ panel = {
393
+ "chartType": "custom",
394
+ "chartId": new_id(),
395
+ "instanceId": instance_id,
396
+ "instanceName": instance_name,
397
+ }
398
+ if name:
399
+ panel["chartName"] = name
400
+ if default_config:
401
+ panel["defaultConfig"] = default_config
402
+ return panel
403
+
404
+
405
+ def python_panel(
406
+ template_id: str,
407
+ revision_id: str = "",
408
+ name: str = "",
409
+ code_version: str = "",
410
+ ) -> Dict[str, Any]:
411
+ """A Python panel (a saved Python panel template rendered server-side).
412
+
413
+ Like ``custom_panel``, the ids refer to an existing saved panel template.
414
+ """
415
+ return {
416
+ "chartType": "python",
417
+ "chartId": new_id(),
418
+ "chartName": name,
419
+ "templateId": template_id,
420
+ "revisionId": revision_id,
421
+ "codeVersion": code_version,
422
+ "code": None,
423
+ "locked": False,
424
+ }
425
+
426
+
427
+ # Every legacy chart_state bucket, so older readers see a well-formed dict.
428
+ _LEGACY_BUCKETS = [
429
+ "line",
430
+ "scatter",
431
+ "bar",
432
+ "parallel",
433
+ "scalar",
434
+ "image",
435
+ "data",
436
+ "custom",
437
+ "video",
438
+ "pcd",
439
+ "curves",
440
+ "python",
441
+ "autoGeneratedMetrics",
442
+ "autoGeneratedCurve",
443
+ "autoGeneratedImage",
444
+ "autoGeneratedDataframe",
445
+ ]
446
+
447
+
448
+ # ---------------------------------------------------------------------------
449
+ # Sections
450
+ # ---------------------------------------------------------------------------
451
+
452
+
453
+ class Section:
454
+ """A titled, collapsible group of panels on a dashboard.
455
+
456
+ Args:
457
+ title: the section header shown in the UI.
458
+ panels: panel dicts from the factories above.
459
+ columns: panels per row (1, 2, 3, or 6). Panels flow left to right.
460
+ expanded: whether the section starts open.
461
+ height: row height in grid units (1 is the UI default, max 3).
462
+ """
463
+
464
+ def __init__(
465
+ self,
466
+ title: str,
467
+ panels: Sequence[Dict[str, Any]] = (),
468
+ columns: int = 3,
469
+ expanded: bool = True,
470
+ height: int = 1,
471
+ ):
472
+ if GRID_WIDTH % columns:
473
+ raise ValueError("columns must divide %d evenly" % GRID_WIDTH)
474
+ self.title = title
475
+ self.panels = list(panels)
476
+ self.columns = columns
477
+ self.expanded = expanded
478
+ self.height = height
479
+ self.id = new_id()
480
+
481
+ def add(self, *panels: Dict[str, Any]) -> "Section":
482
+ """Append panels; returns self so calls can be chained."""
483
+ self.panels.extend(panels)
484
+ return self
485
+
486
+ def layout(self) -> List[Dict[str, Any]]:
487
+ """Compute the react-grid-layout entries for this section's panels."""
488
+ width = GRID_WIDTH // self.columns
489
+ entries = []
490
+ for index, panel in enumerate(self.panels):
491
+ entries.append(
492
+ {
493
+ "i": panel["chartId"],
494
+ "x": (index % self.columns) * width,
495
+ "y": (index // self.columns) * self.height,
496
+ "w": width,
497
+ "h": self.height,
498
+ "minW": 2,
499
+ "maxH": 3,
500
+ "moved": False,
501
+ "static": False,
502
+ }
503
+ )
504
+ return entries
505
+
506
+ def to_dict(self) -> Dict[str, Any]:
507
+ return {
508
+ "id": self.id,
509
+ "title": self.title,
510
+ "autogenerated": False,
511
+ "expanded": self.expanded,
512
+ "panels": self.panels,
513
+ "layout": self.layout(),
514
+ }
515
+
516
+
517
+ # ---------------------------------------------------------------------------
518
+ # Dashboard-level state
519
+ # ---------------------------------------------------------------------------
520
+
521
+
522
+ def global_config(
523
+ x_axis: str = "step",
524
+ smoothing: float = 0,
525
+ transform_y: Optional[str] = None,
526
+ outliers: str = "show",
527
+ sample_size: int = 100,
528
+ ) -> Dict[str, Any]:
529
+ """Dashboard-wide defaults applied to every non-``locked`` panel."""
530
+ return {
531
+ "selectedXAxis": x_axis,
532
+ "transformY": transform_y,
533
+ "smoothingY": smoothing,
534
+ "selectedOutliers": outliers,
535
+ "sampleSize": sample_size,
536
+ }
537
+
538
+
539
+ def table_state(
540
+ columns: Sequence[str] = (),
541
+ sort_by: Optional[str] = None,
542
+ descending: bool = True,
543
+ page_size: int = 25,
544
+ decimals: int = 6,
545
+ visible: bool = True,
546
+ column_width: int = 150,
547
+ ) -> str:
548
+ """Serialize the experiment-table configuration (``reactGridTableState``).
549
+
550
+ Args:
551
+ columns: column ids, in display order. Metric and parameter names work
552
+ directly; UI columns include "Name", "duration", "experimentTags",
553
+ "end_server_timestamp", "user_name".
554
+ sort_by: column to sort on.
555
+ """
556
+ fixed = ["pinned", "singleExperimentBtn", "isVisibleOnDashboard"]
557
+ ordered = fixed + [c for c in columns if c not in fixed]
558
+ state = {
559
+ "columnGrouping": [],
560
+ "columnOrders": ordered,
561
+ "columnSorting": (
562
+ [{"columnName": sort_by, "direction": "desc" if descending else "asc"}]
563
+ if sort_by
564
+ else []
565
+ ),
566
+ "columnWidths": [
567
+ {"columnName": c, "width": column_width} for c in ordered if c not in fixed
568
+ ],
569
+ "decimalsPrecision": decimals,
570
+ "expandedGroups": [],
571
+ "isVisible": visible,
572
+ "pageSize": page_size,
573
+ "selection": [],
574
+ "hiddenExperiments": [],
575
+ }
576
+ return json.dumps(state)
577
+
578
+
579
+ def rule(
580
+ field: str,
581
+ operator: str,
582
+ value: Any,
583
+ source: str = "metrics",
584
+ type_: Optional[str] = None,
585
+ ) -> Dict[str, Any]:
586
+ """One filter condition.
587
+
588
+ Args:
589
+ field: metric / parameter / column name.
590
+ operator: "greater", "less", "equal", "not_equal", "contains",
591
+ "not_contains", "is_not_empty", "begins_with".
592
+ source: "metrics", "params", "log_other", "tags", "metadata".
593
+ type_: value type -- inferred from ``value`` when omitted
594
+ ("double", "string", "boolean").
595
+ """
596
+ if type_ is None:
597
+ if isinstance(value, bool):
598
+ type_ = "boolean"
599
+ elif isinstance(value, (int, float)):
600
+ type_ = "double"
601
+ else:
602
+ type_ = "string"
603
+ return {
604
+ "type": type_,
605
+ "field": field,
606
+ "source": source,
607
+ "value": value,
608
+ "operator": operator,
609
+ }
610
+
611
+
612
+ def all_of(*rules: Dict[str, Any]) -> Dict[str, Any]:
613
+ """AND-group of rules; use as one branch inside ``query_state``."""
614
+ return {"condition": "AND", "rules": list(rules)}
615
+
616
+
617
+ def any_of(*groups: Dict[str, Any]) -> Dict[str, Any]:
618
+ """OR of AND-groups -- the shape the filter builder produces."""
619
+ return {"condition": "OR", "rules": list(groups) or [all_of()]}
620
+
621
+
622
+ def query_state(
623
+ rules_tree: Optional[Dict[str, Any]] = None, segment_id: str = ""
624
+ ) -> str:
625
+ """Serialize the filter state (``experimentQueryState``).
626
+
627
+ Example:
628
+ query_state(any_of(all_of(rule("accuracy", "greater", 0.9))))
629
+ """
630
+ return json.dumps(
631
+ {
632
+ "activeSegment": {"segment_id": segment_id},
633
+ "rulesTree": rules_tree or any_of(),
634
+ "filters": [],
635
+ }
636
+ )
637
+
638
+
639
+ # ---------------------------------------------------------------------------
640
+ # Assembly
641
+ # ---------------------------------------------------------------------------
642
+
643
+
644
+ def build_view(
645
+ name: str,
646
+ sections: Sequence[Section],
647
+ filters: Optional[str] = None,
648
+ table: Optional[str] = None,
649
+ config: Optional[Dict[str, Any]] = None,
650
+ auto_generate: bool = False,
651
+ personal: bool = False,
652
+ ) -> View:
653
+ """Assemble sections into a ``View`` ready for ``create_view``.
654
+
655
+ Args:
656
+ name: the view's display name.
657
+ sections: ``Section`` objects, top to bottom.
658
+ filters: output of ``query_state()``, or None for no filtering.
659
+ table: output of ``table_state()``, or None to keep the UI default.
660
+ config: output of ``global_config()``.
661
+ auto_generate: True lets Comet append auto-generated panels for
662
+ newly logged metrics underneath your sections.
663
+ personal: mark the view as personal (visible only to you).
664
+
665
+ Returns:
666
+ A ``View``. Pass it to ``API.create_view(workspace, project, view)`` or
667
+ ``APIExperiment.create_view(view)``.
668
+ """
669
+ section_dicts = [s.to_dict() for s in sections]
670
+ all_panels = [p for s in section_dicts for p in s["panels"]]
671
+
672
+ v3 = {
673
+ "layout": [], # legacy top-level mirror; sections carry the real layout
674
+ "globalConfig": config or global_config(),
675
+ "sections": section_dicts,
676
+ "autoGenerate": auto_generate,
677
+ "isPersonalView": personal,
678
+ }
679
+
680
+ charts: Dict[str, Any] = {bucket: [] for bucket in _LEGACY_BUCKETS}
681
+ for panel in all_panels:
682
+ charts.setdefault(panel["chartType"], []).append(panel)
683
+ charts["numberOfCharts"] = len(all_panels)
684
+
685
+ return View(
686
+ name=name,
687
+ chart_state=json.dumps({"charts": charts, "layout": None}),
688
+ query_state=filters if filters is not None else query_state(),
689
+ table_state=table,
690
+ v2={
691
+ "toggles": {
692
+ "DASHBOARD_TOGGLE": {"toggleId": "DASHBOARD_TOGGLE", "isToggled": True},
693
+ "EXPERIMENT_TABLE_TOGGLE": {
694
+ "toggleId": "EXPERIMENT_TABLE_TOGGLE",
695
+ "isToggled": True,
696
+ },
697
+ }
698
+ },
699
+ v3=v3,
700
+ )
701
+
702
+
703
+ def build_experiment_view(
704
+ name: str,
705
+ sections: Sequence[Section],
706
+ x_axis: str = "step",
707
+ smoothing: float = 0,
708
+ transform_y: Optional[str] = None,
709
+ sample_size: int = 100,
710
+ auto_generate: bool = False,
711
+ auto_refresh: bool = True,
712
+ media_sort_by: str = "name",
713
+ media_order: str = "desc",
714
+ group_media: bool = False,
715
+ ) -> View:
716
+ """Assemble sections into a ``View`` for ``APIExperiment.create_view``.
717
+
718
+ Experiment-scoped views are stored in the chart-template tables, which use a
719
+ different envelope than project dashboards: everything lives under ``v2``
720
+ (sections inside ``v2["panels"]``), and the per-tab display settings for
721
+ images / text / audio / histograms / confusion matrices sit alongside them.
722
+
723
+ Args:
724
+ name: the view's display name.
725
+ sections: ``Section`` objects -- the same ones ``build_view`` takes.
726
+ x_axis: "step", "duration", "wall", or "epoch".
727
+ smoothing: 0..1 curve smoothing applied to every chart.
728
+ transform_y: None, "log", "ln", "sqrt".
729
+ sample_size: max points fetched per curve.
730
+ auto_generate: let Comet append auto panels for new metrics.
731
+ auto_refresh: refresh the view while the run is live.
732
+ media_sort_by / media_order / group_media: sorting of the image, audio
733
+ and text tabs.
734
+
735
+ Returns:
736
+ A ``View`` to pass to ``APIExperiment.create_view(view)``. Do not set
737
+ ``experiment_key`` -- ``create_view`` stamps it on for you.
738
+ """
739
+ media = {"groupBy": group_media, "orderBy": media_order, "sortBy": media_sort_by}
740
+ v2 = {
741
+ "template_name": name,
742
+ "x_axis": x_axis,
743
+ "smoothing": smoothing,
744
+ "transformY": transform_y,
745
+ "sampleSize": sample_size,
746
+ "entire_row_charts": False,
747
+ "charts": [], # legacy per-experiment chart list; sections supersede it
748
+ "outliers": {"percentile": 0, "isVisible": True},
749
+ "panels": {
750
+ "version": 5,
751
+ "sections": [s.to_dict() for s in sections],
752
+ "panels": [],
753
+ "layout": [],
754
+ "compareXAxis": x_axis,
755
+ "autoGenerate": auto_generate,
756
+ "isAutoRefreshEnabled": auto_refresh,
757
+ },
758
+ "autoGeneratedDraft": False,
759
+ "deleteAutogenerated": False,
760
+ "projectName": "",
761
+ "project_id": "",
762
+ "metricColors": [],
763
+ "metricColorsV2": {
764
+ "singleExperiment": {
765
+ "isSingleExperimentColor": False,
766
+ "colors": [],
767
+ "singleExperimentColor": False,
768
+ }
769
+ },
770
+ "lastUpdate": 0,
771
+ "layout": [],
772
+ "images": dict(media),
773
+ "audio": dict(media),
774
+ "text": {
775
+ "groupBy": group_media,
776
+ "orderBy": media_order,
777
+ "sortBy": "step",
778
+ "fullHeightToggle": False,
779
+ "step": -1,
780
+ },
781
+ "confusionMatrix": {
782
+ "assetsIds": [],
783
+ "compareAssetsIds": [],
784
+ "cellValue": "Counts",
785
+ "colorDistribution": "Equal",
786
+ },
787
+ "histogram": {
788
+ "groupBy": group_media,
789
+ "ignoreOutliers": 0.1,
790
+ "maxSteps": 50,
791
+ "orderBy": media_order,
792
+ "sortBy": "name",
793
+ },
794
+ "metrics": {"decimalsPrecision": None},
795
+ "hyperParams": {"decimalsPrecision": None},
796
+ "otherParams": {"decimalsPrecision": None},
797
+ }
798
+ return View(name=name, v2=v2)
799
+
800
+
801
+ # ---------------------------------------------------------------------------
802
+ # Inspection
803
+ # ---------------------------------------------------------------------------
804
+
805
+
806
+ def describe_view(view: View, indent: str = "") -> str:
807
+ """Render a human-readable outline of a View's sections and panels."""
808
+ lines = ["%s%s" % (indent, view.name)]
809
+ meta = []
810
+ if view.view_source:
811
+ meta.append(view.view_source)
812
+ if view.template_id:
813
+ meta.append("id=%s" % view.template_id)
814
+ if view.experiment_key:
815
+ meta.append("experiment=%s" % view.experiment_key[:8])
816
+ if view.project_default:
817
+ meta.append("project default")
818
+ if meta:
819
+ lines.append("%s [%s]" % (indent, ", ".join(meta)))
820
+
821
+ sections = None
822
+ if isinstance(view.v3, dict):
823
+ sections = view.v3.get("sections")
824
+ if not sections and isinstance(view.v2, dict):
825
+ # Experiment-scoped view: sections live under v2["panels"].
826
+ sections = (view.v2.get("panels") or {}).get("sections")
827
+ if sections:
828
+ for section in sections:
829
+ flag = " (auto)" if section.get("autogenerated") else ""
830
+ lines.append("%s - section %r%s" % (indent, section.get("title"), flag))
831
+ for panel in section.get("panels", []):
832
+ lines.append("%s * %s" % (indent, _panel_label(panel)))
833
+ else:
834
+ # Older view: read the legacy bucketed chart_state.
835
+ try:
836
+ charts = json.loads(view.chart_state or "{}").get("charts", {})
837
+ except ValueError:
838
+ charts = {}
839
+ panels = [p for v in charts.values() if isinstance(v, list) for p in v]
840
+ if panels:
841
+ lines.append("%s - (legacy chart_state, no sections)" % indent)
842
+ for panel in panels:
843
+ lines.append("%s * %s" % (indent, _panel_label(panel)))
844
+ else:
845
+ lines.append("%s - (no panels)" % indent)
846
+
847
+ try:
848
+ tree = json.loads(view.query_state or "{}").get("rulesTree", {})
849
+ except ValueError:
850
+ tree = {}
851
+ conditions = [r for group in tree.get("rules", []) for r in group.get("rules", [])]
852
+ for condition in conditions:
853
+ lines.append(
854
+ "%s filter: %s %s %r"
855
+ % (
856
+ indent,
857
+ condition.get("field"),
858
+ condition.get("operator"),
859
+ condition.get("value"),
860
+ )
861
+ )
862
+ return "\n".join(lines)
863
+
864
+
865
+ def _panel_label(panel: Dict[str, Any]) -> str:
866
+ kind = panel.get("chartType", "?")
867
+ name = panel.get("chartName") or panel.get("instanceName") or ""
868
+ detail = (
869
+ panel.get("metricNames")
870
+ or panel.get("metricName")
871
+ or panel.get("fileName")
872
+ or panel.get("images")
873
+ or panel.get("params")
874
+ or ""
875
+ )
876
+ label = "%-9s %s" % (kind, name)
877
+ return "%s %s" % (label.rstrip(), detail) if detail else label.rstrip()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cometx
3
- Version: 3.6.7
3
+ Version: 3.6.8
4
4
  Summary: Python tools for Comet
5
5
  Home-page: https://github.com/comet-ml/cometx/
6
6
  Author: cometx development team
@@ -13,6 +13,7 @@ cometx/magics.py
13
13
  cometx/optimizer.py
14
14
  cometx/panel_utils.py
15
15
  cometx/utils.py
16
+ cometx/views.py
16
17
  cometx.egg-info/PKG-INFO
17
18
  cometx.egg-info/SOURCES.txt
18
19
  cometx.egg-info/dependency_links.txt
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
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