├── .all-contributorsrc ├── .circleci └── config.yml ├── .docs ├── Makefile ├── apidoc.sh ├── changelog.md ├── conf.py ├── index.rst ├── requirements.txt └── source │ ├── modules.rst │ ├── spython.client.rst │ ├── spython.image.cmd.rst │ ├── spython.image.rst │ ├── spython.instance.cmd.rst │ ├── spython.instance.rst │ ├── spython.logger.rst │ ├── spython.main.base.rst │ ├── spython.main.parse.rst │ ├── spython.main.rst │ ├── spython.rst │ ├── spython.tests.rst │ └── spython.utils.rst ├── .github ├── CODE_OF_CONDUCT.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── dev-requirements.txt ├── stale.yml └── workflows │ ├── main.yml │ ├── release.yaml │ └── update-contributors.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .pylintrc ├── .tributors ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── Singularity ├── docs ├── README.md ├── _config.yml ├── _data │ └── toc.yml ├── _includes │ ├── editable.html │ ├── head.html │ ├── navigation.html │ ├── page-footer.html │ ├── page-header.html │ ├── search.html │ └── toc.html ├── _layouts │ ├── default.html │ └── page.html ├── api │ ├── _sources │ │ ├── changelog.md.txt │ │ ├── index.rst.txt │ │ └── source │ │ │ ├── modules.rst.txt │ │ │ ├── spython.client.rst.txt │ │ │ ├── spython.image.cmd.rst.txt │ │ │ ├── spython.image.rst.txt │ │ │ ├── spython.instance.cmd.rst.txt │ │ │ ├── spython.instance.rst.txt │ │ │ ├── spython.logger.rst.txt │ │ │ ├── spython.main.base.rst.txt │ │ │ ├── spython.main.parse.rst.txt │ │ │ ├── spython.main.rst.txt │ │ │ ├── spython.rst.txt │ │ │ ├── spython.tests.rst.txt │ │ │ └── spython.utils.rst.txt │ ├── assets │ │ ├── basic.css │ │ ├── css │ │ │ ├── badge_only.css │ │ │ ├── fonts │ │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ ├── fontawesome-webfont.woff2 │ │ │ │ ├── lato-bold-italic.woff │ │ │ │ ├── lato-bold-italic.woff2 │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-normal-italic.woff │ │ │ │ ├── lato-normal-italic.woff2 │ │ │ │ ├── lato-normal.woff │ │ │ │ └── lato-normal.woff2 │ │ │ └── theme.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── file.png │ │ ├── jquery-3.5.1.js │ │ ├── jquery.js │ │ ├── js │ │ │ ├── badge_only.js │ │ │ ├── html5shiv-printshiv.min.js │ │ │ ├── html5shiv.min.js │ │ │ └── theme.js │ │ ├── language_data.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── underscore-1.13.1.js │ │ └── underscore.js │ ├── changelog.html │ ├── genindex.html │ ├── index.html │ ├── modules │ │ ├── index.html │ │ └── spython │ │ │ ├── client.html │ │ │ ├── client │ │ │ ├── recipe.html │ │ │ ├── shell.html │ │ │ └── test.html │ │ │ ├── image.html │ │ │ ├── instance.html │ │ │ ├── instance │ │ │ ├── cmd.html │ │ │ └── cmd │ │ │ │ ├── start.html │ │ │ │ └── stop.html │ │ │ ├── logger │ │ │ ├── message.html │ │ │ ├── progress.html │ │ │ └── spinner.html │ │ │ ├── main.html │ │ │ ├── main │ │ │ ├── apps.html │ │ │ ├── base.html │ │ │ ├── base │ │ │ │ ├── command.html │ │ │ │ ├── flags.html │ │ │ │ ├── generate.html │ │ │ │ ├── logger.html │ │ │ │ └── sutils.html │ │ │ ├── build.html │ │ │ ├── execute.html │ │ │ ├── help.html │ │ │ ├── inspect.html │ │ │ ├── instances.html │ │ │ ├── parse │ │ │ │ └── recipe.html │ │ │ ├── pull.html │ │ │ └── run.html │ │ │ └── utils │ │ │ ├── fileio.html │ │ │ └── terminal.html │ ├── objects.inv │ ├── py-modindex.html │ ├── search.html │ ├── searchindex.js │ └── source │ │ ├── modules.html │ │ ├── spython.client.html │ │ ├── spython.html │ │ ├── spython.image.cmd.html │ │ ├── spython.image.html │ │ ├── spython.instance.cmd.html │ │ ├── spython.instance.html │ │ ├── spython.logger.html │ │ ├── spython.main.base.html │ │ ├── spython.main.html │ │ ├── spython.main.parse.html │ │ ├── spython.tests.html │ │ └── spython.utils.html ├── css │ ├── normalize.css │ └── sregistry.css ├── favicon.ico ├── img │ ├── logo.png │ ├── robot.png │ ├── shub-logo.png │ └── stanford.png ├── js │ ├── jquery.dlmenu.js │ ├── lunr.min.js │ ├── modernizr.custom.js │ ├── search.js │ └── toc.js └── pages │ ├── client.md │ ├── commands-images.md │ ├── commands-instances.md │ ├── commands-oci.md │ ├── commands.md │ ├── contribute-docs.md │ ├── install.md │ ├── recipes.md │ └── search.html ├── pyproject.toml ├── setup.cfg ├── setup.py └── spython ├── README.md ├── __init__.py ├── client ├── __init__.py ├── recipe.py ├── shell.py └── test.py ├── image.py ├── instance ├── __init__.py └── cmd │ ├── __init__.py │ ├── logs.py │ ├── start.py │ └── stop.py ├── logger ├── __init__.py ├── compatibility.py ├── message.py ├── progress.py └── spinner.py ├── main ├── __init__.py ├── apps.py ├── base │ ├── Dockerfile │ ├── README.md │ ├── __init__.py │ ├── command.py │ ├── flags.py │ ├── generate.py │ ├── logger.py │ └── sutils.py ├── build.py ├── execute.py ├── export.py ├── help.py ├── inspect.py ├── instances.py ├── parse │ ├── __init__.py │ ├── parsers │ │ ├── README.md │ │ ├── __init__.py │ │ ├── base.py │ │ ├── docker.py │ │ └── singularity.py │ ├── recipe.py │ └── writers │ │ ├── __init__.py │ │ ├── base.py │ │ ├── docker.py │ │ └── singularity.py ├── pull.py └── run.py ├── oci ├── README.md ├── __init__.py ├── cmd │ ├── __init__.py │ ├── actions.py │ ├── mounts.py │ └── states.py └── config.json ├── tests ├── Xtest_oci.py ├── __init__.py ├── conftest.py ├── helpers.sh ├── test_base.py ├── test_client.py ├── test_client.sh ├── test_conversion.py ├── test_instances.py ├── test_parsers.py ├── test_recipe.py ├── test_utils.py ├── test_writers.py └── testdata │ ├── Dockerfile │ ├── README.md │ ├── Singularity │ ├── docker2singularity │ ├── add.def │ ├── add.docker │ ├── argsub.def │ ├── argsub.docker │ ├── cmd.def │ ├── cmd.docker │ ├── comments.def │ ├── comments.docker │ ├── copy.def │ ├── copy.docker │ ├── entrypoint-cmd.def │ ├── entrypoint-cmd.docker │ ├── entrypoint.def │ ├── entrypoint.docker │ ├── expose.def │ ├── expose.docker │ ├── from.def │ ├── from.docker │ ├── healthcheck.def │ ├── healthcheck.docker │ ├── label.def │ ├── label.docker │ ├── multiple-lines.def │ ├── multiple-lines.docker │ ├── multistage.def │ ├── multistage.docker │ ├── user.def │ ├── user.docker │ ├── workdir.def │ └── workdir.docker │ └── singularity2docker │ ├── files.def │ ├── files.docker │ ├── from.def │ ├── from.docker │ ├── labels.def │ ├── labels.docker │ ├── multiple-lines.def │ ├── multiple-lines.docker │ ├── multistage.def │ ├── multistage.docker │ ├── post.def │ ├── post.docker │ ├── runscript.def │ ├── runscript.docker │ ├── test.def │ └── test.docker ├── utils ├── __init__.py ├── fileio.py ├── misc.py └── terminal.py └── version.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | # Modified from hpcng/singularity 4 | 5 | workflows: 6 | version: 2 7 | build_and_test: 8 | jobs: 9 | - test_singularity_python: 10 | filters: 11 | branches: 12 | ignore: master 13 | 14 | setup_environment: &setup_environment 15 | name: Setup environment 16 | command: |- 17 | echo 'set -x' >> $BASH_ENV 18 | echo 'export GOPATH=$HOME/go' >> $BASH_ENV 19 | echo 'export GOROOT=/usr/local/go' >> $BASH_ENV 20 | echo 'export GOBIN=$HOME/go/bin' >> $BASH_ENV 21 | echo 'export PATH=$GOBIN:$GOROOT/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin' >> $BASH_ENV 22 | env | sed -e 's,^,ENV: ,' | sort 23 | test -e $BASH_ENV && sed -e 's,^,BASH_ENV: ,' < $BASH_ENV 24 | 25 | update_go: &update_go 26 | name: Update Go to 1.14.9 27 | working_directory: /tmp 28 | command: |- 29 | wget https://dl.google.com/go/go1.14.9.linux-amd64.tar.gz 30 | sudo rm -rf $GOROOT 31 | sudo tar -C /usr/local -xzf go1.14.9.linux-amd64.tar.gz 32 | sudo ln -s $GOROOT/bin/go /usr/local/bin/go 33 | 34 | fetch_deb_deps: &fetch_deb_deps 35 | name: Fetch deps 36 | working_directory: /tmp 37 | command: |- 38 | # https://discuss.circleci.com/t/could-not-get-lock-var-lib-apt-lists-lock/28337/4 39 | sudo killall -9 apt-get || true && \ 40 | sudo apt-get update -y && \ 41 | sudo apt-get install -y build-essential squashfs-tools libseccomp-dev cryptsetup 42 | 43 | build_singularity: &install_singularity 44 | name: Build Singularity 45 | working_directory: ~/go/singularity 46 | command: |- 47 | cd ~/go 48 | wget https://github.com/hpcng/singularity/releases/download/v3.7.1/singularity-3.7.1.tar.gz && \ 49 | tar -xzvf singularity-3.7.1.tar.gz && \ 50 | cd singularity 51 | ./mconfig -p /usr/local && \ 52 | make -C builddir && \ 53 | sudo make -C builddir install 54 | 55 | install_spython: &install_spython 56 | name: install spython 57 | command: |- 58 | export PATH=~/conda/Python3/bin:$PATH 59 | which python 60 | pip uninstall spython --yes || echo "Not installed" 61 | python --version 62 | python setup.py install 63 | 64 | install_dependencies: &install_dependencies 65 | name: install dependencies 66 | command: |- 67 | PYTHON_VERSION=3 68 | CONDA_PATH="$HOME/conda/Python${PYTHON_VERSION}" 69 | echo 'export PATH="'"$CONDA_PATH"'/bin:$PATH"' >> "$BASH_ENV" 70 | source "$BASH_ENV" 71 | if [ ! -d "$CONDA_PATH" ]; then 72 | CONDA_SCRIPT=Miniconda${PYTHON_VERSION}-latest-Linux-x86_64.sh 73 | wget https://repo.anaconda.com/miniconda/$CONDA_SCRIPT 74 | /bin/bash $CONDA_SCRIPT -b -p $CONDA_PATH 75 | else 76 | echo "Miniconda is already installed, continuing to build." 77 | fi 78 | python --version 79 | [ $(python -c'import sys;print(sys.version_info.major)') -eq $PYTHON_VERSION ] 80 | 81 | pip install --upgrade pytest 82 | pip install black || true 83 | 84 | run_linter: &run_linter 85 | name: run linter 86 | command: |- 87 | export PATH=~/conda/Python3/bin:$PATH 88 | cd ~/repo 89 | black --check spython 90 | 91 | test_spython: &test_spython 92 | name: Test Singularity Python 93 | command: |- 94 | export PATH=~/conda/Python3/bin:$PATH 95 | pytest ~/repo/spython 96 | 97 | 98 | jobs: 99 | test_singularity_python: 100 | working_directory: ~/repo 101 | machine: 102 | image: ubuntu-2004:202008-01 103 | steps: 104 | - checkout 105 | - restore_cache: 106 | keys: v2-dependencies 107 | - run: *install_dependencies 108 | - run: *setup_environment 109 | - run: *update_go 110 | - run: *fetch_deb_deps 111 | - run: *install_singularity 112 | - run: *install_spython 113 | - save_cache: 114 | paths: 115 | - ~/conda 116 | key: v3-dependencies 117 | - run: *run_linter 118 | - run: *test_spython 119 | -------------------------------------------------------------------------------- /.docs/apidoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # If the modules changed, the content of "source" should be backed up and 3 | # new files generated (to update) by doing: 4 | # 5 | # sphinx-apidoc -o source/ ../spython 6 | 7 | HERE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | BASE=$(dirname $HERE) 9 | cd $HERE 10 | cd $BASE && python setup.py install && cd $HERE && make html 11 | rm -rf $BASE/docs/api 12 | 13 | # Create new folders 14 | mkdir -p $BASE/docs/api 15 | 16 | # Rename folders 17 | find $HERE/_build/html -exec sed -i -e 's/_static/assets/g' {} \; 18 | find $HERE/_build/html -exec sed -i -e 's/_modules/modules/g' {} \; 19 | 20 | # Copy to new locations 21 | mv $HERE/_build/html/_static $BASE/docs/api/assets 22 | mv $HERE/_build/html/_modules $BASE/docs/api/modules 23 | cp -R $HERE/_build/html/* $BASE/docs/api 24 | -------------------------------------------------------------------------------- /.docs/changelog.md: -------------------------------------------------------------------------------- 1 | ../CHANGELOG.md -------------------------------------------------------------------------------- /.docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Singularity Python API documentation master file, created by 2 | sphinx-quickstart on Sun Feb 11 12:17:05 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Singularity Python API's documentation! 7 | ================================================== 8 | 9 | Singularity Python is the Python API for working with Singularity containers. The module is licensed 10 | under the MPL 2.0 LICENSE. 11 | 12 | Contents: 13 | 14 | .. toctree:: 15 | :maxdepth: 3 16 | 17 | source/spython.rst 18 | changelog.md 19 | 20 | 21 | Indices and tables 22 | ------------------ 23 | 24 | * :ref:`modindex` 25 | -------------------------------------------------------------------------------- /.docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinxcontrib-napoleon 3 | sphinx-argparse 4 | sphinx_rtd_theme 5 | docutils==0.12 6 | recommonmark 7 | configargparse 8 | appdirs 9 | -------------------------------------------------------------------------------- /.docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | spython 2 | ======= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | spython 8 | -------------------------------------------------------------------------------- /.docs/source/spython.client.rst: -------------------------------------------------------------------------------- 1 | spython\.client package 2 | ======================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.client\.recipe module 8 | ------------------------------ 9 | 10 | .. automodule:: spython.client.recipe 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.client\.shell module 16 | ----------------------------- 17 | 18 | .. automodule:: spython.client.shell 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.client\.test module 24 | ---------------------------- 25 | 26 | .. automodule:: spython.client.test 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: spython.client 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /.docs/source/spython.image.cmd.rst: -------------------------------------------------------------------------------- 1 | spython\.image\.cmd package 2 | =========================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.image\.cmd\.create module 8 | ---------------------------------- 9 | 10 | .. automodule:: spython.image.cmd.create 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.image\.cmd\.export module 16 | ---------------------------------- 17 | 18 | .. automodule:: spython.image.cmd.export 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.image\.cmd\.importcmd module 24 | ------------------------------------- 25 | 26 | .. automodule:: spython.image.cmd.importcmd 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | spython\.image\.cmd\.utils module 32 | --------------------------------- 33 | 34 | .. automodule:: spython.image.cmd.utils 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: spython.image.cmd 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /.docs/source/spython.image.rst: -------------------------------------------------------------------------------- 1 | spython\.image package 2 | ====================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | spython.image.cmd 10 | 11 | Module contents 12 | --------------- 13 | 14 | .. automodule:: spython.image 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | -------------------------------------------------------------------------------- /.docs/source/spython.instance.cmd.rst: -------------------------------------------------------------------------------- 1 | spython\.instance\.cmd package 2 | ============================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.instance\.cmd\.iutils module 8 | ------------------------------------- 9 | 10 | .. automodule:: spython.instance.cmd.iutils 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.instance\.cmd\.start module 16 | ------------------------------------ 17 | 18 | .. automodule:: spython.instance.cmd.start 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.instance\.cmd\.stop module 24 | ----------------------------------- 25 | 26 | .. automodule:: spython.instance.cmd.stop 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: spython.instance.cmd 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /.docs/source/spython.instance.rst: -------------------------------------------------------------------------------- 1 | spython\.instance package 2 | ========================= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | spython.instance.cmd 10 | 11 | Module contents 12 | --------------- 13 | 14 | .. automodule:: spython.instance 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | -------------------------------------------------------------------------------- /.docs/source/spython.logger.rst: -------------------------------------------------------------------------------- 1 | spython\.logger package 2 | ======================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.logger\.message module 8 | ------------------------------- 9 | 10 | .. automodule:: spython.logger.message 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.logger\.progress module 16 | -------------------------------- 17 | 18 | .. automodule:: spython.logger.progress 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.logger\.spinner module 24 | ------------------------------- 25 | 26 | .. automodule:: spython.logger.spinner 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: spython.logger 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /.docs/source/spython.main.base.rst: -------------------------------------------------------------------------------- 1 | spython\.main\.base package 2 | =========================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.main\.base\.command module 8 | ----------------------------------- 9 | 10 | .. automodule:: spython.main.base.command 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.main\.base\.flags module 16 | --------------------------------- 17 | 18 | .. automodule:: spython.main.base.flags 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.main\.base\.generate module 24 | ------------------------------------ 25 | 26 | .. automodule:: spython.main.base.generate 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | spython\.main\.base\.logger module 32 | ---------------------------------- 33 | 34 | .. automodule:: spython.main.base.logger 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | spython\.main\.base\.sutils module 40 | ---------------------------------- 41 | 42 | .. automodule:: spython.main.base.sutils 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | 48 | Module contents 49 | --------------- 50 | 51 | .. automodule:: spython.main.base 52 | :members: 53 | :undoc-members: 54 | :show-inheritance: 55 | -------------------------------------------------------------------------------- /.docs/source/spython.main.parse.rst: -------------------------------------------------------------------------------- 1 | spython\.main\.parse package 2 | ============================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.main\.parse\.converters module 8 | --------------------------------------- 9 | 10 | .. automodule:: spython.main.parse.converters 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.main\.parse\.docker module 16 | ----------------------------------- 17 | 18 | .. automodule:: spython.main.parse.docker 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.main\.parse\.environment module 24 | ---------------------------------------- 25 | 26 | .. automodule:: spython.main.parse.environment 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | spython\.main\.parse\.recipe module 32 | ----------------------------------- 33 | 34 | .. automodule:: spython.main.parse.recipe 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | spython\.main\.parse\.singularity module 40 | ---------------------------------------- 41 | 42 | .. automodule:: spython.main.parse.singularity 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | 48 | Module contents 49 | --------------- 50 | 51 | .. automodule:: spython.main.parse 52 | :members: 53 | :undoc-members: 54 | :show-inheritance: 55 | -------------------------------------------------------------------------------- /.docs/source/spython.main.rst: -------------------------------------------------------------------------------- 1 | spython\.main package 2 | ===================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | spython.main.base 10 | spython.main.parse 11 | 12 | Submodules 13 | ---------- 14 | 15 | spython\.main\.apps module 16 | -------------------------- 17 | 18 | .. automodule:: spython.main.apps 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.main\.build module 24 | --------------------------- 25 | 26 | .. automodule:: spython.main.build 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | spython\.main\.execute module 32 | ----------------------------- 33 | 34 | .. automodule:: spython.main.execute 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | spython\.main\.help module 40 | -------------------------- 41 | 42 | .. automodule:: spython.main.help 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | spython\.main\.inspect module 48 | ----------------------------- 49 | 50 | .. automodule:: spython.main.inspect 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | spython\.main\.instances module 56 | ------------------------------- 57 | 58 | .. automodule:: spython.main.instances 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | spython\.main\.pull module 64 | -------------------------- 65 | 66 | .. automodule:: spython.main.pull 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | 71 | spython\.main\.run module 72 | ------------------------- 73 | 74 | .. automodule:: spython.main.run 75 | :members: 76 | :undoc-members: 77 | :show-inheritance: 78 | 79 | 80 | Module contents 81 | --------------- 82 | 83 | .. automodule:: spython.main 84 | :members: 85 | :undoc-members: 86 | :show-inheritance: 87 | -------------------------------------------------------------------------------- /.docs/source/spython.rst: -------------------------------------------------------------------------------- 1 | spython package 2 | =============== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | spython.client 10 | spython.image 11 | spython.instance 12 | spython.logger 13 | spython.main 14 | spython.utils 15 | 16 | Submodules 17 | ---------- 18 | 19 | spython\.version module 20 | ----------------------- 21 | 22 | .. automodule:: spython.version 23 | :members: 24 | :undoc-members: 25 | :show-inheritance: 26 | 27 | 28 | Module contents 29 | --------------- 30 | 31 | .. automodule:: spython 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | -------------------------------------------------------------------------------- /.docs/source/spython.tests.rst: -------------------------------------------------------------------------------- 1 | spython\.tests package 2 | ====================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.tests\.test\_client module 8 | ----------------------------------- 9 | 10 | .. automodule:: spython.tests.test_client 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.tests\.test\_instances module 16 | -------------------------------------- 17 | 18 | .. automodule:: spython.tests.test_instances 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.tests\.test\_utils module 24 | ---------------------------------- 25 | 26 | .. automodule:: spython.tests.test_utils 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: spython.tests 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /.docs/source/spython.utils.rst: -------------------------------------------------------------------------------- 1 | spython\.utils package 2 | ====================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.utils\.fileio module 8 | ----------------------------- 9 | 10 | .. automodule:: spython.utils.fileio 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.utils\.terminal module 16 | ------------------------------- 17 | 18 | .. automodule:: spython.utils.terminal 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | 24 | Module contents 25 | --------------- 26 | 27 | .. automodule:: spython.utils 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team leader @vsoch. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: vsoch 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve singularity python 4 | 5 | --- 6 | 7 | ## Expected Behavior 8 | 9 | [ Please describe the behavior that are expecting ] 10 | 11 | ## Actual Behavior 12 | 13 | ## Steps to Reproduce 14 | 15 | [ Please provide detailed steps for reproducing the issue and reproducible Code Snippet ] 16 | 17 | ## Context 18 | 19 | [provide more detailed introduction to the issue itself . This is for make a reproducible issue.] 20 | * Operating System: 21 | * singularity version: 22 | * spython version: 23 | * python version: 24 | 25 | ## Failure Logs 26 | 27 | [ log or files here. If relevant, include a screenshot] 28 | 29 | ## Possible Fix 30 | 31 | [ the suggest fixes or reasons for the bug] 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for singularity python 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question, or prompt an update of the docs 4 | 5 | --- 6 | 7 | ## What is your question? 8 | 9 | [write your question here, and answer the prompts below] 10 | 11 | - [ ] I found the documentation at https://singularityhub.github.io/singularity-cli/ 12 | - [ ] The documentation does not answer my question 13 | 14 | If the documentation doesn't answer your question, where do you think it would be appropriate to add (i.e., where did you go looking for it?) 15 | -------------------------------------------------------------------------------- /.github/dev-requirements.txt: -------------------------------------------------------------------------------- 1 | pre-commit 2 | black==23.3.0 3 | isort 4 | flake8 5 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: spython-ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: [] 8 | 9 | jobs: 10 | formatting: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Check Spelling 16 | uses: crate-ci/typos@592b36d23c62cb378f6097a292bc902ee73f93ef # version 1.0.4 17 | with: 18 | files: ./spython ./.docs ./README.md 19 | 20 | - name: Setup black linter 21 | run: conda create --quiet --name black pyflakes 22 | 23 | - name: Lint and format Python code 24 | run: | 25 | export PATH="/usr/share/miniconda/bin:$PATH" 26 | source activate black 27 | pip install -r .github/dev-requirements.txt 28 | pre-commit run --all-files 29 | 30 | pytest: 31 | runs-on: ubuntu-latest 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | python-version: [3.7, 3.8, 3.9] 36 | 37 | steps: 38 | - uses: actions/checkout@v3 39 | - uses: singularityhub/install-singularity@main 40 | - name: Set up Python ${{ matrix.python-version }} 41 | uses: actions/setup-python@v4 42 | with: 43 | python-version: ${{ matrix.python-version }} 44 | 45 | - name: Install dependencies 46 | run: | 47 | python -m pip install --upgrade pip 48 | pip install pytest semver pytest-runner requests 49 | 50 | - name: Run unit tests 51 | run: pytest -xs spython/tests/test*.py 52 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-20.04 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Install 15 | run: conda create --quiet --name spython twine 16 | 17 | - name: Install dependencies 18 | run: | 19 | export PATH="/usr/share/miniconda/bin:$PATH" 20 | source activate spython 21 | pip install -e . 22 | pip install setuptools wheel twine 23 | - name: Build and publish 24 | env: 25 | TWINE_USERNAME: ${{ secrets.PYPI_USER }} 26 | TWINE_PASSWORD: ${{ secrets.PYPI_PASS }} 27 | run: | 28 | export PATH="/usr/share/miniconda/bin:$PATH" 29 | source activate spython 30 | python setup.py sdist bdist_wheel 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /.github/workflows/update-contributors.yaml: -------------------------------------------------------------------------------- 1 | name: allcontributors-auto-detect 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | Update: 10 | name: Generate 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Repository 14 | uses: actions/checkout@v4 15 | - name: Tributors Update 16 | uses: con/tributors@master 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | with: 20 | parsers: unset 21 | update_lookup: github 22 | log_level: DEBUG 23 | force: true 24 | threshold: 1 25 | run_twice: true 26 | 27 | - name: Checkout New Branch 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | BRANCH_AGAINST: "master" 31 | run: | 32 | printf "GitHub Actor: ${GITHUB_ACTOR}\n" 33 | export BRANCH_FROM="contributors/update-$(date '+%Y-%m-%d')" 34 | git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" 35 | 36 | BRANCH_EXISTS=$(git ls-remote --heads origin ${BRANCH_FROM}) 37 | if [[ -z ${BRANCH_EXISTS} ]]; then 38 | printf "Branch does not exist in remote.\n" 39 | else 40 | printf "Branch already exists in remote.\n" 41 | exit 1 42 | fi 43 | git branch 44 | git checkout -b "${BRANCH_FROM}" || git checkout "${BRANCH_FROM}" 45 | git branch 46 | 47 | git config --global user.name "github-actions" 48 | git config --global user.email "github-actions@users.noreply.github.com" 49 | git status 50 | 51 | if git diff-index --quiet HEAD --; then 52 | export OPEN_PULL_REQUEST=0 53 | printf "No changes\n" 54 | else 55 | export OPEN_PULL_REQUEST=1 56 | printf "Changes\n" 57 | git commit -a -m "Automated deployment to update contributors $(date '+%Y-%m-%d')" 58 | git push origin "${BRANCH_FROM}" 59 | fi 60 | 61 | echo "OPEN_PULL_REQUEST=${OPEN_PULL_REQUEST}" >> $GITHUB_ENV 62 | echo "PULL_REQUEST_FROM_BRANCH=${BRANCH_FROM}" >> $GITHUB_ENV 63 | echo "PULL_REQUEST_TITLE=[tributors] ${BRANCH_FROM}" >> $GITHUB_ENV 64 | echo "PULL_REQUEST_BODY=Tributors update automated pull request." >> $GITHUB_ENV 65 | 66 | - name: Open Pull Request 67 | uses: vsoch/pull-request-action@master 68 | if: ${{ env.OPEN_PULL_REQUEST == '1' }} 69 | env: 70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 71 | PULL_REQUEST_BRANCH: "master" 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | spython.egg-info 2 | __pycache__ 3 | *.img 4 | *.simg 5 | pypi.sh 6 | _site 7 | .eggs 8 | _build 9 | build 10 | dist 11 | *.pyc 12 | OLD 13 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: ".all-contributorsrc|.tributors" 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.3.0 5 | hooks: 6 | - id: check-added-large-files 7 | - id: check-case-conflict 8 | - id: check-docstring-first 9 | - id: mixed-line-ending 10 | 11 | - repo: local 12 | hooks: 13 | - id: black 14 | name: black 15 | language: python 16 | types: [python] 17 | entry: black 18 | 19 | - id: isort 20 | name: isort 21 | args: [--filter-files] 22 | language: python 23 | types: [python] 24 | entry: isort 25 | 26 | - id: flake8 27 | name: flake8 28 | language: python 29 | types: [python] 30 | entry: flake8 31 | -------------------------------------------------------------------------------- /.tributors: -------------------------------------------------------------------------------- 1 | { 2 | "vsoch": { 3 | "name": "Vanessasaurus", 4 | "bio": "I'm the Vanessasaurus!", 5 | "blog": "https://vsoch.github.io" 6 | }, 7 | "Flamefire": { 8 | "name": "Alexander Grund", 9 | "blog": "https://github.com/Flamefire" 10 | }, 11 | "pierlauro": { 12 | "name": "Pierlauro Sciarelli", 13 | "bio": "Computer Scientist", 14 | "blog": "https://github.com/pierlauro", 15 | "orcid": "0000-0002-3046-2869" 16 | }, 17 | "al3x609": { 18 | "name": "Alex Bernal", 19 | "bio": "DevOps HPC Engineer -\r\nSuper computing laboratory - Industrial University of Santander.", 20 | "blog": "https://github.com/al3x609" 21 | }, 22 | "wkpalan": { 23 | "name": "Kokulapalan (Gokul) Wimalanathan", 24 | "bio": "Genomics Data Scientist with experience in Bioinformatics, cloud computing, and HPC environments", 25 | "blog": "http://www.bioinformapping.com/", 26 | "orcid": "0000-0001-7811-935X", 27 | "affiliation": "University of Colombo Faculty of Science" 28 | }, 29 | "tschoonj": { 30 | "name": "Tom Schoonjans", 31 | "email": "Tom.Schoonjans@genomicsplc.com", 32 | "bio": "I'm a software engineer working @genomicsplc, and am passionate about developing quality open source software.", 33 | "blog": "tschoonj.github.io", 34 | "orcid": "0000-0002-3919-3546", 35 | "affiliation": "Rosalind Franklin Institute" 36 | }, 37 | "biosugar0": { 38 | "name": "Yuto Kimura", 39 | "email": "biosugar0@gmail.com", 40 | "bio": " SRE", 41 | "blog": "https://www.biosugar0.com/" 42 | }, 43 | "kinow": { 44 | "name": "Bruno P. Kinoshita", 45 | "bio": "From Casa Verde, São Paulo . Now in CBD, Auckland .", 46 | "blog": "https://kinoshita.eti.br" 47 | }, 48 | "cceyda": { 49 | "name": "Ceyda Cinarel", 50 | "bio": "AI researcher & engineer~\r\nall things NLP\r\n🤖 generative models\r\n★ like trying out new libraries & tools\r\n♥ Python ", 51 | "blog": "cceyda.github.io/blog/" 52 | }, 53 | "shnizzedy": { 54 | "name": "Jon Clucas", 55 | "email": "jon.clucas@childmind.org", 56 | "bio": "Associate Software Developer in the Computational Neuroimaging Lab at the @ChildMindInstitute. they/them / he/him", 57 | "blog": "http://matter.childmind.org/jon-clucas.html", 58 | "orcid": "0000-0001-7590-5806", 59 | "affiliation": "Child Mind Institute" 60 | }, 61 | "mobiusklein": { 62 | "name": "Joshua Klein", 63 | "email": "mobiusklein@gmail.com", 64 | "blog": "https://github.com/mobiusklein" 65 | }, 66 | "MarDiehl": { 67 | "name": "Martin Diehl", 68 | "email": "martin.diehl@kuleuven.be", 69 | "bio": "Materials Science, Fortran, Python, DAMASK, open source", 70 | "blog": "https://martin-diehl.net" 71 | }, 72 | "neumann-nico": { 73 | "name": "Nico Neumann", 74 | "bio": "Software Engineer", 75 | "blog": "https://www.linkedin.com/in/neumann-nico/", 76 | "orcid": "0000-0003-3094-6238" 77 | }, 78 | "tcpan": { 79 | "name": "Tony Pan", 80 | "blog": "https://github.com/tcpan" 81 | }, 82 | "tjgalvin": { 83 | "name": "tjgalvin", 84 | "blog": "https://github.com/tjgalvin" 85 | }, 86 | "EricDeveaud": { 87 | "name": "Eric Deveaud", 88 | "blog": "http://www.pasteur.fr/ip/easysite/pasteur/fr/recherche/Centre-Informatique-pour-la-Biologie" 89 | }, 90 | "pustoshilov-d": { 91 | "name": "Dmitry Pustoshilov", 92 | "email": "pustoshilov.d@gmail.com", 93 | "bio": "𝑳𝒊𝒇𝒆 & 𝑫𝒂𝒕𝒂 𝑺𝒄𝒊𝒆𝒏𝒕𝒊𝒔𝒕, open to work", 94 | "blog": "https://github.com/pustoshilov-d" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE 2 | recursive-include spython * 3 | recursive-exclude * __pycache__ 4 | recursive-exclude * *.pyc 5 | recursive-exclude * *.pyo 6 | recursive-exclude * *.simg 7 | recursive-exclude * *.img 8 | prune docs 9 | prune .docs 10 | -------------------------------------------------------------------------------- /Singularity: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: continuumio/miniconda3 3 | 4 | %runscript 5 | exec /opt/conda/bin/spython "$@" 6 | 7 | %labels 8 | maintainer vsochat@stanford.edu 9 | 10 | %post 11 | apt-get update && apt-get install -y git 12 | 13 | # Dependencies 14 | cd /opt 15 | git clone https://www.github.com/singularityhub/singularity-cli 16 | cd singularity-cli 17 | /opt/conda/bin/pip install setuptools 18 | /opt/conda/bin/python setup.py install 19 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: {{ site.name }} 4 | pdf: true 5 | permalink: / 6 | excluded_in_search: true 7 | --- 8 | 9 |
10 |
11 | 12 |
13 | 14 |


