everyome 0.1.0a1__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.
- everyome/__init__.py +2 -0
- everyome/__main__.py +30 -0
- everyome/core/__init__.py +1 -0
- everyome/core/backends.py +166 -0
- everyome/core/demo/__init__.py +38 -0
- everyome/core/demo/fake_slurm.py +151 -0
- everyome/core/engine.py +78 -0
- everyome/core/manifest.py +301 -0
- everyome/core/modules.py +38 -0
- everyome/core/nfschema.py +69 -0
- everyome/core/projects.py +51 -0
- everyome/core/records.py +277 -0
- everyome/core/rerun.py +114 -0
- everyome/core/runs.py +169 -0
- everyome/core/server.py +512 -0
- everyome/core/session.py +113 -0
- everyome/core/sessions.py +68 -0
- everyome/core/slurm.py +62 -0
- everyome/core/suites.py +74 -0
- everyome/modules/__init__.py +1 -0
- everyome/modules/rna/__init__.py +1 -0
- everyome/modules/rna/bulk_de/__init__.py +1 -0
- everyome/modules/rna/bulk_de/demo_data.py +55 -0
- everyome/modules/rna/bulk_de/demo_pipeline.py +54 -0
- everyome/modules/rna/bulk_de/docs/manual.md +77 -0
- everyome/modules/rna/bulk_de/engine.py +191 -0
- everyome/modules/rna/bulk_de/helpers.py +68 -0
- everyome/modules/rna/bulk_de/methods.py +39 -0
- everyome/modules/rna/bulk_de/module.yaml +84 -0
- everyome/modules/rna/bulk_de/pipeline/LICENSE-nf-core-rnaseq +21 -0
- everyome/modules/rna/bulk_de/pipeline/README.md +9 -0
- everyome/modules/rna/bulk_de/pipeline/nextflow_schema.json +1063 -0
- everyome/modules/rna/bulk_de/pipeline/schema_input.json +72 -0
- everyome/modules/rna/bulk_de/ui.js +168 -0
- everyome/modules/singlecell/__init__.py +1 -0
- everyome/modules/singlecell/splitseq/__init__.py +1 -0
- everyome/modules/singlecell/splitseq/demo_data.py +135 -0
- everyome/modules/singlecell/splitseq/docs/manual.md +94 -0
- everyome/modules/singlecell/splitseq/docs/playground/UMAP-JS-LICENSE +202 -0
- everyome/modules/singlecell/splitseq/docs/playground/play_core.js +237 -0
- everyome/modules/singlecell/splitseq/docs/playground/play_ui.js +295 -0
- everyome/modules/singlecell/splitseq/docs/playground/umap-js.min.js +1 -0
- everyome/modules/singlecell/splitseq/engine.py +371 -0
- everyome/modules/singlecell/splitseq/helpers.py +70 -0
- everyome/modules/singlecell/splitseq/methods.py +90 -0
- everyome/modules/singlecell/splitseq/mock_stage.py +71 -0
- everyome/modules/singlecell/splitseq/module.yaml +159 -0
- everyome/modules/singlecell/splitseq/stage.py +82 -0
- everyome/modules/singlecell/splitseq/templates/cluster_identities_template.xlsx +0 -0
- everyome/modules/singlecell/splitseq/templates/round1_barcodes_template.xlsx +0 -0
- everyome/modules/singlecell/splitseq/ui.js +298 -0
- everyome/modules/singlecell/splitseq/workflow/python/10_label_cells.py +114 -0
- everyome/modules/singlecell/splitseq/workflow/python/11_qc_filter.py +56 -0
- everyome/modules/singlecell/splitseq/workflow/python/12_integrate_cluster.py +85 -0
- everyome/modules/singlecell/splitseq/workflow/python/13_marker_plots.py +92 -0
- everyome/modules/singlecell/splitseq/workflow/python/14_marker_reports.py +196 -0
- everyome/modules/singlecell/splitseq/workflow/python/15_annotate_clusters.py +115 -0
- everyome/modules/singlecell/splitseq/workflow/python/common.py +121 -0
- everyome/modules/singlecell/splitseq/workflow/reads/01_read_preprocessing.sh +101 -0
- everyome/modules/singlecell/splitseq/workflow/reads/02_align_merge_dge.sh +98 -0
- everyome/modules/singlecell/splitseq/workflow/reads/common.sh +57 -0
- everyome/modules/tutorial/__init__.py +1 -0
- everyome/modules/tutorial/sequence_summary/__init__.py +1 -0
- everyome/modules/tutorial/sequence_summary/demo/sequences.fasta +366 -0
- everyome/modules/tutorial/sequence_summary/docs/manual.md +37 -0
- everyome/modules/tutorial/sequence_summary/engine.py +77 -0
- everyome/modules/tutorial/sequence_summary/module.yaml +67 -0
- everyome/modules/tutorial/sequence_summary/steps.py +73 -0
- everyome/modules/tutorial/sequence_summary/ui.js +43 -0
- everyome/static/app.css +272 -0
- everyome/static/app.js +824 -0
- everyome/static/fonts/albert-sans-LICENSE +93 -0
- everyome/static/fonts/albert-sans-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/albert-sans-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/albert-sans-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/albert-sans-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/albert-sans-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/albert-sans-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/albert-sans-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/albert-sans-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/alegreya-LICENSE +93 -0
- everyome/static/fonts/alegreya-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/alegreya-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/alegreya-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/alegreya-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/alegreya-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/alegreya-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/alegreya-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/alegreya-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/alegreya-sans-LICENSE +93 -0
- everyome/static/fonts/alegreya-sans-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/alegreya-sans-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/alegreya-sans-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/alegreya-sans-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/alegreya-sans-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/alegreya-sans-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/archivo-LICENSE +93 -0
- everyome/static/fonts/archivo-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/archivo-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/archivo-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/archivo-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/archivo-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/archivo-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/archivo-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/archivo-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/barlow-LICENSE +93 -0
- everyome/static/fonts/barlow-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/barlow-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/barlow-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/barlow-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/barlow-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/barlow-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/barlow-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/barlow-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/bodoni-moda-LICENSE +93 -0
- everyome/static/fonts/bodoni-moda-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/bodoni-moda-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/bodoni-moda-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/bodoni-moda-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/bodoni-moda-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/bodoni-moda-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/bodoni-moda-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/bodoni-moda-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/bricolage-grotesque-LICENSE +93 -0
- everyome/static/fonts/bricolage-grotesque-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/bricolage-grotesque-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/bricolage-grotesque-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/bricolage-grotesque-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/chivo-LICENSE +93 -0
- everyome/static/fonts/chivo-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/chivo-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/chivo-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/chivo-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/chivo-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/chivo-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/chivo-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/chivo-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/cormorant-garamond-LICENSE +93 -0
- everyome/static/fonts/cormorant-garamond-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/cormorant-garamond-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/cormorant-garamond-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/cormorant-garamond-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/cormorant-garamond-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/cormorant-garamond-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/cormorant-garamond-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/cormorant-garamond-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/crimson-pro-LICENSE +93 -0
- everyome/static/fonts/crimson-pro-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/crimson-pro-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/crimson-pro-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/crimson-pro-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/crimson-pro-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/crimson-pro-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/crimson-pro-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/crimson-pro-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/dm-sans-LICENSE +93 -0
- everyome/static/fonts/dm-sans-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/dm-sans-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/dm-sans-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/dm-sans-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/dm-sans-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/dm-sans-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/dm-sans-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/dm-sans-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/dm-serif-display-LICENSE +93 -0
- everyome/static/fonts/dm-serif-display-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/dm-serif-display-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/eb-garamond-LICENSE +93 -0
- everyome/static/fonts/eb-garamond-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/eb-garamond-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/eb-garamond-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/eb-garamond-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/eb-garamond-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/eb-garamond-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/eb-garamond-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/eb-garamond-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/epilogue-LICENSE +93 -0
- everyome/static/fonts/epilogue-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/epilogue-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/epilogue-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/epilogue-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/epilogue-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/epilogue-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/epilogue-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/epilogue-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/exo-2-LICENSE +93 -0
- everyome/static/fonts/exo-2-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/exo-2-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/exo-2-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/exo-2-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/exo-2-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/exo-2-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/exo-2-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/exo-2-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/figtree-LICENSE +93 -0
- everyome/static/fonts/figtree-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/figtree-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/figtree-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/figtree-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/figtree-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/figtree-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/figtree-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/figtree-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/fonts.css +357 -0
- everyome/static/fonts/fraunces-LICENSE +93 -0
- everyome/static/fonts/fraunces-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/fraunces-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/fraunces-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/fraunces-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/fraunces-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/fraunces-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/fraunces-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/fraunces-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/gloock-LICENSE +93 -0
- everyome/static/fonts/gloock-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/hanken-grotesk-LICENSE +93 -0
- everyome/static/fonts/hanken-grotesk-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/hanken-grotesk-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/hanken-grotesk-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/hanken-grotesk-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/hanken-grotesk-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/hanken-grotesk-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/hanken-grotesk-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/hanken-grotesk-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/ibm-plex-mono-LICENSE +93 -0
- everyome/static/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/ibm-plex-mono-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/ibm-plex-mono-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/ibm-plex-mono-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/ibm-plex-mono-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/ibm-plex-sans-LICENSE +93 -0
- everyome/static/fonts/ibm-plex-sans-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/ibm-plex-sans-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/ibm-plex-sans-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/ibm-plex-sans-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/ibm-plex-sans-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/ibm-plex-serif-LICENSE +93 -0
- everyome/static/fonts/ibm-plex-serif-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/ibm-plex-serif-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/ibm-plex-serif-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/ibm-plex-serif-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/ibm-plex-serif-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/ibm-plex-serif-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/ibm-plex-serif-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/ibm-plex-serif-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/instrument-sans-LICENSE +93 -0
- everyome/static/fonts/instrument-sans-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/instrument-sans-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/instrument-sans-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/instrument-sans-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/instrument-sans-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/instrument-sans-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/instrument-sans-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/instrument-sans-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/instrument-serif-LICENSE +93 -0
- everyome/static/fonts/instrument-serif-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/instrument-serif-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/jetbrains-mono-LICENSE +93 -0
- everyome/static/fonts/jetbrains-mono-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/jetbrains-mono-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/jetbrains-mono-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/jetbrains-mono-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/jetbrains-mono-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/jetbrains-mono-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/josefin-sans-LICENSE +93 -0
- everyome/static/fonts/josefin-sans-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/josefin-sans-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/josefin-sans-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/josefin-sans-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/josefin-sans-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/josefin-sans-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/josefin-sans-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/josefin-sans-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/jost-LICENSE +93 -0
- everyome/static/fonts/jost-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/jost-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/jost-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/jost-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/jost-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/jost-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/jost-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/jost-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/karla-LICENSE +93 -0
- everyome/static/fonts/karla-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/karla-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/karla-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/karla-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/karla-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/karla-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/karla-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/karla-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/lexend-LICENSE +93 -0
- everyome/static/fonts/lexend-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/lexend-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/lexend-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/lexend-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/libre-baskerville-LICENSE +93 -0
- everyome/static/fonts/libre-baskerville-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/libre-baskerville-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/libre-baskerville-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/libre-baskerville-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/libre-baskerville-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/libre-baskerville-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/libre-baskerville-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/libre-baskerville-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/lora-LICENSE +93 -0
- everyome/static/fonts/lora-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/lora-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/lora-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/lora-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/lora-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/lora-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/lora-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/lora-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/manrope-LICENSE +93 -0
- everyome/static/fonts/manrope-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/manrope-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/manrope-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/manrope-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/newsreader-LICENSE +93 -0
- everyome/static/fonts/newsreader-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/newsreader-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/newsreader-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/newsreader-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/newsreader-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/newsreader-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/newsreader-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/newsreader-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/nunito-LICENSE +93 -0
- everyome/static/fonts/nunito-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/nunito-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/nunito-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/nunito-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/nunito-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/nunito-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/nunito-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/nunito-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/onest-LICENSE +93 -0
- everyome/static/fonts/onest-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/onest-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/onest-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/onest-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/outfit-LICENSE +93 -0
- everyome/static/fonts/outfit-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/outfit-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/outfit-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/outfit-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/playfair-display-LICENSE +93 -0
- everyome/static/fonts/playfair-display-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/playfair-display-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/playfair-display-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/playfair-display-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/playfair-display-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/playfair-display-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/playfair-display-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/playfair-display-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/plus-jakarta-sans-LICENSE +93 -0
- everyome/static/fonts/plus-jakarta-sans-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/plus-jakarta-sans-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/plus-jakarta-sans-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/plus-jakarta-sans-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/plus-jakarta-sans-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/plus-jakarta-sans-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/plus-jakarta-sans-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/plus-jakarta-sans-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/quicksand-LICENSE +93 -0
- everyome/static/fonts/quicksand-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/quicksand-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/quicksand-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/quicksand-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/raleway-LICENSE +93 -0
- everyome/static/fonts/raleway-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/raleway-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/raleway-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/raleway-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/raleway-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/raleway-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/raleway-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/raleway-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/red-hat-display-LICENSE +93 -0
- everyome/static/fonts/red-hat-display-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/red-hat-display-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/red-hat-display-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/red-hat-display-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/red-hat-display-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/red-hat-display-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/red-hat-display-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/red-hat-display-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/red-hat-text-LICENSE +93 -0
- everyome/static/fonts/red-hat-text-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/red-hat-text-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/red-hat-text-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/red-hat-text-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/red-hat-text-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/red-hat-text-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/red-hat-text-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/red-hat-text-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/rubik-LICENSE +93 -0
- everyome/static/fonts/rubik-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/rubik-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/rubik-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/rubik-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/rubik-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/rubik-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/rubik-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/rubik-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/schibsted-grotesk-LICENSE +93 -0
- everyome/static/fonts/schibsted-grotesk-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/schibsted-grotesk-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/schibsted-grotesk-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/schibsted-grotesk-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/schibsted-grotesk-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/schibsted-grotesk-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/schibsted-grotesk-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/schibsted-grotesk-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/sora-LICENSE +93 -0
- everyome/static/fonts/sora-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/sora-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/sora-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/sora-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/source-serif-4-LICENSE +93 -0
- everyome/static/fonts/source-serif-4-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/source-serif-4-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/source-serif-4-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/source-serif-4-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/source-serif-4-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/source-serif-4-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/source-serif-4-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/source-serif-4-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/space-grotesk-LICENSE +93 -0
- everyome/static/fonts/space-grotesk-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/space-grotesk-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/space-grotesk-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/space-grotesk-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/spectral-LICENSE +93 -0
- everyome/static/fonts/spectral-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/spectral-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/spectral-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/spectral-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/spectral-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/spectral-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/spectral-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/spectral-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/syne-LICENSE +93 -0
- everyome/static/fonts/syne-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/syne-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/syne-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/syne-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/unbounded-LICENSE +93 -0
- everyome/static/fonts/unbounded-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/unbounded-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/unbounded-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/unbounded-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/urbanist-LICENSE +93 -0
- everyome/static/fonts/urbanist-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/urbanist-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/urbanist-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/urbanist-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/urbanist-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/urbanist-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/urbanist-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/urbanist-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/work-sans-LICENSE +93 -0
- everyome/static/fonts/work-sans-latin-400-italic.woff2 +0 -0
- everyome/static/fonts/work-sans-latin-400-normal.woff2 +0 -0
- everyome/static/fonts/work-sans-latin-500-italic.woff2 +0 -0
- everyome/static/fonts/work-sans-latin-500-normal.woff2 +0 -0
- everyome/static/fonts/work-sans-latin-600-italic.woff2 +0 -0
- everyome/static/fonts/work-sans-latin-600-normal.woff2 +0 -0
- everyome/static/fonts/work-sans-latin-700-italic.woff2 +0 -0
- everyome/static/fonts/work-sans-latin-700-normal.woff2 +0 -0
- everyome/static/fonts/young-serif-LICENSE +93 -0
- everyome/static/fonts/young-serif-latin-400-normal.woff2 +0 -0
- everyome/static/index.html +41 -0
- everyome/static/themes.json +1 -0
- everyome/suites.yaml +92 -0
- everyome-0.1.0a1.dist-info/METADATA +121 -0
- everyome-0.1.0a1.dist-info/RECORD +491 -0
- everyome-0.1.0a1.dist-info/WHEEL +5 -0
- everyome-0.1.0a1.dist-info/entry_points.txt +2 -0
- everyome-0.1.0a1.dist-info/licenses/LICENSE +21 -0
- everyome-0.1.0a1.dist-info/licenses/THIRD_PARTY_NOTICES.md +15 -0
- everyome-0.1.0a1.dist-info/top_level.txt +1 -0
everyome/__init__.py
ADDED
everyome/__main__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Start Everyome: `everyome` (or `python -m everyome`) opens the app in your browser."""
|
|
2
|
+
import argparse
|
|
3
|
+
import os
|
|
4
|
+
import threading
|
|
5
|
+
import webbrowser
|
|
6
|
+
|
|
7
|
+
import uvicorn
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
ap = argparse.ArgumentParser(description="Everyome: every -ome, for everyone")
|
|
12
|
+
ap.add_argument("--port", type=int, default=8777)
|
|
13
|
+
ap.add_argument("--no-browser", action="store_true", help="do not open a browser window")
|
|
14
|
+
a = ap.parse_args()
|
|
15
|
+
try:
|
|
16
|
+
import setproctitle
|
|
17
|
+
# Intent: Stowers policy names every process Claude_<user>_<tool>_<stage>.
|
|
18
|
+
setproctitle.setproctitle(f"Claude_{os.environ.get('USER', 'user')}_everyome_app")
|
|
19
|
+
except ImportError:
|
|
20
|
+
pass
|
|
21
|
+
url = f"http://127.0.0.1:{a.port}"
|
|
22
|
+
if not a.no_browser:
|
|
23
|
+
threading.Timer(1.2, lambda: webbrowser.open(url)).start()
|
|
24
|
+
print(f"Everyome is running at {url} (press Ctrl+C to quit)")
|
|
25
|
+
# Intent: listen on localhost only; the app is for this computer's user.
|
|
26
|
+
uvicorn.run("everyome.core.server:app", host="127.0.0.1", port=a.port, log_level="warning")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core: backends, scheduler, projects, runs and the module loader (see docs/PLAN.md, section 3)."""
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""How the app talks to the computer that does the work (carried over from SPLiTscape).
|
|
2
|
+
|
|
3
|
+
Two backends with one interface:
|
|
4
|
+
* SSHBackend — a real cluster. Uses the system `ssh` client, so the user's
|
|
5
|
+
key, ssh-agent, ~/.ssh/config and jump hosts all apply. The
|
|
6
|
+
app never reads the private key. BatchMode means it never
|
|
7
|
+
prompts for a password: key-based login only.
|
|
8
|
+
* LocalBackend — this computer, inside a folder. With fake Slurm on PATH it is
|
|
9
|
+
the demo cluster; without it, it runs local modules.
|
|
10
|
+
|
|
11
|
+
Both expose: run, read_bytes, write_bytes, listdir, exists, tunnel, close.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import shlex
|
|
17
|
+
import socket
|
|
18
|
+
import subprocess
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Entry:
|
|
25
|
+
name: str
|
|
26
|
+
is_dir: bool
|
|
27
|
+
size: int
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def free_port() -> int:
|
|
31
|
+
with socket.socket() as s:
|
|
32
|
+
s.bind(("127.0.0.1", 0))
|
|
33
|
+
return s.getsockname()[1]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Backend:
|
|
37
|
+
label = "cluster"
|
|
38
|
+
user = "unknown"
|
|
39
|
+
|
|
40
|
+
def run(self, cmd: str, timeout: float = 60, stdin: bytes | None = None) -> tuple[int, str, str]:
|
|
41
|
+
raise NotImplementedError
|
|
42
|
+
|
|
43
|
+
def read_bytes(self, path: str) -> bytes:
|
|
44
|
+
rc, out, err = self.run_bytes(f"cat {shlex.quote(path)}")
|
|
45
|
+
if rc:
|
|
46
|
+
raise FileNotFoundError(f"{path}: {err.strip()}")
|
|
47
|
+
return out
|
|
48
|
+
|
|
49
|
+
def read_text(self, path: str) -> str:
|
|
50
|
+
return self.read_bytes(path).decode()
|
|
51
|
+
|
|
52
|
+
def write_bytes(self, path: str, data: bytes) -> None:
|
|
53
|
+
q = shlex.quote(path)
|
|
54
|
+
rc, _, err = self.run(f"mkdir -p $(dirname {q}) && cat > {q}", stdin=data)
|
|
55
|
+
if rc:
|
|
56
|
+
raise OSError(f"could not write {path}: {err.strip()}")
|
|
57
|
+
|
|
58
|
+
def write_text(self, path: str, text: str) -> None:
|
|
59
|
+
self.write_bytes(path, text.encode())
|
|
60
|
+
|
|
61
|
+
def exists(self, path: str) -> bool:
|
|
62
|
+
return self.run(f"test -e {shlex.quote(path)}")[0] == 0
|
|
63
|
+
|
|
64
|
+
def listdir(self, path: str) -> list[Entry]:
|
|
65
|
+
# Intent: portable listing (type, size, name), one entry per line.
|
|
66
|
+
cmd = (f"cd {shlex.quote(path)} && for f in * .[!.]*; do [ -e \"$f\" ] || continue; "
|
|
67
|
+
"if [ -d \"$f\" ]; then t=d; s=0; else t=f; s=$(wc -c < \"$f\" | tr -d ' '); fi; "
|
|
68
|
+
"printf '%s\\t%s\\t%s\\n' \"$t\" \"$s\" \"$f\"; done")
|
|
69
|
+
rc, out, err = self.run(cmd)
|
|
70
|
+
if rc:
|
|
71
|
+
raise FileNotFoundError(f"{path}: {err.strip() or 'not a folder'}")
|
|
72
|
+
rows = [line.split("\t", 2) for line in out.splitlines() if line.count("\t") >= 2]
|
|
73
|
+
return sorted((Entry(n, t == "d", int(s)) for t, s, n in rows), key=lambda e: (not e.is_dir, e.name.lower()))
|
|
74
|
+
|
|
75
|
+
def run_bytes(self, cmd: str, timeout: float = 120) -> tuple[int, bytes, str]:
|
|
76
|
+
raise NotImplementedError
|
|
77
|
+
|
|
78
|
+
def tunnel(self, node: str, remote_port: int) -> int:
|
|
79
|
+
"""Forward a local port to node:remote_port; returns the local port."""
|
|
80
|
+
raise NotImplementedError
|
|
81
|
+
|
|
82
|
+
def close(self) -> None:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class LocalBackend(Backend):
|
|
87
|
+
"""This computer: a working folder, optionally with fake Slurm commands (the demo cluster)."""
|
|
88
|
+
|
|
89
|
+
def __init__(self, root: Path, fake_slurm_bin: Path | None = None, label: str = "this computer"):
|
|
90
|
+
self.root = root
|
|
91
|
+
self.label = label
|
|
92
|
+
self.env = dict(os.environ)
|
|
93
|
+
if fake_slurm_bin:
|
|
94
|
+
self.env["PATH"] = f"{fake_slurm_bin}{os.pathsep}{self.env.get('PATH', '')}"
|
|
95
|
+
self.env["FAKE_SLURM_HOME"] = str(root / ".slurm")
|
|
96
|
+
# Intent: jobs import this same copy of Everyome, as they would from a cluster install.
|
|
97
|
+
self.env["PYTHONPATH"] = os.pathsep.join(filter(None, [str(Path(__file__).resolve().parents[2]), self.env.get("PYTHONPATH")]))
|
|
98
|
+
self.user = os.environ.get("USER", "user")
|
|
99
|
+
self.host = "localhost"
|
|
100
|
+
|
|
101
|
+
def run(self, cmd, timeout=60, stdin=None):
|
|
102
|
+
p = subprocess.run(["bash", "-c", cmd], cwd=self.root, env=self.env, input=stdin,
|
|
103
|
+
capture_output=True, timeout=timeout)
|
|
104
|
+
return p.returncode, p.stdout.decode(errors="replace"), p.stderr.decode(errors="replace")
|
|
105
|
+
|
|
106
|
+
def run_bytes(self, cmd, timeout=120):
|
|
107
|
+
p = subprocess.run(["bash", "-c", cmd], cwd=self.root, env=self.env, capture_output=True, timeout=timeout)
|
|
108
|
+
return p.returncode, p.stdout, p.stderr.decode(errors="replace")
|
|
109
|
+
|
|
110
|
+
def tunnel(self, node, remote_port):
|
|
111
|
+
return remote_port # same machine: nothing to forward
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class SSHBackend(Backend):
|
|
115
|
+
"""Real cluster over OpenSSH with connection sharing (one login, many commands)."""
|
|
116
|
+
|
|
117
|
+
def __init__(self, host: str, user: str, port: int = 22, ssh_options: list[str] | None = None):
|
|
118
|
+
self.host, self.user, self.port = host, user, port
|
|
119
|
+
self.label = f"{user}@{host}"
|
|
120
|
+
ctl = Path.home() / ".ssh" / "everyome-%r@%h-%p"
|
|
121
|
+
(Path.home() / ".ssh").mkdir(mode=0o700, exist_ok=True)
|
|
122
|
+
# Intent: share one login across commands (not supported by Windows' OpenSSH).
|
|
123
|
+
share = [] if os.name == "nt" else ["-o", "ControlMaster=auto", "-o", f"ControlPath={ctl}", "-o", "ControlPersist=15m"]
|
|
124
|
+
self.base = ["ssh", "-p", str(port), "-o", "BatchMode=yes", "-o", "ConnectTimeout=15", *share,
|
|
125
|
+
"-o", "ServerAliveInterval=30", *(ssh_options or []), f"{user}@{host}"]
|
|
126
|
+
self.tunnels: list[subprocess.Popen] = []
|
|
127
|
+
|
|
128
|
+
def _ssh(self, cmd, stdin=None, timeout=60):
|
|
129
|
+
# Intent: run under a login shell so the user's cluster PATH (Slurm, modules) is set.
|
|
130
|
+
return subprocess.run([*self.base, f"bash -lc {shlex.quote(cmd)}"], input=stdin, capture_output=True, timeout=timeout)
|
|
131
|
+
|
|
132
|
+
def run(self, cmd, timeout=60, stdin=None):
|
|
133
|
+
p = self._ssh(cmd, stdin, timeout)
|
|
134
|
+
return p.returncode, p.stdout.decode(errors="replace"), p.stderr.decode(errors="replace")
|
|
135
|
+
|
|
136
|
+
def run_bytes(self, cmd, timeout=120):
|
|
137
|
+
p = self._ssh(cmd, None, timeout)
|
|
138
|
+
return p.returncode, p.stdout, p.stderr.decode(errors="replace")
|
|
139
|
+
|
|
140
|
+
def tunnel(self, node, remote_port):
|
|
141
|
+
lport = free_port()
|
|
142
|
+
args = [a for a in self.base if a != f"{self.user}@{self.host}"]
|
|
143
|
+
proc = subprocess.Popen([*args, "-N", "-o", "ExitOnForwardFailure=yes",
|
|
144
|
+
"-L", f"127.0.0.1:{lport}:{node}:{remote_port}", f"{self.user}@{self.host}"],
|
|
145
|
+
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
|
146
|
+
self.tunnels.append(proc)
|
|
147
|
+
return lport
|
|
148
|
+
|
|
149
|
+
def close(self):
|
|
150
|
+
for t in self.tunnels:
|
|
151
|
+
t.terminate()
|
|
152
|
+
subprocess.run([*self.base[:-1], "-O", "exit", f"{self.user}@{self.host}"], capture_output=True)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def describe_ssh_error(err: str) -> str:
|
|
156
|
+
"""Turn common ssh failures into plain-language advice."""
|
|
157
|
+
e = err.lower()
|
|
158
|
+
if "permission denied" in e:
|
|
159
|
+
return "The cluster refused your key. Check that your SSH key is added to the cluster and loaded (ssh-add)."
|
|
160
|
+
if "could not resolve" in e or "name or service not known" in e:
|
|
161
|
+
return "That cluster address could not be found. Check the host name, and that you are on the institute network or VPN."
|
|
162
|
+
if "timed out" in e or "no route" in e:
|
|
163
|
+
return "The cluster did not answer. Check you are on the institute network or VPN."
|
|
164
|
+
if "host key verification failed" in e:
|
|
165
|
+
return "Your computer has not seen this cluster before. Log in once from a terminal to accept its host key, or ask IT."
|
|
166
|
+
return err.strip() or "Connection failed."
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""The demo cluster: a folder on this computer plus a pretend Slurm.
|
|
2
|
+
|
|
3
|
+
<root>/
|
|
4
|
+
projects/ where demo projects are saved
|
|
5
|
+
data/<module id>/ each module's small demo dataset
|
|
6
|
+
.bin/ sbatch, squeue, sacct, scancel (fake_slurm.py)
|
|
7
|
+
.slurm/ the pretend scheduler's job records
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import importlib
|
|
12
|
+
import shlex
|
|
13
|
+
import shutil
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
HERE = Path(__file__).resolve().parent
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def setup(root: Path, modules: dict) -> Path:
|
|
21
|
+
"""Create or refresh the demo cluster; returns the folder holding the fake Slurm commands."""
|
|
22
|
+
(root / "projects").mkdir(parents=True, exist_ok=True)
|
|
23
|
+
for m in modules.values():
|
|
24
|
+
if m.demo.get("data"):
|
|
25
|
+
# Intent: refresh demo data from the installed modules, never touching users' demo projects.
|
|
26
|
+
shutil.copytree(m.dir / m.demo["data"], root / "data" / m.id, dirs_exist_ok=True)
|
|
27
|
+
if m.demo.get("build") and not (root / "data" / m.id / ".demo_ready").exists():
|
|
28
|
+
# Intent: modules whose demo data is generated (too large to ship) build it once, here.
|
|
29
|
+
mod, _, fn = m.demo["build"].partition(":")
|
|
30
|
+
getattr(importlib.import_module(mod), fn)(root / "data" / m.id)
|
|
31
|
+
(root / "data" / m.id / ".demo_ready").write_text("1")
|
|
32
|
+
bin_dir = root / ".bin"
|
|
33
|
+
bin_dir.mkdir(exist_ok=True)
|
|
34
|
+
for cmd in ("sbatch", "squeue", "sacct", "scancel"):
|
|
35
|
+
f = bin_dir / cmd
|
|
36
|
+
f.write_text(f"#!/bin/bash\nexec {shlex.quote(sys.executable)} {shlex.quote(str(HERE / 'fake_slurm.py'))} {cmd} \"$@\"\n")
|
|
37
|
+
f.chmod(0o755)
|
|
38
|
+
return bin_dir
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""A tiny stand-in for Slurm, used by the demo cluster.
|
|
2
|
+
|
|
3
|
+
Implements the subset Everyome uses (carried over from SPLiTscape):
|
|
4
|
+
sbatch [--parsable] [--dependency=afterok:ID[:ID]] [--job-name=..] [--output=..] script
|
|
5
|
+
squeue -h -j IDS -o "%i %T %N"
|
|
6
|
+
sacct -n -P -j IDS -o JobID,State,Elapsed
|
|
7
|
+
scancel ID [ID ...]
|
|
8
|
+
|
|
9
|
+
Jobs run as background processes; state lives in $FAKE_SLURM_HOME.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import socket
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
import time
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
HOME = Path(os.environ.get("FAKE_SLURM_HOME", Path.home() / ".fake_slurm"))
|
|
23
|
+
JOBS = HOME / "jobs"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _load(jid):
|
|
27
|
+
return json.loads((JOBS / f"{jid}.json").read_text())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _save(job):
|
|
31
|
+
tmp = JOBS / f"{job['id']}.json.tmp"
|
|
32
|
+
tmp.write_text(json.dumps(job))
|
|
33
|
+
tmp.replace(JOBS / f"{job['id']}.json")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _directives(script: str) -> dict:
|
|
37
|
+
out = {}
|
|
38
|
+
for line in Path(script).read_text().splitlines():
|
|
39
|
+
m = re.match(r"#SBATCH\s+--([\w-]+)(?:[= ](.+))?", line.strip())
|
|
40
|
+
if m:
|
|
41
|
+
out[m.group(1)] = (m.group(2) or "").strip()
|
|
42
|
+
return out
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def sbatch(args):
|
|
46
|
+
JOBS.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
opts, script = {}, None
|
|
48
|
+
for a in args:
|
|
49
|
+
if a.startswith("--"):
|
|
50
|
+
k, _, v = a[2:].partition("=")
|
|
51
|
+
opts[k] = v
|
|
52
|
+
else:
|
|
53
|
+
script = a
|
|
54
|
+
if not script:
|
|
55
|
+
sys.exit("sbatch: no script given")
|
|
56
|
+
opts = {**_directives(script), **opts}
|
|
57
|
+
counter = HOME / "counter"
|
|
58
|
+
jid = int(counter.read_text()) + 1 if counter.exists() else 1001
|
|
59
|
+
counter.write_text(str(jid))
|
|
60
|
+
deps = []
|
|
61
|
+
if opts.get("dependency", "").startswith("afterok:"):
|
|
62
|
+
deps = [int(x) for x in opts["dependency"][8:].split(":") if x]
|
|
63
|
+
out = opts.get("output", str(Path.cwd() / f"slurm-{jid}.out")).replace("%j", str(jid))
|
|
64
|
+
job = {"id": jid, "name": opts.get("job-name", Path(script).name), "script": str(Path(script).resolve()),
|
|
65
|
+
"cwd": str(Path.cwd()), "output": out, "deps": deps, "state": "PENDING", "start": None, "end": None,
|
|
66
|
+
"node": socket.gethostname(), "pid": None}
|
|
67
|
+
_save(job)
|
|
68
|
+
# Intent: detach a runner that waits for dependencies, then executes the script.
|
|
69
|
+
subprocess.Popen([sys.executable, __file__, "_run", str(jid)], start_new_session=True,
|
|
70
|
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, env=os.environ.copy())
|
|
71
|
+
print(jid if "parsable" in opts else f"Submitted batch job {jid}")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _run(jid):
|
|
75
|
+
job = _load(jid)
|
|
76
|
+
while True:
|
|
77
|
+
states = [_load(d)["state"] for d in job["deps"]]
|
|
78
|
+
if any(s in ("FAILED", "CANCELLED", "TIMEOUT") for s in states):
|
|
79
|
+
job = _load(jid); job.update(state="CANCELLED", reason="DependencyNeverSatisfied", end=time.time()); _save(job); return
|
|
80
|
+
if all(s == "COMPLETED" for s in states):
|
|
81
|
+
break
|
|
82
|
+
if _load(jid)["state"] == "CANCELLED":
|
|
83
|
+
return
|
|
84
|
+
time.sleep(0.5)
|
|
85
|
+
job = _load(jid)
|
|
86
|
+
if job["state"] == "CANCELLED":
|
|
87
|
+
return
|
|
88
|
+
Path(job["output"]).parent.mkdir(parents=True, exist_ok=True)
|
|
89
|
+
with open(job["output"], "w") as log:
|
|
90
|
+
env = dict(os.environ, SLURM_JOB_ID=str(jid), SLURMD_NODENAME=job["node"])
|
|
91
|
+
p = subprocess.Popen(["bash", job["script"]], cwd=job["cwd"], stdout=log, stderr=subprocess.STDOUT, env=env, start_new_session=True)
|
|
92
|
+
job.update(state="RUNNING", start=time.time(), pid=p.pid); _save(job)
|
|
93
|
+
rc = p.wait()
|
|
94
|
+
job = _load(jid)
|
|
95
|
+
if job["state"] != "CANCELLED":
|
|
96
|
+
job.update(state="COMPLETED" if rc == 0 else "FAILED", end=time.time(), exit=rc); _save(job)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _ids(args):
|
|
100
|
+
for i, a in enumerate(args):
|
|
101
|
+
if a == "-j" and i + 1 < len(args):
|
|
102
|
+
return [int(x) for x in args[i + 1].split(",") if x]
|
|
103
|
+
if a.startswith("--jobs="):
|
|
104
|
+
return [int(x) for x in a[7:].split(",") if x]
|
|
105
|
+
return sorted(int(p.stem) for p in JOBS.glob("*.json")) if JOBS.exists() else []
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _elapsed(job):
|
|
109
|
+
if not job["start"]:
|
|
110
|
+
return "00:00:00"
|
|
111
|
+
s = int((job["end"] or time.time()) - job["start"])
|
|
112
|
+
return f"{s // 3600:02d}:{s % 3600 // 60:02d}:{s % 60:02d}"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def squeue(args):
|
|
116
|
+
for jid in _ids(args):
|
|
117
|
+
try:
|
|
118
|
+
j = _load(jid)
|
|
119
|
+
except FileNotFoundError:
|
|
120
|
+
continue
|
|
121
|
+
if j["state"] in ("PENDING", "RUNNING"):
|
|
122
|
+
print(f"{jid} {j['state']} {j['node'] if j['state'] == 'RUNNING' else '(None)'}")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def sacct(args):
|
|
126
|
+
for jid in _ids(args):
|
|
127
|
+
try:
|
|
128
|
+
j = _load(jid)
|
|
129
|
+
except FileNotFoundError:
|
|
130
|
+
continue
|
|
131
|
+
print(f"{jid}|{j['state']}|{_elapsed(j)}")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def scancel(args):
|
|
135
|
+
for a in args:
|
|
136
|
+
if not a.isdigit():
|
|
137
|
+
continue
|
|
138
|
+
j = _load(int(a))
|
|
139
|
+
if j["state"] in ("PENDING", "RUNNING"):
|
|
140
|
+
if j.get("pid"):
|
|
141
|
+
try:
|
|
142
|
+
os.killpg(j["pid"], 15)
|
|
143
|
+
except ProcessLookupError:
|
|
144
|
+
pass
|
|
145
|
+
j.update(state="CANCELLED", end=time.time()); _save(j)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
cmd, rest = sys.argv[1], sys.argv[2:]
|
|
150
|
+
{"sbatch": sbatch, "squeue": squeue, "sacct": sacct, "scancel": scancel,
|
|
151
|
+
"_run": lambda a: _run(int(a[0]))}[cmd](rest)
|
everyome/core/engine.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Base class for interactive module engines (generalised from SPLiTscape part 2).
|
|
2
|
+
|
|
3
|
+
An engine runs inside a session on a compute node (or this computer for the
|
|
4
|
+
demo). It works on one run folder, keeps its decisions in session/state.json,
|
|
5
|
+
and records every decision in order so the run can be written up later.
|
|
6
|
+
Long steps run in the background and report progress.
|
|
7
|
+
|
|
8
|
+
Subclasses list the methods the GUI may call in API; nothing else is reachable.
|
|
9
|
+
Methods listed in REPLAY change the analysis; each call is logged with its
|
|
10
|
+
arguments, so the session can be replayed exactly to repeat the run.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import threading
|
|
16
|
+
import time
|
|
17
|
+
import traceback
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseEngine:
|
|
22
|
+
API: tuple[str, ...] = ()
|
|
23
|
+
REPLAY: tuple[str, ...] = ()
|
|
24
|
+
|
|
25
|
+
def __init__(self, run_dir: str):
|
|
26
|
+
self.dir = Path(run_dir)
|
|
27
|
+
self.out = self.dir / "session"
|
|
28
|
+
self.out.mkdir(exist_ok=True)
|
|
29
|
+
(self.dir / "results").mkdir(exist_ok=True)
|
|
30
|
+
self.run = json.loads((self.dir / "run.json").read_text())
|
|
31
|
+
self.params = json.loads((self.dir / "params.json").read_text())
|
|
32
|
+
self.state_path = self.out / "state.json"
|
|
33
|
+
self.state = json.loads(self.state_path.read_text()) if self.state_path.exists() else {"step": "start", "history": []}
|
|
34
|
+
self.task = {"running": False, "step": "", "progress": 0, "error": None, "message": ""}
|
|
35
|
+
self.lock = threading.Lock()
|
|
36
|
+
self._state_lock = threading.RLock() # Intent: the call log and background steps both write state.json
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------- state
|
|
39
|
+
def _save_state(self):
|
|
40
|
+
with self._state_lock:
|
|
41
|
+
self.state_path.write_text(json.dumps(self.state, indent=1))
|
|
42
|
+
|
|
43
|
+
def _record(self, action: str, **details):
|
|
44
|
+
"""Note a decision (in order) for the run record and the lab-book protocol."""
|
|
45
|
+
with self._state_lock:
|
|
46
|
+
self.state.setdefault("history", []).append({"at": time.strftime("%Y-%m-%d %H:%M:%S"), "action": action, **details})
|
|
47
|
+
self._save_state()
|
|
48
|
+
|
|
49
|
+
def _log_call(self, method: str, args: dict):
|
|
50
|
+
"""Keep the exact call (method and arguments) for replaying the session later."""
|
|
51
|
+
with self._state_lock:
|
|
52
|
+
self.state.setdefault("calls", []).append({"at": time.strftime("%Y-%m-%d %H:%M:%S"), "method": method, "args": args})
|
|
53
|
+
self._save_state()
|
|
54
|
+
|
|
55
|
+
def status(self):
|
|
56
|
+
return {"step": self.state.get("step"), "task": self.task, "module": self.run["module"], "run": self.run["id"]}
|
|
57
|
+
|
|
58
|
+
# ---------------------------------------------------------------- background work
|
|
59
|
+
def _background(self, fn, *args):
|
|
60
|
+
if self.task["running"]:
|
|
61
|
+
raise RuntimeError("Another step is still running.")
|
|
62
|
+
self.task = {"running": True, "step": fn.__name__, "progress": 0, "error": None, "message": ""}
|
|
63
|
+
|
|
64
|
+
def wrap():
|
|
65
|
+
try:
|
|
66
|
+
with self.lock:
|
|
67
|
+
fn(*args)
|
|
68
|
+
except Exception as e: # report, never crash the session
|
|
69
|
+
self.task["error"] = f"{e}"
|
|
70
|
+
traceback.print_exc()
|
|
71
|
+
finally:
|
|
72
|
+
self.task["running"] = False
|
|
73
|
+
self.task["progress"] = 100 if not self.task["error"] else self.task["progress"]
|
|
74
|
+
threading.Thread(target=wrap, daemon=True).start()
|
|
75
|
+
return self.task
|
|
76
|
+
|
|
77
|
+
def _prog(self, p, msg):
|
|
78
|
+
self.task.update(progress=p, message=msg)
|