sparkstagelapse 0.1.0__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.
- sparkstagelapse-0.1.0/.github/workflows/ci.yml +67 -0
- sparkstagelapse-0.1.0/.github/workflows/release.yml +76 -0
- sparkstagelapse-0.1.0/.gitignore +280 -0
- sparkstagelapse-0.1.0/PKG-INFO +127 -0
- sparkstagelapse-0.1.0/README.md +104 -0
- sparkstagelapse-0.1.0/pyproject.toml +43 -0
- sparkstagelapse-0.1.0/sparkstagelapse/__init__.py +8 -0
- sparkstagelapse-0.1.0/sparkstagelapse/__main__.py +76 -0
- sparkstagelapse-0.1.0/sparkstagelapse/dashboard/__init__.py +6 -0
- sparkstagelapse-0.1.0/sparkstagelapse/dashboard/__main__.py +57 -0
- sparkstagelapse-0.1.0/sparkstagelapse/dashboard/app.py +65 -0
- sparkstagelapse-0.1.0/sparkstagelapse/dashboard/client.py +137 -0
- sparkstagelapse-0.1.0/sparkstagelapse/dashboard/server.py +58 -0
- sparkstagelapse-0.1.0/sparkstagelapse/dashboard/templates.py +504 -0
- sparkstagelapse-0.1.0/sparkstagelapse/display.py +188 -0
- sparkstagelapse-0.1.0/test.ipynb +598 -0
- sparkstagelapse-0.1.0/tests/test_templates.py +12 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_dispatch: {}
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
lint:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.12"
|
|
22
|
+
- run: pip install ".[dev]"
|
|
23
|
+
- run: ruff check .
|
|
24
|
+
|
|
25
|
+
test:
|
|
26
|
+
needs: lint
|
|
27
|
+
runs-on: ${{ matrix.os }}
|
|
28
|
+
strategy:
|
|
29
|
+
fail-fast: false
|
|
30
|
+
matrix:
|
|
31
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
32
|
+
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
|
|
36
|
+
- uses: actions/setup-python@v5
|
|
37
|
+
with:
|
|
38
|
+
python-version: ${{ matrix.python-version }}
|
|
39
|
+
|
|
40
|
+
- name: Install package with dev + tui extras
|
|
41
|
+
run: pip install ".[dev,tui]"
|
|
42
|
+
|
|
43
|
+
- name: Run test suite
|
|
44
|
+
run: pytest --cov=sparkstagelapse --cov-report=xml -v
|
|
45
|
+
|
|
46
|
+
- name: Upload coverage
|
|
47
|
+
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
|
|
48
|
+
uses: actions/upload-artifact@v4
|
|
49
|
+
with:
|
|
50
|
+
name: coverage-report
|
|
51
|
+
path: coverage.xml
|
|
52
|
+
|
|
53
|
+
build:
|
|
54
|
+
needs: test
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@v4
|
|
58
|
+
- uses: actions/setup-python@v5
|
|
59
|
+
with:
|
|
60
|
+
python-version: "3.12"
|
|
61
|
+
- run: pip install build twine
|
|
62
|
+
- run: python -m build
|
|
63
|
+
- run: twine check dist/*
|
|
64
|
+
- uses: actions/upload-artifact@v4
|
|
65
|
+
with:
|
|
66
|
+
name: dist
|
|
67
|
+
path: dist/
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
target:
|
|
9
|
+
description: "Where to publish"
|
|
10
|
+
required: true
|
|
11
|
+
default: testpypi
|
|
12
|
+
type: choice
|
|
13
|
+
options: [testpypi]
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.12"
|
|
24
|
+
|
|
25
|
+
- name: Install package with dev extras
|
|
26
|
+
run: pip install ".[dev,tui]"
|
|
27
|
+
|
|
28
|
+
- name: Run test suite
|
|
29
|
+
run: pytest -v
|
|
30
|
+
|
|
31
|
+
- name: Build sdist and wheel
|
|
32
|
+
run: |
|
|
33
|
+
pip install build twine
|
|
34
|
+
python -m build
|
|
35
|
+
twine check dist/*
|
|
36
|
+
|
|
37
|
+
- uses: actions/upload-artifact@v4
|
|
38
|
+
with:
|
|
39
|
+
name: dist
|
|
40
|
+
path: dist/
|
|
41
|
+
|
|
42
|
+
publish-testpypi:
|
|
43
|
+
if: github.event_name == 'workflow_dispatch'
|
|
44
|
+
needs: build
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
environment:
|
|
47
|
+
name: testpypi
|
|
48
|
+
url: https://test.pypi.org/p/sparkstagelapse
|
|
49
|
+
permissions:
|
|
50
|
+
id-token: write # required for OIDC trusted publishing, no token/secret needed
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/download-artifact@v4
|
|
53
|
+
with:
|
|
54
|
+
name: dist
|
|
55
|
+
path: dist/
|
|
56
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
57
|
+
with:
|
|
58
|
+
repository-url: https://test.pypi.org/legacy/
|
|
59
|
+
|
|
60
|
+
publish-pypi:
|
|
61
|
+
# Only real PyPI releases are triggered by publishing a GitHub Release
|
|
62
|
+
# (tag a commit, then "Create a new release" from that tag in the UI).
|
|
63
|
+
if: github.event_name == 'release'
|
|
64
|
+
needs: build
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
environment:
|
|
67
|
+
name: pypi
|
|
68
|
+
url: https://pypi.org/p/sparkstagelapse
|
|
69
|
+
permissions:
|
|
70
|
+
id-token: write # required for OIDC trusted publishing, no token/secret needed
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/download-artifact@v4
|
|
73
|
+
with:
|
|
74
|
+
name: dist
|
|
75
|
+
path: dist/
|
|
76
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Created by https://www.toptal.com/developers/gitignore/api/python,spark
|
|
2
|
+
# Edit at https://www.toptal.com/developers/gitignore?templates=python,spark
|
|
3
|
+
## Databricks toolbox ##
|
|
4
|
+
artifacts/
|
|
5
|
+
|
|
6
|
+
### Python ###
|
|
7
|
+
# Byte-compiled / optimized / DLL files
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[cod]
|
|
10
|
+
*$py.class
|
|
11
|
+
|
|
12
|
+
# C extensions
|
|
13
|
+
*.so
|
|
14
|
+
|
|
15
|
+
#VSCODE
|
|
16
|
+
.vscode/
|
|
17
|
+
|
|
18
|
+
# Distribution / packaging
|
|
19
|
+
.Python
|
|
20
|
+
build/
|
|
21
|
+
develop-eggs/
|
|
22
|
+
dist/
|
|
23
|
+
downloads/
|
|
24
|
+
eggs/
|
|
25
|
+
.eggs/
|
|
26
|
+
lib/
|
|
27
|
+
lib64/
|
|
28
|
+
parts/
|
|
29
|
+
sdist/
|
|
30
|
+
var/
|
|
31
|
+
wheels/
|
|
32
|
+
share/python-wheels/
|
|
33
|
+
*.egg-info/
|
|
34
|
+
.installed.cfg
|
|
35
|
+
*.egg
|
|
36
|
+
MANIFEST
|
|
37
|
+
|
|
38
|
+
# PyInstaller
|
|
39
|
+
# Usually these files are written by a python script from a template
|
|
40
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
41
|
+
*.manifest
|
|
42
|
+
*.spec
|
|
43
|
+
|
|
44
|
+
# Installer logs
|
|
45
|
+
pip-log.txt
|
|
46
|
+
pip-delete-this-directory.txt
|
|
47
|
+
|
|
48
|
+
# Unit test / coverage reports
|
|
49
|
+
htmlcov/
|
|
50
|
+
.tox/
|
|
51
|
+
.nox/
|
|
52
|
+
.coverage
|
|
53
|
+
.coverage.*
|
|
54
|
+
.cache
|
|
55
|
+
nosetests.xml
|
|
56
|
+
coverage.xml
|
|
57
|
+
*.cover
|
|
58
|
+
*.py,cover
|
|
59
|
+
.hypothesis/
|
|
60
|
+
.pytest_cache/
|
|
61
|
+
cover/
|
|
62
|
+
|
|
63
|
+
# Translations
|
|
64
|
+
*.mo
|
|
65
|
+
*.pot
|
|
66
|
+
|
|
67
|
+
# Django stuff:
|
|
68
|
+
*.log
|
|
69
|
+
local_settings.py
|
|
70
|
+
db.sqlite3
|
|
71
|
+
db.sqlite3-journal
|
|
72
|
+
|
|
73
|
+
# Flask stuff:
|
|
74
|
+
instance/
|
|
75
|
+
.webassets-cache
|
|
76
|
+
|
|
77
|
+
# Scrapy stuff:
|
|
78
|
+
.scrapy
|
|
79
|
+
|
|
80
|
+
# Sphinx documentation
|
|
81
|
+
docs/_build/
|
|
82
|
+
|
|
83
|
+
# PyBuilder
|
|
84
|
+
.pybuilder/
|
|
85
|
+
target/
|
|
86
|
+
|
|
87
|
+
# Jupyter Notebook
|
|
88
|
+
.ipynb_checkpoints
|
|
89
|
+
|
|
90
|
+
# IPython
|
|
91
|
+
profile_default/
|
|
92
|
+
ipython_config.py
|
|
93
|
+
|
|
94
|
+
# pyenv
|
|
95
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
96
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
97
|
+
# .python-version
|
|
98
|
+
|
|
99
|
+
# pipenv
|
|
100
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
101
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
102
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
103
|
+
# install all needed dependencies.
|
|
104
|
+
#Pipfile.lock
|
|
105
|
+
|
|
106
|
+
# poetry
|
|
107
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
108
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
109
|
+
# commonly ignored for libraries.
|
|
110
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
111
|
+
#poetry.lock
|
|
112
|
+
|
|
113
|
+
# pdm
|
|
114
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
115
|
+
#pdm.lock
|
|
116
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
117
|
+
# in version control.
|
|
118
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
119
|
+
.pdm.toml
|
|
120
|
+
|
|
121
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
122
|
+
__pypackages__/
|
|
123
|
+
|
|
124
|
+
# Celery stuff
|
|
125
|
+
celerybeat-schedule
|
|
126
|
+
celerybeat.pid
|
|
127
|
+
|
|
128
|
+
# SageMath parsed files
|
|
129
|
+
*.sage.py
|
|
130
|
+
|
|
131
|
+
# Environments
|
|
132
|
+
.env
|
|
133
|
+
.venv
|
|
134
|
+
env/
|
|
135
|
+
venv/
|
|
136
|
+
ENV/
|
|
137
|
+
env.bak/
|
|
138
|
+
venv.bak/
|
|
139
|
+
|
|
140
|
+
# Spyder project settings
|
|
141
|
+
.spyderproject
|
|
142
|
+
.spyproject
|
|
143
|
+
|
|
144
|
+
# Rope project settings
|
|
145
|
+
.ropeproject
|
|
146
|
+
|
|
147
|
+
# mkdocs documentation
|
|
148
|
+
/site
|
|
149
|
+
|
|
150
|
+
# mypy
|
|
151
|
+
.mypy_cache/
|
|
152
|
+
.dmypy.json
|
|
153
|
+
dmypy.json
|
|
154
|
+
|
|
155
|
+
# Pyre type checker
|
|
156
|
+
.pyre/
|
|
157
|
+
|
|
158
|
+
# pytype static type analyzer
|
|
159
|
+
.pytype/
|
|
160
|
+
|
|
161
|
+
# Cython debug symbols
|
|
162
|
+
cython_debug/
|
|
163
|
+
|
|
164
|
+
# PyCharm
|
|
165
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
166
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
167
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
168
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
169
|
+
#.idea/
|
|
170
|
+
|
|
171
|
+
### Python Patch ###
|
|
172
|
+
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
|
173
|
+
poetry.toml
|
|
174
|
+
|
|
175
|
+
# ruff
|
|
176
|
+
.ruff_cache/
|
|
177
|
+
|
|
178
|
+
# LSP config files
|
|
179
|
+
pyrightconfig.json
|
|
180
|
+
|
|
181
|
+
### Spark ###
|
|
182
|
+
*#*#
|
|
183
|
+
*.#*
|
|
184
|
+
*.iml
|
|
185
|
+
*.ipr
|
|
186
|
+
*.iws
|
|
187
|
+
*.pyc
|
|
188
|
+
*.pyo
|
|
189
|
+
*.swp
|
|
190
|
+
*~
|
|
191
|
+
.DS_Store
|
|
192
|
+
.classpath
|
|
193
|
+
.ensime
|
|
194
|
+
.ensime_cache/
|
|
195
|
+
.ensime_lucene
|
|
196
|
+
.generated-mima*
|
|
197
|
+
.idea/
|
|
198
|
+
.idea_modules/
|
|
199
|
+
.project
|
|
200
|
+
.pydevproject
|
|
201
|
+
.scala_dependencies
|
|
202
|
+
.settings
|
|
203
|
+
/lib/
|
|
204
|
+
R-unit-tests.log
|
|
205
|
+
R/unit-tests.out
|
|
206
|
+
R/cran-check.out
|
|
207
|
+
R/pkg/vignettes/sparkr-vignettes.html
|
|
208
|
+
R/pkg/tests/fulltests/Rplots.pdf
|
|
209
|
+
build/*.jar
|
|
210
|
+
build/apache-maven*
|
|
211
|
+
build/scala*
|
|
212
|
+
build/zinc*
|
|
213
|
+
cache
|
|
214
|
+
checkpoint
|
|
215
|
+
conf/*.cmd
|
|
216
|
+
conf/*.conf
|
|
217
|
+
conf/*.properties
|
|
218
|
+
conf/*.sh
|
|
219
|
+
conf/*.xml
|
|
220
|
+
conf/java-opts
|
|
221
|
+
conf/slaves
|
|
222
|
+
dependency-reduced-pom.xml
|
|
223
|
+
derby.log
|
|
224
|
+
dev/create-release/*final
|
|
225
|
+
dev/create-release/*txt
|
|
226
|
+
dev/pr-deps/
|
|
227
|
+
docs/_site
|
|
228
|
+
docs/api
|
|
229
|
+
sql/docs
|
|
230
|
+
sql/site
|
|
231
|
+
lib_managed/
|
|
232
|
+
lint-r-report.log
|
|
233
|
+
log/
|
|
234
|
+
logs/
|
|
235
|
+
out/
|
|
236
|
+
project/boot/
|
|
237
|
+
project/build/target/
|
|
238
|
+
project/plugins/lib_managed/
|
|
239
|
+
project/plugins/project/build.properties
|
|
240
|
+
project/plugins/src_managed/
|
|
241
|
+
project/plugins/target/
|
|
242
|
+
python/lib/pyspark.zip
|
|
243
|
+
python/deps
|
|
244
|
+
python/test_coverage/coverage_data
|
|
245
|
+
python/test_coverage/htmlcov
|
|
246
|
+
python/pyspark/python
|
|
247
|
+
reports/
|
|
248
|
+
scalastyle-on-compile.generated.xml
|
|
249
|
+
scalastyle-output.xml
|
|
250
|
+
scalastyle.txt
|
|
251
|
+
spark-*-bin-*.tgz
|
|
252
|
+
spark-tests.log
|
|
253
|
+
src_managed/
|
|
254
|
+
streaming-tests.log
|
|
255
|
+
unit-tests.log
|
|
256
|
+
work/
|
|
257
|
+
docs/.jekyll-metadata
|
|
258
|
+
|
|
259
|
+
# For Hive
|
|
260
|
+
TempStatsStore/
|
|
261
|
+
metastore/
|
|
262
|
+
metastore_db/
|
|
263
|
+
sql/hive-thriftserver/test_warehouses
|
|
264
|
+
warehouse/
|
|
265
|
+
spark-warehouse/
|
|
266
|
+
|
|
267
|
+
# For R session data
|
|
268
|
+
.RData
|
|
269
|
+
.RHistory
|
|
270
|
+
.Rhistory
|
|
271
|
+
*.Rproj
|
|
272
|
+
*.Rproj.*
|
|
273
|
+
|
|
274
|
+
.Rproj.user
|
|
275
|
+
|
|
276
|
+
# For SBT
|
|
277
|
+
.jvmopts
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# End of https://www.toptal.com/developers/gitignore/api/python,spark
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sparkstagelapse
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Affichage interactif de DataFrames (Spark/pandas) en notebook ou en script, via un dashboard web local persistant.
|
|
5
|
+
Project-URL: Homepage, https://github.com/OWNER/sparkstagelapse
|
|
6
|
+
Project-URL: Repository, https://github.com/OWNER/sparkstagelapse
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Requires-Dist: fastapi>=0.100
|
|
9
|
+
Requires-Dist: pandas>=1.5
|
|
10
|
+
Requires-Dist: requests>=2.28
|
|
11
|
+
Requires-Dist: rich>=13.0
|
|
12
|
+
Requires-Dist: uvicorn[standard]>=0.23
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: httpx>=0.24; extra == 'dev'
|
|
16
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
18
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
19
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
20
|
+
Provides-Extra: tui
|
|
21
|
+
Requires-Dist: textual>=0.40; extra == 'tui'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# sparkstagelapse
|
|
25
|
+
|
|
26
|
+
Interactive DataFrame display (Spark or pandas) — whether you're in a
|
|
27
|
+
notebook, a script, or a Databricks run — backed by a local web dashboard
|
|
28
|
+
that **stays open and up to date even after the script that launched it has
|
|
29
|
+
exited.**
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
From the root of this folder:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install -e .
|
|
37
|
+
# or, if you also want the full-screen explorer (Textual):
|
|
38
|
+
pip install -e ".[tui]"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick usage
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from sparkstagelapse import display_spark
|
|
45
|
+
|
|
46
|
+
# df = spark.sql("select * from ...")
|
|
47
|
+
display_spark(df, title="Employees") # "auto" mode: notebook -> rich object, script -> web dashboard
|
|
48
|
+
display_spark(df, title="Employees", mode="web") # force the web dashboard
|
|
49
|
+
display_spark(df, title="Employees", mode="tui") # full-screen explorer (blocking)
|
|
50
|
+
display_spark(df, title="Employees", mode="rich") # plain ASCII output in the terminal
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or directly on a pandas DataFrame:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from sparkstagelapse import SparkDisplay
|
|
57
|
+
|
|
58
|
+
SparkDisplay(pdf, title="Preview").show_web()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## The persistent dashboard
|
|
62
|
+
|
|
63
|
+
On the first `show_web()` call (or `mode="web"`), the client:
|
|
64
|
+
|
|
65
|
+
1. Checks whether a server already responds on `127.0.0.1:8765` (`/health`).
|
|
66
|
+
2. If yes: just pushes the new table to it over HTTP — no new process is
|
|
67
|
+
created.
|
|
68
|
+
3. If no: **spawns the server as a detached process** (not a thread — a
|
|
69
|
+
daemon thread dies with its parent process, a separate process doesn't).
|
|
70
|
+
Your script keeps running normally, without blocking.
|
|
71
|
+
|
|
72
|
+
The server therefore stays alive after your script exits. The next run —
|
|
73
|
+
even tomorrow, even from a different script — will find the same dashboard
|
|
74
|
+
already open in your browser and update it live over WebSocket.
|
|
75
|
+
|
|
76
|
+
### Managing the server manually
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
python -m sparkstagelapse.dashboard status
|
|
80
|
+
python -m sparkstagelapse.dashboard stop
|
|
81
|
+
python -m sparkstagelapse.dashboard start # manual foreground start (blocking)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Logs and pid files live in `~/.cache/sparkstagelapse/dashboard_<port>.log` / `.pid`.
|
|
85
|
+
|
|
86
|
+
### Changing the port / host
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from sparkstagelapse import DashboardClient, SparkDisplay
|
|
90
|
+
|
|
91
|
+
client = DashboardClient(host="127.0.0.1", port=9000)
|
|
92
|
+
SparkDisplay(pdf, title="Preview", _dashboard=client).show_web()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Package structure
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
sparkstagelapse/
|
|
99
|
+
├── __init__.py # Public API: SparkDisplay, display_spark, DashboardClient
|
|
100
|
+
├── display.py # SparkDisplay / display_spark — renders depending on context
|
|
101
|
+
└── dashboard/
|
|
102
|
+
├── app.py # FastAPI app (HTTP + WebSocket routes), in-memory state
|
|
103
|
+
├── server.py # Server bootstrap (foreground), pid/log files
|
|
104
|
+
├── client.py # DashboardClient: health-check, detached spawn, HTTP push
|
|
105
|
+
├── templates.py # HTML/JS template + table -> HTML rendering
|
|
106
|
+
└── __main__.py # CLI: start / stop / status
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Development
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pip install -e ".[dev,tui]"
|
|
113
|
+
|
|
114
|
+
ruff check . # lint
|
|
115
|
+
pytest -v # unit + integration tests
|
|
116
|
+
python -m build # build sdist + wheel
|
|
117
|
+
twine check dist/* # validate package metadata before upload
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## CI/CD
|
|
121
|
+
|
|
122
|
+
See `.github/workflows/ci.yml` (lint + test matrix + build, on every push/PR)
|
|
123
|
+
and `.github/workflows/release.yml` (build + publish to PyPI/TestPyPI via
|
|
124
|
+
Trusted Publishing, on tagged GitHub Releases). Before the release workflow
|
|
125
|
+
can actually publish, you need to register this repo as a Trusted Publisher
|
|
126
|
+
on PyPI/TestPyPI — see the CI/CD explanation in this conversation for the
|
|
127
|
+
exact steps.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# sparkstagelapse
|
|
2
|
+
|
|
3
|
+
Interactive DataFrame display (Spark or pandas) — whether you're in a
|
|
4
|
+
notebook, a script, or a Databricks run — backed by a local web dashboard
|
|
5
|
+
that **stays open and up to date even after the script that launched it has
|
|
6
|
+
exited.**
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
From the root of this folder:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install -e .
|
|
14
|
+
# or, if you also want the full-screen explorer (Textual):
|
|
15
|
+
pip install -e ".[tui]"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick usage
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from sparkstagelapse import display_spark
|
|
22
|
+
|
|
23
|
+
# df = spark.sql("select * from ...")
|
|
24
|
+
display_spark(df, title="Employees") # "auto" mode: notebook -> rich object, script -> web dashboard
|
|
25
|
+
display_spark(df, title="Employees", mode="web") # force the web dashboard
|
|
26
|
+
display_spark(df, title="Employees", mode="tui") # full-screen explorer (blocking)
|
|
27
|
+
display_spark(df, title="Employees", mode="rich") # plain ASCII output in the terminal
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or directly on a pandas DataFrame:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from sparkstagelapse import SparkDisplay
|
|
34
|
+
|
|
35
|
+
SparkDisplay(pdf, title="Preview").show_web()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## The persistent dashboard
|
|
39
|
+
|
|
40
|
+
On the first `show_web()` call (or `mode="web"`), the client:
|
|
41
|
+
|
|
42
|
+
1. Checks whether a server already responds on `127.0.0.1:8765` (`/health`).
|
|
43
|
+
2. If yes: just pushes the new table to it over HTTP — no new process is
|
|
44
|
+
created.
|
|
45
|
+
3. If no: **spawns the server as a detached process** (not a thread — a
|
|
46
|
+
daemon thread dies with its parent process, a separate process doesn't).
|
|
47
|
+
Your script keeps running normally, without blocking.
|
|
48
|
+
|
|
49
|
+
The server therefore stays alive after your script exits. The next run —
|
|
50
|
+
even tomorrow, even from a different script — will find the same dashboard
|
|
51
|
+
already open in your browser and update it live over WebSocket.
|
|
52
|
+
|
|
53
|
+
### Managing the server manually
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
python -m sparkstagelapse.dashboard status
|
|
57
|
+
python -m sparkstagelapse.dashboard stop
|
|
58
|
+
python -m sparkstagelapse.dashboard start # manual foreground start (blocking)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Logs and pid files live in `~/.cache/sparkstagelapse/dashboard_<port>.log` / `.pid`.
|
|
62
|
+
|
|
63
|
+
### Changing the port / host
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from sparkstagelapse import DashboardClient, SparkDisplay
|
|
67
|
+
|
|
68
|
+
client = DashboardClient(host="127.0.0.1", port=9000)
|
|
69
|
+
SparkDisplay(pdf, title="Preview", _dashboard=client).show_web()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Package structure
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
sparkstagelapse/
|
|
76
|
+
├── __init__.py # Public API: SparkDisplay, display_spark, DashboardClient
|
|
77
|
+
├── display.py # SparkDisplay / display_spark — renders depending on context
|
|
78
|
+
└── dashboard/
|
|
79
|
+
├── app.py # FastAPI app (HTTP + WebSocket routes), in-memory state
|
|
80
|
+
├── server.py # Server bootstrap (foreground), pid/log files
|
|
81
|
+
├── client.py # DashboardClient: health-check, detached spawn, HTTP push
|
|
82
|
+
├── templates.py # HTML/JS template + table -> HTML rendering
|
|
83
|
+
└── __main__.py # CLI: start / stop / status
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Development
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install -e ".[dev,tui]"
|
|
90
|
+
|
|
91
|
+
ruff check . # lint
|
|
92
|
+
pytest -v # unit + integration tests
|
|
93
|
+
python -m build # build sdist + wheel
|
|
94
|
+
twine check dist/* # validate package metadata before upload
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## CI/CD
|
|
98
|
+
|
|
99
|
+
See `.github/workflows/ci.yml` (lint + test matrix + build, on every push/PR)
|
|
100
|
+
and `.github/workflows/release.yml` (build + publish to PyPI/TestPyPI via
|
|
101
|
+
Trusted Publishing, on tagged GitHub Releases). Before the release workflow
|
|
102
|
+
can actually publish, you need to register this repo as a Trusted Publisher
|
|
103
|
+
on PyPI/TestPyPI — see the CI/CD explanation in this conversation for the
|
|
104
|
+
exact steps.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sparkstagelapse"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Affichage interactif de DataFrames (Spark/pandas) en notebook ou en script, via un dashboard web local persistant."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pandas>=1.5",
|
|
13
|
+
"fastapi>=0.100",
|
|
14
|
+
"uvicorn[standard]>=0.23",
|
|
15
|
+
"requests>=2.28",
|
|
16
|
+
"rich>=13.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
tui = ["textual>=0.40"]
|
|
21
|
+
dev = ["pytest>=7.0", "pytest-cov>=4.0", "ruff>=0.5", "build>=1.0", "twine>=5.0", "httpx>=0.24"]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/OWNER/sparkstagelapse"
|
|
25
|
+
Repository = "https://github.com/OWNER/sparkstagelapse"
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.wheel]
|
|
28
|
+
packages = ["sparkstagelapse"]
|
|
29
|
+
|
|
30
|
+
[tool.pytest.ini_options]
|
|
31
|
+
addopts = "-ra -q"
|
|
32
|
+
testpaths = ["tests"]
|
|
33
|
+
|
|
34
|
+
[tool.ruff]
|
|
35
|
+
line-length = 100
|
|
36
|
+
target-version = "py39"
|
|
37
|
+
|
|
38
|
+
[tool.ruff.lint]
|
|
39
|
+
# Keep this to correctness + import hygiene. This is a small utility package;
|
|
40
|
+
# broad except/log-exception/security-linter opinions (BLE/S/RUF/SIM/C4) add
|
|
41
|
+
# noise without changing behavior, so they're intentionally not selected.
|
|
42
|
+
select = ["E", "F", "I"]
|
|
43
|
+
ignore = ["E501"] # long lines in HTML/CSS template strings aren't worth wrapping
|