15 | 16 | 17 | # Singularity Python 18 | Welcome to the Singularity Python documentation! 19 | 20 | ## What is Singularity Python 21 | Singularity Python is a Python API to work with the Singularity open source software. Are you a scientist or developer? You likely will be most interested in the ability to interact with Singularity containers from your own Python scripts. See here for the core module docstrings. 22 | 23 | 24 | ## Getting Started 25 | 26 | - [Installation](/singularity-cli/install): to your system with the python package manager pip, or use the client in a container that we provide. 27 | - [Recipes](/singularity-cli/recipes): Interact with and convert Dockerfile and Singularity Recipes 28 | - [Commands](/singularity-cli/commands): Learn how to interact with Singularity using functions from the spython module. 29 | - [Developers](https://singularityhub.github.io/singularity-cli/api/source/spython.main.html): The readthedocs documentation for the Singularity Python API. 30 | 31 | 32 | ## Getting Help 33 | This is an open source project. Please contribute to the package, or post feedback and questions as issues. You'll notice a little eliipsis () next to each header section. If you click this, you can open an issue relevant to the section, grab a permalink, or suggest a change. You can also talk to us directly on Gitter. 34 | 35 | [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/singularityhub/lobby) 36 | 37 | 38 | ## Contributing 39 | We welcome any kind of contribution, whether a suggestion, a bug fix, feature request, or new feature. See our [contributing docs](/singularity-cli/contribute-docs) for more information. 40 | 41 | ## License 42 | This code is licensed under the MPL 2.0 [LICENSE](https://github.com/singularityhub/singularity-cli/blob/master/LICENSE). 43 | 44 | 45 |
46 | 47 |

48 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Setup 2 | title: Singularity Python 3 | tagline: "" 4 | baseurl: "/singularity-cli" 5 | github: "https://www.github.com/singularityhub/singularity-cli" 6 | 7 | # About/contact 8 | author: 9 | name: Vanessa Sochat 10 | url: https://singularityhub.github.io/singularity-cli 11 | 12 | # Defaults 13 | defaults: 14 | - 15 | scope: 16 | path: "" 17 | type: "pages" 18 | values: 19 | layout: "default" 20 | 21 | #Others 22 | markdown: kramdown 23 | -------------------------------------------------------------------------------- /docs/_data/toc.yml: -------------------------------------------------------------------------------- 1 | - title: "Home" 2 | url: "/singularity-cli/" 3 | slug: singularity-cli 4 | - title: "Installation" 5 | url: "/singularity-cli/install" 6 | slug: install 7 | - title: "Python API" 8 | url: "/singularity-cli/commands" 9 | slug: commands 10 | - title: "Image Commands" 11 | url: "/singularity-cli/commands-images" 12 | slug: images 13 | - title: "Instance Commands" 14 | url: "/singularity-cli/commands-instances" 15 | slug: instances 16 | - title: "OCI Commands" 17 | url: "/singularity-cli/commands-oci" 18 | slug: oci 19 | - title: "Recipe Generation" 20 | url: "/singularity-cli/recipes" 21 | slug: recipes 22 | - title: "Python API Docstring" 23 | url: "https://singularityhub.github.io/singularity-cli/api/source/spython.main.html" 24 | slug: docstring 25 | -------------------------------------------------------------------------------- /docs/_includes/editable.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 34 | 66 | -------------------------------------------------------------------------------- /docs/_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ site.title }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/_includes/navigation.html: -------------------------------------------------------------------------------- 1 | 9 |
10 |
11 | 12 | 36 |
37 |
38 | 39 | 40 | 41 | 55 | -------------------------------------------------------------------------------- /docs/_includes/page-footer.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /docs/_includes/page-header.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /docs/_includes/search.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | -------------------------------------------------------------------------------- /docs/_includes/toc.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 17 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include head.html %} 5 | {% include navigation.html %} 6 | 7 | 8 | {% include page-header.html %} 9 | 10 |


11 |
12 | 15 | 16 |

