pfund-plot 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. pfund_plot/__init__.py +183 -0
  2. pfund_plot/__main__.py +9 -0
  3. pfund_plot/cli/__init__.py +3 -0
  4. pfund_plot/cli/commands/gallery/__init__.py +15 -0
  5. pfund_plot/cli/commands/gallery/gallery_marimo.py +462 -0
  6. pfund_plot/cli/commands/serve.py +21 -0
  7. pfund_plot/cli/main.py +20 -0
  8. pfund_plot/config.py +109 -0
  9. pfund_plot/enums/__init__.py +16 -0
  10. pfund_plot/enums/dataframe_backend.py +6 -0
  11. pfund_plot/enums/display_mode.py +7 -0
  12. pfund_plot/enums/panel_design.py +8 -0
  13. pfund_plot/enums/panel_theme.py +6 -0
  14. pfund_plot/enums/plotting_backend.py +12 -0
  15. pfund_plot/js_tap/components/candlestick.js +9566 -0
  16. pfund_plot/mixins/streaming_market_feed_mixin.py +162 -0
  17. pfund_plot/plots/altair.py +32 -0
  18. pfund_plot/plots/area/__init__.py +82 -0
  19. pfund_plot/plots/area/bokeh.py +151 -0
  20. pfund_plot/plots/bar/__init__.py +80 -0
  21. pfund_plot/plots/bar/bokeh.py +128 -0
  22. pfund_plot/plots/bokeh.py +32 -0
  23. pfund_plot/plots/candlestick/__init__.py +77 -0
  24. pfund_plot/plots/candlestick/bokeh.py +124 -0
  25. pfund_plot/plots/candlestick/svelte.py +161 -0
  26. pfund_plot/plots/holoviews.py +32 -0
  27. pfund_plot/plots/label/__init__.py +43 -0
  28. pfund_plot/plots/label/bokeh.py +89 -0
  29. pfund_plot/plots/layout/__init__.py +98 -0
  30. pfund_plot/plots/layout/layout.py +116 -0
  31. pfund_plot/plots/layout/panel.py +51 -0
  32. pfund_plot/plots/layout/tabs/__init__.py +36 -0
  33. pfund_plot/plots/layout/tabs/panel.py +51 -0
  34. pfund_plot/plots/lazy.py +408 -0
  35. pfund_plot/plots/line/__init__.py +37 -0
  36. pfund_plot/plots/line/bokeh.py +137 -0
  37. pfund_plot/plots/matplotlib.py +32 -0
  38. pfund_plot/plots/plot.py +1131 -0
  39. pfund_plot/plots/plotly.py +32 -0
  40. pfund_plot/plots/scatter/__init__.py +62 -0
  41. pfund_plot/plots/scatter/bokeh.py +158 -0
  42. pfund_plot/plots/scatter/marker.py +107 -0
  43. pfund_plot/plots/ta.py +6 -0
  44. pfund_plot/renderers/base.py +84 -0
  45. pfund_plot/renderers/browser.py +28 -0
  46. pfund_plot/renderers/desktop.py +109 -0
  47. pfund_plot/renderers/notebook.py +92 -0
  48. pfund_plot/typing.py +29 -0
  49. pfund_plot/utils/__init__.py +176 -0
  50. pfund_plot/utils/bokeh.py +177 -0
  51. pfund_plot/widgets/base.py +76 -0
  52. pfund_plot/widgets/datetime_widget.py +221 -0
  53. pfund_plot/widgets/ticker_widget.py +82 -0
  54. pfund_plot-0.0.1.dist-info/METADATA +148 -0
  55. pfund_plot-0.0.1.dist-info/RECORD +57 -0
  56. pfund_plot-0.0.1.dist-info/WHEEL +4 -0
  57. pfund_plot-0.0.1.dist-info/entry_points.txt +6 -0