17 | 18 | {% if page.toc %} 19 | {% include toc.html %} 20 | {% endif %} 21 | {{ content }} 22 | {% include editable.html %} 23 | {% include page-footer.html %} 24 | 25 |
26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | {{ content }} 5 | -------------------------------------------------------------------------------- /docs/api/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. Singularity Python API documentation master file, created by 2 | sphinx-quickstart on Sun Feb 11 12:17:05 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Singularity Python API's documentation! 7 | ================================================== 8 | 9 | Singularity Python is the Python API for working with Singularity containers. The module is licensed 10 | under the MPL 2.0 LICENSE. 11 | 12 | Contents: 13 | 14 | .. toctree:: 15 | :maxdepth: 3 16 | 17 | source/spython.rst 18 | changelog.md 19 | 20 | 21 | Indices and tables 22 | ------------------ 23 | 24 | * :ref:`modindex` 25 | -------------------------------------------------------------------------------- /docs/api/_sources/source/modules.rst.txt: -------------------------------------------------------------------------------- 1 | spython 2 | ======= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | spython 8 | -------------------------------------------------------------------------------- /docs/api/_sources/source/spython.client.rst.txt: -------------------------------------------------------------------------------- 1 | spython\.client package 2 | ======================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.client\.recipe module 8 | ------------------------------ 9 | 10 | .. automodule:: spython.client.recipe 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.client\.shell module 16 | ----------------------------- 17 | 18 | .. automodule:: spython.client.shell 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.client\.test module 24 | ---------------------------- 25 | 26 | .. automodule:: spython.client.test 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: spython.client 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /docs/api/_sources/source/spython.image.cmd.rst.txt: -------------------------------------------------------------------------------- 1 | spython\.image\.cmd package 2 | =========================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.image\.cmd\.create module 8 | ---------------------------------- 9 | 10 | .. automodule:: spython.image.cmd.create 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.image\.cmd\.export module 16 | ---------------------------------- 17 | 18 | .. automodule:: spython.image.cmd.export 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.image\.cmd\.importcmd module 24 | ------------------------------------- 25 | 26 | .. automodule:: spython.image.cmd.importcmd 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | spython\.image\.cmd\.utils module 32 | --------------------------------- 33 | 34 | .. automodule:: spython.image.cmd.utils 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: spython.image.cmd 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /docs/api/_sources/source/spython.image.rst.txt: -------------------------------------------------------------------------------- 1 | spython\.image package 2 | ====================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | spython.image.cmd 10 | 11 | Module contents 12 | --------------- 13 | 14 | .. automodule:: spython.image 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | -------------------------------------------------------------------------------- /docs/api/_sources/source/spython.instance.cmd.rst.txt: -------------------------------------------------------------------------------- 1 | spython\.instance\.cmd package 2 | ============================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.instance\.cmd\.iutils module 8 | ------------------------------------- 9 | 10 | .. automodule:: spython.instance.cmd.iutils 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.instance\.cmd\.start module 16 | ------------------------------------ 17 | 18 | .. automodule:: spython.instance.cmd.start 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.instance\.cmd\.stop module 24 | ----------------------------------- 25 | 26 | .. automodule:: spython.instance.cmd.stop 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: spython.instance.cmd 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /docs/api/_sources/source/spython.instance.rst.txt: -------------------------------------------------------------------------------- 1 | spython\.instance package 2 | ========================= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | spython.instance.cmd 10 | 11 | Module contents 12 | --------------- 13 | 14 | .. automodule:: spython.instance 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | -------------------------------------------------------------------------------- /docs/api/_sources/source/spython.logger.rst.txt: -------------------------------------------------------------------------------- 1 | spython\.logger package 2 | ======================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.logger\.message module 8 | ------------------------------- 9 | 10 | .. automodule:: spython.logger.message 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.logger\.progress module 16 | -------------------------------- 17 | 18 | .. automodule:: spython.logger.progress 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.logger\.spinner module 24 | ------------------------------- 25 | 26 | .. automodule:: spython.logger.spinner 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: spython.logger 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /docs/api/_sources/source/spython.main.base.rst.txt: -------------------------------------------------------------------------------- 1 | spython\.main\.base package 2 | =========================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.main\.base\.command module 8 | ----------------------------------- 9 | 10 | .. automodule:: spython.main.base.command 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.main\.base\.flags module 16 | --------------------------------- 17 | 18 | .. automodule:: spython.main.base.flags 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.main\.base\.generate module 24 | ------------------------------------ 25 | 26 | .. automodule:: spython.main.base.generate 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | spython\.main\.base\.logger module 32 | ---------------------------------- 33 | 34 | .. automodule:: spython.main.base.logger 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | spython\.main\.base\.sutils module 40 | ---------------------------------- 41 | 42 | .. automodule:: spython.main.base.sutils 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | 48 | Module contents 49 | --------------- 50 | 51 | .. automodule:: spython.main.base 52 | :members: 53 | :undoc-members: 54 | :show-inheritance: 55 | -------------------------------------------------------------------------------- /docs/api/_sources/source/spython.main.parse.rst.txt: -------------------------------------------------------------------------------- 1 | spython\.main\.parse package 2 | ============================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.main\.parse\.converters module 8 | --------------------------------------- 9 | 10 | .. automodule:: spython.main.parse.converters 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.main\.parse\.docker module 16 | ----------------------------------- 17 | 18 | .. automodule:: spython.main.parse.docker 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.main\.parse\.environment module 24 | ---------------------------------------- 25 | 26 | .. automodule:: spython.main.parse.environment 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | spython\.main\.parse\.recipe module 32 | ----------------------------------- 33 | 34 | .. automodule:: spython.main.parse.recipe 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | spython\.main\.parse\.singularity module 40 | ---------------------------------------- 41 | 42 | .. automodule:: spython.main.parse.singularity 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | 48 | Module contents 49 | --------------- 50 | 51 | .. automodule:: spython.main.parse 52 | :members: 53 | :undoc-members: 54 | :show-inheritance: 55 | -------------------------------------------------------------------------------- /docs/api/_sources/source/spython.main.rst.txt: -------------------------------------------------------------------------------- 1 | spython\.main package 2 | ===================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | spython.main.base 10 | spython.main.parse 11 | 12 | Submodules 13 | ---------- 14 | 15 | spython\.main\.apps module 16 | -------------------------- 17 | 18 | .. automodule:: spython.main.apps 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.main\.build module 24 | --------------------------- 25 | 26 | .. automodule:: spython.main.build 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | spython\.main\.execute module 32 | ----------------------------- 33 | 34 | .. automodule:: spython.main.execute 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | spython\.main\.help module 40 | -------------------------- 41 | 42 | .. automodule:: spython.main.help 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | spython\.main\.inspect module 48 | ----------------------------- 49 | 50 | .. automodule:: spython.main.inspect 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | spython\.main\.instances module 56 | ------------------------------- 57 | 58 | .. automodule:: spython.main.instances 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | spython\.main\.pull module 64 | -------------------------- 65 | 66 | .. automodule:: spython.main.pull 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | 71 | spython\.main\.run module 72 | ------------------------- 73 | 74 | .. automodule:: spython.main.run 75 | :members: 76 | :undoc-members: 77 | :show-inheritance: 78 | 79 | 80 | Module contents 81 | --------------- 82 | 83 | .. automodule:: spython.main 84 | :members: 85 | :undoc-members: 86 | :show-inheritance: 87 | -------------------------------------------------------------------------------- /docs/api/_sources/source/spython.rst.txt: -------------------------------------------------------------------------------- 1 | spython package 2 | =============== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | spython.client 10 | spython.image 11 | spython.instance 12 | spython.logger 13 | spython.main 14 | spython.utils 15 | 16 | Submodules 17 | ---------- 18 | 19 | spython\.version module 20 | ----------------------- 21 | 22 | .. automodule:: spython.version 23 | :members: 24 | :undoc-members: 25 | :show-inheritance: 26 | 27 | 28 | Module contents 29 | --------------- 30 | 31 | .. automodule:: spython 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | -------------------------------------------------------------------------------- /docs/api/_sources/source/spython.tests.rst.txt: -------------------------------------------------------------------------------- 1 | spython\.tests package 2 | ====================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.tests\.test\_client module 8 | ----------------------------------- 9 | 10 | .. automodule:: spython.tests.test_client 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.tests\.test\_instances module 16 | -------------------------------------- 17 | 18 | .. automodule:: spython.tests.test_instances 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | spython\.tests\.test\_utils module 24 | ---------------------------------- 25 | 26 | .. automodule:: spython.tests.test_utils 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: spython.tests 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /docs/api/_sources/source/spython.utils.rst.txt: -------------------------------------------------------------------------------- 1 | spython\.utils package 2 | ====================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | spython\.utils\.fileio module 8 | ----------------------------- 9 | 10 | .. automodule:: spython.utils.fileio 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | spython\.utils\.terminal module 16 | ------------------------------- 17 | 18 | .. automodule:: spython.utils.terminal 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | 24 | Module contents 25 | --------------- 26 | 27 | .. automodule:: spython.utils 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | -------------------------------------------------------------------------------- /docs/api/assets/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} 2 | -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/api/assets/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/api/assets/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: '1', 4 | LANGUAGE: 'None', 5 | COLLAPSE_INDEX: false, 6 | BUILDER: 'html', 7 | FILE_SUFFIX: '.html', 8 | LINK_SUFFIX: '.html', 9 | HAS_SOURCE: true, 10 | SOURCELINK_SUFFIX: '.txt', 11 | NAVIGATION_WITH_KEYS: false, 12 | SHOW_SEARCH_SUMMARY: true, 13 | ENABLE_SEARCH_SHORTCUTS: true, 14 | }; 15 | -------------------------------------------------------------------------------- /docs/api/assets/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/file.png -------------------------------------------------------------------------------- /docs/api/assets/js/badge_only.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}}); 2 | -------------------------------------------------------------------------------- /docs/api/assets/js/html5shiv-printshiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3-pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); 5 | -------------------------------------------------------------------------------- /docs/api/assets/js/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); 5 | -------------------------------------------------------------------------------- /docs/api/assets/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/minus.png -------------------------------------------------------------------------------- /docs/api/assets/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/assets/plus.png -------------------------------------------------------------------------------- /docs/api/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/api/objects.inv -------------------------------------------------------------------------------- /docs/api/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Search — Singularity Python API 1 documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 50 | 51 |
55 | 56 |
57 |
58 |
59 |
    60 |
  • »
  • 61 |
  • Search
  • 62 |
  • 63 |
  • 64 |
65 |
66 |
67 |
68 |
69 | 70 | 77 | 78 | 79 |
80 | 81 |
82 | 83 |
84 |
85 | 99 |
100 |
101 |
102 |
103 | 108 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/favicon.ico -------------------------------------------------------------------------------- /docs/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/img/logo.png -------------------------------------------------------------------------------- /docs/img/robot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/img/robot.png -------------------------------------------------------------------------------- /docs/img/shub-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/img/shub-logo.png -------------------------------------------------------------------------------- /docs/img/stanford.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/docs/img/stanford.png -------------------------------------------------------------------------------- /docs/js/search.js: -------------------------------------------------------------------------------- 1 | --- 2 | layout: null 3 | --- 4 | (function () { 5 | function getQueryVariable(variable) { 6 | var query = window.location.search.substring(1), 7 | vars = query.split("&"); 8 | 9 | for (var i = 0; i < vars.length; i++) { 10 | var pair = vars[i].split("="); 11 | 12 | if (pair[0] === variable) { 13 | return decodeURIComponent(pair[1].replace(/\+/g, '%20')).trim(); 14 | } 15 | } 16 | } 17 | 18 | function getPreview(query, content, previewLength) { 19 | previewLength = previewLength || (content.length * 2); 20 | 21 | var parts = query.split(" "), 22 | match = content.toLowerCase().indexOf(query.toLowerCase()), 23 | matchLength = query.length, 24 | preview; 25 | 26 | // Find a relevant location in content 27 | for (var i = 0; i < parts.length; i++) { 28 | if (match >= 0) { 29 | break; 30 | } 31 | 32 | match = content.toLowerCase().indexOf(parts[i].toLowerCase()); 33 | matchLength = parts[i].length; 34 | } 35 | 36 | // Create preview 37 | if (match >= 0) { 38 | var start = match - (previewLength / 2), 39 | end = start > 0 ? match + matchLength + (previewLength / 2) : previewLength; 40 | 41 | preview = content.substring(start, end).trim(); 42 | 43 | if (start > 0) { 44 | preview = "..." + preview; 45 | } 46 | 47 | if (end < content.length) { 48 | preview = preview + "..."; 49 | } 50 | 51 | // Highlight query parts 52 | preview = preview.replace(new RegExp("(" + parts.join("|") + ")", "gi"), "$1"); 53 | } else { 54 | // Use start of content if no match found 55 | preview = content.substring(0, previewLength).trim() + (content.length > previewLength ? "..." : ""); 56 | } 57 | 58 | return preview; 59 | } 60 | 61 | function displaySearchResults(results, query) { 62 | var searchResultsEl = document.getElementById("search-results"), 63 | searchProcessEl = document.getElementById("search-process"); 64 | 65 | if (results.length) { 66 | var resultsHTML = ""; 67 | results.forEach(function (result) { 68 | var item = window.data[result.ref], 69 | contentPreview = getPreview(query, item.content, 170), 70 | titlePreview = getPreview(query, item.title); 71 | 72 | resultsHTML += "
  • " + titlePreview + "

    " + contentPreview + "

  • "; 73 | }); 74 | 75 | searchResultsEl.innerHTML = resultsHTML; 76 | searchProcessEl.innerText = "Showing"; 77 | } else { 78 | searchResultsEl.style.display = "none"; 79 | searchProcessEl.innerText = "No"; 80 | } 81 | } 82 | 83 | window.index = lunr(function () { 84 | this.field("id"); 85 | this.field("title", {boost: 10}); 86 | this.field("categories"); 87 | this.field("url"); 88 | this.field("content"); 89 | }); 90 | 91 | var query = decodeURIComponent((getQueryVariable("q") || "").replace(/\+/g, "%20")), 92 | searchQueryContainerEl = document.getElementById("search-query-container"), 93 | searchQueryEl = document.getElementById("search-query"); 94 | 95 | searchQueryEl.innerText = query; 96 | searchQueryContainerEl.style.display = "inline"; 97 | 98 | for (var key in window.data) { 99 | window.index.add(window.data[key]); 100 | } 101 | 102 | displaySearchResults(window.index.search(query), query); // Hand the results off to be displayed 103 | })(); 104 | -------------------------------------------------------------------------------- /docs/js/toc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/ghiculescu/jekyll-table-of-contents 2 | (function($){ 3 | $.fn.toc = function(options) { 4 | var defaults = { 5 | noBackToTopLinks: false, 6 | title: '', 7 | minimumHeaders: 3, 8 | headers: 'h1, h2, h3, h4', 9 | listType: 'ol', // values: [ol|ul] 10 | showEffect: 'show', // values: [show|slideDown|fadeIn|none] 11 | showSpeed: 'slow' // set to 0 to deactivate effect 12 | }, 13 | settings = $.extend(defaults, options); 14 | 15 | var headers = $(settings.headers).filter(function() { 16 | // get all headers with an ID 17 | var previousSiblingName = $(this).prev().attr( "name" ); 18 | if (!this.id && previousSiblingName) { 19 | this.id = $(this).attr( "id", previousSiblingName.replace(/\./g, "-") ); 20 | } 21 | return this.id; 22 | }), output = $(this); 23 | if (!headers.length || headers.length < settings.minimumHeaders || !output.length) { 24 | return; 25 | } 26 | 27 | if (0 === settings.showSpeed) { 28 | settings.showEffect = 'none'; 29 | } 30 | 31 | var render = { 32 | show: function() { output.hide().html(html).show(settings.showSpeed); }, 33 | slideDown: function() { output.hide().html(html).slideDown(settings.showSpeed); }, 34 | fadeIn: function() { output.hide().html(html).fadeIn(settings.showSpeed); }, 35 | none: function() { output.html(html); } 36 | }; 37 | 38 | var get_level = function(ele) { return parseInt(ele.nodeName.replace("H", ""), 10); } 39 | var highest_level = headers.map(function(_, ele) { return get_level(ele); }).get().sort()[0]; 40 | var return_to_top = ' '; 41 | 42 | var level = get_level(headers[0]), 43 | this_level, 44 | html = settings.title + " <"+settings.listType+">"; 45 | headers.on('click', function() { 46 | if (!settings.noBackToTopLinks) { 47 | window.location.hash = this.id; 48 | } 49 | }) 50 | .addClass('clickable-header') 51 | .each(function(_, header) { 52 | this_level = get_level(header); 53 | if (!settings.noBackToTopLinks && this_level === highest_level) { 54 | $(header).addClass('top-level-header').after(return_to_top); 55 | } 56 | if (this_level === level) // same level as before; same indenting 57 | html += "
  • " + header.innerHTML + ""; 58 | else if (this_level <= level){ // higher level than before; end parent ol 59 | for(i = this_level; i < level; i++) { 60 | html += "
  • " 61 | } 62 | html += "
  • " + header.innerHTML + ""; 63 | } 64 | else if (this_level > level) { // lower level than before; expand the previous to contain a ol 65 | for(i = this_level; i > level; i--) { 66 | html += "<"+settings.listType+">
  • " 67 | } 68 | html += "" + header.innerHTML + ""; 69 | } 70 | level = this_level; // update for the next one 71 | }); 72 | html += ""; 73 | if (!settings.noBackToTopLinks) { 74 | $(document).on('click', '.back-to-top', function() { 75 | $(window).scrollTop(0); 76 | window.location.hash = ''; 77 | }); 78 | } 79 | 80 | render[settings.showEffect](); 81 | }; 82 | })(jQuery); 83 | -------------------------------------------------------------------------------- /docs/pages/client.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Command Line Client (spython) 4 | pdf: true 5 | permalink: /client 6 | toc: false 7 | --- 8 | 9 | # Singularity Python Command Line 10 | Welcome to the Singularity Python command line tool, `spython` documentation! Follow the links to the documentation that is of interest to you. 11 | 12 | - [Client](/singularity-cli/commands): Walk through the basic client commands to push, pull, build, and otherwise interact with images from Python 13 | - [Recipes](/singularity-cli/recipes): Convert and interact with Dockerfile and Singularity Recipes 14 | 15 | 16 |
    17 | 18 | 19 |

    20 | -------------------------------------------------------------------------------- /docs/pages/commands.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Getting Started 4 | pdf: true 5 | permalink: /commands 6 | toc: false 7 | --- 8 | 9 | # Python API 10 | 11 | This commands section primarily focuses on using Singularity Python from within Python and then in an interactive shell (for testing). We will first discuss the Python API, meaning functions that you can use in python to work with Singularity images or instances. Python is strong in the world of scientific programming, and so if you are reading these notes it's likely that you want to integrate Singularity containers into your Python applications. We wrote you a client to do that! Please select the type of command you are interested in below to continue! 12 | 13 | - [Images](/singularity-cli/commands-images): base API to interact with containers: 14 | - [Instances](/singularity-cli/commands-instances): interact with container instances. 15 | - [OCI](/singularity-cli/commands-oci): interact with the OCI command group (3.1.0 and up) 16 | 17 | Note that for any command that allows specifying `sudo`, you can provide additional options to sudo 18 | (e.g., `-E` to preserve the environment) via `sudo_options`. 19 | 20 |
    21 | 22 |
    23 | 24 | 25 |

    26 | -------------------------------------------------------------------------------- /docs/pages/contribute-docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Contribute to Singularity Python Client Documentation 4 | pdf: true 5 | permalink: /contribute-docs 6 | toc: false 7 | --- 8 | 9 | # Singularity Python API Documentation 10 | 11 | The documentation is served in the Github pages site served in the docs folder. 12 | If you clone the repo, cd into this folder, and then install (and run) Jekyll you 13 | can usually get started working on the documentation. 14 | 15 | 16 | ## Dependencies 17 | Initially (on OS X), you will need to setup [Brew](http://brew.sh/) which is a package manager for OS X and [Git](https://git-scm.com/). To install Brew and Git, run the following commands: 18 | 19 | ```bash 20 | /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 21 | brew install git 22 | ``` 23 | If you are on Debian/Ubuntu, then you can easily install git with `apt-get` 24 | 25 | ```bash 26 | apt-get update && apt-get install -y git 27 | ``` 28 | 29 | ### Fork the Repository 30 | To contribute to the web based documentation, you should obtain a GitHub account and *fork* the Singularity Python repository by clicking the *fork* button on the top right of the page. Once forked, you will want to clone the fork of the repo to your computer. Let's say my Github username is *vsoch*: 31 | 32 | ```bash 33 | git clone https://github.com/vsoch/singularity-cli.git 34 | cd singularity-cli 35 | ``` 36 | 37 | ### Install Jekyll 38 | You can also install Jekyll with brew. 39 | 40 | ```bash 41 | brew install ruby 42 | gem install jekyll 43 | gem install bundler 44 | bundle install 45 | ``` 46 | On Ubuntu I do a different method: 47 | 48 | ``` 49 | git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build 50 | echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc 51 | exec $SHELL 52 | rbenv install 2.3.1 53 | rbenv global 2.3.1 54 | gem install bundler 55 | rbenv rehash 56 | ruby -v 57 | 58 | # Rails 59 | curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash - 60 | sudo apt-get install -y nodejs 61 | gem install rails -v 4.2.6 62 | rbenv rehash 63 | 64 | # Jekyll 65 | gem install jekyll 66 | gem install github-pages 67 | gem install jekyll-sass-converter 68 | 69 | rbenv rehash 70 | ``` 71 | 72 | ## Build and Serve 73 | After you cd into the docs folder (the base of the site) you can see the site locally by running the server with jekyll: 74 | 75 | ```bash 76 | cd docs/ 77 | bundle exec jekyll serve 78 | ``` 79 | 80 | or sometimes this works. 81 | 82 | ``` 83 | jekyll serve 84 | ``` 85 | 86 | And the site will be viewable at http://127.0.0.1:4000/singularity-cli/. You can edit the markdown files in various folders to see changes in your browser, and push to your fork and then pull request to make changes. If you have a minor change, you can also do a patch directly in your browser from Github. 87 | 88 |
    89 | 90 | 91 |

    92 | -------------------------------------------------------------------------------- /docs/pages/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Installation 4 | pdf: true 5 | permalink: /install 6 | toc: false 7 | --- 8 | 9 | # Installation Local 10 | You need `python3` and `pip3` in order to use this API. 11 | 12 | To install from the Github repository: 13 | 14 | ```bash 15 | git clone https://www.github.com/singularityhub/singularity-cli.git 16 | cd singularity-cli 17 | python3 setup.py install 18 | ``` 19 | 20 | And you can also install from pip: 21 | 22 | ```bash 23 | # Client and Database 24 | pip3 install spython 25 | ``` 26 | 27 | 28 | # Singularity 29 | You can also use our Singularity image provided, each directly from Singularity 30 | Hub, or via building on your own. To build a singularity container 31 | 32 | ``` 33 | sudo singularity build spython Singularity 34 | ``` 35 | 36 |
    37 | 38 | 39 |

    40 | -------------------------------------------------------------------------------- /docs/pages/search.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: Search 3 | sitemap: false 4 | permalink: /search 5 | not_editable: true 6 | --- 7 | 8 |

    Loading results

    9 | 10 | 11 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | profile = "black" 3 | exclude = ["^env/"] 4 | 5 | [tool.isort] 6 | profile = "black" # needed for black/isort compatibility 7 | skip = [] 8 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = benchmarks docs 3 | max-line-length = 100 4 | ignore = E1 E2 E5 W5 5 | per-file-ignores = 6 | spython/__init__.py:F401 7 | spython/utils/__init__.py:F401 8 | spython/clint/__init__.py:F401 9 | spython/logger/__init__.py:F401 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import find_packages, setup 4 | 5 | ################################################################################ 6 | # HELPER FUNCTIONS ############################################################# 7 | ################################################################################ 8 | 9 | 10 | def get_lookup(): 11 | """get version by way of singularity.version, returns a 12 | lookup dictionary with several global variables without 13 | needing to import spython 14 | """ 15 | lookup = dict() 16 | version_file = os.path.join("spython", "version.py") 17 | with open(version_file) as filey: 18 | exec(filey.read(), lookup) 19 | return lookup 20 | 21 | 22 | def get_requirements(lookup=None): 23 | """get_requirements reads in requirements and versions from 24 | the lookup obtained with get_lookup""" 25 | 26 | if lookup is None: 27 | lookup = get_lookup() 28 | 29 | install_requires = [] 30 | for module in lookup["INSTALL_REQUIRES"]: 31 | module_name = module[0] 32 | module_meta = module[1] 33 | if "exact_version" in module_meta: 34 | dependency = "%s==%s" % (module_name, module_meta["exact_version"]) 35 | elif "min_version" in module_meta: 36 | if module_meta["min_version"] is None: 37 | dependency = module_name 38 | else: 39 | dependency = "%s>=%s" % (module_name, module_meta["min_version"]) 40 | install_requires.append(dependency) 41 | return install_requires 42 | 43 | 44 | # Make sure everything is relative to setup.py 45 | install_path = os.path.dirname(os.path.abspath(__file__)) 46 | os.chdir(install_path) 47 | 48 | # Get version information from the lookup 49 | lookup = get_lookup() 50 | VERSION = lookup["__version__"] 51 | NAME = lookup["NAME"] 52 | AUTHOR = lookup["AUTHOR"] 53 | AUTHOR_EMAIL = lookup["AUTHOR_EMAIL"] 54 | PACKAGE_URL = lookup["PACKAGE_URL"] 55 | KEYWORDS = lookup["KEYWORDS"] 56 | DESCRIPTION = lookup["DESCRIPTION"] 57 | LICENSE = lookup["LICENSE"] 58 | with open("README.md") as readme: 59 | LONG_DESCRIPTION = readme.read() 60 | 61 | ########################################################################################## 62 | # MAIN ################################################################################### 63 | ########################################################################################## 64 | 65 | 66 | if __name__ == "__main__": 67 | INSTALL_REQUIRES = get_requirements(lookup) 68 | TESTS_REQUIRES = get_requirements(lookup) 69 | 70 | setup( 71 | name=NAME, 72 | version=VERSION, 73 | author=AUTHOR, 74 | author_email=AUTHOR_EMAIL, 75 | maintainer=AUTHOR, 76 | maintainer_email=AUTHOR_EMAIL, 77 | packages=find_packages(), 78 | include_package_data=True, 79 | zip_safe=False, 80 | url=PACKAGE_URL, 81 | license=LICENSE, 82 | description=DESCRIPTION, 83 | long_description=LONG_DESCRIPTION, 84 | long_description_content_type="text/markdown", 85 | keywords=KEYWORDS, 86 | setup_requires=["pytest-runner"], 87 | tests_require=TESTS_REQUIRES, 88 | install_requires=INSTALL_REQUIRES, 89 | classifiers=[ 90 | "Intended Audience :: Science/Research", 91 | "Intended Audience :: Developers", 92 | "Programming Language :: Python", 93 | "Topic :: Software Development", 94 | "Topic :: Scientific/Engineering", 95 | "Operating System :: Unix", 96 | "Programming Language :: Python :: 3", 97 | ], 98 | entry_points={"console_scripts": ["spython=spython.client:main"]}, 99 | ) 100 | -------------------------------------------------------------------------------- /spython/README.md: -------------------------------------------------------------------------------- 1 | # Singularity Python Organization 2 | This will briefly outline the content of the folders here. 3 | 4 | ## Main Functions 5 | 6 | - [main](main) holds the primary client functions to interact with Singularity (e.g., exec, run, pull), and the subfolders within represent command groups (e.g., instance, image) along with the base of the client ([base](main/base)). This folder is where **commands** go! 7 | - [image](image.py) is a class that represents and holds an image object, in the case that the user wants to initialize a client with an image. This holds the ImageClass, which is only needed to instantiate an image. 8 | - [instance](instance) is a class that represents and holds an instance object. The user can instantiate the class, and then run it's sub functions to interact with it. The higher level "list" command is provided on the level of the client. 9 | - [cli](cli): is the actual entry point that connects the user to the python API client from the command line. The user inputs are parsed, and then passed into functions from main. 10 | 11 | ## Supporting 12 | - [utils](utils) are various file and other utilities shared across submodules. 13 | - [logger](logger) includes functions for progress bars, and logging levels. 14 | - [tests](tests) are important for continuous integration, but likely overlooked. 15 | -------------------------------------------------------------------------------- /spython/__init__.py: -------------------------------------------------------------------------------- 1 | from spython.version import __version__ 2 | -------------------------------------------------------------------------------- /spython/client/recipe.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | import json 8 | import os 9 | import sys 10 | 11 | from spython.logger import bot 12 | from spython.main.parse.parsers import get_parser 13 | from spython.main.parse.writers import get_writer 14 | from spython.utils import write_file, write_json 15 | 16 | 17 | def main(args, options, parser): 18 | """This function serves as a wrapper around the DockerParser, 19 | SingularityParser, DockerWriter, and SingularityParser converters. 20 | We can either save to file if args.outfile is defined, or print 21 | to the console if not. 22 | """ 23 | # We need something to work with 24 | if not args.files: 25 | parser.print_help() 26 | sys.exit(1) 27 | 28 | # Get the user specified input and output files 29 | outfile = None 30 | if len(args.files) > 1: 31 | outfile = args.files[1] 32 | 33 | # First try to get writer and parser, if not defined will return None 34 | writer = get_writer(args.writer) 35 | parser = get_parser(args.parser) 36 | 37 | # If the user wants to auto-detect the type 38 | if args.parser == "auto": 39 | if "dockerfile" in args.files[0].lower(): 40 | parser = get_parser("docker") 41 | elif "singularity" in args.files[0].lower(): 42 | parser = get_parser("singularity") 43 | 44 | # If the parser still isn't defined, no go. 45 | if parser is None: 46 | bot.exit( 47 | "Please provide a Dockerfile or Singularity recipe, or define the --parser type." 48 | ) 49 | 50 | # If the writer needs auto-detect 51 | if args.writer == "auto": 52 | if parser.name == "docker": 53 | writer = get_writer("singularity") 54 | else: 55 | writer = get_writer("docker") 56 | 57 | # If the writer still isn't defined, no go 58 | if writer is None: 59 | bot.exit("Please define the --writer type.") 60 | 61 | # Initialize the chosen parser 62 | recipeParser = parser(args.files[0]) 63 | 64 | # By default, discover entrypoint / cmd from Dockerfile 65 | entrypoint = "/bin/bash" 66 | force = False 67 | 68 | if args.entrypoint is not None: 69 | entrypoint = args.entrypoint 70 | 71 | # This is only done if the user intended to print json here 72 | recipeParser.entrypoint = args.entrypoint 73 | recipeParser.cmd = None 74 | force = True 75 | 76 | if args.json: 77 | if outfile is not None: 78 | if not os.path.exists(outfile): 79 | if force: 80 | write_json(outfile, recipeParser.recipe.json()) 81 | else: 82 | bot.exit("%s exists, set --force to overwrite." % outfile) 83 | else: 84 | print(json.dumps(recipeParser.recipe.json(), indent=4)) 85 | 86 | else: 87 | # Do the conversion 88 | recipeWriter = writer(recipeParser.recipe) 89 | result = recipeWriter.convert(runscript=entrypoint, force=force) 90 | 91 | # If the user specifies an output file, save to it 92 | if outfile is not None: 93 | write_file(outfile, result) 94 | 95 | # Otherwise, convert and print to screen 96 | else: 97 | print(result) 98 | -------------------------------------------------------------------------------- /spython/client/shell.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | def main(args, options, parser): 9 | # If we have options, first is image 10 | image = None 11 | if options: 12 | image = options.pop(0) 13 | 14 | lookup = {"ipython": ipython, "python": python, "bpython": run_bpython} 15 | 16 | shells = ["ipython", "python", "bpython"] 17 | 18 | # Otherwise present order of liklihood to have on system 19 | for shell in shells: 20 | try: 21 | return lookup[shell](image) 22 | except ImportError: 23 | pass 24 | 25 | 26 | def prepare_client(image): 27 | """prepare a client to embed in a shell with recipe parsers and writers.""" 28 | # The client will announce itself (backend/database) unless it's get 29 | from spython.main import get_client 30 | from spython.main.parse import parsers, writers 31 | 32 | client = get_client() 33 | 34 | if image: 35 | client.load(image) 36 | 37 | # Add recipe parsers 38 | client.parsers = parsers 39 | client.writers = writers 40 | return client 41 | 42 | 43 | def ipython(image): 44 | """give the user an ipython shell""" 45 | client = prepare_client(image) # noqa 46 | 47 | try: 48 | from IPython import embed 49 | except ImportError: 50 | return python(image) 51 | 52 | embed() 53 | 54 | 55 | def run_bpython(image): 56 | """give the user a bpython shell""" 57 | client = prepare_client(image) 58 | 59 | try: 60 | import bpython 61 | except ImportError: 62 | return python(image) 63 | 64 | bpython.embed(locals_={"client": client}) 65 | 66 | 67 | def python(image): 68 | """give the user a python shell""" 69 | import code 70 | 71 | client = prepare_client(image) 72 | code.interact(local={"client": client}) 73 | -------------------------------------------------------------------------------- /spython/client/test.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | def main(args, options, parser): 9 | print("TBA, additional tests for Singularity containers.") 10 | print("What would you like to see? Let us know!") 11 | print("https://www.github.com/singularityhub/singularity-cli/issues") 12 | -------------------------------------------------------------------------------- /spython/image.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | import hashlib 9 | import os 10 | 11 | from spython.logger import bot 12 | from spython.utils import split_uri 13 | 14 | 15 | class ImageBase: 16 | def __str__(self): 17 | protocol = getattr(self, "protocol", None) 18 | if protocol: 19 | return "%s://%s" % (protocol, self.image) 20 | return self.image 21 | 22 | def __repr__(self): 23 | return self.__str__() 24 | 25 | def parse_image_name(self, image): 26 | """ 27 | simply split the uri from the image. Singularity handles 28 | parsing of registry, namespace, image. 29 | 30 | Parameters 31 | ========== 32 | image: the complete image uri to load (e.g., docker://ubuntu) 33 | 34 | """ 35 | self._image = image 36 | self.protocol, self.image = split_uri(image) 37 | 38 | 39 | class Image(ImageBase): 40 | def __init__(self, image=None): 41 | """An image here is an image file or a record. 42 | The user can choose to load the image when starting the client, or 43 | update the main client with an image. The image object is kept 44 | with the main client to make running additional commands easier. 45 | 46 | Parameters 47 | ========== 48 | image: the image uri to parse (required) 49 | 50 | """ 51 | super(Image, self).__init__() 52 | self.parse_image_name(image) 53 | 54 | def get_hash(self, image=None): 55 | """return an md5 hash of the file based on a criteria level. This 56 | is intended to give the file a reasonable version. This only is 57 | useful for actual image files. 58 | 59 | Parameters 60 | ========== 61 | image: the image path to get hash for (first priority). Second 62 | priority is image path saved with image object, if exists. 63 | 64 | """ 65 | hasher = hashlib.md5() 66 | image = image or self.image 67 | 68 | if os.path.exists(image): 69 | with open(image, "rb") as f: 70 | for chunk in iter(lambda: f.read(4096), b""): 71 | hasher.update(chunk) 72 | return hasher.hexdigest() 73 | 74 | bot.warning("%s does not exist." % image) 75 | -------------------------------------------------------------------------------- /spython/instance/cmd/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | def generate_instance_commands(): 9 | """The Instance client holds the Singularity Instance command group 10 | The levels of verbosity (debug and quiet) are passed from the main 11 | client via the environment variable MESSAGELEVEL. 12 | 13 | """ 14 | from spython.instance import Instance 15 | 16 | # run_command uses run_cmd, but wraps to catch error 17 | from spython.main.base.command import init_command, run_command 18 | from spython.main.base.generate import RobotNamer 19 | from spython.main.base.logger import println 20 | from spython.main.instances import list_instances 21 | 22 | from .logs import _logs, error_logs, output_logs 23 | from .start import start 24 | from .stop import stop 25 | 26 | Instance.RobotNamer = RobotNamer() 27 | Instance._init_command = init_command 28 | Instance.run_command = run_command 29 | Instance._list = list_instances # list command is used to get metadata 30 | Instance._println = println 31 | Instance.start = start # intended to be called on init, not by user 32 | Instance.stop = stop 33 | Instance.error_logs = error_logs 34 | Instance.output_logs = output_logs 35 | Instance._logs = _logs 36 | 37 | # Give an instance the ability to breed :) 38 | Instance.instance = Instance 39 | 40 | return Instance 41 | -------------------------------------------------------------------------------- /spython/instance/cmd/logs.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | import os 9 | import platform 10 | 11 | from spython.logger import bot 12 | from spython.utils import get_userhome, get_username 13 | 14 | 15 | def error_logs(self, print_logs=False): 16 | """For Singularity 3.5 and later, we are able to programatically 17 | derive the name of the log. In this case, return the content 18 | to the user. See 19 | https://github.com/sylabs/singularity/issues/1115#issuecomment-560457918 20 | for when this was added. 21 | 22 | Parameters 23 | ========== 24 | print_logs: boolean to indicate to print to the screen along with 25 | return (defaults to False to just return log string) 26 | """ 27 | return self._logs(print_logs, "err") 28 | 29 | 30 | def output_logs(self, print_logs=False): 31 | """Get output logs for the user, if they exist. 32 | 33 | Parameters 34 | ========== 35 | print_logs: boolean to indicate to print to the screen along with 36 | return (defaults to False to just return log string) 37 | """ 38 | return self._logs(print_logs, "out") 39 | 40 | 41 | def _logs(self, print_logs=False, ext="out"): 42 | """A shared function to print log files. The only differing element is 43 | the extension (err or out) 44 | """ 45 | from spython.utils import check_install 46 | 47 | check_install() 48 | 49 | # Formulate the path of the logs 50 | hostname = platform.node() 51 | logpath = os.path.join( 52 | get_userhome(), 53 | ".singularity", 54 | "instances", 55 | "logs", 56 | hostname, 57 | get_username(), 58 | "%s.%s" % (self.name, ext), 59 | ) 60 | 61 | if os.path.exists(logpath): 62 | with open(logpath, "r") as filey: 63 | logs = filey.read() 64 | if print_logs is True: 65 | print(logs) 66 | else: 67 | bot.warning("No log files have been produced.") 68 | return logs 69 | -------------------------------------------------------------------------------- /spython/instance/cmd/start.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | from spython.logger import bot 9 | 10 | 11 | def start( 12 | self, 13 | image=None, 14 | name=None, 15 | args=None, 16 | sudo=False, 17 | sudo_options=None, 18 | options=None, 19 | capture=False, 20 | singularity_options=None, 21 | environ=None, 22 | quiet=True, 23 | ): 24 | """start an instance. This is done by default when an instance is created. 25 | 26 | Parameters 27 | ========== 28 | image: optionally, an image uri (if called as a command from Client) 29 | name: a name for the instance 30 | sudo: if the user wants to run the command with sudo 31 | capture: capture output, default is False. With True likely to hang. 32 | args: arguments to provide to the instance (supported Singularity 3.1+) 33 | singularity_options: a list of options to provide to the singularity client 34 | quiet: Do not print verbose output. 35 | options: a list of tuples, each an option to give to the start command 36 | [("--bind", "/tmp"),...] 37 | 38 | USAGE: 39 | singularity [...] instance.start [...] 40 | 41 | """ 42 | from spython.utils import check_install, run_command 43 | 44 | check_install() 45 | 46 | # If name provided, over write robot (default) 47 | if name is not None: 48 | self.name = name 49 | 50 | # If an image isn't provided, we have an initialized instance 51 | if image is None: 52 | # Not having this means it was called as a command, without an image 53 | if not hasattr(self, "_image"): 54 | bot.exit("Please provide an image, or create an Instance first.") 55 | 56 | image = self._image 57 | 58 | cmd = self._init_command(["instance", "start"], singularity_options) 59 | 60 | # Set options and args 61 | args = args or self.args 62 | options = options or self.options 63 | 64 | # Add options, if they are provided 65 | if not isinstance(options, list): 66 | options = [] if options is None else options.split(" ") 67 | 68 | # Assemble the command! 69 | cmd = cmd + options + [image, self.name] 70 | 71 | # If arguments are provided 72 | if args is not None: 73 | if not isinstance(args, list): 74 | args = [args] 75 | cmd = cmd + args 76 | 77 | # Print verbose output 78 | if not (quiet or self.quiet): 79 | bot.info(" ".join(cmd)) 80 | 81 | # Save the options and cmd, if the user wants to see them later 82 | self.options = options 83 | self.args = args 84 | self.cmd = cmd 85 | 86 | output = run_command( 87 | cmd, 88 | sudo=sudo, 89 | sudo_options=sudo_options, 90 | quiet=True, 91 | capture=capture, 92 | environ=environ, 93 | ) 94 | 95 | if output["return_code"] == 0: 96 | self._update_metadata() 97 | 98 | else: 99 | message = "%s : return code %s" % (output["message"], output["return_code"]) 100 | bot.error(message) 101 | 102 | return self 103 | -------------------------------------------------------------------------------- /spython/instance/cmd/stop.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | from spython.logger import bot 9 | 10 | 11 | def stop( 12 | self, 13 | name=None, 14 | sudo=False, 15 | sudo_options=None, 16 | timeout=None, 17 | singularity_options=None, 18 | quiet=True, 19 | ): 20 | """stop an instance. This is done by default when an instance is created. 21 | 22 | Parameters 23 | ========== 24 | name: a name for the instance 25 | sudo: if the user wants to run the command with sudo 26 | singularity_options: a list of options to provide to the singularity client 27 | quiet: Do not print verbose output. 28 | timeout: forcebly kill non-stopped instance after the 29 | timeout specified in seconds 30 | 31 | USAGE: 32 | singularity [...] instance.stop [...] 33 | 34 | """ 35 | from spython.utils import check_install, run_command 36 | 37 | check_install() 38 | 39 | subgroup = ["instance", "stop"] 40 | if timeout: 41 | subgroup += ["-t", str(timeout)] 42 | 43 | cmd = self._init_command(subgroup, singularity_options) 44 | 45 | # If name is provided assume referencing an instance 46 | instance_name = self.name 47 | if name is not None: 48 | instance_name = name 49 | cmd = cmd + [instance_name] 50 | 51 | # Print verbose output 52 | if not (quiet or self.quiet): 53 | bot.info(" ".join(cmd)) 54 | 55 | output = run_command(cmd, sudo=sudo, sudo_options=sudo_options, quiet=True) 56 | 57 | if output["return_code"] != 0: 58 | message = "%s : return code %s" % (output["message"], output["return_code"]) 59 | bot.error(message) 60 | return output["return_code"] 61 | 62 | return output["return_code"] 63 | -------------------------------------------------------------------------------- /spython/logger/__init__.py: -------------------------------------------------------------------------------- 1 | from .compatibility import decodeUtf8String 2 | from .message import bot 3 | from .progress import ProgressBar 4 | -------------------------------------------------------------------------------- /spython/logger/compatibility.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | def decodeUtf8String(inputStr): 9 | """Convert an UTF8 sequence into a string 10 | 11 | Required for compatibility with Python 2 where str==bytes 12 | inputStr -- Either a str or bytes instance with UTF8 encoding 13 | """ 14 | return ( 15 | inputStr 16 | if isinstance(inputStr, str) or not isinstance(inputStr, bytes) 17 | else inputStr.decode("utf8") 18 | ) 19 | -------------------------------------------------------------------------------- /spython/logger/spinner.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | import sys 8 | import threading 9 | import time 10 | from random import choice 11 | 12 | 13 | class Spinner: 14 | spinning = False 15 | delay = 0.1 16 | 17 | @staticmethod 18 | def spinning_cursor(): 19 | while 1: 20 | for cursor in "|/-\\": 21 | yield cursor 22 | 23 | @staticmethod 24 | def balloons_cursor(): 25 | while 1: 26 | for cursor in ". o O @ *": 27 | yield cursor 28 | 29 | @staticmethod 30 | def changing_arrows(): 31 | while 1: 32 | for cursor in "<^>v": 33 | yield cursor 34 | 35 | def select_generator(self, generator): 36 | if generator is None: 37 | generator = choice(["cursor", "arrow", "balloons"]) 38 | 39 | return generator 40 | 41 | def __init__(self, delay=None, generator=None): 42 | generator = self.select_generator(generator) 43 | 44 | if generator == "cursor": 45 | self.spinner_generator = self.spinning_cursor() 46 | elif generator == "arrow": 47 | self.spinner_generator = self.changing_arrows() 48 | elif generator == "balloons": 49 | self.spinner_generator = self.balloons_cursor() 50 | if delay is None: 51 | delay = 0.2 52 | else: 53 | self.spinner_generator = self.spinning_cursor() 54 | 55 | if delay and float(delay): 56 | self.delay = delay 57 | 58 | def run(self): 59 | while self.spinning: 60 | sys.stdout.write(next(self.spinner_generator)) 61 | sys.stdout.flush() 62 | time.sleep(self.delay) 63 | sys.stdout.write("\b") 64 | sys.stdout.flush() 65 | 66 | def start(self): 67 | self.spinning = True 68 | threading.Thread(target=self.run).start() 69 | 70 | def stop(self): 71 | self.spinning = False 72 | time.sleep(self.delay) 73 | -------------------------------------------------------------------------------- /spython/main/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | def get_client(quiet=False, debug=False): 9 | """ 10 | get the client and perform imports not on init, in case there are any 11 | initialization or import errors. 12 | 13 | Parameters 14 | ========== 15 | quiet: if True, suppress most output about the client 16 | debug: turn on debugging mode 17 | 18 | """ 19 | from .base import Client as client 20 | 21 | client.quiet = quiet 22 | client.debug = debug 23 | 24 | # Do imports here, can be customized 25 | from .apps import apps 26 | from .build import build 27 | from .execute import execute, shell 28 | from .export import export 29 | from .help import helpcmd 30 | from .inspect import inspect 31 | from .instances import list_instances, stopall # global instance commands 32 | from .pull import pull 33 | from .run import run 34 | 35 | # Actions 36 | client.apps = apps 37 | client.build = build 38 | client.execute = execute 39 | client.export = export 40 | client.help = helpcmd 41 | client.inspect = inspect 42 | client.instances = list_instances 43 | client.run = run 44 | client.shell = shell 45 | client.pull = pull 46 | 47 | # Commands Groups, Instances 48 | from spython.instance.cmd import ( # instance level commands 49 | generate_instance_commands, 50 | ) 51 | 52 | client.instance = generate_instance_commands() 53 | client.instance_stopall = stopall 54 | client.instance.version = client.version 55 | 56 | # Commands Groups, OCI (Singularity version 3 and up) 57 | from spython.oci.cmd import generate_oci_commands 58 | 59 | client.oci = generate_oci_commands()() # first () runs function, second 60 | # initializes OciImage class 61 | client.oci.debug = client.debug 62 | client.oci.quiet = client.quiet 63 | client.oci.OciImage.quiet = client.quiet 64 | client.oci.OciImage.debug = client.debug 65 | 66 | # Initialize 67 | cli = client() 68 | 69 | # Pass on verbosity 70 | cli.instance.debug = cli.debug 71 | cli.instance.quiet = cli.quiet 72 | cli.instance.version = cli.version 73 | 74 | return cli 75 | 76 | 77 | Client = get_client() 78 | -------------------------------------------------------------------------------- /spython/main/apps.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | def apps(self, image=None, full_path=False, root=""): 9 | """ 10 | return list of SCIF apps in image. The Singularity software serves 11 | a scientific filesystem integration that will install apps to 12 | /scif/apps and associated data to /scif/data. For more information 13 | about SCIF, see https://sci-f.github.io. Note that this seems 14 | to be deprecated in Singularity 3.x. 15 | 16 | Parameters 17 | ========== 18 | full_path: if True, return relative to scif base folder 19 | image_path: full path to the image 20 | 21 | """ 22 | from spython.utils import check_install 23 | 24 | check_install() 25 | 26 | # No image provided, default to use the client's loaded image 27 | if image is None: 28 | image = self._get_uri() 29 | 30 | cmd = self._init_command("apps") + [image] 31 | output = self._run_command(cmd) 32 | 33 | if full_path: 34 | root = "/scif/apps/" 35 | 36 | if output: 37 | output = "".join(output).split("\n") 38 | output = ["%s%s" % (root, x) for x in output if x] 39 | 40 | return output 41 | -------------------------------------------------------------------------------- /spython/main/base/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM continuumio/miniconda3 2 | 3 | ######################################### 4 | # The Robot Namer 5 | # 6 | # docker build -t vanessa/robotname . 7 | # docker run vanessa/robotname 8 | ######################################### 9 | 10 | LABEL maintainer vsochat@stanford.edu 11 | ADD generate.py / 12 | ENV PATH /usr/local/bin:$PATH 13 | ENTRYPOINT ["python", "/generate.py"] 14 | -------------------------------------------------------------------------------- /spython/main/base/README.md: -------------------------------------------------------------------------------- 1 | # Robot Generator 2 | 3 | This folder contains a (sub-application) for a robot name generator. 4 | 5 | ## Docker 6 | It's built on Docker Hub, so you can run as: 7 | 8 | ``` 9 | docker run vanessa/robotname 10 | ``` 11 | 12 | or build locally first, with the present working directory as this folder. 13 | 14 | ``` 15 | docker build -t vanessa/robotname . 16 | ``` 17 | 18 | ``` 19 | for i in `seq 1 10`; 20 | do 21 | docker run vanessa/robotname 22 | done 23 | boopy-peanut-butter-7311 24 | blank-snack-0334 25 | hello-buttface-6320 26 | chocolate-bicycle-9982 27 | frigid-frito-9511 28 | doopy-soup-7712 29 | phat-pancake-4952 30 | wobbly-kitty-3213 31 | lovely-mango-1987 32 | milky-poo-7960 33 | ``` 34 | 35 | ## Singularity 36 | 37 | To build your image: 38 | 39 | ``` 40 | sudo singularity build robotname Singularity 41 | ``` 42 | 43 | or pull from Docker Hub :) 44 | 45 | ``` 46 | singularity pull --name robotname docker://vanessa/robotname 47 | sregistry pull docker://vanessa/robotname 48 | ``` 49 | -------------------------------------------------------------------------------- /spython/main/base/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2022 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | from spython.logger import bot 9 | from spython.utils import check_install, get_singularity_version 10 | 11 | from .command import generate_bind_list, init_command, run_command 12 | from .flags import parse_verbosity 13 | from .generate import RobotNamer 14 | from .logger import init_level, println 15 | from .sutils import get_filename, get_uri, load, setenv 16 | 17 | 18 | class Client: 19 | def __str__(self): 20 | base = "[singularity-python]" 21 | if hasattr(self, "simage"): 22 | if self.simage.image not in [None, ""]: 23 | base = "%s[%s]" % (base, self.simage) 24 | return base 25 | 26 | def __repr__(self): 27 | return self.__str__() 28 | 29 | def __init__(self): 30 | """ 31 | The base client for singularity, will have commands added to it. 32 | upon init, store verbosity requested in environment MESSAGELEVEL. 33 | """ 34 | self._init_level() 35 | 36 | def version(self): 37 | """ 38 | Shortcut to get_singularity_version, takes no arguments. 39 | """ 40 | return get_singularity_version() 41 | 42 | def _check_install(self): 43 | """ 44 | Ensure that singularity is installed, and exit if not. 45 | """ 46 | if check_install() is not True: 47 | bot.exit("Cannot find Singularity! Is it installed?") 48 | 49 | 50 | # Image Utils 51 | Client.load = load 52 | Client._get_filename = get_filename 53 | Client._get_uri = get_uri 54 | Client.setenv = setenv 55 | 56 | # Commands 57 | Client._generate_bind_list = generate_bind_list 58 | Client._init_command = init_command 59 | Client._run_command = run_command 60 | 61 | # Flags and Logger 62 | Client._parse_verbosity = parse_verbosity 63 | Client._println = println 64 | Client._init_level = init_level 65 | Client.RobotNamer = RobotNamer() 66 | -------------------------------------------------------------------------------- /spython/main/base/flags.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2022 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | """ 9 | GLOBAL OPTIONS: 10 | -d|--debug Print debugging information 11 | -h|--help Display usage summary 12 | -s|--silent Only print errors 13 | -q|--quiet Suppress all normal output 14 | --version Show application version 15 | -v|--verbose Increase verbosity +1 16 | -x|--sh-debug Print shell wrapper debugging information 17 | 18 | GENERAL COMMANDS: 19 | help Show additional help for a command or container 20 | selftest Run some self tests for singularity install 21 | 22 | CONTAINER USAGE COMMANDS: 23 | exec Execute a command within container 24 | run Launch a runscript within container 25 | shell Run a Bourne shell within container 26 | test Launch a testscript within container 27 | 28 | CONTAINER MANAGEMENT COMMANDS: 29 | apps List available apps within a container 30 | bootstrap *Deprecated* use build instead 31 | build Build a new Singularity container 32 | check Perform container lint checks 33 | inspect Display container's metadata 34 | mount Mount a Singularity container image 35 | pull Pull a Singularity/Docker container to $PWD 36 | siflist list data object descriptors of a SIF container image 37 | sign Sign a group of data objects in container 38 | verify Verify the crypto signature of group of data objects in container 39 | 40 | COMMAND GROUPS: 41 | capability User's capabilities management command group 42 | image Container image command group 43 | instance Persistent instance command group 44 | 45 | """ 46 | 47 | 48 | def parse_verbosity(self, args): 49 | """parse_verbosity will take an argument object, and return the args 50 | passed (from a dictionary) to a list 51 | 52 | Parameters 53 | ========== 54 | args: the argparse argument objects 55 | 56 | """ 57 | 58 | flags = [] 59 | 60 | if args.silent: 61 | flags.append("--silent") 62 | elif args.quiet: 63 | flags.append("--quiet") 64 | elif args.debug: 65 | flags.append("--debug") 66 | elif args.verbose: 67 | flags.append("-" + "v" * args.verbose) 68 | 69 | return flags 70 | -------------------------------------------------------------------------------- /spython/main/base/logger.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2022 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | import os 9 | 10 | from spython.logger import decodeUtf8String 11 | 12 | 13 | def init_level(self, quiet=False): 14 | """set the logging level based on the environment 15 | 16 | Parameters 17 | ========== 18 | quiet: boolean if True, set to quiet. Gets overridden by environment 19 | setting, and only exists to define default 20 | 21 | """ 22 | 23 | if os.environ.get("MESSAGELEVEL") == "QUIET": 24 | quiet = True 25 | 26 | self.quiet = quiet 27 | 28 | 29 | def println(self, output, quiet=False): 30 | """print will print the output, given that quiet is not True. This 31 | function also serves to convert output in bytes to utf-8 32 | 33 | Parameters 34 | ========== 35 | output: the string to print 36 | quiet: a runtime variable to over-ride the default. 37 | 38 | """ 39 | if not self.quiet and not quiet: 40 | print(decodeUtf8String(output)) 41 | -------------------------------------------------------------------------------- /spython/main/base/sutils.py: -------------------------------------------------------------------------------- 1 | # Singularity Image utils for interacting with the Image/Instance 2 | # classes from the client 3 | 4 | # Copyright (C) 2017-2022 Vanessa Sochat. 5 | 6 | # This Source Code Form is subject to the terms of the 7 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 8 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | 10 | 11 | import os 12 | import re 13 | 14 | from spython.logger import bot 15 | 16 | 17 | def load(self, image=None): 18 | """load an image, either an actual path on the filesystem or a uri. 19 | 20 | Parameters 21 | ========== 22 | image: the image path or uri to load (e.g., docker://ubuntu 23 | 24 | """ 25 | from spython.image import Image 26 | from spython.instance import Instance 27 | 28 | self.simage = Image(image) 29 | 30 | if image is not None: 31 | if image.startswith("instance://"): 32 | self.simage = Instance(image) 33 | bot.info(self.simage) 34 | 35 | 36 | def setenv(self, variable, value): 37 | """set an environment variable for Singularity 38 | 39 | Parameters 40 | ========== 41 | variable: the variable to set 42 | value: the value to set 43 | """ 44 | os.environ[variable] = value 45 | os.putenv(variable, value) 46 | bot.debug("%s set to %s" % (variable, value)) 47 | 48 | 49 | def get_filename(self, image, ext="sif", pwd=True): 50 | """return an image filename based on the image uri. 51 | 52 | Parameters 53 | ========== 54 | ext: the extension to use 55 | pwd: derive a filename for the pwd 56 | """ 57 | if pwd: 58 | image = os.path.basename(image) 59 | image = re.sub("^.*://", "", image) 60 | if not image.endswith(ext): 61 | image = "%s.%s" % (image, ext) 62 | return image 63 | 64 | 65 | def get_uri(self): 66 | """check if the loaded image object (self.simage) has an associated uri 67 | return if yes, None if not. 68 | """ 69 | if hasattr(self, "simage"): 70 | if self.simage is not None: 71 | if self.simage.image not in ["", None]: 72 | # Concatenates the :// 73 | return str(self.simage) 74 | -------------------------------------------------------------------------------- /spython/main/export.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | import os 8 | 9 | from spython.logger import bot 10 | 11 | 12 | def export( 13 | self, 14 | image_path, 15 | pipe=False, 16 | output_file=None, 17 | command=None, 18 | sudo=False, 19 | singularity_options=None, 20 | ): 21 | """export will export an image, sudo must be used. Since we have Singularity 22 | versions after 3, export is replaced with building into a sandbox. 23 | 24 | Parameters 25 | ========== 26 | image_path: full path to image 27 | pipe: export to pipe and not file (default, False) 28 | singularity_options: a list of options to provide to the singularity client 29 | output_file: if pipe=False, export tar to this file. If not specified, 30 | will generate temporary directory. 31 | """ 32 | from spython.utils import check_install 33 | 34 | check_install() 35 | 36 | # If export is deprecated, we run a build 37 | bot.warning( 38 | "Export is not supported for Singularity 3.x. Building to sandbox instead." 39 | ) 40 | 41 | if output_file is None: 42 | basename, _ = os.path.splitext(image_path) 43 | output_file = self._get_filename(basename, "sandbox", pwd=False) 44 | 45 | return self.build( 46 | recipe=image_path, 47 | image=output_file, 48 | sandbox=True, 49 | force=True, 50 | sudo=sudo, 51 | singularity_options=singularity_options, 52 | ) 53 | -------------------------------------------------------------------------------- /spython/main/help.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | def helpcmd(self, command=None): 9 | """help prints the general function help, or help for a specific command 10 | 11 | Parameters 12 | ========== 13 | command: the command to get help for, if none, prints general help 14 | 15 | """ 16 | from spython.utils import check_install 17 | 18 | check_install() 19 | 20 | cmd = ["singularity", "--help"] 21 | if command is not None: 22 | cmd.append(command) 23 | return self._run_command(cmd) 24 | -------------------------------------------------------------------------------- /spython/main/inspect.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | import json as jsonp 9 | 10 | from spython.logger import bot 11 | from spython.utils import check_install, run_command 12 | 13 | 14 | def inspect( 15 | self, image=None, json=True, app=None, quiet=True, singularity_options=None 16 | ): 17 | """inspect will show labels, defile, runscript, and tests for an image 18 | 19 | Parameters 20 | ========== 21 | image: path of image to inspect 22 | json: print json instead of raw text (default True) 23 | quiet: Don't print result to the screen (default True) 24 | app: if defined, return help in context of an app 25 | singularity_options: a list of options to provide to the singularity client 26 | 27 | """ 28 | check_install() 29 | 30 | # No image provided, default to use the client's loaded image 31 | if not image: 32 | image = self._get_uri() 33 | 34 | # If there still isn't an image, exit on error 35 | if not image: 36 | bot.exit("Please provide an image to inspect.") 37 | 38 | cmd = self._init_command("inspect", singularity_options) 39 | if app: 40 | cmd = cmd + ["--app", app] 41 | 42 | options = ["e", "d", "l", "r", "H", "t"] 43 | for x in options: 44 | cmd.append("-%s" % x) 45 | 46 | if json: 47 | cmd.append("--json") 48 | 49 | cmd.append(image) 50 | 51 | # Does the user want to see the command printed? 52 | if not (quiet or self.quiet): 53 | bot.info(" ".join(cmd)) 54 | 55 | result = run_command(cmd, quiet=quiet) 56 | 57 | if result["return_code"] == 0: 58 | result = jsonp.loads(result["message"][0]) 59 | 60 | # Unify output to singularity 3 format 61 | if "data" in result: 62 | result = result["data"] 63 | 64 | # Fix up labels 65 | result = parse_labels(result) 66 | 67 | if not quiet: 68 | print(jsonp.dumps(result, indent=4)) 69 | 70 | return result 71 | 72 | 73 | def parse_labels(result): 74 | """fix up the labels, meaning parse to json if needed, and return 75 | original updated object 76 | 77 | Parameters 78 | ========== 79 | result: the json object to parse from inspect 80 | """ 81 | 82 | labels = result["attributes"].get("labels") or {} 83 | try: 84 | labels = jsonp.loads(labels) 85 | except Exception: 86 | pass 87 | 88 | result["attributes"]["labels"] = labels 89 | 90 | return result 91 | -------------------------------------------------------------------------------- /spython/main/parse/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/spython/main/parse/__init__.py -------------------------------------------------------------------------------- /spython/main/parse/parsers/README.md: -------------------------------------------------------------------------------- 1 | # Parsers 2 | 3 | A parser class is intended to read in a container recipe file, and parse 4 | sections into a spython.main.recipe Recipe object. To create a new subclass 5 | of parser, you can copy one of the current (Docker or Singularity) as an 6 | example, and keep in mind the following: 7 | 8 | - The base class, `ParserBase` in [base.py](base.py) has already added an instantiated (and empty) Recipe() for the subclass to interact with (fill with content). 9 | - The subclass is encouraged to define the name (self.name) attribute for printing to the user. 10 | - The subclass should take the input file as an argument to pass the the ParserBase, which will handle reading in lines to a list self.lines. 11 | - The subclass should have a main method, parse, that when called will read the input file and populate the recipe (and return it to the user). 12 | -------------------------------------------------------------------------------- /spython/main/parse/parsers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2022 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | from .docker import DockerParser 8 | from .singularity import SingularityParser 9 | 10 | 11 | def get_parser(name): 12 | """get_parser is a simple helper function to return a parser based on it's 13 | name, if it exists. If there is no writer defined, we return None. 14 | 15 | Parameters 16 | ========== 17 | name: the name of the parser to return. 18 | """ 19 | name = name.lower() 20 | parsers = { 21 | "docker": DockerParser, 22 | "singularity": SingularityParser, 23 | "dockerfile": DockerParser, 24 | } 25 | 26 | if name in parsers: 27 | return parsers[name] 28 | -------------------------------------------------------------------------------- /spython/main/parse/recipe.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2022 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | class Recipe: 9 | """ 10 | a recipe includes an environment, labels, runscript or command, 11 | and install sequence. This object is interacted with by a Parser 12 | (intended to popualte the recipe with content) and a Writer (intended 13 | to write a recipe to file). The parsers and writers are located in 14 | parsers.py, and writers.py, respectively. The user is also free to use 15 | the recipe class to build recipes. 16 | 17 | Parameters 18 | ========== 19 | recipe: the original recipe file, parsed by the subclass either 20 | DockerParser or SingularityParser 21 | layer: the count of the layer, for human readability 22 | 23 | """ 24 | 25 | def __init__(self, recipe=None, layer=1): 26 | self.cmd = None 27 | self.comments = [] 28 | self.entrypoint = None 29 | self.environ = [] 30 | self.files = [] 31 | self.layer_files = {} 32 | self.install = [] 33 | self.labels = [] 34 | self.ports = [] 35 | self.test = None 36 | self.volumes = [] 37 | self.workdir = None 38 | self.layer = layer 39 | self.fromHeader = None 40 | 41 | self.source = recipe 42 | 43 | def __str__(self): 44 | """show the user the recipe object, along with the type. E.g., 45 | 46 | [spython-recipe][source:Singularity] 47 | [spython-recipe][source:Dockerfile] 48 | 49 | """ 50 | base = "[spython-recipe]" 51 | if self.source: 52 | base = "%s[source:%s]" % (base, self.source) 53 | return base 54 | 55 | def json(self): 56 | """return a dictionary version of the recipe, intended to be parsed 57 | or printed as json. 58 | 59 | Returns: a dictionary of attributes including cmd, comments, 60 | entrypoint, environ, files, install, labels, ports, 61 | test, volumes, and workdir, organized by layer for 62 | multistage builds. 63 | """ 64 | attributes = [ 65 | "cmd", 66 | "comments", 67 | "entrypoint", 68 | "environ", 69 | "files", 70 | "fromHeader", 71 | "layer_files", 72 | "install", 73 | "labels", 74 | "ports", 75 | "test", 76 | "volumes", 77 | "workdir", 78 | ] 79 | 80 | result = {} 81 | 82 | for attrib in attributes: 83 | value = getattr(self, attrib) 84 | if value: 85 | result[attrib] = value 86 | 87 | return result 88 | 89 | def __repr__(self): 90 | return self.__str__() 91 | -------------------------------------------------------------------------------- /spython/main/parse/writers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2022 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | from .docker import DockerWriter 9 | from .singularity import SingularityWriter 10 | 11 | 12 | def get_writer(name): 13 | """get_writer is a simple helper function to return a writer based on it's 14 | name, if it exists. If there is no writer defined, we return None. 15 | 16 | Parameters 17 | ========== 18 | name: the name of the writer to return. 19 | """ 20 | name = name.lower() 21 | writers = { 22 | "docker": DockerWriter, 23 | "singularity": SingularityWriter, 24 | "dockerfile": DockerWriter, 25 | } 26 | 27 | if name in writers: 28 | return writers[name] 29 | -------------------------------------------------------------------------------- /spython/main/parse/writers/base.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2022 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | import os 9 | import tempfile 10 | 11 | from spython.logger import bot 12 | from spython.utils import write_file 13 | 14 | 15 | class WriterBase: 16 | def __init__(self, recipe=None): 17 | """a writer base will take a recipe object (parser.base.Recipe) and 18 | provide helpers for writing to file. 19 | 20 | Parameters 21 | ========== 22 | recipe: the recipe instance to parse 23 | 24 | """ 25 | self.recipe = recipe 26 | 27 | def write(self, output_file=None, force=False): 28 | """convert a recipe to a specified format, and write to file, meaning 29 | we use the loaded recipe to write to an output file. 30 | If the output file is not specified, a temporary file is used. 31 | 32 | Parameters 33 | ========== 34 | output_file: the file to save to, not required (estimates default) 35 | force: if True, if file exists, over-write existing file 36 | 37 | """ 38 | if output_file is None: 39 | output_file = self._get_conversion_outfile() 40 | 41 | # Cut out early if file exists and we aren't overwriting 42 | if os.path.exists(output_file) and not force: 43 | bot.exit("%s exists, and force is False." % output_file) 44 | 45 | # Do the conversion if function is provided by subclass 46 | if hasattr(self, "convert"): 47 | converted = self.convert() 48 | bot.info("Saving to %s" % output_file) 49 | write_file(output_file, converted) 50 | 51 | def _get_conversion_outfile(self): 52 | """a helper function to return a conversion temporary output file 53 | based on kind of conversion 54 | 55 | Parameters 56 | ========== 57 | convert_to: a string either docker or singularity, if a different 58 | 59 | """ 60 | prefix = "spythonRecipe" 61 | if hasattr(self, "name"): 62 | prefix = self.name 63 | suffix = next(tempfile._get_candidate_names()) 64 | return "%s.%s" % (prefix, suffix) 65 | 66 | # Printing 67 | 68 | def __str__(self): 69 | """show the user the recipe object, along with the type. E.g., 70 | 71 | [spython-writer][docker] 72 | [spython-writer][singularity] 73 | 74 | """ 75 | base = "[spython-writer]" 76 | if hasattr(self, "name"): 77 | base = "%s[%s]" % (base, self.name) 78 | return base 79 | 80 | def __repr__(self): 81 | return self.__str__() 82 | -------------------------------------------------------------------------------- /spython/main/pull.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | import os 8 | import re 9 | 10 | from spython.logger import bot 11 | from spython.utils import ScopedEnvVar, stream_command 12 | 13 | 14 | def pull( 15 | self, 16 | image=None, 17 | name=None, 18 | pull_folder="", 19 | ext="sif", 20 | force=False, 21 | capture=False, 22 | stream=False, 23 | quiet=False, 24 | singularity_options=None, 25 | ): 26 | """pull will pull a singularity hub or Docker image 27 | 28 | Parameters 29 | ========== 30 | image: the complete image uri. If not provided, the client loaded is used 31 | singularity_options: a list of options to provide to the singularity client 32 | pull_folder: if not defined, pulls to $PWD (''). If defined, pulls to 33 | user specified location instead. 34 | 35 | Docker and Singularity Hub Naming 36 | --------------------------------- 37 | name: a custom name to use, to override default 38 | ext: if no name specified, the default extension to use. 39 | 40 | """ 41 | from spython.utils import check_install 42 | 43 | check_install() 44 | 45 | cmd = self._init_command("pull", singularity_options) 46 | 47 | # Quiet is honored if set by the client, or user 48 | quiet = quiet or self.quiet 49 | 50 | # No image provided, default to use the client's loaded image 51 | if image is None: 52 | image = self._get_uri() 53 | 54 | # If it's still None, no go! 55 | if image is None: 56 | bot.exit("You must provide an image uri, or use client.load() first.") 57 | 58 | # Singularity Only supports shub, docker and library pull 59 | if not re.search("^(shub|docker|library|https|oras)://", image): 60 | bot.exit("pull only valid for docker, oras, https, shub and library.") 61 | 62 | # If we still don't have a custom name, base off of image uri. 63 | if name is None: 64 | name = self._get_filename(image, ext) 65 | 66 | if pull_folder: 67 | final_image = os.path.join(pull_folder, os.path.basename(name)) 68 | 69 | # Regression Singularity 3.* onward, PULLFOLDER not honored 70 | # https://github.com/sylabs/singularity/issues/2788 71 | name = final_image 72 | pull_folder = None # Don't use pull_folder 73 | else: 74 | final_image = name 75 | 76 | cmd = cmd + ["--name", name] 77 | 78 | if force: 79 | cmd = cmd + ["--force"] 80 | 81 | cmd.append(image) 82 | 83 | if not quiet: 84 | bot.info(" ".join(cmd)) 85 | 86 | with ScopedEnvVar("SINGULARITY_PULLFOLDER", pull_folder): 87 | # Option 1: Streaming we just run to show user 88 | if not stream: 89 | self._run_command(cmd, capture=capture, quiet=quiet) 90 | 91 | # Option 3: A custom name we can predict (not commit/hash) and can also show 92 | else: 93 | # As of Singularity 3.x (at least 3.8) output goes to stderr 94 | return final_image, stream_command(cmd, sudo=False, output_type="stderr") 95 | 96 | if os.path.exists(final_image) and not quiet: 97 | bot.info(final_image) 98 | return final_image 99 | -------------------------------------------------------------------------------- /spython/main/run.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | import json 8 | 9 | from spython.logger import bot 10 | from spython.utils import stream_command 11 | 12 | 13 | def run( 14 | self, 15 | image=None, 16 | args=None, 17 | app=None, 18 | sudo=False, 19 | writable=False, 20 | contain=False, 21 | bind=None, 22 | stream=False, 23 | nv=False, 24 | options=None, 25 | singularity_options=None, 26 | return_result=False, 27 | quiet=False, 28 | background=False, 29 | stream_type="stdout", 30 | ): 31 | """ 32 | run will run the container, with or withour arguments (which 33 | should be provided in a list) 34 | 35 | Parameters 36 | ========== 37 | image: full path to singularity image 38 | args: args to include with the run 39 | app: if not None, execute a command in context of an app 40 | writable: This option makes the file system accessible as read/write 41 | options: an optional list of options to provide to run. 42 | singularity_options: a list of options to provide to the singularity client 43 | contain: This option disables the automatic sharing of writable 44 | filesystems on your host 45 | bind: list or single string of bind paths. 46 | This option allows you to map directories on your host system to 47 | directories within your container using bind mounts 48 | stream: if True, return for the user to run 49 | nv: if True, load Nvidia Drivers in runtime (default False) 50 | return_result: if True, return entire json object with return code 51 | and message result (default is False) 52 | quiet: print the command to the user 53 | stream_type: Sets which output stream from the singularity command should be return. Values are 'stdout', 'stderr', 'both'. 54 | """ 55 | from spython.utils import check_install 56 | 57 | check_install() 58 | 59 | cmd = self._init_command("run", singularity_options) 60 | 61 | # Does the user want to see the command printed? 62 | quiet = quiet or self.quiet 63 | 64 | # nv option leverages any GPU cards 65 | if nv: 66 | cmd += ["--nv"] 67 | 68 | # No image provided, default to use the client's loaded image 69 | if image is None: 70 | image = self._get_uri() 71 | 72 | # If an instance is provided, grab it's name 73 | if isinstance(image, self.instance): 74 | image = image.get_uri() 75 | 76 | # If image is still None, not defined by user or previously with client 77 | if image is None: 78 | bot.exit("Please load or provide an image.") 79 | 80 | # Does the user want to use bind paths option? 81 | if bind is not None: 82 | cmd += self._generate_bind_list(bind) 83 | 84 | # Does the user want to run an app? 85 | if app is not None: 86 | cmd = cmd + ["--app", app] 87 | 88 | # Does the user want writable? 89 | if writable: 90 | cmd.append("--writable") 91 | 92 | # Add options 93 | if options is not None: 94 | cmd = cmd + options 95 | 96 | cmd = cmd + [image] 97 | 98 | if args is not None: 99 | if not isinstance(args, list): 100 | args = args.split(" ") 101 | cmd = cmd + args 102 | 103 | if not quiet: 104 | bot.info(" ".join(cmd)) 105 | 106 | if background: 107 | return self._run_command(cmd, sudo=sudo, background=True) 108 | 109 | elif not stream: 110 | result = self._run_command(cmd, sudo=sudo, return_result=return_result) 111 | else: 112 | return stream_command(cmd, sudo=sudo, output_type=stream_type) 113 | 114 | # If the user wants the raw result object 115 | if return_result: 116 | return result 117 | 118 | # Otherwise, we parse the result if it was successful 119 | if result: 120 | result = result.strip("\n") 121 | 122 | try: 123 | result = json.loads(result) 124 | except Exception: 125 | pass 126 | return result 127 | -------------------------------------------------------------------------------- /spython/oci/README.md: -------------------------------------------------------------------------------- 1 | # OCI Development 2 | 3 | Here I'll write how I created an OCI bundle using Singularity to help with 4 | development of the client. First, notice the [config.json](config.json) 5 | in the present working directory - it's a general configuration for OCI 6 | runtime specification (version 1.0) that we can put into a bundle (a folder 7 | that will serve as the root of a container). Here is how I did that. 8 | 9 | First, we are developing with the first release of Singularity that supports 10 | OCI: 11 | 12 | ```bash 13 | $ singularity --version 14 | singularity version 3.1.0-rc2.28.ga72e427 15 | ``` 16 | 17 | Next, we are going to create a bundle. A bundle is a folder that we will 18 | treat as the root of our container filesystem. The easiest way to do 19 | this is to dump a filesystem there from another container. 20 | 21 | ```bash 22 | $ singularity build --sandbox /tmp/bundle docker://ubuntu:18.04 23 | $ cp config.json /tmp/bundle 24 | ``` 25 | 26 | The purpose of the build is only to dump a complete operating system into the 27 | bundle folder. The configuration file then is to conform to the Oci 28 | Runtime specification. 29 | 30 | We can then test interaction with the OCI client of Singularity python 31 | by providing the bundle directory at /tmp/bundle. 32 | -------------------------------------------------------------------------------- /spython/oci/cmd/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2022 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | def generate_oci_commands(): 9 | """The oci command group will allow interaction with an image using 10 | OCI commands. 11 | """ 12 | # run_command uses run_cmd, but wraps to catch error 13 | from spython.main.base.command import run_command, send_command 14 | from spython.main.base.generate import RobotNamer 15 | from spython.main.base.logger import println 16 | from spython.oci import OciImage 17 | 18 | from .actions import _run, attach, create, delete, execute, run, update 19 | 20 | # Oci Command Groups 21 | from .mounts import mount, umount 22 | from .states import _state_command, kill, pause, resume, start, state 23 | 24 | # Oci Commands 25 | OciImage.start = start 26 | OciImage.mount = mount 27 | OciImage.umount = umount 28 | OciImage.state = state 29 | OciImage.resume = resume 30 | OciImage.pause = pause 31 | OciImage.attach = attach 32 | OciImage.create = create 33 | OciImage.delete = delete 34 | OciImage.execute = execute 35 | OciImage.update = update 36 | OciImage.kill = kill 37 | OciImage.run = run 38 | OciImage._run = _run 39 | OciImage._state_command = _state_command 40 | 41 | OciImage.RobotNamer = RobotNamer() 42 | OciImage._send_command = send_command # send and disregard stderr, stdout 43 | OciImage._run_command = run_command 44 | OciImage._println = println 45 | OciImage.OciImage = OciImage 46 | 47 | return OciImage 48 | -------------------------------------------------------------------------------- /spython/oci/cmd/mounts.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019-2022 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | def mount(self, image, sudo=None): 9 | """create an OCI bundle from SIF image 10 | 11 | Parameters 12 | ========== 13 | image: the container (sif) to mount 14 | """ 15 | return self._state_command(image, command="mount", sudo=sudo) 16 | 17 | 18 | def umount(self, image, sudo=None): 19 | """delete an OCI bundle created from SIF image 20 | 21 | Parameters 22 | ========== 23 | image: the container (sif) to mount 24 | """ 25 | return self._state_command(image, command="umount", sudo=sudo) 26 | -------------------------------------------------------------------------------- /spython/oci/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ociVersion": "1.0.1", 3 | "process": { 4 | "terminal": true, 5 | "user": { 6 | "uid": 1, 7 | "gid": 1 8 | }, 9 | "args": [ 10 | "sh" 11 | ], 12 | "env": [ 13 | "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 14 | "TERM=xterm" 15 | ], 16 | "cwd": "/", 17 | "noNewPrivileges": true 18 | }, 19 | "root": { 20 | "path": ".", 21 | "readonly": true 22 | }, 23 | "hostname": "slartibartfast", 24 | "mounts": [ 25 | { 26 | "destination": "/proc", 27 | "type": "proc", 28 | "source": "proc" 29 | }, 30 | { 31 | "destination": "/dev/pts", 32 | "type": "devpts", 33 | "source": "devpts" 34 | }, 35 | { 36 | "destination": "/dev/shm", 37 | "type": "tmpfs", 38 | "source": "shm" 39 | }, 40 | { 41 | "destination": "/dev/mqueue", 42 | "type": "mqueue", 43 | "source": "mqueue" 44 | }, 45 | { 46 | "destination": "/sys", 47 | "type": "sysfs", 48 | "source": "sysfs" 49 | }, 50 | { 51 | "destination": "/sys/fs/cgroup", 52 | "type": "cgroup", 53 | "source": "cgroup" 54 | } 55 | ], 56 | "linux": { 57 | "uidMappings": [ 58 | { 59 | "containerID": 0, 60 | "hostID": 1000, 61 | "size": 32000 62 | } 63 | ], 64 | "gidMappings": [ 65 | { 66 | "containerID": 0, 67 | "hostID": 1000, 68 | "size": 32000 69 | } 70 | ], 71 | "rootfsPropagation": "slave", 72 | "namespaces": [ 73 | { 74 | "type": "pid" 75 | }, 76 | { 77 | "type": "network" 78 | }, 79 | { 80 | "type": "ipc" 81 | }, 82 | { 83 | "type": "uts" 84 | }, 85 | { 86 | "type": "mount" 87 | }, 88 | { 89 | "type": "user" 90 | }, 91 | { 92 | "type": "cgroup" 93 | } 94 | ], 95 | "maskedPaths": [ 96 | "/proc/kcore", 97 | "/proc/latency_stats", 98 | "/proc/timer_stats", 99 | "/proc/sched_debug" 100 | ], 101 | "readonlyPaths": [ 102 | "/proc/asound", 103 | "/proc/bus", 104 | "/proc/fs", 105 | "/proc/irq", 106 | "/proc/sys", 107 | "/proc/sysrq-trigger" 108 | ] 109 | }, 110 | "annotations": { 111 | "com.example.key1": "value1", 112 | "com.example.key2": "value2" 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /spython/tests/Xtest_oci.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2017-2024 Vanessa Sochat. 4 | 5 | # This Source Code Form is subject to the terms of the 6 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 7 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | import os 10 | import shutil 11 | 12 | import pytest 13 | 14 | from spython.main import Client 15 | from spython.main.base.generate import RobotNamer 16 | from spython.utils import get_installdir 17 | 18 | 19 | @pytest.fixture 20 | def sandbox(tmp_path): 21 | image = Client.build( 22 | "docker://busybox:1.30.1", 23 | image=str(tmp_path / "sandbox"), 24 | sandbox=True, 25 | sudo=False, 26 | ) 27 | 28 | assert os.path.exists(image) 29 | 30 | config = os.path.join(get_installdir(), "oci", "config.json") 31 | shutil.copyfile(config, os.path.join(image, "config.json")) 32 | return image 33 | 34 | 35 | def test_oci_image(): 36 | image = Client.oci.OciImage("oci://imagename") 37 | assert image.get_uri() == "[singularity-python-oci:oci://imagename]" 38 | 39 | 40 | def test_oci(sandbox): # pylint: disable=redefined-outer-name 41 | image = sandbox 42 | container_id = RobotNamer().generate() 43 | 44 | # A non existing process should not have a state 45 | print("...Case 1. Check status of non-existing bundle.") 46 | state = Client.oci.state("mycontainer") 47 | assert state is None 48 | 49 | # This will use sudo 50 | print("...Case 2: Create OCI image from bundle") 51 | result = Client.oci.create(bundle=image, container_id=container_id) 52 | 53 | print(result) 54 | assert result["status"] == "created" 55 | 56 | print("...Case 3. Execute command to non running bundle.") 57 | result = Client.oci.execute( 58 | container_id=container_id, sudo=True, command=["ls", "/"] 59 | ) 60 | 61 | print(result) 62 | assert "bin" in result 63 | 64 | print("...Case 4. Start container return value 0.") 65 | state = Client.oci.start(container_id, sudo=True) 66 | assert state == 0 67 | 68 | print("...Case 5. Execute command to running bundle.") 69 | result = Client.oci.execute( 70 | container_id=container_id, sudo=True, command=["ls", "/"] 71 | ) 72 | 73 | print(result) 74 | assert "bin" in result 75 | 76 | print("...Case 6. Check status of existing bundle.") 77 | state = Client.oci.state(container_id, sudo=True) 78 | assert state["status"] == "running" 79 | 80 | print("...Case 7. Pause running container return value 0.") 81 | state = Client.oci.pause(container_id, sudo=True) 82 | assert state == 0 83 | 84 | # State was still reported as running 85 | print("...check status of paused bundle.") 86 | state = Client.oci.state(container_id, sudo=True) 87 | assert state["status"] == "paused" 88 | 89 | print("...Case 8. Resume paused container return value 0.") 90 | state = Client.oci.resume(container_id, sudo=True) 91 | assert state == 0 92 | 93 | print("...check status of resumed bundle.") 94 | state = Client.oci.state(container_id, sudo=True) 95 | assert state["status"] == "running" 96 | 97 | print("...Case 9. Kill container.") 98 | state = Client.oci.kill(container_id, sudo=True) 99 | assert state == 0 100 | 101 | # Clean up the image (should still use sudo) 102 | # Bug in singularity that kill doesn't kill completely - this returns 103 | # 255. When testsupdated to 3.1.* add signal=K to run 104 | result = Client.oci.delete(container_id, sudo=True) 105 | assert result in [0, 255] 106 | -------------------------------------------------------------------------------- /spython/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singularityhub/singularity-cli/a2e013f44c945fc514033522cdc69ee99b7d0025/spython/tests/__init__.py -------------------------------------------------------------------------------- /spython/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | from glob import glob 3 | 4 | import pytest 5 | 6 | from spython.main import Client 7 | from spython.utils import get_installdir 8 | 9 | 10 | @pytest.fixture 11 | def installdir(): 12 | return get_installdir() 13 | 14 | 15 | @pytest.fixture 16 | def test_data(installdir): # pylint: disable=redefined-outer-name 17 | root = os.path.join(installdir, "tests", "testdata") 18 | dockerFiles = glob(os.path.join(root, "docker2singularity", "*.docker")) 19 | singularityFiles = glob(os.path.join(root, "singularity2docker", "*.def")) 20 | return { 21 | "root": root, 22 | "d2s": [(file, os.path.splitext(file)[0] + ".def") for file in dockerFiles], 23 | "s2d": [ 24 | (file, os.path.splitext(file)[0] + ".docker") for file in singularityFiles 25 | ], 26 | } 27 | 28 | 29 | @pytest.fixture(scope="session") 30 | def oras_container(tmp_path_factory): 31 | folder = tmp_path_factory.mktemp("oras-img") 32 | return folder, Client.pull( 33 | "oras://ghcr.io/singularityhub/github-ci:latest", pull_folder=str(folder) 34 | ) 35 | 36 | 37 | @pytest.fixture(scope="session") 38 | def docker_container(tmp_path_factory): 39 | folder = tmp_path_factory.mktemp("docker-img") 40 | return folder, Client.pull("docker://busybox:1.30.1", pull_folder=str(folder)) 41 | -------------------------------------------------------------------------------- /spython/tests/helpers.sh: -------------------------------------------------------------------------------- 1 | runTest() { 2 | 3 | # The first argument is the code we should get 4 | ERROR="${1:-}" 5 | shift 6 | OUTPUT=${1:-} 7 | shift 8 | 9 | "$@" > "${OUTPUT}" 2>&1 10 | RETVAL="$?" 11 | 12 | if [ "$ERROR" = "0" -a "$RETVAL" != "0" ]; then 13 | echo "$@ (retval=$RETVAL) ERROR" 14 | cat ${OUTPUT} 15 | echo "Output in ${OUTPUT}" 16 | exit 1 17 | elif [ "$ERROR" != "0" -a "$RETVAL" = "0" ]; then 18 | echo "$@ (retval=$RETVAL) ERROR" 19 | echo "Output in ${OUTPUT}" 20 | cat ${OUTPUT} 21 | exit 1 22 | else 23 | echo "$@ (retval=$RETVAL) OK" 24 | fi 25 | } 26 | -------------------------------------------------------------------------------- /spython/tests/test_base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2017-2024 Vanessa Sochat. 4 | 5 | # This Source Code Form is subject to the terms of the 6 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 7 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | from spython.image import Image 10 | 11 | 12 | def test_image(): 13 | image = Image("docker://ubuntu") 14 | assert str(image) == "docker://ubuntu" 15 | assert image.protocol == "docker" 16 | assert image.image == "ubuntu" 17 | -------------------------------------------------------------------------------- /spython/tests/test_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2017-2024 Vanessa Sochat. 4 | 5 | # This Source Code Form is subject to the terms of the 6 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 7 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | 10 | import os 11 | import shutil 12 | from subprocess import CalledProcessError 13 | 14 | import pytest 15 | 16 | from spython.main import Client 17 | from spython.utils import write_file 18 | 19 | 20 | def test_build_from_docker(tmp_path): 21 | container = str(tmp_path / "container.sif") 22 | 23 | created_container = Client.build( 24 | "docker://busybox:1.30.1", image=container, sudo=False 25 | ) 26 | assert created_container == container 27 | assert os.path.exists(created_container) 28 | 29 | 30 | def test_export(): 31 | sandbox = "busybox:1.30.sandbox" 32 | created_sandbox = Client.export("docker://busybox:1.30.1") 33 | assert created_sandbox == sandbox 34 | assert os.path.exists(created_sandbox) 35 | shutil.rmtree(created_sandbox) 36 | 37 | 38 | def test_docker_pull(docker_container): 39 | tmp_path, container = docker_container 40 | print(container) 41 | assert container == str(tmp_path / ("busybox:1.30.1.sif")) 42 | assert os.path.exists(container) 43 | 44 | 45 | def test_oras_pull(oras_container): 46 | tmp_path, container = oras_container 47 | print(container) 48 | assert container == str(tmp_path / ("github-ci:latest.sif")) 49 | assert os.path.exists(container) 50 | 51 | 52 | def test_execute(docker_container): 53 | result = Client.execute(docker_container[1], "ls /") 54 | print(result) 55 | if isinstance(result, list): 56 | result = "".join(result) 57 | assert "tmp\nusr\nvar" in result 58 | 59 | 60 | def test_execute_with_return_code(docker_container): 61 | result = Client.execute(docker_container[1], "ls /", return_result=True) 62 | print(result) 63 | if isinstance(result["message"], list): 64 | result["message"] = "".join(result["message"]) 65 | assert "tmp\nusr\nvar" in result["message"] 66 | assert result["return_code"] == 0 67 | 68 | 69 | def test_execute_with_stream(docker_container): 70 | output = Client.execute(docker_container[1], "ls /", stream=True) 71 | message = "".join(list(output)) 72 | assert "tmp\nusr\nvar" in message 73 | 74 | output = Client.execute( 75 | docker_container[1], "ls /", stream=True, stream_type="both" 76 | ) 77 | message = "".join(list(output)) 78 | assert "tmp\nusr\nvar" in message 79 | 80 | # ls / should be successful, so there will be no stderr 81 | output = Client.execute( 82 | docker_container[1], "ls /", stream=True, stream_type="stderr" 83 | ) 84 | message = "".join(list(output)) 85 | assert "tmp\nusr\nvar" not in message 86 | 87 | 88 | @pytest.mark.parametrize("return_code", [True, False]) 89 | def test_execute_with_called_process_error( 90 | capsys, docker_container, return_code, tmp_path 91 | ): 92 | tmp_file = os.path.join(tmp_path, "CalledProcessError.sh") 93 | # "This is stdout" to stdout, "This is stderr" to stderr 94 | script = f"""#!/bin/bash 95 | echo "This is stdout" 96 | >&2 echo "This is stderr" 97 | {"exit 1" if return_code else ""} 98 | """ 99 | write_file(tmp_file, script) 100 | if return_code: 101 | with pytest.raises(CalledProcessError): 102 | for line in Client.execute( 103 | docker_container[1], f"/bin/sh {tmp_file}", stream=True 104 | ): 105 | print(line, "") 106 | else: 107 | for line in Client.execute( 108 | docker_container[1], f"/bin/sh {tmp_file}", stream=True 109 | ): 110 | print(line, "") 111 | captured = capsys.readouterr() 112 | assert "stdout" in captured.out 113 | if return_code: 114 | assert "stderr" in captured.err 115 | else: 116 | assert "stderr" not in captured.err 117 | 118 | 119 | def test_inspect(docker_container): 120 | result = Client.inspect(docker_container[1]) 121 | assert "attributes" in result or "data" in result 122 | -------------------------------------------------------------------------------- /spython/tests/test_client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Include help functions 4 | . helpers.sh 5 | 6 | echo 7 | echo "************** START: test_client.sh **********************" 8 | 9 | # Create temporary testing directory 10 | echo "Creating temporary directory to work in." 11 | tmpdir=$(mktemp -d) 12 | output=$(mktemp ${tmpdir:-/tmp}/spython_test.XXXXXX) 13 | here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 14 | 15 | echo "Testing help commands..." 16 | 17 | # Test help for all commands 18 | for command in recipe shell; 19 | do 20 | runTest 0 $output spython $command --help 21 | done 22 | 23 | echo "#### Testing recipe auto generation" 24 | runTest 1 $output spython recipe $here/testdata/Dockerfile | grep "FROM" 25 | runTest 0 $output spython recipe $here/testdata/Dockerfile | grep "%post" 26 | runTest 1 $output spython recipe $here/testdata/Singularity | grep "%post" 27 | runTest 0 $output spython recipe $here/testdata/Singularity | grep "FROM" 28 | 29 | echo "#### Testing recipe targeted generation" 30 | runTest 0 $output spython recipe --writer docker $here/testdata/Dockerfile | grep "FROM" 31 | runTest 1 $output spython recipe --writer docker $here/testdata/Dockerfile | grep "%post" 32 | runTest 0 $output spython recipe --writer singularity $here/testdata/Singularity | grep "%post" 33 | runTest 1 $output spython recipe --writer singularity $here/testdata/Singularity | grep "FROM" 34 | 35 | echo "#### Testing recipe file generation" 36 | outfile=$(mktemp ${tmpdir:-/tmp}/spython_recipe.XXXXXX) 37 | runTest 0 $output spython recipe $here/testdata/Dockerfile $outfile 38 | runTest 0 $output test -f "$outfile" 39 | runTest 0 $output cat $outfile | grep "%post" 40 | rm $outfile 41 | 42 | echo "#### Testing recipe json export" 43 | runTest 0 $output spython recipe --json $here/testdata/Dockerfile | grep "ports" 44 | runTest 0 $output spython recipe $here/testdata/Dockerfile $outfile 45 | runTest 0 $output test -f "$outfile" 46 | runTest 0 $output cat $outfile | grep "%post" 47 | 48 | # Force is false, should fail 49 | echo "#### Testing recipe json export, writing to file" 50 | runTest 0 $output spython recipe --json $here/testdata/Dockerfile $outfile 51 | runTest 0 $output spython recipe --force --json $here/testdata/Dockerfile $outfile 52 | runTest 0 $output test -f "$outfile" 53 | runTest 0 $output cat $outfile | grep "ports" 54 | 55 | echo "Finish testing basic client" 56 | rm -rf ${tmpdir} 57 | -------------------------------------------------------------------------------- /spython/tests/test_conversion.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2017-2024 Vanessa Sochat. 4 | 5 | # This Source Code Form is subject to the terms of the 6 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 7 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | import os 10 | from glob import glob 11 | 12 | 13 | def read_file(file): 14 | with open(file) as fd: 15 | content = fd.read().strip("\n") 16 | return content 17 | 18 | 19 | def test_other_recipe_exists(test_data): 20 | # Have any example 21 | assert test_data["d2s"] 22 | assert test_data["s2d"] 23 | 24 | for _, outFile in test_data["d2s"] + test_data["s2d"]: 25 | assert os.path.exists(outFile), outFile + " is missing" 26 | 27 | dockerfiles = glob( 28 | os.path.join(os.path.dirname(test_data["s2d"][0][0]), "*.docker") 29 | ) 30 | singularityfiles = glob( 31 | os.path.join(os.path.dirname(test_data["d2s"][0][0]), "*.def") 32 | ) 33 | for file in dockerfiles: 34 | assert file in [out for _, out in test_data["s2d"]] 35 | for file in singularityfiles: 36 | assert file in [out for _, out in test_data["d2s"]] 37 | 38 | 39 | def test_docker2singularity(test_data, tmp_path): 40 | from spython.main.parse.parsers import DockerParser 41 | from spython.main.parse.writers import SingularityWriter 42 | 43 | for dockerfile, recipe in test_data["d2s"]: 44 | parser = DockerParser(dockerfile) 45 | writer = SingularityWriter(parser.recipe) 46 | result = read_file(recipe).strip() 47 | assert writer.convert().replace("\n", "") == result.replace("\n", "") 48 | 49 | 50 | def test_singularity2docker(test_data, tmp_path): 51 | print("Testing spython conversion from singularity2docker") 52 | from spython.main.parse.parsers import SingularityParser 53 | from spython.main.parse.writers import DockerWriter 54 | 55 | for recipe, dockerfile in test_data["s2d"]: 56 | parser = SingularityParser(recipe) 57 | writer = DockerWriter(parser.recipe) 58 | assert writer.convert() == read_file(dockerfile) 59 | -------------------------------------------------------------------------------- /spython/tests/test_instances.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2017-2024 Vanessa Sochat. 4 | 5 | # This Source Code Form is subject to the terms of the 6 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 7 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | # name instance based on Python version in case running in parallel 10 | import sys 11 | 12 | import pytest 13 | 14 | from spython.main import Client 15 | 16 | version_string = "%s_%s_%s" % ( 17 | sys.version_info[0], 18 | sys.version_info[1], 19 | sys.version_info[2], 20 | ) 21 | 22 | 23 | def test_instance_class(): 24 | instance = Client.instance("docker://ubuntu", start=False) 25 | assert instance.get_uri() == "instance://" + instance.name 26 | assert instance.name != "" 27 | 28 | name = "coolName" 29 | instance = Client.instance("docker://busybox:1.30.1", start=False, name=name) 30 | assert instance.get_uri() == "instance://" + instance.name 31 | assert instance.name == name 32 | 33 | 34 | def test_has_no_instances(): 35 | instances = Client.instances() 36 | assert instances == [] 37 | 38 | 39 | class TestInstanceFuncs: 40 | @pytest.fixture(autouse=True) 41 | def test_instance_cmds(self, docker_container): 42 | image = docker_container[1] 43 | instance_name = "instance1_" + version_string 44 | myinstance = Client.instance(image, name=instance_name) 45 | assert myinstance.get_uri().startswith("instance://") 46 | 47 | print("...Case 2: List instances") 48 | instances = Client.instances() 49 | assert len(instances) == 1 50 | instances = Client.instances(return_json=True) 51 | assert len(instances) == 1 52 | assert isinstance(instances[0], dict) 53 | 54 | print("...Case 3: Commands to instances") 55 | result = Client.execute(myinstance, ["echo", "hello"]) 56 | assert result == "hello\n" 57 | 58 | print("...Case 4: Return value from instance") 59 | result = Client.execute(myinstance, "ls /", return_result=True) 60 | print(result) 61 | assert "tmp\nusr\nvar" in result["message"] 62 | assert result["return_code"] == 0 63 | 64 | print("...Case 5: Stop instances") 65 | myinstance.stop() 66 | instances = Client.instances() 67 | assert instances == [] 68 | myinstance1 = Client.instance(image, name="instance1_" + version_string) 69 | myinstance2 = Client.instance(image, name="instance2_" + version_string) 70 | assert myinstance1 is not None 71 | assert myinstance2 is not None 72 | instances = Client.instances() 73 | assert len(instances) == 2 74 | myinstance1.stop() 75 | myinstance2.stop() 76 | instances = Client.instances() 77 | assert instances == [] 78 | -------------------------------------------------------------------------------- /spython/tests/test_parsers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2017-2024 Vanessa Sochat. 4 | 5 | # This Source Code Form is subject to the terms of the 6 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 7 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | import os 10 | 11 | from spython.main.parse.parsers import DockerParser, SingularityParser 12 | 13 | 14 | def test_get_parser(): 15 | from spython.main.parse.parsers import get_parser 16 | 17 | parser = get_parser("docker") 18 | assert parser == DockerParser 19 | 20 | parser = get_parser("Dockerfile") 21 | assert parser == DockerParser 22 | 23 | parser = get_parser("Singularity") 24 | assert parser == SingularityParser 25 | 26 | 27 | def test_docker_parser(test_data): 28 | dockerfile = os.path.join(test_data["root"], "Dockerfile") 29 | parser = DockerParser(dockerfile) 30 | 31 | assert str(parser) == "[spython-parser][docker]" 32 | assert "spython-base" in parser.recipe 33 | recipe = parser.recipe["spython-base"] 34 | 35 | # Test all fields from recipe 36 | assert recipe.fromHeader == "python:3.5.1" 37 | assert recipe.cmd == "/code/run_uwsgi.sh" 38 | assert recipe.entrypoint is None 39 | assert recipe.workdir == "/code" 40 | assert recipe.volumes == [] 41 | assert recipe.ports == ["3031"] 42 | assert recipe.files[0] == ["requirements.txt", "/tmp/requirements.txt"] 43 | assert recipe.environ == ["PYTHONUNBUFFERED=1"] 44 | assert recipe.source == dockerfile 45 | 46 | 47 | def test_singularity_parser(test_data): 48 | recipefile = os.path.join(test_data["root"], "Singularity") 49 | parser = SingularityParser(recipefile) 50 | 51 | assert str(parser) == "[spython-parser][singularity]" 52 | assert "spython-base" in parser.recipe 53 | recipe = parser.recipe["spython-base"] 54 | 55 | # Test all fields from recipe 56 | assert recipe.fromHeader == "continuumio/miniconda3" 57 | assert recipe.cmd == 'exec /opt/conda/bin/spython "$@"' 58 | assert recipe.entrypoint is None 59 | assert recipe.workdir is None 60 | assert recipe.volumes == [] 61 | assert recipe.files == [] 62 | assert recipe.environ == [] 63 | assert recipe.source == recipefile 64 | -------------------------------------------------------------------------------- /spython/tests/test_recipe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2017-2024 Vanessa Sochat. 4 | 5 | # This Source Code Form is subject to the terms of the 6 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 7 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | 10 | def test_recipe_base(): 11 | from spython.main.parse.recipe import Recipe 12 | 13 | recipe = Recipe() 14 | assert str(recipe) == "[spython-recipe]" 15 | 16 | attributes = [ 17 | "cmd", 18 | "comments", 19 | "entrypoint", 20 | "environ", 21 | "files", 22 | "install", 23 | "labels", 24 | "ports", 25 | "test", 26 | "volumes", 27 | "workdir", 28 | ] 29 | 30 | for att in attributes: 31 | assert hasattr(recipe, att) 32 | 33 | print("Checking that empty recipe returns empty") 34 | result = recipe.json() 35 | assert not result 36 | 37 | print("Checking that non-empty recipe returns values") 38 | recipe.cmd = ["echo", "hello"] 39 | recipe.entrypoint = "/bin/bash" 40 | recipe.comments = ["This recipe is great", "Yes it is!"] 41 | recipe.environ = ["PANCAKES=WITHSYRUP"] 42 | recipe.files = [["one", "two"]] 43 | recipe.test = ["true"] 44 | recipe.install = ["apt-get update"] 45 | recipe.labels = ["Maintainer vanessasaur"] 46 | recipe.ports = ["3031"] 47 | recipe.volumes = ["/data"] 48 | recipe.workdir = "/code" 49 | 50 | result = recipe.json() 51 | for att in attributes: 52 | assert att in result 53 | -------------------------------------------------------------------------------- /spython/tests/test_writers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (C) 2017-2024 Vanessa Sochat. 4 | 5 | # This Source Code Form is subject to the terms of the 6 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 7 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | import os 10 | 11 | from spython.main.parse.writers import DockerWriter, SingularityWriter 12 | 13 | 14 | def test_writers(): 15 | from spython.main.parse.writers import get_writer 16 | 17 | writer = get_writer("docker") 18 | assert writer == DockerWriter 19 | 20 | writer = get_writer("Dockerfile") 21 | assert writer == DockerWriter 22 | 23 | writer = get_writer("Singularity") 24 | assert writer == SingularityWriter 25 | 26 | 27 | def test_docker_writer(test_data): 28 | from spython.main.parse.parsers import DockerParser 29 | 30 | dockerfile = os.path.join(test_data["root"], "Dockerfile") 31 | parser = DockerParser(dockerfile) 32 | writer = DockerWriter(parser.recipe) 33 | 34 | assert str(writer) == "[spython-writer][docker]" 35 | print(writer.convert()) 36 | 37 | 38 | def test_singularity_writer(test_data): 39 | from spython.main.parse.parsers import SingularityParser 40 | 41 | recipe = os.path.join(test_data["root"], "Singularity") 42 | parser = SingularityParser(recipe) 43 | writer = SingularityWriter(parser.recipe) 44 | 45 | assert str(writer) == "[spython-writer][singularity]" 46 | print(writer.convert()) 47 | -------------------------------------------------------------------------------- /spython/tests/testdata/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.5.1 2 | ENV PYTHONUNBUFFERED 1 3 | 4 | ################################################################################ 5 | # CORE 6 | # Do not modify this section 7 | 8 | RUN apt-get update && apt-get install -y \ 9 | pkg-config \ 10 | cmake \ 11 | openssl \ 12 | wget \ 13 | git \ 14 | vim 15 | 16 | RUN apt-get update && apt-get install -y \ 17 | anacron \ 18 | autoconf \ 19 | automake \ 20 | libarchive-dev \ 21 | libtool \ 22 | libopenblas-dev \ 23 | libglib2.0-dev \ 24 | gfortran \ 25 | libxml2-dev \ 26 | libxmlsec1-dev \ 27 | libhdf5-dev \ 28 | libgeos-dev \ 29 | libsasl2-dev \ 30 | libldap2-dev \ 31 | squashfs-tools \ 32 | build-essential 33 | 34 | # Install Singularity 35 | RUN git clone -b vault/release-2.5 https://www.github.com/sylabs/singularity.git 36 | WORKDIR singularity 37 | RUN ./autogen.sh && ./configure --prefix=/usr/local && make && make install 38 | 39 | # Install Python requirements out of /tmp so not triggered if other contents of /code change 40 | ADD requirements.txt /tmp/requirements.txt 41 | RUN pip install --upgrade pip 42 | RUN pip install -r /tmp/requirements.txt 43 | 44 | ADD . /code/ 45 | 46 | ################################################################################ 47 | # PLUGINS 48 | # You are free to comment out those plugins that you don't want to use 49 | 50 | # Install LDAP (uncomment if wanted) 51 | # RUN pip install python3-ldap 52 | # RUN pip install django-auth-ldap 53 | 54 | # Install Globus (uncomment if wanted) 55 | # RUN /bin/bash /code/scripts/globus/globus-install.sh 56 | 57 | # Install SAML (uncomment if wanted) 58 | # RUN pip install python3-saml 59 | # RUN pip install social-auth-core[saml] 60 | 61 | ################################################################################ 62 | # BASE 63 | 64 | RUN mkdir -p /code && mkdir -p /code/images 65 | RUN mkdir -p /var/www/images && chmod -R 0755 /code/images/ 66 | 67 | USER tacos 68 | 69 | WORKDIR /code 70 | RUN apt-get remove -y gfortran 71 | 72 | RUN apt-get autoremove -y 73 | RUN apt-get clean 74 | RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 75 | 76 | # Install crontab to setup job 77 | RUN echo "0 0 * * * /usr/bin/python /code/manage.py generate_tree" >> /code/cronjob 78 | RUN crontab /code/cronjob 79 | RUN rm /code/cronjob 80 | 81 | # Create hashed temporary upload locations 82 | RUN mkdir -p /var/www/images/_upload/{0..9} && chmod 777 -R /var/www/images/_upload 83 | 84 | CMD /code/run_uwsgi.sh 85 | 86 | EXPOSE 3031 87 | -------------------------------------------------------------------------------- /spython/tests/testdata/README.md: -------------------------------------------------------------------------------- 1 | # Test Data 2 | 3 | This folder contains test data for Singularity Python. 4 | 5 | - [Singularity](Singularity) and [Docker](Docker) are generic recipes used to run the tests in [one folder up](../). They are not inended to be converted between one another. 6 | - [singularity2docker](singularity2docker) is a folder of docker recipes (`*.docker`) and Singularity recipes (`*.def`) that are tested for conversion *from* Singularity to Docker. 7 | - [docker2singularity](docker2singularity) is a folder of Singularity recipes (`*.def`) and docker recipes (`*.docker`) that are tested for conversion *from* Docker to Singularity. 8 | 9 | To add a new pair of recipes to either folder, simply write a .def and .docker file with the same name. They will be tested by [test_conversion.py](../test_conversion.py). 10 | -------------------------------------------------------------------------------- /spython/tests/testdata/Singularity: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: continuumio/miniconda3 3 | 4 | %runscript 5 | exec /opt/conda/bin/spython "$@" 6 | 7 | %labels 8 | maintainer vsochat@stanford.edu 9 | 10 | %post 11 | apt-get update && apt-get install -y git 12 | 13 | # Dependencies 14 | cd /opt 15 | git clone https://www.github.com/singularityhub/singularity-cli 16 | cd singularity-cli 17 | /opt/conda/bin/pip install setuptools 18 | /opt/conda/bin/python setup.py install 19 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/add.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | Stage: spython-base 4 | 5 | %files 6 | . /opt 7 | %runscript 8 | exec /bin/bash "$@" 9 | %startscript 10 | exec /bin/bash "$@" 11 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/add.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest 2 | ADD . /opt 3 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/argsub.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: nvidia/cuda:11.1.1-cudnn8-devel-ubuntu20.04 3 | Stage: spython-base 4 | 5 | %files 6 | ./requirements.txt /workspace 7 | %labels 8 | maintainer="Dong Wang" 9 | %post 10 | CUDA_VERSION=11.1.1 11 | OS_VERSION=20.04 12 | 13 | 14 | 15 | 16 | PATH="/root/miniconda3/bin:${PATH}" 17 | PATH="/root/miniconda3/bin:${PATH}" 18 | DEBIAN_FRONTEND=noninteractive 19 | 20 | SHELL ["/bin/bash", "-c"] 21 | 22 | apt-get update && apt-get upgrade -y &&\ 23 | apt-get install -y wget python3-pip 24 | 25 | python3 -m pip install --upgrade pip 26 | 27 | mkdir -p /workspace 28 | cd /workspace 29 | python3 -m pip install -r /workspace/requirements.txt and &&\ 30 | rm /workspace/requirements.txt 31 | 32 | %environment 33 | export PATH="/root/miniconda3/bin:${PATH}" 34 | %runscript 35 | cd /workspace 36 | exec /bin/bash /bin/bash "$@" 37 | %startscript 38 | cd /workspace 39 | exec /bin/bash /bin/bash "$@" 40 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/argsub.docker: -------------------------------------------------------------------------------- 1 | ARG CUDA_VERSION=11.1.1 2 | ARG OS_VERSION=20.04 3 | 4 | FROM nvidia/cuda:${CUDA_VERSION}-cudnn8-devel-ubuntu${OS_VERSION} 5 | 6 | LABEL maintainer="Dong Wang" 7 | 8 | 9 | ENV PATH="/root/miniconda3/bin:${PATH}" 10 | ARG PATH="/root/miniconda3/bin:${PATH}" 11 | ARG DEBIAN_FRONTEND=noninteractive 12 | 13 | SHELL ["/bin/bash", "-c"] 14 | 15 | RUN apt-get update && apt-get upgrade -y &&\ 16 | apt-get install -y wget python3-pip 17 | 18 | RUN python3 -m pip install --upgrade pip 19 | 20 | WORKDIR /workspace 21 | ADD ./requirements.txt /workspace 22 | RUN python3 -m pip install -r /workspace/requirements.txt and &&\ 23 | rm /workspace/requirements.txt 24 | 25 | CMD ["/bin/bash"] 26 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/cmd.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | Stage: spython-base 4 | 5 | %runscript 6 | exec /bin/bash echo hello "$@" 7 | %startscript 8 | exec /bin/bash echo hello "$@" 9 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/cmd.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest 2 | CMD ["echo", "hello"] 3 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/comments.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | Stage: spython-base 4 | 5 | %post 6 | 7 | # This is a really important line 8 | cp /bin/echo /opt/echo 9 | 10 | # I'm sure you agree with me? 11 | %runscript 12 | exec /bin/bash "$@" 13 | %startscript 14 | exec /bin/bash "$@" 15 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/comments.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest 2 | 3 | # This is a really important line 4 | RUN cp /bin/echo /opt/echo 5 | 6 | # I'm sure you agree with me? 7 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/copy.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | Stage: spython-base 4 | 5 | %files 6 | . /opt 7 | %runscript 8 | exec /bin/bash "$@" 9 | %startscript 10 | exec /bin/bash "$@" 11 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/copy.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest 2 | COPY . /opt 3 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/entrypoint-cmd.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | Stage: spython-base 4 | 5 | %runscript 6 | exec python /code/script.py "$@" 7 | %startscript 8 | exec python /code/script.py "$@" 9 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/entrypoint-cmd.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest 2 | CMD ["/code/script.py"] 3 | ENTRYPOINT ["python"] 4 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/entrypoint.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | Stage: spython-base 4 | 5 | %runscript 6 | exec /bin/bash run_uwsgi.sh "$@" 7 | %startscript 8 | exec /bin/bash run_uwsgi.sh "$@" 9 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/entrypoint.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest 2 | ENTRYPOINT /bin/bash run_uwsgi.sh 3 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/expose.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | Stage: spython-base 4 | 5 | %post 6 | # EXPOSE 3031 7 | # EXPOSE 9000 8 | %runscript 9 | exec /bin/bash "$@" 10 | %startscript 11 | exec /bin/bash "$@" 12 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/expose.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest 2 | EXPOSE 3031 3 | EXPOSE 9000 4 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/from.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | Stage: spython-base 4 | 5 | %runscript 6 | exec /bin/bash "$@" 7 | %startscript 8 | exec /bin/bash "$@" 9 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/from.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest 2 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/healthcheck.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | Stage: spython-base 4 | 5 | %runscript 6 | exec /bin/bash "$@" 7 | %startscript 8 | exec /bin/bash "$@" 9 | %test 10 | true 11 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/healthcheck.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest 2 | HEALTHCHECK true 3 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/label.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | Stage: spython-base 4 | 5 | %labels 6 | maintainer dinosaur 7 | %runscript 8 | exec /bin/bash "$@" 9 | %startscript 10 | exec /bin/bash "$@" 11 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/label.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest 2 | LABEL maintainer dinosaur 3 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/multiple-lines.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | Stage: spython-base 4 | 5 | %post 6 | 7 | apt-get update && \ 8 | apt-get install -y git \ 9 | wget \ 10 | curl \ 11 | squashfs-tools 12 | %runscript 13 | exec /bin/bash "$@" 14 | %startscript 15 | exec /bin/bash "$@" 16 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/multiple-lines.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y git \ 5 | wget \ 6 | curl \ 7 | squashfs-tools 8 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/multistage.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: golang:1.12.3-alpine3.9 3 | Stage: devel 4 | 5 | %post 6 | export PATH="/go/bin:/usr/local/go/bin:$PATH" 7 | export HOME="/root" 8 | cd /root 9 | touch hello 10 | 11 | Bootstrap: docker 12 | From: alpine:3.9 13 | Stage: final 14 | 15 | %files from devel 16 | /root/hello /bin/hello 17 | %runscript 18 | exec /bin/bash "$@" 19 | %startscript 20 | exec /bin/bash "$@" 21 | 22 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/multistage.docker: -------------------------------------------------------------------------------- 1 | FROM golang:1.12.3-alpine3.9 AS devel 2 | RUN export PATH="/go/bin:/usr/local/go/bin:$PATH" 3 | RUN export HOME="/root" 4 | RUN cd /root 5 | RUN touch hello 6 | FROM alpine:3.9 AS final 7 | COPY --from=devel /root/hello /bin/hello 8 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/user.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | Stage: spython-base 4 | 5 | %post 6 | echo "cloud" 7 | su - rainman # USER rainman 8 | echo "makeitrain" 9 | su - root # USER root 10 | %runscript 11 | exec /bin/bash "$@" 12 | %startscript 13 | exec /bin/bash "$@" 14 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/user.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest 2 | RUN echo "cloud" 3 | USER rainman 4 | RUN echo "makeitrain" 5 | USER root 6 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/workdir.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | Stage: spython-base 4 | 5 | %post 6 | mkdir -p /code 7 | cd /code 8 | %runscript 9 | cd /code 10 | exec /bin/bash "$@" 11 | %startscript 12 | cd /code 13 | exec /bin/bash "$@" 14 | -------------------------------------------------------------------------------- /spython/tests/testdata/docker2singularity/workdir.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest 2 | WORKDIR /code 3 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/files.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | %files 4 | file.txt /opt/file.txt 5 | /path/to/thing /opt/thing 6 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/files.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest AS spython-base 2 | ADD file.txt /opt/file.txt 3 | ADD /path/to/thing /opt/thing 4 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/from.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/from.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest AS spython-base 2 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/labels.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | %labels 4 | Maintainer dinosaur 5 | Version 1.0.0 6 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/labels.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest AS spython-base 2 | LABEL Maintainer dinosaur 3 | LABEL Version 1.0.0 4 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/multiple-lines.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | %post 4 | 5 | apt-get update && \ 6 | apt-get install -y git \ 7 | wget \ 8 | curl \ 9 | squashfs-tools 10 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/multiple-lines.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest AS spython-base 2 | RUN apt-get update && \ 3 | apt-get install -y git \ 4 | wget \ 5 | curl \ 6 | squashfs-tools 7 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/multistage.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: golang:1.12.3-alpine3.9 3 | Stage: devel 4 | 5 | %post 6 | # prep environment 7 | export PATH="/go/bin:/usr/local/go/bin:$PATH" 8 | export HOME="/root" 9 | cd /root 10 | touch hello 11 | 12 | # Install binary into final image 13 | Bootstrap: docker 14 | From: alpine:3.9 15 | Stage: final 16 | 17 | # install binary from stage one 18 | %files from devel 19 | /root/hello /bin/hello 20 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/multistage.docker: -------------------------------------------------------------------------------- 1 | FROM golang:1.12.3-alpine3.9 AS devel 2 | RUN export PATH="/go/bin:/usr/local/go/bin:$PATH" 3 | RUN export HOME="/root" 4 | RUN cd /root 5 | RUN touch hello 6 | FROM alpine:3.9 AS final 7 | COPY --from=devel /root/hello /bin/hello 8 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/post.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | %post 4 | apt-get update 5 | apt-get install -y git \ 6 | wget 7 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/post.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest AS spython-base 2 | RUN apt-get update 3 | RUN apt-get install -y git \ 4 | wget 5 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/runscript.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | %runscript 4 | exec /bin/bash echo hello "$@" 5 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/runscript.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest AS spython-base 2 | CMD exec /bin/bash echo hello "$@" 3 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/test.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: busybox:latest 3 | %test 4 | true 5 | -------------------------------------------------------------------------------- /spython/tests/testdata/singularity2docker/test.docker: -------------------------------------------------------------------------------- 1 | FROM busybox:latest AS spython-base 2 | RUN echo "true" >> /tests.sh 3 | RUN chmod u+x /tests.sh 4 | HEALTHCHECK /bin/bash /tests.sh 5 | -------------------------------------------------------------------------------- /spython/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .fileio import mkdir_p, read_file, read_json, write_file, write_json 2 | from .misc import ScopedEnvVar 3 | from .terminal import ( 4 | check_install, 5 | format_container_name, 6 | get_installdir, 7 | get_singularity_version, 8 | get_userhome, 9 | get_username, 10 | remove_uri, 11 | run_command, 12 | split_uri, 13 | stream_command, 14 | ) 15 | -------------------------------------------------------------------------------- /spython/utils/fileio.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | import errno 8 | import json 9 | import os 10 | import sys 11 | 12 | from spython.logger import bot 13 | 14 | ################################################################################ 15 | ## FOLDER OPERATIONS ########################################################### 16 | ################################################################################ 17 | 18 | 19 | def mkdir_p(path): 20 | """mkdir_p attempts to get the same functionality as mkdir -p 21 | :param path: the path to create. 22 | """ 23 | try: 24 | os.makedirs(path) 25 | except OSError as e: 26 | if e.errno == errno.EEXIST and os.path.isdir(path): 27 | pass 28 | else: 29 | bot.error("Error creating path %s, exiting." % path) 30 | sys.exit(1) 31 | 32 | 33 | ################################################################################ 34 | ## FILE OPERATIONS ############################################################# 35 | ################################################################################ 36 | 37 | 38 | def write_file(filename, content, mode="w"): 39 | """write_file will open a file, "filename" and write content, "content" 40 | and properly close the file 41 | """ 42 | with open(filename, mode) as filey: 43 | filey.writelines(content) 44 | return filename 45 | 46 | 47 | def write_json(json_obj, filename, mode="w", print_pretty=True): 48 | """ 49 | write_json will (optionally,pretty print) a json object to file 50 | :param json_obj: the dict to print to json 51 | :param filename: the output file to write to 52 | :param pretty_print: if True, will use nicer formatting 53 | """ 54 | with open(filename, mode) as filey: 55 | if print_pretty: 56 | filey.writelines(json.dumps(json_obj, indent=4, separators=(",", ": "))) 57 | else: 58 | filey.writelines(json.dumps(json_obj)) 59 | return filename 60 | 61 | 62 | def read_file(filename, mode="r", readlines=True): 63 | """ 64 | write_file will open a file, "filename" and write content, "content" 65 | and properly close the file 66 | """ 67 | with open(filename, mode) as filey: 68 | if readlines: 69 | content = filey.readlines() 70 | else: 71 | content = filey.read() 72 | return content 73 | 74 | 75 | def read_json(filename, mode="r"): 76 | """ 77 | read_json reads in a json file and returns 78 | the data structure as dict. 79 | """ 80 | with open(filename, mode) as filey: 81 | data = json.load(filey) 82 | return data 83 | -------------------------------------------------------------------------------- /spython/utils/misc.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | import os 8 | 9 | 10 | def setEnvVar(name, value): 11 | """Set or unset an environment variable 12 | 13 | name -- Name of the variable to set 14 | value -- Value to use or None to clear 15 | """ 16 | if value is None: 17 | if name in os.environ: 18 | del os.environ[name] 19 | else: 20 | os.environ[name] = value 21 | 22 | 23 | class ScopedEnvVar: 24 | """Temporarily change an environment variable 25 | 26 | Usage: 27 | with ScopedEnvVar("FOO", "bar"): 28 | print(os.environ["FOO"]) # "bar" 29 | print(os.environ["FOO"]) # 30 | """ 31 | 32 | def __init__(self, name, value): 33 | """Create the scoped environment variable object 34 | 35 | name -- Name of the variable to set 36 | value -- Value to use or None to clear 37 | """ 38 | self.name = name 39 | self.value = value 40 | self.oldValue = None 41 | 42 | def __enter__(self): 43 | self.oldValue = os.environ.get(self.name) 44 | setEnvVar(self.name, self.value) 45 | return self 46 | 47 | def __exit__(self, ex_type, ex_value, traceback): 48 | setEnvVar(self.name, self.oldValue) 49 | -------------------------------------------------------------------------------- /spython/version.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017-2024 Vanessa Sochat. 2 | 3 | # This Source Code Form is subject to the terms of the 4 | # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed 5 | # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | 8 | __version__ = "0.3.14" 9 | AUTHOR = "Vanessa Sochat" 10 | AUTHOR_EMAIL = "vsoch@users.noreply.github.com" 11 | NAME = "spython" 12 | PACKAGE_URL = "https://github.com/singularityhub/singularity-cli" 13 | KEYWORDS = "singularity python client (spython)" 14 | DESCRIPTION = "Command line python tool for working with singularity." 15 | LICENSE = "LICENSE" 16 | 17 | INSTALL_REQUIRES = () 18 | TESTS_REQUIRES = (("pytest", {"min_version": "4.6.2"}),) 19 | --------------------------------------------------------------------------------