pfund_plot/__init__.py ADDED
@@ -0,0 +1,183 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from pfund_plot.plots.altair import (
7
+ Altair as altair,
8
+ )
9
+ from pfund_plot.plots.altair import (
10
+ Altair as vega,
11
+ )
12
+ from pfund_plot.plots.area import (
13
+ Area as area,
14
+ )
15
+ from pfund_plot.plots.bar import (
16
+ Bar as bar,
17
+ )
18
+ from pfund_plot.plots.bokeh import (
19
+ Bokeh as bokeh,
20
+ )
21
+ from pfund_plot.plots.candlestick import (
22
+ Candlestick as candlestick,
23
+ )
24
+ from pfund_plot.plots.candlestick import (
25
+ Candlestick as kline,
26
+ )
27
+ from pfund_plot.plots.candlestick import (
28
+ Candlestick as ohlc,
29
+ )
30
+ from pfund_plot.plots.holoviews import (
31
+ Holoviews as holoviews,
32
+ )
33
+ from pfund_plot.plots.holoviews import (
34
+ Holoviews as hv,
35
+ )
36
+ from pfund_plot.plots.label import (
37
+ Label as label,
38
+ )
39
+ from pfund_plot.plots.layout import (
40
+ Layout as layout,
41
+ )
42
+ from pfund_plot.plots.layout.tabs import (
43
+ Tabs as tabs,
44
+ )
45
+ from pfund_plot.plots.line import (
46
+ Line as line,
47
+ )
48
+ from pfund_plot.plots.matplotlib import (
49
+ Matplotlib as matplotlib,
50
+ )
51
+ from pfund_plot.plots.matplotlib import (
52
+ Matplotlib as mpl,
53
+ )
54
+ from pfund_plot.plots.plotly import (
55
+ Plotly as plotly,
56
+ )
57
+ from pfund_plot.plots.scatter import (
58
+ Scatter as scatter,
59
+ )
60
+ from pfund_plot.plots.scatter.marker import (
61
+ Marker as marker,
62
+ )
63
+
64
+ # NOTE: data update in anywidget (backend=svelte) may have issues (especially in marimo) after loading panel extensions
65
+ # if anywidget+svelte backend is not working, try to comment this out
66
+ #
67
+ # plotly/vega resolve to panel.models.* (bundled with Panel) — they don't import the
68
+ # plotly/altair libs, so they're safe even when those optional extras aren't installed.
69
+ # "ipywidgets" pulls in panel.io.ipywidget -> ipywidgets_bokeh, which is absent in WASM
70
+ # (and where the anywidget/svelte backend isn't available anyway), so guard only that one.
71
+ import importlib.util
72
+
73
+ import panel as pn
74
+
75
+ from pfund_plot.config import configure, get_config
76
+
77
+ _panel_extensions = ["plotly", "vega"]
78
+ if importlib.util.find_spec("ipywidgets_bokeh") is not None:
79
+ _panel_extensions.append("ipywidgets")
80
+ pn.extension(*_panel_extensions)
81
+ # NOTE: this MUST be True, otherwise, some widgets won't work properly, e.g. candlestick widgets, slider and input will both trigger each other due to panel's async update, which leads to infinite loop.
82
+ pn.config.throttled = (
83
+ True # If panel sliders and inputs should be throttled until release of mouse.
84
+ )
85
+ # NOTE: /assets can only be recognized when setting pn.serve(static_dirs=pfund_plot.config.static_dirs)
86
+ # see static_dirs in config.py
87
+ # pn.config.js_files = {
88
+ # "your_custom_js_file": "/assets/your_custom_js_file.js",
89
+ # }
90
+
91
+
92
+ def __getattr__(name: str):
93
+ if name == "__version__":
94
+ from importlib.metadata import version
95
+
96
+ return version("pfund_plot")
97
+ elif name == "plotly":
98
+ from pfund_plot.plots.plotly import Plotly
99
+
100
+ return Plotly
101
+ elif name in ("candlestick", "ohlc", "kline"):
102
+ from pfund_plot.plots.candlestick import Candlestick
103
+
104
+ return Candlestick
105
+ elif name == "line":
106
+ from pfund_plot.plots.line import Line
107
+
108
+ return Line
109
+ elif name == "area":
110
+ from pfund_plot.plots.area import Area
111
+
112
+ return Area
113
+ elif name == "layout":
114
+ from pfund_plot.plots.layout import Layout
115
+
116
+ return Layout
117
+ elif name == "tabs":
118
+ from pfund_plot.plots.layout.tabs import Tabs
119
+
120
+ return Tabs
121
+ elif name == "scatter":
122
+ from pfund_plot.plots.scatter import Scatter
123
+
124
+ return Scatter
125
+ elif name == "marker":
126
+ from pfund_plot.plots.scatter.marker import Marker
127
+
128
+ return Marker
129
+ elif name == "label":
130
+ from pfund_plot.plots.label import Label
131
+
132
+ return Label
133
+ elif name == "bar":
134
+ from pfund_plot.plots.bar import Bar
135
+
136
+ return Bar
137
+ elif name in ("altair", "vega"):
138
+ from pfund_plot.plots.altair import Altair
139
+
140
+ return Altair
141
+ elif name in ("matplotlib", "mpl"):
142
+ from pfund_plot.plots.matplotlib import Matplotlib
143
+
144
+ return Matplotlib
145
+ elif name == "bokeh":
146
+ from pfund_plot.plots.bokeh import Bokeh
147
+
148
+ return Bokeh
149
+ elif name in ("holoviews", "hv"):
150
+ from pfund_plot.plots.holoviews import Holoviews
151
+
152
+ return Holoviews
153
+ else:
154
+ raise AttributeError(f"'{__name__}' has no attribute '{name}'")
155
+
156
+
157
+ __all__ = (
158
+ "altair",
159
+ "area",
160
+ "bar",
161
+ "bokeh",
162
+ "candlestick",
163
+ "configure",
164
+ "get_config",
165
+ "holoviews",
166
+ "hv",
167
+ "kline",
168
+ "label",
169
+ "layout",
170
+ "line",
171
+ "marker",
172
+ "matplotlib",
173
+ "mpl",
174
+ "ohlc",
175
+ "plotly",
176
+ "scatter",
177
+ "tabs",
178
+ "vega",
179
+ )
180
+
181
+
182
+ def __dir__():
183
+ return sorted(__all__)
pfund_plot/__main__.py ADDED
@@ -0,0 +1,9 @@
1
+ def run_cli() -> None:
2
+ """Application Entrypoint."""
3
+ from pfund_plot.cli import pfund_plot_group
4
+
5
+ pfund_plot_group(obj={})
6
+
7
+
8
+ if __name__ == "__main__":
9
+ run_cli()
@@ -0,0 +1,3 @@
1
+ from pfund_plot.cli.main import pfund_plot_group
2
+
3
+ __all__ = ["pfund_plot_group"]
@@ -0,0 +1,15 @@
1
+ import subprocess
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ import click
6
+
7
+ GALLERY_DIR = Path(__file__).parent
8
+
9
+
10
+ @click.command(hidden=True)
11
+ def gallery():
12
+ """Open the gallery to visually verify all supported plots."""
13
+ script = GALLERY_DIR / "gallery_marimo.py"
14
+ result = subprocess.run(["marimo", "edit", str(script)], check=False)
15
+ sys.exit(result.returncode)
@@ -0,0 +1,462 @@
1
+ # ruff: noqa
2
+
3
+ import marimo
4
+
5
+ __generated_with = "0.23.4"
6
+ app = marimo.App(width="columns")
7
+
8
+ with app.setup:
9
+ from threading import Thread
10
+
11
+ import altair as alt
12
+ import holoviews as hv
13
+ import marimo as mo
14
+ import matplotlib.pyplot as mpl
15
+ import numpy as np
16
+ import pfeed as pe
17
+ import plotly.graph_objects as go
18
+ import polars as pl
19
+ from bokeh.plotting import figure
20
+
21
+ import pfund_plot as plt
22
+
23
+ def stop_later(thread, delay):
24
+ import time
25
+
26
+ time.sleep(delay)
27
+ thread.stop()
28
+ print("Stopped the streaming thread")
29
+
30
+ def prepare_streaming_feed():
31
+ # separate this streaming feed from the non-streaming one on purpose
32
+ streaming_feed = pe.Bybit(pipeline_mode=True).market_feed
33
+ streaming_resolution = "1s"
34
+ for streaming_product in products:
35
+ streaming_feed.stream(
36
+ product=streaming_product,
37
+ resolution=streaming_resolution,
38
+ )
39
+ return streaming_feed
40
+
41
+ feed = pe.Bybit(pipeline_mode=True).market_feed
42
+ products = ["ETH_USDT_PERP", "BTC_USDT_PERP"]
43
+ date = "2026-03-01"
44
+ resolution = "1h"
45
+ storage = "local"
46
+
47
+ # FIXME: use pfund-sampledata when its ready
48
+ for product in products:
49
+ for func in [feed.retrieve, feed.download]:
50
+ df = func(
51
+ product=product,
52
+ resolution=resolution,
53
+ start_date=date,
54
+ end_date=date,
55
+ storage_config=pe.StorageConfig(
56
+ storage=storage,
57
+ ),
58
+ ).run()
59
+ if df is not None:
60
+ break
61
+
62
+ # create some fake columns for plt.marker and plt.label
63
+ df = df.collect()
64
+ df = df.with_columns(
65
+ pl.Series("trade", np.random.choice([1, -1], size=len(df))),
66
+ ).with_columns(
67
+ pl.when(pl.col("trade") == 1)
68
+ .then(pl.lit("buy"))
69
+ .otherwise(pl.lit("sell"))
70
+ .alias("label")
71
+ )
72
+
73
+
74
+ @app.cell
75
+ def _():
76
+ mo.md("""
77
+ # Gallery
78
+ This is a gallery of plots supported by `pfund-plot` for maintainers to quickly visualize them to make sure they are working correctly. Press `Cmd + .` to toggle the app view to see all the plots nicely.
79
+ > WARNING: somehow some widgets don't work in app view or even after switching back from the app view. So you might want to manually test the widgets a bit before toggling it.
80
+ """)
81
+ return
82
+
83
+
84
+ @app.cell(hide_code=True)
85
+ def _():
86
+ mo.md(r"""
87
+ # Data Used in the Gallery
88
+ """)
89
+ return
90
+
91
+
92
+ @app.cell
93
+ def _():
94
+ df.head()
95
+ return
96
+
97
+
98
+ @app.cell(hide_code=True)
99
+ def _():
100
+ mo.md(r"""
101
+ # 1. Candlestick
102
+ """)
103
+ return
104
+
105
+
106
+ @app.cell
107
+ def _():
108
+ candlestick = plt.ohlc(df).backend("bokeh")
109
+ candlestick
110
+ return (candlestick,)
111
+
112
+
113
+ @app.cell(hide_code=True)
114
+ def _():
115
+ mo.md(r"""
116
+ # 2. Line
117
+ """)
118
+ return
119
+
120
+
121
+ @app.cell
122
+ def _():
123
+ line = (
124
+ plt.line(df, x="date", y="close").style(color="orange").control(widgets=False)
125
+ )
126
+ line
127
+ return (line,)
128
+
129
+
130
+ @app.cell(hide_code=True)
131
+ def _():
132
+ mo.md(r"""
133
+ # 3. Scatter
134
+ """)
135
+ return
136
+
137
+
138
+ @app.cell
139
+ def _():
140
+ scatter = plt.scatter(df, x="open", y="close").control(widgets=False)
141
+ scatter
142
+ return (scatter,)
143
+
144
+
145
+ @app.cell(hide_code=True)
146
+ def _():
147
+ mo.md(r"""
148
+ # 4. Marker
149
+ """)
150
+ return
151
+
152
+
153
+ @app.cell
154
+ def _():
155
+ marker = plt.marker(df, x="date", y="close", signal="trade")
156
+ marker
157
+ return
158
+
159
+
160
+ @app.cell(hide_code=True)
161
+ def _():
162
+ mo.md(r"""
163
+ # 5. Label
164
+ """)
165
+ return
166
+
167
+
168
+ @app.cell
169
+ def _():
170
+ label = plt.label(df, x="date", y="close", text="label")
171
+ label
172
+ return
173
+
174
+
175
+ @app.cell(hide_code=True)
176
+ def _():
177
+ mo.md(r"""
178
+ # 6. Area
179
+ """)
180
+ return
181
+
182
+
183
+ @app.cell
184
+ def _():
185
+ area = plt.area(df, x="date", y="close").style(marker="x")
186
+ area
187
+ return (area,)
188
+
189
+
190
+ @app.cell(hide_code=True)
191
+ def _():
192
+ mo.md(r"""
193
+ # 7. Bar
194
+ """)
195
+ return
196
+
197
+
198
+ @app.cell
199
+ def _():
200
+ bar = plt.bar(df, x="date", y=["high", "low"]).style(color=["steelblue", "coral"])
201
+ bar
202
+ return (bar,)
203
+
204
+
205
+ @app.cell(column=1, hide_code=True)
206
+ def _():
207
+ mo.md(r"""
208
+ # Operators: "+" and "|"
209
+ """)
210
+ return
211
+
212
+
213
+ @app.cell
214
+ def _(candlestick, line, scatter):
215
+ (candlestick + line) | scatter
216
+ return
217
+
218
+
219
+ @app.cell(hide_code=True)
220
+ def _():
221
+ mo.md(r"""
222
+ # Overlays: "*"
223
+ """)
224
+ return
225
+
226
+
227
+ @app.cell
228
+ def _():
229
+ plt.ohlc(df).backend("bokeh") * plt.label(
230
+ df, x="date", y="close", text="label"
231
+ ) * plt.marker(df, x="date", y="close", signal="trade")
232
+ return
233
+
234
+
235
+ @app.cell(hide_code=True)
236
+ def _():
237
+ mo.md(r"""
238
+ # Plotly Wrapper: `plt.plotly`
239
+ """)
240
+ return
241
+
242
+
243
+ @app.cell
244
+ def _():
245
+ plotly_fig = go.Figure()
246
+ plotly_fig.add_trace(go.Scatter(x=df["date"], y=df["close"], mode="lines"))
247
+ plotly_fig.update_layout(title="Price", xaxis_title="date", yaxis_title="close")
248
+ plotly_fig = plt.plotly(plotly_fig, sizing_mode="stretch_width")
249
+ plotly_fig
250
+ return (plotly_fig,)
251
+
252
+
253
+ @app.cell(hide_code=True)
254
+ def _():
255
+ mo.md(r"""
256
+ # Altair Wrapper: `plt.altair`
257
+ """)
258
+ return
259
+
260
+
261
+ @app.cell
262
+ def _():
263
+ altair_fig = plt.altair(
264
+ alt.Chart(df)
265
+ .mark_line()
266
+ .encode(
267
+ x="date:T",
268
+ y=alt.Y("close:Q").scale(zero=False),
269
+ tooltip=["date:T", "close:Q"],
270
+ )
271
+ .properties(
272
+ title="Price",
273
+ width=600,
274
+ height=400,
275
+ ),
276
+ sizing_mode="stretch_width",
277
+ )
278
+ altair_fig
279
+ return (altair_fig,)
280
+
281
+
282
+ @app.cell(hide_code=True)
283
+ def _():
284
+ mo.md(r"""
285
+ # Matplotlib Wrapper: `plt.matplotlib`
286
+ """)
287
+ return
288
+
289
+
290
+ @app.cell
291
+ def _():
292
+ matplotlib_fig, ax = mpl.subplots()
293
+ ax.plot(df["date"], df["close"])
294
+ ax.set_title("Price")
295
+ ax.set_xlabel("date")
296
+ ax.set_ylabel("close")
297
+ matplotlib_fig = plt.matplotlib(
298
+ matplotlib_fig, height=500, sizing_mode="stretch_width"
299
+ )
300
+ matplotlib_fig
301
+ return
302
+
303
+
304
+ @app.cell(hide_code=True)
305
+ def _():
306
+ mo.md(r"""
307
+ # Bokeh Wrapper: `plt.bokeh`
308
+ """)
309
+ return
310
+
311
+
312
+ @app.cell
313
+ def _():
314
+ bokeh_fig = figure(
315
+ title="Price",
316
+ x_axis_label="date",
317
+ y_axis_label="close",
318
+ x_axis_type="datetime",
319
+ height=400,
320
+ width=700,
321
+ )
322
+ bokeh_fig.line(df["date"], df["close"])
323
+ bokeh_fig = plt.bokeh(bokeh_fig, sizing_mode="stretch_width")
324
+ bokeh_fig
325
+ return
326
+
327
+
328
+ @app.cell(hide_code=True)
329
+ def _():
330
+ mo.md(r"""
331
+ # Holoviews Wrapper: `plt.holoviews`
332
+ """)
333
+ return
334
+
335
+
336
+ @app.cell
337
+ def _():
338
+ holoviews_fig = hv.Curve(df, kdims="date", vdims="close").opts(title="Price")
339
+ holoviews_fig = plt.holoviews(holoviews_fig, sizing_mode="stretch_width")
340
+ holoviews_fig
341
+ return
342
+
343
+
344
+ @app.cell(column=2, hide_code=True)
345
+ def _():
346
+ mo.md(r"""
347
+ # Reactive Widgets
348
+ """)
349
+ return
350
+
351
+
352
+ @app.cell
353
+ def _():
354
+ def get_df(product, resolution):
355
+ return feed.retrieve(
356
+ product=product,
357
+ resolution=resolution,
358
+ start_date=date,
359
+ end_date=date,
360
+ storage_config=pe.StorageConfig(
361
+ storage=storage,
362
+ ),
363
+ ).run()
364
+
365
+ reactive_candlestick = plt.ohlc(
366
+ get_df(products[0], resolution),
367
+ callback=get_df,
368
+ product=products,
369
+ resolution=[resolution],
370
+ )
371
+ reactive_candlestick
372
+ return (reactive_candlestick,)
373
+
374
+
375
+ @app.cell(hide_code=True)
376
+ def _():
377
+ mo.md(r"""
378
+ # Tabs
379
+ """)
380
+ return
381
+
382
+
383
+ @app.cell
384
+ def _(altair_fig, area, bar, line, plotly_fig, reactive_candlestick):
385
+ tabs = plt.tabs(
386
+ reactive_candlestick,
387
+ line,
388
+ area,
389
+ bar,
390
+ plotly_fig,
391
+ altair_fig,
392
+ )
393
+ tabs
394
+ return (tabs,)
395
+
396
+
397
+ @app.cell(hide_code=True)
398
+ def _():
399
+ mo.md(r"""
400
+ # Layout + Streaming
401
+ > put streaming plot inside layout so that we can stop them both at the same time
402
+ """)
403
+ return
404
+
405
+
406
+ @app.cell
407
+ def _(altair_fig, plotly_fig, tabs):
408
+ browser_thread = (
409
+ plt.layout(
410
+ plt.ohlc(prepare_streaming_feed())
411
+ .control(update_interval=1000)
412
+ .mode("browser"),
413
+ tabs,
414
+ plt.line(prepare_streaming_feed(), x="date", y="close")
415
+ .control(update_interval=1000)
416
+ .mode("browser"),
417
+ plt.area(prepare_streaming_feed(), x="date", y="close")
418
+ .style(marker="x")
419
+ .control(update_interval=1000)
420
+ .mode("browser"),
421
+ plt.bar(prepare_streaming_feed(), x="date", y="close")
422
+ .control(update_interval=1000)
423
+ .mode("browser"),
424
+ plotly_fig,
425
+ altair_fig,
426
+ )
427
+ .mode("browser")
428
+ .control(allow_drag=False, linked_axes=False)
429
+ .show()
430
+ )
431
+
432
+ # stop in background without blocking
433
+ Thread(target=stop_later, args=(browser_thread, 20)).start()
434
+ return
435
+
436
+
437
+ @app.cell
438
+ def _():
439
+ desktop_thread = (
440
+ plt.line(df, x="date", y="close")
441
+ .style(color="orange")
442
+ .control(widgets=False)
443
+ .mode("desktop")
444
+ .show()
445
+ )
446
+ # desktop_thread = plt.layout(
447
+ # plt.ohlc(prepare_streaming_feed()).control(update_interval=1000).mode('desktop'),
448
+ # tabs,
449
+ # plt.line(prepare_streaming_feed(), x='date', y='close').control(update_interval=1000).mode('desktop'),
450
+ # plt.area(prepare_streaming_feed(), x='date', y='close').style(marker='x').control(update_interval=1000).mode('desktop'),
451
+ # plt.bar(prepare_streaming_feed(), x='date', y='close').control(update_interval=1000).mode('desktop'),
452
+ # plotly_fig,
453
+ # altair_fig,
454
+ # ).mode('desktop').control(allow_drag=False, linked_axes=False).show()
455
+
456
+ # stop in background without blocking
457
+ Thread(target=stop_later, args=(desktop_thread, 3)).start()
458
+ return
459
+
460
+
461
+ if __name__ == "__main__":
462
+ app.run()
@@ -0,0 +1,21 @@
1
+ import subprocess
2
+ import sys
3
+
4
+ import click
5
+
6
+
7
+ @click.command(
8
+ add_help_option=False,
9
+ context_settings=dict(
10
+ ignore_unknown_options=True,
11
+ allow_extra_args=True,
12
+ ),
13
+ )
14
+ @click.pass_context
15
+ def serve(ctx):
16
+ """Serve a Panel application.
17
+
18
+ Passes all arguments directly to 'panel serve'.
19
+ """
20
+ result = subprocess.run(["panel", "serve", *ctx.args], check=False)
21
+ sys.exit(result.returncode)
pfund_plot/cli/main.py ADDED
@@ -0,0 +1,20 @@
1
+ from pfund_kit.cli import create_cli_group
2
+ from pfund_kit.cli.commands import config, docker_compose, remove
3
+
4
+ from pfund_plot.cli.commands.gallery import gallery
5
+ from pfund_plot.cli.commands.serve import serve
6
+
7
+
8
+ def init_context(ctx):
9
+ """Initialize pfund_plot-specific context"""
10
+ from pfund_plot.config import get_config
11
+
12
+ ctx.obj["config"] = get_config()
13
+
14
+
15
+ pfund_plot_group = create_cli_group("pfund_plot", init_context=init_context)
16
+ pfund_plot_group.add_command(config)
17
+ pfund_plot_group.add_command(docker_compose)
18
+ pfund_plot_group.add_command(remove)
19
+ pfund_plot_group.add_command(serve)
20
+ pfund_plot_group.add_command(gallery)