diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 0000000000..6fb67ed14c --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,237 @@ +# only run for releases +only_if: $CIRRUS_TAG != '' + +env: + MACOSX_DEPLOYMENT_TARGET: "10.15" + +macosx_arm64_wheel_task: + macos_instance: + image: ghcr.io/cirruslabs/macos-monterey-base:latest + + checkout_script: + - git submodule init + - git submodule update + + install_deps_script: + - brew update + # go 1.22 doesn't work, see also: https://github.com/golang/go/issues/37602 + - brew install coreutils go@1.21 pyenv python + + install_various_python_script: + - pyenv install 3.8.18 + - pyenv install 3.9.18 + - pyenv install 3.10.13 + - pyenv install 3.11.8 + - pyenv install 3.12.2 + + install_apache_arrow_script: | + echo "Installing apache-arrow ..." + mkdir -p /tmp/install + cd /tmp/install + curl -L https://github.com/apache/arrow/archive/refs/tags/apache-arrow-8.0.1.tar.gz --output apache-arrow-8.0.1.tar.gz + tar zxf apache-arrow-8.0.1.tar.gz + cd arrow-apache-arrow-8.0.1 + mkdir -p build-dir + cd build-dir + cmake ../cpp \ + -DARROW_COMPUTE=OFF \ + -DARROW_WITH_UTF8PROC=OFF \ + -DARROW_CSV=OFF \ + -DARROW_CUDA=OFF \ + -DARROW_DATASET=OFF \ + -DARROW_FILESYSTEM=ON \ + -DARROW_FLIGHT=OFF \ + -DARROW_GANDIVA=OFF \ + -DARROW_GANDIVA_JAVA=OFF \ + -DARROW_HDFS=OFF \ + -DARROW_HIVESERVER2=OFF \ + -DARROW_JSON=OFF \ + -DARROW_ORC=OFF \ + -DARROW_PARQUET=OFF \ + -DARROW_PLASMA=OFF \ + -DARROW_PLASMA_JAVA_CLIENT=OFF \ + -DARROW_PYTHON=OFF \ + -DARROW_S3=OFF \ + -DARROW_WITH_BZ2=OFF \ + -DARROW_WITH_ZLIB=OFF \ + -DARROW_WITH_LZ4=OFF \ + -DARROW_WITH_SNAPPY=OFF \ + -DARROW_WITH_ZSTD=OFF \ + -DARROW_WITH_BROTLI=OFF \ + -DARROW_IPC=ON \ + -DARROW_BUILD_BENCHMARKS=OFF \ + -DARROW_BUILD_EXAMPLES=OFF \ + -DARROW_BUILD_INTEGRATION=OFF \ + -DARROW_BUILD_UTILITIES=OFF \ + -DARROW_BUILD_TESTS=OFF \ + -DARROW_ENABLE_TIMING_TESTS=OFF \ + -DARROW_FUZZING=OFF \ + -DARROW_USE_ASAN=OFF \ + -DARROW_USE_TSAN=OFF \ + -DARROW_USE_UBSAN=OFF \ + -DARROW_JEMALLOC=OFF \ + -DARROW_BUILD_SHARED=OFF \ + -DARROW_BUILD_STATIC=ON + sudo make install -j$(sysctl -n hw.ncpu) + cd /tmp + sudo rm -rf /tmp/install + + install_boost_script: | + echo "Installing boost ..." + mkdir -p /tmp/install + cd /tmp/install + wget -q https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.gz + tar zxf boost_1_75_0.tar.gz + cd boost_1_75_0 + ./bootstrap.sh + sudo ./b2 install -j`nproc` variant=release threading=multi \ + --with-atomic \ + --with-chrono \ + --with-date_time \ + --with-filesystem \ + --with-random \ + --with-regex \ + --with-system \ + --with-thread + cd /tmp + sudo rm -rf /tmp/install + + install_gflags_script: | + echo "Installing gflags ..." + mkdir -p /tmp/install + cd /tmp/install + curl -L https://github.com/gflags/gflags/archive/v2.2.2.tar.gz --output gflags-v2.2.2.tar.gz + tar zxf gflags-v2.2.2.tar.gz + cd gflags-2.2.2 + mkdir -p build-dir + cd build-dir + cmake .. -DBUILD_SHARED_LIBS=OFF + sudo make install -j$(sysctl -n hw.ncpu) + cd /tmp + sudo rm -rf /tmp/install + + install_glog_script: | + echo "Installing glog ..." + mkdir -p /tmp/install + cd /tmp/install + curl -L https://github.com/google/glog/archive/v0.6.0.tar.gz --output glog-v0.6.0.tar.gz + tar zxf glog-v0.6.0.tar.gz + cd glog-0.6.0 + mkdir -p build-dir + cd build-dir + cmake .. -DBUILD_SHARED_LIBS=OFF \ + -DWITH_GFLAGS=OFF \ + -DBUILD_TESTING=OFF + sudo make install -j$(sysctl -n hw.ncpu) + cd /tmp + sudo rm -rf /tmp/install + + install_protobuf_grpc_script: | + echo "Installing protobuf & grpc ..." + mkdir -p /tmp/install + cd /tmp/install + wget -q https://github.com/unsafecoerce/git-submodules-tarball/releases/download/grpc%2Fgrpc-v1.36.x/grpc-grpc-1.36.x.tar.gz + tar zxf grpc-grpc-1.36.x.tar.gz + cd grpc-grpc-1.36.x + mkdir -p cmake-build + cd cmake-build + cmake .. -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DBUILD_SHARED_LIBS=OFF \ + -DgRPC_INSTALL=ON \ + -DgRPC_BUILD_TESTS=OFF \ + -DgRPC_BUILD_CSHARP_EXT=OFF \ + -DgRPC_BUILD_GRPC_CSHARP_PLUGIN=OFF \ + -DgRPC_BUILD_GRPC_NODE_PLUGIN=OFF \ + -DgRPC_BUILD_GRPC_OBJECTIVE_C_PLUGIN=OFF \ + -DgRPC_BUILD_GRPC_PHP_PLUGIN=OFF \ + -DgRPC_BUILD_GRPC_PYTHON_PLUGIN=OFF \ + -DgRPC_BUILD_GRPC_RUBY_PLUGIN=OFF \ + -DgRPC_SSL_PROVIDER=package \ + -DgRPC_ZLIB_PROVIDER=package \ + -DOPENSSL_ROOT_DIR=$(brew --prefix openssl) \ + -DgRPC_BACKWARDS_COMPATIBILITY_MODE=ON + sudo make install -j$(sysctl -n hw.ncpu) + cd /tmp + sudo rm -rf /tmp/install + + build_vineyardctl_script: + - | + export PATH=$(brew --prefix go@1.21)/bin:$PATH + make -C k8s vineyardctl + + vineyardctl_artifacts: + path: k8s/vineyardctl + + build_vineyardd_script: + - mkdir -p build + - cd build + - | + pyenv global 3.11.8 + cmake .. -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=17 \ + -DCMAKE_CXX_STANDARD_REQUIRED=TRUE \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_VINEYARD_SERVER=ON \ + -DBUILD_VINEYARD_CLIENT=OFF \ + -DBUILD_VINEYARD_PYTHON_BINDINGS=OFF \ + -DBUILD_VINEYARD_PYPI_PACKAGES=OFF \ + -DBUILD_VINEYARD_BASIC=OFF \ + -DBUILD_VINEYARD_GRAPH=OFF \ + -DBUILD_VINEYARD_IO=OFF \ + -DBUILD_VINEYARD_HOSSEINMOEIN_DATAFRAME=OFF \ + -DBUILD_VINEYARD_TESTS=OFF \ + -DBUILD_VINEYARD_TESTS_ALL=OFF \ + -DBUILD_VINEYARD_PROFILING=OFF + make vineyardd -j$(sysctl -n hw.ncpu) + + vineyardd_artifacts: + path: build/bin/vineyardd + + build_vineyard_bdist_script: + - pyenv global 3.11.8 + - cp k8s/vineyardctl ./python/vineyard/bdist/vineyardctl + - cp build/bin/vineyardd ./python/vineyard/bdist/vineyardd + - strip ./python/vineyard/bdist/vineyardctl + - strip ./python/vineyard/bdist/vineyardd + - python3 setup_bdist.py bdist_wheel --plat=macosx_11_0_arm64 + + build_vineyard_python_script: + - | + for py in 3.8.18 3.9.18 3.10.13 3.11.8 3.12.2; do + current_python=$(pyenv root)/versions/$py/bin/python + echo "Python is: $current_python, $($current_python --version)" + $current_python -m pip install delocate setuptools wheel + rm -rf build/lib* build/bdist.* python/vineyard/*.dylib python/vineyard/*.so + mkdir -p build + cd build + cmake .. -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=17 \ + -DCMAKE_CXX_STANDARD_REQUIRED=TRUE \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_VINEYARD_SERVER=OFF \ + -DBUILD_VINEYARD_CLIENT=ON \ + -DBUILD_VINEYARD_PYTHON_BINDINGS=ON \ + -DBUILD_VINEYARD_PYPI_PACKAGES=ON \ + -DPYTHON_EXECUTABLE=$current_python \ + -DBUILD_VINEYARD_BASIC=OFF \ + -DBUILD_VINEYARD_GRAPH=OFF \ + -DBUILD_VINEYARD_IO=OFF \ + -DBUILD_VINEYARD_HOSSEINMOEIN_DATAFRAME=OFF \ + -DBUILD_VINEYARD_TESTS=OFF \ + -DBUILD_VINEYARD_TESTS_ALL=OFF \ + -DBUILD_VINEYARD_PROFILING=OFF + make vineyard_client_python -j$(sysctl -n hw.ncpu) + cd .. + $current_python setup.py bdist_wheel + done + + delocate_wheel_script: + - | + $(brew --prefix python)/bin/python3 -m pip install delocate wheel + for wheel in dist/*.whl; do + delocate-wheel -w fixed_wheels -v $wheel + done + ls -la ./fixed_wheels + + macosx_arm64_wheel_artifacts: + path: fixed_wheels/*.whl diff --git a/.cspell.json b/.cspell.json new file mode 100644 index 0000000000..dd5596ce48 --- /dev/null +++ b/.cspell.json @@ -0,0 +1,327 @@ +// cSpell Settings +{ + // Version of the setting file. Always 0.2 + "version": "0.2", + // language - current active spelling language + "language": "en", + // a list of globs to specify which files are to be ignored + "ignorePaths": [ + "**/go.mod", + "**/thirdparty/**", + "**/vendor/**", + "**/target/**", + "**/dist/**", + "**/build/**", + "**/_build/**", + "**/*.egg-info/**", + "**/*.eggs/**", + "**/__pycache__/**", + "**/default.etcd/**", + "**/*.log", + "**/*.jar", + "**/.cache/**", + "**/.ossutil_checkpoint", + "**/metastore_db/**", + "docs/_static", + "misc/cpplint.py", + "python/vineyard/drivers/io/tests/test_ossfs.py", + "python/vineyard/contrib/kedro/benchmark/mlops/data/**", + "k8s/vineyardctl", + ], + // words - list of words to be always considered correct + "words": [ + "Aliyun", + "allclose", + "ALLOC", + "Alluxio", + "apierrors", + "apiextensions", + "apimachinery", + "apiv", + "Appender", + "arange", + "asio", + "astype", + "Backoff", + "bdist", + "bigdata", + "bitcode", + "bitwidth", + "buildx", + "bulkstore", + "bytecode", + "chatgpt", + "checkpointing", + "chrono", + "cindex", + "CLASSNAME", + "clientref", + "clientset", + "clippy", + "cloudpickle", + "CNCF", + "codecov", + "codegen", + "codereview", + "composability", + "configmap", + "Consolidatable", + "consts", + "coreutils", + "cout", + "coveragerc", + "cpplint", + "cpprestsdk", + "crds", + "CUDA", + "cycleclock", + "daemonset", + "dask", + "dataframe", + "dataframes", + "datafusion", + "datalens", + "datasource", + "datatypes", + "davinci", + "DCPPREST", + "debruijn", + "dedup", + "defaultscheme", + "Deque", + "deserialization", + "destaticize", + "distro", + "divein", + "dlist", + "dlmalloc", + "dlog", + "doctest", + "dropna", + "dtype", + "DVLOG", + "endl", + "etcdctl", + "etcdserverpb", + "etcdv", + "exitfirst", + "fastpath", + "filesystems", + "flajolet", + "Flink", + "flyteconsole", + "flytectl", + "flytesnacks", + "formatcp", + "fsspec", + "genieai", + "gensym", + "getset", + "getxattr", + "Giraph", + "gitbook", + "globalobject", + "GLOO", + "goccy", + "gopath", + "GPTAPI", + "graphlearn", + "graphscope", + "hashable", + "hasher", + "hashers", + "hdfs", + "Healthz", + "HOSSEINMOEIN", + "htap", + "htpasswd", + "HUGETLB", + "hyperloglog", + "hyperparameters", + "idxmax", + "idxmin", + "impls", + "Infof", + "inlines", + "inuse", + "iocontext", + "ipcserver", + "iproduct", + "irecv", + "isend", + "isinstance", + "issigned", + "iszero", + "itertools", + "izip", + "Kaggle", + "kedro", + "KEEPALIVE", + "kprobe", + "kretprobe", + "kubebuilder", + "kubeconfig", + "kustomization", + "Kustomize", + "lambdafy", + "ldbc", + "leaseid", + "leasekeepalive", + "LLMKV", + "libboost", + "libclang", + "libgrape", + "libgrpc", + "libprotobuf", + "livegraph", + "localobject", + "localobjects", + "loglog", + "logr", + "lossen", + "makefun", + "malloc", + "mallocinfo", + "manylinux", + "mathjax", + "maxdepth", + "memmap", + "memoryview", + "metadatas", + "metakv", + "metas", + "metastore", + "metatree", + "microbatches", + "mimalloc", + "minio", + "mmap", + "multierr", + "multispace", + "MVCC", + "nbytes", + "NCCL", + "ndarray", + "nfilters", + "nobuffer", + "NODENAME", + "noindex", + "nonblocking", + "nonminimal", + "nonoverlapping", + "nonspaces", + "nopython", + "nproc", + "nslices", + "Numpy", + "objectfs", + "ogbn", + "oneof", + "Oobleck", + "OPENAI", + "ossfs", + "PARTITIONER", + "persuing", + "pflag", + "platlib", + "Pluggable", + "pointee", + "popen", + "POSIX", + "pplx", + "Preprocess", + "printk", + "privkey", + "protobuf", + "ptrguard", + "purelib", + "pyarrow", + "pyenv", + "Pylance", + "pylint", + "pylintrc", + "pyorc", + "pypa", + "pytest", + "pythonapi", + "quickstart", + "rcfile", + "rdtsc", + "rdzv", + "readwrite", + "Readyz", + "realpath", + "rebalance", + "Rebalancer", + "rechunk", + "recordbatch", + "recordbatches", + "reinit", + "reinterpert", + "relu", + "replicaset", + "repr", + "Reprable", + "Retryable", + "rfind", + "roadmap", + "rpcserver", + "rustdoc", + "rustfmt", + "scikit", + "sendfd", + "serde", + "setxattr", + "sharded", + "sighingnow", + "significand", + "SIGUSR", + "SIMD", + "skipna", + "skywalking", + "SLURM", + "softwareupdate", + "staticize", + "staticized", + "strategized", + "streamable", + "subdir", + "subhealthy", + "Succ", + "thirdparty", + "thiserror", + "TENSORBYTES", + "Timepoint", + "toctree", + "TORCHELASTIC", + "tparam", + "traceback", + "transformative", + "typecheck", + "typekind", + "UDFs", + "ujson", + "unittests", + "Unpublish", + "unsafecoerce", + "Unstage", + "uprobe", + "uretprobe", + "varint", + "VCDL", + "vectorize", + "vfile", + "vineyardcloudnative", + "vineyardctl", + "VINEYARDD", + "Wattributes", + "WEBSOCKETS", + "whitegrid", + "Wrapf", + "xattr", + "xmls", + "xxhash", + "zapcore", + "zxvf", + ], +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..e151f36845 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,59 @@ +.git +**/.git + +# contents from .gitignore +build/ +build-mac/ +install/ +build-static/ +build-static-mac/ +install-static/ +build-docker/ +install-docker/ +build-docker-gsa/ +install-docker-gsa/ +*.code-workspace +.vscode +.idea +.DS_store +cmake-build-debug/ +modules/graph/thirdparty/**/build/ + +# for python packaging +dist/ +**/*.egg-info/ +**/*.so +**/*.dylib +**/*.a +**/*.pyc +**/*.bin +**/*.egg + +# generated files +*.vineyard.h + +# etcd data directory during testing +default.etcd/ + +# tmp data directory during building image +docker/dist/ + +# docs +docs/_build/ + +# go vendor +/**/vendor/ + +# Rust build +/**/target/ + +# k8s +k8s/bin/ +k8s/vendor/ +k8s/examples/ +k8s/test/ +k8s/hack/ +!k8s/config/scheduler/config.yaml + +# artifacts +wheels/ diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000000..8833043ae7 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,85 @@ +title = "Gitleaks for Vineyard" + +[extend] +useDefault = true + +[[rules]] +description = "Alibaba AccessKey ID" +id = "alibaba-access-key-id" +regex = '''(?i)((LTAI)[a-z0-9]+)''' +keywords = [ + "ltai", +] + +[[rules]] +description = "Alibaba AccessKey ID" +id = "alibaba-access-id-in-config" +regex = '''(?i)((access).?id\s*=\s*.+)''' +keywords = [ + "access", +] + +[[rules]] +description = "Alibaba AccessKey ID" +id = "alibaba-access-key-in-config" +regex = '''(?i)((access).?key\s*=\s*.+)''' +keywords = [ + "access", +] + +[[rules]] +description = "Alibaba AccessKey ID" +id = "alibaba-access-secret-in-config" +regex = '''(?i)((access).?secret\s*=\s*.+)''' +keywords = [ + "access", + "secret", +] + +[[rules]] +description = "Alibaba AccessKey ID" +id = "alibaba-access-key-id-in-config" +regex = '''(?i)((access).?key.?id\s*=\s*.+)''' +keywords = [ + "access", +] + +[rules.allowlist] +paths = [ + '''python/vineyard/drivers/io/tests/test_open.py''', + '''python/vineyard/drivers/io/tests/test_serialize.py''', +] + +[[rules]] +description = "Alibaba AccessKey ID" +id = "alibaba-access-key-secret-in-config" +regex = '''(?i)((access).?key.?secret\s*=\s*.+)''' +keywords = [ + "access", + "secret", +] + +[rules.allowlist] +paths = [ + '''python/vineyard/drivers/io/tests/test_open.py''', + '''python/vineyard/drivers/io/tests/test_serialize.py''', +] + +[[rules]] +description = "Alibaba AccessKey ID" +id = "alibaba-secret-access-key-in-config" +regex = '''(?i)((secret).?access.?key\s*=\s*.+)''' +keywords = [ + "access", + "secret", +] + +[allowlist] +paths = [ + '''build''', + '''docs/_build''', + '''docs/_templates/footer.html''', + '''thirdparty''', + '''modules/graph/thirdparty''', +] + diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..657d0c7b52 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: + - repo: https://github.com/zricethezav/gitleaks + rev: v8.15.0 + hooks: + - id: gitleaks + args: + - '--verbose' diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000000..4b3b599d40 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,614 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist=numpy, + vineyard + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=yes + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=abstract-method, + apply-builtin, + attribute-defined-outside-init, + backtick, + bad-continuation, + bad-inline-option, + bad-option-value, + bad-python3-import, + bare-except, + basestring-builtin, + buffer-builtin, + cmp-builtin, + cmp-method, + coerce-builtin, + coerce-method, + comprehension-escape, + consider-using-f-string, + delslice-method, + deprecated-itertools-function, + deprecated-operator-function, + deprecated-pragma, + deprecated-str-translate-call, + deprecated-string-function, + deprecated-sys-function, + deprecated-types-field, + deprecated-urllib-function, + dict-items-not-iterating, + dict-iter-method, + dict-keys-not-iterating, + dict-values-not-iterating, + dict-view-method, + div-method, + duplicate-code, + eq-without-hash, + exception-escape, + exception-message-attribute, + execfile-builtin, + file-builtin, + file-ignored, + filter-builtin-not-iterating, + fixme, + getslice-method, + global-statement, + hex-method, + idiv-method, + import-star-module-level, + indexing-exception, + input-builtin, + intern-builtin, + invalid-name, + invalid-str-codec, + locally-disabled, + long-builtin, + long-suffix, + map-builtin-not-iterating, + metaclass-assignment, + missing-class-docstring, + missing-function-docstring, + missing-docstring, + missing-module-docstring, + next-method-called, + next-method-defined, + no-absolute-import, + no-else-continue, + no-else-raise, + no-else-return, + no-self-use, + non-ascii-bytes-literal, + nonzero-method, + oct-method, + old-division, + old-ne-operator, + old-octal-literal, + old-raise-syntax, + parameter-unpacking, + print-statement, + protected-access, + raising-string, + range-builtin-not-iterating, + raw-checker-failed, + raw_input-builtin, + rdiv-method, + redefined-builtin, + redefined-outer-name, + reduce-builtin, + reload-builtin, + round-builtin, + setslice-method, + standarderror-builtin, + suppressed-message, + sys-max-int, + too-few-public-methods, + too-many-ancestors, + too-many-arguments, + too-many-branches, + too-many-locals, + unichr-builtin, + unicode-builtin, + unnecessary-comprehension, + unpacking-in-except, + unrecognized-option, + unsubscriptable-object, + use-dict-literal, + use-list-literal, + use-symbolic-message-instead, + useless-else-on-loop, + useless-option-value, + useless-suppression, + using-cmp-argument, + xrange-builtin, + xreadlines-attribute, + zip-builtin-not-iterating, + django-not-configured + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[LOGGING] + +# Format style used to check logging format string. `old` means using % +# formatting, `new` is for `{}` formatting,and `fstr` is for f-strings. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=128 + +# Maximum number of lines in a module. +max-module-lines=4294967296 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[STRING] + +# This flag controls whether the implicit-str-concat-in-sequence should +# generate a warning on implicit string concatenation in sequences defined over +# several lines. +check-str-concat-over-line-jumps=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/.stoat/config.yaml b/.stoat/config.yaml new file mode 100644 index 0000000000..02f7dd8e6d --- /dev/null +++ b/.stoat/config.yaml @@ -0,0 +1,80 @@ +--- +version: 1 +enabled: true +plugins: + static_hosting: + e2e-tests-airflow-integration-logs: + metadata: + name: "airflow-integration-logs" + path: e2e-tests-airflow-integration-logs + file_viewer: true + e2e-tests-assembly-local-logs: + metadata: + name: "assembly-local-logs" + path: e2e-tests-assembly-local-logs + file_viewer: true + e2e-tests-assembly-distributed-logs: + metadata: + name: "assembly-distributed-logs" + path: e2e-tests-assembly-distributed-logs + file_viewer: true + e2e-tests-autogenerated-helm-chart-logs: + metadata: + name: "autogenerated-helm-chart-logs" + path: e2e-tests-autogenerated-helm-chart-logs + file_viewer: true + e2e-tests-failover-logs: + metadata: + name: "failover-logs" + path: e2e-tests-failover-logs + file_viewer: true + e2e-tests-repartition-dask-logs: + metadata: + name: "repartition-dask-logs" + path: e2e-tests-repartition-dask-logs + file_viewer: true + e2e-tests-schedule-workflow-logs: + metadata: + name: "schedule-workflow-logs" + path: e2e-tests-schedule-workflow-logs + file_viewer: true + e2e-tests-schedule-workflow-without-crd-logs: + metadata: + name: "schedule-workflow-without-crd-logs" + path: e2e-tests-schedule-workflow-without-crd-logs + file_viewer: true + e2e-tests-serialize-logs: + metadata: + name: "serialize-logs" + path: e2e-tests-serialize-logs + file_viewer: true + e2e-tests-sidecar-logs: + metadata: + name: "sidecar-logs" + path: e2e-tests-sidecar-logs + file_viewer: true + e2e-tests-spill-logs: + metadata: + name: "spill-logs" + path: e2e-tests-spill-logs + file_viewer: true + e2e-tests-workflow-logs: + metadata: + name: "workflow-logs" + path: e2e-tests-workflow-logs + file_viewer: true + e2e-tests-vineyardctl-logs: + metadata: + name: "vineyardctl-logs" + path: e2e-tests-vineyardctl-logs + file_viewer: true + e2e-tests-deploy-raw-backup-and-recover-logs: + metadata: + name: "deploy-raw-backup-and-recover-logs" + path: e2e-tests-deploy-raw-backup-and-recover-logs + file_viewer: true + e2e-tests-schedule-workload-logs: + metadata: + name: "schedule-workload-logs" + path: e2e-tests-schedule-workload-logs + file_viewer: true \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 0000000000..f14607a400 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +v6d.io \ No newline at end of file diff --git a/_images/dask-tf.jpg b/_images/dask-tf.jpg new file mode 100644 index 0000000000..e33dddd526 Binary files /dev/null and b/_images/dask-tf.jpg differ diff --git a/_images/data_sharing_with_deployment.jpg b/_images/data_sharing_with_deployment.jpg new file mode 100644 index 0000000000..7053b4875e Binary files /dev/null and b/_images/data_sharing_with_deployment.jpg differ diff --git a/_images/data_sharing_with_sidecar.jpg b/_images/data_sharing_with_sidecar.jpg new file mode 100644 index 0000000000..99f6b909ac Binary files /dev/null and b/_images/data_sharing_with_sidecar.jpg differ diff --git a/_images/fraud-detection-job.jpg b/_images/fraud-detection-job.jpg new file mode 100644 index 0000000000..34005ee5a7 Binary files /dev/null and b/_images/fraud-detection-job.jpg differ diff --git a/_images/kubeflow_create_run.png b/_images/kubeflow_create_run.png new file mode 100644 index 0000000000..84ac8dd621 Binary files /dev/null and b/_images/kubeflow_create_run.png differ diff --git a/_images/kubeflow_upload_pipeline.png b/_images/kubeflow_upload_pipeline.png new file mode 100644 index 0000000000..bd9321fa06 Binary files /dev/null and b/_images/kubeflow_upload_pipeline.png differ diff --git a/_images/vineyard-logo-rect.png b/_images/vineyard-logo-rect.png new file mode 100644 index 0000000000..2c3ca4dae7 Binary files /dev/null and b/_images/vineyard-logo-rect.png differ diff --git a/_images/vineyard_arch.jpg b/_images/vineyard_arch.jpg new file mode 100644 index 0000000000..813ab5d29e Binary files /dev/null and b/_images/vineyard_arch.jpg differ diff --git a/_images/vineyard_composable.jpg b/_images/vineyard_composable.jpg new file mode 100644 index 0000000000..f4541b655d Binary files /dev/null and b/_images/vineyard_composable.jpg differ diff --git a/_images/vineyard_distributed_tensor.jpg b/_images/vineyard_distributed_tensor.jpg new file mode 100644 index 0000000000..2b0ce68980 Binary files /dev/null and b/_images/vineyard_distributed_tensor.jpg differ diff --git a/_images/vineyard_operator_arch.png b/_images/vineyard_operator_arch.png new file mode 100644 index 0000000000..68283ed017 Binary files /dev/null and b/_images/vineyard_operator_arch.png differ diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 0000000000..2745f72fc0 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,451 @@ + + + + + + + + Overview: module code - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+ +
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/vineyard.html b/_modules/vineyard.html new file mode 100644 index 0000000000..3dfdbb70e4 --- /dev/null +++ b/_modules/vineyard.html @@ -0,0 +1,980 @@ + + + + + + + + vineyard - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for vineyard

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020-2023 Alibaba Group Holding Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import contextlib
+import logging
+import os
+import sys
+from typing import Any
+from typing import Dict
+from typing import Generator
+from typing import Optional
+from typing import Tuple
+from typing import Union
+
+from .version import __version__
+
+__doc__ = """
+Vineyard - an in-memory immutable data manager. (Project under CNCF)
+====================================================================
+
+Vineyard (v6d) is an in-memory immutable data manager that provides
+out-of-the-box high-level abstraction and zero-copy in-memory
+sharing for distributed data in big data tasks, such as graph analytics
+(e.g., GraphScope), numerical computing (e.g., Mars), and machine learning.
+"""
+
+# pylint: disable=import-outside-toplevel,wrong-import-position
+
+logger = logging.getLogger('vineyard')
+
+
+@contextlib.contextmanager
+def envvars(
+    key: Union[str, Dict[str, str]],
+    value: Union[str, None] = None,
+    append: bool = False,
+) -> Generator[os._Environ, Any, Any]:
+    """Create a context with specified environment variables set.
+
+    It is useful for setting the :code`VINEYARD_IPC_SOCKET` environment
+    variable to obtain a proper default vineyard client.
+
+    This context macro can be used as
+
+    .. code:: python
+
+        with environment('KEY'):
+            # env :code:`KEY` will be set to None.
+
+        with environment('KEY', 'value'):
+            # env :code:`KEY` will be set as :code:`value`.
+
+        with environment({'KEY1': None, 'KEY2': 'value2'}):
+            # env :code:`KEY1` will be set as None and :code:`KEY2` will
+            # be set as :code:`value2`.
+    """
+    if isinstance(key, str):
+        if value is None:
+            items = dict()
+        else:
+            items: Dict[str, str] = {key: value}
+    else:
+        items: Dict[str, str] = key
+    original_items = dict()
+    for k, v in items.items():
+        original_items[k] = os.environ.get(k, None)
+        if append and original_items[k] is not None:
+            os.environ[k] = original_items[k] + ':' + v
+        else:
+            os.environ[k] = v
+
+    yield os.environ
+
+    for k, v in original_items.items():
+        if v is not None:
+            os.environ[k] = v
+        else:
+            del os.environ[k]
+
+
+def _init_global_context():
+    import os as _dl_flags  # pylint: disable=reimported
+
+    if sys.platform == 'linux':
+        registry = os.path.join(
+            os.path.dirname(__file__), 'libvineyard_internal_registry.so'
+        )
+    elif sys.platform == 'darwin':
+        registry = os.path.join(
+            os.path.dirname(__file__), 'libvineyard_internal_registry.dylib'
+        )
+    else:
+        raise RuntimeError("Unsupported platform: %s" % sys.platform)
+
+    ctx = {'__VINEYARD_INTERNAL_REGISTRY': registry}
+
+    if os.environ.get('VINEYARD_DEVELOP', None) is None:
+        with envvars(ctx):  # n.b., no append
+            from . import _C
+        return
+
+    if not hasattr(_dl_flags, 'RTLD_GLOBAL') or not hasattr(_dl_flags, 'RTLD_LAZY'):
+        try:
+            # next try if DLFCN exists
+            import DLFCN as _dl_flags  # noqa: N811
+        except ImportError:
+            _dl_flags = None
+
+    if _dl_flags is not None:
+        old_flags = sys.getdlopenflags()
+
+        # import the extension module
+        sys.setdlopenflags(_dl_flags.RTLD_GLOBAL | _dl_flags.RTLD_LAZY)
+        with envvars(ctx):  # n.b., no append
+            from . import _C  # noqa: F811
+        # restore
+        sys.setdlopenflags(old_flags)
+
+
+_init_global_context()
+del _init_global_context
+
+
+from . import core
+from . import csi
+from . import data
+from . import deploy
+from . import io
+from . import launcher
+from . import shared_memory
+from ._C import ArrowErrorException
+from ._C import AssertionFailedException
+from ._C import Blob
+from ._C import BlobBuilder
+from ._C import ConnectionErrorException
+from ._C import ConnectionFailedException
+from ._C import EndOfFileException
+from ._C import EtcdErrorException
+from ._C import InstanceStatus
+from ._C import InvalidException
+from ._C import InvalidStreamStateException
+from ._C import IOErrorException
+from ._C import IPCClient
+from ._C import KeyErrorException
+from ._C import MetaTreeInvalidException
+from ._C import MetaTreeLinkInvalidException
+from ._C import MetaTreeNameInvalidException
+from ._C import MetaTreeNameNotExistsException
+from ._C import MetaTreeSubtreeNotExistsException
+from ._C import MetaTreeTypeInvalidException
+from ._C import MetaTreeTypeNotExistsException
+from ._C import NotEnoughMemoryException
+from ._C import NotImplementedException
+from ._C import Object
+from ._C import ObjectBuilder
+from ._C import ObjectExistsException
+from ._C import ObjectID
+from ._C import ObjectMeta
+from ._C import ObjectName
+from ._C import ObjectNotExistsException
+from ._C import ObjectNotSealedException
+from ._C import ObjectSealedException
+from ._C import RemoteBlob
+from ._C import RemoteBlobBuilder
+from ._C import RPCClient
+from ._C import StreamDrainedException
+from ._C import StreamFailedException
+from ._C import TypeErrorException
+from ._C import UnknownErrorException
+from ._C import UserInputErrorException
+from ._C import VineyardServerNotReadyException
+from ._C import _connect
+from ._C import memory_copy
+from .core import Client
+from .core import builder_context
+from .core import default_builder_context
+from .core import default_driver_context
+from .core import default_resolver_context
+from .core import driver_context
+from .core import resolver_context
+from .core.builder import BuilderContext
+from .core.resolver import ResolverContext
+from .data import register_builtin_types
+from .data.graph import Graph
+from .deploy.local import get_current_socket
+from .deploy.local import shutdown
+from .deploy.local import try_init
+
+
+def _init_vineyard_modules():  # noqa: C901
+    """Resolve registered vineyard modules in the following order:
+
+    * /etc/vineyard/config.py
+    * {sys.prefix}/etc/vineyard/config.py
+    * /usr/share/vineyard/01-xxx.py
+    * /usr/local/share/vineyard/01-xxx.py
+    * {sys.prefix}/share/vineyard/02-xxxx.py
+    * $HOME/.vineyard/03-xxxxx.py
+
+    Then import packages like vineyard.drivers.*:
+
+    * vineyard.drivers.io
+    """
+
+    import glob
+    import importlib.util
+    import pkgutil
+    import site
+    import sysconfig
+
+    def _import_module_from_file(filepath):
+        filepath = os.path.expanduser(os.path.expandvars(filepath))
+        if os.path.exists(filepath):
+            try:
+                spec = importlib.util.spec_from_file_location(
+                    "vineyard._contrib", filepath
+                )
+                mod = importlib.util.module_from_spec(spec)
+                spec.loader.exec_module(mod)
+            except Exception:  # pylint: disable=broad-except
+                logger.debug("Failed to load %s", filepath, exc_info=True)
+
+    def _import_module_from_qualified_name(module):
+        try:
+            importlib.import_module(module)
+        except Exception:  # pylint: disable=broad-except
+            logger.debug('Failed to load module %s', module, exc_info=True)
+
+    _import_module_from_file('/etc/vineyard/config.py')
+    _import_module_from_file(os.path.join(sys.prefix, '/etc/vineyard/config.py'))
+    for filepath in glob.glob('/usr/share/vineyard/*-*.py'):
+        _import_module_from_file(filepath)
+    for filepath in glob.glob('/usr/local/share/vineyard/*-*.py'):
+        _import_module_from_file(filepath)
+    for filepath in glob.glob(os.path.join(sys.prefix, '/share/vineyard/*-*.py')):
+        _import_module_from_file(filepath)
+    for filepath in glob.glob(os.path.expanduser('$HOME/.vineyard/*-*.py')):
+        _import_module_from_file(filepath)
+
+    package_sites = set()
+    pkg_sites = site.getsitepackages()
+    if not isinstance(pkg_sites, (list, tuple)):
+        pkg_sites = [pkg_sites]
+    package_sites.update(pkg_sites)
+    pkg_sites = site.getusersitepackages()
+    if not isinstance(pkg_sites, (list, tuple)):
+        pkg_sites = [pkg_sites]
+    package_sites.update(pkg_sites)
+
+    paths = sysconfig.get_paths()
+    if 'purelib' in paths:
+        package_sites.add(paths['purelib'])
+    if 'platlib' in paths:
+        package_sites.add(paths['purelib'])
+
+    # add relative path
+    package_sites.add(os.path.join(os.path.dirname(__file__), '..'))
+
+    # dedup
+    deduped = set()
+    for pkg_site in package_sites:
+        deduped.add(os.path.abspath(pkg_site))
+
+    for pkg_site in deduped:
+        for _, mod, _ in pkgutil.iter_modules(
+            [os.path.join(pkg_site, 'vineyard', 'drivers')]
+        ):
+            _import_module_from_qualified_name('vineyard.drivers.%s' % mod)
+
+
+try:
+    _init_vineyard_modules()
+except Exception:  # pylint: disable=broad-except
+    pass
+del _init_vineyard_modules
+
+
+
[docs]def connect(*args, **kwargs): + """ + Connect to vineyard by specified UNIX-domain socket or TCP endpoint. + + If no arguments are provided and failed to resolve both the environment + variables :code:`VINEYARD_IPC_SOCKET`, :code:`VINEYARD_RPC_ENDPOINT`, + :code:`VINEYARD_CONFIG`, and the default configuration file + :code:`/var/run/vineyard-config.yaml` and + :code:`/var/run/vineyard/vineyard-config.yaml`, it will launch a standalone + vineyardd server in the background and then connect to it. + + The `connect()` method has various overloading: + + .. function:: connect(socket: str, + username: str = None, + password: str = None) -> IPCClient + :noindex: + + Connect to vineyard via UNIX domain socket for IPC service: + + .. code:: python + + client = vineyard.connect('/var/run/vineyard.sock') + + Parameters: + socket: str + UNIX domain socket path to setup an IPC connection. + username: str + Username to login, default to None. + password: str + Password to login, default to None. + + Returns: + IPCClient: The connected IPC client. + + .. function:: connect(host: str, + port: int or str, + username: str = None, + password: str = None) -> RPCClient + :noindex: + + Connect to vineyard via TCP socket. + + Parameters: + host: str + Hostname to connect to. + port: int or str + The TCP that listened by vineyard TCP service. + username: str + Username to login, default to None. + password: str + Password to login, default to None. + + Returns: + RPCClient: The connected RPC client. + + .. function:: connect(endpoint: (str, int or str), + username: str = None, + password: str = None) -> RPCClient + :noindex: + + Connect to vineyard via TCP socket. + + Parameters: + endpoint: tuple(str, int or str) + Endpoint to connect to. The parameter is a tuple, in which the first + element is the host, and the second parameter is the port (can be int + a str). + username: str + Username to login, default to None. + password: str + Password to login, default to None. + + Returns: + RPCClient: The connected RPC client. + + .. function:: connect(username: str = None, + password: str = None) -> IPCClient or RPCClient + :noindex: + + Connect to vineyard via UNIX domain socket or TCP endpoint. This method normally + usually no arguments, and will first tries to resolve IPC socket from the + environment variable `VINEYARD_IPC_SOCKET` and connect to it. If it fails to + establish a connection with vineyard server, the method will tries to resolve + RPC endpoint from the environment variable `VINEYARD_RPC_ENDPOINT`. If both + tries are failed, this method will try to resolve the configuration file that + contains IPC socket and RPC endpoint from the environment variable + `VINEYARD_CONFIG`, and then connect to the vineyard server with the + resolved configuration. + + If above all are failed, this method will raise a :class:`ConnectionFailed` + exception. + + In rare cases, user may be not sure about if the IPC socket or RPC endpoint + is available, i.e., the variable might be :code:`None`. In such cases this + method can accept a `None` as arguments, and do resolution as described above. + + Parameters: + username: str + Username to login, default to None. + password: str + Password to login, default to None. + + Raises: + ConnectionFailed + """ + if ( + not args + and not kwargs + and 'VINEYARD_IPC_SOCKET' not in os.environ + and 'VINEYARD_RPC_ENDPOINT' not in os.environ + and 'VINEYARD_CONFIG' not in os.environ + and not any( + os.path.exists(path) + for path in [ + "/var/run/vineyard-config.yaml", + "/var/run/vineyard/vineyard-config.yaml", + ] + ) + ): + logger.info( + 'No vineyard socket or endpoint is specified, ' + 'try to launch a standalone one.' + ) + try_init() + return Client(*args, **kwargs)
+ + +def put( + value: Any, + builder: Optional[BuilderContext] = None, + persist: bool = False, + name: Optional[str] = None, + **kwargs, +): + """ + Connect the vineyard server by the following Environment Variables: + + VINEYARD_IPC_SOCKET: + UNIX domain socket path to setup an IPC connection. + E.g. /var/run/vineyard.sock + VINEYARD_RPC_ENDPOINT: + TCP endpoint to setup an RPC connection. + E.g. 127.0.0.1:9600 + VINEYARD_CONFIG: + Either be a path to a YAML configuration file or a path to a + directory containing the default config file `vineyard-config.yaml`. + + The configuration file should be like: + + .. code:: yaml + + Vineyard: + IPCSocket: '/path/to/vineyard.sock' + RPCEndpoint: 'hostname1:port1,hostname2:port2,...' + + Then put python value to vineyard. + + .. code:: python + + >>> os.environ['VINEYARD_IPC_SOCKET'] = '/var/run/vineyard.sock' + >>> arr = np.arange(8) + >>> arr_id = vineyard.put(arr) + >>> arr_id + 00002ec13bc81226 + + Parameters: + value: + The python value that will be put to vineyard. Supported python value + types are decided by modules that registered to vineyard. By default, + python value can be put to vineyard after serialized as a bytes buffer + using pickle. + builder: optional + When putting python value to vineyard, an optional *builder* can be + specified to tell vineyard how to construct the corresponding vineyard + :class:`Object`. If not specified, the default builder context will be + used to select a proper builder. + persist: bool, optional + If true, persist the object after creation. + name: str, optional + If given, the name will be automatically associated with the resulted + object. Note that only take effect when the object is persisted. + kw: + User-specific argument that will be passed to the builder. + + Returns: + ObjectID: The result object id will be returned. + """ + + client = connect() + return client.put(value, builder, persist, name, **kwargs) + + +def get( + object_id: Optional[ObjectID] = None, + name: Optional[str] = None, + resolver: Optional[ResolverContext] = None, + fetch: bool = False, + **kwargs, +): + """ + Connect the vineyard server by the following Environment Variables: + + VINEYARD_IPC_SOCKET: + UNIX domain socket path to setup an IPC connection. + E.g. /var/run/vineyard.sock + VINEYARD_RPC_ENDPOINT: + TCP endpoint to setup an RPC connection. + E.g. 127.0.0.1:9600 + VINEYARD_CONFIG: + Either be a path to a YAML configuration file or a path to a + directory containing the default config file `vineyard-config.yaml`. + + The configuration file should be like: + + .. code:: yaml + + Vineyard: + IPCSocket: '/path/to/vineyard.sock' + RPCEndpoint: 'hostname1:port1,hostname2:port2,...' + + Then get vineyard object as python value. + + .. code:: python + + >>> os.environ['VINEYARD_IPC_SOCKET'] = '/var/run/vineyard.sock' + >>> arr_id = vineyard.ObjectID('00002ec13bc81226') + >>> arr = vineyard.get(arr_id) + >>> arr + array([0, 1, 2, 3, 4, 5, 6, 7]) + + Parameters: + object_id: ObjectID + The object id that will be obtained from vineyard. + name: ObjectID + The object name that will be obtained from vineyard, ignored if + ``object_id`` is not None. + resolver: + When retrieving vineyard object, an optional *resolver* can be specified. + If no resolver given, the default resolver context will be used. + fetch: + Whether to trigger a migration when the target object is located on + remote instances. + kw: + User-specific argument that will be passed to the builder. + + Returns: + A python object that return by the resolver, by resolving an vineyard object. + """ + + client = connect() + return client.get(object_id, name, resolver, fetch, **kwargs) +
+
+
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/vineyard/core/builder.html b/_modules/vineyard/core/builder.html new file mode 100644 index 0000000000..21715cc793 --- /dev/null +++ b/_modules/vineyard/core/builder.html @@ -0,0 +1,663 @@ + + + + + + + + vineyard.core.builder - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for vineyard.core.builder

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020-2023 Alibaba Group Holding Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import contextlib
+import copy
+import inspect
+import threading
+import warnings
+from typing import Any
+from typing import Callable
+from typing import Dict
+from typing import Generator
+from typing import Optional
+
+from vineyard._C import IPCClient
+from vineyard._C import Object
+from vineyard._C import ObjectID
+from vineyard._C import ObjectMeta
+from vineyard._C import RPCClient
+
+
+
[docs]class BuilderContext: + def __init__(self, parent_context: Optional["BuilderContext"] = None): + self._factory = dict() + if parent_context is not None: + self._parent_context = parent_context + else: + self._parent_context = self + + def __str__(self) -> str: + return str(self._factory) + + @property + def parent_context(self) -> "BuilderContext": + return self._parent_context + +
[docs] def register(self, type_id: type, builder: Callable): + """Register a Python type to the builder context. + + Parameters + ---------- + type_id: Python type + Like `int`, or `numpy.ndarray` + + builder: callable, e.g., a method, callable object + A builder translates a python object to vineyard, it accepts a Python + value as parameter, and returns an vineyard object as result. + """ + self._factory[type_id] = builder
+ +
[docs] def run(self, client, value, **kw): + """Follows the MRO to find the proper builder for given python value. + + Here "Follows the MRO" implies: + + - If the type of python value has been found in the context, the registered + builder will be used. + + - If not, it follows the MRO chain from down to top to find a registered + Python type and used the associated builder. + + - When the traversal reaches the :code:`object` type, since there's a default + builder that serialization the python value, the parameter will be serialized + and be put into a blob. + """ + + # if the python value comes from a vineyard object, we choose to just reuse it. + + # N.B.: don't do that. + # + # we still construct meta, and optimize the duplicate copy in blob builder + # + # base = getattr(value, '__vineyard_ref', None) + # if base: + # return base.meta + + for ty in type(value).__mro__: + if ty in self._factory: + builder_func_sig = inspect.getfullargspec(self._factory[ty]) + if ( + 'builder' in builder_func_sig.args + or builder_func_sig.varkw is not None + ): + kw['builder'] = self + return self._factory[ty](client, value, **kw) + raise RuntimeError('Unknown type to build as vineyard object')
+ + def __call__(self, client, value, **kw): + return self.run(client, value, **kw) + + def extend(self, builders=None): + builder = BuilderContext(self) + builder._factory = copy.copy( # pylint: disable=unused-private-member + self._factory + ) + if builders: + builder._factory.update(builders) + return builder
+ + +default_builder_context = BuilderContext() + +_builder_context_local = threading.local() +_builder_context_local.default_builder = default_builder_context + + +
[docs]def get_current_builders() -> BuilderContext: + '''Obtain the current builder context.''' + default_builder = getattr(_builder_context_local, 'default_builder', None) + if not default_builder: + default_builder = default_builder_context.extend() + return default_builder
+ + +
[docs]@contextlib.contextmanager +def builder_context( + builders: Optional[Dict[type, Callable]] = None, + base: Optional[BuilderContext] = None, +) -> Generator[BuilderContext, Any, Any]: + """Open a new context for register builders, without populating outside global + environment. + + See Also: + resolver_context + driver_context + """ + current_builder = get_current_builders() + try: + builders = builders or dict() + base = base or current_builder + local_builder = base.extend(builders) + _builder_context_local.default_builder = local_builder + yield local_builder + finally: + _builder_context_local.default_builder = current_builder
+ + +def put( + client, + value: Any, + builder: Optional[BuilderContext] = None, + persist: bool = False, + name: Optional[str] = None, + **kwargs +): + """Put python value to vineyard. + + .. code:: python + + >>> arr = np.arange(8) + >>> arr_id = client.put(arr) + >>> arr_id + 00002ec13bc81226 + + Parameters: + client: IPCClient or RPCClient + The vineyard client to use. + value: + The python value that will be put to vineyard. Supported python value + types are decided by modules that registered to vineyard. By default, + python value can be put to vineyard after serialized as a bytes buffer + using pickle. + builder: optional + When putting python value to vineyard, an optional *builder* can be + specified to tell vineyard how to construct the corresponding vineyard + :class:`Object`. If not specified, the default builder context will be + used to select a proper builder. + persist: bool, optional + If true, persist the object after creation. + name: str, optional + If given, the name will be automatically associated with the resulted + object. Note that only take effect when the object is persisted. + kw: + User-specific argument that will be passed to the builder. + + Returns: + ObjectID: The result object id will be returned. + """ + if builder is not None: + return builder(client, value, **kwargs) + + meta = get_current_builders().run(client, value, **kwargs) + + # the builders is expected to return an :class:`ObjectMeta`, or an + # :class:`Object` (in the `bytes_builder` and `memoryview` builder). + if isinstance(meta, (ObjectMeta, Object)): + r = meta.id + else: + r = meta # None or ObjectID + + if isinstance(r, ObjectID): + if persist: + client.persist(r) + if name is not None: + if persist: + client.put_name(r, name) + else: + warnings.warn( + "The object is not persisted, the name won't be associated.", + UserWarning, + ) + return r + + +setattr(IPCClient, 'put', put) +setattr(RPCClient, 'put', put) + +__all__ = [ + 'default_builder_context', + 'builder_context', + 'get_current_builders', +] +
+
+
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/vineyard/core/client.html b/_modules/vineyard/core/client.html new file mode 100644 index 0000000000..08fc3b06e3 --- /dev/null +++ b/_modules/vineyard/core/client.html @@ -0,0 +1,1265 @@ + + + + + + + + vineyard.core.client - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for vineyard.core.client

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020-2023 Alibaba Group Holding Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import contextlib
+import os
+import warnings
+from concurrent.futures import ThreadPoolExecutor
+from concurrent.futures import as_completed
+from typing import Any
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import Tuple
+from typing import Union
+
+from vineyard import envvars
+from vineyard._C import Blob
+from vineyard._C import BlobBuilder
+from vineyard._C import IPCClient
+from vineyard._C import NotEnoughMemoryException
+from vineyard._C import Object
+from vineyard._C import ObjectID
+from vineyard._C import ObjectMeta
+from vineyard._C import RemoteBlob
+from vineyard._C import RemoteBlobBuilder
+from vineyard._C import RPCClient
+from vineyard._C import VineyardException
+from vineyard._C import _connect
+from vineyard.core.builder import BuilderContext
+from vineyard.core.builder import put
+from vineyard.core.resolver import ResolverContext
+from vineyard.core.resolver import get
+
+
+def _apply_docstring(func):
+    def _apply(fn):
+        fn.__doc__ = func.__doc__
+        return fn
+
+    return _apply
+
+
+def _parse_configuration(config) -> Tuple[Optional[str], Optional[str]]:
+    '''Parse vineyard IPC socket and RPC endpoints from configuration.
+
+    Parameters:
+        config: Path to a YAML configuration file or a directory containing
+                the default config file `vineyard-config.yaml`. If you define
+                the directory of vineyard config, the
+                `/directory/vineyard-config.yaml` and
+                `/directory/vineyard/vineyard-config.yaml` will be parsed.
+
+    Returns:
+        (socket, endpoints): IPC socket path and RPC endpoints.
+    '''
+    if not config:
+        return None, None
+
+    try:
+        import yaml  # pylint: disable=import-outside-toplevel
+    except ImportError:
+        return None, None
+
+    if os.path.isdir(config):
+        config_options = ['vineyard-config.yaml', 'vineyard/vineyard-config.yaml']
+        for config_option in config_options:
+            config_path = os.path.join(config, config_option)
+            if os.path.isfile(config_path):
+                config = config_path
+                break
+    if not os.path.isfile(config):
+        return None, None
+
+    try:
+        with open(config, 'r', encoding='utf-8') as f:
+            vineyard_config = yaml.safe_load(f).get('Vineyard', {})
+    except:  # noqa: E722, pylint: disable=bare-except
+        return None, None
+
+    ipc_socket = vineyard_config.get('IPCSocket', None)
+    rpc_endpoint = vineyard_config.get('RPCEndpoint', None)
+
+    if ipc_socket:
+        if not os.path.isabs(ipc_socket):
+            base_dir = os.path.dirname(config) if os.path.isfile(config) else config
+            ipc_socket = os.path.join(base_dir, ipc_socket)
+
+        if not os.path.exists(ipc_socket):
+            ipc_socket = None
+
+    return ipc_socket, rpc_endpoint
+
+
+def _is_blob(object_id: ObjectID):
+    """_is_blob_
+
+    Args:
+        object_id (ObjectID): ObjectID to check if it is a blob
+
+    Returns:
+        bool: True if the object_id is a blob, False otherwise
+    """
+    return int(object_id) & 0x8000000000000000
+
+
+def _traverse_blobs(meta: ObjectMeta, blobs=None):
+    """_traverse_blobs_
+
+    Recursively traverses ObjectMeta to find and accumulate blob IDs by instance_id.
+
+    Args:
+        meta (ObjectMeta): ObjectMeta to traverse for blobs.
+        blobs (dict, optional): Accumulator for blobs organized by instance_id.
+
+    Returns:
+        dict: A dictionary of blobs organized by instance_id.
+    """
+
+    if blobs is None:
+        blobs = {}
+
+    def add_blob(instance_id, blob_id):
+        if instance_id not in blobs:
+            blobs[instance_id] = []
+        blobs[instance_id].append(blob_id)
+
+    if _is_blob(meta.id):
+        add_blob(meta.instance_id, meta.id)
+    else:
+        for _, v in meta.items():
+            if isinstance(v, ObjectMeta):
+                if _is_blob(v.id):
+                    add_blob(v.instance_id, v.id)
+                else:
+                    _traverse_blobs(v, blobs)
+
+    return blobs
+
+
+
[docs]class Client: + """Client is responsible for managing IPC and RPC clients for Vineyard + and provides a high-level interface to fetch objects from the Vineyard cluster. + """ + + def __init__( + self, + socket: str = None, + port: Union[int, str] = None, + # move host after port to make sure unnamed (host, port) works + host: str = None, + endpoint: Tuple[str, Union[str, int]] = None, + session: int = None, + username: str = None, + password: str = None, + config: str = None, + ): + """Connects to the vineyard IPC socket and RPC socket. + + - For the IPC Client, the argument `socket` takes precedence over the + environment variable `VINEYARD_IPC_SOCKET`, which in turn takes precedence + over the `IPCSocket` field in the config file." + - For the RPC Client, the argument `endpoint` takes precedence over the + argument `host` and `port`, which in turn takes precedence over the + environment variable `VINEYARD_RPC_ENDPOINT`, which further takes precedence + over the `RPCEndpoint` field in the config file. + + The `connect()` API can be used in following ways: + + - `connect()` without any arguments, which will try to connect to the vineyard + by resolving endpoints from the environment variables. + - `connect('/path/to/vineyard.sock')`, which will try to establish an IPC + connection. + - `connect('hostname:port')`, which will try to establish an RPC connection. + - `connect('hostname', port)`, which will try to establish an RPC connection. + - `connect(endpoint=('hostname', port))`, which will try to establish an RPC + connection. + - `connect(config='/path/to/vineyard-config.yaml')`, which will try to + resolve the IPC socket and RPC endpoints from the configuration file. + + Parameters: + socket: Optional, the path to the IPC socket, or RPC endpoints of format + `host:port`. + port: Optional, the port of the RPC endpoint. + host: Optional, the host of the RPC endpoint. + endpoint: Optional, the RPC endpoint of format `host:port`. + session: Optional, the session id to connect. + username: Optional, the required username of vineyardd when authentication + is enabled. + password: Optional, the required password of vineyardd when authentication + is enabled. + config: Optional, can either be a path to a YAML configuration file or + a path to a directory containing the default config file + `vineyard-config.yaml`. Also, the environment variable + `VINEYARD_CONFIG` can be used to specify the + path to the configuration file. If not defined, the default + config file `/var/run/vineyard-config.yaml` or + `/var/run/vineyard/vineyard-config.yaml` + + The content of the configuration file should has the following content: + + .. code:: yaml + + Vineyard: + IPCSocket: '/path/to/vineyard.sock' + RPCEndpoint: 'hostname1:port1,hostname2:port2,...' + """ + self._ipc_client: IPCClient = None + self._rpc_client: RPCClient = None + + kwargs = {} + if session is not None: + kwargs['session'] = session + if username is not None: + kwargs['username'] = username + if password is not None: + kwargs['password'] = password + + if socket is not None and port is not None and host is None: + socket, host = None, socket + + hosts, ports = [], [] + if not socket: + socket = os.getenv('VINEYARD_IPC_SOCKET', None) + if not endpoint and not (host and port): + endpoint = os.getenv('VINEYARD_RPC_ENDPOINT', None) + if not config: + config = os.getenv('VINEYARD_CONFIG', '/var/run') + if endpoint: + if not isinstance(endpoint, (tuple, list)): + for ep in endpoint.split(','): + h, p = [e.strip() for e in ep.split(':')] + hosts.append(h) + ports.append(p) + else: + h, p = endpoint + hosts = [h] + ports = [p] + + if host and port: + hosts.append(host) + ports.append(port) + + if config and ((not socket) or (not (hosts and ports))): + ipc_socket, rpc_endpoint = _parse_configuration(config) + if ipc_socket and not socket: + socket = ipc_socket + if rpc_endpoint and not (hosts and ports): + for ep in rpc_endpoint.split(','): + h, p = [e.strip() for e in ep.split(':')] + hosts.append(h) + ports.append(p) + + if socket: + self._ipc_client = _connect(socket, **kwargs) + for host, port in zip(hosts, ports): + try: + self._rpc_client = _connect(host, port, **kwargs) + break + except VineyardException: + continue + + self._spread = False + self._compression = True + if self._ipc_client is None and self._rpc_client is None: + raise ConnectionError( + "Failed to connect to vineyard via both IPC and RPC connection. " + "Arguments, environment variables `VINEYARD_IPC_SOCKET` " + "and `VINEYARD_RPC_ENDPOINT`, as well as the configuration file, " + "are all unavailable." + ) + + @property + def compression(self) -> bool: + '''Whether the compression is enabled for underlying RPC client.''' + if self._rpc_client: + return self._rpc_client.compression + return self._compression + + @compression.setter + def compression(self, value: bool = True): + if self._rpc_client: + self._rpc_client.compression = value + self._compression = value + + @property + def spread(self) -> bool: + '''Whether the spread is enabled for underlying RPC client.''' + return self._spread + + @spread.setter + def spread(self, value: bool = False): + self._spread = value + + @property + def ipc_client(self) -> IPCClient: + assert self._ipc_client is not None, "IPC client is not available." + return self._ipc_client + + @property + def rpc_client(self) -> RPCClient: + assert self._rpc_client is not None, "RPC client is not available." + return self._rpc_client + + def has_ipc_client(self): + return self._ipc_client is not None + + def has_rpc_client(self): + return self._rpc_client is not None + + def default_client(self) -> Union[IPCClient, RPCClient]: + return self._ipc_client if self._ipc_client else self._rpc_client + + # The following functions are wrappers of the corresponding functions in the + # ClientBase class. + +
[docs] @_apply_docstring(IPCClient.create_metadata) + def create_metadata( + self, metadata: Union[ObjectMeta, List[ObjectMeta]], instance_id: int = None + ) -> Union[ObjectMeta, List[ObjectMeta]]: + if instance_id is not None: + return self.default_client().create_metadata(metadata, instance_id) + return self.default_client().create_metadata(metadata)
+ +
[docs] @_apply_docstring(IPCClient.delete) + def delete( + self, + object: Union[ObjectID, Object, ObjectMeta, List[ObjectID]], + force: bool = False, + deep: bool = True, + memory_trim: bool = False, + ) -> None: + return self.default_client().delete(object, force, deep, memory_trim)
+ + @_apply_docstring(IPCClient.create_stream) + def create_stream(self, id: ObjectID) -> None: + return self.default_client().create_stream(id) + + @_apply_docstring(IPCClient.open_stream) + def open_stream(self, id: ObjectID, mode: str) -> None: + return self.default_client().open_stream(id, mode) + + @_apply_docstring(IPCClient.push_chunk) + def push_chunk(self, stream_id: ObjectID, chunk: ObjectID) -> None: + return self.default_client().push_chunk(stream_id, chunk) + + @_apply_docstring(IPCClient.next_chunk_id) + def next_chunk_id(self, stream_id: ObjectID) -> ObjectID: + return self.default_client().next_chunk_id(stream_id) + + @_apply_docstring(IPCClient.next_chunk_meta) + def next_chunk_meta(self, stream_id: ObjectID) -> ObjectMeta: + return self.default_client().next_chunk_meta(stream_id) + + @_apply_docstring(IPCClient.next_chunk) + def next_chunk(self, stream_id: ObjectID) -> Object: + return self.default_client().next_chunk(stream_id) + + @_apply_docstring(IPCClient.stop_stream) + def stop_stream(self, stream_id: ObjectID, failed: bool) -> None: + return self.default_client().stop_stream(stream_id, failed) + + @_apply_docstring(IPCClient.drop_stream) + def drop_stream(self, stream_id: ObjectID) -> None: + return self.default_client().drop_stream(stream_id) + +
[docs] @_apply_docstring(IPCClient.persist) + def persist(self, object: Union[ObjectID, Object, ObjectMeta]) -> None: + return self.default_client().persist(object)
+ +
[docs] @_apply_docstring(IPCClient.exists) + def exists(self, object: ObjectID) -> bool: + return self.default_client().exists(object)
+ +
[docs] @_apply_docstring(IPCClient.shallow_copy) + def shallow_copy( + self, object_id: ObjectID, extra_metadata: dict = None + ) -> ObjectID: + if extra_metadata: + return self.default_client().shallow_copy(object_id, extra_metadata) + return self.default_client().shallow_copy(object_id)
+ +
[docs] @_apply_docstring(IPCClient.list_names) + def list_names( + self, pattern: str, regex: bool = False, limit: int = 5 + ) -> List[str]: + return self.default_client().list_names(pattern, regex, limit)
+ +
[docs] @_apply_docstring(IPCClient.put_name) + def put_name(self, object: Union[Object, ObjectMeta, ObjectID], name: str) -> None: + return self.default_client().put_name(object, name)
+ +
[docs] @_apply_docstring(IPCClient.get_name) + def get_name(self, name: str, wait: bool = False) -> ObjectID: + return self.default_client().get_name(name, wait)
+ +
[docs] @_apply_docstring(IPCClient.drop_name) + def drop_name(self, name: str) -> None: + return self.default_client().drop_name(name)
+ +
[docs] @_apply_docstring(IPCClient.sync_meta) + def sync_meta(self) -> None: + return self.default_client().sync_meta()
+ + @_apply_docstring(IPCClient.migrate) + def migrate(self, object_id: ObjectID) -> ObjectID: + return self.default_client().migrate(object_id) + +
[docs] @_apply_docstring(IPCClient.clear) + def clear(self) -> None: + return self.default_client().clear()
+ +
[docs] @_apply_docstring(IPCClient.memory_trim) + def memory_trim(self) -> bool: + return self.default_client().memory_trim()
+ + @_apply_docstring(IPCClient.label) + def label( + self, + object_id: ObjectID, + key_or_labels: Union[str, Dict[str, str]], + value: str = None, + ) -> None: + if isinstance(key_or_labels, dict) and value is None: + return self.default_client().label(object_id, key_or_labels) + else: + return self.default_client().label(object_id, key_or_labels, value) + + @_apply_docstring(IPCClient.evict) + def evict(self, objects: List[ObjectID]) -> None: + return self.default_client().evict(objects) + + @_apply_docstring(IPCClient.load) + def load(self, objects: List[ObjectID], pin: bool = False) -> None: + return self.default_client().load(objects, pin) + + @_apply_docstring(IPCClient.unpin) + def unpin(self, objects: List[ObjectID]) -> None: + return self.default_client().unpin(objects) + +
[docs] @_apply_docstring(IPCClient.reset) + def reset(self) -> None: + if self._ipc_client: + self._ipc_client.reset() + if self._rpc_client: + self._rpc_client.reset()
+ + @property + @_apply_docstring(IPCClient.connected) + def connected(self): + return self.default_client().connected + + @property + @_apply_docstring(IPCClient.instance_id) + def instance_id(self): + return self.default_client().instance_id + + @property + @_apply_docstring(IPCClient.meta) + def meta(self): + return self.default_client().meta + + @property + @_apply_docstring(IPCClient.status) + def status(self): + return self.default_client().status + + @_apply_docstring(IPCClient.debug) + def debug(self, debug: dict): + return self.default_client().debug(debug) + + @property + @_apply_docstring(IPCClient.ipc_socket) + def ipc_socket(self): + return self.default_client().ipc_socket + + @property + @_apply_docstring(IPCClient.rpc_endpoint) + def rpc_endpoint(self): + if self._rpc_client: + return self._rpc_client.rpc_endpoint + return self.default_client().rpc_endpoint + + @property + @_apply_docstring(IPCClient.is_ipc) + def is_ipc(self): + return self.default_client().is_ipc + + @property + @_apply_docstring(IPCClient.is_rpc) + def is_rpc(self): + return self.default_client().is_rpc + + @property + @_apply_docstring(IPCClient.version) + def version(self): + return self.default_client().version + + # The following functions are wrappers of the corresponding functions in the + # IPCClient and RPCClient classes. + +
[docs] @_apply_docstring(IPCClient.create_blob) + def create_blob( + self, size: Union[int, List[int]] + ) -> Union[BlobBuilder, List[BlobBuilder]]: + return self.ipc_client.create_blob(size)
+ +
[docs] @_apply_docstring(IPCClient.create_empty_blob) + def create_empty_blob(self) -> BlobBuilder: + return self.ipc_client.create_empty_blob()
+ +
[docs] @_apply_docstring(IPCClient.get_blob) + def get_blob(self, object_id: ObjectID, unsafe: bool = False) -> Blob: + return self.ipc_client.get_blob(object_id, unsafe)
+ +
[docs] @_apply_docstring(IPCClient.get_blobs) + def get_blobs(self, object_ids: List[ObjectID], unsafe: bool = False) -> List[Blob]: + return self.ipc_client.get_blobs(object_ids, unsafe)
+ +
[docs] @_apply_docstring(RPCClient.create_remote_blob) + def create_remote_blob( + self, blob_builder: Union[RemoteBlobBuilder, List[RemoteBlobBuilder]] + ) -> Union[ObjectMeta, List[ObjectMeta]]: + return self.rpc_client.create_remote_blob(blob_builder)
+ +
[docs] @_apply_docstring(RPCClient.get_remote_blob) + def get_remote_blob(self, object_id: ObjectID, unsafe: bool = False) -> RemoteBlob: + return self.rpc_client.get_remote_blob(object_id, unsafe)
+ +
[docs] @_apply_docstring(RPCClient.get_remote_blobs) + def get_remote_blobs( + self, object_ids: List[ObjectID], unsafe: bool = False + ) -> List[RemoteBlob]: + return self.rpc_client.get_remote_blobs(object_ids, unsafe)
+ +
[docs] @_apply_docstring(IPCClient.get_object) + def get_object( + self, object_id: ObjectID, sync_remote: bool = True, fetch: bool = False + ) -> Object: + """ + Fetches the object associated with the given object_id from Vineyard. + The IPC client is preferred if it's available, otherwise the RPC client + """ + return self._fetch_object( + object_id, sync_remote=sync_remote, enable_migrate=fetch + )
+ +
[docs] @_apply_docstring(IPCClient.get_objects) + def get_objects( + self, object_ids: List[ObjectID], sync_remote: bool = True + ) -> List[Object]: + objects = [] + for object_id in object_ids: + objects.append(self.get_object(object_id, sync_remote)) + return objects
+ +
[docs] @_apply_docstring(IPCClient.get_meta) + def get_meta( + self, + object_id: ObjectID, + sync_remote: bool = True, + ) -> ObjectMeta: + return self.default_client().get_meta(object_id, sync_remote)
+ +
[docs] @_apply_docstring(IPCClient.get_metas) + def get_metas( + self, object_ids: List[ObjectID], sync_remote: bool = True + ) -> List[ObjectMeta]: + metas = [] + for object_id in object_ids: + metas.append(self.get_meta(object_id, sync_remote)) + return metas
+ +
[docs] @_apply_docstring(IPCClient.list_objects) + def list_objects( + self, pattern: str, regex: bool = False, limit: int = 5 + ) -> List[ObjectID]: + return self.default_client().list_objects(pattern, regex, limit)
+ +
[docs] @_apply_docstring(IPCClient.list_metadatas) + def list_metadatas( + self, pattern: str, regex: bool = False, limit: int = 5, nobuffer: bool = False + ) -> List[ObjectMeta]: + return self.default_client().list_metadatas(pattern, regex, limit, nobuffer)
+ + @_apply_docstring(IPCClient.new_buffer_chunk) + def new_buffer_chunk(self, stream: ObjectID, size: int) -> memoryview: + return self.ipc_client.new_buffer_chunk(stream, size) + + @_apply_docstring(IPCClient.next_buffer_chunk) + def next_buffer_chunk(self, stream: ObjectID) -> memoryview: + return self.ipc_client.next_buffer_chunk(stream) + +
[docs] @_apply_docstring(IPCClient.allocated_size) + def allocated_size(self, object_id: Union[Object, ObjectID]) -> int: + return self.ipc_client.allocated_size(object_id)
+ +
[docs] @_apply_docstring(IPCClient.is_shared_memory) + def is_shared_memory(self, pointer: int) -> bool: + return self.ipc_client.is_shared_memory(pointer)
+ +
[docs] @_apply_docstring(IPCClient.find_shared_memory) + def find_shared_memory(self, pointer: int) -> ObjectID: + return self.ipc_client.find_shared_memory(pointer)
+ + @property + @_apply_docstring(RPCClient.remote_instance_id) + def remote_instance_id(self) -> int: + return self.rpc_client.remote_instance_id + +
[docs] @_apply_docstring(IPCClient.close) + def close(self) -> None: + if self._ipc_client: + self._ipc_client.close() + if self._rpc_client: + self._rpc_client.close()
+ + @_apply_docstring(IPCClient.fork) + def fork(self) -> 'Client': + if self._ipc_client: + self._ipc_client = self._ipc_client.fork() + if self._rpc_client: + self._rpc_client = self._rpc_client.fork() + return self + + def _fetch_object( + self, object_id: ObjectID, enable_migrate: bool, sync_remote: bool = True + ) -> Object: + meta = self.get_meta(object_id, sync_remote=sync_remote) + + if self.has_ipc_client() and enable_migrate: + # no need to sync remote metadata as the metadata is already fetched + return self._ipc_client.get_object(object_id, fetch=True, sync_remote=False) + + blobs = _traverse_blobs(meta) + + # If the object is local, return it directly + if blobs.keys() == {self.instance_id}: + return Object.from_(meta) + + cluster_info = self.default_client().meta + meta.force_local() + meta._client = None + + with ThreadPoolExecutor() as executor: + futures = { + executor.submit( + self._fetch_blobs_from_instance, + cluster_info, + instance_id, + blobs[instance_id], + self.compression, + ) + for instance_id in blobs + if instance_id != self.instance_id + } + + for future in as_completed(futures): + fetched_blobs = future.result() + for blob in fetched_blobs: + meta.add_remote_blob(blob) + + return Object.from_(meta) + + def _fetch_blobs_from_instance( + self, cluster_info, instance_id, blob_ids, compression + ) -> Object: + """Fetches all blobs from a given instance id in the Vineyard cluster. + + Args: + cluster_info (Dict): The cluster information of the Vineyard cluster. + instance_id (int): The instance id to fetch blobs from. + blob_ids (List): The list of blob ids to fetch. + compression (bool): Whether to enable compression for RPC Client. + + Returns: + RemoteBlob(List): The list of fetched remote blobs. + """ + instance_status = cluster_info.get(instance_id) + + if instance_status is None or instance_status['rpc_endpoint'] is None: + raise RuntimeError( + "The rpc endpoint of the vineyard instance " + f"{instance_id} is not available." + ) + + if self.has_rpc_client() and self.remote_instance_id == instance_id: + remote_client = self._rpc_client + else: + host, port = instance_status['rpc_endpoint'].split(':') + try: + with envvars('VINEYARD_RPC_SKIP_RETRY', '1'): + remote_client = _connect(host, port) + remote_client.compression = compression + except Exception as exec: + raise RuntimeError( + f"Failed to connect to the vineyard instance {instance_id} " + f"at {host}:{port}." + ) from exec + + return remote_client.get_remote_blobs(blob_ids) + + def _connect_and_get_memory(self, instance_id, rpc_endpoint): + host, port = rpc_endpoint.split(':') + try: + new_client = _connect(host, port) + current_available_memory = ( + new_client.status.memory_limit - new_client.status.memory_usage + ) + return instance_id, current_available_memory + except Exception: + return instance_id, float('-inf') + + def _find_the_most_available_memory_instance(self) -> int: + """ + Find the vineyard instance with the most available memory. + + Returns: + int: The instance id with the most available memory. + if only have one instance, return -1 + """ + futures = [] + cluster_info = self.default_client().meta + with ThreadPoolExecutor() as executor: + for instance_id, status in cluster_info.items(): + if not ( + status['ipc_socket'] == self.ipc_socket + and status['rpc_endpoint'] == self.rpc_endpoint + ): + futures.append( + executor.submit( + self._connect_and_get_memory, + instance_id, + status['rpc_endpoint'], + ) + ) + + instance_id_with_most_available_memory = -1 + available_memory = float('-inf') + + for future in as_completed(futures): + instance_id, current_available_memory = future.result() + if current_available_memory > available_memory: + instance_id_with_most_available_memory = instance_id + available_memory = current_available_memory + + return instance_id_with_most_available_memory + +
[docs] @_apply_docstring(get) + def get( + self, + object_id: Optional[ObjectID] = None, + name: Optional[str] = None, + resolver: Optional[ResolverContext] = None, + fetch: bool = False, + **kwargs, + ): + return get(self, object_id, name, resolver, fetch, **kwargs)
+ +
[docs] @_apply_docstring(put) + def put( + self, + value: Any, + builder: Optional[BuilderContext] = None, + persist: bool = False, + name: Optional[str] = None, + **kwargs, + ): + try: + return put(self, value, builder, persist, name, **kwargs) + except NotEnoughMemoryException as exec: + with envvars( + {'VINEYARD_RPC_SKIP_RETRY': '1', 'VINEYARD_IPC_SKIP_RETRY': '1'} + ): + instance_id = self._find_the_most_available_memory_instance() + previous_compression_state = self.compression + if instance_id == -1: + warnings.warn("No other vineyard instance available") + raise exec + else: + meta = self.default_client().meta + warnings.warn( + f"Put object to the vineyard instance {instance_id}" + "with the most available memory." + ) + # connect to the instance with the most available memory + self._ipc_client = None + if os.path.exists(meta[instance_id]['ipc_socket']): + self._ipc_client = _connect(meta[instance_id]['ipc_socket']) + # avoid the case the vineyard instance is restarted + if self._ipc_client.instance_id != instance_id: + self._ipc_client = None + host, port = meta[instance_id]['rpc_endpoint'].split(':') + self._rpc_client = _connect(host, port) + self.compression = previous_compression_state + return put(self, value, builder, persist, name, **kwargs)
+ +
[docs] @contextlib.contextmanager + def with_compression(self, enabled: bool = True): + """Disable compression for the following put operations.""" + compression = self.compression + self.compression = enabled + yield + self.compression = compression
+ +
[docs] @contextlib.contextmanager + def with_spread(self, enabled: bool = True): + """Enable spread for the following put operations.""" + tmp_spread = self._spread + self.spread = enabled + yield + self.spread = tmp_spread
+ + +__all__ = ['Client'] +
+
+
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/vineyard/core/driver.html b/_modules/vineyard/core/driver.html new file mode 100644 index 0000000000..f452a0ae05 --- /dev/null +++ b/_modules/vineyard/core/driver.html @@ -0,0 +1,603 @@ + + + + + + + + vineyard.core.driver - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for vineyard.core.driver

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020-2023 Alibaba Group Holding Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import contextlib
+import copy
+import functools
+import threading
+
+from sortedcontainers import SortedDict
+
+from vineyard.core.utils import find_most_precise_match
+
+
+
[docs]class DriverContext: + def __init__(self): + self._factory = SortedDict(dict) + + def __str__(self) -> str: + return str(self._factory) + + def register(self, typename_prefix, meth, func): + if typename_prefix not in self._factory: + self._factory[typename_prefix] = dict() + self._factory[typename_prefix][meth] = func + + def resolve(self, obj, typename): + prefix, methods = find_most_precise_match(typename, self._factory) + if prefix: + for meth, func in methods.items(): + # if shouldn't failed, since it has already been wrapped in during + # resolving + setattr(obj, meth, functools.partial(func, obj)) + return obj + + def __call__(self, obj, typename): + return self.resolve(obj, typename) + + def extend(self, drivers=None): + driver = DriverContext() + driver._factory.update(((k, copy.copy(v)) for k, v in self._factory.items())) + if drivers: + for ty, methods in drivers.items(): + if ty not in self._factory: + driver._factory[ty] = dict() + driver._factory[ty].update(methods) + return driver
+ + +default_driver_context = DriverContext() + +_driver_context_local = threading.local() +_driver_context_local.default_driver = default_driver_context + + +
[docs]def get_current_drivers(): + '''Obtain current driver context.''' + default_driver = getattr(_driver_context_local, 'default_driver', None) + if not default_driver: + default_driver = default_driver_context.extend() + return default_driver
+ + +
[docs]@contextlib.contextmanager +def driver_context(drivers=None, base=None): + """Open a new context for register drivers, without populting outside global + environment. + + See Also: + builder_context + resolver_context + """ + current_driver = get_current_drivers() + try: + drivers = drivers or dict() + base = base or current_driver + local_driver = base.extend(drivers) + _driver_context_local.default_driver = local_driver + yield local_driver + finally: + _driver_context_local.default_driver = current_driver
+ + +def register_builtin_drivers(ctx): + assert isinstance(ctx, DriverContext) + + # TODO + # there's no builtin drivers yet. + + +def registerize(func): + """Registerize a method, add a `_factory` attribute and a `register` + interface to a method. + + multiple-level register is automatically supported, users can + + >>> open.register(local_io_adaptor) + >>> open.register(oss_io_adaptor) + + OR + + >>> open.register('file', local_io_adaptor) + >>> open.register('odps', odps_io_adaptor) + + OR + + >>> open.register('file', 'csv', local_csv_reader) + >>> open.register('file', 'tsv', local_tsv_reader) + """ + + @functools.wraps(func) + def wrap(*args, **kwargs): + return func(*args, **kwargs) + + setattr(wrap, '_factory', None) + + def register(*args): + if len(args) == 1: + if wrap._factory is None: + wrap._factory = [] + if not isinstance(wrap._factory, list): + raise RuntimeError( + 'Invalid arguments: inconsistent with existing registerations' + ) + wrap._factory.append(args[0]) + else: + if wrap._factory is None: + wrap._factory = {} + if not isinstance(wrap._factory, dict): + raise RuntimeError( + 'Invalid arguments: inconsistent with existing registerations' + ) + root = wrap._factory + for arg in args[:-2]: + if arg not in root: + root[arg] = dict() + root = root[arg] + if args[-2] not in root: + root[args[-2]] = list() + root[args[-2]].append(args[-1]) + + setattr(wrap, 'register', register) + + return wrap + + +__all__ = [ + 'default_driver_context', + 'register_builtin_drivers', + 'driver_context', + 'get_current_drivers', + 'registerize', +] +
+
+
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/vineyard/core/resolver.html b/_modules/vineyard/core/resolver.html new file mode 100644 index 0000000000..7b341d9ecd --- /dev/null +++ b/_modules/vineyard/core/resolver.html @@ -0,0 +1,685 @@ + + + + + + + + vineyard.core.resolver - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for vineyard.core.resolver

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020-2023 Alibaba Group Holding Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import contextlib
+import inspect
+import threading
+from typing import Any
+from typing import Callable
+from typing import Dict
+from typing import Generator
+from typing import Optional
+
+from sortedcontainers import SortedDict
+
+from vineyard._C import IPCClient
+from vineyard._C import Object
+from vineyard._C import ObjectID
+from vineyard._C import RPCClient
+from vineyard.core.utils import find_most_precise_match
+
+
+
[docs]class ResolverContext: + def __init__(self, parent_context: Optional["ResolverContext"] = None): + self._factory = SortedDict() + if parent_context is not None: + self._parent_context = parent_context + else: + self._parent_context = self + + def __str__(self) -> str: + return str(self._factory) + + def __repr__(self) -> str: + return repr(self._factory) + + @property + def parent_context(self) -> "ResolverContext": + return self._parent_context + + def register(self, typename_prefix: str, resolver: Callable): + self._factory[typename_prefix] = resolver + + def run(self, obj: Any, **kw): + typename = obj.meta.typename + prefix, resolver = find_most_precise_match(typename, self._factory) + vineyard_client = kw.pop('__vineyard_client', None) + if prefix: + resolver_func_sig = inspect.getfullargspec(resolver) + if resolver_func_sig.varkw is not None: + value = resolver(obj, resolver=self, **kw) + else: + try: + # don't pass the `**kw`. + if 'resolver' in resolver_func_sig.args: + value = resolver(obj, resolver=self) + else: + value = resolver(obj) + except Exception as e: + raise RuntimeError( # pylint: disable=raise-missing-from + 'Unable to construct the object using resolver: ' + 'typename is %s, resolver is %s' % (obj.meta.typename, resolver) + ) from e + if value is None: + # if the obj has been resolved by pybind types, and there's no proper + # resolver, it shouldn't be an error + if type(obj) is not Object: # pylint: disable=unidiomatic-typecheck + return obj + + # we might `client.put(None)` + return None + + # associate a reference to the base C++ object + try: + setattr(value, '__vineyard_ref', obj) + setattr(value, '__vineyard_client', vineyard_client) + + # register methods + from vineyard.core.driver import get_current_drivers + + get_current_drivers().resolve(value, obj.typename) + except AttributeError: + pass + + return value + # keep it as it is + return obj + + def __call__(self, obj, **kw): + return self.run(obj, **kw) + + def extend(self, resolvers=None): + resolver = ResolverContext(self) + resolver._factory = ( # pylint: disable=unused-private-member + self._factory.copy() + ) + if resolvers: + resolver._factory.update(resolvers) + return resolver
+ + +default_resolver_context = ResolverContext() + +_resolver_context_local = threading.local() +_resolver_context_local.default_resolver = default_resolver_context + + +
[docs]def get_current_resolvers() -> ResolverContext: + '''Obtain current resolver context.''' + default_resolver = getattr(_resolver_context_local, 'default_resolver', None) + if not default_resolver: + default_resolver = default_resolver_context.extend() + return default_resolver
+ + +
[docs]@contextlib.contextmanager +def resolver_context( + resolvers: Optional[Dict[str, Callable]] = None, + base: Optional[ResolverContext] = None, +) -> Generator[ResolverContext, Any, Any]: + """Open a new context for register resolvers, without populating outside + the global environment. + + The :code:`resolver_context` can be useful when users have more than + more resolver for a certain type, e.g., the :code:`vineyard::Tensor` + object can be resolved as :code:`numpy.ndarray` or :code:`xgboost::DMatrix`. + + We could have + + .. code:: python + + def numpy_resolver(obj): + ... + + default_resolver_context.register('vineyard::Tensor', numpy_resolver) + + and + + .. code:: python + + def xgboost_resolver(obj): + ... + + default_resolver_context.register('vineyard::Tensor', xgboost_resolver) + + Obviously there's a conflict, and the stackable :code:`resolver_context` could + help there, + + .. code:: python + + with resolver_context({'vineyard::Tensor', xgboost_resolver}): + ... + + Assuming the default context resolves :code:`vineyard::Tensor` to + :code:`numpy.ndarray`, inside the :code:`with resolver_context` the + :code:`vineyard::Tensor` will be resolved to :code:`xgboost::DMatrix`, + and after exiting the context the global environment will be restored + back as default. + + The :code:`with resolver_context` is nestable as well. + + See Also: + builder_context + driver_context + """ + current_resolver = get_current_resolvers() + try: + resolvers = resolvers or dict() + base = base or current_resolver + local_resolver = base.extend(resolvers) + _resolver_context_local.default_resolver = local_resolver + yield local_resolver + finally: + _resolver_context_local.default_resolver = current_resolver
+ + +def get( + client, + object_id: Optional[ObjectID] = None, + name: Optional[str] = None, + resolver: Optional[ResolverContext] = None, + fetch: bool = False, + **kwargs +): + """Get vineyard object as python value. + + .. code:: python + + >>> arr_id = vineyard.ObjectID('00002ec13bc81226') + >>> arr = client.get(arr_id) + >>> arr + array([0, 1, 2, 3, 4, 5, 6, 7]) + + Parameters: + client: IPCClient or RPCClient + The vineyard client to use. + object_id: ObjectID + The object id that will be obtained from vineyard. + name: ObjectID + The object name that will be obtained from vineyard, ignored if + ``object_id`` is not None. + resolver: + When retrieving vineyard object, an optional *resolver* can be specified. + If no resolver given, the default resolver context will be used. + fetch: + Whether to trigger a migration when the target object is located on + remote instances. + kw: + User-specific argument that will be passed to the builder. + + Returns: + A python object that return by the resolver, by resolving an vineyard object. + """ + # wrap object_id + if object_id is not None: + if isinstance(object_id, (int, str)): + object_id = ObjectID(object_id) + elif name is not None: + object_id = client.get_name(name) + + obj = client.get_object(object_id, fetch=fetch) + + if resolver is None: + resolver = get_current_resolvers() + return resolver(obj, __vineyard_client=client, **kwargs) + + +setattr(IPCClient, 'get', get) +setattr(RPCClient, 'get', get) + +__all__ = [ + 'default_resolver_context', + 'resolver_context', + 'get_current_resolvers', +] +
+
+
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/vineyard/csi.html b/_modules/vineyard/csi.html new file mode 100644 index 0000000000..f529dcaafa --- /dev/null +++ b/_modules/vineyard/csi.html @@ -0,0 +1,527 @@ + + + + + + + + vineyard.csi - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for vineyard.csi

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020-2023 Alibaba Group Holding Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+from typing import Any
+
+import vineyard
+
+
+def _find_vineyard_socket(path):
+    current_dir = path
+    while current_dir != "/":
+        socket_path = os.path.join(current_dir, "vineyard.sock")
+        if os.path.exists(socket_path):
+            return socket_path
+        current_dir = os.path.dirname(current_dir)
+    return None
+
+
+
[docs]def write( + value: Any, + path: str, +): + """ + Write python value to vineyard. + Notice, the API is only used for CSI driver. + + Parameters: + path: str + The path that represents a vineyard object. + + .. code:: python + + >>> arr = np.arange(8) + >>> vineyard.write(arr) + """ + socket_path = _find_vineyard_socket(path) + + if socket_path is None: + raise FileNotFoundError( + f"The given path is not generated by vineyard CSI driver: {path}" + ) + + client = vineyard.connect(socket_path) + client.put(value, persist=True, name=path)
+ + +
[docs]def read( + path: str, +): + """ + Read vineyard object from path, and return python value. + Notice, the API is only used for CSI driver. + + Parameters: + path: str + The path that represents a vineyard object. + + .. code:: python + + >>> arr = vineyard.read('/a/b/c/d/f') + >>> arr + array([0, 1, 2, 3, 4, 5, 6, 7]) + + Returns: + A python object that return by the resolver, by resolving an vineyard object. + """ + socket_path = _find_vineyard_socket(path) + + if socket_path is None: + raise FileNotFoundError( + f"The given path is not generated by vineyard CSI driver: {path}" + ) + + client = vineyard.connect(socket_path) + return client.get(name=path)
+
+
+
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/vineyard/deploy/distributed.html b/_modules/vineyard/deploy/distributed.html new file mode 100644 index 0000000000..9225855517 --- /dev/null +++ b/_modules/vineyard/deploy/distributed.html @@ -0,0 +1,577 @@ + + + + + + + + vineyard.deploy.distributed - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for vineyard.deploy.distributed

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020-2023 Alibaba Group Holding Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import contextlib
+import logging
+import subprocess
+import sys
+import textwrap
+import time
+
+import pkg_resources
+
+from vineyard.deploy.utils import ssh_base_cmd
+from vineyard.deploy.utils import start_etcd
+
+logger = logging.getLogger('vineyard')
+
+
+
[docs]@contextlib.contextmanager +def start_vineyardd( + hosts=None, + etcd_endpoints=None, + vineyardd_path=None, + size='', + socket='/var/run/vineyard.sock', + rpc_socket_port=9600, + debug=False, +): + """Launch a local vineyard cluster in a distributed fashion. + + Parameters: + hosts: list of str + A list of machines to launch vineyard server. + etcd_endpoint: str + Launching vineyard using specified etcd endpoints. If not specified, + vineyard will launch its own etcd instance. + vineyardd_path: str + Location of vineyard server program. If not specified, vineyard will + use its own bundled vineyardd binary. + size: int + The memory size limit for vineyard's shared memory. The memory size + can be a plain integer or as a fixed-point number using one of these + suffixes: + + .. code:: + + E, P, T, G, M, K. + + You can also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki. + Defaults to "", means not limited. + + For example, the following represent roughly the same value: + + .. code:: + + 128974848, 129k, 129M, 123Mi, 1G, 10Gi, ... + socket: str + The UNIX domain socket socket path that vineyard server will listen on. + rpc_socket_port: int + The port that vineyard will use to privode RPC service. + debug: bool + Whether print debug logs. + """ + if vineyardd_path is None: + vineyardd_path = pkg_resources.resource_filename('vineyard', 'vineyardd') + + if hosts is None: + hosts = ['localhost'] + + if etcd_endpoints is None: + etcd_ctx = start_etcd(host=hosts[0]) + _etcd_proc, etcd_endpoints = etcd_ctx.__enter__() # pylint: disable=no-member + else: + etcd_ctx = None + + env = dict() + if debug: + env['GLOG_v'] = 11 + + command = [ + vineyardd_path, + '--deployment', + 'distributed', + '--size', + str(size), + '--socket', + socket, + '--rpc_socket_port', + str(rpc_socket_port), + '--etcd_endpoint', + etcd_endpoints, + ] + + procs = [] + try: + for host in hosts: + proc = subprocess.Popen( + ssh_base_cmd(host) + command, + env=env, + stdout=subprocess.PIPE, + stderr=sys.__stderr__, + universal_newlines=True, + encoding='utf-8', + ) + procs.append(proc) + time.sleep(1) + for proc in procs: + rc = proc.poll() + if rc is not None: + err = textwrap.indent(proc.stdout.read(), ' ' * 4) + raise RuntimeError( + 'vineyardd exited unexpectedly with ' + 'code %d: error is:\n%s' % (rc, err) + ) + yield procs, socket, etcd_endpoints + finally: + logger.info('Distributed vineyardd being killed') + for proc in procs: + if proc.poll() is None: + logger.info('killing the vineyardd') + proc.kill() + if etcd_ctx is not None: + etcd_ctx.__exit__(None, None, None) # pylint: disable=no-member
+ + +__all__ = ['start_vineyardd'] +
+
+
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/vineyard/deploy/kubernetes.html b/_modules/vineyard/deploy/kubernetes.html new file mode 100644 index 0000000000..2fd1ddbd0c --- /dev/null +++ b/_modules/vineyard/deploy/kubernetes.html @@ -0,0 +1,721 @@ + + + + + + + + vineyard.deploy.kubernetes - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for vineyard.deploy.kubernetes

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020-2023 Alibaba Group Holding Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import logging
+import os
+import re
+import tempfile
+import time
+
+try:
+    import kubernetes
+except ImportError:
+    kubernetes = None
+
+from vineyard.deploy.etcd import start_etcd_k8s
+from vineyard.deploy.utils import ensure_kubernetes_namespace
+
+logger = logging.getLogger('vineyard')
+
+
+
[docs]def start_vineyardd( + namespace='vineyard', + size='512Mi', + socket='/var/run/vineyard.sock', + rpc_socket_port=9600, + vineyard_image='vineyardcloudnative/vineyardd:latest', + vineyard_image_pull_policy='IfNotPresent', + vineyard_image_pull_secrets=None, + k8s_client=None, +): + """Launch a vineyard cluster on kubernetes. + + Parameters: + namespace: str + namespace in kubernetes + size: int + The memory size limit for vineyard's shared memory. The memory size + can be a plain integer or as a fixed-point number using one of these + suffixes: + + .. code:: + + E, P, T, G, M, K. + + You can also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki. + + For example, the following represent roughly the same value: + + .. code:: + + 128974848, 129k, 129M, 123Mi, 1G, 10Gi, ... + socket: str + The UNIX domain socket socket path that vineyard server will listen on. + rpc_socket_port: int + The port that vineyard will use to provide RPC service. + k8s_client: kubernetes.client.api.ApiClient + A kubernetes client. If not specified, vineyard will try to resolve the + kubernetes configuration from current context. + vineyard_image: str + The docker image of vineyardd to launch the daemonset. + vineyard_image_pull_policy: str + The docker image pull policy of vineyardd. + vineyard_image_pull_secrets: str + The docker image pull secrets of vineyardd. + + Returns: + A list of created kubernetes resources during the deploying process. The + resources can be later release using :meth:`delete_kubernetes_objects`. + + See Also: + vineyard.deploy.kubernetes.delete_kubernetes_objects + """ + if kubernetes is None: + raise RuntimeError('Please install the package python "kubernetes" first') + + if k8s_client is None: + kubernetes.config.load_kube_config() + k8s_client = kubernetes.client.ApiClient() + + created_objects = [] + ensure_kubernetes_namespace(namespace, k8s_client=k8s_client) + created_objects.extend(start_etcd_k8s(namespace, k8s_client=k8s_client)) + + with open( + os.path.join(os.path.dirname(__file__), "vineyard.yaml.tpl"), + 'r', + encoding='utf-8', + ) as fp: + formatter = { + 'Namespace': namespace, + 'Size': size, + 'Socket': socket, + 'Port': rpc_socket_port, + 'Image': vineyard_image, + 'ImagePullPolicy': vineyard_image_pull_policy, + 'ImagePullSecrets': ( + vineyard_image_pull_secrets if vineyard_image_pull_secrets else 'none', + ), + } + definitions = fp.read().format(**formatter) + + with tempfile.NamedTemporaryFile( + mode='w', encoding='utf-8', delete=False + ) as rendered: + rendered.write(definitions) + rendered.flush() + rendered.close() + + with open(rendered.name, 'r', encoding='utf-8') as fp: + logger.debug(fp.read()) + + created_objects.extend( + kubernetes.utils.create_from_yaml( + k8s_client, rendered.name, namespace=namespace + ) + ) + return created_objects
+ + +def recursive_flatten(targets): + """Flatten the given maybe nested list as a 1-level list.""" + + def _recursive_flatten_impl(destination, targets): + if isinstance(targets, (list, tuple)): + for target in targets: + _recursive_flatten_impl(destination, target) + else: + destination.append(targets) + + destination = [] + _recursive_flatten_impl(destination, targets) + return destination + + +
[docs]def delete_kubernetes_objects( + targets, k8s_client=None, verbose=False, wait=False, timeout_seconds=60, **kwargs +): + """Delete the given kubernetes resources. + + Parameters: + target: List + List of Kubernetes objects + k8s_client: + The kubernetes client. If not specified, vineyard will try to resolve + the kubernetes configuration from current context. + verbose: bool + Whether to print the deletion logs. + wait: bool + Whether to wait for the deletion to complete. + timeout_seconds: int + The timeout in seconds for waiting for the deletion to complete. + + See Also: + vineyard.deploy.kubernetes.start_vineyardd + vineyard.deploy.kubernetes.delete_kubernetes_object + """ + for target in recursive_flatten(targets): + delete_kubernetes_object( + target, + k8s_client, + verbose=verbose, + wait=wait, + timeout_seconds=timeout_seconds, + **kwargs + )
+ + +def delete_kubernetes_object( + target, k8s_client=None, verbose=False, wait=False, timeout_seconds=60, **kwargs +): + """Delete the given kubernetes resource. + + Parameters: + target: object + The Kubernetes objects that will be deleted. + k8s_client: + The kubernetes client. If not specified, vineyard will try to resolve + the kubernetes configuration from current context. + verbose: bool + If True, print confirmation from the delete action. Defaults to False. + wait: bool + Whether to wait for the deletion to complete. Defaults to False. + timeout_seconds: int + The timeout in seconds for waiting for the deletion to complete. Defaults + to 60. + + Returns: + Status: Return status for calls kubernetes delete method. + + See Also: + vineyard.deploy.kubernetes.start_vineyardd + vineyard.deploy.kubernetes.delete_kubernetes_objects + """ + if isinstance(target, (list, tuple)): + return delete_kubernetes_objects( + target, k8s_client, verbose=verbose, wait=wait, **kwargs + ) + + if kubernetes is None: + raise RuntimeError('Please install the package python "kubernetes" first') + + if k8s_client is None: + kubernetes.config.load_kube_config() + k8s_client = kubernetes.client.ApiClient() + + group, _, version = target.api_version.partition("/") + if version == "": + version = group + group = "core" + # Take care for the case e.g. api_type is "apiextensions.k8s.io" + # Only replace the last instance + group = "".join(group.rsplit(".k8s.io", 1)) + # convert group name from DNS subdomain format to + # python class name convention + group = "".join(word.capitalize() for word in group.split(".")) + fcn_to_call = "{0}{1}Api".format(group, version.capitalize()) + k8s_api = getattr(kubernetes.client, fcn_to_call)( + k8s_client + ) # pylint: disable=not-callable + + kind = target.kind + kind = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", kind) + kind = re.sub("([a-z0-9])([A-Z])", r"\1_\2", kind).lower() + + try: + # Expect the user to create namespaced objects more often + kwargs["name"] = target.metadata.name + if hasattr(k8s_api, "delete_namespaced_{0}".format(kind)): + # Decide which namespace we are going to put the object in, if any + kwargs["namespace"] = target.metadata.namespace + resp = getattr(k8s_api, "delete_namespaced_{0}".format(kind))(**kwargs) + else: + kwargs.pop("namespace", None) + resp = getattr(k8s_api, "delete_{0}".format(kind))(**kwargs) + except kubernetes.client.rest.ApiException: + # Object already deleted. + return None + else: + # Waiting for delete + if wait: + start_time = time.time() + if hasattr(k8s_api, "read_namespaced_{0}".format(kind)): + while True: + try: + getattr(k8s_api, "read_namespaced_{0}".format(kind))(**kwargs) + except kubernetes.client.rest.ApiException as ex: + if ex.status != 404: + logger.exception( + "Deleting %s %s failed", kind, target.metadata.name + ) + break + else: + time.sleep(1) + if time.time() - start_time > timeout_seconds: + logger.info( + "Deleting %s/%s timeout", kind, target.metadata.name + ) + if verbose: + msg = "{0}/{1} deleted.".format(kind, target.metadata.name) + if hasattr(resp, "status"): + msg += " status='{0}'".format(str(resp.status)) + logger.info(msg) + return resp + + +__all__ = [ + 'start_vineyardd', + 'delete_kubernetes_object', + 'delete_kubernetes_objects', +] +
+
+
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/vineyard/deploy/local.html b/_modules/vineyard/deploy/local.html new file mode 100644 index 0000000000..0677184245 --- /dev/null +++ b/_modules/vineyard/deploy/local.html @@ -0,0 +1,702 @@ + + + + + + + + vineyard.deploy.local - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for vineyard.deploy.local

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020-2023 Alibaba Group Holding Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import atexit
+import contextlib
+import logging
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+import textwrap
+import time
+from typing import Generator
+from typing import Optional
+from typing import Tuple
+
+from vineyard.deploy.etcd import start_etcd
+from vineyard.deploy.utils import check_socket
+from vineyard.deploy.utils import find_vineyardd_path
+
+logger = logging.getLogger('vineyard')
+
+
+
[docs]@contextlib.contextmanager +def start_vineyardd( + meta: Optional[str] = 'etcd', + etcd_endpoints: Optional[str] = None, + etcd_prefix: Optional[str] = None, + vineyardd_path: Optional[str] = None, + size: Optional[str] = '', + socket: Optional[str] = None, + rpc: Optional[str] = True, + rpc_socket_port: Optional[str] = 9600, + debug=False, +) -> Generator[Tuple[subprocess.Popen, str, str], None, None]: + """Launch a local vineyard cluster. + + Parameters: + meta: str, optional. + Metadata backend, can be "etcd", "redis" and "local". Defaults to + "etcd". + etcd_endpoint: str + Launching vineyard using specified etcd endpoints. If not specified, + vineyard will launch its own etcd instance. + etcd_prefix: str + Specify a common prefix to establish a local vineyard cluster. + vineyardd_path: str + Location of vineyard server program. If not specified, vineyard will + use its own bundled vineyardd binary. + size: int + The memory size limit for vineyard's shared memory. The memory size + can be a plain integer or as a fixed-point number using one of these + suffixes: + + .. code:: + + E, P, T, G, M, K. + + You can also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki. + Defaults to "", means not limited. + + For example, the following represent roughly the same value: + + .. code:: + + 128974848, 129k, 129M, 123Mi, 1G, 10Gi, ... + socket: str + The UNIX domain socket socket path that vineyard server will listen on. + Default is None. + + When the socket parameter is None, a random path under temporary directory + will be generated and used. + rpc_socket_port: int + The port that vineyard will use to provided RPC service. + debug: bool + Whether print debug logs. + + Returns: + (proc, socket): + Yields a tuple with the subprocess as the first element and the UNIX-domain + IPC socket as the second element. + """ + + if not vineyardd_path: + vineyardd_path = find_vineyardd_path() + + if not vineyardd_path: + raise RuntimeError('Unable to find the "vineyardd" executable') + + if socket is None: + socketfp = tempfile.NamedTemporaryFile( + delete=True, prefix='vineyard-', suffix='.sock' + ) + socket = socketfp.name + socketfp.close() + + command = [ + vineyardd_path, + '--deployment', + 'local', + '--size', + str(size), + '--socket', + socket, + '--rpc' if rpc else '--norpc', + '--rpc_socket_port', + str(rpc_socket_port), + '--meta', + meta, + ] + + if meta == 'etcd': + if etcd_endpoints is None: + etcd_ctx = start_etcd() + ( + _etcd_proc, + etcd_endpoints, + ) = etcd_ctx.__enter__() # pylint: disable=no-member + else: + etcd_ctx = None + command.extend(('--etcd_endpoint', etcd_endpoints)) + if etcd_prefix is not None: + command.extend(('--etcd_prefix', etcd_prefix)) + else: + etcd_ctx = None + + env = os.environ.copy() + if debug: + env['GLOG_v'] = '11' + + proc = None + try: + proc = subprocess.Popen( + command, + env=env, + stdout=subprocess.PIPE, + stderr=sys.__stderr__, + universal_newlines=True, + encoding='utf-8', + ) + # wait for vineyardd ready: check the rpc port and ipc sockets + rc = proc.poll() + while rc is None: + if check_socket(socket) and ( + (not rpc) or check_socket(('0.0.0.0', rpc_socket_port)) + ): + break + time.sleep(1) + rc = proc.poll() + + if rc is not None: + err = textwrap.indent(proc.stdout.read(), ' ' * 4) + raise RuntimeError( + 'vineyardd exited unexpectedly ' + 'with code %d, error is:\n%s' % (rc, err) + ) + + logger.debug('vineyardd is ready.............') + yield proc, socket, etcd_endpoints + finally: + logger.debug('Local vineyardd being killed') + if proc is not None and proc.poll() is None: + proc.terminate() + proc.wait() + try: + shutil.rmtree(socket) + except Exception: # pylint: disable=broad-except + pass + if etcd_ctx is not None: + etcd_ctx.__exit__(None, None, None) # pylint: disable=no-member
+ + +__default_instance_contexts = {} + + +def try_init() -> str: + """ + Launching a local vineyardd instance and get a client as easy as possible. + + In a clean environment, simply use: + + .. code:: python + + vineyard.try_init() + + It will launch a local vineyardd and return a connected client to the + vineyardd. + + It will also setup the environment variable :code:`VINEYARD_IPC_SOCKET`. + + The init method can only be called once in a process, to get the established + sockets or clients later in the process, use :code:`get_current_socket` or + :code:`connect()` respectively. + """ + assert not __default_instance_contexts + + if 'VINEYARD_IPC_SOCKET' in os.environ: + raise ValueError( + "VINEYARD_IPC_SOCKET has already been set: %s, which " + "means there might be a vineyard daemon already running " + "locally" % os.environ['VINEYARD_IPC_SOCKET'] + ) + + ctx = start_vineyardd(meta='local', rpc=False) + _, ipc_socket, etcd_endpoints = ctx.__enter__() # pylint: disable=no-member + __default_instance_contexts[ipc_socket] = ctx + + # populate the environment variable + os.environ['VINEYARD_IPC_SOCKET'] = ipc_socket + return get_current_socket() + + +
[docs]def get_current_socket() -> str: + """ + Get current vineyard UNIX-domain socket established by :code:`vineyard.try_init()`. + + Raises: + ValueError if vineyard is not initialized. + """ + if not __default_instance_contexts: + raise ValueError( + 'Vineyard has not been initialized, ' + 'use vineyard.try_init() to launch vineyard instances' + ) + sockets = list(__default_instance_contexts.keys()) + if sockets: + return sockets[0] + raise RuntimeError("Vineyard has not been initialized")
+ + +def shutdown(): + """ + Shutdown the vineyardd instances launched by previous :code:`vineyard.try_init()`. + """ + global __default_instance_contexts + if __default_instance_contexts: + for ipc_socket in reversed(__default_instance_contexts.keys()): + __default_instance_contexts[ipc_socket].__exit__(None, None, None) + # NB. don't pop pre-existing env if we not launch + os.environ.pop('VINEYARD_IPC_SOCKET', None) + __default_instance_contexts = {} + + +@atexit.register +def __shutdown_handler(): + with contextlib.suppress(Exception): + shutdown() + + +__all__ = ['start_vineyardd', 'try_init', 'shutdown'] +
+
+
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/vineyard/io/byte.html b/_modules/vineyard/io/byte.html new file mode 100644 index 0000000000..bf8d642e67 --- /dev/null +++ b/_modules/vineyard/io/byte.html @@ -0,0 +1,611 @@ + + + + + + + + vineyard.io.byte - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for vineyard.io.byte

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020-2023 Alibaba Group Holding Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+''' This module exposes support for ByteStream, that use can used like:
+
+.. code:: python
+
+    # create a builder, then seal it as stream
+    >>> stream = ByteStream.new(client)
+    >>> stream
+    ByteStream <o0001e09ddd98fd70>
+
+    # use write to put chunks
+    >>> writer = stream.open_writer(client)
+    >>> chunk = reader.next(1024)
+    >>> chunk
+    <memory at 0x136ca2ac0>
+    >>> len(chunk)
+    1024
+    >>> chunk.readonly
+    False
+    >>> vineyard.memory_copy(chunk, src=b'abcde', offset=0)
+
+    # mark the stream as finished
+    >>> writer.finish()
+
+    # open a reader
+    >>> reader = stream.open_reader(client)
+    >>> chunk = reader.next()
+    >>> chunk
+    <memory at 0x136d207c0>
+    >>> len(chunk)
+    1234
+    >>> chunk.readonly
+    True
+    >>> bytes(chunk[:10])
+    b'abcde\x00\x00\x00\x00\x00'
+
+    # the reader reaches the end of the stream
+    >>> chunk = reader.next()
+    ---------------------------------------------------------------------------
+    StreamDrainedException                    Traceback (most recent call last)
+    ~/libvineyard/python/vineyard/io/byte.py in next(self)
+        108
+    --> 109         def next(self) -> memoryview:
+        110             try:
+
+    StreamDrainedException: Stream drain: Stream drained: no more chunks
+
+    The above exception was the direct cause of the following exception:
+
+    StopIteration                             Traceback (most recent call last)
+    <ipython-input-11-d8809de11870> in <module>
+    ----> 1 chunk = reader.next()
+
+    ~/libvineyard/python/vineyard/io/byte.py in next(self)
+        109         def next(self) -> memoryview:
+        110             try:
+    --> 111                 return self._client.next_buffer_chunk(self._stream)
+        112             except StreamDrainedException as e:
+        113                 raise StopIteration('No more chunks') from e
+
+    StopIteration: No more chunks
+'''
+
+import json
+from io import BytesIO
+from typing import Dict
+
+from vineyard._C import ObjectID
+from vineyard._C import ObjectMeta
+from vineyard._C import StreamDrainedException
+from vineyard._C import memory_copy
+from vineyard.io.stream import BaseStream
+
+
+
[docs]class ByteStream(BaseStream): + def __init__(self, meta: ObjectMeta, params: Dict = None): + super().__init__(meta) + self._params = params + + @property + def params(self): + return self._params + + @staticmethod + def new(client, params: Dict = None, meta: ObjectMeta = None) -> "ByteStream": + if meta is None: + meta = ObjectMeta() + meta['typename'] = 'vineyard::ByteStream' + if params is None: + params = dict() + meta['params_'] = params + meta = client.create_metadata(meta) + client.create_stream(meta.id) + return ByteStream(meta, params) + + class Reader(BaseStream.Reader): + def __init__(self, client, stream: ObjectID): + super().__init__(client, stream) + + def next(self) -> memoryview: + try: + return self._client.next_buffer_chunk(self._stream) + except StreamDrainedException as e: + raise StopIteration('No more chunks') from e + + class Writer(BaseStream.Writer): + def __init__(self, client, stream: ObjectID): + super().__init__(client, stream) + + self._buffer_size_limit = 1024 * 1024 * 256 + self._buffer = BytesIO() + + @property + def buffer_size_limit(self): + return self._buffer_size_limit + + @buffer_size_limit.setter + def buffer_size_limit(self, value: int): + self._buffer_size_limit = value + + def next(self, size: int) -> memoryview: + return self._client.new_buffer_chunk(self._stream, size) + + def write(self, data: bytes): + self._buffer.write(data) + self._try_flush_buffer() + + def _try_flush_buffer(self, force=False): + view = self._buffer.getbuffer() + if len(view) >= self._buffer_size_limit or (force and len(view) > 0): + if len(view) > 0: + chunk = self.next(len(view)) + memory_copy(chunk, view) + self._buffer = BytesIO() + + def finish(self): + self._try_flush_buffer(True) + return self._client.stop_stream(self._stream, False) + + def _open_new_reader(self, client): + return ByteStream.Reader(client, self.id) + + def _open_new_writer(self, client): + return ByteStream.Writer(client, self.id)
+ + +def byte_stream_resolver(obj): + meta = obj.meta + if 'params_' in meta: + params = json.loads(meta['params_']) + else: + params = dict + return ByteStream(obj.meta, params) + + +def register_byte_stream_types(_builder_ctx, resolver_ctx): + if resolver_ctx is not None: + resolver_ctx.register('vineyard::ByteStream', byte_stream_resolver) +
+
+
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/vineyard/io/dataframe.html b/_modules/vineyard/io/dataframe.html new file mode 100644 index 0000000000..4cd7e7bc4a --- /dev/null +++ b/_modules/vineyard/io/dataframe.html @@ -0,0 +1,609 @@ + + + + + + + + vineyard.io.dataframe - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for vineyard.io.dataframe

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020-2023 Alibaba Group Holding Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+''' This module exposes support for DataframeStream, that use can used like:
+
+.. code:: python
+
+    # create a builder, then seal it as stream
+    >>> stream = DataframeStream.new(client)
+    >>> stream = builder.seal(client)
+    >>> stream
+    DataframeStream <o0001e09ddd98fd70>
+
+    # use write to put chunks
+    >>> writer = stream.open_writer(client)
+    >>> writer.write_table(
+            pa.Table.from_pandas(
+                pd.DataFrame({"x": [1,2,3], "y": [4,5,6]})))
+
+    # mark the stream as finished
+    >>> writer.finish()
+
+    # open a reader
+    >>> reader = stream.open_reader(client)
+    >>> batch = reader.next()
+    >>> batch
+    pyarrow.RecordBatch
+    x: int64
+    y: int64
+
+    # the reader reaches the end of the stream
+    >>> batch = reader.next()
+    ---------------------------------------------------------------------------
+    StreamDrainedException                    Traceback (most recent call last)
+    ~/libvineyard/python/vineyard/io/dataframe.py in next(self)
+        97             try:
+    ---> 98                 buffer = self._client.next_buffer_chunk(self._stream)
+        99                 with pa.ipc.open_stream(buffer) as reader:
+
+    StreamDrainedException: Stream drain: Stream drained: no more chunks
+
+    The above exception was the direct cause of the following exception:
+
+    StopIteration                             Traceback (most recent call last)
+    <ipython-input-11-10f09bf65f8a> in <module>
+    ----> 1 batch = reader.next()
+
+    ~/libvineyard/python/vineyard/io/dataframe.py in next(self)
+        100                     return reader.read_next_batch()
+        101             except StreamDrainedException as e:
+    --> 102                 raise StopIteration('No more chunks') from e
+        103
+        104         def __str__(self) -> str:
+
+    StopIteration: No more chunks
+'''
+
+import contextlib
+import json
+from io import BytesIO
+from typing import Dict
+
+import pyarrow as pa
+import pyarrow.ipc  # pylint: disable=unused-import
+
+from vineyard._C import ObjectID
+from vineyard._C import ObjectMeta
+from vineyard._C import StreamDrainedException
+from vineyard._C import memory_copy
+from vineyard.io.stream import BaseStream
+
+
+
[docs]class DataframeStream(BaseStream): + def __init__(self, meta: ObjectMeta, params: Dict = None): + super().__init__(meta) + self._params = params + + @property + def params(self): + return self._params + + @staticmethod + def new(client, params: Dict = None, meta: ObjectMeta = None) -> "DataframeStream": + if meta is None: + meta = ObjectMeta() + meta['typename'] = 'vineyard::DataframeStream' + if params is None: + params = dict() + meta['params_'] = params + meta = client.create_metadata(meta) + client.create_stream(meta.id) + return DataframeStream(meta, params) + + class Reader(BaseStream.Reader): + def __init__(self, client, stream: ObjectID): + super().__init__(client, stream) + + def next(self) -> pa.RecordBatch: + try: + buffer = self._client.next_buffer_chunk(self._stream) + with pa.ipc.open_stream(buffer) as reader: + return reader.read_next_batch() + except StreamDrainedException as e: + raise StopIteration('No more chunks') from e + + def read_table(self) -> pa.Table: + batches = [] + while True: + try: + batches.append(self.next()) + except StopIteration: + break + return pa.Table.from_batches(batches) + + class Writer(BaseStream.Writer): + def __init__(self, client, stream: ObjectID): + super().__init__(client, stream) + + self._buffer = BytesIO() + + def next(self, size: int) -> memoryview: + return self._client.new_buffer_chunk(self._stream, size) + + def write(self, batch: pa.RecordBatch): + sink = BytesIO() + with pa.ipc.new_stream(sink, batch.schema) as writer: + writer.write(batch) + view = sink.getbuffer() + if len(view) > 0: + buffer = self.next(len(view)) + memory_copy(buffer, view) + + def write_table(self, table: pa.Table): + for batch in table.to_batches(): + self.write(batch) + + def finish(self): + return self._client.stop_stream(self._stream, False) + + def _open_new_reader(self, client): + return DataframeStream.Reader(client, self.id) + + def _open_new_writer(self, client): + return DataframeStream.Writer(client, self.id)
+ + +def dataframe_stream_resolver(obj): + meta = obj.meta + if 'params_' in meta: + params = json.loads(meta['params_']) + else: + params = dict + return DataframeStream(obj.meta, params) + + +def register_dataframe_stream_types(_builder_ctx, resolver_ctx): + if resolver_ctx is not None: + resolver_ctx.register('vineyard::DataframeStream', dataframe_stream_resolver) +
+
+
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/vineyard/io/recordbatch.html b/_modules/vineyard/io/recordbatch.html new file mode 100644 index 0000000000..7e9fc06ac5 --- /dev/null +++ b/_modules/vineyard/io/recordbatch.html @@ -0,0 +1,515 @@ + + + + + + + + vineyard.io.recordbatch - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for vineyard.io.recordbatch

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020-2023 Alibaba Group Holding Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+''' This module exposes support for RecordBatchStream.
+'''
+
+import contextlib
+import json
+from typing import Any
+from typing import Dict
+from typing import Optional
+
+from vineyard._C import ObjectMeta
+from vineyard.core import context
+from vineyard.io.stream import BaseStream
+
+
+
[docs]class RecordBatchStream(BaseStream): + def __init__(self, meta: ObjectMeta, params: Optional[Dict[str, Any]] = None): + super().__init__(meta) + self._params = params + + @property + def params(self): + return self._params + + @staticmethod + def new( + client, + params: Optional[Dict[str, Any]] = None, + meta: Optional[ObjectMeta] = None, + ) -> "RecordBatchStream": + if meta is None: + meta = ObjectMeta() + meta['typename'] = 'vineyard::RecordBatchStream' + if params is None: + params = dict() + meta['params_'] = params + meta = client.create_metadata(meta) + client.create_stream(meta.id) + return RecordBatchStream(meta, params)
+ + +def recordbatch_stream_resolver(obj, resolver): # pylint: disable=unused-argument + meta = obj.meta + if 'params_' in meta: + params = json.loads(meta['params_']) + else: + params = dict + return RecordBatchStream(meta, params) + + +def register_recordbatch_stream_types(_builder_ctx, resolver_ctx): + if resolver_ctx is not None: + resolver_ctx.register( + 'vineyard::RecordBatchStream', recordbatch_stream_resolver + ) + + +@contextlib.contextmanager +def recordbatch_stream_context(): + with context() as (builder_ctx, resolver_ctx): + register_recordbatch_stream_types(builder_ctx, resolver_ctx) + yield builder_ctx, resolver_ctx +
+
+
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/vineyard/io/stream.html b/_modules/vineyard/io/stream.html new file mode 100644 index 0000000000..1a35f9d826 --- /dev/null +++ b/_modules/vineyard/io/stream.html @@ -0,0 +1,892 @@ + + + + + + + + vineyard.io.stream - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for vineyard.io.stream

+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020-2023 Alibaba Group Holding Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import json
+import logging
+import traceback
+from typing import Callable
+from typing import Dict
+from typing import List
+from typing import Optional
+from urllib.parse import urlparse
+
+from vineyard._C import ObjectID
+from vineyard._C import ObjectMeta
+from vineyard._C import StreamDrainedException
+from vineyard.core.driver import registerize
+from vineyard.core.resolver import resolver_context
+
+logger = logging.getLogger('vineyard')
+
+
+
[docs]@registerize +def read( + path, + *args, + handlers=None, + accumulate=False, + chunk_hook: Optional[Callable] = None, + **kwargs +): + """Open a path and read it as a single stream. + + Parameters + ---------- + path: str + Path to read, the last reader registered for the scheme of + the path will be used. + handlers: list, optional + If handlers is not None, launched worker processes will be + emplaced into the list for further customized job lifecycle + management. Default is None. + accumulate: bool, optional + If :code:`accumulate` is True, it will return a data frame, + rather than dataframe stream. Default is False. + chunk_hook: callable, optional + If the read/write target is a global dataframe (e.g., csv, + orc, parquet, etc.), the hook will be called for each chunk + to be read or write (usually a :code:`pyarrow.RecordBatch`). + The hook should return a :code:`pyarrow.RecordBatch` object + and should be stateless as the invoke order is not guaranteed. + E.g., + + .. code:: python + + def exchange_column(batch): + import pyarrow as pa + + columns = batch.columns + first = columns[0] + second = columns[1] + columns = [second, first] + columns[2:] + return pa.RecordBatch.from_arrays(columns, schema=batch.schema) + + vineyard_ipc_socket: str + The local or remote vineyard's IPC socket location that the + remote readers will use to establish connections with the + vineyard server. + vineyard_endpoint: str, optional + An optional address of vineyard's RPC socket, which will be + used for retrieving server's information on the client side. + If not provided, the `vineyard_ipc_socket` will be used, or + it will tries to discovery vineyard's IPC or RPC endpoints + from environment variables. + """ + parsed = urlparse(path) + if read._factory and read._factory.get(parsed.scheme): + errors = [] + for reader in read._factory[parsed.scheme][::-1]: + try: + proc_kwargs = kwargs.copy() + r = reader( + path, + proc_kwargs.pop('vineyard_ipc_socket'), + *args, + handlers=handlers, + accumulate=accumulate, + chunk_hook=chunk_hook, + **proc_kwargs + ) + if r is not None: + return r + except Exception: # pylint: disable=broad-except + errors.append('%s: %s' % (reader.__name__, traceback.format_exc())) + raise RuntimeError( + 'Unable to find a proper IO driver for %s, potential causes are:\n %s' + % (path, '\n'.join(errors)) + ) + else: + raise ValueError("No IO driver registered for %s" % path)
+ + +
[docs]@registerize +def write( + path, stream, *args, handlers=None, chunk_hook: Optional[Callable] = None, **kwargs +): + """Write the stream to a given path. + + Parameters + ---------- + path: str + Path to write, the last writer registered for the scheme of the path + will be used. + stream: vineyard stream + Stream that produces the data to write. + handlers: list, optional + If handlers is not None, launched worker processes will be + emplaced into the list for further customized job lifecycle + management. Default is None. + chunk_hook: callable, optional + If the read/write target is a global dataframe (e.g., csv, + orc, parquet, etc.), the hook will be called for each chunk + to be read or write (usually a :code:`pyarrow.RecordBatch`). + The hook should return a :code:`pyarrow.RecordBatch` object + and should be stateless as the invoke order is not guaranteed. + E.g., + + .. code:: python + + def exchange_column(batch): + import pyarrow as pa + + columns = batch.columns + first = columns[0] + second = columns[1] + columns = [second, first] + columns[2:] + return pa.RecordBatch.from_arrays(columns, schema=batch.schema) + + vineyard_ipc_socket: str + The local or remote vineyard's IPC socket location that the remote + readers will use to establish connections with the vineyard server. + vineyard_endpoint: str, optional + An optional address of vineyard's RPC socket, which will be used for + retrieving server's information on the client side. If not provided, + the `vineyard_ipc_socket` will be used, or it will tries to discovery + vineyard's IPC or RPC endpoints from environment variables. + """ + parsed = urlparse(path) + if write._factory and write._factory.get(parsed.scheme): + errors = [] + for writer in write._factory[parsed.scheme][::-1]: + try: + proc_kwargs = kwargs.copy() + writer( + path, + stream, + proc_kwargs.pop('vineyard_ipc_socket'), + *args, + handlers=handlers, + chunk_hook=chunk_hook, + **proc_kwargs + ) + except Exception: # pylint: disable=broad-except + exc = traceback.format_exc() + errors.append('%s: %s' % (writer.__name__, exc)) + if 'StreamFailedException: Stream failed' in exc: + # if the source stream has already failed, we should + # fail immediately and don't try other drivers. + break + continue + else: + return + raise RuntimeError( + 'Unable to find a proper IO driver for %s, potential causes are:\n %s' + % (path, '\n'.join(errors)) + ) + else: + raise ValueError("No IO driver registered for %s" % path)
+ + +
[docs]def open( + path, + *args, + mode='r', + handlers=None, + chunk_hook: Optional[Callable] = None, + **kwargs +): + """Open a path as a reader or writer, depends on the parameter :code:`mode`. + If :code:`mode` is :code:`r`, it will open a stream for read, and open a + stream for write when :code:`mode` is :code:`w`. + + Parameters + ---------- + path: str + Path to open. + mode: char + Mode about how to open the path, :code:`r` is for read and :code:`w` for write. + handlers: + A dict that will be filled with a :code:`handler` that contains the process + handler of the underlying read/write process that can be joined using + :code:`join` to capture the possible errors during the I/O proceeding. + chunk_hook: callable, optional + If the read/write target is a global dataframe (e.g., csv, + orc, parquet, etc.), the hook will be called for each chunk + to be read or write (usually a :code:`pyarrow.RecordBatch`). + The hook should return a :code:`pyarrow.RecordBatch` object + and should be stateless as the invoke order is not guaranteed. + E.g., + + .. code:: python + + def exchange_column(batch): + import pyarrow as pa + + columns = batch.columns + first = columns[0] + second = columns[1] + columns = [second, first] + columns[2:] + return pa.RecordBatch.from_arrays(columns, schema=batch.schema) + + vineyard_ipc_socket: str + Vineyard's IPC socket location. + vineyard_endpoint: str + Vineyard's RPC socket address. + + See Also + -------- + vineyard.io.read + vineyard.io.write + """ + parsed = urlparse(path) + if not parsed.scheme: + path = 'file://' + path + + if mode == 'r': + return read(path, *args, handlers=handlers, chunk_hook=chunk_hook, **kwargs) + + if mode == 'w': + return write(path, *args, handlers=handlers, chunk_hook=chunk_hook, **kwargs) + + raise RuntimeError('Opening %s with mode %s is not supported' % (path, mode))
+ + +class BaseStream: + class Reader: + def __init__(self, client, stream: ObjectID, resolver=None): + self._client = client + self._stream = stream + self._resolver = resolver + self._client.open_stream(stream, 'r') + + def next(self) -> object: + try: + chunk = self._client.next_chunk(self._stream) + except StreamDrainedException as e: + raise StopIteration('No more chunks') from e + + if self._resolver is not None: + return self._resolver(chunk) + else: + with resolver_context() as ctx: + return ctx.run(chunk) + + def next_metadata(self) -> ObjectMeta: + try: + return self._client.next_chunk_meta(self._stream) + except StreamDrainedException as e: + raise StopIteration('No more chunks') from e + + def __str__(self) -> str: + return repr(self) + + def __repr__(self) -> str: + return '%s of Stream <%r>' % (self.__class__, self._stream) + + class Writer: + def __init__(self, client, stream: ObjectID): + self._client = client + self._stream = stream + self._client.open_stream(stream, 'w') + + def next(self, size: int) -> memoryview: + return self._client.new_buffer_chunk(self._stream, size) + + def append(self, chunk: ObjectID): + return self._client.push_chunk(self._stream, chunk) + + def fail(self): + return self._client.stop_stream(self._stream, True) + + def finish(self): + return self._client.stop_stream(self._stream, False) + + def __str__(self) -> str: + return repr(self) + + def __repr__(self) -> str: + return '%s of Stream <%r>' % (self.__class__, self._stream) + + def __init__(self, meta: ObjectMeta, resolver=None): + self._meta = meta + self._stream = meta.id + self._resolver = resolver + + self._reader = None + self._writer = None + + @property + def id(self) -> ObjectID: + return self._stream + + @property + def meta(self) -> ObjectMeta: + return self._meta + + @property + def reader(self) -> "BaseStream.Reader": + return self.open_reader() + + def __str__(self) -> str: + return repr(self) + + def __repr__(self) -> str: + return '%s <%r>' % (self.__class__.__name__, self._stream) + + def _open_new_reader(self, client) -> "BaseStream.Reader": + '''Always open a new reader.''' + return BaseStream.Reader(client, self.id, self._resolver) + + def open_reader(self, client=None) -> "BaseStream.Reader": + if self._reader is None: + if client is None: + client = self._meta._client + self._reader = self._open_new_reader(client) + return self._reader + + @property + def writer(self) -> "BaseStream.Writer": + return self.open_writer() + + def _open_new_writer(self, client) -> "BaseStream.Writer": + return BaseStream.Writer(client, self.id) + + def open_writer(self, client=None) -> "BaseStream.Writer": + if self._writer is None: + if client is None: + client = self._meta._client + self._writer = self._open_new_writer(client) + return self._writer + + def drop(self, client=None): + if client is None: + client = self._meta._client + if hasattr(client, 'drop_stream'): + client.drop_stream(self.id) + + +class StreamCollection: + """A stream collection is a set of stream, where each element is a stream, or, + another stream collection. + """ + + KEY_OF_STREAMS = '__streams' + KEY_OF_PATH = '__path' + KEY_OF_GLOBAL = '__global' + KEY_OF_OPTIONS = '__options' + + def __init__(self, meta: ObjectMeta, streams: List[ObjectID]): + self._meta = meta + self._streams = streams + if StreamCollection.KEY_OF_GLOBAL in self._meta: + self._global = self._meta[StreamCollection.KEY_OF_GLOBAL] + else: + self._global = False + + @staticmethod + def new( + client, metadata: Dict, streams: List[ObjectID], meta: ObjectMeta = None + ) -> "StreamCollection": + if meta is None: + meta = ObjectMeta() + meta['typename'] = 'vineyard::StreamCollection' + for k, v in metadata.items(): + if k not in [ + 'id', + 'signature', + 'instance_id', + 'transient', + 'global', + 'typename', + ]: + meta[k] = v + meta[StreamCollection.KEY_OF_STREAMS] = [int(s) for s in streams] + meta = client.create_metadata(meta) + return StreamCollection(meta, streams) + + @property + def id(self): + return self.meta.id + + @property + def meta(self): + return self._meta + + @property + def isglobal(self): + return self._global + + @property + def streams(self): + return self._streams + + def __repr__(self) -> str: + return "StreamCollection: %s [%s]" % ( + repr(self.id), + [repr(s) for s in self.streams], + ) + + def __str__(self) -> str: + return repr(self) + + +def stream_collection_resolver(obj): + meta = obj.meta + streams = json.loads(meta[StreamCollection.KEY_OF_STREAMS]) + return StreamCollection(meta, [ObjectID(s) for s in streams]) + + +def register_stream_collection_types(_builder_ctx, resolver_ctx): + if resolver_ctx is not None: + resolver_ctx.register('vineyard::StreamCollection', stream_collection_resolver) + + +__all__ = [ + 'open', + 'read', + 'write', + 'BaseStream', + 'StreamCollection', + 'register_stream_collection_types', +] +
+
+
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/vineyard/shared_memory/shared_memory.html b/_modules/vineyard/shared_memory/shared_memory.html new file mode 100644 index 0000000000..fe39e207fc --- /dev/null +++ b/_modules/vineyard/shared_memory/shared_memory.html @@ -0,0 +1,813 @@ + + + + + + + + vineyard.shared_memory.shared_memory - Vineyard + + + + + + + + + + + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark mode + + + + + + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+
+
Vineyard
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for vineyard.shared_memory.shared_memory

+#! /usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020-2023 Alibaba Group Holding Limited.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+'''
+The shared_memory module provides similar interface like multiprocessing.shared_memory
+for direct access shared memory backed by vineyard across processes.
+
+The API is kept consistent with multiprocessing.shared_memory but the semantics is
+slightly different. For vineyard, to make the shared memory visible for other process,
+a explicitly ``seal`` or ``close`` operation is needed.
+
+Refer to the documentation of multiprocessing.shared_memory for details.
+'''
+
+# pylint: skip-file
+
+try:
+    import multiprocessing.shared_memory as shm
+except ImportError:
+    # multiprocessing.shared_memory is available since Python 3.8, we use the slim
+    # library for earlier version of Python.
+    #
+    # see also github #327.
+    import shared_memory as shm
+
+import struct
+import warnings
+
+from vineyard._C import ObjectID
+
+
+
[docs]class SharedMemory: + def __init__(self, vineyard_client, name=None, create=False, size=0): + """Create or obtain a shared memory block that backed by vineyard. + + Parameters + ---------- + vineyard_client: + The vineyard IPC or RPC client. + name: + The vineyard ObjectID, could be vineyard.ObjectID, int or stringified + ObjectID. + create: + Whether to create a new shared memory block or just obtain existing one. + size: + Size of the shared memory block. + + See Also + -------- + multiprocessing.shared_memory.SharedMemory + """ + if not size >= 0: + raise ValueError("'size' must be a positive integer") + if create: + if size == 0: + raise ValueError( + "'size' must be a positive number " "different from zero" + ) + if name is not None: + raise ValueError("'name' can only be None if create=True") + else: + if size != 0: + warnings.warn( + "'size' will take no effect if create=False", + ) + if name is None: + raise ValueError("'name' cannot be None if create=False") + + self._name = None + self._size = None + self._buf = None + self._blob, self._blob_builder = None, None + self._vineyard_client = vineyard_client + + if create: + self._blob_builder = vineyard_client.create_blob(size) + self._name = self._blob_builder.id + self._size = size + self._buf = memoryview(self._blob_builder) + else: + self._blob = vineyard_client.get_object(ObjectID(name)) + self._name = self._blob.id + self._size = self._blob.size + self._buf = memoryview(self._blob) + + def __del__(self): + try: + self.close() + except Exception: + pass + + def __reduce__(self): + return ( + self.__class__, + ( + self.name, + False, + self.size, + ), + ) + + def __repr__(self): + return f'{self.__class__.__name__}({self.name!r}, size={self.size})' + + @property + def buf(self): + "A memoryview of contents of the shared memory block." + return self._buf + + @property + def name(self): + "Unique name that identifies the shared memory block." + return repr(self._name) + + @property + def size(self): + "Size in bytes." + return self._size + +
[docs] def freeze(self): + "Seal the shared memory to make it visible for other processes." + if self._blob_builder: + self._blob = self._blob_builder.seal(self._vineyard_client) + self._blob_builder = None + return self
+ + def close(self): + self.freeze() + +
+ + +_encoding = "utf8" + + +
[docs]class ShareableList(shm.ShareableList): + """ + Pattern for a mutable list-like object shareable via a shared + memory block. It differs from the built-in list type in that these + lists can not change their overall length (i.e. no append, insert, + etc.) + + Because values are packed into a memoryview as bytes, the struct + packing format for any storable value must require no more than 8 + characters to describe its format. + + The ShareableList in vineyard differs slightly with its equivalent + in the multiprocessing.shared_memory.ShareableList, as it becomes + immutable after obtaining from the vineyard backend. + + See Also + -------- + multiprocessing.shared_memory.ShareableList + """ + + # note that the implementation of ``__init__`` entirely comes from + # multiprocessing.shared_memory. + # + # and note that + # + # https://github.com/python/cpython/commit/c8f1715283ec51822fb37a702bf253cbac1af276 + # + # has made a set of changes to the ``ShareableList`` code. + # + def __init__(self, vineyard_client, sequence=None, *, name=None): + if name is None or sequence is not None: + if name is not None: + warnings.warn( + "'name' will take no effect as we are going to " + "create a ShareableList", + ) + + sequence = sequence or () + _formats = [ + ( + self._types_mapping[type(item)] + if not isinstance(item, (str, bytes)) # noqa: E131 + else self._types_mapping[type(item)] + % ( + self._alignment * (len(item) // self._alignment + 1), + ) # noqa: E131 + ) + for item in sequence + ] + self._list_len = len(_formats) + assert sum(len(fmt) <= 8 for fmt in _formats) == self._list_len + offset = 0 + # The offsets of each list element into the shared memory's + # data area (0 meaning the start of the data area, not the start + # of the shared memory area). + self._allocated_offsets = [0] + for fmt in _formats: + offset += self._alignment if fmt[-1] != "s" else int(fmt[:-1]) + self._allocated_offsets.append(offset) + _recreation_codes = [ + self._extract_recreation_code(item) for item in sequence + ] + requested_size = struct.calcsize( + "q" + + self._format_size_metainfo + + "".join(_formats) + + self._format_packing_metainfo + + self._format_back_transform_codes + ) + + self.shm = SharedMemory(vineyard_client, create=True, size=requested_size) + else: + self.shm = SharedMemory(vineyard_client, name) + + if sequence is not None: + _enc = _encoding + struct.pack_into( + "q" + self._format_size_metainfo, + self.shm.buf, + 0, + self._list_len, + *(self._allocated_offsets), + ) + struct.pack_into( + "".join(_formats), + self.shm.buf, + self._offset_data_start, + *(v.encode(_enc) if isinstance(v, str) else v for v in sequence), + ) + struct.pack_into( + self._format_packing_metainfo, + self.shm.buf, + self._offset_packing_formats, + *(v.encode(_enc) for v in _formats), + ) + struct.pack_into( + self._format_back_transform_codes, + self.shm.buf, + self._offset_back_transform_codes, + *(_recreation_codes), + ) + + else: + self._list_len = len(self) # Obtains size from offset 0 in buffer. + self._allocated_offsets = list( + struct.unpack_from(self._format_size_metainfo, self.shm.buf, 1 * 8) + ) + + def _get_back_transform(self, position): + "Gets the back transformation function for a single value." + + if (position >= self._list_len) or (self._list_len < 0): + raise IndexError("Requested position out of range.") + + transform_code = struct.unpack_from( + "b", self.shm.buf, self._offset_back_transform_codes + position + )[0] + transform_function = self._back_transforms_mapping[transform_code] + + return transform_function + + def _set_packing_format_and_transform(self, position, fmt_as_str, value): + """Sets the packing format and back transformation code for a + single value in the list at the specified position.""" + + if (position >= self._list_len) or (self._list_len < 0): + raise IndexError("Requested position out of range.") + + struct.pack_into( + "8s", + self.shm.buf, + self._offset_packing_formats + position * 8, + fmt_as_str.encode(_encoding), + ) + + transform_code = self._extract_recreation_code(value) + struct.pack_into( + "b", + self.shm.buf, + self._offset_back_transform_codes + position, + transform_code, + ) + + def __getitem__(self, position): + position = position if position >= 0 else position + self._list_len + try: + offset = self._offset_data_start + self._allocated_offsets[position] + (v,) = struct.unpack_from( + self._get_packing_format(position), self.shm.buf, offset + ) + except IndexError: + raise IndexError("index out of range") + + back_transform = self._get_back_transform(position) + v = back_transform(v) + + return v + + def __setitem__(self, position, value): + position = position if position >= 0 else position + self._list_len + try: + item_offset = self._allocated_offsets[position] + offset = self._offset_data_start + item_offset + current_format = self._get_packing_format(position) + except IndexError: + raise IndexError("assignment index out of range") + + if not isinstance(value, (str, bytes)): + new_format = self._types_mapping[type(value)] + encoded_value = value + else: + allocated_length = self._allocated_offsets[position + 1] - item_offset + + encoded_value = value.encode(_encoding) if isinstance(value, str) else value + if len(encoded_value) > allocated_length: + raise ValueError("bytes/str item exceeds available storage") + if current_format[-1] == "s": + new_format = current_format + else: + new_format = self._types_mapping[str] % (allocated_length,) + + self._set_packing_format_and_transform(position, new_format, value) + struct.pack_into(new_format, self.shm.buf, offset, encoded_value) + + @property + def _format_size_metainfo(self): + "The struct packing format used for the items' storage offsets." + return "q" * (self._list_len + 1) + + @property + def _format_packing_metainfo(self): + "The struct packing format used for the items' packing formats." + return "8s" * self._list_len + + @property + def _format_back_transform_codes(self): + "The struct packing format used for the items' back transforms." + return "b" * self._list_len + + @property + def _offset_data_start(self): + # - 8 bytes for the list length + # - (N + 1) * 8 bytes for the element offsets + return (self._list_len + 2) * 8 + + @property + def _offset_packing_formats(self): + return self._offset_data_start + self._allocated_offsets[-1] + + @property + def _offset_back_transform_codes(self): + return self._offset_packing_formats + self._list_len * 8 + +
[docs] def freeze(self): + '''Make the shareable list immutable and visible for other vineyard clients.''' + self.shm.freeze()
+ + +__all__ = ['SharedMemory', 'ShareableList'] +
+
+
+
+ + + + + + + + + +
+
+ + Rendered with Sphinx and Furo +

The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, + please see our Trademark Usage page. +

+
+ +
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/_panels_static/panels-bootstrap.5fd3999ee7762ccc51105388f4a9d115.css b/_panels_static/panels-bootstrap.5fd3999ee7762ccc51105388f4a9d115.css new file mode 100644 index 0000000000..1b057df2f2 --- /dev/null +++ b/_panels_static/panels-bootstrap.5fd3999ee7762ccc51105388f4a9d115.css @@ -0,0 +1 @@ +.badge{border-radius:.25rem;display:inline-block;font-size:75%;font-weight:700;line-height:1;padding:.25em .4em;text-align:center;vertical-align:baseline;white-space:nowrap}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{border-radius:10rem;padding-left:.6em;padding-right:.6em}.badge-primary{background-color:#007bff;color:#fff}.badge-primary[href]:focus,.badge-primary[href]:hover{background-color:#0062cc;color:#fff;text-decoration:none}.badge-secondary{background-color:#6c757d;color:#fff}.badge-secondary[href]:focus,.badge-secondary[href]:hover{background-color:#545b62;color:#fff;text-decoration:none}.badge-success{background-color:#28a745;color:#fff}.badge-success[href]:focus,.badge-success[href]:hover{background-color:#1e7e34;color:#fff;text-decoration:none}.badge-info{background-color:#17a2b8;color:#fff}.badge-info[href]:focus,.badge-info[href]:hover{background-color:#117a8b;color:#fff;text-decoration:none}.badge-warning{background-color:#ffc107;color:#212529}.badge-warning[href]:focus,.badge-warning[href]:hover{background-color:#d39e00;color:#212529;text-decoration:none}.badge-danger{background-color:#dc3545;color:#fff}.badge-danger[href]:focus,.badge-danger[href]:hover{background-color:#bd2130;color:#fff;text-decoration:none}.badge-light{background-color:#f8f9fa;color:#212529}.badge-light[href]:focus,.badge-light[href]:hover{background-color:#dae0e5;color:#212529;text-decoration:none}.badge-dark{background-color:#343a40;color:#fff}.badge-dark[href]:focus,.badge-dark[href]:hover{background-color:#1d2124;color:#fff;text-decoration:none}.border-0{border:0 !important}.border-top-0{border-top:0 !important}.border-right-0{border-right:0 !important}.border-bottom-0{border-bottom:0 !important}.border-left-0{border-left:0 !important}.p-0{padding:0 !important}.pt-0,.py-0{padding-top:0 !important}.pr-0,.px-0{padding-right:0 !important}.pb-0,.py-0{padding-bottom:0 !important}.pl-0,.px-0{padding-left:0 !important}.p-1{padding:.25rem !important}.pt-1,.py-1{padding-top:.25rem !important}.pr-1,.px-1{padding-right:.25rem !important}.pb-1,.py-1{padding-bottom:.25rem !important}.pl-1,.px-1{padding-left:.25rem !important}.p-2{padding:.5rem !important}.pt-2,.py-2{padding-top:.5rem !important}.pr-2,.px-2{padding-right:.5rem !important}.pb-2,.py-2{padding-bottom:.5rem !important}.pl-2,.px-2{padding-left:.5rem !important}.p-3{padding:1rem !important}.pt-3,.py-3{padding-top:1rem !important}.pr-3,.px-3{padding-right:1rem !important}.pb-3,.py-3{padding-bottom:1rem !important}.pl-3,.px-3{padding-left:1rem !important}.p-4{padding:1.5rem !important}.pt-4,.py-4{padding-top:1.5rem !important}.pr-4,.px-4{padding-right:1.5rem !important}.pb-4,.py-4{padding-bottom:1.5rem !important}.pl-4,.px-4{padding-left:1.5rem !important}.p-5{padding:3rem !important}.pt-5,.py-5{padding-top:3rem !important}.pr-5,.px-5{padding-right:3rem !important}.pb-5,.py-5{padding-bottom:3rem !important}.pl-5,.px-5{padding-left:3rem !important}.m-0{margin:0 !important}.mt-0,.my-0{margin-top:0 !important}.mr-0,.mx-0{margin-right:0 !important}.mb-0,.my-0{margin-bottom:0 !important}.ml-0,.mx-0{margin-left:0 !important}.m-1{margin:.25rem !important}.mt-1,.my-1{margin-top:.25rem !important}.mr-1,.mx-1{margin-right:.25rem !important}.mb-1,.my-1{margin-bottom:.25rem !important}.ml-1,.mx-1{margin-left:.25rem !important}.m-2{margin:.5rem !important}.mt-2,.my-2{margin-top:.5rem !important}.mr-2,.mx-2{margin-right:.5rem !important}.mb-2,.my-2{margin-bottom:.5rem !important}.ml-2,.mx-2{margin-left:.5rem !important}.m-3{margin:1rem !important}.mt-3,.my-3{margin-top:1rem !important}.mr-3,.mx-3{margin-right:1rem !important}.mb-3,.my-3{margin-bottom:1rem !important}.ml-3,.mx-3{margin-left:1rem !important}.m-4{margin:1.5rem !important}.mt-4,.my-4{margin-top:1.5rem !important}.mr-4,.mx-4{margin-right:1.5rem !important}.mb-4,.my-4{margin-bottom:1.5rem !important}.ml-4,.mx-4{margin-left:1.5rem !important}.m-5{margin:3rem !important}.mt-5,.my-5{margin-top:3rem !important}.mr-5,.mx-5{margin-right:3rem !important}.mb-5,.my-5{margin-bottom:3rem !important}.ml-5,.mx-5{margin-left:3rem !important}.btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;color:#212529;cursor:pointer;display:inline-block;font-size:1rem;font-weight:400;line-height:1.5;padding:.375rem .75rem;text-align:center;transition:color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none;vertical-align:middle}.btn:hover{color:#212529;text-decoration:none}.btn:visited{color:#212529}.btn.focus,.btn:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,0.25);outline:0}.btn.disabled,.btn:disabled{opacity:.65}@media (prefers-reduced-motion: reduce){.btn{transition:none}}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{background-color:#007bff;border-color:#007bff;color:#fff}.btn-primary:visited{color:#fff}.btn-primary:hover{background-color:#0069d9;border-color:#0062cc;color:#fff}.btn-primary.focus,.btn-primary:focus{background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(0,123,255,0.5);color:#fff}.btn-primary.disabled,.btn-primary:disabled{background-color:#007bff;border-color:#007bff;color:#fff}.btn-primary.active:not(:disabled):not(.disabled),.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{background-color:#0062cc;border-color:#005cbf;color:#fff}.btn-primary.active:not(:disabled):not(.disabled):focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,0.5)}.btn-secondary{background-color:#6c757d;border-color:#6c757d;color:#fff}.btn-secondary:visited{color:#fff}.btn-secondary:hover{background-color:#5a6268;border-color:#545b62;color:#fff}.btn-secondary.focus,.btn-secondary:focus{background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(108,117,125,0.5);color:#fff}.btn-secondary.disabled,.btn-secondary:disabled{background-color:#6c757d;border-color:#6c757d;color:#fff}.btn-secondary.active:not(:disabled):not(.disabled),.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{background-color:#545b62;border-color:#4e555b;color:#fff}.btn-secondary.active:not(:disabled):not(.disabled):focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,0.5)}.btn-success{background-color:#28a745;border-color:#28a745;color:#fff}.btn-success:visited{color:#fff}.btn-success:hover{background-color:#218838;border-color:#1e7e34;color:#fff}.btn-success.focus,.btn-success:focus{background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(40,167,69,0.5);color:#fff}.btn-success.disabled,.btn-success:disabled{background-color:#28a745;border-color:#28a745;color:#fff}.btn-success.active:not(:disabled):not(.disabled),.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{background-color:#1e7e34;border-color:#1c7430;color:#fff}.btn-success.active:not(:disabled):not(.disabled):focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,0.5)}.btn-info{background-color:#17a2b8;border-color:#17a2b8;color:#fff}.btn-info:visited{color:#fff}.btn-info:hover{background-color:#138496;border-color:#117a8b;color:#fff}.btn-info.focus,.btn-info:focus{background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(23,162,184,0.5);color:#fff}.btn-info.disabled,.btn-info:disabled{background-color:#17a2b8;border-color:#17a2b8;color:#fff}.btn-info.active:not(:disabled):not(.disabled),.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{background-color:#117a8b;border-color:#10707f;color:#fff}.btn-info.active:not(:disabled):not(.disabled):focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,0.5)}.btn-warning{background-color:#ffc107;border-color:#ffc107;color:#212529}.btn-warning:visited{color:#212529}.btn-warning:hover{background-color:#e0a800;border-color:#d39e00;color:#212529}.btn-warning.focus,.btn-warning:focus{background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(255,193,7,0.5);color:#212529}.btn-warning.disabled,.btn-warning:disabled{background-color:#ffc107;border-color:#ffc107;color:#212529}.btn-warning.active:not(:disabled):not(.disabled),.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{background-color:#d39e00;border-color:#c69500;color:#212529}.btn-warning.active:not(:disabled):not(.disabled):focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,0.5)}.btn-danger{background-color:#dc3545;border-color:#dc3545;color:#fff}.btn-danger:visited{color:#fff}.btn-danger:hover{background-color:#c82333;border-color:#bd2130;color:#fff}.btn-danger.focus,.btn-danger:focus{background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(220,53,69,0.5);color:#fff}.btn-danger.disabled,.btn-danger:disabled{background-color:#dc3545;border-color:#dc3545;color:#fff}.btn-danger.active:not(:disabled):not(.disabled),.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{background-color:#bd2130;border-color:#b21f2d;color:#fff}.btn-danger.active:not(:disabled):not(.disabled):focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,0.5)}.btn-light{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-light:visited{color:#212529}.btn-light:hover{background-color:#e2e6ea;border-color:#dae0e5;color:#212529}.btn-light.focus,.btn-light:focus{background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(248,249,250,0.5);color:#212529}.btn-light.disabled,.btn-light:disabled{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-light.active:not(:disabled):not(.disabled),.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{background-color:#dae0e5;border-color:#d3d9df;color:#212529}.btn-light.active:not(:disabled):not(.disabled):focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,0.5)}.btn-dark{background-color:#343a40;border-color:#343a40;color:#fff}.btn-dark:visited{color:#fff}.btn-dark:hover{background-color:#23272b;border-color:#1d2124;color:#fff}.btn-dark.focus,.btn-dark:focus{background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(52,58,64,0.5);color:#fff}.btn-dark.disabled,.btn-dark:disabled{background-color:#343a40;border-color:#343a40;color:#fff}.btn-dark.active:not(:disabled):not(.disabled),.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{background-color:#1d2124;border-color:#171a1d;color:#fff}.btn-dark.active:not(:disabled):not(.disabled):focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,0.5)}.btn-outline-primary{border-color:#007bff;color:#007bff}.btn-outline-primary:visited{color:#007bff}.btn-outline-primary:hover{background-color:#007bff;border-color:#007bff;color:#fff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,0.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{background-color:transparent;color:#007bff}.btn-outline-primary.active:not(:disabled):not(.disabled),.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{background-color:#007bff;border-color:#007bff;color:#fff}.btn-outline-primary.active:not(:disabled):not(.disabled):focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,0.5)}.btn-outline-secondary{border-color:#6c757d;color:#6c757d}.btn-outline-secondary:visited{color:#6c757d}.btn-outline-secondary:hover{background-color:#6c757d;border-color:#6c757d;color:#fff}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,0.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{background-color:transparent;color:#6c757d}.btn-outline-secondary.active:not(:disabled):not(.disabled),.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{background-color:#6c757d;border-color:#6c757d;color:#fff}.btn-outline-secondary.active:not(:disabled):not(.disabled):focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,0.5)}.btn-outline-success{border-color:#28a745;color:#28a745}.btn-outline-success:visited{color:#28a745}.btn-outline-success:hover{background-color:#28a745;border-color:#28a745;color:#fff}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,0.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{background-color:transparent;color:#28a745}.btn-outline-success.active:not(:disabled):not(.disabled),.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{background-color:#28a745;border-color:#28a745;color:#fff}.btn-outline-success.active:not(:disabled):not(.disabled):focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,0.5)}.btn-outline-info{border-color:#17a2b8;color:#17a2b8}.btn-outline-info:visited{color:#17a2b8}.btn-outline-info:hover{background-color:#17a2b8;border-color:#17a2b8;color:#fff}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,0.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{background-color:transparent;color:#17a2b8}.btn-outline-info.active:not(:disabled):not(.disabled),.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{background-color:#17a2b8;border-color:#17a2b8;color:#fff}.btn-outline-info.active:not(:disabled):not(.disabled):focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,0.5)}.btn-outline-warning{border-color:#ffc107;color:#ffc107}.btn-outline-warning:visited{color:#ffc107}.btn-outline-warning:hover{background-color:#ffc107;border-color:#ffc107;color:#212529}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,0.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{background-color:transparent;color:#ffc107}.btn-outline-warning.active:not(:disabled):not(.disabled),.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{background-color:#ffc107;border-color:#ffc107;color:#212529}.btn-outline-warning.active:not(:disabled):not(.disabled):focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,0.5)}.btn-outline-danger{border-color:#dc3545;color:#dc3545}.btn-outline-danger:visited{color:#dc3545}.btn-outline-danger:hover{background-color:#dc3545;border-color:#dc3545;color:#fff}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,0.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{background-color:transparent;color:#dc3545}.btn-outline-danger.active:not(:disabled):not(.disabled),.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{background-color:#dc3545;border-color:#dc3545;color:#fff}.btn-outline-danger.active:not(:disabled):not(.disabled):focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,0.5)}.btn-outline-light{border-color:#f8f9fa;color:#f8f9fa}.btn-outline-light:visited{color:#f8f9fa}.btn-outline-light:hover{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,0.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{background-color:transparent;color:#f8f9fa}.btn-outline-light.active:not(:disabled):not(.disabled),.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-outline-light.active:not(:disabled):not(.disabled):focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,0.5)}.btn-outline-dark{border-color:#343a40;color:#343a40}.btn-outline-dark:visited{color:#343a40}.btn-outline-dark:hover{background-color:#343a40;border-color:#343a40;color:#fff}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,0.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{background-color:transparent;color:#343a40}.btn-outline-dark.active:not(:disabled):not(.disabled),.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{background-color:#343a40;border-color:#343a40;color:#fff}.btn-outline-dark.active:not(:disabled):not(.disabled):focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,0.5)}.btn-link{color:#007bff;font-weight:400;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{box-shadow:none;text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{border-radius:.3rem;font-size:1.25rem;line-height:1.5;padding:.5rem 1rem}.btn-group-sm>.btn,.btn-sm{border-radius:.2rem;font-size:.875rem;line-height:1.5;padding:.25rem .5rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input.btn-block[type=button],input.btn-block[type=reset],input.btn-block[type=submit]{width:100%}.stretched-link::after{background-color:rgba(0,0,0,0);bottom:0;content:'';left:0;pointer-events:auto;position:absolute;right:0;top:0;z-index:1}.text-wrap{white-space:normal !important}.card{background-clip:border-box;background-color:#fff;border:1px solid rgba(0,0,0,0.125);border-radius:.25rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.card>hr{margin-left:0;margin-right:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-bottom:0;margin-top:-.375rem}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{background-color:rgba(0,0,0,0.03);border-bottom:1px solid rgba(0,0,0,0.125);margin-bottom:0;padding:.75rem 1.25rem}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{background-color:rgba(0,0,0,0.03);border-top:1px solid rgba(0,0,0,0.125);padding:.75rem 1.25rem}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{border-bottom:0;margin-bottom:-.75rem;margin-left:-.625rem;margin-right:-.625rem}.card-header-pills{margin-left:-.625rem;margin-right:-.625rem}.card-img-overlay{bottom:0;left:0;padding:1.25rem;position:absolute;right:0;top:0}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-left-radius:calc(.25rem - 1px);border-bottom-right-radius:calc(.25rem - 1px)}.w-100{width:100% !important}.shadow{box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15) !important}.bg-primary{background-color:#007bff !important}button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc !important}a.bg-primary:focus,a.bg-primary:hover{background-color:#0062cc !important}a.text-primary:focus,a.text-primary:hover{color:#121416 !important}.bg-secondary{background-color:#6c757d !important}button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62 !important}a.bg-secondary:focus,a.bg-secondary:hover{background-color:#545b62 !important}a.text-secondary:focus,a.text-secondary:hover{color:#121416 !important}.bg-success{background-color:#28a745 !important}button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34 !important}a.bg-success:focus,a.bg-success:hover{background-color:#1e7e34 !important}a.text-success:focus,a.text-success:hover{color:#121416 !important}.bg-info{background-color:#17a2b8 !important}button.bg-info:focus,button.bg-info:hover{background-color:#117a8b !important}a.bg-info:focus,a.bg-info:hover{background-color:#117a8b !important}a.text-info:focus,a.text-info:hover{color:#121416 !important}.bg-warning{background-color:#ffc107 !important}button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00 !important}a.bg-warning:focus,a.bg-warning:hover{background-color:#d39e00 !important}a.text-warning:focus,a.text-warning:hover{color:#121416 !important}.bg-danger{background-color:#dc3545 !important}button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130 !important}a.bg-danger:focus,a.bg-danger:hover{background-color:#bd2130 !important}a.text-danger:focus,a.text-danger:hover{color:#121416 !important}.bg-light{background-color:#f8f9fa !important}button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5 !important}a.bg-light:focus,a.bg-light:hover{background-color:#dae0e5 !important}a.text-light:focus,a.text-light:hover{color:#121416 !important}.bg-dark{background-color:#343a40 !important}button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124 !important}a.bg-dark:focus,a.bg-dark:hover{background-color:#1d2124 !important}a.text-dark:focus,a.text-dark:hover{color:#121416 !important}.bg-white{background-color:#fff !important}button.bg-white:focus,button.bg-white:hover{background-color:#e6e6e6 !important}a.bg-white:focus,a.bg-white:hover{background-color:#e6e6e6 !important}a.text-white:focus,a.text-white:hover{color:#121416 !important}.text-primary{color:#007bff !important}.text-secondary{color:#6c757d !important}.text-success{color:#28a745 !important}.text-info{color:#17a2b8 !important}.text-warning{color:#ffc107 !important}.text-danger{color:#dc3545 !important}.text-light{color:#f8f9fa !important}.text-dark{color:#343a40 !important}.text-white{color:#fff !important}.text-body{color:#212529 !important}.text-muted{color:#6c757d !important}.text-black-50{color:rgba(0,0,0,0.5) !important}.text-white-50{color:rgba(255,255,255,0.5) !important}.bg-transparent{background-color:transparent !important}.text-justify{text-align:justify !important}.text-left{text-align:left !important}.text-right{text-align:right !important}.text-center{text-align:center !important}.font-weight-light{font-weight:300 !important}.font-weight-lighter{font-weight:lighter !important}.font-weight-normal{font-weight:400 !important}.font-weight-bold{font-weight:700 !important}.font-weight-bolder{font-weight:bolder !important}.font-italic{font-style:italic !important}.container{margin-left:auto;margin-right:auto;padding-left:15px;padding-right:15px;width:100%}@media (min-width: 576px){.container{max-width:540px}}@media (min-width: 768px){.container{max-width:720px}}@media (min-width: 992px){.container{max-width:960px}}@media (min-width: 1200px){.container{max-width:1140px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{margin-left:auto;margin-right:auto;padding-left:15px;padding-right:15px;width:100%}@media (min-width: 576px){.container,.container-sm{max-width:540px}}@media (min-width: 768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width: 992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width: 1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-15px;margin-right:-15px}.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{padding-left:15px;padding-right:15px;position:relative;width:100%}@media (min-width: 576px){.col-sm{flex-basis:0;flex-grow:1;-ms-flex-positive:1;-ms-flex-preferred-size:0;max-width:100%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;width:auto}.col-sm-1{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-sm-2{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-sm-5{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-sm-8{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-sm-11{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}}@media (min-width: 768px){.col-md{flex-basis:0;flex-grow:1;-ms-flex-positive:1;-ms-flex-preferred-size:0;max-width:100%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;width:auto}.col-md-1{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-md-2{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-md-5{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-md-8{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-md-11{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}}@media (min-width: 992px){.col-lg{flex-basis:0;flex-grow:1;-ms-flex-positive:1;-ms-flex-preferred-size:0;max-width:100%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;width:auto}.col-lg-1{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-lg-2{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-lg-5{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-lg-8{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-lg-11{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}}@media (min-width: 1200px){.col-xl{flex-basis:0;flex-grow:1;-ms-flex-positive:1;-ms-flex-preferred-size:0;max-width:100%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;width:auto}.col-xl-1{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-xl-2{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-xl-5{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-xl-8{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-xl-11{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}}.d-flex{display:-ms-flexbox !important;display:flex !important}.sphinx-bs,.sphinx-bs *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sphinx-bs p{margin-top:0} diff --git a/_panels_static/panels-main.c949a650a448cc0ae9fd3441c0e17fb0.css b/_panels_static/panels-main.c949a650a448cc0ae9fd3441c0e17fb0.css new file mode 100644 index 0000000000..fc14abc85d --- /dev/null +++ b/_panels_static/panels-main.c949a650a448cc0ae9fd3441c0e17fb0.css @@ -0,0 +1 @@ +details.dropdown .summary-title{padding-right:3em !important;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}details.dropdown:hover{cursor:pointer}details.dropdown .summary-content{cursor:default}details.dropdown summary{list-style:none;padding:1em}details.dropdown summary .octicon.no-title{vertical-align:middle}details.dropdown[open] summary .octicon.no-title{visibility:hidden}details.dropdown summary::-webkit-details-marker{display:none}details.dropdown summary:focus{outline:none}details.dropdown summary:hover .summary-up svg,details.dropdown summary:hover .summary-down svg{opacity:1}details.dropdown .summary-up svg,details.dropdown .summary-down svg{display:block;opacity:.6}details.dropdown .summary-up,details.dropdown .summary-down{pointer-events:none;position:absolute;right:1em;top:.75em}details.dropdown[open] .summary-down{visibility:hidden}details.dropdown:not([open]) .summary-up{visibility:hidden}details.dropdown.fade-in[open] summary~*{-moz-animation:panels-fade-in .5s ease-in-out;-webkit-animation:panels-fade-in .5s ease-in-out;animation:panels-fade-in .5s ease-in-out}details.dropdown.fade-in-slide-down[open] summary~*{-moz-animation:panels-fade-in .5s ease-in-out, panels-slide-down .5s ease-in-out;-webkit-animation:panels-fade-in .5s ease-in-out, panels-slide-down .5s ease-in-out;animation:panels-fade-in .5s ease-in-out, panels-slide-down .5s ease-in-out}@keyframes panels-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes panels-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.octicon{display:inline-block;fill:currentColor;vertical-align:text-top}.tabbed-content{box-shadow:0 -.0625rem var(--tabs-color-overline),0 .0625rem var(--tabs-color-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.tabbed-content>:first-child{margin-top:0 !important}.tabbed-content>:last-child{margin-bottom:0 !important}.tabbed-content>.tabbed-set{margin:0}.tabbed-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.tabbed-set>input{opacity:0;position:absolute}.tabbed-set>input:checked+label{border-color:var(--tabs-color-label-active);color:var(--tabs-color-label-active)}.tabbed-set>input:checked+label+.tabbed-content{display:block}.tabbed-set>input:focus+label{outline-style:auto}.tabbed-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.tabbed-set>label{border-bottom:.125rem solid transparent;color:var(--tabs-color-label-inactive);cursor:pointer;font-size:var(--tabs-size-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .tabbed-set>label:hover{color:var(--tabs-color-label-active)} diff --git a/_panels_static/panels-variables.06eb56fa6e07937060861dad626602ad.css b/_panels_static/panels-variables.06eb56fa6e07937060861dad626602ad.css new file mode 100644 index 0000000000..adc6166222 --- /dev/null +++ b/_panels_static/panels-variables.06eb56fa6e07937060861dad626602ad.css @@ -0,0 +1,7 @@ +:root { +--tabs-color-label-active: hsla(231, 99%, 66%, 1); +--tabs-color-label-inactive: rgba(178, 206, 245, 0.62); +--tabs-color-overline: rgb(207, 236, 238); +--tabs-color-underline: rgb(207, 236, 238); +--tabs-size-label: 1rem; +} \ No newline at end of file diff --git a/_sources/cncf/2022-vineyard-annual.md.txt b/_sources/cncf/2022-vineyard-annual.md.txt new file mode 100644 index 0000000000..909d49b260 --- /dev/null +++ b/_sources/cncf/2022-vineyard-annual.md.txt @@ -0,0 +1,149 @@ +# Vineyard 2022 Annual Review + +## Background + +[Vineyard](https://v6d.io) is an in-memory immutable data manager that provides +**out-of-the-box high-level** abstraction and **zero-copy in-memory** sharing for +distributed data in big data tasks, such as graph analytics, numerical computing, and +machine learning. + +**Vineyard provides**: + +1. Efficient in-memory data management and zero-copy sharing across different systems. +2. Out-of-the-box high-level data abstraction for distributed objects (e.g., tensors, tables, + graphs) and efficient polyglot support (currently including C++, Python, and Java). +3. Built-in streaming support for data accessing and across system pipelining. +4. An extensible driver framework and a set of efficient built-in drivers for eliminating + the boilerplate part in computation engines, e.g., I/O, serialization, and checkpointing. + +**Alignment with CNCF**: + +1. Vineyard builds on Kubernetes for deploying and scaling and the objects are observable + in Kubernetes as CRDs. +2. Vineyard makes efficient zero-copy sharing possible for data-intensive workflows on + cloud-native infrastructure by a data-aware Kubernetes scheduler plugin. +3. Vineyard adopts a immutable object design, which aligns with the immutable infrastructure + of the cloud-native environment. + +**Vineyard was accepted as a CNCF sandbox project on Apr 28th, 2021.** + +## DevStats + +> Include a link to your project’s devstats page. We will be looking for signs of consistent +> or increasing contribution activity. Please feel free to add commentary to add colour to +> the numbers and graphs we will see on devstats. + +- Stargazers and Forks + - [https://vineyard.devstats.cncf.io/d/3/stars-and-forks-by-repository?orgId=1&from=now-2y&to=now](https://vineyard.devstats.cncf.io/d/3/stars-and-forks-by-repository?orgId=1&from=now-2y&to=now) +- Commits per week + - [https://vineyard.devstats.cncf.io/d/1/activity-repository-groups?orgId=1&var-period=w&var-repogroups=All&from=now-6M&to=now](https://vineyard.devstats.cncf.io/d/1/activity-repository-groups?orgId=1&var-period=w&var-repogroups=All&from=now-6M&to=now) +- Contributors and Companies + - [https://vineyard.devstats.cncf.io/d/7/companies-contributing-in-repository-groups?orgId=1&var-period=d7&var-repogroup_name=All&from=now-1y&to=now](https://vineyard.devstats.cncf.io/d/7/companies-contributing-in-repository-groups?orgId=1&var-period=d7&var-repogroup_name=All&from=now-1y&to=now) + +The vineyard community has grown since the project entered the CNCF sandbox. + +- Number of contributors: 11 -> 26 +- Github stars: 300+ -> 600+ +- Github forks: 20+ -> 80+ +- Contributing organizations: 1 -> 12 + +## Maintainers + +> How many maintainers do you have, and which organisations are they from? (Feel free +> to link to an existing MAINTAINERS file if appropriate.) + +We currently have 7 maintainers and 2 committers and have [a maintainer list on Github](https://github.com/v6d-io/v6d/blob/main/MAINTAINERS.md). + +- **Initial maintainers** + + | Name | GitHub ID | Affiliation | Email | + | --- | --- | --- | --- | + | Tao He | [sighingnow](https://github.com/sighingnow) | Alibaba | [linzhu.ht@alibaba-inc.com](mailto:linzhu.ht@alibaba-inc.com) | + | Xiaojian Luo | [luoxiaojian](https://github.com/luoxiaojian) | Alibaba | [lxj193371@alibaba-inc.com](mailto:lxj193371@alibaba-inc.com) | + | Wenyuan Yu | [wenyuanyu](https://github.com/wenyuanyu) | Alibaba | [wenyuan.ywy@alibaba-inc.com](mailto:wenyuan.ywy@alibaba-inc.com) | + | Weibin Zeng | [acezen](https://github.com/acezen) | Alibaba | [qiaozi.zwb@alibaba-inc.com](mailto:qiaozi.zwb@alibaba-inc.com) | + | Siyuan Zhang | [siyuan0322](https://github.com/siyuan0322) | Alibaba | [siyuanzhang.zsy@alibaba-inc.com](mailto:siyuanzhang.zsy@alibaba-inc.com) | + | Diwen Zhu | [andydiwenzhu](https://github.com/andydiwenzhu) | Alibaba | [diwen.zdw@alibaba-inc.com](mailto:diwen.zdw@alibaba-inc.com) | + +- **New maintainers in this year** + + | Name | GitHub ID | Affiliation | Email | + | --- | --- | --- | --- | + | Ke Meng | [septicmk](https://github.com/septicmk) | Alibaba | [mengke.mk@alibaba-inc.com](mailto:mengke.mk@alibaba-inc.com) | + +- **New Committers in this year** + + | Name | GitHub ID | Affiliation | Email | + | --- | --- | --- | --- | + | Lihong Lin | [linlih](https://github.com/linlih) | PKU | [linlh@stu.pku.edu.cn](mailto:linlh@stu.pku.edu.cn) | + | Pei Li | [peilii](https://github.com/peilii) | CMU | [peili.dev@gmail.com](mailto:peili.dev@gmail.com) | + +## Adoption + +> What do you know about adoption, and how has this changed since your last review / since +> you joined Sandbox? If you can list companies that are end-users of your project, please +> do so. (Feel free to link to an existing ADOPTERS file if appropriate.) + +We know several cases where vineyard has been adopted in both testing and production environments. + +- _GraphScope_: production stage + - _GraphScope_ is an open-source graph processing platform. + - Vineyard is used in graphscope to provide distributed shared in-memory storage for different + graph processing engines. +- _weilaisudu_: transiting from testing to production stage + - _weilaisudu_ is the company behind the project [Mars](https://github.com/mars-project/mars), + a distributed scientific computing engine that provides numpy and pandas like API. + - Vineyard is used as the shared-memory storage for actors that do computation on chunks. +- _ESRF_: testing stage + - _ESRF_ is a joint research facility situated in France, one of the biggest x-ray science + facilities in Europe. + - VIneyard is used in the BLISS software to serve as the shared storage between sensors and + data processing jobs. +- _PingAn_: testing stage + - _PingAn_ is a large-scale fin-tech company in China. + - Vineyard is used in a research platform to support efficient dataset sharing and management + among data science researchers. + +We have also integrated with the apache-airflow project, which is a workflow orchestration engine +and has been widely adopted. We have published airflow-vineyard-provider on Astronomer Registry +and received much feedback from end-users, but we haven't tracked the actual adoption yet. + +## Project Goals + +> How has the project performed against its goals since the last review? (We won't penalize you +> if your goals changed for good reasons.) + +Vineyard has successfully archived the goal of bringing value to big data analytical workflows +on Kubernetes. We have shown the gain in an internal project which involves both ETL, graph +computation, and machine learning jobs. + +Our goal hasn't changed since becoming CNCF sandbox project and we are still aiming at supporting +a more efficient big data analytical workflow on the cloud-native infrastructure. Specifically, +we'll keep moving towards following goals in the next year: + +1. Providing efficient cross-engine data sharing for data-intensive workflows in Kubernetes +2. Integrating with projects in the cloud-native community for orchestration and scheduling + and integrating with more big data computing engines to improve the end-to-end efficiency. +3. Building a new cloud-native paradigm for big data applications working together. By + integrating Vineyard, Kubernetes can help orchestrating data and workloads together for + better alignment and efficiency. + +We still need to do more to engage end-users to show the value-added of the vineyard project. + +## CNCF membership + +> How can the CNCF help you achieve your upcoming goals? + +Vineyard has incredibly benefited from CNCF since accepted as a sandbox project. We believe +the end-users in the CNCF community are critical for Vineyard to become successful. We have +submitted proposals for the KubeCon and CNCF Conferences in the past year but got rejected. +We hope we could have more opportunities to introduce our project to border end-users in the +CNCF community to increase adoption. + +## Incubation + +> Do you think that your project meets the [criteria for incubation](https://github.com/cncf/toc/blob/master/process/graduation_criteria.adoc#incubating-stage)? + +We think our project vineyard still needs further exploration to get border adoption in the +production environment and we are looking forward to meeting the incubation criteria in near +future. diff --git a/_sources/cncf/2023-vineyard-annual.md.txt b/_sources/cncf/2023-vineyard-annual.md.txt new file mode 100644 index 0000000000..d5c00feae2 --- /dev/null +++ b/_sources/cncf/2023-vineyard-annual.md.txt @@ -0,0 +1,208 @@ +# Vineyard 2023 Annual Review + +## Background + +[Vineyard](https://v6d.io) is an in-memory immutable data manager that provides +**out-of-the-box high-level** abstraction and **zero-copy in-memory** sharing for +distributed data in big data tasks, such as graph analytics, numerical computing, and +machine learning. Vineyard is design to address the inefficiency of data sharing in +big data analytical workflows on Kubernetes. + +**Vineyard provides**: + +1. Efficient in-memory data management and zero-copy sharing across different systems. +2. Out-of-the-box high-level data abstraction for distributed objects (e.g., tensors, tables, + graphs, and distributed datasets) and efficient polyglot support (currently including C++, + Python, Go, Rust and Java). +3. Seamless integration with Kubernetes for cluster deployment and management, workloads + orchestration, and observability. +4. Out-of-the-box integration with workflow orchestration engines (including + [Apache Airflow](https://github.com/v6d-io/v6d/tree/main/python/vineyard/contrib/airflow), + [Flyte](https://github.com/v6d-io/v6d/tree/main/python/vineyard/contrib/kedro) + and [Kubeflow Pipelines](https://github.com/v6d-io/v6d/tree/main/k8s/examples)), + providing end-users with a unified and intrusive experience to leverage Vineyard in + their data-intensive workflows to improving performance. + +**Alignment with CNCF**: + +1. Vineyard builds on Kubernetes for deploying and scaling, and the objects are observable + in Kubernetes as CRDs. +2. Vineyard makes efficient zero-copy sharing possible for data-intensive workflows on + cloud-native infrastructure by a data-aware Kubernetes scheduler plugin. +3. Vineyard adopts an immutable object design, which aligns with the immutable infrastructure + of the cloud-native environment. +4. Vineyard aligns with the CNCF effort on helping migrate batching system workflows to cloud + native environments. + +## Development + +### DevStats + +> Include a link to your project’s devstats page. We will be looking for signs of consistent +> or increasing contribution activity. Please feel free to add commentary to add colour to +> the numbers and graphs we will see on devstats. + +- Stargazers and Forks + - [https://vineyard.devstats.cncf.io/d/3/stars-and-forks-by-repository?orgId=1&from=now-2y&to=now](https://vineyard.devstats.cncf.io/d/3/stars-and-forks-by-repository?orgId=1&from=now-2y&to=now) +- Commits per week + - [https://vineyard.devstats.cncf.io/d/1/activity-repository-groups?orgId=1&var-period=w&var-repogroups=All&from=now-6M&to=now](https://vineyard.devstats.cncf.io/d/1/activity-repository-groups?orgId=1&var-period=w&var-repogroups=All&from=now-6M&to=now) +- Contributors and Companies + - [https://vineyard.devstats.cncf.io/d/7/companies-contributing-in-repository-groups?orgId=1&var-period=d7&var-repogroup_name=All&from=now-1y&to=now](https://vineyard.devstats.cncf.io/d/7/companies-contributing-in-repository-groups?orgId=1&var-period=d7&var-repogroup_name=All&from=now-1y&to=now) + +The vineyard community has grown since the project entered the CNCF sandbox. + +- Number of contributors: 26 -> 40 +- Github stars: 600+ -> ~750 +- Github forks: 80+ -> 110+ + +### Highlights of new features + +Vineyard published 8 release (one release about per 1.5 month) since the last annual review. +The major new features and improvements include: + +- Language SDKs in Rust and Go, where the Rust SDK was a collaboration with our external + end-user, and enabled users seamlessly and efficiently interoperating their data between + Python and Rust. +- Integration with the workflow engine Kedro, and gained attention in the Kedro community. +- Vineyard supports the [Apache Hive](https://github.com/apache/hive) data processing engine, + letting users can easily connect traditional data processing pipelines built with the + Hadoop ecosystem with emerging big-data and AI applications (e.g., applications in the + [PyData](https://pandas.pydata.org/community/ecosystem.html) community). +- A initial version of CSI driver, which helped Vineyard aligned with the Kubernetes platform + and enables users to leverage Vineyard in their Kubeflow pipelines to optimize the data + sharing between steps with only minor changes to their existing source code. + +### Academic Research + +We have conducted a series of research work around Vineyard and published the paper +_Vineyard: Optimizing Data Sharing in Data-Intensive Analytics_ in SIGMOD 2023, a top-tier +conference in the data management community. + +- Wenyuan Yu, Tao He, Lei Wang, Ke Meng, Ye Cao, Diwen Zhu, Sanhong Li, Jingren Zhou. + Vineyard: Optimizing Data Sharing in Data-Intensive Analytics. ACM SIG Conference on + Management of Data (SIGMOD), industry, 2023. [https://dl.acm.org/doi/10.1145/3589780](https://dl.acm.org/doi/10.1145/3589780). + +## Maintainers + +> How many maintainers do you have, and which organisations are they from? (Feel free +> to link to an existing MAINTAINERS file if appropriate.) + +We currently have 10 maintainers and 2 committers and have [a maintainer list on Github](https://github.com/v6d-io/v6d/blob/main/MAINTAINERS.md). + +- **Initial maintainers** + + | Name | GitHub ID | Affiliation | Email | + | --- | --- | --- | --- | + | Tao He | [sighingnow](https://github.com/sighingnow) | Alibaba | [linzhu.ht@alibaba-inc.com](mailto:linzhu.ht@alibaba-inc.com) | + | Xiaojian Luo | [luoxiaojian](https://github.com/luoxiaojian) | Alibaba | [lxj193371@alibaba-inc.com](mailto:lxj193371@alibaba-inc.com) | + | Ke Meng | [septicmk](https://github.com/septicmk) | Alibaba | [mengke.mk@alibaba-inc.com](mailto:mengke.mk@alibaba-inc.com) | + | Wenyuan Yu | [wenyuanyu](https://github.com/wenyuanyu) | Alibaba | [wenyuan.ywy@alibaba-inc.com](mailto:wenyuan.ywy@alibaba-inc.com) | + | Weibin Zeng | [acezen](https://github.com/acezen) | Alibaba | [qiaozi.zwb@alibaba-inc.com](mailto:qiaozi.zwb@alibaba-inc.com) | + | Siyuan Zhang | [siyuan0322](https://github.com/siyuan0322) | Alibaba | [siyuanzhang.zsy@alibaba-inc.com](mailto:siyuanzhang.zsy@alibaba-inc.com) | + | Diwen Zhu | [andydiwenzhu](https://github.com/andydiwenzhu) | Alibaba | [diwen.zdw@alibaba-inc.com](mailto:diwen.zdw@alibaba-inc.com) | + +- **New maintainers in this year** + + | Name | GitHub ID | Affiliation | Email | + | --- | --- | --- | --- | + | Ye Cao | [dashanji](https://github.com/dashanji) | Alibaba | [caoye.cao@alibaba-inc.com](mailto:caoye.cao@alibaba-inc.com) | + | Shumin Yuan | [vegetableysm](https://github.com/vegetableysm) | Alibaba | [yuanshumin.ysm@alibaba-inc.com](mailto:yuanshumin.ysm@alibaba-inc.com) | + | Denghao Li | [lidh15](https://github.com/lidh15) | PingAn Tech | [lidhrandom@gmail.com](mailto:lidhrandom@gmail.com) | + +- **New Committers in this year** + + | Name | GitHub ID | Affiliation | Email | + | --- | --- | --- | --- | + | Lihong Lin | [linlih](https://github.com/linlih) | PKU | [linlh@stu.pku.edu.cn](mailto:linlh@stu.pku.edu.cn) | + | Pei Li | [peilii](https://github.com/peilii) | CMU | [peili.dev@gmail.com](mailto:peili.dev@gmail.com) | + +## Adoption + +> What do you know about adoption, and how has this changed since your last review / since +> you joined Sandbox? If you can list companies that are end-users of your project, please +> do so. (Feel free to link to an existing ADOPTERS file if appropriate.) + +We have tracked the following two major adoption since [our last annual review]() + +- _StartDT (Qidianyun)_: transiting towards production stage + - _StartDT_ is a startup company in China, providing a cloud-native data platform for + big-data analytics and machine learning applications. + - Vineyard is currently used in their Python-centric data processing pipelines to + share distributed dataframe artifacts between steps, and help build a composable + and efficient data processing platform to end-users. + + Vineyard has passed their eager-evaluation, and they are working on building their + distributed data processing platform on top of Vineyard. + +- _PingAn Tech_: production stage + - _PingAn_ is a large-scale fin-tech company in China. + - Vineyard is used in their data science platform to support efficient dataset sharing and + management among data science researchers. + + The status of Vineyard in their platform has been transited from testing to production + stage, and one of their engineers has become a maintainer of the Vineyard project. + +Besides these two major companies, since our last annual review, we have also noticed some other +questions about using Vineyard in machine learning inference scenarios, but we haven't tracked +the actual adoption yet. + +## Project Goals + +> How has the project performed against its goals since the last review? (We won't penalize you +> if your goals changed for good reasons.) + +Vineyard has successfully archived the goals about easing the getting started process for +end-users from three aspects: + +1. Out-of-the-box integration with data processing systems, especially Spark and Hive, + the most popular data processing engines in the big data community; +2. Data processing pipeline orchestration: providing non-intrusive interfaces to help users + migrate their existing data processing pipelines to Vineyard on Kubernetes and finally + benefit from the efficient data sharing; +3. Seamless inter-operability with other systems in the cloud-native environments: we invest + a lot of effort in the Vineyard operator to help use deploy vineyard along with their + workloads in a non-intrusive, declarative way and has tested the functionality with + _GraphScope_ in end-users production environments. + +Besides, Vineyard has successfully attracted new end-users from the big data community to +adopt Vineyard in their own data processing platform, and the feedback from the Kedro +community is also positive. + +> What are the current goals of the project? For example, are you working on major new features? Or are you concentrating on adoption or documentation? + +Our current goals are mainly focused on the attracting more end-user to adopt Vineyard in +their scenarios from different domains. Specifically, we are keeping moving towards the +following goals in the next year: + +1. Optimizing our current Kubeflow integration and find more opportunities to evaluate + and deploy Vineyard in production machine learning applications; +2. Publish our integration with the big data processing systems to their end-user + community and gather feedback for further improvements; +3. Seeking more opportunities to evaluate Vineyard in the emerging LLM applications, + for both data preprocessing, training, and inference serving to see if Vineyard + can bring added value to these applications as where the data cost is usually high; +4. Getting engaged with the Batch System WG in CNCF to seek opportunities about + further collaboration with other projects in CNCF. + +## CNCF membership + +> How can the CNCF help you achieve your upcoming goals? + +Vineyard has incredibly benefited from CNCF since accepted as a sandbox project. We believe +the end-users in the CNCF community are critical for Vineyard to become successful. With +the help of CNCF service desk, we have successfully built a new website for Vineyard, which +is more friendly to end-users. We are also working on components like CSI driver and hope +that could make the inter-operation with other projects in the CNCF community easier. + +We will host a _Project Kiosk_ in this KubeCon China and hope to get more feedback from the +community, and hope to get more feedback from the community. Furthermore, we hope we could have +more opportunities to introduce our project to border end-users in the CNCF community to +increase adoption. + +## Incubation + +> Do you think that your project meets the [criteria for incubation](https://github.com/cncf/toc/blob/master/process/graduation_criteria.adoc#incubating-stage)? + +We think our project vineyard still needs further exploration to get border adoption in the +end-user's production deployment and gather more feedback, and we are looking forward to +meeting the incubation criteria in the near future. diff --git a/_sources/docs.rst.txt b/_sources/docs.rst.txt new file mode 100644 index 0000000000..3f68441758 --- /dev/null +++ b/_sources/docs.rst.txt @@ -0,0 +1,265 @@ +.. vineyard documentation master file, created by + sphinx-quickstart on Tue Aug 27 10:19:05 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +.. meta:: + :description: Vineyard (v6d), a CNCF sandbox project, is an in-memory immutable data manager + that provides **out-of-the-box high-level** abstraction and **zero-copy in-memory** sharing for + distributed data in big data tasks, such as graph analytics (e.g., `GraphScope`_), numerical + computing (e.g., `Mars`_), and machine learning. + :keywords: distributed-systems, distributed, shared-memory, graph-analytics, in-memory-storage, + big-data-analytics, distributed-comp + +.. figure:: images/vineyard-logo-rect.png + :width: 397 + :alt: Vineyard: an in-memory immutable data manager + :target: https://v6d.io + + *an in-memory immutable data manager* + +|PyPI| |FAQ| |Discussion| |Slack| |License| |ACM DL| + +Why bother? +----------- + +Sharing intermediate data between systems in modern big data and AI workflows +can be challenging, often causing significant bottlenecks in such jobs. Let's +consider the following fraud detection pipeline: + +.. figure:: images/fraud-detection-job.jpg + :width: 75% + :alt: A real-life fraud detection job + + A real-life fraud detection job + +From the pipeline, we observed: + +1. Users usually prefer to program with dedicated computing systems for different tasks in the + same applications, such as SQL and Python. + + **Integrating a new computing system into production environments demands high technical + effort to align with existing production environments in terms of I/O, failover, etc.** + +2. Data could be polymorphic. Non-relational data, such as tensors, dataframes (in Pandas) and + graphs/networks (in `GraphScope`_) are becoming increasingly prevalent. Tables and SQL may + not be the best way to store, exchange, or process them. + + **Transforming the data back and forth between different systems as "tables" could + result in a significant overhead.** + +3. Saving/loading the data to/from the external storage requires numerous memory copies and + incurs high IO costs. + +What is Vineyard? +----------------- + +Vineyard (v6d) is an **in-memory immutable data manager** that offers **out-of-the-box high-level** +abstraction and **zero-copy sharing** for distributed data in big data tasks, such as +graph analytics (e.g., `GraphScope`_), numerical computing (e.g., `Mars`_), and machine learning. + +Features +^^^^^^^^ + +Efficient data sharing +~~~~~~~~~~~~~~~~~~~~~~ + +Vineyard shares immutable data across different systems using shared memory without extra overheads, +eliminating the overhead of serialization/deserialization and IO when exchanging immutable +data between systems. + +Out-of-the-box data abstraction +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Vineyard defines a metadata-payload separated data model to capture the payload commonalities and +method commonalities between sharable objects in different programming languages and different +computing systems in a unified way. + +The :ref:`VCDL` (Vineyard Component Description Language) is specifically designed to annotate +sharable members and methods, enabling automatic generation of boilerplate code for minimal +integration effort. + +Pluggable I/O routines +~~~~~~~~~~~~~~~~~~~~~~ + +In many big data analytical tasks, a substantial portion of the workload consists of boilerplate +routines that are unrelated to the core computation. These routines include various IO adapters, +data partition strategies, and migration jobs. Due to different data structure abstractions across +systems, these routines are often not easily reusable, leading to increased complexity and redundancy. + +Vineyard provides common manipulation routines for immutable data as drivers, which extend +the capabilities of data structures by registering appropriate drivers. This enables out-of-the-box +reuse of boilerplate components across diverse computation jobs. + +Data orchestration on Kubernetes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Vineyard provides efficient distributed data sharing in cloud-native environments by embracing +cloud-native big data processing. Kubernetes helps Vineyard leverage the scale-in/out and +scheduling abilities of Kubernetes. + +Use cases +^^^^^^^^^ + +.. panels:: + :header: text-center + :container: container-lg pb-4 + :column: col-lg-4 col-md-4 col-sm-4 col-xs-12 p-2 + :body: text-center + + .. link-button:: # + :type: url + :text: Object manager + :classes: btn-block stretched-link + + Put and get arbitrary objects using Vineyard, in a zero-copy way! + + --- + + .. link-button:: # + :type: url + :text: Cross-system sharing + :classes: btn-block stretched-link + + Share large objects across computing systems. + + --- + + .. link-button:: # + :type: url + :text: Data orchestration + :classes: btn-block stretched-link + + Vineyard coordinates the flow of objects and jobs on Kubernetes based on data-aware scheduling. + +Get started now! +---------------- + +.. panels:: + :header: text-center + :column: col-lg-12 p-2 + + .. link-button:: notes/getting-started + :type: ref + :text: User Guides + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Get started with Vineyard. + + --- + + .. link-button:: notes/cloud-native/deploy-kubernetes + :type: ref + :text: Deploy on Kubernetes + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Deploy Vineyard on Kubernetes and accelerate big-data analytical workflows on cloud-native + infrastructures. + + --- + + .. link-button:: tutorials/tutorials + :type: ref + :text: Tutorials + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Explore use cases and tutorials where Vineyard can bring added value. + + --- + + .. link-button:: notes/developers + :type: ref + :text: Getting Involved + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Get involved and become part of the Vineyard community. + + --- + + .. link-button:: notes/developers/faq + :type: ref + :text: FAQ + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Frequently asked questions and discussions during the adoption of Vineyard. + +Read the Paper +-------------- + +- Wenyuan Yu, Tao He, Lei Wang, Ke Meng, Ye Cao, Diwen Zhu, Sanhong Li, Jingren Zhou. + `Vineyard: Optimizing Data Sharing in Data-Intensive Analytics `_. + ACM SIG Conference on Management of Data (SIGMOD), industry, 2023. |ACM DL|. + +Vineyard is a `CNCF sandbox project`_ and is made successful by its community. + +.. image:: https://v6d.io/_static/cncf-color.svg + :width: 400 + :alt: Vineyard is a CNCF sandbox project + +.. toctree:: + :maxdepth: 1 + :caption: User Guides + :hidden: + + notes/getting-started.rst + notes/architecture.rst + notes/key-concepts.rst + +.. toctree:: + :maxdepth: 1 + :caption: Cloud-Native + :hidden: + + notes/cloud-native/deploy-kubernetes.rst + notes/cloud-native/vineyard-operator.rst + Command-line tool + +.. toctree:: + :maxdepth: 1 + :caption: Tutorials + :hidden: + + tutorials/data-processing.rst + tutorials/kubernetes.rst + tutorials/extending.rst + +.. toctree:: + :maxdepth: 1 + :caption: Integration + :hidden: + + notes/integration-bigdata.rst + notes/integration-orchestration.rst + +.. toctree:: + :maxdepth: 2 + :caption: API Reference + :hidden: + + notes/references.rst + +.. toctree:: + :maxdepth: 1 + :caption: Developer Guides + :hidden: + + notes/developers.rst + notes/developers/faq.rst + +.. _Mars: https://github.com/mars-project/mars +.. _GraphScope: https://github.com/alibaba/GraphScope +.. _CNCF sandbox project: https://www.cncf.io/sandbox-projects/ + +.. |PyPI| image:: https://img.shields.io/pypi/v/vineyard?color=blue + :target: https://pypi.org/project/vineyard +.. |FAQ| image:: https://img.shields.io/badge/-FAQ-blue?logo=Read%20The%20Docs + :target: https://v6d.io/notes/faq.html +.. |Discussion| image:: https://img.shields.io/badge/Discuss-Ask%20Questions-blue?logo=GitHub + :target: https://github.com/v6d-io/v6d/discussions +.. |Slack| image:: https://img.shields.io/badge/Slack-Join%20%23vineyard-purple?logo=Slack + :target: https://slack.cncf.io/ +.. |License| image:: https://img.shields.io/github/license/v6d-io/v6d + :target: https://github.com/v6d-io/v6d/blob/main/LICENSE + +.. |ACM DL| image:: https://img.shields.io/badge/ACM%20DL-10.1145%2F3589780-blue + :target: https://dl.acm.org/doi/10.1145/3589780 diff --git a/_sources/notes/architecture.rst.txt b/_sources/notes/architecture.rst.txt new file mode 100644 index 0000000000..fa52021ca3 --- /dev/null +++ b/_sources/notes/architecture.rst.txt @@ -0,0 +1,182 @@ +.. _architecture-of-vineyard: + +Architecture +============ + +Overview +-------- + +The following figure illustrates the architecture of Vineyard. + +.. figure:: ../images/vineyard_arch.jpg + :width: 75% + :alt: Architecture of Vineyard + + Architecture of Vineyard + +Server side +^^^^^^^^^^^ + +On the server (daemon) side (i.e., the aforementioned Vineyard instance), there are +three primary components: + +1. The **shared memory** is the memory space in Vineyard that is shared with Vineyard + clients via the UNIX domain socket through memory mapping. + + As previously mentioned, the partitions of the distributed data reside in the + shared memory of the corresponding Vineyard instance in the cluster. + +2. The **metadata manager** is responsible for managing the metadata of the data stored + in Vineyard. + + The metadata manager maintains the metadata (structures, layouts, and properties) of + the data to provide high-level abstractions (e.g., graphs, tensors, dataframes). + The metadata managers in a Vineyard cluster communicate with each other through + the backend key-value store, such as etcd server, to ensure the consistency of the + distributed data stored in Vineyard. + +3. The **IPC/RPC servers** manage the IPC/RPC connections from Vineyard + clients for data sharing. + + Specifically, the client can retrieve both metadata and data partitions stored in + Vineyard via IPC and RPC connections. However, when both connection types are available, + IPC connections are prioritized due to the greater efficiency of memory mapping for data + sharing. + +.. _client-side: + +Client side +^^^^^^^^^^^ + +On the client side, the core component is the **Vineyard client**. The client side +includes both low-level APIs for accessing Vineyard instances in a precise +manner and high-level APIs for data structure sharing, manipulation, and +routine reuse (e.g., I/O drivers). More specifically, + +1. The **IPC client** communicates with *local* Vineyard instances by connecting + to the UNIX domain socket. + + The IPC client is used to establish an IPC connection between the Vineyard server and + the client, enabling memory-sharing (by :code:`mmap` and transferring the file descriptor) + between the Vineyard server and the computing engines. + +2. The **RPC client** communicates with *remote* Vineyard instances by connecting + to the TCP port that the Vineyard daemon is bound to. + + Unlike the IPC client, the RPC client doesn't allow memory sharing between processes + and is less efficient in that regard. However, it is still useful for retrieving both + metadata and payloads from the Vineyard cluster via TCP or RDMA. + +3. The **builders and resolvers** for out-of-the-box high-level data abstractions + offer a convenient way for applications to consume objects in Vineyard and + produce result objects into Vineyard. + + The builders and resolvers adopt an extensible design where users can register + their own builders and resolvers for their newly defined data types, as well as + new builders and resolvers that build ad-hoc engine-specific data structures + as Vineyard objects and wrap Vineyard objects as engine-specific data types + at a low cost. + + The builders, resolvers, and the registry are part of the language-specific + SDKs of Vineyard. Currently, Python and C++ are officially supported, and the Rust + and Go SDKs are under heavy development. + +4. The **pluggable drivers** assign specific functionalities to certain types of data + in Vineyard. + + In particular, I/O drivers synchronize with external storages such as databases and file + systems to read data into and write data from Vineyard, while partition and + re-partition drivers reorganize the distributed graphs stored in Vineyard to + balance the workload. + + .. note:: + + The drivers typically employ the low-level APIs for precise operations. + +5. **Object migration** is the mechanism implemented on the client side to + migrate objects between Vineyard instances in a cluster. Object migration + is usually needed when the computing engines cannot be scheduled to co-locate + with the data required by the jobs. + + Object migration is implemented on the client side as a process pair where the + sender and receiver are both connected to (different) Vineyard instances and + communicate with each other using TCP to move objects between Vineyard instances. + We don't put the object migration on the server side to decouple the functionalities + and allow users to register a more efficient object migration implemented on + their own deployment infrastructures, e.g.,leveraging RDMA and other high-performance + network technologies. + +Core features +------------- + +Zero-cost in-memory data sharing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Vineyard provides zero-cost data sharing through memory-mapping, as data objects +in Vineyard are immutable. When an object is created, we allocate blobs in +Vineyard to store the data payload. On the other hand, when retrieving the object, +we map the blob from the Vineyard instance into the application process using +inter-process memory mapping techniques, ensuring that no memory copy is involved +in sharing the data payload. + +Distributed data sharing in big data tasks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By examining the practices of big data tasks such as numeric computing, machine learning, +and graph analysis, we have identified four key properties of the data involved: + ++ Distributed and each partitioned fragment usually fits into memory; ++ Immutable, i.e., never modified after creation; ++ With complex structure, e.g., graph in CSR format; ++ Required to share between different computation systems and programming languages. + +Vineyard is designed to address these challenges with: + ++ Composable design for Vineyard objects; ++ Immutable zero-cost in-memory data sharing via memory mapping; ++ Out-of-the-box high-level data abstraction for complex data structures; ++ Extensible design for builder/resolver/driver, enabling flexible cross-system and + cross-language data sharing. + +In general, Vineyard's design choices are fully determined by addressing +the difficulties in handling large-scale distributed data in practice. + +Out-of-the-box high-level data abstraction +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Vineyard objects are stored with structures and high-level abstractions. +For instance, a graph with CSR format in Vineyard stores the index along with +the vertices and edges, enabling operations like edge iteration based on the +index. This means users don't have to implement the index-building +function and edge iterators themselves, which is often required in +existing big data practices. + +Convenient data integration +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The extensible design of builder/resolver/driver allows for convenient extension +of existing Vineyard objects to different programming languages. Moreover, +with codegen tools in Vineyard, users can easily transplant their +data structures into Vineyard with only a few annotations. + +Data orchestration in a Python notebook +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Using Vineyard as the common data orchestration engine throughout the end-to-end +big data processing, users can hold large-scale distributed data as variables +of Vineyard objects in Python. As long as the computation modules +involved provide Python APIs, users can write down the entire processing +pipeline in a Python notebook. By running the Python script, users can +manage trillions of data and different computation systems in the background +distributedly across the cluster. + +Non-goals and limitations +------------------------- + +*NO* mutable objects +^^^^^^^^^^^^^^^^^^^^ + +Once a Vineyard object is created and sealed in the Vineyard instance, it +becomes immutable and can NOT be modified anymore. Thus, Vineyard is not +suitable for use as a data cache to store mutable data that changes +rapidly along the processing pipeline. diff --git a/_sources/notes/cloud-native/deploy-kubernetes.rst.txt b/_sources/notes/cloud-native/deploy-kubernetes.rst.txt new file mode 100644 index 0000000000..4df37b2bec --- /dev/null +++ b/_sources/notes/cloud-native/deploy-kubernetes.rst.txt @@ -0,0 +1,209 @@ +.. _deploy-on-kubernetes: + +Deploy on Kubernetes +==================== + +Vineyard is managed by the :ref:`vineyard-operator` on Kubernetes. + +Quick start +----------- + +If you want to install vineyard cluster quickly, you can +use the following command. + +Install `vineyardctl`_ as follows. + +.. code:: bash + + pip3 install vineyard + +Use the vineyardctl to install vineyard cluster. + +.. code:: bash + + python3 -m vineyard.ctl deploy vineyard-cluster --create-namespace + +Also, you could follow the next guide to install vineyard cluster steps +by steps. + +Install vineyard-operator +------------------------- + +There are two recommended methods for installing the vineyard operator: using Helm (preferred) or +installing directly from the source code. + +.. note:: + + Prior to installing the vineyard operator, ensure that you have a Kubernetes cluster and kubectl + installed. In this guide, we will use `kind`_ to create a cluster. + + +Option #1: Install from helm chart (recommended) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: bash + + $ helm repo add vineyard https://vineyard.oss-ap-southeast-1.aliyuncs.com/charts/ + $ helm repo update + $ helm install vineyard-operator vineyard/vineyard-operator \ + --namespace vineyard-system \ + --create-namespace + +Wait for the vineyard operator until ready. + +Option #2: Install form source code +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. Clone the vineyard repo: + + .. code:: bash + + $ git clone https://github.com/v6d-io/v6d.git + +2. Build the vineyard operator's Docker image: + + .. code:: bash + + $ cd k8s + $ make -C k8s docker-build + + .. caution:: + + With `kind`_, you need to first import the image into the kind cluster: + + .. code:: bash + + $ kind load docker-image vineyardcloudnative/vineyard-operator:latest + +3. Next, deploy the vineyard operator: + + .. code:: bash + + $ make -C k8s deploy + +Wait vineyard-operator ready +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Once the operator is installed, its deployment can be checked using :code:`kubectl`: + +.. code:: bash + + $ kubectl get all -n vineyard-system + +.. admonition:: Expected output + :class: admonition-details + + .. code:: bash + + NAME READY STATUS RESTARTS AGE + pod/vineyard-controller-manager-5c6f4bc454-8xm8q 2/2 Running 0 62m + + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + service/vineyard-controller-manager-metrics-service ClusterIP 10.96.240.173 8443/TCP 62m + service/vineyard-webhook-service ClusterIP 10.96.41.132 443/TCP 62m + + NAME READY UP-TO-DATE AVAILABLE AGE + deployment.apps/vineyard-controller-manager 1/1 1 1 62m + + NAME DESIRED CURRENT READY AGE + replicaset.apps/vineyard-controller-manager-5c6f4bc454 1 1 1 62m + +Create vineyard cluster +----------------------- + +Once the vineyard operator becomes ready, you can create a vineyard cluster by creating a +:code:`Vineyardd` `CRD`_. The following is an example of creating a vineyard cluster with 3 daemon +replicas: + +.. code:: yaml + + $ cat < 2379/TCP 48s + service/etcd0 ClusterIP 10.96.128.87 2379/TCP,2380/TCP 48s + service/etcd1 ClusterIP 10.96.72.116 2379/TCP,2380/TCP 48s + service/etcd2 ClusterIP 10.96.99.182 2379/TCP,2380/TCP 48s + service/vineyard-controller-manager-metrics-service ClusterIP 10.96.240.173 8443/TCP 72s + service/vineyard-webhook-service ClusterIP 10.96.41.132 443/TCP 72s + service/vineyardd-sample-rpc ClusterIP 10.96.102.183 9600/TCP 48s + + NAME READY UP-TO-DATE AVAILABLE AGE + deployment.apps/vineyard-controller-manager 1/1 1 1 72s + deployment.apps/vineyardd-sample 3/3 3 3 48s + + NAME DESIRED CURRENT READY AGE + replicaset.apps/vineyard-controller-manager-5c6f4bc454 1 1 1 72s + replicaset.apps/vineyardd-sample-5cc797668f 3 3 3 48s + +References +---------- + +In addition to deploying and managing the vineyard cluster, the operator plays a crucial role in scheduling +workloads on vineyard. This optimizes data sharing between tasks in workflows and triggers necessary data +movement or transformation tasks. Detailed references and examples can be found in :code:`vineyard-operator`. + +To simplify interactions with vineyard on Kubernetes, we offer a command-line tool, :code:`vineyardctl`, which +automates much of the boilerplate configuration required when deploying workflows with vineyard on Kubernetes. + +.. panels:: + :header: text-center + :column: col-lg-12 p-2 + + .. link-button:: ./vineyard-operator + :type: ref + :text: Vineyard operator + :classes: btn-block stretched-link text-left + ^^^^^^^^^^^^ + Vineyard operator manages vineyard cluster and orchestrates shared objects on Kubernetes. + + --- + + .. link-button:: ./vineyardctl + :type: ref + :text: vineyardctl + :classes: btn-block stretched-link text-left + ^^^^^^^^^^^^ + :code:`vineyardctl` is the command-line tool for working with the Vineyard Operator. + +.. _vineyardctl: https://github.com/v6d-io/v6d/blob/main/k8s/cmd/README.md +.. _kind: https://kind.sigs.k8s.io +.. _CRD: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions diff --git a/_sources/notes/cloud-native/vineyard-operator.rst.txt b/_sources/notes/cloud-native/vineyard-operator.rst.txt new file mode 100644 index 0000000000..4a4cabf58a --- /dev/null +++ b/_sources/notes/cloud-native/vineyard-operator.rst.txt @@ -0,0 +1,1403 @@ +.. _vineyard-operator: + +Vineyard Operator +================= + +Architecture +------------ + +The following figure demonstrates the architecture of vineyard operator. + +.. figure:: ../../images/vineyard_operator_arch.png + :width: 75% + :alt: Architecture of vineyard operator + + Architecture of vineyard operator + +.. contents:: Table of Contents + :depth: 2 + :local: + :class: this-will-duplicate-information-and-it-is-still-useful-here + +Create a vineyard Cluster +------------------------- + +After successfully installing the vineyard operator (refer to :ref:`deploy-on-kubernetes` +for installation details), you can effortlessly create a vineyard cluster by utilizing +the :code:`Vineyardd` CRD. The following example demonstrates the creation of a vineyard +cluster with 3 daemon replicas: + +.. note:: + + The namespace of the vineyard cluster must be the same as the namespace of the + vineyard operator, as the vineyard cluster will use the vineyard operator's + service account. + +.. code:: yaml + + $ cat < 2379/TCP 48s + service/etcd0 ClusterIP 10.96.128.87 2379/TCP,2380/TCP 48s + service/etcd1 ClusterIP 10.96.72.116 2379/TCP,2380/TCP 48s + service/etcd2 ClusterIP 10.96.99.182 2379/TCP,2380/TCP 48s + service/vineyard-controller-manager-metrics-service ClusterIP 10.96.240.173 8443/TCP 72s + service/vineyard-webhook-service ClusterIP 10.96.41.132 443/TCP 72s + service/vineyardd-sample-rpc ClusterIP 10.96.102.183 9600/TCP 48s + + NAME READY UP-TO-DATE AVAILABLE AGE + deployment.apps/vineyard-controller-manager 1/1 1 1 72s + deployment.apps/vineyardd-sample 3/3 3 3 48s + + NAME DESIRED CURRENT READY AGE + replicaset.apps/vineyard-controller-manager-5c6f4bc454 1 1 1 72s + replicaset.apps/vineyardd-sample-5cc797668f 3 3 3 48s + +Also, if you want to use the custom vineyard socket path and mount something like /dev to the +vineyard container, you could use the following YAML file: + +.. code:: yaml + + $ cat <`_. + +Installing vineyard as sidecar +------------------------------ + +Vineyard can be seamlessly integrated as a sidecar container within a pod. We offer the `Sidecar` +Custom Resource Definition (CRD) for configuring and managing the sidecar container. The `Sidecar` +CRD shares many similarities with the `Vineyardd` CRD, and all available configurations can be found +in the `Sidecar CRD <../references/crds.md#sidecar>`_. + +Besides, We provide some labels and annotations to help users to use the sidecar in vineyard operator. +The following are all labels that we provide: + +.. list-table:: Sidecar Configurations + :widths: 25 15 60 + :header-rows: 1 + + * - Name + - Yaml Fields + - Description + + * - "sidecar.v6d.io/enabled" + - labels + - Enable the sidecar. + + * - "sidecar.v6d.io/name" + - annotations + - The name of sidecar cr. If the name is `default`, the default sidecar cr will be created. + +There are two methods to install vineyard as a sidecar: + +- Utilize the **default sidecar configuration**. Users should add two annotations, + **sidecar.v6d.io/enabled: true** and **sidecar.v6d.io/name: default**, to their app's YAML. + This will create a default sidecar Custom Resource (CR) for observation. + +- Employ the **custom sidecar configuration**. Users must first create a custom sidecar CR, + such as `sidecar-demo`, and then add two annotations, **sidecar.v6d.io/enabled: true** and + **sidecar.v6d.io/name: sidecar-demo**, to their app's YAML. + +The following example demonstrates how to install vineyard as a sidecar container within a +pod. First, install the vineyard operator according to the previous steps, and then create +a namespace with the specific label `sidecar-injection: enabled` to enable the sidecar. + +.. code:: bash + + $ kubectl create namespace vineyard-job + $ kubectl label namespace vineyard-job sidecar-injection=enabled + + +Next, use the following YAML to inject the default sidecar into the pod. + +.. note:: + + Please configure the command field of your app container to be in the format + **["/bin/sh" or "/bin/bash", "-c", (your app command)]**. After injecting the vineyard + sidecar, the command field will be modified to **["/bin/sh" or "/bin/bash", "-c", + while [ ! -e /var/run/vineyard.sock ]; do sleep 1; done;" + (your app command)]** to + ensure that the vineyard sidecar is ready before the app container starts. + +.. code:: yaml + + $ cat <`_. + + +In general, the GlobalObjects are created as intermediate objects when deploying +users' applications. You could get them as follows. + +.. code:: bash + + $ kubectl get globalobjects -A + NAMESPACE NAME ID NAME SIGNATURE TYPENAME + vineyard-system o001bcbcea406acd0 o001bcbcea406acd0 s001bcbcea4069f60 vineyard::GlobalDataFrame + vineyard-system o001bcc19dbfc9c34 o001bcc19dbfc9c34 s001bcc19dbfc8d7a vineyard::GlobalDataFrame + +LocalObject +^^^^^^^^^^^ + +The **LocalObject** custom resource definition (CRD) declaratively defines the local object +in a Kubernetes cluster, and you can find all configurations in the `LocalObject CRD +<../references/crds.md#localobject>`_. + +The LocalObjects are also intermediate objects just like the GlobalObjects, and you could +get them as follows. + +.. code:: bash + + $ kubectl get localobjects -A + +.. admonition:: Expected output + :class: admonition-details + + .. code:: bash + + NAMESPACE NAME ID NAME SIGNATURE TYPENAME INSTANCE HOSTNAME + vineyard-system o001bcbce202ab390 o001bcbce202ab390 s001bcbce202aa6f6 vineyard::DataFrame 0 kind-worker2 + vineyard-system o001bcbce21d273e4 o001bcbce21d273e4 s001bcbce21d269c2 vineyard::DataFrame 1 kind-worker + vineyard-system o001bcbce24606f6a o001bcbce24606f6a s001bcbce246067fc vineyard::DataFrame 2 kind-worker3 + +Vineyard Scheduler +------------------ + +The Vineyard operator includes a scheduler plugin designed to efficiently schedule workloads +on Vineyard by placing them as close as possible to their input data, thereby reducing data +migration costs. The Vineyard scheduler plugin is developed based on the `Kubernetes Scheduling +Framework`_, and its overall scheduling strategy can be summarized as follows: + +- All Vineyard workloads can only be deployed on nodes where the Vineyard daemon server is + present. +- If a workload does not depend on any other workload, it will be scheduled using a + **round-robin** approach. For example, if a workload has 3 replicas and there are 3 nodes + with Vineyard daemon servers, the first replica will be scheduled on the first node, the + second replica on the second node, and so on. +- If a workload depends on other workloads, it will be scheduled using a **best-effort** policy. + Assuming a workload produces N chunks during its lifecycle, and there are M nodes with + Vineyard daemon servers, the best-effort policy will attempt to make the next workload + consume :code:`M/N`: chunks. For instance, imagine a workload produces 12 chunks with the + following distribution: + + .. code:: + + node1: 0-8 + node2: 9-11 + node3: 12 + + The next workload has 3 replicas, and the best-effort policy will schedule it as follows: + + .. code:: + + replica1 -> node1 (consume 0-3 chunks) + replica2 -> node1 (consume 4-7 chunks) + replica3 -> node2 (consume 9-11 chunks, the other chunks will be migrated to the node) + +Utilizing the Vineyard Scheduler +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Vineyard scheduler is integrated into the Vineyard operator and deployed alongside it. +This scheduler plugin relies on specific annotations and labels to provide necessary input +information. The required configurations are listed below in a clear and comprehensive manner: + +.. admonition:: Scheduler Plugin Configurations + :class: admonition-details + + .. list-table:: + :widths: 25 15 60 + :header-rows: 1 + + * - Name + - Yaml Fields + - Description + + * - "scheduling.k8s.v6d.io/required" + - annotations + - All jobs required by the job. If there are + more than two tasks, use the concatenator ',' + to concatenate them into a string. + E.g. `job1,job2,job3`. + If there is no required jobs, set `none`. + + * - "scheduling.k8s.v6d.io/vineyardd" + - labels + - The name or namespaced name of vineyardd. e.g., + `vineyard-sample` or + `vineyard-system/vineyard-sample`. + + * - "scheduling.k8s.v6d.io/job "" + - labels + - The job name. + + * - "schedulerName" + - spec + - The vineyard scheduler's name, and the + default value is `vineyard-scheduler`. + +In this section, we will demonstrate a comprehensive example of utilizing the Vineyard +scheduler. To begin, ensure that the Vineyard operator and Vineyard daemon server are +installed by following the steps outlined earlier. Then, proceed to deploy `workflow-job1`_ +as shown below. + +.. code:: bash + + $ kubectl create ns vineyard-job + +.. code:: yaml + + $ cat < 1 kind-worker2 + o001c8729e4590626 o001c8729e4590626 s001c8729e458f47a vineyard::Tensor 2 kind-worker3 + + # when a job is scheduled, the scheduler will create a configmap to record the globalobject id + # that the next job will consume. + $ kubectl get configmap v6d-workflow-demo-job1 -n vineyard-job -o yaml + apiVersion: v1 + data: + kind-worker3: o001c8729e4590626 + v6d-workflow-demo-job1: o001c8729e49e06b8 + kind: ConfigMap + ... + +Then deploy the `workflow-job2`_ as follows. + +.. code:: yaml + + $ cat <`_ to get more details. + +The Operation Custom Resource (CR) is created by the vineyard scheduler while scheduling vineyard jobs. +You can retrieve the created Operation CRs as follows: + +.. code:: bash + + $ kubectl get operation -A + NAMESPACE NAME OPERATION TYPE STATE + vineyard-job dask-repartition-job2-bbf596bf4-985vc repartition dask + +Currently, the vineyard operator includes three pluggable drivers: `checkpoint`, `assembly`, and +`repartition`. The following sections provide a brief introduction to each of these drivers. + +Checkpoint +^^^^^^^^^^ + +Vineyard currently supports two types of checkpoint drivers: + +1. Active checkpoint - **Serialization**: Users can store data in temporary or persistent storage + for checkpoint purposes using the API (`vineyard.io.serialize/deserialize`). *Note* that the + serialization process is triggered by the user within the application image, and the volume is + also created by the user. Therefore, it is not managed by the vineyard operator. + +2. Passive checkpoint - **Spill**: Vineyard now supports spilling data from memory to storage + when the data size exceeds the available memory capacity. There are two watermarks for memory + spilling: the low watermark and the high watermark. When the data size surpasses the high watermark, + vineyardd will spill the excess data to storage until it reaches the low watermark. + +Triggering a checkpoint job +""""""""""""""""""""""""""" + +Now, the checkpoint driver (**Spill**) is configured within the `vineyardd` Custom Resource +Definition (CRD). To create a default vineyardd daemon server with the spill mechanism enabled, +use the following YAML file: + +.. note:: + + The spill mechanism supports temporary storage (`HostPath`_) and persistent storage + (`PersistentVolume`_) + +.. code:: yaml + + $ cat <`_ +for more details. + +After data backup, you can create a Recover CR to restore a certain vineyard backup data. +Please refer the `Recover CRD <../references/crds.md#recover>`_ for more details. + +Next, we will show how to use the failover mechanism in vineyard operator. Assuming that +we have a vineyard cluster that contains some objects, then we create a backup cr to back +up the data. The following is the yaml file of the backup: + +.. note:: + + Please make sure you have installed the vineyard operator and vineyardd before + running the following yaml file. + +.. code:: yaml + + $ cat < operation -> job2. + +``` +vineyardctl create operation [flags] +``` + +**SEE ALSO** + +* [vineyardctl create](#vineyardctl-create) - Create a vineyard jobs on kubernetes + +### Examples + +```shell + # create a local assembly operation between job1 and job2 + vineyardctl create operation --name assembly \ + --type local \ + --require job1 \ + --target job2 \ + --timeoutSeconds 600 + + # create a distributed assembly operation between job1 and job2 + vineyardctl create operation --name assembly \ + --type distributed \ + --require job1 \ + --target job2 \ + --timeoutSeconds 600 + + # create a dask repartition operation between job1 and job2 + vineyardctl create operation --name repartition \ + --type dask \ + --require job1 \ + --target job2 \ + --timeoutSeconds 600 +``` + +### Options + +``` + -h, --help help for operation + --kind string the kind of operation, including "assembly" and "repartition" + --name string the name of operation + --require string the job that need an operation to be executed + --target string the job that need to be executed before this operation + --timeoutSeconds int the timeout seconds of operation (default 600) + --type string the type of operation: for assembly, it can be "local" or "distributed"; for repartition, it can be "dask" +``` + +## `vineyardctl create recover` + +Create a recover cr to recover the current vineyard cluster on kubernetes + +### Synopsis + +Recover the current vineyard cluster on kubernetes. You could +recover all objects from a backup of vineyard cluster. Usually, +the recover crd should be created in the same namespace of +the backup job. + +Notice, the command is used to create a recover cr for the +vineyard operator and you must deploy the vineyard operator +and vineyard cluster before using it. + +``` +vineyardctl create recover [flags] +``` + +**SEE ALSO** + +* [vineyardctl create](#vineyardctl-create) - Create a vineyard jobs on kubernetes + +### Examples + +```shell + # create a recover cr for a backup job in the same namespace + vineyardctl create recover --backup-name vineyardd-sample -n vineyard-system +``` + +### Options + +``` + --backup-name string the name of backup job (default "vineyard-backup") + -h, --help help for recover + --recover-name string the name of recover job (default "vineyard-recover") +``` + +## `vineyardctl csi` + +Start the vineyard csi driver + +``` +vineyardctl csi [flags] +``` + +**SEE ALSO** + +* [vineyardctl](#vineyardctl) - vineyardctl is the command-line tool for interact with the Vineyard Operator. + +### Examples + +```shell + # start the csi with the specific endpoint and node id + vineyardctl csi --endpoint=unix:///csi/csi.sock --nodeid=csinode1 +``` + +### Options + +``` + -f, --endpoint string the endpoint of vineyard csi driver + -h, --help help for csi + --nodeid string the node id of vineyard csi driver + --state-file-path string the path of state file (default "/csi/state") +``` + +## `vineyardctl delete` + +Delete the vineyard components from kubernetes + +**SEE ALSO** + +* [vineyardctl](#vineyardctl) - vineyardctl is the command-line tool for interact with the Vineyard Operator. +* [vineyardctl delete backup](#vineyardctl-delete-backup) - Delete the backup job on kubernetes +* [vineyardctl delete csidriver](#vineyardctl-delete-csidriver) - Delete the vineyard csi driver on kubernetes +* [vineyardctl delete operation](#vineyardctl-delete-operation) - Delete the operation from kubernetes +* [vineyardctl delete operator](#vineyardctl-delete-operator) - Delete the vineyard operator from kubernetes +* [vineyardctl delete recover](#vineyardctl-delete-recover) - Delete the recover job from kubernetes +* [vineyardctl delete vineyard-cluster](#vineyardctl-delete-vineyard-cluster) - Delete the vineyard cluster from kubernetes +* [vineyardctl delete vineyard-deployment](#vineyardctl-delete-vineyard-deployment) - delete vineyard-deployment will delete the vineyard deployment without vineyard operator +* [vineyardctl delete vineyardd](#vineyardctl-delete-vineyardd) - Delete the vineyardd cluster from kubernetes + +### Examples + +```shell + # delete the default vineyard cluster on kubernetes + vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config delete + + # delete the default vineyard operator on kubernetes + vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config delete operator + + # delete the default vineyardd on kubernetes + vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config delete vineyardd +``` + +### Options + +``` + -h, --help help for delete +``` + +## `vineyardctl delete backup` + +Delete the backup job on kubernetes + +### Synopsis + +Delete the backup job on kubernetes. + +``` +vineyardctl delete backup [flags] +``` + +**SEE ALSO** + +* [vineyardctl delete](#vineyardctl-delete) - Delete the vineyard components from kubernetes + +### Examples + +```shell + # delete the default backup job + vineyardctl delete backup +``` + +### Options + +``` + --backup-name string the name of backup job (default "vineyard-backup") + -h, --help help for backup +``` + +## `vineyardctl delete csidriver` + +Delete the vineyard csi driver on kubernetes + +``` +vineyardctl delete csidriver [flags] +``` + +**SEE ALSO** + +* [vineyardctl delete](#vineyardctl-delete) - Delete the vineyard components from kubernetes + +### Examples + +```shell + # delete the csi driver named "csidriver-test" + vineyardctl delete csidriver --name csidriver-test +``` + +### Options + +``` + -h, --help help for csidriver + --name string The name of the csi driver cr. (default "csidriver-sample") +``` + +## `vineyardctl delete operation` + +Delete the operation from kubernetes + +``` +vineyardctl delete operation [flags] +``` + +**SEE ALSO** + +* [vineyardctl delete](#vineyardctl-delete) - Delete the vineyard components from kubernetes + +### Examples + +```shell + # delete the operation named "assembly-test" in the "vineyard-system" namespace + vineyardctl delete operation --name assembly-test +``` + +### Options + +``` + -h, --help help for operation + --name string the name of operation +``` + +## `vineyardctl delete operator` + +Delete the vineyard operator from kubernetes + +``` +vineyardctl delete operator [flags] +``` + +**SEE ALSO** + +* [vineyardctl delete](#vineyardctl-delete) - Delete the vineyard components from kubernetes + +### Examples + +```shell + # delete the default vineyard operator in the vineyard-system namespace + vineyardctl delete operator + + # delete the vineyard operator in a specific namespace + vineyardctl delete operator -n +``` + +### Options + +``` + -h, --help help for operator +``` + +## `vineyardctl delete recover` + +Delete the recover job from kubernetes + +``` +vineyardctl delete recover [flags] +``` + +**SEE ALSO** + +* [vineyardctl delete](#vineyardctl-delete) - Delete the vineyard components from kubernetes + +### Examples + +```shell + # delete the default recover job on kubernetes + vineyardctl delete recover +``` + +### Options + +``` + -h, --help help for recover + --recover-name string the name of recover job (default "vineyard-recover") +``` + +## `vineyardctl delete vineyard-cluster` + +Delete the vineyard cluster from kubernetes + +``` +vineyardctl delete vineyard-cluster [flags] +``` + +**SEE ALSO** + +* [vineyardctl delete](#vineyardctl-delete) - Delete the vineyard components from kubernetes + +### Examples + +```shell + # delete the default vineyard cluster on kubernetes + vineyardctl delete vineyard-cluster +``` + +### Options + +``` + -h, --help help for vineyard-cluster +``` + +## `vineyardctl delete vineyard-deployment` + +delete vineyard-deployment will delete the vineyard deployment without vineyard operator + +``` +vineyardctl delete vineyard-deployment [flags] +``` + +**SEE ALSO** + +* [vineyardctl delete](#vineyardctl-delete) - Delete the vineyard components from kubernetes + +### Examples + +```shell + # delete the default vineyard deployment in the vineyard-system namespace + vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config delete vineyard-deployment + + # delete the vineyard deployment with specific name in the vineyard-system namespace + vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config delete vineyard-deployment \ + --name vineyardd-0 +``` + +### Options + +``` + -h, --help help for vineyard-deployment + --name string the name of vineyardd (default "vineyardd-sample") +``` + +## `vineyardctl delete vineyardd` + +Delete the vineyardd cluster from kubernetes + +``` +vineyardctl delete vineyardd [flags] +``` + +**SEE ALSO** + +* [vineyardctl delete](#vineyardctl-delete) - Delete the vineyard components from kubernetes + +### Examples + +```shell + # delete the default vineyardd cluster(vineyardd-sample) in the default namespace + vineyardctl delete vineyardd + + # delete the specific vineyardd cluster in the vineyard-system namespace + vineyardctl -n vineyard-system delete vineyardd --name vineyardd-test +``` + +### Options + +``` + -h, --help help for vineyardd + --name string the name of vineyardd (default "vineyardd-sample") +``` + +## `vineyardctl deploy` + +Deploy the vineyard components on kubernetes + +**SEE ALSO** + +* [vineyardctl](#vineyardctl) - vineyardctl is the command-line tool for interact with the Vineyard Operator. +* [vineyardctl deploy backup-job](#vineyardctl-deploy-backup-job) - Deploy a backup job of vineyard cluster on kubernetes +* [vineyardctl deploy csidriver](#vineyardctl-deploy-csidriver) - Deploy the vineyard csi driver on kubernetes +* [vineyardctl deploy operator](#vineyardctl-deploy-operator) - Deploy the vineyard operator on kubernetes +* [vineyardctl deploy recover-job](#vineyardctl-deploy-recover-job) - Deploy a recover job to recover a backup of current vineyard cluster on kubernetes +* [vineyardctl deploy vineyard-cluster](#vineyardctl-deploy-vineyard-cluster) - Deploy the vineyard cluster from kubernetes +* [vineyardctl deploy vineyard-deployment](#vineyardctl-deploy-vineyard-deployment) - DeployVineyardDeployment builds and deploy the yaml file of vineyardd without vineyard operator +* [vineyardctl deploy vineyardd](#vineyardctl-deploy-vineyardd) - Deploy the vineyardd on kubernetes + +### Examples + +```shell + # deploy the default vineyard cluster on kubernetes + vineyardctl --kubeconfig $HOME/.kube/config deploy vineyard-cluster + + # deploy the vineyard operator on kubernetes + vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config deploy operator + + # deploy the vineyardd on kubernetes + vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config deploy vineyardd + + # deploy the vineyard csi driver on kubernetes + vineyardctl deploy csidriver --name vineyard-csi-sample \ + --clusters vineyard-system/vineyardd-sample,default/vineyardd-sample +``` + +### Options + +``` + -h, --help help for deploy +``` + +## `vineyardctl deploy backup-job` + +Deploy a backup job of vineyard cluster on kubernetes + +### Synopsis + +Deploy the backup job for the vineyard cluster on kubernetes, +which will backup all objects of the current vineyard cluster +quickly. For persistent storage, you could specify the pv spec +and pv spec and the related pv and pvc will be created automatically. +Also, you could also specify the existing pv and pvc name to use + +``` +vineyardctl deploy backup-job [flags] +``` + +**SEE ALSO** + +* [vineyardctl deploy](#vineyardctl-deploy) - Deploy the vineyard components on kubernetes + +### Examples + +```shell + # deploy a backup job for all vineyard objects of the vineyard + # cluster on kubernetes and you could define the pv and pvc + # spec from json string as follows + vineyardctl deploy backup-job \ + --vineyard-deployment-name vineyardd-sample \ + --vineyard-deployment-namespace vineyard-system \ + --path /var/vineyard/dump \ + --pv-pvc-spec '{ + "pv-spec": { + "capacity": { + "storage": "1Gi" + }, + "accessModes": [ + "ReadWriteOnce" + ], + "storageClassName": "manual", + "hostPath": { + "path": "/var/vineyard/dump" + } + }, + "pvc-spec": { + "storageClassName": "manual", + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "1Gi" + } + } + } + }' + + # deploy a backup job for the vineyard cluster on kubernetes + # you could define the pv and pvc spec from yaml string as follows + vineyardctl deploy backup-job \ + --vineyard-deployment-name vineyardd-sample \ + --vineyard-deployment-namespace vineyard-system \ + --path /var/vineyard/dump \ + --pv-pvc-spec \ + ' + pv-spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + storageClassName: manual + hostPath: + path: "/var/vineyard/dump" + pvc-spec: + storageClassName: manual + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + ' + + # deploy a backup job for specific vineyard objects of the vineyard + # cluster on kubernetes. + cat pv-pvc.json | vineyardctl deploy backup-job \ + --vineyard-deployment-name vineyardd-sample \ + --vineyard-deployment-namespace vineyard-system \ + --objectIDs "o000018d29207fd01,o000018d80d264010" \ + --path /var/vineyard/dump + + # Assume you have already deployed a pvc named "pvc-sample", you + # could use them as the backend storage for the backup job as follows + vineyardctl deploy backup-job \ + --vineyard-deployment-name vineyardd-sample \ + --vineyard-deployment-namespace vineyard-system \ + --path /var/vineyard/dump \ + --pvc-name pvc-sample + + # The namespace to deploy the backup and recover job must be the same + # as the vineyard cluster namespace. + # Assume the vineyard cluster is deployed in the namespace "test", you + # could deploy the backup job as follows + vineyardctl deploy backup-job \ + --vineyard-deployment-name vineyardd-sample \ + --vineyard-deployment-namespace test \ + --namespace test \ + --path /var/vineyard/dump \ + --pvc-name pvc-sample +``` + +### Options + +``` + --backup-name string the name of backup job (default "vineyard-backup") + -h, --help help for backup-job + --objectIDs strings the specific objects to be backed up + --path string the path of the backup data + --pv-pvc-spec string the PersistentVolume and PersistentVolumeClaim of the backup data + --pvc-name string the name of an existing PersistentVolumeClaim + --vineyard-deployment-name string the name of vineyard deployment + --vineyard-deployment-namespace string the namespace of vineyard deployment +``` + +## `vineyardctl deploy csidriver` + +Deploy the vineyard csi driver on kubernetes + +### Synopsis + +Deploy the Vineyard CSI Driver on kubernetes. +The CR is a cluster-scoped resource, and can only be created once. + +``` +vineyardctl deploy csidriver [flags] +``` + +**SEE ALSO** + +* [vineyardctl deploy](#vineyardctl-deploy) - Deploy the vineyard components on kubernetes + +### Examples + +```shell + # deploy the Vineyard CSI Driver named vineyard-csi-sample on kubernetes + # Notice, the clusters are built as {vineyard-deployment-namespace}/{vineyard-deployment-name} + # and sperated by comma, e.g. vineyard-system/vineyardd-sample, default/vineyardd-sample + # They must be created before deploying the Vineyard CSI Driver. + vineyardctl deploy csidriver --name vineyard-csi-sample \ + --clusters vineyard-system/vineyardd-sample,default/vineyardd-sample +``` + +### Options + +``` + --attacherImage string The image of csi attacher. (default "registry.k8s.io/sig-storage/csi-attacher:v4.0.0") + --clusters strings The list of vineyard clusters. + --enableToleration Enable toleration for vineyard csi driver. + -h, --help help for csidriver + -i, --image string The image of vineyard csi driver. (default "vineyardcloudnative/vineyard-operator") + --imagePullPolicy string The image pull policy of vineyard csi driver. (default "IfNotPresent") + --livenessProbeImage string The image of livenessProbe. (default "registry.k8s.io/sig-storage/livenessprobe:v2.8.0") + --name string The name of the csi driver cr. (default "csidriver-sample") + --nodeRegistrarImage string The image of csi nodeRegistrar. (default "registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.6.0") + --provisionerImage string The image of csi provisioner. (default "registry.k8s.io/sig-storage/csi-provisioner:v3.3.0") + --sidecar.enableTopology Enable topology for the csi driver. + --sidecar.imagePullPolicy string The image pull policy of all sidecar containers. (default "Always") + -s, --storageClassName string The name of storage class. (default "vineyard-csi") + -m, --volumeBindingMode string The volume binding mode of the storage class. (default "WaitForFirstConsumer") +``` + +## `vineyardctl deploy operator` + +Deploy the vineyard operator on kubernetes + +### Synopsis + +Deploy the vineyard operator on kubernetes. + +``` +vineyardctl deploy operator [flags] +``` + +**SEE ALSO** + +* [vineyardctl deploy](#vineyardctl-deploy) - Deploy the vineyard components on kubernetes + +### Examples + +```shell + # deploy the vineyard operator on the 'vineyard-system' namespace + vineyardctl deploy operator + + # deploy the vineyard operator on the existing namespace + vineyardctl deploy operator -n my-custom-namespace + + # deploy the vineyard operator on the new namespace + vineyardctl deploy operator -n a-new-namespace-name --create-namespace +``` + +### Options + +``` + -h, --help help for operator +``` + +## `vineyardctl deploy recover-job` + +Deploy a recover job to recover a backup of current vineyard cluster on kubernetes + +### Synopsis + +Deploy the recover job for vineyard cluster on kubernetes, which +will recover all objects from a backup of vineyard cluster. Usually, +the recover job should be created in the same namespace of +the backup job. + +``` +vineyardctl deploy recover-job [flags] +``` + +**SEE ALSO** + +* [vineyardctl deploy](#vineyardctl-deploy) - Deploy the vineyard components on kubernetes + +### Examples + +```shell + # Deploy a recover job for the vineyard deployment in the same namespace. + # After the recover job finished, the command will create a kubernetes + # configmap named [recover-name]+"-mapping-table" that contains the + # mapping table from the old vineyard objects to the new ones. + # + # If you create the recover job as follows, you can get the mapping table via + # "kubectl get configmap vineyard-recover-mapping-table -n vineyard-system -o yaml" + # the left column is the old object id, and the right column is the new object id. + vineyardctl deploy recover-job \ + --vineyard-deployment-name vineyardd-sample \ + --vineyard-deployment-namespace vineyard-system \ + --recover-path /var/vineyard/dump \ + --pvc-name vineyard-backup +``` + +### Options + +``` + -h, --help help for recover-job + --pvc-name string the name of an existing PersistentVolumeClaim + --recover-name string the name of recover job (default "vineyard-recover") + --recover-path string the path of recover job + --vineyard-deployment-name string the name of vineyard deployment + --vineyard-deployment-namespace string the namespace of vineyard deployment +``` + +## `vineyardctl deploy vineyard-cluster` + +Deploy the vineyard cluster from kubernetes + +``` +vineyardctl deploy vineyard-cluster [flags] +``` + +**SEE ALSO** + +* [vineyardctl deploy](#vineyardctl-deploy) - Deploy the vineyard components on kubernetes + +### Examples + +```shell + # deploy the default vineyard cluster on kubernetes + vineyardctl deploy vineyard-cluster +``` + +### Options + +``` + -h, --help help for vineyard-cluster +``` + +## `vineyardctl deploy vineyard-deployment` + +DeployVineyardDeployment builds and deploy the yaml file of vineyardd without vineyard operator + +### Synopsis + +Builds and deploy the yaml file of vineyardd the vineyardd +without vineyard operator. You could deploy a customized +vineyardd from stdin or file. + +``` +vineyardctl deploy vineyard-deployment [flags] +``` + +**SEE ALSO** + +* [vineyardctl deploy](#vineyardctl-deploy) - Deploy the vineyard components on kubernetes + +### Examples + +```shell + # deploy the default vineyard deployment on kubernetes + vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config \ + deploy vineyard-deployment + + # deploy the vineyard deployment with customized image + vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config \ + deploy vineyard-deployment --image vineyardcloudnative/vineyardd:v0.12.2 +``` + +### Options + +``` + --etcd.replicas int the number of etcd replicas in a vineyard cluster (default 1) + -f, --file string the path of vineyardd + -h, --help help for vineyard-deployment + --name string the name of vineyardd (default "vineyardd-sample") + --owner-references string The owner reference of all vineyard deployment resources + --pluginImage.backupImage string the backup image of vineyardd (default "ghcr.io/v6d-io/v6d/backup-job") + --pluginImage.daskRepartitionImage string the dask repartition image of vineyardd workflow (default "ghcr.io/v6d-io/v6d/dask-repartition") + --pluginImage.distributedAssemblyImage string the distributed image of vineyard workflow (default "ghcr.io/v6d-io/v6d/distributed-assembly") + --pluginImage.localAssemblyImage string the local assembly image of vineyardd workflow (default "ghcr.io/v6d-io/v6d/local-assembly") + --pluginImage.recoverImage string the recover image of vineyardd (default "ghcr.io/v6d-io/v6d/recover-job") + --replicas int the number of vineyardd replicas (default 3) + --securityContext string the json string of security context of vineyardd + --vineyardd.cpu string the cpu requests and limits of vineyard container + --vineyardd.envs strings The environment variables of vineyardd + --vineyardd.image string the image of vineyardd (default "vineyardcloudnative/vineyardd:latest") + --vineyardd.imagePullPolicy string the imagePullPolicy of vineyardd (default "IfNotPresent") + --vineyardd.memory string the memory requests and limits of vineyard container + --vineyardd.metric.enable enable metrics of vineyardd + --vineyardd.metric.image string the metic image of vineyardd (default "vineyardcloudnative/vineyard-grok-exporter:latest") + --vineyardd.metric.imagePullPolicy string the imagePullPolicy of the metric image (default "IfNotPresent") + --vineyardd.reserve_memory Reserving enough physical memory pages for vineyardd + --vineyardd.service.port int the service port of vineyard service (default 9600) + --vineyardd.service.type string the service type of vineyard service (default "ClusterIP") + --vineyardd.size string The size of vineyardd. You can use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki. Defaults "", means not limited + --vineyardd.socket string The directory on host for the IPC socket file. The namespace and name will be replaced with your vineyard config (default "/var/run/vineyard-kubernetes/{{.Namespace}}/{{.Name}}") + --vineyardd.spill.config string If you want to enable the spill mechanism, please set the name of spill config + --vineyardd.spill.path string The path of spill config + --vineyardd.spill.pv-pvc-spec string the json string of the persistent volume and persistent volume claim + --vineyardd.spill.spillLowerRate string The low watermark of spilling memory (default "0.3") + --vineyardd.spill.spillUpperRate string The high watermark of spilling memory (default "0.8") + --vineyardd.streamThreshold int memory threshold of streams (percentage of total memory) (default 80) + --vineyardd.syncCRDs enable metrics of vineyardd (default true) + --vineyardd.volume.mountPath string Set the mount path for the pvc + --vineyardd.volume.pvcname string Set the pvc name for storing the vineyard objects persistently + --volume string the json string of vineyardd volume + --volumeMount string the json string of vineyardd volume mount +``` + +## `vineyardctl deploy vineyardd` + +Deploy the vineyardd on kubernetes + +### Synopsis + +Deploy the vineyardd on kubernetes. You could deploy a +customized vineyardd from stdin or file. + +``` +vineyardctl deploy vineyardd [flags] +``` + +**SEE ALSO** + +* [vineyardctl deploy](#vineyardctl-deploy) - Deploy the vineyard components on kubernetes + +### Examples + +```shell + # deploy the default vineyard on kubernetes + # wait for the vineyardd to be ready(default option) + vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config deploy vineyardd + + # not to wait for the vineyardd to be ready + vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config deploy vineyardd \ + --wait=false + + # deploy the vineyardd from a yaml file + vineyardctl --kubeconfig $HOME/.kube/config deploy vineyardd --file vineyardd.yaml + + # deploy the vineyardd with customized image + vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config deploy vineyardd \ + --image vineyardd:v0.12.2 + + # deploy the vineyardd with spill mechanism on persistent storage from json string + vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config deploy vineyardd \ + --vineyardd.spill.config spill-path \ + --vineyardd.spill.path /var/vineyard/spill \ + --vineyardd.spill.pv-pvc-spec '{ + "pv-spec": { + "capacity": { + "storage": "1Gi" + }, + "accessModes": [ + "ReadWriteOnce" + ], + "storageClassName": "manual", + "hostPath": { + "path": "/var/vineyard/spill" + } + }, + "pvc-spec": { + "storageClassName": "manual", + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "512Mi" + } + } + } + }' + + # deploy the vineyardd with spill mechanism on persistent storage from yaml string + vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config deploy vineyardd \ + --vineyardd.spill.config spill-path \ + --vineyardd.spill.path /var/vineyard/spill \ + --vineyardd.spill.pv-pvc-spec \ + ' + pv-spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + storageClassName: manual + hostPath: + path: "/var/vineyard/spill" + pvc-spec: + storageClassName: manual + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 512Mi + ' + +# deploy the vineyardd with spill mechanism on persistent storage from json file + # also you could use the yaml file + cat pv-pvc.json | vineyardctl -n vineyard-system --kubeconfig $HOME/.kube/config deploy vineyardd \ + --vineyardd.spill.config spill-path \ + --vineyardd.spill.path /var/vineyard/spill \ + - +``` + +### Options + +``` + --etcd.replicas int the number of etcd replicas in a vineyard cluster (default 1) + -f, --file string the path of vineyardd + -h, --help help for vineyardd + --name string the name of vineyardd (default "vineyardd-sample") + --pluginImage.backupImage string the backup image of vineyardd (default "ghcr.io/v6d-io/v6d/backup-job") + --pluginImage.daskRepartitionImage string the dask repartition image of vineyardd workflow (default "ghcr.io/v6d-io/v6d/dask-repartition") + --pluginImage.distributedAssemblyImage string the distributed image of vineyard workflow (default "ghcr.io/v6d-io/v6d/distributed-assembly") + --pluginImage.localAssemblyImage string the local assembly image of vineyardd workflow (default "ghcr.io/v6d-io/v6d/local-assembly") + --pluginImage.recoverImage string the recover image of vineyardd (default "ghcr.io/v6d-io/v6d/recover-job") + --replicas int the number of vineyardd replicas (default 3) + --securityContext string the json string of security context of vineyardd + --vineyardd.cpu string the cpu requests and limits of vineyard container + --vineyardd.envs strings The environment variables of vineyardd + --vineyardd.image string the image of vineyardd (default "vineyardcloudnative/vineyardd:latest") + --vineyardd.imagePullPolicy string the imagePullPolicy of vineyardd (default "IfNotPresent") + --vineyardd.memory string the memory requests and limits of vineyard container + --vineyardd.metric.enable enable metrics of vineyardd + --vineyardd.metric.image string the metic image of vineyardd (default "vineyardcloudnative/vineyard-grok-exporter:latest") + --vineyardd.metric.imagePullPolicy string the imagePullPolicy of the metric image (default "IfNotPresent") + --vineyardd.reserve_memory Reserving enough physical memory pages for vineyardd + --vineyardd.service.port int the service port of vineyard service (default 9600) + --vineyardd.service.type string the service type of vineyard service (default "ClusterIP") + --vineyardd.size string The size of vineyardd. You can use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki. Defaults "", means not limited + --vineyardd.socket string The directory on host for the IPC socket file. The namespace and name will be replaced with your vineyard config (default "/var/run/vineyard-kubernetes/{{.Namespace}}/{{.Name}}") + --vineyardd.spill.config string If you want to enable the spill mechanism, please set the name of spill config + --vineyardd.spill.path string The path of spill config + --vineyardd.spill.pv-pvc-spec string the json string of the persistent volume and persistent volume claim + --vineyardd.spill.spillLowerRate string The low watermark of spilling memory (default "0.3") + --vineyardd.spill.spillUpperRate string The high watermark of spilling memory (default "0.8") + --vineyardd.streamThreshold int memory threshold of streams (percentage of total memory) (default 80) + --vineyardd.syncCRDs enable metrics of vineyardd (default true) + --vineyardd.volume.mountPath string Set the mount path for the pvc + --vineyardd.volume.pvcname string Set the pvc name for storing the vineyard objects persistently + --volume string the json string of vineyardd volume + --volumeMount string the json string of vineyardd volume mount +``` + +## `vineyardctl get` + +Get vineyard object, metadata, blob or cluster-info + +**SEE ALSO** + +* [vineyardctl](#vineyardctl) - vineyardctl is the command-line tool for interact with the Vineyard Operator. +* [vineyardctl get blob](#vineyardctl-get-blob) - Get vineyard blob +* [vineyardctl get cluster-info](#vineyardctl-get-cluster-info) - Get vineyard cluster info +* [vineyardctl get metadata](#vineyardctl-get-metadata) - Get vineyard metadata +* [vineyardctl get object](#vineyardctl-get-object) - Get vineyard object + +### Examples + +```shell + # Connect the vineyardd deployment with IPC client + # Get the cluster info and output as table + vineyardctl get cluster-info --deployment-name vineyardd-sample -n vineyard-system +``` + +### Options + +``` + -h, --help help for get +``` + +## `vineyardctl get blob` + +Get vineyard blob + +### Synopsis + +Get vineyard blob and only support IPC socket. +If you don't specify the ipc socket every time, you can set it as the +environment variable VINEYARD_IPC_SOCKET. + +``` +vineyardctl get blob [flags] +``` + +**SEE ALSO** + +* [vineyardctl get](#vineyardctl-get) - Get vineyard object, metadata, blob or cluster-info + +### Examples + +```shell + # Get vineyard blob with the given vineyard object_id and the ipc socket + vineyardctl get blob --object_id xxxxxxxx --ipc-socket /var/run/vineyard.sock + + # Get vineyard blob with the given vineyard object_id and the ipc socket + # and set the unsafe to be true + vineyardctl get blob --object_id xxxxxxxx --unsafe --ipc-socket /var/run/vineyard.sock + + # If you set the environment variable VINEYARD_IPC_SOCKET + # you can use the following command to get vineyard blob + vineyardctl get blob --object_id xxxxxxxx +``` + +### Options + +``` + --deployment-name string the name of vineyard deployment + -o, --format string the output format, support table or json, default is table (default "table") + --forward-port int the forward port of vineyard deployment (default 9600) + -h, --help help for blob + --ipc-socket string vineyard IPC socket path + --object_id string The object id to get + --port int the port of vineyard deployment (default 9600) + --rpc-socket string vineyard RPC socket path + --syncRemote If the target object is a remote object,code_remote=True will force a meta synchronization on the vineyard server. + --unsafe unsafe means getting the blob even the blob is not sealed yet.Default is False. +``` + +## `vineyardctl get cluster-info` + +Get vineyard cluster info + +### Synopsis + +Get vineyard cluster info, including +the instanceId, hostName, node name and so on. + +``` +vineyardctl get cluster-info [flags] +``` + +**SEE ALSO** + +* [vineyardctl get](#vineyardctl-get) - Get vineyard object, metadata, blob or cluster-info + +### Examples + +```shell + # Get the cluster info of vineyard deployment and output as table + vineyardctl get cluster-info --deployment-name vineyardd-sample -n vineyard-system + + # Get the cluster info of vineyard deployment and output as json + vineyardctl get cluster-info --deployment-name vineyardd-sample -n vineyard-system -o json + + # Get the cluster info via IPC socket + vineyardctl get cluster-info --ipc-socket /var/run/vineyard.sock +``` + +### Options + +``` + --deployment-name string the name of vineyard deployment + -o, --format string the output format, support table or json, default is table (default "table") + --forward-port int the forward port of vineyard deployment (default 9600) + -h, --help help for cluster-info + --ipc-socket string vineyard IPC socket path + --port int the port of vineyard deployment (default 9600) + --rpc-socket string vineyard RPC socket path +``` + +## `vineyardctl get metadata` + +Get vineyard metadata + +### Synopsis + +Get vineyard metadata and support IPC socket, +RPC socket and vineyard deployment. If you don't specify the ipc socket or rpc socket +every time, you can set it as the environment variable VINEYARD_IPC_SOCKET or +VINEYARD_RPC_SOCKET. + +``` +vineyardctl get metadata [flags] +``` + +**SEE ALSO** + +* [vineyardctl get](#vineyardctl-get) - Get vineyard object, metadata, blob or cluster-info + +### Examples + +```shell + # Get vineyard metadata with the given vineyard object_id and the ipc socket + vineyardctl get metadata --object_id xxxxxxxx --ipc-socket /var/run/vineyard.sock + + # Get vineyard metadata with the given vineyard object_id and the ipc socket + # and set the syncRemote to be true + vineyardctl get metadata --object_id xxxxxxxx --syncRemote --ipc-socket /var/run/vineyard.sock + + # Get vineyard metadata with the given vineyard object_id and the rpc socket + vineyardctl get metadata --object_id xxxxxxxx --rpc-socket 127.0.0.1:9600 +``` + +### Options + +``` + --deployment-name string the name of vineyard deployment + -o, --format string the output format, support table or json, default is table (default "table") + --forward-port int the forward port of vineyard deployment (default 9600) + -h, --help help for metadata + --ipc-socket string vineyard IPC socket path + --object_id string The object id to get + --port int the port of vineyard deployment (default 9600) + --rpc-socket string vineyard RPC socket path + --syncRemote If the target object is a remote object,code_remote=True will force a meta synchronization on the vineyard server. + --unsafe unsafe means getting the blob even the blob is not sealed yet.Default is False. +``` + +## `vineyardctl get object` + +Get vineyard object + +### Synopsis + +Get vineyard object and support IPC socket, +RPC socket and vineyard deployment. If you don't specify the ipc socket or rpc socket +every time, you can set it as the environment variable VINEYARD_IPC_SOCKET or +VINEYARD_RPC_SOCKET. + +``` +vineyardctl get object [flags] +``` + +**SEE ALSO** + +* [vineyardctl get](#vineyardctl-get) - Get vineyard object, metadata, blob or cluster-info + +### Examples + +```shell + # Get vineyard object with the given vineyard object_id and the ipc socket + vineyardctl get object --object_id xxxxxxxx --ipc-socket /var/run/vineyard.sock + + # Get vineyard object with the given vineyard object_id and the rpc socket + vineyardctl get object --object_id xxxxxxxx --rpc-socket 127.0.0.1:9600 +``` + +### Options + +``` + --deployment-name string the name of vineyard deployment + -o, --format string the output format, support table or json, default is table (default "table") + --forward-port int the forward port of vineyard deployment (default 9600) + -h, --help help for object + --ipc-socket string vineyard IPC socket path + --object_id string The object id to get + --port int the port of vineyard deployment (default 9600) + --rpc-socket string vineyard RPC socket path + --syncRemote If the target object is a remote object,code_remote=True will force a meta synchronization on the vineyard server. + --unsafe unsafe means getting the blob even the blob is not sealed yet.Default is False. +``` + +## `vineyardctl inject` + +Inject the vineyard sidecar container into a workload + +### Synopsis + +Inject the vineyard sidecar container into a workload. You can +input a workload yaml or a workload json and then get the injected +workload and some etcd manifests from the output. The workload can +be a pod or a deployment or a statefulset, etc. + +The output is a set of manifests that includes the injected workload, +the rpc service, the etcd service and the etcd cluster(e.g. several +pods and services). + +If you have a pod yaml: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: python +spec: + containers: + - name: python + image: python:3.10 + command: ["python", "-c", "import time; time.sleep(100000)"] +``` +Then, you can use the following command to inject the vineyard sidecar + +$ vineyardctl inject -f pod.yaml + +After running the command, the output is as follows: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + labels: + app.vineyard.io/name: vineyard-sidecar + app.vineyard.io/role: etcd + etcd_node: vineyard-sidecar-etcd-0 + name: vineyard-sidecar-etcd-0 + namespace: null + ownerReferences: [] +spec: + containers: + - command: + - etcd + - --name + - vineyard-sidecar-etcd-0 + - --initial-advertise-peer-urls + - http://vineyard-sidecar-etcd-0:2380 + - --advertise-client-urls + - http://vineyard-sidecar-etcd-0:2379 + - --listen-peer-urls + - http://0.0.0.0:2380 + - --listen-client-urls + - http://0.0.0.0:2379 + - --initial-cluster + - vineyard-sidecar-etcd-0=http://vineyard-sidecar-etcd-0:2380 + - --initial-cluster-state + - new + image: vineyardcloudnative/vineyardd:latest + name: etcd + ports: + - containerPort: 2379 + name: client + protocol: TCP + - containerPort: 2380 + name: server + protocol: TCP + restartPolicy: Always +--- +apiVersion: v1 +kind: Service +metadata: + labels: + etcd_node: vineyard-sidecar-etcd-0 + name: vineyard-sidecar-etcd-0 + namespace: null + ownerReferences: [] +spec: + ports: + - name: client + port: 2379 + protocol: TCP + targetPort: 2379 + - name: server + port: 2380 + protocol: TCP + targetPort: 2380 + selector: + app.vineyard.io/role: etcd + etcd_node: vineyard-sidecar-etcd-0 +--- +apiVersion: v1 +kind: Service +metadata: + name: vineyard-sidecar-etcd-service + namespace: null + ownerReferences: [] +spec: + ports: + - name: etcd-for-vineyard-port + port: 2379 + protocol: TCP + targetPort: 2379 + selector: + app.vineyard.io/name: vineyard-sidecar + app.vineyard.io/role: etcd +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.vineyard.io/name: vineyard-sidecar + name: vineyard-sidecar-rpc + namespace: null + ownerReferences: [] +spec: + ports: + - name: vineyard-rpc + port: 9600 + protocol: TCP + selector: + app.vineyard.io/name: vineyard-sidecar + app.vineyard.io/role: vineyardd + type: ClusterIP +--- +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: null + labels: + app.vineyard.io/name: vineyard-sidecar + app.vineyard.io/role: vineyardd + name: python + ownerReferences: [] +spec: + containers: + - command: + - python + - -c + - while [ ! -e /var/run/vineyard.sock ]; do sleep 1; done;import time; time.sleep(100000) + env: + - name: VINEYARD_IPC_SOCKET + value: /var/run/vineyard.sock + image: python:3.10 + name: python + resources: {} + volumeMounts: + - mountPath: /var/run + name: vineyard-socket + - command: + - /bin/bash + - -c + - | + /usr/local/bin/vineyardd --sync_crds true --socket /var/run/vineyard.sock --size \ + --stream_threshold 80 --etcd_cmd etcd --etcd_prefix /vineyard --etcd_endpoint http://vineyard-sidecar-etcd-service:2379 + env: + - name: VINEYARDD_UID + value: null + - name: VINEYARDD_NAME + value: vineyard-sidecar + - name: VINEYARDD_NAMESPACE + value: null + image: vineyardcloudnative/vineyardd:latest + imagePullPolicy: IfNotPresent + name: vineyard-sidecar + ports: + - containerPort: 9600 + name: vineyard-rpc + protocol: TCP + resources: + limits: null + requests: null + securityContext: {} + volumeMounts: + - mountPath: /var/run + name: vineyard-socket + volumes: + - emptyDir: {} + name: vineyard-socket +status: {} +``` + +Next, we will introduce a simple example to show the injection with +the apply-resources flag. + +Assume you have the following workload yaml: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + # Notice, you must set the namespace here + namespace: vineyard-job +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 +``` + +Then, you can use the following command to inject the vineyard sidecar +which means that all resources will be created during the injection except +the workload itself. The workload should be created by users. + +$ vineyardctl inject -f workload.yaml --apply-resources + +After running the command, the main output(removed some unnecessary fields) +is as follows: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + name: nginx-deployment + namespace: vineyard-job +spec: + selector: + matchLabels: + app: nginx +template: + metadata: + labels: + app: nginx + # the default sidecar name is vineyard-sidecar + app.vineyard.io/name: vineyard-sidecar + spec: + containers: + - command: null + image: nginx:1.14.2 + name: nginx + ports: + - containerPort: 80 + volumeMounts: + - mountPath: /var/run + name: vineyard-socket + - command: + - /bin/bash + - -c + - | + /usr/local/bin/vineyardd --sync_crds true --socket /var/run/vineyard.sock \ + --stream_threshold 80 --etcd_cmd etcd --etcd_prefix /vineyard \ + --etcd_endpoint http://vineyard-sidecar-etcd-service:2379 + env: + - name: VINEYARDD_UID + value: null + - name: VINEYARDD_NAME + value: vineyard-sidecar + - name: VINEYARDD_NAMESPACE + value: vineyard-job + image: vineyardcloudnative/vineyardd:latest + imagePullPolicy: IfNotPresent + name: vineyard-sidecar + ports: + - containerPort: 9600 + name: vineyard-rpc + protocol: TCP + volumeMounts: + - mountPath: /var/run + name: vineyard-socket + volumes: + - emptyDir: {} + name: vineyard-socket +``` + +The sidecar template can be accessed from the following link: +https://github.com/v6d-io/v6d/blob/main/k8s/pkg/templates/sidecar/injection-template.yaml +also you can get some inspiration from the doc link: +https://v6d.io/notes/cloud-native/vineyard-operator.html#installing-vineyard-as-sidecar + +``` +vineyardctl inject [flags] +``` + +**SEE ALSO** + +* [vineyardctl](#vineyardctl) - vineyardctl is the command-line tool for interact with the Vineyard Operator. +* [vineyardctl inject argo-workflow](#vineyardctl-inject-argo-workflow) - Inject the vineyard volumes into the argo workflow + +### Examples + +```shell + # use json format to output the injected workload + # notice that the output is a json string of all manifests + # it looks like: + # { + # "workload": "workload json string", + # "rpc_service": "rpc service json string", + # "etcd_service": "etcd service json string", + # "etcd_internal_service": [ + # "etcd internal service json string 1", + # "etcd internal service json string 2", + # "etcd internal service json string 3" + # ], + # "etcd_pod": [ + # "etcd pod json string 1", + # "etcd pod json string 2", + # "etcd pod json string 3" + # ] + # } + vineyardctl inject -f workload.yaml -o json + + # inject the default vineyard sidecar container into a workload + # output all injected manifests and then deploy them + vineyardctl inject -f workload.yaml | kubectl apply -f - + + # if you only want to get the injected workload yaml rather than + # all manifests that includes the etcd cluster and the rpc service, + # you can enable the apply-resources and then the manifests will be + # created during the injection, finally you will get the injected + # workload yaml + vineyardctl inject -f workload.yaml --apply-resources +``` + +### Options + +``` + --apply-resources Whether to apply the resources including the etcd cluster and the rpc service if you enable this flag, the etcd cluster and the rpc service will be created during the injection + --etcd-replicas int The number of etcd replicas (default 1) + -f, --file string The yaml of workload + -h, --help help for inject + --name string The name of sidecar (default "vineyard-sidecar") + -o, --output string The output format of the command, support yaml and json (default "yaml") + --owner-references string The owner reference of all injectied resources + --resource string The resource of workload + --securityContext string the json string of security context of vineyard sidecar container + --sidecar.cpu string the cpu requests and limits of vineyard container + --sidecar.envs strings The environment variables of vineyardd + --sidecar.image string the image of vineyardd (default "vineyardcloudnative/vineyardd:latest") + --sidecar.imagePullPolicy string the imagePullPolicy of vineyardd (default "IfNotPresent") + --sidecar.memory string the memory requests and limits of vineyard container + --sidecar.metric.enable enable metrics of vineyardd + --sidecar.metric.image string the metic image of vineyardd (default "vineyardcloudnative/vineyard-grok-exporter:latest") + --sidecar.metric.imagePullPolicy string the imagePullPolicy of the metric image (default "IfNotPresent") + --sidecar.reserve_memory Reserving enough physical memory pages for vineyardd + --sidecar.service.port int the service port of vineyard service (default 9600) + --sidecar.service.type string the service type of vineyard service (default "ClusterIP") + --sidecar.size string The size of vineyardd. You can use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki. Defaults "", means not limited + --sidecar.socket string The directory on host for the IPC socket file. The namespace and name will be replaced with your vineyard config (default "/var/run/vineyard-kubernetes/{{.Namespace}}/{{.Name}}") + --sidecar.spill.config string If you want to enable the spill mechanism, please set the name of spill config + --sidecar.spill.path string The path of spill config + --sidecar.spill.pv-pvc-spec string the json string of the persistent volume and persistent volume claim + --sidecar.spill.spillLowerRate string The low watermark of spilling memory (default "0.3") + --sidecar.spill.spillUpperRate string The high watermark of spilling memory (default "0.8") + --sidecar.streamThreshold int memory threshold of streams (percentage of total memory) (default 80) + --sidecar.syncCRDs enable metrics of vineyardd (default true) + --sidecar.volume.mountPath string Set the mount path for the pvc + --sidecar.volume.pvcname string Set the pvc name for storing the vineyard objects persistently + --volume string the json string of vineyard sidecar container volume + --volumeMount string the json string of vineyard sidecar container volume mount +``` + +## `vineyardctl inject argo-workflow` + +Inject the vineyard volumes into the argo workflow + +### Synopsis + +Inject the vineyard volumes into the argo workflow DAGs. You can input +the workflow manifest file and the injected manifest file with +vineyard volume will be output to the file with the suffix +"_with_vineyard", such as "workflow_with_vineyard.yaml". + +Suppose the workflow manifest named "workflow.yaml" is as follows: + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: mlops- +spec: + entrypoint: dag + templates: + - name: producer + container: + image: producer:latest + command: [python] + args: ["/producer.py"] + - name: consumer + container: + image: consumer:latest + command: [python] + args: ["/consumer.py"] + - name: dag + dag: + tasks: + - name: producer + template: producer + - name: consumer + template: consumer + dependencies: + - producer +``` + +Assume the 'producer' and 'consumer' task all need to use vineyard +volume, you can inject the vineyard volume into the workflow manifest +with the following command: + +$ vineyardctl inject argo-workflow -f workflow.yaml \ + --templates="producer,consumer" \ + --vineyard-cluster="vineyard-system/vineyardd-sample" \ + --mount-path="/vineyard/data" \ + --dag="dag" \ + --tasks="producer,consumer" \ + --output-as-file + +The injected manifest will be output to the file named "workflow_with_vineyard.yaml". + +$ cat workflow_with_vineyard.yaml + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: mlops- +spec: + entrypoint: dag + templates: + - name: producer + container: + image: producer:latest + command: [python] + args: ["/producer.py"] + ################## Injected ################# + volumeMounts: + - name: vineyard-objects + mountPath: /vineyard/data + ############################################# + ######################## Injected ####################### + volumes: + - name: vineyard-objects + persistentVolumeClaim: + claimName: '{{inputs.parameters.vineyard-objects-name}}' + ######################################################### + ############## Injected ############# + inputs: + parameters: + - {name: vineyard-objects-name} + ##################################### + - name: consumer + container: + image: consumer:latest + command: [python] + args: ["/consumer.py"] + ################## Injected ################# + volumeMounts: + - name: vineyard-objects + mountPath: /vineyard/data + ############################################# + ######################## Injected ####################### + volumes: + - name: vineyard-objects + persistentVolumeClaim: + claimName: '{{inputs.parameters.vineyard-objects-name}}' + ######################################################### + ############## Injected ############# + inputs: + parameters: + - {name: vineyard-objects-name} + ##################################### + - name: dag + dag: + tasks: + - name: producer + template: producer + arguments: + parameters: + ################################# Injected ################################ + - name: vineyard-objects-name + value: '{{tasks.vineyard-objects.outputs.parameters.vineyard-objects-name}}' + ########################################################################### + dependencies: + ########### Injected ########## + - vineyard-objects + ############################### + - name: consumer + template: consumer + arguments: + parameters: + ################################# Injected ################################ + - name: vineyard-objects-name + value: '{{tasks.vineyard-objects.outputs.parameters.vineyard-objects-name}}' + ########################################################################### + dependencies: + - producer + ########### Injected ########## + - vineyard-objects + ############################### + ########## Injected ######### + - name: vineyard-objects + template: vineyard-objects + ############################# +############################# Injected ########################## + - name: vineyard-objects + resource: + action: create + setOwnerReference: true + manifest: | + apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: '{{workflow.name}}-vineyard-objects-pvc' + spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Mi + storageClassName: vineyard-system.vineyardd-sample.csi + outputs: + parameters: + - name: vineyard-objects-name + valueFrom: + jsonPath: '{.metadata.name}' +############################################################## +``` +Suppose your workflow YAML only has a single template as follows. +$ cat workflow.yaml + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: mlops- +spec: + entrypoint: dag + templates: + - name: MLops + inputs: + parameters: + - name: functions + container: + imagePullPolicy: IfNotPresent + image: mlops-benchmark:latest + command: [python] + args: ["-m", "{{inputs.parameters.functions}}"] + - name: dag + dag: + tasks: + - name: producer + template: MLops + arguments: + parameters: + - name: functions + value: producer.py + - name: consumer + template: MLops + dependencies: + - producer + arguments: + parameters: + - name: functions + value: consumer.py +``` +Suppose only the 'consumer' task need to use vineyard volume, +you can inject the vineyard volume into the workflow manifest +with the following command: +$ vineyardctl inject argo-workflow -f workflow.yaml \ + --templates="MLops" \ + --vineyard-cluster="vineyard-system/vineyardd-sample" \ + --mount-path="/vineyard/data" \ + --dag="dag" \ + --tasks="consumer" + +Then the injected manifest will be as follows: + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: mlops- +spec: + entrypoint: dag + templates: + - container: + args: + - -m + - '{{inputs.parameters.functions}}' + command: + - python + image: mlops-benchmark:latest + imagePullPolicy: IfNotPresent + ############# Injected ############ + volumeMounts: + - name: vineyard-objects + mountPath: /vineyard/data + ################################### + inputs: + parameters: + - name: functions + ############# Injected ############ + - name: vineyard-objects-name + ################################### + name: MLops + ######################### Injected ######################## + volumes: + - name: vineyard-objects + persistentVolumeClaim: + claimName: '{{inputs.parameters.vineyard-objects-name}}' + ########################################################### + - dag: + tasks: + - arguments: + parameters: + - name: functions + value: producer.py + ################################# Injected ################################# + - name: vineyard-objects-name + value: '{{tasks.vineyard-objects.outputs.parameters.vineyard-objects-name}}' + ############################################################################ + dependencies: + ##### Injected ##### + - vineyard-objects + #################### + name: producer + template: MLops + - arguments: + parameters: + - name: functions + value: consumer.py + ################################# Injected ################################# + - name: vineyard-objects-name + value: '{{tasks.vineyard-objects.outputs.parameters.vineyard-objects-name}}' + ############################################################################ + dependencies: + - producer + ##### Injected ##### + - vineyard-objects + #################### + name: consumer + template: MLops + ########### Injected ########### + - name: vineyard-objects + template: vineyard-objects + ################################ + name: dag + ################################# Injected ################################# + - name: vineyard-objects + outputs: + parameters: + - name: vineyard-objects-name + valueFrom: + jsonPath: '{.metadata.name}' + resource: + action: create + manifest: |- + apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: '{{workflow.name}}-vineyard-objects-pvc' + spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Mi + storageClassName: vineyard-system.vineyardd-sample.csi + setOwnerReference: true + ############################################################################ +``` + +``` +vineyardctl inject argo-workflow [flags] +``` + +**SEE ALSO** + +* [vineyardctl inject](#vineyardctl-inject) - Inject the vineyard sidecar container into a workload + +### Examples + +```shell + # Inject the vineyard volumes into the argo workflow + $ vineyardctl inject argo-workflow -f workflow.yaml \ + --templates="preprocess-data,train-data" \ + --vineyard-cluster="vineyard-system/vineyardd-sample" \ + --mount-path="/vineyard/data" \ + --dag="dag" \ + --tasks="preprocess-data,train-data" + + # Suppose you only have a single template in the workflow + # you could set only one template name in the --templates flag + $ vineyardctl inject argo-workflow -f workflow.yaml \ +--templates="mlops" \ + --vineyard-cluster="vineyard-system/vineyardd-sample" \ + --mount-path="/vineyard/data" \ + --dag="dag" \ + --tasks="preprocess-data,test-data" +``` + +### Options + +``` + --dag string The name of dag which will be injected with vineyard volumes + -f, --file string The file name of argo workflow + -h, --help help for argo-workflow + --mount-path string The mount path of vineyard volumes + --output-as-file Whether to output the injected workflow as a file, default is falseThe output file name will add a suffix '_with_vineyard' to the original file name + --tasks strings The set of task names under the dag + -t, --templates strings The name of workflow template which will be injected with vineyard volumes + --vineyard-cluster string The name of vineyard cluster which the argo workflow will use +``` + +## `vineyardctl ls` + +List vineyard objects, metadatas or blobs + +**SEE ALSO** + +* [vineyardctl](#vineyardctl) - vineyardctl is the command-line tool for interact with the Vineyard Operator. +* [vineyardctl ls blobs](#vineyardctl-ls-blobs) - List vineyard blobs +* [vineyardctl ls metadatas](#vineyardctl-ls-metadatas) - List vineyard metadatas +* [vineyardctl ls objects](#vineyardctl-ls-objects) - List vineyard objects + +### Examples + +```shell + # Connect the vineyardd server with IPC client + # List the vineyard objects no more than 10 + vineyardctl ls objects --limit 10 --ipc-socket /var/run/vineyard.sock + + # List the vineyard blobs no more than 10 + vineyardctl ls blobs --limit 10 --ipc-socket /var/run/vineyard.sock + + # List the vineyard objects with the specified pattern + vineyardctl ls objects --pattern "vineyard::Tensor<.*>" --regex --ipc-socket /var/run/vineyard.sock + + # Connect the vineyardd server with RPC client + # List the vineyard metadatas no more than 1000 + vineyardctl ls metadatas --rpc-socket 127.0.0.1:9600 --limit 1000 + + # Connect the vineyard deployment with PRC client + # List the vineyard objects no more than 1000 + vineyardctl ls objects --deployment-name vineyardd-sample -n vineyard-system +``` + +### Options + +``` + -h, --help help for ls +``` + +## `vineyardctl ls blobs` + +List vineyard blobs + +### Synopsis + +List vineyard blobs and only support IPC socket. +If you don't specify the ipc socket every time, you can set it as the +environment variable VINEYARD_IPC_SOCKET. + +``` +vineyardctl ls blobs [flags] +``` + +**SEE ALSO** + +* [vineyardctl ls](#vineyardctl-ls) - List vineyard objects, metadatas or blobs + +### Examples + +```shell + # List no more than 10 vineyard blobs + vineyardctl ls blobs --limit 10 --ipc-socket /var/run/vineyard.sock + + # List no more than 1000 vineyard blobs + vineyardctl ls blobs --ipc-socket /var/run/vineyard.sock --limit 1000 + + # List vineyard blobs with the name matching + vineyardctl ls blobs --pattern "vineyard::Tensor<.*>" --regex --ipc-socket /var/run/vineyard.sock + + # List vineyard blobs with the regex pattern + vineyardctl ls blobs --pattern "*DataFrame*" --ipc-socket /var/run/vineyard.sock + + # If you set the environment variable VINEYARD_IPC_SOCKET + # you can use the following command to list vineyard blobs + vineyardctl ls blobs --limit 1000 +``` + +### Options + +``` + --deployment-name string the name of vineyard deployment + -o, --format string the output format, support table or json, default is table (default "table") + --forward-port int the forward port of vineyard deployment (default 9600) + -h, --help help for blobs + --ipc-socket string vineyard IPC socket path + -l, --limit int maximum number of objects to return (default 5) + --port int the port of vineyard deployment (default 9600) + --rpc-socket string vineyard RPC socket path +``` + +## `vineyardctl ls metadatas` + +List vineyard metadatas + +### Synopsis + +List vineyard metadatas and support IPC socket, +RPC socket and vineyard deployment. If you don't specify the ipc socket or rpc socket +every time, you can set it as the environment variable VINEYARD_IPC_SOCKET or +VINEYARD_RPC_SOCKET. + +``` +vineyardctl ls metadatas [flags] +``` + +**SEE ALSO** + +* [vineyardctl ls](#vineyardctl-ls) - List vineyard objects, metadatas or blobs + +### Examples + +```shell + # List no more than 10 vineyard metadatas + vineyardctl ls metadatas --limit 10 --ipc-socket /var/run/vineyard.sock + + # List no more than 1000 vineyard metadatas + vineyardctl ls metadatas --rpc-socket 127.0.0.1:9600 --limit 1000 + + # List vineyard metadatas with the name matching the regex pattern + vineyardctl ls metadatas --pattern "vineyard::Blob" --ipc-socket /var/run/vineyard.sock + + # List vineyard metadatas of the vineyard deployment + vineyardctl ls metadatas --deployment-name vineyardd-sample -n vineyard-system --limit 1000 + + # List vineyard metadatas sorted by the instance id + vineyardctl ls metadatas --sorted-key instance_id --limit 1000 --ipc-socket /var/run/vineyard.sock + + # List vineyard metadatas sorted by the type and print the output as json format + vineyardctl ls metadatas --sorted-key type --limit 1000 --format json --ipc-socket /var/run/vineyard.sock +``` + +### Options + +``` + --deployment-name string the name of vineyard deployment + -o, --format string the output format, support table or json, default is table (default "table") + --forward-port int the forward port of vineyard deployment (default 9600) + -h, --help help for metadatas + --ipc-socket string vineyard IPC socket path + -l, --limit int maximum number of objects to return (default 5) + -p, --pattern string string that will be matched against the object’s typenames (default "*") + --port int the port of vineyard deployment (default 9600) + -r, --regex regex pattern to match the object’s typenames + --rpc-socket string vineyard RPC socket path + -k, --sorted-key string key to sort the objects, support: + - id: object id, the default value. + - typename: object typename, e.g. tensor, dataframe, etc. + - type: object type, e.g. global, local, etc. + - instance_id: object instance id. (default "id") +``` + +## `vineyardctl ls objects` + +List vineyard objects + +### Synopsis + +List vineyard objects and support IPC socket, +RPC socket and vineyard deployment. If you don't specify the ipc socket or rpc socket +every time, you can set it as the environment variable VINEYARD_IPC_SOCKET or +VINEYARD_RPC_SOCKET. + +``` +vineyardctl ls objects [flags] +``` + +**SEE ALSO** + +* [vineyardctl ls](#vineyardctl-ls) - List vineyard objects, metadatas or blobs + +### Examples + +```shell + # List no more than 10 vineyard objects + vineyardctl ls objects --limit 10 --ipc-socket /var/run/vineyard.sock + + # List any vineyard objects and no more than 1000 objects + vineyardctl ls objects --pattern "*" --ipc-socket /var/run/vineyard.sock --limit 1000 + + # List vineyard objects with the name matching the regex pattern + vineyardctl ls objects --pattern "vineyard::Tensor<.*>" --regex --ipc-socket /var/run/vineyard.sock + + # List vineyard objects and output as json format + vineyardctl ls objects --format json --ipc-socket /var/run/vineyard.sock + + # List vineyard objects sorted by the typename + vineyardctl ls objects --sorted-key typename --limit 1000 --ipc-socket /var/run/vineyard.sock +``` + +### Options + +``` + --deployment-name string the name of vineyard deployment + -o, --format string the output format, support table or json, default is table (default "table") + --forward-port int the forward port of vineyard deployment (default 9600) + -h, --help help for objects + --ipc-socket string vineyard IPC socket path + -l, --limit int maximum number of objects to return (default 5) + -p, --pattern string string that will be matched against the object’s typenames (default "*") + --port int the port of vineyard deployment (default 9600) + -r, --regex regex pattern to match the object’s typenames + --rpc-socket string vineyard RPC socket path + -k, --sorted-key string key to sort the objects, support: + - id: object id, the default value. + - typename: object typename, e.g. tensor, dataframe, etc. + - type: object type, e.g. global, local, etc. + - instance_id: object instance id. (default "id") +``` + +## `vineyardctl manager` + +Start the manager of vineyard operator + +``` +vineyardctl manager [flags] +``` + +**SEE ALSO** + +* [vineyardctl](#vineyardctl) - vineyardctl is the command-line tool for interact with the Vineyard Operator. + +### Examples + +```shell + # start the manager of vineyard operator with default configuration + # (Enable the controller, webhooks and scheduler) + vineyardctl manager + + # start the manager of vineyard operator without webhooks + vineyardctl manager --enable-webhook=false + + # start the manager of vineyard operator without scheduler + vineyardctl manager --enable-scheduler=false + + # only start the controller + vineyardctl manager --enable-webhook=false --enable-scheduler=false +``` + +### Options + +``` + --enable-scheduler Enable scheduler for controller manager. (default true) + --enable-webhook Enable webhook for controller manager. (default true) + --health-probe-bind-address string The address the probe endpoint binds to. (default ":8081") + -h, --help help for manager + --leader-elect Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. + --metrics-bind-address string The address the metric endpoint binds to. (default "127.0.0.1:8080") + --scheduler-config-file string The location of scheduler plugin's configuration file. (default "/etc/kubernetes/scheduler.yaml") + --webhook-cert-dir string The directory to store the generated certificates. (default "/etc/webhook/certs") +``` + +## `vineyardctl put` + +Put basic data type into vineyard + +### Synopsis + +Put basic data type into vineyard and only support IPC socket. +It receives the flag --value as string and will print object id if succeed. +If you don't specify the ipc socket every time, you can set it as the +environment variable VINEYARD_IPC_SOCKET. + +``` +vineyardctl put [flags] +``` + +**SEE ALSO** + +* [vineyardctl](#vineyardctl) - vineyardctl is the command-line tool for interact with the Vineyard Operator. + +### Examples + +```shell + # put value into vineyard with the given ipc socket + vineyardctl put --value 12345 --ipc-socket /var/run/vineyard.sock + vineyardctl put --value hello,world --ipc-socket /var/run/vineyard.sock + + # If you set the environment variable VINEYARD_IPC_SOCKET + # you can use the following command to get vineyard blob + vineyardctl put --value 12345 +``` + +### Options + +``` + --deployment-name string the name of vineyard deployment + -o, --format string the output format, support table or json, default is table (default "table") + --forward-port int the forward port of vineyard deployment (default 9600) + -h, --help help for put + --ipc-socket string vineyard IPC socket path + --port int the port of vineyard deployment (default 9600) + --rpc-socket string vineyard RPC socket path + --value string vineyard blob value +``` + +## `vineyardctl schedule` + +Schedule a workload or a workflow to existing vineyard cluster. + +**SEE ALSO** + +* [vineyardctl](#vineyardctl) - vineyardctl is the command-line tool for interact with the Vineyard Operator. +* [vineyardctl schedule workflow](#vineyardctl-schedule-workflow) - Schedule a workflow based on the vineyard cluster +* [vineyardctl schedule workload](#vineyardctl-schedule-workload) - Schedule the workload to a vineyard cluster + +### Examples + +```shell + # Schedule a workload to a vineyard cluster + # it will add PodAffinity to the workload + vineyardctl schedule workload --resource '{kubernetes workload json string}' + + # schedule a workflow to the vineyard cluster + # it will use the best-effort scheduling strategy + vineyardctl schedule workflow --file workflow.yaml +``` + +### Options + +``` + -h, --help help for schedule +``` + +## `vineyardctl schedule workflow` + +Schedule a workflow based on the vineyard cluster + +### Synopsis + +Schedule a workflow based on the vineyard cluster. +It will apply the workflow to kubernetes cluster and deploy the workload +of the workflow on the vineyard cluster with the best-fit strategy. + +``` +vineyardctl schedule workflow [flags] +``` + +**SEE ALSO** + +* [vineyardctl schedule](#vineyardctl-schedule) - Schedule a workload or a workflow to existing vineyard cluster. + +### Examples + +```shell + # schedule a workflow to the vineyard cluster with the best-fit strategy + vineyardctl schedule workflow --file workflow.yaml + + # schedule a workflow without CRD installed + # Notice, it only works for the workflow built by pods + vineyardctl schedule workflow --file pod-workflow.yaml --without-crd +``` + +### Options + +``` + -f, --file string the path of workflow file + -h, --help help for workflow + --without-crd whether the CRD(especially for GlobalObject and LocalObject) is installed +``` + +## `vineyardctl schedule workload` + +Schedule the workload to a vineyard cluster + +### Synopsis + +Schedule the workload to a vineyard cluster. +It will add the podAffinity to the workload so that the workload +will be scheduled to the vineyard cluster. Besides, if the workload +does not have the socket volumeMount and volume, it will add one. + +Assume you have the following workload yaml: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: python-client + # Notice, you must set the namespace here + namespace: vineyard-job +spec: + selector: + matchLabels: + app: python + template: + metadata: + labels: + app: python + spec: + containers: + - name: python + image: python:3.10 + command: ["python", "-c", "import time; time.sleep(100000)"] +``` + +Then you can run the following command to add the podAffinity and socket volume +to the workload yaml: + +$ vineyard schedule workload -f workload.yaml -o yaml + +After that, you will get the following workload yaml: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + name: python-client + namespace: vineyard-job +spec: + selector: + matchLabels: + app: python + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: python + spec: + affinity: + podAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app.kubernetes.io/instance + operator: In + values: + - vineyard-system-vineyardd-sample + namespaces: + - vineyard-system + topologyKey: kubernetes.io/hostname + + containers: + - command: + - python + - -c + - import time; time.sleep(100000) + env: + - name: VINEYARD_IPC_SOCKET + value: /var/run/vineyard.sock + image: python:3.10 + name: python + resources: {} + volumeMounts: + - mountPath: /var/run + name: vineyard-socket + volumes: + - hostPath: + path: /var/run/vineyard-kubernetes/vineyard-system/vineyardd-sample + name: vineyard-socket +``` + +``` +vineyardctl schedule workload [flags] +``` + +**SEE ALSO** + +* [vineyardctl schedule](#vineyardctl-schedule) - Schedule a workload or a workflow to existing vineyard cluster. + +### Examples + +```shell + # Add the podAffinity to the workload yaml + vineyardctl schedule workload -f workload.yaml \ + --vineyardd-name vineyardd-sample \ + --vineyardd-namespace vineyard-system + + # Add the podAffinity to the workload for the specific vineyard cluster + vineyardctl schedule workload --resource '{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "web-server" + }, + "spec": { + "selector": { + "matchLabels": { + "app": "web-store" + } + }, + "replicas": 3, + "template": { + "metadata": { + "labels": { + "app": "web-store" + } + }, + "spec": { + "affinity": { + "podAntiAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchExpressions": [ + { + "key": "app", + "operator": "In", + "values": [ + "web-store" + ] + } + ] + }, + "topologyKey": "kubernetes.io/hostname" + } + ] + }, + "podAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": [ + { + "labelSelector": { + "matchExpressions": [ + { + "key": "app", + "operator": "In", + "values": [ + "store" + ] + } + ] + }, + "topologyKey": "kubernetes.io/hostname" + } + ] + } + }, + "containers": [ + { + "name": "web-app", + "image": "nginx:1.16-alpine" + } + ] + } + } + } + }' \ + --vineyardd-name vineyardd-sample \ + --vineyardd-namespace vineyard-system +``` + +### Options + +``` + -f, --file string the file path of workload + -h, --help help for workload + -o, --output string the output format for vineyardctl schedule workload command (default "json") + --resource string the json string of kubernetes workload + --vineyardd-name string the namespace of vineyard cluster (default "vineyardd-sample") + --vineyardd-namespace string the namespace of vineyard cluster (default "vineyard-system") +``` diff --git a/_sources/notes/developers.rst.txt b/_sources/notes/developers.rst.txt new file mode 100644 index 0000000000..67c45449bd --- /dev/null +++ b/_sources/notes/developers.rst.txt @@ -0,0 +1,82 @@ +Getting Involved +---------------- + +.. toctree:: + :maxdepth: 1 + :caption: TOC + :hidden: + + developers/build-from-source.rst + developers/contributing.rst + developers/troubleshooting.rst + developers/roadmap.rst + +Vineyard is an open-source project that was accepted into the CNCF sandbox in April 2021. +It has been successfully developed and maintained by the open-source community. We are +committed to engaging community members to help us improve Vineyard. You will find our +community welcoming and responsive by joining our Github discussions or Slack channel: + +.. panels:: + :container: container-lg pb-4 + :column: col-lg-4 col-md-4 col-sm-4 col-xs-12 p-2 + :body: text-center + + .. link-button:: https://github.com/v6d-io/v6d/discussions + :type: url + :text: Github Discussions + :classes: btn-block stretched-link + + :fa:`github` + + --- + + .. link-button:: http://slack.cncf.io + :type: url + :text: Slack + :classes: btn-block stretched-link + + :fa:`slack` + +To modify the Vineyard source code, you will need to set up the development environment +and build the project from source. Follow the instructions below: + +.. panels:: + :container: container-lg pb-4 + :column: col-lg-6 col-md-6 col-sm-6 col-xs-12 p-2 + :body: text-center card-body-less-padding + + .. link-button:: developers/build-from-source + :type: ref + :text: Building from source + :classes: btn-block stretched-link + +If you encounter any issues during your journey with Vineyard, you may find solutions in: + +.. panels:: + :container: container-lg pb-4 + :column: col-lg-4 col-md-4 col-sm-4 col-xs-12 p-2 + :body: text-center card-body-less-padding + + .. link-button:: developers/troubleshooting + :type: ref + :text: Troubleshooting + :classes: btn-block stretched-link + + --- + + .. link-button:: https://github.com/v6d-io/v6d/issues + :type: url + :text: Github Issues + :classes: btn-block stretched-link + +We also have a public roadmap that outlines our future goals and highlights our ongoing efforts: + +.. panels:: + :container: container-lg pb-4 + :column: col-lg-4 col-md-4 col-sm-4 col-xs-12 p-2 + :body: text-center card-body-less-padding + + .. link-button:: developers/roadmap + :type: ref + :text: Our Roadmap + :classes: btn-block stretched-link diff --git a/_sources/notes/developers/build-from-source.rst.txt b/_sources/notes/developers/build-from-source.rst.txt new file mode 100644 index 0000000000..030515f9bc --- /dev/null +++ b/_sources/notes/developers/build-from-source.rst.txt @@ -0,0 +1,169 @@ +Building from source +==================== + +Install vineyard +---------------- + +Vineyard is distributed as a `python package `_ +and can be easily installed with :code:`pip`: + +.. code:: shell + + pip3 install vineyard + +Install etcd +------------ + +Vineyard is based on `etcd `_, please refer the `doc `_ to install it. + +Install from source +------------------- + +Vineyard is open source on Github: `https://github.com/v6d-io/v6d `_. +You can obtain the source code using ``git``: + +.. code:: console + + git clone https://github.com/v6d-io/v6d + cd v6d + git submodule update --init + +Prepare dependencies +^^^^^^^^^^^^^^^^^^^^ + +Vineyard can be built and deployed on common Unix-like systems. Vineyard has been +fully tests with C++ compilers that supports C++ 14. + +Dependencies +~~~~~~~~~~~~ + +Vineyard requires the following software as dependencies to build and run: + ++ apache-arrow >= 3.0.0 ++ gflags ++ glog ++ boost ++ mpi, for the graph data structure module + +If you want to build the vineyard server, the following additional libraries are needed: + ++ protobuf ++ grpc + +And the following python packages are required: + ++ libclang + + Can be installed using pip + + .. code:: shell + + pip3 install libclang + +Install on Ubuntu (or Debian) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Vineyard has been fully tested on Ubuntu 20.04. The dependencies can be installed by + +.. code:: shell + + apt-get install -y ca-certificates \ + cmake \ + doxygen \ + libboost-all-dev \ + libcurl4-openssl-dev \ + libgflags-dev \ + libgoogle-glog-dev \ + libgrpc-dev \ + libgrpc++-dev \ + libmpich-dev \ + libprotobuf-dev \ + libssl-dev \ + libunwind-dev \ + libz-dev \ + protobuf-compiler-grpc \ + python3-pip \ + wget + +Then install the apache-arrow (see also `https://arrow.apache.org/install `_): + +.. code:: shell + + wget https://apache.jfrog.io/artifactory/arrow/$(lsb_release --id --short | tr 'A-Z' 'a-z')/apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb \ + -O /tmp/apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb + apt install -y -V /tmp/apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb + apt update -y + apt install -y libarrow-dev + +Dependencies on MacOS +~~~~~~~~~~~~~~~~~~~~~ + +Vineyard has been tested on MacOS as well, the dependencies can be installed using :code:`brew`: + +.. code:: shell + + brew install apache-arrow boost gflags glog grpc protobuf llvm mpich openssl zlib autoconf + +Building vineyard +^^^^^^^^^^^^^^^^^ + +After the required dependencies are installed, you do an out-of-source build using **CMake**: + +.. tip:: + + We recommend to use the brew installed LLVM as the compiler for building vineyard on MacOS, + which can be accomplished by setting the environment variable :code:`CC` and :code:`CXX`: + + .. code:: + + export CC=$(brew --prefix llvm)/bin/clang + export CXX=$(brew --prefix llvm)/bin/clang++ + +.. code:: shell + + mkdir build + cd build + cmake .. + make -j$(nproc) + sudo make install # optionally + +You will see vineyard server binary under the ``bin`` directory, and static or shared linked +libraries will be placed under the ``lib-shared`` folder. + +Building python wheels +^^^^^^^^^^^^^^^^^^^^^^ + +After building the vineyard library successfully, you can package an install wheel distribution by + +.. code:: shell + + python3 setup.py bdist_wheel + + +Building the documentation +-------------------------- + +Vineyard documentation is organized and generated by sphinx. There are other packages that +help us build the documentation, which can be easily installed using ``pip``: + +.. code:: shell + + pip3 install -r requirements.txt -r requirements-dev.txt + +Once installed, you could go to the `docs/` directory and build the documentation by + +.. code:: shell + + cd docs/ # skip if you are already there + make html + +Building on various platforms +----------------------------- + +Vineyard is continuously tested on various platforms and you may find building and installation steps +from our CI: + +- `Ubuntu `_ +- `MacOS `_ +- `CentOS `_ +- `Arch Linux `_ diff --git a/_sources/notes/developers/contributing.rst.txt b/_sources/notes/developers/contributing.rst.txt new file mode 100644 index 0000000000..fe48147cb8 --- /dev/null +++ b/_sources/notes/developers/contributing.rst.txt @@ -0,0 +1,4 @@ +.. This file is just a placeholder to refer the top-level CONTRIBUTING.rst + to sphinx doc workspace. + +.. include:: ../../../CONTRIBUTING.rst diff --git a/_sources/notes/developers/faq.rst.txt b/_sources/notes/developers/faq.rst.txt new file mode 100644 index 0000000000..a3ea573285 --- /dev/null +++ b/_sources/notes/developers/faq.rst.txt @@ -0,0 +1,124 @@ +Frequently Asked Questions +========================== + +This *FAQ* page compiles questions frequently asked by our end users to provide +informative and concise answers. If the following sections do not address your +concerns, please feel free to `open an issue`_ or `post it to discussions`_. + +1. *What are the objects in vineyard?* + + A global object is composed of multiple local objects distributed across the cluster, + with each local object stored in a single vineyard daemon (ensuring that a local object + can always fit into the memory of a single machine). + + These local objects represent partitions of the global object (e.g., partitioned dataframes + within a large dataframe, graph fragments within a vast graph). Generally, a global object + serves as an abstraction for the input or output of a parallel-processing workload, while + a local object corresponds to the input or output of an individual worker within that workload. + +2. *Can multiple readers access the same data simultaneously in vineyard?* + + Absolutely. Vineyard stores objects as **immutable** entities, which are shared + among readers' processes through memory mapping. This ensures safe and concurrent + access to objects by multiple readers without any conflicts. + +3. *How can I launch a cluster with multiple vineyardd instances?* + + A vineyard daemon server represents a single vineyard instance within a vineyard cluster. To + initiate a vineyard cluster, simply start the ``vineyardd`` process on all the + machines within the cluster, ensuring that these vineyard instances can register with + the same ``etcd_endpoint``. The default value for ``etcd_endpoint`` is + ``http://127.0.0.1:2379``, and if the etcd servers are not already running on the cluster, + ``vineyard`` will automatically launch the ``etcd_endpoint``. + + For additional parameter settings, refer to the help documentation by running + ``python3 -m vineyard --help``. + +4. *Is Kubernetes a necessity for vineyard?* + + No, Kubernetes is not a necessity for vineyard. However, deploying vineyard on Kubernetes + allows users to benefit from the flexible resource management offered by cloud-native + deployments for their application workloads. Additionally, the scheduler plugin assists + in co-locating worker pods with the data for improved data-work alignment. + +5. *How does vineyard achieve IPC and memory sharing (i.e., zero-copy sharing) on Kubernetes?* + + Inter-process memory sharing can be challenging in Kubernetes, but it is achievable. When + deployed on Kubernetes, vineyard exposes its UNIX-domain socket as a :code:`PersistentVolume`. + This volume can be mounted into the job's pod, allowing the socket to be used for IPC + connections to the vineyard daemon. Memory sharing is accomplished by mounting a volume of + medium :code:`Memory` into both the vineyard daemon's pod and the job's pod. + +6. *How does vineyard's stream differ from similar systems, such as Kafka?* + + Vineyard's stream is an abstraction of a sequence of objects, where each object typically + represents a small portion of the entire object (e.g., a mini-batch of a tensor). This + abstraction is designed to support cross-engine pipelining between consecutive workers in + a data analytics pipeline (e.g., a dataframe engine generating training data while the + subsequent machine learning engine consumes the data and trains the model simultaneously). + + The primary distinction between vineyard's stream and traditional stream frameworks like + Kafka is that data in vineyard's stream is still abstracted as (high-level) objects and + can be consumed in a zero-copy manner, similar to normal objects in vineyard. In contrast, + Kafka is designed for stream processing applications and abstracts data as (low-level) + messages. Utilizing Kafka in the aforementioned scenario would still incur (de)serialization + and memory copy costs. + +7. *Does vineyard support accessing remote objects?* + + Yes, vineyard's RPC client can access the metadata of an object, regardless of whether + the object is local or remote. This capability enables users and internal operators to + examine essential information (e.g., chunk axis, size) about an object, assisting in + decision-making processes related to object management (e.g., determining the need for + repartitioning, planning the next workload). With this capability, the `vineyard client`_ + connects to the Vineyard daemon that stores the object's payloads, retrieves these payloads, + and assembles the objects locally. + +8. *How does migration work in vineyard? Is it automatically triggered?* + + Consider a scenario where workload *A* produces a global object *O*, and the subsequent + workload *B* consumes *O* as input. In a Kubernetes cluster with multiple hosts (e.g., + *h1*, *h2*, *h3*, *h4*), if *A* has two worker pods on *h1* and *h2*, the local objects + (i.e., *O1* and *O2*) of *O* are stored on *h1* and *h2*, respectively. + + If the two worker pods of *B* (i.e., *B1* and *B2*) are placed on *h1* and *h3*, *B1* + can access *O1* locally via memory mapping. However, *B2* (on *h3*) cannot access *O2* + since it resides on *h2*. In this situation, a utility program distributed with vineyard + in the :code:`initContainer` of *B2* triggers the migration of *O2* from *h2* to *h3*, + enabling pod *B2* to access *O2* locally. + + Although data migration incurs a cost, the scheduler plugin has been developed to + prioritize *h2* when launching *B2*, minimizing the need for migration whenever possible. + +9. *What's the minimal Kubernetes version requirement for vineyard operator?* + + At present, we only test the vineyard operator based on Kubernetes 1.24.0. + So we highly recommend using Kubernetes 1.24.0 or above. + +10. *Why the vineyard operator can't be deployed on Kubernetes?* + + If you use the helm to deploy the vineyard operator, you may find the vineyard operator + can't be deployed successfully after a long time. In this case, you should check whether + the command contains the flag `--wait`. If so, you should remove the flag `--wait` and + try to install the operator again. + +11. *How to connect to the vineyard cluster deployed by the vineyard operator?* + + There are two ways to connect to the vineyard cluster deployed by the vineyard operator: + + - `Through IPC`. Create a pod with the specific labels so that the pod can be scheduled + to the node where the vineyard cluster is deployed. + + - `Through RPC`. Connect to the vineyard cluster through the RPC service exposed by the + vineyard operator. You could refer to the `guide`_ for more details. + +12. *Is there a way to install the vineyard cluster on Kubernetes quickly?* + + To reduce the complexity of the installation, we provide a `command line tool`_ + to install the vineyard cluster on Kubernetes quickly. + +.. _open an issue: https://github.com/v6d-io/v6d/issues/new +.. _post it to discussions: https://github.com/v6d-io/v6d/discussions/new +.. _guide: ../../tutorials/kubernetes/using-vineyard-operator.rst +.. _command line tool: ../../notes/cloud-native/vineyardctl.md +.. _vineyard client: ../../notes/references/python-api.html#vineyard.Client \ No newline at end of file diff --git a/_sources/notes/developers/roadmap.rst.txt b/_sources/notes/developers/roadmap.rst.txt new file mode 100644 index 0000000000..1d7f9b63a2 --- /dev/null +++ b/_sources/notes/developers/roadmap.rst.txt @@ -0,0 +1,126 @@ +Roadmap +======= + +Vineyard aims to serve as an open-source in-memory immutable data manager. We +cut a major release once a year, a minor release for about every two months, +and a patch release every one or two weeks. + +The roadmap for major vineyard releases are listed as follows: + +v0.8.0 +------ + +Vineyard *v0.8.0* will deliver the first implementation of the following +important features and will be hopefully release in the later Aug, 2022: + +- Filesystem view of vineyard objects: vineyard objects can be accessed like + files on a filesystem in a high-performance fashion. Such a feature would + greatly ease the integration of computing processes with vineyard. +- Copy-on-realloc and data lineage: the mutation support would be extended + from blobs to general objects with a carefully concurrency-control design. +- Transparent object spilling: objects in vineyard can be spilled to disk + when they are too large to fit in memory. +- Sharing GPU memory between processes of different compute engines: we are + working on shared memory on devices to enable boarder applications that + can benefit from the shared vineyard store, especially for deep learning + frameworks and GNN frameworks. + +v0.7.0 +------ + +Vineyard *v0.7.0* will be released in later July, 2022. Vineyard v0.7.0 will +introduces the following experimental features to ease the integration of +various kinds of workloads with Vineyard: + +- Limited mutation support on blobs: starts from vineyard *v0.7.0*, unsealed + blobs can be get by other clients with an :code:`unsafe` flag to ease the + integration of some online storage engines. +- Limited support for remote data accessing using the RPC client: vineyard + *v0.7.0* will bring the feature about creating and accessing remote blobs + using the RPC client. It would be greatly helpful for some specific deployment + and the cost of remote data sourcing to vineyard is tolerable. + +v0.6.0 +------ + +We plan to release the *v0.6.0* version before tne end of June, 2022. The *v0.6.0* +release will include the following enhancement: + +- Better compatibility on various platforms (e.g., CentOS and ArchLinux), and process + platform-specific features like `LD_LIBRARY_PATH` and `libunwind` dependency + carefully. +- Ensure the backwards compatibility with various third-party integrations, e.g., + apache-airflow. +- Vineyard v0.6.0 will be available from `homebrew `_. + +v0.5.0 +------ + +We plan to release the first preliminary version for the Rust SDK and Go SDK +in vineyard *v0.5.0*, that is expected to be delivered in later May, 2022. + +In vineyard *v0.5.0*, we will investigate the opportunity about code generation +based on the metadata of vineyard objects, i.e., we could generate the data +structure definition based on the structure of metadata in runtime (for Python) +and in compile time (even maybe in runtime) for C++ and Rust. + +The integration with Kubernetes (especially the CSI part) will be another key +improvement for *v0.5.0*. + +Further details about release for *v0.5.0* will be added later. + +v0.4.0 +------ + +The release of vineyard *v0.4.0*, will be hopefully released before April, 2022, will +be a follow-up bugfix releases after *v0.3.0*. The version *v0.4.0* makes the +kubernetes related components better. + ++ Improve the robustness of the scheduler plugin. ++ Refine the definition of CRDs. ++ Distribute the vineyard operator to artifact hub as a chart, to make it available for more users. + +v0.3.0 +------ + +We plan to release *v0.3.0* by the end of 2021. vineyard *v0.3.0* will be the first major +stable releases with fully kubernetes support, which will include: + ++ A stable CRD definition for ``LocalObject`` and ``GlobalObject`` to represents vineyard objects + as kubernetes resources. ++ A full-features scheduler plugin for kubernetes, as well as a custom controller that manages + objects (custom resources) in vineyard cluster. ++ A refined version of Helm integration. ++ Application-aware far memory will be included in v0.3.0 as an experimental feature. + +v0.2.0 +------ + +Vineyard *v0.2.0* will address the issue about Python ecosystem compatibility, I/O, and +the kubernetes integration. Vineyard v0.2.0 will take about half of a year with several bugfix +release to testing the design and APIs to reach a stable stable state. + ++ Vineyard *v0.2.0* will support any *filesystem-spec*-compatible data source/sink as well as file + format. ++ Vineyard *v0.2.0* will support Python ecosystem (especially numpy and pandas) better. ++ Vineyard *v0.2.0* will include basic Helm integration for deploying on Kubernetes as a ``DaemonSet``. ++ A prototype of scheduler plugin to do data locality scheduling will be included into vineyard v0.2.0 + to demonstrates the capability about co-scheduling job and data in kubernetes brought by vineyard. ++ Match the criterion of CNCF sandbox project. + +v0.1.0 +------ + +Vineyard *v0.1.0* is the first release after open source. This version includes: + ++ Complete functionality for both server and client. ++ Complete Python SDK. ++ User-friendly package distribution on pypi (for python SDK) and on dockerhub (for vineyardd server). + +Release Notes +------------- + +For more details about what changes happened for every version, please refer to +our `releases notes`_ as well. + +.. _releases notes: https://github.com/v6d-io/v6d/releases diff --git a/_sources/notes/developers/troubleshooting.rst.txt b/_sources/notes/developers/troubleshooting.rst.txt new file mode 100644 index 0000000000..68abee637d --- /dev/null +++ b/_sources/notes/developers/troubleshooting.rst.txt @@ -0,0 +1,49 @@ +Troubleshooting +=============== + +This page provides guidance for addressing common issues that may arise when +working with Vineyard. + +.. Installation Errors +.. ------------------- + +Vineyard Fails to Start +----------------------- + +1. Improper Etcd Configuration + + If you encounter the following error when sending requests to Vineyard: + + .. code:: + + Etcd error: etcdserver: too many operations in txn request, error code: 3 + + This indicates that your Etcd configuration is not set up correctly and does not support + more than 128 operations within a single transaction. To resolve this issue, check your Etcd + startup parameters and increase the :code:`--max-txn-ops` value, for example, to :code:`102400`. + +2. bind: Permission Denied Error When Launching vineyardd + + The Vineyard server uses a UNIX-domain socket for IPC connections and memory sharing with clients. + By default, the UNIX-domain socket is located at :code:`/var/run/vineyard.sock`, which typically + requires root permission. + + To launch vineyardd, you can either: + + + Run the :code:`vineyardd` command with :code:`sudo`, + + Or, specify a different location for the UNIX-domain socket that does not require root permission + using the :code:`--socket` command line argument, e.g., + .. code:: bash + + python3 -m vineyard --socket=/tmp/vineyard.sock + +Vineyard Issues on Kubernetes +----------------------------- + +1. Etcd Pod Resource Limitations in Kubernetes Deployment + + We have observed that etcd performance may degrade when a Vineyard client persists a large + object, particularly in Kubernetes deployments where the CPU cores of the etcd pod are limited by + cgroups. In such cases, users should increase the CPU resources allocated to the etcd pod. For + more information on etcd tuning, please refer to the `Hardware recommendations + `_ section in the etcd documentation. diff --git a/_sources/notes/getting-started.rst.txt b/_sources/notes/getting-started.rst.txt new file mode 100644 index 0000000000..a1b117337d --- /dev/null +++ b/_sources/notes/getting-started.rst.txt @@ -0,0 +1,236 @@ +Getting Started +=============== + +.. _getting-started: + +Installing vineyard +------------------- + +Vineyard is distributed as a `Python package`_ and can be effortlessly installed using :code:`pip`: + +.. code:: console + + $ pip3 install vineyard + +Launching vineyard server +------------------------- + +.. code:: console + + $ python3 -m vineyard + +A vineyard daemon server will be launched with default settings. By default, :code:`/var/run/vineyard.sock` +will be used by vineyardd to listen for incoming IPC connections. + +To stop the running vineyardd instance, simply press :code:`Ctrl-C` in the terminal. + +.. tip:: + + If you encounter errors like ``cannot launch vineyardd on '/var/run/vineyard.sock': + Permission denied,``, it means **you don't have the permission** to create a UNIX-domain + socket at :code:`/var/run/vineyard.sock`. You can either: + + - Run vineyard as root, using ``sudo``: + + .. code:: console + + $ sudo -E python3 -m vineyard + + - Or, change the socket path to a writable location with the ``--socket`` command + line option: + + .. code:: console + + $ python3 -m vineyard --socket /tmp/vineyard.sock + +Connecting to vineyard +---------------------- + +Once launched, you can call :code:`vineyard.connect` with the socket name to initiate a vineyard client +from Python: + +.. code:: python + + >>> import vineyard + >>> client = vineyard.connect('/var/run/vineyard.sock') + +Storing and Retrieving Python Objects +------------------------------------- + +Vineyard is designed as an in-memory object store and offers two high-level APIs :code:`put` and +:code:`get` for creating and accessing shared objects, enabling seamless interoperability with the Python +ecosystem. The former returns a :code:`vineyard.ObjectID` upon success, which can be used +to retrieve shared objects from vineyard using the latter. + +In the following example, we use :code:`client.put()` to build a vineyard object from the numpy +ndarray ``arr``, which returns the ``object_id`` - a unique identifier in vineyard representing +the object. Given the ``object_id``, we can obtain a shared-memory object from vineyard with the +:code:`client.get()` method. + +.. code:: python + + >>> import numpy as np + >>> + >>> object_id = client.put(np.random.rand(2, 4)) + >>> object_id + o0015c78883eddf1c + >>> + >>> shared_array = client.get(object_id) + >>> shared_array + ndarray([[0.39736989, 0.38047846, 0.01948815, 0.38332264], + [0.61671189, 0.48903213, 0.03875045, 0.5873005 ]]) + +.. note:: + + :code:`shared_array` does not allocate extra memory in the Python process; instead, it shares memory + with the vineyard server via `mmap`_ in a zero-copy process. + +The sharable objects can be complex and nested. Like numpy ndarray, the pandas dataframe ``df`` can +be seamlessly stored in vineyard and retrieved with the ``.put()`` and ``.get()`` methods as follows: + +.. code:: python + + >>> import pandas as pd + >>> + >>> df = pd.DataFrame({'u': [0, 0, 1, 2, 2, 3], + >>> 'v': [1, 2, 3, 3, 4, 4], + >>> 'weight': [1.5, 3.2, 4.7, 0.3, 0.8, 2.5]}) + >>> object_id = client.put(df) + >>> + >>> shared_dataframe = client.get(object_id) + >>> shared_dataframe + u v weight + 0 0 1 1.5 + 1 0 2 3.2 + 2 1 3 4.7 + 3 2 3 0.3 + 4 2 4 0.8 + 5 3 4 2.5 + +Under the hood, vineyard implements a builder/resolver mechanism to represent arbitrary +data structures as *vineyard objects* and resolve them back to native values in the corresponding +programming languages and computing systems. See also :ref:`divein-driver-label` for more information. + +Sharing objects between tasks +----------------------------- + +Vineyard is designed for sharing intermediate data between tasks. The following example +demonstrates how a dataframe can be passed between two **processes** using vineyard, namely +the producer and consumer in the example below: + +.. code:: python + + import multiprocessing as mp + import vineyard + + import numpy as np + import pandas as pd + + socket = '/var/run/vineyard.sock' + + def produce(name): + client = vineyard.connect(socket) + client.put(pd.DataFrame(np.random.randn(100, 4), columns=list('ABCD')), + persist=True, name=name) + + def consume(name): + client = vineyard.connect(socket) + print(client.get(name=name).sum()) + + if __name__ == '__main__': + name = 'dataset' + + producer = mp.Process(target=produce, args=(name,)) + producer.start() + consumer = mp.Process(target=consume, args=(name,)) + consumer.start() + + producer.join() + consumer.join() + +Running the code above, you should see the following output: + +.. code:: text + + A -4.529080 + B -2.969152 + C -7.067356 + D 4.003676 + dtype: float64 + +Next steps +---------- + +Beyond the core functionality of sharing objects between tasks, vineyard also provides: + +- Distributed objects and stream abstraction over immutable chunks; +- An IDL (:ref:`vcdl`) that helps integrate vineyard with other systems at minimal cost; +- A mechanism of pluggable drivers for various tasks that serve as the glue + between the core compute engine and the external world, e.g., data sources, data + sinks; +- Integration with Kubernetes for sharing between tasks in workflows deployed + on cloud-native infrastructures. + +.. panels:: + :header: text-center + :column: col-lg-12 p-2 + + .. link-button:: architecture + :type: ref + :text: Architecture + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Overview of vineyard. + +Learn more about vineyard's key concepts from the following user guides: + +.. panels:: + :header: text-center + :container: container-lg pb-4 + :column: col-lg-4 col-md-4 col-sm-4 col-xs-12 p-2 + :body: text-center + + .. link-button:: key-concepts/objects + :type: ref + :text: Vineyard Objects + :classes: btn-block stretched-link + + Explore the design of the object model in vineyard. + + --- + + .. link-button:: key-concepts/vcdl + :type: ref + :text: VCDL + :classes: btn-block stretched-link + + Discover how vineyard integrates with other computing systems. + + --- + + .. link-button:: key-concepts/io-drivers + :type: ref + :text: I/O Drivers + :classes: btn-block stretched-link + + Understand the design and implementation of pluggable routines for I/O, repartition, + migration, and more. + +Vineyard is a natural fit for cloud-native computing, where it can be deployed and +managed by the *vineyard operator*, providing data-aware scheduling for data analytical +workflows to achieve efficient data sharing on Kubernetes. More details about vineyard +on Kubernetes can be found here: + +.. panels:: + :header: text-center + :column: col-lg-12 p-2 + + .. link-button:: cloud-native/deploy-kubernetes + :type: ref + :text: Kubernetes + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Deploy vineyard on Kubernetes and accelerate your big-data workflows. + +.. _Python package: https://pypi.org/project/vineyard +.. _mmap: https://man7.org/linux/man-pages/man2/mmap.2.html diff --git a/_sources/notes/integration-bigdata.rst.txt b/_sources/notes/integration-bigdata.rst.txt new file mode 100644 index 0000000000..e9382f784a --- /dev/null +++ b/_sources/notes/integration-bigdata.rst.txt @@ -0,0 +1,34 @@ +Big-data on Vineyard +==================== + +.. toctree:: + :maxdepth: 1 + :caption: TOC + :hidden: + + integration/dask.rst + integration/ml.rst + +Vineyard serves as a powerful data-sharing engine, seamlessly integrating with +a variety of big-data computing platforms. This includes machine learning +frameworks and the distributed data processing engine, Dask. + +.. panels:: + :header: text-center + :column: col-lg-12 p-2 + + .. link-button:: integration/ml + :type: ref + :text: Machine Learning + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Executing machine learning workflows on top of vineyard. + + --- + + .. link-button:: integration/dask + :type: ref + :text: Dask + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Using vineyard as the data source / sink of dask computations. diff --git a/_sources/notes/integration-orchestration.rst.txt b/_sources/notes/integration-orchestration.rst.txt new file mode 100644 index 0000000000..9b354750ad --- /dev/null +++ b/_sources/notes/integration-orchestration.rst.txt @@ -0,0 +1,46 @@ +Workflow orchestration +====================== + +.. toctree:: + :maxdepth: 1 + :caption: TOC + :hidden: + + integration/airflow.rst + integration/kedro.md + +Vineyard seamlessly integrates with the workflow orchestration engines, e.g., +Apache Airflow and Kedro, enabling users to effortlessly incorporate Vineyard +into their workflows for enhanced performance. + +Moreover, the Airflow integration empowers users to work with large Python objects +featuring complex data types (e.g., :code:`pandas.DataFrame`) at minimal cost, while +eliminating the need for cumbersome :code:`pickle.dump/loads` operations. + +.. panels:: + :header: text-center + :column: col-lg-12 p-2 + + .. link-button:: integration/airflow + :type: ref + :text: Airflow + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Airflow uses vineyard as the XCom backend to efficiently handle complex data in Python. + +The Kedro integration enables users to easily share large data objects across +nodes in a pipeline and eliminates the high cost of (de)serialization and I/O +compared with alternatives like AWS S3 or Minio, without the need to modify +the pipeline code intrusively, and provides seamless user experience when scaling +pipelines to Kubernetes. + +.. panels:: + :header: text-center + :column: col-lg-12 p-2 + + .. link-button:: integration/kedro + :type: ref + :text: Kedro + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Kedro uses vineyard as a `DataSet` implementation for efficient intermediate data sharing. diff --git a/_sources/notes/integration/airflow.rst.txt b/_sources/notes/integration/airflow.rst.txt new file mode 100644 index 0000000000..11d0163507 --- /dev/null +++ b/_sources/notes/integration/airflow.rst.txt @@ -0,0 +1,227 @@ +Airflow on Vineyard +=================== + +Big data analytical pipelines often involve various types of workloads, each +requiring a dedicated computing system to complete the task. Intermediate +data flows between tasks in the pipeline, and the additional cost of transferring data +accounts for a significant portion of the end-to-end performance in real-world deployments, +making optimization a challenging task. + +Integrating Vineyard with Airflow presents opportunities to alleviate this problem. + +Introducing Airflow +------------------- + +Airflow is a platform that enables users to programmatically author, schedule, and +monitor workflows. Users organize tasks in a Directed Acyclic Graph (DAG), and the +Airflow scheduler executes the tasks on workflows while adhering to the specified +dependencies. + +Consider the following ETL workflow as an example [1]_, + +.. code:: python + + @dag(schedule_interval=None, start_date=days_ago(2), tags=['example']) + def tutorial_taskflow_api_etl(): + @task() + def extract(): + data_string = '{"1001": 301.27, "1002": 433.21, "1003": 502.22}' + + order_data_dict = json.loads(data_string) + return order_data_dict + + @task(multiple_outputs=True) + def transform(order_data_dict: dict): + return {"total_order_value": total_order_value} + + @task() + def load(total_order_value: float): + print(f"Total order value is: {total_order_value:.2f}") + + order_data = extract() + order_summary = transform(order_data) + + + tutorial_etl_dag = tutorial_taskflow_api_etl() + +It forms the following DAG, including three individual tasks as the nodes, and +runs the tasks sequentially based on their data This forms a DAG, including +three individual tasks as nodes, and edges between nodes that describe the +data dependency relations. The Airflow scheduler runs the tasks sequentially +based on their data dependencies.dependencies. Airflow ETL Workflow + +Airflow on Vineyard +------------------- + +The Rationale for Airflow on Vineyard +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Airflow excels at defining and orchestrating complex workflows. However, managing +data flow within the pipeline remains a challenge. Airflow relies on database +backends such as SQLite, MySQL, and PostgreSQL to store intermediate data between +tasks. In real-world scenarios, large-scale data, such as large tensors, dataframes, +and distributed graphs, cannot fit into these databases. As a result, external +storage systems like HDFS and S3 are used to store intermediate data, with only +an identifier stored in the database. + +Utilizing external storage systems to share intermediate data among tasks in big +data analytical pipelines incurs performance costs due to data copying, +serialization/deserialization, and network data transfer. + +Vineyard is designed to efficiently share intermediate in-memory data for big data +analytical pipelines, making it a natural fit for workloads on Airflow. + +How Vineyard Enhances Airflow +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Airflow allows users to register an external **XCom** backend, which is precisely +what Vineyard is designed for. + +Vineyard serves as an *XCom* backend for Airflow workers, enabling the transfer of +large-scale data objects between tasks without relying on Airflow's database backend +or external storage systems like HDFS. The Vineyard XCom backend also handles object +migration when the required inputs are not located where the task is scheduled to +execute. + +Vineyard's XCom backend achieves its functionality by injecting hooks into the +processes of saving values to the backend and fetching values from the backend, +as described below: + +.. code:: python + + class VineyardXCom(BaseXCom): + + @staticmethod + def serialize_value(value: Any): + """ Store the value to vineyard server, and serialized the result + Object ID to save it into the backend database later. + """ + + @staticmethod + def deserialize_value(result: "XCom") -> Any: + """ Obtain the Object ID after deserialization, and fetching the + underlying value from vineyard. + + This value is resolved from vineyard objects in a zero-copy + fashion. + """ + + +Addressing Distributed Deployment Challenges +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Airflow supports parallel task execution across multiple workers to efficiently +process complex workflows. In a distributed deployment (using the `CeleryExecutor`), +tasks sharing intermediate data might be scheduled on different workers, necessitating +remote data access. + +Vineyard seamlessly handles object migration for various data types. In the XCom backend, +when the IPC client encounters remote objects, it triggers a migration action to move +the objects to the local worker, ensuring input data is readily available before task +execution. + +This transparent object migration simplifies complex data operations and movement, +allowing data scientists to focus on computational logic when developing big data +applications on Airflow. + +Running Vineyard + Airflow +-------------------------- + +Users can try Airflow provider for Vineyard by the following steps: + +1. Install required packages: + + .. code:: bash + + pip3 install airflow-provider-vineyard + +2. Configure Vineyard locally + + The vineyard server can be easier launched locally with the following command: + + .. code:: bash + + python -m vineyard --socket=/tmp/vineyard.sock + + See also our documentation about `launching vineyard`_. + +3. Configure Airflow to use the vineyard XCom backend by specifying the environment + variable + + .. code:: bash + + export AIRFLOW__CORE__XCOM_BACKEND=vineyard.contrib.airflow.xcom.VineyardXCom + + and configure the location of UNIX-domain IPC socket for vineyard client by + + .. code:: bash + + export AIRFLOW__VINEYARD__IPC_SOCKET=/tmp/vineyard.sock + + or + + .. code:: bash + + export VINEYARD_IPC_SOCKET=/tmp/vineyard.sock + +4. Launching your airflow scheduler and workers, and run the following DAG as example, + + .. code:: python + + import numpy as np + import pandas as pd + + from airflow.decorators import dag, task + from airflow.utils.dates import days_ago + + default_args = { + 'owner': 'airflow', + } + + @dag(default_args=default_args, schedule_interval=None, start_date=days_ago(2), tags=['example']) + def taskflow_etl_pandas(): + @task() + def extract(): + order_data_dict = pd.DataFrame({ + 'a': np.random.rand(100000), + 'b': np.random.rand(100000), + }) + return order_data_dict + + @task(multiple_outputs=True) + def transform(order_data_dict: dict): + return {"total_order_value": order_data_dict["a"].sum()} + + @task() + def load(total_order_value: float): + print(f"Total order value is: {total_order_value:.2f}") + + order_data = extract() + order_summary = transform(order_data) + load(order_summary["total_order_value"]) + + taskflow_etl_pandas_dag = taskflow_etl_pandas() + +In the example above, the `extract` and `transform` tasks share a `pandas.DataFrame` as +intermediate data. This presents a challenge, as the DataFrame cannot be pickled, and when +dealing with large data, it cannot fit into the backend databases of Airflow. + +This example is adapted from the Airflow documentation. For more information, refer to the +`Tutorial on the Taskflow API`_. + +Further Ahead +------------- + +The Airflow provider for Vineyard, currently in its experimental stage, demonstrates +significant potential for efficiently and flexibly sharing large-scale intermediate data +in big data analytical workflows within Airflow. + +The Airflow community is actively working to enhance support for modern big data and AI +applications. We believe that the integration of Vineyard, Airflow, and other cloud-native +infrastructures can provide a more effective and efficient solution for data scientists. + + +.. [1] See: https://airflow.apache.org/docs/apache-airflow/stable/tutorial_taskflow_api.html + +.. _launching vineyard: https://v6d.io/notes/getting-started.html#starting-vineyard-server +.. _Tutorial on the Taskflow API: https://airflow.apache.org/docs/apache-airflow/stable/tutorial_taskflow_api.html diff --git a/_sources/notes/integration/dask.rst.txt b/_sources/notes/integration/dask.rst.txt new file mode 100644 index 0000000000..628d1906a8 --- /dev/null +++ b/_sources/notes/integration/dask.rst.txt @@ -0,0 +1,150 @@ +Dask on Vineyard +================ + +The integration with Dask enables dask.array and dask.dataframe to be seamlessly persisted in +and retrieved from Vineyard. In the following sections, we demonstrate how Vineyard simplifies +the implementation of an example that utilizes Dask for data preprocessing and TensorFlow for +distributed learning, as previously showcased in the blog_. + +The Deployment +-------------- + +.. image:: ../../images/dask-tf.jpg + :alt: Dask Tensorflow Workflow + +As illustrated in the figure above, we employ two machines for the distributed tasks for +demonstration purposes. The Vineyard daemon processes are launched on both machines, along +with the Dask workers. The Dask scheduler is initiated on the first machine, where we also +run the Dask preprocessing program in the first step, as the Dask scheduler manages the +distribution of computation tasks among its workers. + +In the second step, we execute the training program on both machines with different **TF_CONFIG** +settings. For details on configuring the setup, please refer to the `documentation`_. + +Preprocessing in Dask +--------------------- + +In this step, we load the mnist data and duplicate it to simulate the parallel processing as same as the blog_. + +.. code:: python + + from vineyard.core.builder import builder_context + from vineyard.contrib.dask.dask import dask_context + + def dask_preprocess(dask_scheduler): + def get_mnist(): + (x_train, y_train), _ = tf.keras.datasets.mnist.load_data() + # The `x` arrays are in uint8 and have values in the [0, 255] range. + # You need to convert them to float64 with values in the [0, 1] range. + x_train = x_train / np.float64(255) + y_train = y_train.astype(np.int64) + return pd.DataFrame({'x': list(x_train), 'y': y_train}) + + with dask_context(): + datasets = [delayed(get_mnist)() for i in range(20)] + dfs = [dd.from_delayed(ds) for ds in datasets] + gdf = dd.concat(dfs) + gdf_id = vineyard.connect().put(gdf, dask_scheduler=dask_scheduler) + + return gdf_id + +Here the returned **gdf_id** is the ObjectID of a **vineyard::GlobalDataFrame** +which consists of 20 partitions (10 partitions on each machine). + +Training in Tensorflow +---------------------- + +In this step, we use the preprocessed data **gdf_id** to train a model distributedly +in keras of Tensorflow. + +.. code:: python + + from vineyard.contrib.ml.tensorflow import register_tf_types + from vineyard.core.resolver import resolver_context + + def mnist_dataset(gdf_id, batch_size): + with resolver_context() as resolver: + # register the resolver for tensorflow Dataset to the resolver_context + register_tf_types(None, resolver) + train_datasets = vineyard.connect().get(gdf_id, data='x', label='y') + train_datasets = train_datasets.repeat().batch(batch_size) + + options = tf.data.Options() + options.experimental_distribute.auto_shard_policy = tf.data.experimental.AutoShardPolicy.OFF + train_datasets_no_auto_shard = train_datasets.with_options(options) + return train_datasets_no_auto_shard + + def build_and_compile_cnn_model(): + model = tf.keras.Sequential([ + tf.keras.layers.InputLayer(input_shape=(28, 28)), + tf.keras.layers.Reshape(target_shape=(28, 28, 1)), + tf.keras.layers.Conv2D(32, 3, activation='relu'), + tf.keras.layers.Flatten(), + tf.keras.layers.Dense(128, activation='relu'), + tf.keras.layers.Dense(10) + ]) + model.compile( + loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), + optimizer=tf.keras.optimizers.SGD(learning_rate=0.001), + metrics=['accuracy']) + return model + + def train(gdf_id): + per_worker_batch_size = 64 + strategy = tf.distribute.MultiWorkerMirroredStrategy() + train_dataset = mnist_dataset(gdf_id, per_worker_batch_size) + + with strategy.scope(): + multi_worker_model = mnist.build_and_compile_cnn_model() + + multi_worker_model.fit(train_dataset, epochs=3, steps_per_epoch=70) + +To utilize the preprocessed data, we first register the resolvers capable of resolving a +**vineyard::GlobalDataFrame** distributed across multiple workers within the resolver_context. +Subsequently, we can directly obtain the **tf.data.Dataset** from Vineyard using the **get** +method. + +.. note:: + + It is essential to specify the column names for the data and label, as they were set in + the previous step. + +Transfer Learning +----------------- + +In this section, we demonstrate how the dask-vineyard integration can be effectively utilized +in transfer learning scenarios. Transfer learning is a technique where a pre-trained deep +learning model is used to compute features for downstream models. Storing these features in +memory is advantageous, as it eliminates the need to recompute features or incur significant +I/O costs by repeatedly reading them from disk. We will refer to the featurization_ example +and use the tf_flowers_ dataset as a **dask.array**. We will employ the pre-trained **ResNet50** +model to generate features and subsequently store them in Vineyard. The resulting global +tensor in Vineyard will consist of 8 partitions, each containing 400 data slots. + +.. code:: python + + def get_images(idx, num): + paths = list(Path("flower_photos").rglob("*.jpg"))[idx::num] + data = [] + for p in paths: + with open(p,'rb') as f: + img = Image.open(io.BytesIO(f.read())).resize([224, 224]) + arr = preprocess_input(img_to_array(img)) + data.append(arr) + return np.array(data) + + def featurize(v, block_id=None): + model = ResNet50(include_top=False) + preds = model.predict(np.stack(v)) + return preds.reshape(400, 100352) + + imgs = [da.from_delayed(delayed(get_images)(i,8), shape=(400, 244, 244, 3), dtype='float') for i in range(8)] + imgs = da.concatenate(imgs, axis=0) + res = imgs.map_blocks(featurize, chunks=(400,100352), drop_axis=[2,3], dtype=float) + global_tensor_id = vineyard.connect().put(res, dask_scheduler=dask_scheduler) + + +.. _documentation: https://www.tensorflow.org/tutorials/distribute/multi_worker_with_keras +.. _blog: http://matthewrocklin.com/blog/work/2017/02/11/dask-tensorflow +.. _featurization: https://docs.databricks.com/_static/notebooks/deep-learning/deep-learning-transfer-learning-keras.html +.. _tf_flowers: https://www.tensorflow.org/datasets/catalog/tf_flowers diff --git a/_sources/notes/integration/kedro.md.txt b/_sources/notes/integration/kedro.md.txt new file mode 100644 index 0000000000..c0cb65e339 --- /dev/null +++ b/_sources/notes/integration/kedro.md.txt @@ -0,0 +1,270 @@ +Kedro Vineyard Plugin +===================== + +The Kedro vineyard plugin contains components (e.g., `DataSet` and `Runner`) +to share intermediate data among nodes in Kedro pipelines using vineyard. + +Kedro on Vineyard +----------------- + +Vineyard works as the *DataSet* provider for kedro workers to allow transferring +large-scale data objects between tasks that cannot be efficiently serialized and +is not suitable for `pickle`, without involving external storage systems like +AWS S3 (or Minio as an alternative). The Kedro vineyard plugin handles object migration +as well when the required inputs are not located where the task is scheduled to execute. + +Requirements +------------ + +The following packages are needed to run Kedro on vineyard, + +- kedro >= 0.18 +- vineyard >= 0.14.5 + +Configuration +------------- + +1. Install required packages: + + pip3 install vineyard-kedro + +2. Configure Vineyard locally + + The vineyard server can be easier launched locally with the following command: + + python3 -m vineyard --socket=/tmp/vineyard.sock + + See also our documentation about [Launching Vineyard][1]. + +3. Configure the environment variable to tell Kedro vineyard plugin how to connect to the + vineyardd server: + + export VINEYARD_IPC_SOCKET=/tmp/vineyard.sock + +Usage +----- + +After installing the dependencies and preparing the vineyard server, you can execute the +Kedro workflows as usual and benefits from vineyard for intermediate data sharing. + +We take the [Iris example][2] as an example, + +```bash +$ kedro new --starter=pandas-iris +``` + +The nodes in this pipeline look like + +```python +def split_data( + data: pd.DataFrame, parameters: Dict[str, Any] +) -> Tuple[pd.DataFrame, pd.DataFrame, pd.Series, pd.Series]: + data_train = data.sample( + frac=parameters["train_fraction"], random_state=parameters["random_state"] + ) + data_test = data.drop(data_train.index) + + X_train = data_train.drop(columns=parameters["target_column"]) + X_test = data_test.drop(columns=parameters["target_column"]) + y_train = data_train[parameters["target_column"]] + y_test = data_test[parameters["target_column"]] + + return X_train, X_test, y_train, y_test + + +def make_predictions( + X_train: pd.DataFrame, X_test: pd.DataFrame, y_train: pd.Series +) -> pd.Series: + X_train_numpy = X_train.to_numpy() + X_test_numpy = X_test.to_numpy() + + squared_distances = np.sum( + (X_train_numpy[:, None, :] - X_test_numpy[None, :, :]) ** 2, axis=-1 + ) + nearest_neighbour = squared_distances.argmin(axis=0) + y_pred = y_train.iloc[nearest_neighbour] + y_pred.index = X_test.index + + return y_pred +``` + +You can see that the intermediate data between `split_data` and `make_predictions` is some pandas +dataframes and series. + +Try running the pipeline without vineyard, + +```bash +$ cd iris +$ kedro run +[05/25/23 11:38:56] INFO Kedro project iris session.py:355 +[05/25/23 11:38:57] INFO Loading data from 'example_iris_data' (CSVDataSet)... data_catalog.py:343 + INFO Loading data from 'parameters' (MemoryDataSet)... data_catalog.py:343 + INFO Running node: split: split_data([example_iris_data,parameters]) -> [X_train,X_test,y_train,y_test] node.py:329 + INFO Saving data to 'X_train' (MemoryDataSet)... data_catalog.py:382 + INFO Saving data to 'X_test' (MemoryDataSet)... data_catalog.py:382 + INFO Saving data to 'y_train' (MemoryDataSet)... data_catalog.py:382 + INFO Saving data to 'y_test' (MemoryDataSet)... data_catalog.py:382 + INFO Completed 1 out of 3 tasks sequential_runner.py:85 + INFO Loading data from 'X_train' (MemoryDataSet)... data_catalog.py:343 + INFO Loading data from 'X_test' (MemoryDataSet)... data_catalog.py:343 + INFO Loading data from 'y_train' (MemoryDataSet)... data_catalog.py:343 + INFO Running node: make_predictions: make_predictions([X_train,X_test,y_train]) -> [y_pred] node.py:329 +... +``` + +You can see that the intermediate data is shared with memory. When kedro is deploy to a cluster, e.g., +to [argo workflow][3], the `MemoryDataSet` is not applicable anymore and you will need to setup the +AWS S3 or Minio service and sharing those intermediate data as CSV files. + +```yaml +X_train: + type: pandas.CSVDataSet + filepath: s3://testing/data/02_intermediate/X_train.csv + credentials: minio + +X_test: + type: pandas.CSVDataSet + filepath: s3://testing/data/02_intermediate/X_test.csv + credentials: minio + +y_train: + type: pandas.CSVDataSet + filepath: s3://testing/data/02_intermediate/y_train.csv + credentials: minio +``` + +It might be inefficient for pickling pandas dataframes when data become larger. With the kedro +vineyard plugin, you can run the pipeline with vineyard as the intermediate data medium by + +```bash +$ kedro run --runner vineyard.contrib.kedro.runner.SequentialRunner +[05/25/23 11:45:34] INFO Kedro project iris session.py:355 + INFO Loading data from 'example_iris_data' (CSVDataSet)... data_catalog.py:343 + INFO Loading data from 'parameters' (MemoryDataSet)... data_catalog.py:343 + INFO Running node: split: split_data([example_iris_data,parameters]) -> [X_train,X_test,y_train,y_test] node.py:329 + INFO Saving data to 'X_train' (VineyardDataSet)... data_catalog.py:382 + INFO Saving data to 'X_test' (VineyardDataSet)... data_catalog.py:382 + INFO Saving data to 'y_train' (VineyardDataSet)... data_catalog.py:382 + INFO Saving data to 'y_test' (VineyardDataSet)... data_catalog.py:382 + INFO Loading data from 'X_train' (VineyardDataSet)... data_catalog.py:343 + INFO Loading data from 'X_test' (VineyardDataSet)... data_catalog.py:343 + INFO Loading data from 'y_train' (VineyardDataSet)... data_catalog.py:343 + INFO Running node: make_predictions: make_predictions([X_train,X_test,y_train]) -> [y_pred] node.py:329 +... +``` + +Without any modification to your pipeline code, you can see that the intermediate data is shared +with vineyard using the `VineyardDataSet` and no longer suffers from the overhead of (de)serialization +and the I/O cost between external AWS S3 or Minio services. + +Like `kedro catalog create`, the Kedro vineyard plugin provides a command-line interface to generate +the catalog configuration for given pipeline, which will rewrite the unspecified intermediate data +to `VineyardDataSet`, e.g., + +```bash +$ kedro vineyard catalog create -p __default__ +``` + +You will get + +```yaml +X_test: + ds_name: X_test + type: vineyard.contrib.kedro.io.dataset.VineyardDataSet +X_train: + ds_name: X_train + type: vineyard.contrib.kedro.io.dataset.VineyardDataSet +y_pred: + ds_name: y_pred + type: vineyard.contrib.kedro.io.dataset.VineyardDataSet +y_test: + ds_name: y_test + type: vineyard.contrib.kedro.io.dataset.VineyardDataSet +y_train: + ds_name: y_train + type: vineyard.contrib.kedro.io.dataset.VineyardDataSet +``` + +Deploy to Kubernetes +-------------------- + +When the pipeline scales to Kubernetes, the interaction with the Kedro vineyard plugin is +still simple and non-intrusive. The plugin provides tools to prepare the docker image and +generate Argo workflow specification file for the Kedro pipeline. Next, we'll demonstrate +how to deploy pipelines to Kubernetes while leverage Vineyard for efficient intermediate +sharing between tasks step-by-step. + +1. Prepare the vineyard cluster (see also [Deploy on Kubernetes][5]): + + ```bash + # export your kubeconfig path here + $ export KUBECONFIG=/path/to/your/kubeconfig + + # install the vineyard operator + $ go run k8s/cmd/main.go deploy vineyard-cluster --create-namespace + ``` + +2. Install the argo server: + + ```bash + # install the argo server + $ kubectl create namespace argo + $ kubectl apply -n argo -f https://github.com/argoproj/argo-workflows/releases/download/v3.4.8/install.yaml + ``` + +3. Generate the iris demo project from the official template: + + ```bash + $ kedro new --starter=pandas-iris + ``` + +4. Build the Docker image for this iris demo project: + + ```bash + # walk to the iris demo root directory + $ cd iris + $ kedro vineyard docker build + ``` + + A Docker image named `iris` will be built successfully. The docker image + need to be pushed to your image registry, or loaded to the kind/minikube cluster, to be + available in Kubernetes. + + ```bash + $ docker images | grep iris + iris latest 3c92da8241c6 About a minute ago 690MB + ``` + +5. Next, generate the Argo workflow YAML file from the iris demo project: + + ```bash + $ kedro vineyard argo generate -i iris + + # check the generated Argo workflow YAML file, you can see the Argo workflow YAML file named `iris.yaml` + # is generated successfully. + $ ls -l argo-iris.yml + -rw-rw-r-- 1 root root 3685 Jun 12 23:55 argo-iris.yml + ``` + +6. Finally, submit the Argo workflow to Kubernetes: + + ```bash + $ argo submit -n argo argo-iris.yml + ``` + + You can interact with the Argo workflow using the `argo` command-line tool, e.g., + + ```bash + $ argo list workflows -n argo + NAME STATUS AGE DURATION PRIORITY MESSAGE + iris-sg6qf Succeeded 18m 30s 0 + ``` + +We have prepared a benchmark to evaluate the performance gain brought by vineyard for data +sharing when data scales, for more details, please refer to [this report][4]. + +[1]: https://v6d.io/notes/getting-started.html#starting-vineyard-server +[2]: https://docs.kedro.org/en/stable/get_started/new_project.html +[3]: https://docs.kedro.org/en/stable/deployment/argo.html#how-to-run-your-kedro-pipeline-using-argo-workflows +[4]: https://v6d.io/tutorials/data-processing/accelerate-data-sharing-in-kedro.html +[5]: https://v6d.io/notes/cloud-native/deploy-kubernetes.html diff --git a/_sources/notes/integration/ml.rst.txt b/_sources/notes/integration/ml.rst.txt new file mode 100644 index 0000000000..16c3837439 --- /dev/null +++ b/_sources/notes/integration/ml.rst.txt @@ -0,0 +1,295 @@ +Machine Learning with Vineyard +============================== + + +**Vineyard-ML**: A Vineyard package that integrates Machine Learning Frameworks to Vineyard. + +TensorFlow +---------- + +Using Numpy Data +^^^^^^^^^^^^^^^^ + +.. code:: python + + >>> import tensorflow as tf + >>> from vineyard.contrib.ml import tensorflow + >>> dataset = tf.data.Dataset.from_tensor_slices((data, label)) + >>> data_id = vineyard_client.put(dataset) + >>> vin_data = vineyard_client.get(data_id) + +Vineyard supports the ``tf.data.Dataset``. The ``vin_data`` will be a shared-memory object +from the vineyard. + +Using Dataframe +^^^^^^^^^^^^^^^ + +.. code:: python + + >>> import pandas as pd + >>> df = pd.DataFrame({'a': [1, 2, 3, 4], 'b': [5, 6, 7, 8], 'target': [1.0, 2.0, 3.0, 4.0]}) + >>> label = df.pop('target') + >>> dataset = tf.data.Dataset.from_tensor_slices((dict(df), label)) + >>> data_id = vineyard_client.put(dataset) + >>> vin_data = vineyard_client.get(data_id) + +Wrap the dataframe with ``tf.data.Dataset``. This enables the use of feature columns as a +bridge to map from the columns in Pandas Dataframe to features in Dataset. The +dataset should return a dictionary of column names (from the dataframe) that maps +to column values. The dataset should only contain ``numerical data``. + +Using RecordBatch of Pyarrow +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. code:: python + + >>> import pyarrow as pa + >>> arrays = [pa.array([1, 2, 3, 4]), pa.array([3.0, 4.0, 5.0, 6.0]), pa.array([0, 1, 0, 1])] + >>> batch = pa.RecordBatch.from_arrays(arrays, ['f0', 'f1', 'label']) + >>> data_id = vineyard_client.put(batch) + >>> vin_data = vineyard_client.get(data_id) + +Vineyard supports direct integration of RecordBatch. The ``vin_data`` object will +be a TensorFlow Dataset, i.e. ``tf.data.Dataset``. Here the ``label`` row should be named as ``label``. + +Using Tables of Pyarrow +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + >>> arrays = [pa.array([1, 2, 3, 4]), pa.array([3.0, 4.0, 5.0, 6.0]), pa.array([0, 1, 0, 1])] + >>> batch = pa.RecordBatch.from_arrays(arrays, ['f0', 'f1', 'label']) + >>> batches = [batch]*3 + >>> table = pa.Table.from_batches(batches) + >>> data_id = vineyard_client.put(table) + >>> vin_data = vineyard_client.get(data_id) + +Vineyard supports direct integration of Tables as well. Here, the ``vin_data`` +object will be of type TensorFlow Dataset, i.e. ``tf.data.Dataset``. Here the ``label`` row +should be named as ``label``. + + +PyTorch +------- + +Using Numpy Data +^^^^^^^^^^^^^^^^ + +Vineyard supports ``Custom Datasets`` inherited from the PyTorch Dataset. + +.. code:: python + + >>> import torch + >>> from vineyard.contrib.ml import pytorch + >>> data_id = vineyard_client.put(dataset, typename='Tensor') + >>> vin_data = vineyard_client.get(data_id) + +The dataset object should be an object of the type CustomDataset class which is inherited +from ``torch.utils.data.Dataset`` class. Adding the typename as ``Tensor`` is important. +The ``vin_data`` will be of type ``torch.utils.data.TensorDataset``. + +Using Dataframe +^^^^^^^^^^^^^^^ + +.. code:: python + + >>> df = pd.DataFrame({'a': [1, 2, 3, 4], 'b': [5, 6, 7, 8], 'c': [1.0, 2.0, 3.0, 4.0]}) + >>> label = torch.from_numpy(df['c'].values.astype(np.float32)) + >>> data = torch.from_numpy(df.drop('c', axis=1).values.astype(np.float32)) + >>> dataset = torch.utils.data.TensorDataset(data, label) + >>> data_id = vineyard_client.put(dataset, typename='Dataframe', cols=['a', 'b', 'c'], label='c') + >>> vin_data = vineyard_client.get(data_id, label='c) + +While using the PyTorch form of the dataframe with vineyard, it is important to mention +the typename as ``Dataframe``, a list of column names in ``cols`` and the ``label`` +name in label tag. The ``vin_data`` will be of the form ``TensorDataset`` with +the label as mentioned with the label tag. If no value is passed to the label tag +vineyard will consider the default value which is the value of label passed in while +calling the ``put`` method + +Using RecordBatch of Pyarrow +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + >>> import pyarrow as pa + >>> arrays = [pa.array([1, 2, 3, 4]), pa.array([3.0, 4.0, 5.0, 6.0]), pa.array([0, 1, 0, 1])] + >>> batch = pa.RecordBatch.from_arrays(arrays, ['f0', 'f1', 'f2']) + >>> data_id = vineyard_client.put(batch) + >>> vin_data = vineyard_client.get(data_id, label='f2') + +The ``vin_data`` will be of the form ``TensorDataset`` with the label as mentioned +with the label tag. In this case it is important to mention the label tag. + +Using Tables of Pyarrow +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + >>> arrays = [pa.array([1, 2, 3, 4]), pa.array([3.0, 4.0, 5.0, 6.0]), pa.array([0, 1, 0, 1])] + >>> batch = pa.RecordBatch.from_arrays(arrays, ['f0', 'f1', 'f2']) + >>> batches = [batch]*3 + >>> table = pa.Table.from_batches(batches) + >>> data_id = vineyard_client.put(table) + >>> vin_data = vineyard_client.get(data_id, label='f2') + +The ``vin_data`` object will be of the form ``TensorDataset`` with the label as mentioned +with the label tag. In this case, it is important to mention the label tag. + +MxNet +----- + +Using Numpy Data +^^^^^^^^^^^^^^^^ + +Vineyard supports ``Array Datasets`` from the gluon.data of MxNet. + +.. code:: python + + >>> import mxnet as mx + >>> from vineyard.contrib.ml import mxnet + >>> dataset = mx.gluon.data.ArrayDataset((data, label)) + >>> data_id = vineyard_client.put(dataset, typename='Tensor') + >>> vin_data = vineyard_client.get(data_id) + +The dataset object should be an object of the type ArrayDataset from ``mxnet.gluon.data`` +class. Here, Adding the typename as ``Tensor`` is important. The ``vin_data`` will be +of type ``mxnet.gluon.data.ArrayDataset``. + +Using Dataframe +^^^^^^^^^^^^^^^ + +.. code:: python + + >>> df = pd.DataFrame({'a': [1, 2, 3, 4], 'b': [5, 6, 7, 8], 'c': [1.0, 2.0, 3.0, 4.0]}) + >>> label = df['c'].values.astype(np.float32) + >>> data = df.drop('c', axis=1).values.astype(np.float32) + >>> dataset = mx.gluon.data.ArrayDataset((data, label)) + >>> data_id = vineyard_client.put(dataset, typename='Dataframe', cols=['a', 'b', 'c'], label='c') + >>> vin_data = vineyard_client.get(data_id, label='c) + +While using the MxNet form of the dataframe with vineyard, it is important to mention +the typename as ``Dataframe``, a list of column names in ``cols`` and the ``label`` +name in label tag. The ``vin_data`` will be of the form ``ArrayDataset`` with +the label as mentioned with the label tag. If no value is passed to the label tag +vineyard will consider the default value which is the value of label passed in while +calling the ``put`` method + +Using RecordBatch of Pyarrow +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + >>> import pyarrow as pa + >>> arrays = [pa.array([1, 2, 3, 4]), pa.array([3.0, 4.0, 5.0, 6.0]), pa.array([0, 1, 0, 1])] + >>> batch = pa.RecordBatch.from_arrays(arrays, ['f0', 'f1', 'f2']) + >>> data_id = vineyard_client.put(batch) + >>> vin_data = vineyard_client.get(data_id, label='f2') + +The ``vin_data`` will be of the form ``ArrayDataset`` with the label as mentioned +with the label tag. In this case, it is important to mention the label tag. + +Using Tables of Pyarrow +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + >>> arrays = [pa.array([1, 2, 3, 4]), pa.array([3.0, 4.0, 5.0, 6.0]), pa.array([0, 1, 0, 1])] + >>> batch = pa.RecordBatch.from_arrays(arrays, ['f0', 'f1', 'f2']) + >>> batches = [batch]*3 + >>> table = pa.Table.from_batches(batches) + >>> data_id = vineyard_client.put(table) + >>> vin_data = vineyard_client.get(data_id, label='f2') + +The ``vin_data`` object will be of the form ``ArrayDataset`` with the label as mentioned +with the label tag. In this case, it is important to mention the label tag. + +XGBoost +------- + +Vineyard supports resolving ``XGBoost::DMatrix`` from various kinds of vineyard data types. + +From Vineyard::Tensor +^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + >>> arr = np.random.rand(4, 5) + >>> vin_tensor_id = vineyard_client.put(arr) + >>> dmatrix = vineyard_client.get(vin_tensor_id) + +The ``dmatrix`` will be a ``DMatrix`` instance with the same shape ``(4, 5)`` resolved from the ``Vineyard::Tensor`` +object with the id ``vin_tensor_id``. + +From Vineyard::DataFrame +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + >>> df = pd.DataFrame({'a': [1, 2, 3, 4], 'b': [5, 6, 7, 8], 'c': [1.0, 2.0, 3.0, 4.0]}) + >>> vin_df_id = vineyard_client.put(df) + >>> dmatrix = vineyard_client.get(vin_df_id, label='a') + +The ``dmatrix`` will be a ``DMatrix`` instance with shape of ``(4, 2)`` and ``feature_names`` of ``['b', 'c']``. +While the label of ``dmatrix`` is the values of column ``a``. + +Sometimes the dataframe is a complex data structure and only ``one`` column will be used as the ``features``. +We support this case by providing the ``data`` kwarg. + +.. code:: python + + >>> df = pd.DataFrame({'a': [1, 2, 3, 4], + >>> 'b': [[5, 1.0, 4], [6, 2.0, 3], [7, 3.0, 2], [8, 9.0, 1]]}) + >>> vin_df_id = vineyard_client.put(df) + >>> dmatrix = vineyard_client.get(vin_df_id, data='b', label='a') + +The ``dmatrix`` will have the shape of ``(4, 3)`` corresponding to the values of column ``b``. +While the label is the values of column ``a``. + +From Vineyard::RecordBatch +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + >>> import pyarrow as pa + >>> arrays = [pa.array([1, 2, 3, 4]), pa.array([3.0, 4.0, 5.0, 6.0]), pa.array([0, 1, 0, 1])] + >>> batch = pa.RecordBatch.from_arrays(arrays, ['f0', 'f1', 'target']) + >>> vin_rb_id = vineyard_client.put(batch) + >>> dmatrix = vineyard_client.get(vin_rb_id, label='target') + +The ``dmatrix`` will have the shape of ``(4, 2)`` and ``feature_names`` of ``['f0', 'f1']``. +While the label is the values of column ``target``. + +From Vineyard::Table +^^^^^^^^^^^^^^^^^^^^ + +.. code:: python + + >>> arrays = [pa.array([1, 2]), pa.array([0, 1]), pa.array([0.1, 0.2])] + >>> batch = pa.RecordBatch.from_arrays(arrays, ['f0', 'label', 'f2']) + >>> batches = [batch] * 3 + >>> table = pa.Table.from_batches(batches) + >>> vin_tab_id = vineyard_client.put(table) + >>> dmatrix = vineyard_client.get(vin_tab_id, label='label') + +The ``dmatrix`` will have the shape of ``(6, 2)`` and ``feature_names`` of ``['f0', 'f2']``. +While the label is the values of column ``label``. + +Nvidia-DALI +----------- + +Vineyard supports integration of ``Dali Pipelines``. + +.. code:: python + + >>> from nvidia.dali import pipeline_def + >>> pipeline = pipe(device_id=device_id, num_threads=num_threads, batch_size=batch_size) + >>> pipeline.build() + >>> pipe_out = pipeline.run() + >>> data_id = vineyard_client.put(pipe_out) + >>> vin_pipe = vineyard_client.get(data_id) + +In this case, the pipe is a ``pipeline_def`` function. The data received after executing pipe.run() can +be stored into vineyard. The Pipeline should only return two values, namely data and label. The return +type of the data and label values should be of type ``TensorList``. The ``vin_pipe`` object will be the +output of a simple in-built pipeline after executing the pipeline.build() and pipeline.run(). It will +simply return two values of type Pipeline. \ No newline at end of file diff --git a/_sources/notes/integration/ray.rst.txt b/_sources/notes/integration/ray.rst.txt new file mode 100644 index 0000000000..1ba5d32d40 --- /dev/null +++ b/_sources/notes/integration/ray.rst.txt @@ -0,0 +1,2 @@ +Ray on Vineyard +=============== diff --git a/_sources/notes/key-concepts.rst.txt b/_sources/notes/key-concepts.rst.txt new file mode 100644 index 0000000000..f16d5e3400 --- /dev/null +++ b/_sources/notes/key-concepts.rst.txt @@ -0,0 +1,77 @@ +Key Concepts +============ + +.. toctree:: + :maxdepth: 1 + :caption: TOC + :hidden: + + key-concepts/objects.rst + key-concepts/vcdl.rst + key-concepts/data-accessing.rst + key-concepts/streams.rst + key-concepts/io-drivers.rst + +The *User Guide* section offers an in-depth understanding of vineyard's design +and implementation. It covers detailed environment setup instructions, the +architecture, and the core features within the vineyard engine. + +*Additional information on vineyard's internals will be provided soon*. + +.. tip:: + + If you are new to vineyard, we recommend starting with the + `Getting Started `_ page for a smoother + introduction. + +Concepts +-------- + +.. panels:: + :header: text-center + :column: col-lg-12 p-2 + + .. link-button:: key-concepts/objects + :type: ref + :text: Vineyard Objects + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + The design space of vineyard objects. + + --- + + .. link-button:: key-concepts/vcdl + :type: ref + :text: VCDL and Integration + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + VCDL and how to integration vineyard with computing systems. + + --- + + .. link-button:: key-concepts/data-accessing + :type: ref + :text: Accessing Objects in Vineyard + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + The approaches that can be used to access various kinds of objects stored in + vineyard. + + --- + + .. link-button:: key-concepts/streams + :type: ref + :text: Stream in Vineyard + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + The stream abstraction upon the immutable data sharing storage and its usages. + + --- + + .. link-button:: key-concepts/io-drivers + :type: ref + :text: I/O Drivers + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Design and implementation of the builtin I/O drivers that eases the integration + of computing engines to existing infrastructure. diff --git a/_sources/notes/key-concepts/data-accessing.rst.txt b/_sources/notes/key-concepts/data-accessing.rst.txt new file mode 100644 index 0000000000..51eff7744b --- /dev/null +++ b/_sources/notes/key-concepts/data-accessing.rst.txt @@ -0,0 +1,317 @@ +Data Accessing +============== + +Vineyard is designed to support distributed object sharing and offers both IPCClient +and RPCClient for efficient data access. This section will guide you through various +methods of accessing objects within vineyard. For more information on vineyard object +basics, please refer to :ref:`metadata-and-payloads` and :ref:`distributed-objects`. + +IPCClient vs. RPCClient +----------------------- + +As depicted in the above figure, data is partitioned across different vineyard +instances. The concept of zero-copy sharing was explained in :ref:`architecture-of-vineyard`. +Memory mapping is only available for clients on the same instance, while metadata +is globally synchronized and accessible from clients connected to instances on other hosts. + +Vineyard provides two clients to support IPC and RPC scenarios: + +- IPC Client + + - Can only connect to instances deployed on the same host. + - Offers full support for local data access. Accessing local blobs is enabled + by zero-copy memory mapping. + +- RPC Client + + - Can connect to any instance with an enabled RPC endpoint. + - Provides limited support for remote data access. Creating and fetching remote + blobs incurs considerable network transfer overhead. + +Local vs. Remote +^^^^^^^^^^^^^^^^ + +Distributed shared objects are typically partitioned, with each vineyard instance managing +some chunks of the entire object. As shown in :ref:`distributed-objects`, a :code:`GlobalTensor` +is partitioned into three chunks, and each instance holds one chunk of type :code:`Tensor`. + +**From the perspective of computing engines**, distributed computing engines launch +workers on vineyard instances. Each worker connects to the co-located local instance and +is responsible for processing chunks in that local instance. For example, when starting a Dask +cluster on a vineyard cluster as illustrated in the picture above, each Dask worker is responsible +for executing computations on its local chunks. Some computing tasks require communication between +workers, such as aggregation. In these cases, the communication is performed by the computing +engine itself (in this case, the Dask cluster). + +.. tip:: + + We assume that the computing engines built upon vineyard are responsible for scheduling + tasks based on their awareness of the underlying data partitioning within the vineyard + cluster. + + This design is well-suited for commonly-used modern computing engines,such as GraphScope, + Spark, Presto, Dask, Mars, and Ray. + +Local Objects +------------- + +Creating and accessing local objects in vineyard can be easily achieved using :code:`put` and :code:`get` methods (see +:meth:`vineyard.IPCClient.put` and :meth:`vineyard.IPCClient.get`). + +.. code:: python + :caption: Effortlessly create and access local objects using :code:`put` and :code:`get` + + >>> import pandas as pd + >>> import vineyard + >>> import numpy as np + >>> + >>> vineyard_ipc_client = vineyard.connect("/tmp/vineyard.sock") + >>> + >>> df = pd.DataFrame(np.random.rand(10, 2)) + >>> + >>> # put object into vineyard + >>> r = vineyard_ipc_client.put(df) + >>> r, type(r) + (o00053008257020f8, vineyard._C.ObjectID) + >>> + >>> # get object from vineyard using object id + >>> data = vineyard_ipc_client.get(r) + >>> data + 0 1 + 0 0.534487 0.261941 + 1 0.901056 0.441583 + 2 0.687568 0.671564 + ... + +Vineyard provides low-level APIs to operate on metadatas and raw blobs as well. + +Accessing metadatas +^^^^^^^^^^^^^^^^^^^ + +The method :meth:`vineyard.IPCClient.get_meta` can be used to inspect metadata in the +vineyard cluster, which returns a :class:`vineyard.ObjectMeta` value: + +.. code:: python + :caption: Accessing metadata in vineyard + + >>> meta = vineyard_ipc_client.get_meta(r) + >>> meta.id + o00053008257020f8 + >>> meta.instance_id + 0 + >>> meta.typename + 'vineyard::DataFrame' + >>> meta + { + "instance_id": 0, + "nbytes": 0, + "signature": 1460186430481176, + "transient": true, + "typename": "vineyard::DataFrame" + "__values_-value-0": { + "global": false, + "id": "o0005300822f54d1c", + "instance_id": 0, + "nbytes": 80, + "order_": "\"F\"", + "shape_": "[10]", + "signature": 1460186388165810, + "transient": true, + "typename": "vineyard::Tensor", + "value_type_": "float64", + "value_type_meta_": ">> import vineyard + >>> vineyard_ipc_client = vineyard.connect("/tmp/vineyard.sock") + >>> + >>> # mock a data + >>> payload = b'abcdefgh1234567890uvwxyz' + >>> + >>> # create a blob builder + >>> buffer_builder = vineyard_ipc_client.create_blob(len(payload)) + >>> + >>> # copy the mocked data into the builder + >>> buffer_builder.copy(0, payload) + >>> + >>> # seal the builder then we will get a blob + >>> blob = buffer_builder.seal(vineyard_ipc_client) + +.. code:: python + :caption: Accessing local blobs + + >>> # get the blob from vineyard using object id + >>> blob = vineyard_ipc_client.get_blob(blob.id) + >>> blob, type(blob) + (Object <"o800532e4ab1f2087": vineyard::Blob>, vineyard._C.Blob) + >>> + >>> # inspect the value + >>> bytes(memoryview(blob)) + b'abcdefgh1234567890uvwxyz' + +Remote Objects +-------------- + +Creating and accessing remote objects in vineyard can be easily achieved using :code:`put` and :code:`get` methods (see +:meth:`vineyard.RPCClient.put` and :meth:`vineyard.RPCClient.get`). + +.. code:: python + :caption: Effortlessly create and access remote objects using :code:`put` and :code:`get` + + >>> import pandas as pd + >>> import vineyard + >>> import numpy as np + >>> + >>> vineyard_rpc_client = vineyard.connect("localhost", 9600) + >>> + >>> df = pd.DataFrame(np.random.rand(10, 2)) + >>> + >>> # put object into vineyard + >>> r = vineyard_rpc_client.put(df) + >>> r, type(r) + (o000a45730a85f8fe, vineyard._C.ObjectID) + >>> + >>> # get object from vineyard using object id + >>> data = vineyard_rpc_client.get(r) + >>> data + 0 1 + 0 0.884227 0.576031 + 1 0.863040 0.069815 + 2 0.297906 0.911874 + ... + +The RPC client enables inspection of remote object metadata and facilitates operations on blobs +within the remote cluster, while taking into account the associated network transfer costs. + +Inspecting metadata +^^^^^^^^^^^^^^^^^^^ + +The method :meth:`vineyard.RPCClient.get_meta` allows you to access object metadata in a similar +manner to :meth:`vineyard.IPCClient.get_meta`, but with the added capability of connecting to a +remote instance. + +.. code:: python + :caption: Metadata accessing using RPCClient + + >>> import vineyard + >>> vineyard_rpc_client = vineyard.connect("localhost", 9600) + >>> + >>> # the `r` from the above "Local Objects" section + >>> meta = vineyard_rpc_client.get_meta(r) + >>> meta.id + o00053008257020f8 + >>> meta.instance_id + 0 + >>> meta.typename + 'vineyard::DataFrame' + +Using remote blobs +^^^^^^^^^^^^^^^^^^ + +However, due to the absence of memory sharing between hosts, zero-copy data sharing is not feasible when +connecting to a vineyard instance that is not deployed on the same host as the client. Transferring data +over the network incurs significant costs, and vineyard requires users to explicitly issue a :code:`migrate` +command to move data from the remote instance to the local instance. For more details, please refer to +:ref:`Object Migration in Vineyard `. + +For added convenience, we also provide APIs to fetch remote blobs to the local client by transferring +payloads over the network. + +- :meth:`vineyard.RPCClient.create_remote_blob`: put a **filled** remote blob builder + :class:`vineyard.RemoteBlobBuilder` to connected remote instance. +- :meth:`vineyard.RPCClient.get_remote_blob`: obtain a remote blob :class:`vineyard.RemoteBlob` + from the vineyard cluster by copying over the network. +- :meth:`vineyard.RPCClient.get_remote_blobs`: obtain a set of remote blobs + :code:`List[vineyard.RemoteBlob]` from the vineyard cluster by copying over the network. + +.. warning:: + + Note that the :code:`remote` in the above APIs means the blob will be transferred using + TCP network. For large blobs, it implies a significant cost of time. + +.. code:: python + :caption: Creating remote blobs + + >>> import vineyard + >>> vineyard_rpc_client = vineyard.connect("localhost", 9600) + >>> + >>> # mock a data + >>> payload = b'abcdefgh1234567890uvwxyz' + >>> + >>> # create an empty blob builder + >>> remote_buffer_builder = vineyard.RemoteBlobBuilder(len(payload)) + >>> + >>> # copy the mocked data into the builder + >>> remote_buffer_builder.copy(0, payload) + >>> + >>> # create the remote blob using the RPCClient, with the `remote_buffer_builder` as argument + >>> remote_blob_meta = vineyard_rpc_client.create_remote_blob(remote_buffer_builder) + +.. code:: python + :caption: Accessing remote blobs + + >>> # get the remote blob from vineyard using object id + >>> remote_blob = vineyard_rpc_client.get_remote_blob(remote_blob_meta.id) + >>> remote_blob, type(remote_blob) + (, vineyard._C.RemoteBlob) + >>> + >>> # inspect the value of remote blob + >>> bytes(memoryview(remote_blob)) + b'abcdefgh1234567890uvwxyz' + +.. warning:: + + The APIs for creating blobs in :class:`vineyard.IPCClient` and :class:`vineyard.RPCClient` + have subtle differences. The :meth:`vineyard.IPCClient.create_blob` method first allocates + a shared memory buffer to create an empty blob builder, allowing the user to fill the buffer + and then seal it. In contrast, the :meth:`vineyard.RPCClient.create_remote_blob` method + creates a remote blob builder on-the-fly, enabling the user to fill the buffer and subsequently + use the client API to send the :code:`remote_buffer_builder` to the remote instance. + +Utilizing Distributed Objects +----------------------------- + +In the illustration at the beginning of this section, we demonstrate that vineyard is capable of sharing +distributed objects partitioned across multiple hosts. Accessing these distributed objects +in vineyard can be achieved through two distinct approaches: + +- Inspecting metadata of global. + + The metadata of global objects can be examined using the :class:`vineyard.RPCClient`. This allows + computing engines to understand the distribution of partitions of global tensors using the + RPCClient, and subsequently schedule jobs over those chunks based on the distribution information. + + Mars employs this method to consume distributed tensors and dataframes in vineyard. + + Additionally, by leveraging the metadata of global objects and the server metadata accessible + via :meth:`vineyard.Client.meta`, multiple RPC clients can connect to retrieve the corresponding + blobs from different nodes. These blobs are then assembled locally into a single object. This approach + is also the default method for :class:`vineyard.client`. + +- Accessing local partitions of global objects using the :code:`IPCClient`: + + Another prevalent pattern for accessing shared global objects involves launching a worker on each + instance where the global object is partitioned. Then, using the :class:`vineyard.IPCClient`, + workers can obtain the local partitions of the global object. Each worker is responsible for + processing its local partitions. + + This pattern is commonly utilized in many computing engines that have been integrated with + vineyard, such as GraphScope and Presto. diff --git a/_sources/notes/key-concepts/io-drivers.rst.txt b/_sources/notes/key-concepts/io-drivers.rst.txt new file mode 100644 index 0000000000..78231f3415 --- /dev/null +++ b/_sources/notes/key-concepts/io-drivers.rst.txt @@ -0,0 +1,37 @@ +.. _divein-driver-label: + +I/O Drivers +=========== + +As we have shown in the getting-started, the ``open`` function in vineyard can open a local +file as a stream for consuming, and we notice that the path of the local file is headed +with the scheme ``file://``. + +Actually, vineyard supports several different types of data source, e.g., ``kafka://`` +for kafka topics. The functional methods to open different data sources as vineyard +streams are called ``drivers`` in vineyard. They are registered to ``open`` for +specific schemes, so that when ``open`` is invoked, it will dispatch the corresponding +driver to handle the specific data source according to the scheme of the path. + +The following sample code demonstrates the dispatching logic in ``open``, and the +registration examples. + +.. code:: python + + >>> @registerize + >>> def open(path, *args, **kwargs): + >>> scheme = urlparse(path).scheme + + >>> for reader in open._factory[scheme][::-1]: + >>> r = reader(path, *args, **kwargs) + >>> if r is not None: + >>> return r + >>> raise RuntimeError('Unable to find a proper IO driver for %s' % path) + >>> + >>> # different driver functions are registered as follows + >>> open.register('file', local_driver) + + +Most importantly, the registration design allows users to register their own drivers +to ``registerized`` vineyard methods using ``.register``, which prevents major revisions +on the processing code to fulfill customized computation requirements. diff --git a/_sources/notes/key-concepts/objects.rst.txt b/_sources/notes/key-concepts/objects.rst.txt new file mode 100644 index 0000000000..18b50ef411 --- /dev/null +++ b/_sources/notes/key-concepts/objects.rst.txt @@ -0,0 +1,279 @@ +.. _vineyard-objects: + +Objects +======= + +Vineyard represents various data types as vineyard objects. It employs a +metadata-payloads decoupled design, where an object in vineyard comprises: + +1. A collection of blobs containing the actual data payload; +2. A hierarchical meta tree that describes the data's type, layout, and properties. + +.. _metadata-and-payloads: + +Object = metadata + payloads +---------------------------- + +There are some examples that explain the basic idea of metadata and payload that +forms vineyard objects: + +- Blob: A blob is a pointer with a length that describes the size of the data, + + - metadata: + + - :code:`length` + + - payloads: + + - :code:`pointer`, the actual payload of the blob + +- Tensor: A tensor can be viewed as a blob that contains the actual data and several + metadata entries that describe the shape and type information, + + - metadata: + + - :code:`shape` + - :code:`dtype` + - :code:`data`, a member with type :code:`Blob` + + - payloads: + + - :code:`pointer` in the member :code:`data` + +- Dataframe: A dataframe is an ordered collection of tensors as its columns and each + column has a unique name, + + - metadata: + + - :code:`column_size` + - :code:`names`, a list of members with type :code:`string` + - :code:`columns`, a list of member with type :code:`Tensor` + + - payloads: + + - a set of :code:`pointer` in the member :code:`columns` (the member :code:`data` of + of those :code:`Tensor` s) + +From the example above, it is evident that objects naturally conform to a hierarchical +model, allowing complex data objects to be composed of simpler ones. Each object +consists of a set of blobs as the payload and a metadata tree that describes +the semantics and organization of those blobs. + +.. admonition:: An example for the object metadata: a dataframe with two columns where each + column is a tensor. + :class: admonition-details + + .. code:: json + + { + "__values_-key-0": "1", + "__values_-key-1": "\"a\"", + "__values_-size": 2, + "__values_-value-0": { + "buffer_": { + "id": "o800527ecdf05cff9", + "instance_id": 39, + "length": 0, + "nbytes": 0, + "transient": true, + "typename": "vineyard::Blob" + }, + "id": "o000527ecdffd95c4", + "instance_id": 39, + "nbytes": 400, + "partition_index_": "[]", + "shape_": "[100]", + "signature": 1451273207424436, + "transient": false, + "typename": "vineyard::Tensor", + "value_type_": "float" + }, + "__values_-value-1": { + "buffer_": { + "id": "o800527ecdeaf1015", + "instance_id": 39, + "length": 0, + "nbytes": 0, + "transient": true, + "typename": "vineyard::Blob" + }, + "id": "o000527ece12e4f0a", + "instance_id": 39, + "nbytes": 800, + "partition_index_": "[]", + "shape_": "[100]", + "signature": 1451273227452968, + "transient": false, + "typename": "vineyard::Tensor", + "value_type_": "double" + }, + "columns_": "[\"a\",1]", + "id": "o000527ece15d374c", + "instance_id": 39, + "nbytes": 1200, + "partition_index_column_": 0, + "partition_index_row_": 0, + "row_batch_index_": 0, + "signature": 1451273231074538, + "transient": false, + "typename": "vineyard::DataFrame" + } + +From the above example of object metadata, it is evident that an object is composed +of various sub-objects, forming a hierarchical data model. Each object consists of +a set of blobs and a metadata tree that describes the semantics of those blobs. + +.. tip:: + + Without the metadata, the set of blobs would merely be a collection of memory + pieces without any meaningful interpretation. + +Refer to :ref:`using-objects-python` for a demonstration of how to put Python objects +into vineyard and retrieve them using IPC clients. + +Separating metadata and payload +------------------------------- + +The decoupling of data payload and data layout in vineyard offers three key advantages: + +1. Payloads are stored locally within each vineyard instance, while metadata is shared + across all instances in the cluster. This significantly reduces the overhead of maintaining + consistency for distributed data. + +2. Vineyard objects become self-descriptive, as the metadata fully determines how + the object should be resolved. This not only ensures semantic consistency when + sharing vineyard objects between different systems and programming languages, + but also allows users to store complex data structures with high-level abstractions, such + as graphs in CSR format directly in vineyard, without the need for serialization/deserialization + every time the object is saved or loaded. + +3. This design enables the exploitation of data-aware scheduling techniques. For example, when processing + a graph in vineyard, we can easily access the metadata tree of the graph to determine the size of each + partitioned fragment without accessing the actual vertices and edges. As a result, + we can allocate precise amounts of computational resources for each fragment, leading to overall + performance improvements. + +Vineyard employs two design choices for the metadata and methods of its objects: + +1. A composable design for vineyard objects, which facilitates distributed data management; + +2. An extensible design for object methods, enabling flexible data sharing + between different computation systems with minimal additional development cost. + +Data model +---------- + +Composable +^^^^^^^^^^ + +The composition mechanism in vineyard is based on the hierarchical tree structure of +the metadata of its objects. The root metadata of a complex object stores references +to the root metadata of its components. By recursively traversing these references, +a complete metadata tree is constructed for the complex object. + +.. figure:: ../../images/vineyard_composable.jpg + :width: 75% + :alt: Vineyard objects are composable + + Vineyard objects are composable + +For instance, a distributed dataframe consists of partitioned dataframe chunks, while +a dataframe is composed of column vectors. Considering the decoupling design of payload +and layout in vineyard objects, the blobs are stored in the corresponding vineyard +instance's memory for each partition, and the metadata (e.g., chunk index, shape, +column data types) are stored in the key-value store behind the metadata service. + +To store a distributed graph, we first save the partitioned fragments in each vineyard +instance and share their metadata in the backend key-value store. Then, we can create +the distributed graph by generating the root metadata containing links to the root +metadata of the fragments in an efficient manner. + +.. _distributed-objects: + +Distributed objects +^^^^^^^^^^^^^^^^^^^ + +Vineyard is designed to store large objects across multiple nodes in a cluster, enabling +user programs to seamlessly interact with these objects as a single entity. Data is +sharded across multiple machines without replication. + +.. figure:: ../../images/vineyard_distributed_tensor.jpg + :alt: Distributed objects in vineyard + :width: 60% + + Distributed objects in vineyard + +For example, consider a "Tensor" object that contains billions of columns and rows, making +it too large to fit into a single machine. In such cases, the tensor can be split along +the index or column axis, with each vineyard node holding a subset of chunks. Vineyard +provides a logical view of the complete tensor, allowing distributed computation engines +like Mars and GraphScope to process the data structure as a whole. +.. TODO: add the collection APIs + +.. tip:: + + See also the concepts of *persistent objects* in the following subsection. Refer to + the following subsection for more information on the concept of *persistent objects*. + +Transient vs. Persistent +^^^^^^^^^^^^^^^^^^^^^^^^ + +As previously mentioned, vineyard objects' metadata and payloads are managed separately +by different components of the vineyard server. Payloads are designed to be shared with +computing engines using local memory mapping. However, metadata may need to be inspected +by clients connected to other vineyard instances, such as when forming a distributed object. +In this case, the distributed object consists of a set of chunks placed on different +vineyard instances. When retrieving the distributed objects from vineyard, computing engines +may need to inspect the metadata of non-local pieces to understand the distribution of the +entire dataset. + +This requirement implies that metadata must be globally synchronized and accessible from +clients connected to other vineyard instances. However, global synchronization is a costly +operation, and numerous small key-value pairs can significantly increase the burden on the +key-value store backend of our metadata services. To address this issue, we categorize +objects as transient or persistent. + +- **Transient objects** are designed for cases where the object is known not to be part of a + distributed object and will never need to be inspected by clients on other vineyard instances. + Transient objects are useful for short-lived immediate values within the progress of a + single computing engine. + +- **Persistent objects** are designed for cases where the object chunk will be used to form + a larger distributed object, and the metadata is needed when applications inspect the + distributed object. Persistent objects and distributed objects are commonly used to pass + intermediate data between two distributed engines. + +.. caution:: + + By default, objects are **transient**. We provide an API :code:`client.persist()` that + can explicitly persist the metadata of the target object to etcd, ensuring its visibility + by clients connected to other instances in the cluster. + +.. _builder-resolver: + +Builders and resolvers +^^^^^^^^^^^^^^^^^^^^^^ + +Vineyard utilizes an extensible registry mechanism to enable users to easily integrate their +data structures into the system. This design, which includes builders, resolvers, and drivers, +allows users to create, resolve, and share their data structures across different systems and +paradigms. Notably, even the core data structures and drivers in Vineyard follow this design. + +.. note:: + + **What is the registry mechanism?** + + The registry mechanism decouples methods from the definition of Vineyard data types. For + builders and resolvers, this means users can flexibly register different implementations + in various languages to build and resolve the same Vineyard data type. This enables data + sharing between different systems and paradigms and allows for native language optimizations. + + For drivers, the registry mechanism permits users to flexibly plug in functionality methods + in different languages for Vineyard data types, providing the necessary capabilities for + data types during the data analysis process. + + Moreover, the registered methods can be implemented and optimized according to specific + data analysis tasks, further enhancing efficiency. + +Refer to :ref:`define-python-types` and :ref:`define-cpp-types` for examples of how builders +and resolvers are implemented in Python and C++, respectively. diff --git a/_sources/notes/key-concepts/streams.rst.txt b/_sources/notes/key-concepts/streams.rst.txt new file mode 100644 index 0000000000..afa663367e --- /dev/null +++ b/_sources/notes/key-concepts/streams.rst.txt @@ -0,0 +1,122 @@ +.. _streams-in-vineyard: + +Streams in Vineyard +=================== + +Streams in Vineyard serve as an abstraction over the immutable data sharing storage, +facilitating seamless pipelining between computing engines. Similar to +`pipe `_, Vineyard's streams enable +efficient inter-engine communication while minimizing the overhead associated with +data serialization/deserialization and copying. + +A typical use case for streams in Vineyard involves one process continuously producing +data chunks (e.g., an IO reader) while another process performs scan computations +on the data (e.g., filtering and aggregation operations). A stream consists of a +sequence of immutable data chunks, produced by the former engine and consumed by the +latter engine. + +This section will explore the utilization of streams in Vineyard. + +Using streams +------------- + +We first import required packages: + +.. code:: python + + import threading + import time + from typing import List + + import numpy as np + import pandas as pd + + import vineyard + from vineyard.io.recordbatch import RecordBatchStream + +.. tip:: + + Vineyard has defined some built-in stream types, e.g., + :class:`vineyard.io.recordbatch.RecordBatchStream`. For other stream types, + you could refer to :ref:`python-api-streams`. + +Producer and consumer +--------------------- + +We define a producer that generates random dataframe chunks and inserts them +into the stream: + +.. code:: python + :caption: A producer of :code:`RecordBatchStream` + + def generate_random_dataframe(dtypes, size): + columns = dict() + for k, v in dtypes.items(): + columns[k] = np.random.random(size).astype(v) + return pd.DataFrame(columns) + + def producer(stream: RecordBatchStream, total_chunks, dtypes, produced: List): + writer = stream.writer + for idx in range(total_chunks): + time.sleep(idx) + chunk = generate_random_dataframe(dtypes, 2) # np.random.randint(10, 100)) + chunk_id = vineyard_client.put(chunk) + writer.append(chunk_id) + produced.append((chunk_id, chunk)) + writer.finish() + +Additionally, we create a consumer that retrieves the chunks from the stream in a +loop, continuing until it encounters a :code:`StopIteration` exception: + +.. code:: python + :caption: A consumer of :code:`RecordBatchStream` + + def consumer(stream: RecordBatchStream, total_chunks, produced: List): + reader = stream.reader + index = 0 + while True: + try: + chunk = reader.next() + print('reader receive chunk:', type(chunk), chunk) + pd.testing.assert_frame_equal(produced[index][1], chunk) + except StopIteration: + break + index += 1 + +Streams between processes +------------------------- + +Finally, we can test the producer and consumer using two threads: + +.. code:: python + :caption: Connect the producer and consumer threads using vineyard stream + + def test_recordbatch_stream(vineyard_client, total_chunks): + stream = RecordBatchStream.new(vineyard_client) + dtypes = { + 'a': np.dtype('int'), + 'b': np.dtype('float'), + 'c': np.dtype('bool'), + } + + client1 = vineyard_client.fork() + client2 = vineyard_client.fork() + stream1 = client1.get(stream.id) + stream2 = client2.get(stream.id) + + produced = [] + + thread1 = threading.Thread(target=consumer, args=(stream1, total_chunks, produced)) + thread1.start() + + thread2 = threading.Thread(target=producer, args=(stream2, total_chunks, dtypes, produced)) + thread2.start() + + thread1.join() + thread2.join() + + if __name__ == '__main__': + vineyard_client = vineyard.connect("/tmp/vineyard.sock") + test_recordbatch_stream(vineyard_client, total_chunks=10) + +For more detailed API about the streams, please refer to :ref:`python-api-streams`. diff --git a/_sources/notes/key-concepts/vcdl.rst.txt b/_sources/notes/key-concepts/vcdl.rst.txt new file mode 100644 index 0000000000..24683a28d7 --- /dev/null +++ b/_sources/notes/key-concepts/vcdl.rst.txt @@ -0,0 +1,40 @@ +.. _vcdl: + +Code Generation for Boilerplate +=============================== + +The process of sharing objects between engines involves two fundamental steps: defining +the data structure and establishing the protocol to represent the data type +as :ref:`vineyard-objects`. To alleviate the integration burden with custom data types, +Vineyard offers an auto-generation mechanism. + +This mechanism, known as **VCDL**, relies on the custom annotation :code:`[[shared]]` +applied to C++ classes. + +Consider the following C++ class :code:`Array` as an example: + +.. code:: c++ + + template + class [[vineyard]] Array { + public: + [[shared]] const T& operator[](size_t loc) const { return data()[loc]; } + [[shared]] size_t size() const { return size_; } + [[shared]] const T* data() const { + return reinterpret_cast(buffer_->data()); + } + + private: + [[shared]] size_t size_; + [[shared]] std::shared_ptr buffer_; + }; + +- When applied to classes: the class itself is identified as a shared vineyard + object, and a builder and resolver (see also :ref:`builder-resolver`) are + automatically synthesized. + +- When applied to data members: the data member is treated as a metadata + field or a sub-member. + +- When applied to method members: the method member is deemed + cross-language sharable, and FFI wrappers are automatically synthesized. diff --git a/_sources/notes/references.rst.txt b/_sources/notes/references.rst.txt new file mode 100644 index 0000000000..9d3cc6650c --- /dev/null +++ b/_sources/notes/references.rst.txt @@ -0,0 +1,48 @@ +API Reference +============== + +.. toctree:: + :maxdepth: 1 + :caption: TOC + :hidden: + + references/python-api.rst + references/cpp-api.rst + Kubernetes CRDs + +Vineyard offers a comprehensive suite of SDKs, including Python and C++ versions. +For detailed API references, please explore the following pages: + +.. panels:: + :header: text-center + :column: col-lg-12 p-2 + + .. link-button:: references/python-api + :type: ref + :text: Python + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + API reference for vineyard Python SDK. + + --- + + .. link-button:: references/cpp-api + :type: ref + :text: C++ + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + API reference for vineyard C++ SDK. + +All terms in the documentation site can use search from the following +indexing page: + +.. panels:: + :header: text-center + :column: col-lg-12 p-2 + + .. link-button:: genindex + :type: ref + :text: API Indexes + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Term indexes for the vineyard documentation. diff --git a/_sources/notes/references/cpp-api.rst.txt b/_sources/notes/references/cpp-api.rst.txt new file mode 100644 index 0000000000..e13bb7e647 --- /dev/null +++ b/_sources/notes/references/cpp-api.rst.txt @@ -0,0 +1,146 @@ +C++ API Reference +================= + +.. _cpp-api: + +.. default-domain:: cpp + +Objects +------- + +.. doxygentypedef:: vineyard::ObjectID + +.. doxygenclass:: vineyard::Object + :members: + :protected-members: + :undoc-members: + +.. doxygenclass:: vineyard::ObjectBuilder + :members: + :protected-members: + :undoc-members: + +.. doxygenclass:: vineyard::ObjectBase + :members: + :undoc-members: + +Metadata +-------- + +.. doxygenclass:: vineyard::ObjectMeta + :members: + :protected-members: + :undoc-members: + +Vineyard Clients +---------------- + +.. doxygenclass:: vineyard::ClientBase + :members: + :protected-members: + :undoc-members: + +.. doxygenclass:: vineyard::Client + :members: + :protected-members: + :undoc-members: + +.. doxygenclass:: vineyard::RPCClient + :members: + :protected-members: + :undoc-members: + +Vineyard Server +--------------- + +.. doxygenstruct:: vineyard::InstanceStatus + :members: + :undoc-members: + +Blob +---- + +.. doxygenclass:: vineyard::Blob + :members: + :undoc-members: + +.. doxygenclass:: vineyard::BlobWriter + :members: + :undoc-members: + +Stream +------ + +.. doxygenclass:: vineyard::ByteStream + :members: + :undoc-members: + +Basic Data Types +---------------- + +.. doxygenclass:: vineyard::Array + :members: + :undoc-members: + +.. doxygenclass:: vineyard::ArrayBuilder + :members: + :undoc-members: + +.. doxygenclass:: vineyard::Hashmap + :members: + :undoc-members: + +.. doxygenclass:: vineyard::HashmapBuilder + :members: + :undoc-members: + +.. doxygenclass:: vineyard::Tensor + :members: + :undoc-members: + +.. doxygenclass:: vineyard::TensorBuilder + :members: + :undoc-members: + +.. doxygenclass:: vineyard::DataFrame + :members: + :undoc-members: + +.. doxygenclass:: vineyard::DataFrameBuilder + :members: + :undoc-members: + +.. doxygenclass:: vineyard::Sequence + :members: + :undoc-members: + +.. doxygenclass:: vineyard::SequenceBuilder + :members: + :undoc-members: + +.. doxygenclass:: vineyard::Scalar + :members: + :undoc-members: + +.. doxygenclass:: vineyard::ScalarBuilder + :members: + :undoc-members: + +Distributed Data Types +---------------------- + +.. doxygenclass:: vineyard::GlobalTensor + :members: + :undoc-members: + +.. doxygenclass:: vineyard::GlobalTensorBuilder + :members: + :undoc-members: + +.. doxygenclass:: vineyard::GlobalDataFrame + :members: + :undoc-members: + +.. doxygenclass:: vineyard::GlobalDataFrameBuilder + :members: + :undoc-members: diff --git a/_sources/notes/references/crds.md.txt b/_sources/notes/references/crds.md.txt new file mode 100644 index 0000000000..f837a5093e --- /dev/null +++ b/_sources/notes/references/crds.md.txt @@ -0,0 +1,615 @@ +# API Reference + +## Packages +- [k8s.v6d.io/v1alpha1](#k8sv6diov1alpha1) + + +## k8s.v6d.io/v1alpha1 + +Package v1alpha1 contains API Schema definitions for the k8s v1alpha1 API group + +### Resource Types +- [Backup](#backup) +- [BackupList](#backuplist) +- [CSIDriver](#csidriver) +- [CSIDriverList](#csidriverlist) +- [GlobalObject](#globalobject) +- [GlobalObjectList](#globalobjectlist) +- [LocalObject](#localobject) +- [LocalObjectList](#localobjectlist) +- [Operation](#operation) +- [OperationList](#operationlist) +- [Recover](#recover) +- [RecoverList](#recoverlist) +- [Sidecar](#sidecar) +- [SidecarList](#sidecarlist) +- [Vineyardd](#vineyardd) +- [VineyarddList](#vineyarddlist) + + + +#### Backup + + + +Backup describes a backup operation of vineyard objects, which uses the [Kubernetes PersistentVolume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) to store the backup data. Every backup operation will be binded with the name of Backup. + +_Appears in:_ +- [BackupList](#backuplist) + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `Backup` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[BackupSpec](#backupspec)_ | | + + +#### BackupList + + + +BackupList contains a list of Backup + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `BackupList` +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[Backup](#backup) array_ | | + + +#### BackupSpec + + + +BackupSpec defines the desired state of Backup + +_Appears in:_ +- [Backup](#backup) + +| Field | Description | +| --- | --- | +| `vineyarddName` _string_ | the name of the vineyard cluster | +| `vineyarddNamespace` _string_ | the namespace of the vineyard cluster | +| `objecIDs` _string array_ | the specific objects to be backed up if not specified, all objects will be backed up | +| `backupPath` _string_ | the path of backup data | +| `persistentVolumeSpec` _[PersistentVolumeSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#persistentvolumespec-v1-core)_ | the PersistentVolumeSpec of the backup data | +| `persistentVolumeClaimSpec` _[PersistentVolumeClaimSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#persistentvolumeclaimspec-v1-core)_ | the PersistentVolumeClaimSpec of the backup data | + + + + +#### CSIDriver + + + +CSIDriver is the Schema for the csidrivers API + +_Appears in:_ +- [CSIDriverList](#csidriverlist) + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `CSIDriver` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[CSIDriverSpec](#csidriverspec)_ | | + + +#### CSIDriverList + + + +CSIDriverList contains a list of CSIDriver + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `CSIDriverList` +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[CSIDriver](#csidriver) array_ | | + + +#### CSIDriverSpec + + + +CSIDriverSpec defines the desired state of CSIDriver + +_Appears in:_ +- [CSIDriver](#csidriver) + +| Field | Description | +| --- | --- | +| `image` _string_ | Image is the name of the csi driver image | +| `imagePullPolicy` _string_ | ImagePullPolicy is the image pull policy of the csi driver | +| `storageClassName` _string_ | StorageClassName is the name of the storage class | +| `volumeBindingMode` _string_ | VolumeBindingMode is the volume binding mode of the storage class | +| `sidecar` _[CSISidecar](#csisidecar)_ | Sidecar is the configuration for the CSI sidecar container nolint: lll | +| `clusters` _[VineyardClusters](#vineyardclusters) array_ | Clusters are the list of vineyard clusters | +| `enableToleration` _boolean_ | EnableToleration is the flag to enable toleration for the csi driver | +| `enableVerboseLog` _boolean_ | EnableVerboseLog is the flag to enable verbose log for the csi driver | + + + + +#### CSISidecar + + + +CSISidecar holds the configuration for the CSI sidecar container + +_Appears in:_ +- [CSIDriverSpec](#csidriverspec) + +| Field | Description | +| --- | --- | +| `provisionerImage` _string_ | ProvisionerImage is the image of the provisioner sidecar | +| `attacherImage` _string_ | AttacherImage is the image of the attacher sidecar | +| `nodeRegistrarImage` _string_ | NodeRegistrarImage is the image of the node registrar sidecar | +| `livenessProbeImage` _string_ | LivenessProbeImage is the image of the liveness probe sidecar | +| `imagePullPolicy` _string_ | ImagePullPolicy is the image pull policy of all sidecar containers | +| `enableTopology` _boolean_ | EnableTopology is the flag to enable topology for the csi driver | + + +#### GlobalObject + + + +GlobalObject describes a global object in vineyard, whose metadata will be stored in etcd. + +_Appears in:_ +- [GlobalObjectList](#globalobjectlist) + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `GlobalObject` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[GlobalObjectSpec](#globalobjectspec)_ | | + + +#### GlobalObjectList + + + +GlobalObjectList contains a list of GlobalObject + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `GlobalObjectList` +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[GlobalObject](#globalobject) array_ | | + + +#### GlobalObjectSpec + + + +GlobalObjectSpec defines the desired state of GlobalObject + +_Appears in:_ +- [GlobalObject](#globalobject) + +| Field | Description | +| --- | --- | +| `id` _string_ | | +| `name` _string_ | | +| `signature` _string_ | | +| `typename` _string_ | | +| `members` _string array_ | | +| `metadata` _string_ | Refer to Kubernetes API documentation for fields of `metadata`. | + + + + +#### LocalObject + + + +LocalObject describes a local object in vineyard, whose metadata will only be stored in local vineyard. + +_Appears in:_ +- [LocalObjectList](#localobjectlist) + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `LocalObject` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[LocalObjectSpec](#localobjectspec)_ | | + + +#### LocalObjectList + + + +LocalObjectList contains a list of LocalObject + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `LocalObjectList` +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[LocalObject](#localobject) array_ | | + + +#### LocalObjectSpec + + + +LocalObjectSpec defines the desired state of LocalObject + +_Appears in:_ +- [LocalObject](#localobject) + +| Field | Description | +| --- | --- | +| `id` _string_ | | +| `name` _string_ | | +| `signature` _string_ | | +| `typename` _string_ | | +| `instance_id` _integer_ | | +| `hostname` _string_ | | +| `metadata` _string_ | Refer to Kubernetes API documentation for fields of `metadata`. | + + + + +#### MetricConfig + + + +MetricConfig holds the configuration about metric container + +_Appears in:_ +- [SidecarSpec](#sidecarspec) +- [VineyarddSpec](#vineyarddspec) + +| Field | Description | +| --- | --- | +| `enable` _boolean_ | Enable metrics | +| `image` _string_ | represent the metric's image | +| `imagePullPolicy` _string_ | the policy about pulling image | + + +#### Operation + + + +Operation describes an operation between workloads, such as assembly and repartition. + As for the `assembly` operation, there are several kinds of computing engines, some may not support the stream data, so we need to insert an `assembly` operation to assemble the stream data into a batch data, so that the next computing engines can process the data. + As for the `repartition` operation, the vineyard has integrated with the distributed computing engines, such as Dask. If you want to repartition the data to adapt the dask workers, then the `repartition` operation is essential for such scenario. + +_Appears in:_ +- [OperationList](#operationlist) + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `Operation` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[OperationSpec](#operationspec)_ | | + + +#### OperationList + + + +OperationList contains a list of Operation + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `OperationList` +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[Operation](#operation) array_ | | + + +#### OperationSpec + + + +OperationSpec defines the desired state of Operation + +_Appears in:_ +- [Operation](#operation) + +| Field | Description | +| --- | --- | +| `name` _string_ | the name of vineyard pluggable drivers, including assembly and repartition. | +| `type` _string_ | the type of object, including local and distributed. | +| `require` _string_ | the required job's name of the operation | +| `target` _string_ | the target job's name of the operation | +| `timeoutSeconds` _integer_ | TimeoutSeconds is the timeout of the operation. | + + + + +#### PluginImageConfig + + + +PluginImageConfig holds all image configuration about pluggable drivers(backup, recover, local assembly, distributed assembly, repartition) + +_Appears in:_ +- [VineyarddSpec](#vineyarddspec) + +| Field | Description | +| --- | --- | +| `backupImage` _string_ | the image of backup operation | +| `recoverImage` _string_ | the image of recover operation | +| `daskRepartitionImage` _string_ | the image of dask repartition operation | +| `localAssemblyImage` _string_ | the image of local assembly operation | +| `distributedAssemblyImage` _string_ | the image of distributed assembly operation | + + +#### Recover + + + +Recover describes a recover operation of vineyard objects, which is used to recover a specific backup operation. + +_Appears in:_ +- [RecoverList](#recoverlist) + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `Recover` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[RecoverSpec](#recoverspec)_ | | + + +#### RecoverList + + + +RecoverList contains a list of Recover + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `RecoverList` +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[Recover](#recover) array_ | | + + +#### RecoverSpec + + + +RecoverSpec defines the desired state of Recover + +_Appears in:_ +- [Recover](#recover) + +| Field | Description | +| --- | --- | +| `backupName` _string_ | the name of backup | +| `backupNamespace` _string_ | the namespace of backup | + + + + +#### ServiceConfig + + + +ServiceConfig holds all service configuration about vineyardd + +_Appears in:_ +- [SidecarSpec](#sidecarspec) +- [VineyarddSpec](#vineyarddspec) + +| Field | Description | +| --- | --- | +| `type` _string_ | service type | +| `port` _integer_ | service port | + + +#### Sidecar + + + +Sidecar is used for configuring and managing the vineyard sidecar container. + +_Appears in:_ +- [SidecarList](#sidecarlist) + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `Sidecar` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[SidecarSpec](#sidecarspec)_ | | + + +#### SidecarList + + + +SidecarList contains a list of Sidecar + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `SidecarList` +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[Sidecar](#sidecar) array_ | | + + +#### SidecarSpec + + + +SidecarSpec defines the desired state of Sidecar + +_Appears in:_ +- [Sidecar](#sidecar) + +| Field | Description | +| --- | --- | +| `selector` _string_ | the selector of pod | +| `replicas` _integer_ | the replicas of workload | +| `etcdReplicas` _integer_ | EtcdReplicas describe the etcd replicas | +| `vineyard` _[VineyardConfig](#vineyardconfig)_ | vineyard container configuration nolint: lll | +| `metric` _[MetricConfig](#metricconfig)_ | metric container configuration | +| `volume` _[VolumeConfig](#volumeconfig)_ | metric configurations | +| `service` _[ServiceConfig](#serviceconfig)_ | rpc service configuration | +| `securityContext` _[SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#securitycontext-v1-core)_ | SecurityContext holds the security context settings for the vineyardd container. | +| `volumes` _[Volume](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#volume-v1-core) array_ | Volumes is the list of Kubernetes volumes that can be mounted by the vineyard container. | +| `volumeMounts` _[VolumeMount](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#volumemount-v1-core) array_ | VolumeMounts specifies the volumes listed in ".spec.volumes" to mount into the vineyard container. | + + + + +#### SpillConfig + + + +SpillConfig holds all configuration about spilling + +_Appears in:_ +- [VineyardConfig](#vineyardconfig) + +| Field | Description | +| --- | --- | +| `name` _string_ | the name of the spill config | +| `path` _string_ | the path of spilling | +| `spillLowerRate` _string_ | low watermark of spilling memory | +| `spillUpperRate` _string_ | high watermark of triggering spilling | +| `persistentVolumeSpec` _[PersistentVolumeSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#persistentvolumespec-v1-core)_ | the PersistentVolumeSpec of the spilling PV | +| `persistentVolumeClaimSpec` _[PersistentVolumeClaimSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#persistentvolumeclaimspec-v1-core)_ | the PersistentVolumeClaimSpec of the spill file | + + +#### VineyardClusters + + + +VineyardClusters contains the list of vineyard clusters + +_Appears in:_ +- [CSIDriverSpec](#csidriverspec) + +| Field | Description | +| --- | --- | +| `namespace` _string_ | Namespace is the namespace of the vineyard cluster | +| `name` _string_ | Name is the name of the vineyard deployment | + + +#### VineyardConfig + + + +VineyardConfig holds all configuration about vineyard container + +_Appears in:_ +- [SidecarSpec](#sidecarspec) +- [VineyarddSpec](#vineyarddspec) + +| Field | Description | +| --- | --- | +| `image` _string_ | represent the vineyardd's image | +| `imagePullPolicy` _string_ | the policy about pulling image | +| `syncCRDs` _boolean_ | synchronize CRDs when persisting objects | +| `socket` _string_ | The directory on host for the IPC socket file. The UNIX-domain socket will be placed as `${Socket}/vineyard.sock`. | +| `size` _string_ | shared memory size for vineyardd | +| `reserveMemory` _boolean_ | reserve the shared memory for vineyardd | +| `streamThreshold` _integer_ | memory threshold of streams (percentage of total memory) | +| `spill` _[SpillConfig](#spillconfig)_ | the configuration of spilling | +| `env` _[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#envvar-v1-core) array_ | vineyard environment configuration | +| `memory` _string_ | the memory resources of vineyard container | +| `cpu` _string_ | the cpu resources of vineyard container | + + +#### Vineyardd + + + +Vineyardd is used to deploy a vineyard cluster on kubernetes, which can simplify the configurations of the vineyard binary, the external etcd cluster and the vineyard [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/). As vineyard is bound to a specific socket on the hostpath by default, the vineyard pod cannot be deployed on the same node. Before deploying vineyardd, you should know how many nodes are available for vineyard pod to deploy on and make sure the vineyardd pod number is less than the number of available nodes. + +_Appears in:_ +- [VineyarddList](#vineyarddlist) + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `Vineyardd` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[VineyarddSpec](#vineyarddspec)_ | | + + +#### VineyarddList + + + +VineyarddList contains a list of Vineyardd + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `k8s.v6d.io/v1alpha1` +| `kind` _string_ | `VineyarddList` +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[Vineyardd](#vineyardd) array_ | | + + +#### VineyarddSpec + + + +VineyarddSpec holds all configuration about vineyardd + +_Appears in:_ +- [Vineyardd](#vineyardd) + +| Field | Description | +| --- | --- | +| `replicas` _integer_ | Replicas is the number of vineyardd pods to deploy | +| `etcdReplicas` _integer_ | EtcdReplicas describe the etcd replicas | +| `service` _[ServiceConfig](#serviceconfig)_ | vineyardd's service | +| `vineyard` _[VineyardConfig](#vineyardconfig)_ | vineyard container configuration nolint: lll | +| `pluginImage` _[PluginImageConfig](#pluginimageconfig)_ | operation container configuration nolint: lll | +| `metric` _[MetricConfig](#metricconfig)_ | metric container configuration | +| `volume` _[VolumeConfig](#volumeconfig)_ | Volume configuration | +| `securityContext` _[SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#securitycontext-v1-core)_ | SecurityContext holds the security context settings for the vineyardd container. | +| `volumes` _[Volume](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#volume-v1-core) array_ | Volumes is the list of Kubernetes volumes that can be mounted by the vineyard deployment. | +| `volumeMounts` _[VolumeMount](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#volumemount-v1-core) array_ | VolumeMounts specifies the volumes listed in ".spec.volumes" to mount into the vineyard deployment. | + + + + +#### VolumeConfig + + + +VolumeConfig holds all configuration about persistent volume + +_Appears in:_ +- [SidecarSpec](#sidecarspec) +- [VineyarddSpec](#vineyarddspec) + +| Field | Description | +| --- | --- | +| `pvcName` _string_ | the name of pvc | +| `mountPath` _string_ | the mount path of pv | + + diff --git a/_sources/notes/references/python-api.rst.txt b/_sources/notes/references/python-api.rst.txt new file mode 100644 index 0000000000..f45bee5771 --- /dev/null +++ b/_sources/notes/references/python-api.rst.txt @@ -0,0 +1,163 @@ +Python API Reference +==================== + +.. _python-api: + +.. default-domain:: py + +.. currentmodule:: vineyard + +Objects +------- + +.. autoclass:: ObjectID + :special-members: + :members: + +.. autoclass:: Object + :members: + +.. autoclass:: ObjectBuilder + :members: + +Metadata +-------- + +.. autoclass:: ObjectMeta + :special-members: + :members: + +Vineyard client +--------------- + +.. autofunction:: connect + +.. autoclass:: Client + :inherited-members: + :members: + +.. autoclass:: IPCClient + :inherited-members: + :members: + +.. autoclass:: RPCClient + :inherited-members: + :members: + +Vineyard cluster +---------------- + +.. autoclass:: InstanceStatus + :special-members: + :members: + +Blob +---- + +.. autoclass:: Blob + :members: + +.. autoclass:: BlobBuilder + :members: + +.. autoclass:: RemoteBlob + :members: + +.. autoclass:: RemoteBlobBuilder + :members: + +Resolvers and builders +---------------------- + +.. autoclass:: vineyard.core.resolver.ResolverContext + :members: + +.. autofunction:: vineyard.core.resolver.get_current_resolvers +.. autofunction:: vineyard.core.resolver.resolver_context + +.. autoclass:: vineyard.core.builder.BuilderContext + :members: + +.. autofunction:: vineyard.core.builder.get_current_builders +.. autofunction:: vineyard.core.builder.builder_context + +.. autoclass:: vineyard.core.driver.DriverContext + :members: + +.. autofunction:: vineyard.core.driver.get_current_drivers +.. autofunction:: vineyard.core.driver.driver_context + +.. _shared-memory: + +Shared memory +------------- + +.. autoclass:: vineyard.shared_memory.SharedMemory + :members: + +.. autoclass:: vineyard.shared_memory.ShareableList + :members: + +.. _vineyard-python-deployment-api: + +Deployment +---------- + +.. autofunction:: vineyard.connect +.. autofunction:: vineyard.get_current_socket +.. autofunction:: vineyard.deploy.local.start_vineyardd +.. autofunction:: vineyard.deploy.distributed.start_vineyardd +.. autofunction:: vineyard.deploy.kubernetes.start_vineyardd +.. autofunction:: vineyard.deploy.kubernetes.delete_kubernetes_objects + +I/O Drivers +----------- + +.. autofunction:: vineyard.io.open +.. autofunction:: vineyard.io.read +.. autofunction:: vineyard.io.write + +.. _python-api-streams: + +Streams +------- + +.. autoclass:: vineyard.io.byte.ByteStream + :members: + +.. autoclass:: vineyard.io.dataframe.DataframeStream + :members: + +.. autoclass:: vineyard.io.recordbatch.RecordBatchStream + :members: + +Interacting with the CSI Driver +------------------------------- + +.. autofunction:: vineyard.csi.read +.. autofunction:: vineyard.csi.write + +Vineyard Cli Tool +----------------- + +You can also use the Python API to interact with internal +`Vineyard Cli Tool`_. + +.. code-block:: bash + + $ python -m vineyard.cli [options] + +Vineyard LLM KV Cache +--------------------- + +Before using the KV Cache, you need to install the ``vineyard-llm`` package. + +.. autoclass:: vineyard.llm.KVCache + :special-members: + :members: + +.. autoclass:: vineyard.llm.KVCacheConfig + :special-members: + :members: + +.. _Vineyard Cli Tool: https://v6d.io/notes/cloud-native/vineyardctl.html diff --git a/_sources/tutorials/data-processing.rst.txt b/_sources/tutorials/data-processing.rst.txt new file mode 100644 index 0000000000..2d4ecc1d75 --- /dev/null +++ b/_sources/tutorials/data-processing.rst.txt @@ -0,0 +1,70 @@ + +Data processing +=============== + +.. toctree:: + :maxdepth: 1 + :caption: TOC + :hidden: + + ./data-processing/using-objects-python.rst + ./data-processing/python-sharedmemory.rst + ./data-processing/distributed-learning.rst + ./data-processing/accelerate-data-sharing-in-kedro.rst + ./data-processing/gpu-memory-sharing.rst + +In these comprehensive case studies, we demonstrate how to seamlessly integrate vineyard's +capabilities with existing data-intensive tasks. By incorporating vineyard into complex +workflows involving multiple computing engines, users can experience significant +improvements in both performance and ease of use. + +.. panels:: + :header: text-center + :column: col-lg-12 p-2 + + .. link-button:: ./data-processing/using-objects-python + :type: ref + :text: Python Objects + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Effortlessly share Python objects between processes using vineyard's intuitive and efficient approach. + + --- + + .. link-button:: ./data-processing/python-sharedmemory + :type: ref + :text: SharedMemory in Python + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Utilize vineyard as an elegant alternative to :code:`multiprocessing.shared_memory` in Python. + + --- + + .. link-button:: ./data-processing/distributed-learning + :type: ref + :text: Distributed Learning + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Discover how vineyard enhances distributed machine learning training workflows by + seamlessly integrating with various computing engines for improved efficiency and elegance. + + --- + + .. link-button:: ./data-processing/accelerate-data-sharing-in-kedro + :type: ref + :text: Accelerate Data Sharing in Kedro + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Vineyard serves as the :code:`DataSet` backend for Kedro pipelines, enabling + efficient data sharing between tasks without intrusive code modification, even + when the pipeline is deployed to Kubernetes. + + --- + + .. link-button:: ./data-processing/gpu-memory-sharing + :type: ref + :text: GPU Memory Sharing + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Vineyard supports sharing GPU memory in zero-copy manner, enabling efficient data sharing + between GPU-accelerated tasks. diff --git a/_sources/tutorials/data-processing/accelerate-data-sharing-in-kedro.rst.txt b/_sources/tutorials/data-processing/accelerate-data-sharing-in-kedro.rst.txt new file mode 100644 index 0000000000..a3ebe5a7eb --- /dev/null +++ b/_sources/tutorials/data-processing/accelerate-data-sharing-in-kedro.rst.txt @@ -0,0 +1,292 @@ +.. _accelerate-data-sharing-in-kedro: + +Accelerate Data Sharing in Kedro +================================ + +This is a tutorial that shows how Vineyard accelerate the intermediate data +sharing between tasks in Kedro pipelines using our +`vineyard-kedro `_ plugin, when data +scales and the pipeline are deployed on Kubernetes. + +.. note:: + + This tutorial is based on the `Developing and Learning MLOps at Home `_ project, + a tutorial about orchestrating a machine learning pipeline with Kedro. + +Prepare the Kubernetes cluster +------------------------------ + +To deploy Kedro pipelines on Kubernetes, you must have a kubernetes cluster. + +.. tip:: + + If you already have a K8s cluster, just skip this section and continue + on deploying. + +We recommend `kind v0.20.0 `_ to create a multi-node +Kubernetes cluster on your local machine as follows: + +.. code:: bash + + $ cat < + + - If you are working with Minio, you first need to expose the services + and then create the bucket: + + - Forward minio-artifacts service: + + .. code:: bash + + $ kubectl port-forward service/minio -n minio-dev 9000:9000 + + - Install the minio client: + + .. code:: bash + + $ wget https://dl.min.io/client/mc/release/linux-amd64/mc + $ chmod +x mc + $ sudo mv mc /usr/local/bin + + - Configure the minio client: + + .. code:: bash + + $ mc alias set minio http://localhost:9000 + Enter Access Key: + Enter Secret Key: + + - Finally, create the bucket :code:`minio-s3-benchmark-bucket`: + + .. code:: bash + + $ mc mb minio/minio-s3-benchmark-bucket + Bucket created successfully `minio/minio-s3-benchmark-bucket`. + +Prepare the Docker images +------------------------- + +1. Vineyard has delivered `a benchmark project `_ + to test Kedro pipelines on Vineyard and S3: + + .. code:: bash + + $ cd python/vineyard/contrib/kedro/benchmark/mlops + +2. Configure the credentials configurations of AWS S3: + + .. code:: bash + + $ cat conf/aws-s3/credentials.yml + benchmark_aws_s3: + client_kwargs: + aws_access_key_id: Your AWS/Minio Access Key ID + aws_secret_access_key: Your AWS/Minio Secret Access Key + region_name: Your AWS Region Name + +3. To deploy pipelines to Kubernetes, you first need to build the Docker image for the + benchmark project. + + To show how vineyard can accelerate the data sharing along with the dataset + scales, Docker images for different data size will be generated: + + - For running Kedro on vineyard: + + .. code:: bash + + $ make docker-build + + You will see Docker images for different data size are generated: + + .. code:: bash + + $ docker images | grep mlops + mlops-benchmark latest fceaeb5a6688 17 seconds ago 1.07GB + +4. To make those images available for your Kubernetes cluster, they need to be + pushed to your registry (or load to kind cluster if you setup your Kubernetes + cluster using kind): + + - Push to registry: + + .. code:: bash + + $ docker tag mlops-benchmark:latest /mlops-benchmark:latest + $ docker push /mlops-benchmark:latest + + - Load to kind cluster: + + .. code:: bash + + $ kind load docker-image mlops-benchmark:latest + +Deploy the Kedro Pipelines +-------------------------- + +1. Deploy the Kedro pipeline with vineyard for intermediate data sharing: + + .. code:: bash + + $ kubectl create namespace vineyard + $ for multiplier in 1 10 100 500; do \ + argo submit -n vineyard --watch argo-vineyard-benchmark.yml -p multiplier=${multiplier}; \ + done + +2. Similarly, using AWS S3 or Minio for intermediate data sharing: + + - Using AWS S3: + + .. code:: bash + + $ kubectl create namespace aws-s3 + # create the aws secrets from your ENV + $ kubectl create secret generic aws-secrets -n aws-s3 \ + --from-literal=access_key_id=$AWS_ACCESS_KEY_ID \ + --from-literal=secret_access_key=$AWS_SECRET_ACCESS_KEY + $ for multiplier in 1 10 100 500 1000 2000; do \ + argo submit -n aws-s3 --watch argo-aws-s3-benchmark.yml -p multiplier=${multiplier}; \ + done + + - Using `Cloudpickle dataset `_: + + .. code:: bash + + $ kubectl create namespace cloudpickle + # create the aws secrets from your ENV + $ kubectl create secret generic aws-secrets -n cloudpickle \ + --from-literal=access_key_id=$AWS_ACCESS_KEY_ID \ + --from-literal=secret_access_key=$AWS_SECRET_ACCESS_KEY + $ for multiplier in 1 10 100 500 1000 2000; do \ + argo submit -n cloudpickle --watch argo-cloudpickle-benchmark.yml -p multiplier=${multiplier}; \ + done + + - Using Minio: + + .. code:: bash + + $ kubectl create namespace minio-s3 + $ for multiplier in 1 10 100 500 1000 2000; do \ + argo submit -n minio-s3 --watch argo-minio-s3-benchmark.yml -p multiplier=${multiplier}; \ + done + +Performance +----------- + +After running the benchmark above on Kubernetes, we recorded each node's execution time from the logs +of the argo workflow and calculated the sum of all nodes as the following end-to-end execution time +for each data scale: + +========== ========= ======== ============== ========= +Data Scale Vineyard Minio S3 Cloudpickle S3 AWS S3 +========== ========= ======== ============== ========= +1 4.2s 4.3s 22.5s 16.9s +10 4.9s 5.5s 28.6s 23.3s +100 13.2s 20.3s 64.4s 74s +500 53.6s 84.5s 173.2s 267.9s +1000 109.8s 164.2s 322.7s 510.6s +2000 231.6s 335.9s 632.8s 1069.7s +========== ========= ======== ============== ========= + +We have the following observations from above comparison: + +- Vineyard can significantly accelerate the data sharing between tasks in Kedro pipelines, without the + need for any intrusive changes to the original Kedro pipelines; +- When data scales, the performance of Vineyard is more impressive, as the intermediate data sharing + cost becomes more dominant in end-to-end execution; +- Even compared with local Minio, Vineyard still outperforms it by a large margin, thanks to the ability + of Vineyard to avoid (de)serialization, file I/O and excessive memory copies. +- When using the Cloudpickle dataset(pickle + zstd), the performance is better than AWS S3, as the dataset + will be compressed before uploading to S3. diff --git a/_sources/tutorials/data-processing/distributed-learning.ipynb.txt b/_sources/tutorials/data-processing/distributed-learning.ipynb.txt new file mode 100644 index 0000000000..dd6c41a750 --- /dev/null +++ b/_sources/tutorials/data-processing/distributed-learning.ipynb.txt @@ -0,0 +1,536 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "Distributed Learning with Vineyard\n", + "==================================\n", + "\n", + "With the growth of data, distributed learning is becoming a must in real-world machine learning\n", + "applications, as the data size can easily exceed the memory limit of a single machine.\n", + "Thus, many distributed systems addressing different workloads are developed\n", + "and they share the same objective of extending users' single machine prototypes \n", + "to distributed settings with as few modifications to the code as possible.\n", + "\n", + "For example, **dask.dataframe** mimics the API of **pandas** which is the de-facto standard\n", + "library for single-machine structured data processing, so that users can apply their\n", + "pandas code for data preprocessing in the dask cluster with few modifications.\n", + "Similarly, **horovod** provides easy-to-use APIs for users to transfer their single-machine\n", + "code in machine learning frameworks (e.g., TensorFlow, PyTorch, MXNet) to the distributed settings\n", + "with only a few additional lines of code.\n", + "\n", + "However, when extending to distributed learning, the data sharing between libraries within the same\n", + "python process (e.g., pandas and tensorflow) becomes inter-process sharing between engines (e.g.,\n", + "dask and horovod), not to mention in the distributed fashion. Existing solutions using external\n", + "distributed file systems are less than optimal for the huge I/O overheads.\n", + "\n", + "Vineyard shares the same design principle with the aforementioned distributed systems, which aims to\n", + "provide efficient cross-engine data sharing with few modifications to the existing code.\n", + "Next, we demonstrate how to transfer a single-machine learning example in **keras** to\n", + "distributed learning with dask, horovod and Vineyard.\n", + "\n", + "An Example from Keras\n", + "---------------------\n", + "\n", + "This [example](https://keras.io/examples/structured_data/wide_deep_cross_networks/)\n", + "uses the Covertype dataset from the UCI Machine Learning Repository.\n", + "The task is to predict forest cover type from cartographic variables.\n", + "The dataset includes 506,011 instances with 12 input features:\n", + "10 numerical features and 2 categorical features.\n", + "Each instance is categorized into 1 of 7 classes.\n", + "\n", + "The solution contains three steps:\n", + "\n", + "1. preprocess the data in pandas to extract the 12 features and the label\n", + "2. store the preprocessed data in files\n", + "3. define and train the model in keras\n", + "\n", + "\n", + "Mapping the solution to distributed learning, we have:\n", + "\n", + "1. preprocess the data in dask.dataframe\n", + "2. share the preprocessed data using Vineyard\n", + "3. train the model in horovod.keras\n", + "\n", + "\n", + "We will walk through the code as follows.\n", + "\n", + "Setup\n", + "-------\n", + "\n", + "The distributed deployment of vineyard and dask is as follows: on each machine, we launch a vineyard daemon process to handle the local data storage on that machine; and we also launch a dask worker on that machine for the computation accordingly. In this notebook, we limit the machine number as 1 (i.e., the local machine) just for demonstration." + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "import vineyard\n", + "import subprocess as sp\n", + "\n", + "# launch local vineyardd\n", + "client = vineyard.connect()\n", + "\n", + "# launch dask scheduler and worker\n", + "dask_scheduler = sp.Popen(['dask-scheduler', '--host', 'localhost'])\n", + "dask_worker = sp.Popen(['dask-worker', 'tcp://localhost:8786'])" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Preprocessing the data\n", + "----------------------\n", + "\n", + "To read the data, we replace\n", + "**pd.read_csv** by **dd.read_csv**, which will automatically\n", + "read the data in parallel." + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "import dask.dataframe as dd\n", + "raw_data = dd.read_csv('covtype.data', header=None)" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Then we preprocess the data using the same code from the example,\n", + "except the replacement of **pd.concat** to **dd.concat** only." + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "\"\"\"\n", + "The two categorical features in the dataset are binary-encoded.\n", + "We will convert this dataset representation to the typical representation, where each\n", + "categorical feature is represented as a single integer value.\n", + "\"\"\"\n", + "import warnings\n", + "warnings.filterwarnings('ignore')\n", + "\n", + "soil_type_values = [f\"soil_type_{idx+1}\" for idx in range(40)]\n", + "wilderness_area_values = [f\"area_type_{idx+1}\" for idx in range(4)]\n", + "\n", + "soil_type = raw_data.loc[:, 14:53].apply(\n", + " lambda x: soil_type_values[0::1][x.to_numpy().nonzero()[0][0]], axis=1\n", + ")\n", + "wilderness_area = raw_data.loc[:, 10:13].apply(\n", + " lambda x: wilderness_area_values[0::1][x.to_numpy().nonzero()[0][0]], axis=1\n", + ")\n", + "\n", + "CSV_HEADER = [\n", + " \"Elevation\",\n", + " \"Aspect\",\n", + " \"Slope\",\n", + " \"Horizontal_Distance_To_Hydrology\",\n", + " \"Vertical_Distance_To_Hydrology\",\n", + " \"Horizontal_Distance_To_Roadways\",\n", + " \"Hillshade_9am\",\n", + " \"Hillshade_Noon\",\n", + " \"Hillshade_3pm\",\n", + " \"Horizontal_Distance_To_Fire_Points\",\n", + " \"Wilderness_Area\",\n", + " \"Soil_Type\",\n", + " \"Cover_Type\",\n", + "]\n", + "\n", + "data = dd.concat(\n", + " [raw_data.loc[:, 0:9], wilderness_area, soil_type, raw_data.loc[:, 54]],\n", + " axis=1,\n", + " ignore_index=True,\n", + ")\n", + "data.columns = CSV_HEADER\n", + "\n", + "# Convert the target label indices into a range from 0 to 6 (there are 7 labels in total).\n", + "data[\"Cover_Type\"] = data[\"Cover_Type\"] - 1" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Finally, instead of saving the preprocessed data into files, we store them in Vineyard.\n" + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "import vineyard\n", + "from vineyard.core.builder import builder_context\n", + "from vineyard.contrib.dask.dask import register_dask_types\n", + "\n", + "with builder_context() as builder:\n", + " register_dask_types(builder, None) # register dask builders\n", + " gdf_id = client.put(data, dask_scheduler='tcp://localhost:8786')\n", + " print(gdf_id)" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We saved the preprocessed data as a global dataframe\n", + "in Vineyard with the ObjectID above." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Training the model\n", + "------------------\n", + "\n", + "In the single machine solution from the example. A **get_dataset_from_csv** function \n", + "is defined to load the dataset from the files of the preprocessed data as follows:\n", + "```python\n", + "def get_dataset_from_csv(csv_file_path, batch_size, shuffle=False):\n", + "\n", + " dataset = tf.data.experimental.make_csv_dataset(\n", + " csv_file_path,\n", + " batch_size=batch_size,\n", + " column_names=CSV_HEADER,\n", + " column_defaults=COLUMN_DEFAULTS,\n", + " label_name=TARGET_FEATURE_NAME,\n", + " num_epochs=1,\n", + " header=True,\n", + " shuffle=shuffle,\n", + " )\n", + " return dataset.cache()\n", + "```\n", + "while in the training procedure, it loads the train_dataset and test_dataset\n", + "separately from two files as:\n", + "```python\n", + "def run_experiment(model):\n", + "\n", + " model.compile(\n", + " optimizer=keras.optimizers.Adam(learning_rate=learning_rate),\n", + " loss=keras.losses.SparseCategoricalCrossentropy(),\n", + " metrics=[keras.metrics.SparseCategoricalAccuracy()],\n", + " )\n", + "\n", + " train_dataset = get_dataset_from_csv(train_data_file, batch_size, shuffle=True)\n", + "\n", + " test_dataset = get_dataset_from_csv(test_data_file, batch_size)\n", + "\n", + " print(\"Start training the model...\")\n", + " history = model.fit(train_dataset, epochs=num_epochs)\n", + " print(\"Model training finished\")\n", + "\n", + " _, accuracy = model.evaluate(test_dataset, verbose=0)\n", + "\n", + " print(f\"Test accuracy: {round(accuracy * 100, 2)}%\")\n", + "```\n", + "In our solution, we provide a function to load dataset from the global dataframe\n", + "generated in the last step." + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "from vineyard.core.resolver import resolver_context\n", + "from vineyard.contrib.ml.tensorflow import register_tf_types\n", + "\n", + "def get_dataset_from_vineyard(object_id, batch_size, shuffle=False):\n", + " with resolver_context() as resolver:\n", + " register_tf_types(None, resolver) # register tf resolvers\n", + " ds = vineyard.connect().get(object_id, label=TARGET_FEATURE_NAME) # specify the label column\n", + "\n", + " if shuffle:\n", + " ds = ds.shuffle(len(ds))\n", + "\n", + " len_test = int(len(ds) * 0.15)\n", + " test_dataset = ds.take(len_test).batch(batch_size)\n", + " train_dataset = ds.skip(len_test).batch(batch_size)\n", + "\n", + " return train_dataset, test_dataset" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "And modify the training procedure with a few lines of horovod code." + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "import horovod.keras as hvd\n", + "\n", + "def run_experiment(model):\n", + "\n", + " hvd.init()\n", + "\n", + " model.compile(\n", + " optimizer=hvd.DistributedOptimizer(keras.optimizers.Adam(learning_rate=learning_rate)),\n", + " loss=keras.losses.SparseCategoricalCrossentropy(),\n", + " metrics=[keras.metrics.SparseCategoricalAccuracy()],\n", + " )\n", + "\n", + " callbacks = [\n", + " # Horovod: broadcast initial variable states from rank 0 to all other processes.\n", + " # This is necessary to ensure consistent initialization of all workers when\n", + " # training is started with random weights or restored from a checkpoint.\n", + " hvd.callbacks.BroadcastGlobalVariablesCallback(0),\n", + " ]\n", + "\n", + " train_dataset, test_dataset = get_dataset_from_vineyard(gdf_id, batch_size, shuffle=True)\n", + "\n", + " print(\"Start training the model...\")\n", + " history = model.fit(train_dataset, epochs=num_epochs, callbacks=callbacks)\n", + " print(\"Model training finished\")\n", + "\n", + " _, accuracy = model.evaluate(test_dataset, verbose=0)\n", + "\n", + " print(f\"Test accuracy: {round(accuracy * 100, 2)}%\")" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "All the other parts of training procedure are the same as the single machine solution." + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "TARGET_FEATURE_NAME = \"Cover_Type\"\n", + "\n", + "TARGET_FEATURE_LABELS = [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\"]\n", + "\n", + "NUMERIC_FEATURE_NAMES = [\n", + " \"Aspect\",\n", + " \"Elevation\",\n", + " \"Hillshade_3pm\",\n", + " \"Hillshade_9am\",\n", + " \"Hillshade_Noon\",\n", + " \"Horizontal_Distance_To_Fire_Points\",\n", + " \"Horizontal_Distance_To_Hydrology\",\n", + " \"Horizontal_Distance_To_Roadways\",\n", + " \"Slope\",\n", + " \"Vertical_Distance_To_Hydrology\",\n", + "]\n", + "\n", + "CATEGORICAL_FEATURES_WITH_VOCABULARY = {\n", + " \"Soil_Type\": soil_type_values,\n", + " \"Wilderness_Area\": wilderness_area_values,\n", + "}\n", + "\n", + "CATEGORICAL_FEATURE_NAMES = list(CATEGORICAL_FEATURES_WITH_VOCABULARY.keys())\n", + "\n", + "FEATURE_NAMES = NUMERIC_FEATURE_NAMES + CATEGORICAL_FEATURE_NAMES\n", + "\n", + "NUM_CLASSES = len(TARGET_FEATURE_LABELS)\n", + "\n", + "learning_rate = 0.001\n", + "dropout_rate = 0.1\n", + "batch_size = 265\n", + "num_epochs = 5\n", + "\n", + "hidden_units = [32, 32]\n", + "\n", + "\"\"\"\n", + "## Create model inputs\n", + "Now, define the inputs for the models as a dictionary, where the key is the feature name,\n", + "and the value is a `keras.layers.Input` tensor with the corresponding feature shape\n", + "and data type.\n", + "\"\"\"\n", + "import tensorflow as tf\n", + "\n", + "def create_model_inputs():\n", + " inputs = {}\n", + " for feature_name in FEATURE_NAMES:\n", + " if feature_name in NUMERIC_FEATURE_NAMES:\n", + " inputs[feature_name] = layers.Input(\n", + " name=feature_name, shape=(), dtype=tf.float32\n", + " )\n", + " else:\n", + " inputs[feature_name] = layers.Input(\n", + " name=feature_name, shape=(), dtype=tf.string\n", + " )\n", + " return inputs\n", + "\n", + "\n", + "\"\"\"\n", + "## Encode features\n", + "We create two representations of our input features: sparse and dense:\n", + "1. In the **sparse** representation, the categorical features are encoded with one-hot\n", + "encoding using the `CategoryEncoding` layer. This representation can be useful for the\n", + "model to *memorize* particular feature values to make certain predictions.\n", + "2. In the **dense** representation, the categorical features are encoded with\n", + "low-dimensional embeddings using the `Embedding` layer. This representation helps\n", + "the model to *generalize* well to unseen feature combinations.\n", + "\"\"\"\n", + "\n", + "\n", + "from tensorflow.keras.layers import StringLookup\n", + "\n", + "\n", + "def encode_inputs(inputs, use_embedding=False):\n", + " encoded_features = []\n", + " for feature_name in inputs:\n", + " if feature_name in CATEGORICAL_FEATURE_NAMES:\n", + " vocabulary = CATEGORICAL_FEATURES_WITH_VOCABULARY[feature_name]\n", + " # Create a lookup to convert string values to an integer indices.\n", + " # Since we are not using a mask token nor expecting any out of vocabulary\n", + " # (oov) token, we set mask_token to None and num_oov_indices to 0.\n", + " lookup = StringLookup(\n", + " vocabulary=vocabulary,\n", + " mask_token=None,\n", + " num_oov_indices=0,\n", + " output_mode=\"int\" if use_embedding else \"binary\",\n", + " )\n", + " if use_embedding:\n", + " # Convert the string input values into integer indices.\n", + " encoded_feature = lookup(inputs[feature_name])\n", + " embedding_dims = int(math.sqrt(len(vocabulary)))\n", + " # Create an embedding layer with the specified dimensions.\n", + " embedding = layers.Embedding(\n", + " input_dim=len(vocabulary), output_dim=embedding_dims\n", + " )\n", + " # Convert the index values to embedding representations.\n", + " encoded_feature = embedding(encoded_feature)\n", + " else:\n", + " # Convert the string input values into a one hot encoding.\n", + " encoded_feature = lookup(tf.expand_dims(inputs[feature_name], -1))\n", + " else:\n", + " # Use the numerical features as-is.\n", + " encoded_feature = tf.expand_dims(inputs[feature_name], -1)\n", + "\n", + " encoded_features.append(encoded_feature)\n", + "\n", + " all_features = layers.concatenate(encoded_features)\n", + " return all_features\n", + "\n", + "\n", + "\"\"\"\n", + "## Experiment 1: a baseline model\n", + "In the first experiment, let's create a multi-layer feed-forward network,\n", + "where the categorical features are one-hot encoded.\n", + "\"\"\"\n", + "from tensorflow import keras\n", + "from tensorflow.keras import layers\n", + "\n", + "def create_baseline_model():\n", + " inputs = create_model_inputs()\n", + " features = encode_inputs(inputs)\n", + "\n", + " for units in hidden_units:\n", + " features = layers.Dense(units)(features)\n", + " features = layers.BatchNormalization()(features)\n", + " features = layers.ReLU()(features)\n", + " features = layers.Dropout(dropout_rate)(features)\n", + "\n", + " outputs = layers.Dense(units=NUM_CLASSES, activation=\"softmax\")(features)\n", + " model = keras.Model(inputs=inputs, outputs=outputs)\n", + " return model\n", + "\n", + "\n", + "baseline_model = create_baseline_model()" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Let's run it:" + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "run_experiment(baseline_model)" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We clear the environments in the end." + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "dask_worker.terminate()\n", + "dask_scheduler.terminate()\n", + "\n", + "vineyard.shutdown()" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Finally, we can use **horovodrun** to run the above code distributedly in a cluster for distributed learning on big datasets." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Conclusion\n", + "----------\n", + "\n", + "From this example, we can see that with the help of Vineyard, users can easily extend\n", + "their single machine solutions to distributed learning using dedicated systems without\n", + "worrying about the cross-system data sharing issues." + ], + "metadata": {} + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/_sources/tutorials/data-processing/gpu-memory-sharing.rst.txt b/_sources/tutorials/data-processing/gpu-memory-sharing.rst.txt new file mode 100644 index 0000000000..39ed8e06d9 --- /dev/null +++ b/_sources/tutorials/data-processing/gpu-memory-sharing.rst.txt @@ -0,0 +1,113 @@ +.. _gpu-memory-sharing: + +Sharing GPU Memory +------------------ + +Vineyard supports sharing both CPU memory and GPU memory between different +processes and different compute engines. The sharing of GPU memory is archived +by using the `CUDA IPC mechanism `_ +and provides a flexible unified memory interfaces. + +CUDA IPC and Unified Memory +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The CUDA IPC memory handle allows GPU memory to be shared between different +processes via IPC. In vineyard, the GPU memory is allocated by the vineyardd +instance when :code:`CreateGPUBuffer()`, then an IPC handle is transferred to the +client process and the GPU memory can be accessed by the client process after +calling :code:`cudaIpcOpenMemHandle()`. For readers, the GPU memory can be accessed +like a normal CPU shared memory object with :code:`GetGPUBuffers()`. + +Like `CUDA unified memory `_, +vineyard's provides a unified memory interface which can be adapted to different +kinds of implementation (GPU, PPU, etc.) as the abstraction to share GPU memory +between different processes, as well as sharing memory between the host and +device. + +The unified memory abstraction is able to automatically synchronize the memory +between host and devices by leverage the RAII mechanism of C++. + +Example +~~~~~~~ + +.. note:: + + The GPU shared memory is still under development and the APIs may change in + the future. + +- Creating a GPU buffer: + + .. code:: c++ + + ObjectID object_id; + Payload object; + std::shared_ptr buffer = nullptr; + RETURN_ON_ERROR(client.CreateGPUBuffer(data_size(), object_id, object, buffer)); + + CHECK(!buffer->is_cpu()); + CHECK(buffer->is_mutable()); + + The result buffer's data :code:`buffer->mutable_data()` is a GPU memory pointer, + which can be directly passed to GPU kernels, e.g., + + .. code:: c++ + + printKernel<<<1, 1>>>(buffer->data()); + +- Composing the buffer content from host code like Unified Memory: + + .. code:: c++ + + { + CUDABufferMirror mirror(*buffer, false); + memcpy(mirror.mutable_data(), "hello world", 12); + } + + Here the :code:`mirror`'s :code:`data()` and :code:`mutable_data()` are host memory pointers + allocated using the :code:`cudaHostAlloc()` API. When :code:`CUDABufferMirror` destructing, + the host memory will be copied back to the GPU memory automatically. + + The second argument of :code:`CUDABufferMirror` indicates whether the initial memory of the + GPU buffer needs to be copied to the host memory. Defaults to :code:`false`. + +- Accessing the GPU buffer from another process: + + .. code:: c++ + + ObjectID object_id = ...; + std::shared_ptr buffer = nullptr; + RETURN_ON_ERROR(client.GetGPUBuffer(object_id, true, buffer)); + CHECK(!buffer->is_cpu()); + CHECK(!buffer->is_mutable()); + + The result buffer's data :code:`buffer->data()` is a GPU memory pointer, which can be directly + passed to GPU kernels, e.g., + + .. code:: c++ + + printKernel<<<1, 1>>>(buffer->data()); + +- Accessing the shared GPU buffer from CPU: + + .. code:: c++ + + { + CUDABufferMirror mirror(*buffer, true); + printf("CPU data from GPU is: %s\n", + reinterpret_cast(mirror.data())); + } + + Using the :code:`CUDABufferMirror` to access the GPU buffer from CPU, the mirror's :code:`data()` + is a host memory pointer allocated using the :code:`cudaHostAlloc()` API. For immutable :code:`Buffer`, + the second argument of :code:`CUDABufferMirror` must be :code:`true`, and the GPU memory will be + copied to the host memory when the mirror is constructed. + +- Freeing the shared GPU buffer: + + .. code:: c++ + + ObjectID object_id = ...; + RETURN_ON_ERROR(client.DelData(object_id)); + +For complete example about GPU memory sharing, please refer to +`gpumalloc_test.cu `_ diff --git a/_sources/tutorials/data-processing/python-sharedmemory.rst.txt b/_sources/tutorials/data-processing/python-sharedmemory.rst.txt new file mode 100644 index 0000000000..4ff434e3c7 --- /dev/null +++ b/_sources/tutorials/data-processing/python-sharedmemory.rst.txt @@ -0,0 +1,42 @@ +:code:`multiprocessing.shared_memory` in Python +=============================================== + +Vineyard offers a shared memory interface through :class:`SharedMemory` and +:class:`ShareableList` classes, ensuring compatibility with Python's `multiprocessing.shared_memory`_. + +Utilize the shared memory interface as demonstrated below: + +.. code:: python + + >>> from vineyard import shared_memory + >>> value = shared_memory.ShareableList(client, [b"a", "bb", 1234, 56.78, True]) + >>> value + ShareableList([b'a', 'bb', 1234, 56.78, True], name='o8000000119aa10c0') + >>> value[4] = False + >>> value + ShareableList([b'a', 'bb', 1234, 56.78, False], name='o8000000119aa10c0') + +.. caution:: + + Please be aware that the semantics of Vineyard's :code:`shared_memory` differ slightly + from those of Python's multiprocessing module's :code:`shared_memory`. In Vineyard, + shared memory cannot be modified once it becomes visible to other clients. + +We have added a :code:`freeze` method to make such transformation happen: + +.. code:: python + + >>> value.freeze() + +After being frozen, the shared memory (aka. the :code:`ShareableList` in this case) +is available for other clients: + +.. code:: python + + >>> value1 = shared_memory.ShareableList(client, name=value.shm.name) + >>> value1 + ShareableList([b'a', 'bb', 1234, 56.78, False], name='o8000000119aa10c0') + +For more details, see :ref:`shared-memory`. + +.. _multiprocessing.shared_memory: https://docs.python.org/3/library/multiprocessing.shared_memory.html diff --git a/_sources/tutorials/data-processing/using-objects-python.rst.txt b/_sources/tutorials/data-processing/using-objects-python.rst.txt new file mode 100644 index 0000000000..4b41e053f1 --- /dev/null +++ b/_sources/tutorials/data-processing/using-objects-python.rst.txt @@ -0,0 +1,87 @@ +.. _using-objects-python: + +Sharing Python Objects with Vineyard +------------------------------------ + +As discussed in :ref:`vineyard-objects`, each object in Vineyard consists of two parts: + +1. The data payload, which is stored locally in the corresponding Vineyard instance +2. The hierarchical metadata, which is shared across the entire Vineyard cluster + +Specifically, a ``Blob`` represents the unit where the data payload resides within a +Vineyard instance. A blob object holds a segment of memory in the bulk store of the +Vineyard instance, allowing users to save their local buffer into a blob and later +retrieve the blob in another process using a zero-copy approach through memory mapping. + +.. code:: python + + >>> payload = b"Hello, World!" + >>> blob_id = client.put(payload) + >>> blob = client.get_object(blob_id) + >>> print(blob.typename, blob.size, blob) + +.. code:: console + + vineyard::Blob 28 Object <"o800000011cfa7040": vineyard::Blob> + +On the other hand, the hierarchical metadata of Vineyard objects is shared across +the entire cluster. In the following example, for the sake of simplicity, we +launch a Vineyard cluster consisting of two Vineyard instances on the same machine. +However, in real-world scenarios, these Vineyard instances would be distributed +across multiple machines within the cluster. + +.. code:: console + + $ python3 -m vineyard --socket /var/run/vineyard.sock1 + $ python3 -m vineyard --socket /var/run/vineyard.sock2 + +With this setup, we can create a distributed pair of arrays in Vineyard, where +the first array is stored in the first Vineyard instance listening to the IPC socket +``/var/run/vineyard.sock1``, and the second array is stored in the second instance +listening to the IPC socket ``/var/run/vineyard.sock2``. + +.. code:: python + + >>> import numpy as np + >>> import vineyard + >>> import vineyard.data.tensor + + >>> # build the first array in the first vineyard instance + >>> client1 = vineyard.connect('/var/run/vineyard.sock1') + >>> id1 = client1.put(np.zeros(8)) + >>> # persist the object to make it visible to form the global object + >>> client1.persist(id1) + + >>> # build the second array in the second vineyard instance + >>> client2 = vineyard.connect('/var/run/vineyard.sock2') + >>> id2 = client2.put(np.ones(4)) + >>> # persist the object to make it visible to form the global object + >>> client2.persist(id2) + + >>> # build the pair from client1 + >>> obj1 = client1.get_object(id1) + >>> obj2 = client2.get_object(id2) + >>> id_pair = client1.put((obj1, obj2)) + + >>> # get the pair object from client2 + >>> obj_pair = client2.get_object(id_pair) + >>> print(obj_pair.first.typename, obj_pair.first.size(), obj_pair.second.size()) + +.. code:: console + + vineyard::Array 8 4 + +.. code:: console + + >>> # get the pair value from client2 + >>> value_pair = client2.get(id_pair) + >>> print(value_pair) + +.. code:: console + + (None, [1, 1, 1, 1]) + +In this example, we can access the metadata of the pair object from ``client2`` +even though it was created by ``client1``. However, we cannot retrieve the payload +of the first element of the pair from ``client2`` because it is stored locally +in the first Vineyard instance. diff --git a/_sources/tutorials/extending.rst.txt b/_sources/tutorials/extending.rst.txt new file mode 100644 index 0000000000..4eab2b7188 --- /dev/null +++ b/_sources/tutorials/extending.rst.txt @@ -0,0 +1,37 @@ + +Extending vineyard +================== + +.. toctree:: + :maxdepth: 1 + :caption: TOC + :hidden: + + ./extending/define-datatypes-python.rst + ./extending/define-datatypes-cpp.rst + +Vineyard offers a collection of efficient data structures tailored for data-intensive tasks, +such as tensors, data frames, tables, and graphs. These data types can be easily extended +to accommodate custom requirements. By registering user-defined types in the vineyard type +registry, computing engines built on top of vineyard can instantly leverage the advantages +provided by these custom data structures. + +.. panels:: + :header: text-center + :column: col-lg-12 p-2 + + .. link-button:: ./extending/define-datatypes-python + :type: ref + :text: Define Python Types + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Craft builders and resolvers for custom Python data types. + + --- + + .. link-button:: ./extending/define-datatypes-cpp + :type: ref + :text: Define C++ Types + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Implement and register custom data types in C++ for seamless integration with vineyard's ecosystem. diff --git a/_sources/tutorials/extending/define-datatypes-cpp.rst.txt b/_sources/tutorials/extending/define-datatypes-cpp.rst.txt new file mode 100644 index 0000000000..2d2b0b1aa6 --- /dev/null +++ b/_sources/tutorials/extending/define-datatypes-cpp.rst.txt @@ -0,0 +1,312 @@ +.. _define-cpp-types: + +Defining Custom Data Types in C++ +================================= + +Vineyard provides an extensive set of efficient built-in data types in +its C++ SDK, such as :code:`Vector`, :code:`HashMap`, :code:`Tensor`, +:code:`DataFrame`, :code:`Table`, and :code:`Graph` (refer to :ref:`cpp-api`). +However, there may be situations where users need to develop their +own data structures and share the data efficiently with Vineyard. This +step-by-step tutorial guides you through the process of adding custom +C++ data types with ease. + +.. note:: + + This tutorial includes code snippets that could be auto-generated to + provide a clear understanding of the design internals and to help + developers grasp the overall functionality of the Vineyard client. + +``Object`` and ``ObjectBuilder`` +-------------------------------- + +Vineyard has a base class :code:`vineyard::Objects`, and a corresponding +base class :code:`Vineyard::ObjectBuilder` for builders as follows, + +.. code:: cpp + + class Object { + public: + static std::unique_ptr Create() { + ... + } + + virtual void Construct(const ObjectMeta& meta); + } + +and the builder + +.. code:: cpp + + class ObjectBuilder { + virtual Status Build(Client& client) override = 0; + + virtual std::shared_ptr _Seal(Client& client) = 0; + } + +Where the object is the base class for user-defined data types, and the +builders is responsible for placing the data into vineyard. + +Defining Your Custom Type +------------------------- + +Let's take the example of defining a custom `Vector` type. Essentially, +a `Vector` consists of a `vineyard::Blob` as its payload, along with +metadata such as `dtype` and `size`. + +The class definition for the `Vector` type typically appears as follows: + +.. code:: cpp + + template + class Vector { + private: + size_t size; + const T *data = nullptr; + public: + Vector(): size(0), data(nullptr) { + } + + Vector(const int size, const T *data): size(size), data(data) { + } + + size_t length() const { + return size; + } + + const T& operator[](size_t index) { + assert(index < size); + return data[index]; + } + }; + +Registering C++ Types +--------------------- + +First, we need to adapt the existing :code:`Vector` to become a Vineyard +:code:`Object`, + +.. code:: diff + + template + -class Vector { + +class Vector: public vineyard::Registered> { + private: + size_t size; + T *data = nullptr; + public: + + static std::unique_ptr Create() __attribute__((used)) { + + return std::static_pointer_cast( + + std::unique_ptr>{ + + new Vector()}); + + } + + + Vector(): size(0), data(nullptr) { + } + + Vector(const int size, const T *data): size(size), data(data) { + } + + ... + } + +Observe the two key modifications above: + ++ Inheriting from :code:`vineyard::Registered>`: + + :code:`vineyard::Registered` serves as a helper to generate static + initialization stubs, registering the data type :code:`T` with the type + resolving factory and associating the type :code:`T` with its typename. + The typename is an auto-generated, human-readable name for C++ types, e.g., + :code:`"Vector"` for :code:`Vector`. + ++ Implementing the zero-parameter static constructor :code:`Create()`: + + :code:`Create()` is a static function registered with the + resolving factory by the helper :code:`vineyard::Registered`. It is + used to construct an instance of type :code:`T` when retrieving objects + from Vineyard. + + The Vineyard client locates the static constructor using the :code:`typename` + found in the metadata of Vineyard objects stored in the daemon server. + +To retrieve the object :code:`Vector` from Vineyard's metadata, we need to +implement a `Construct` method as well. The :code:`Construct` method takes +a :code:`vineyard::ObjectMeta` as input and extracts metadata and +members from it to populate its own data members. The memory in the member +:code:`buffer` (a :code:`vineyard::Blob`) is shared using memory mapping, +eliminating the need for copying. + +.. code:: diff + + template + class Vector: public vineyard::Registered> { + public: + ... + + + void Construct(const ObjectMeta& meta) override { + + this->size = meta.GetKeyValue("size"); + + + + auto buffer = std::dynamic_pointer_cast(meta.GetMember("buffer")); + + this->data = reinterpret_cast(buffer->data()); + + } + + + ... + } + +Builder +------- + +Moving on to the builder section, the :code:`vineyard::ObjectBuilder` consists of two parts: + ++ :code:`Build()`: This method is responsible for storing the blobs of custom data + structures into Vineyard. + ++ :code:`_Seal()`: This method is responsible for generating the corresponding metadata + and inserting the metadata into Vineyard. + +For our :code:`Vector` type, let's first define a general vector builder: + +.. code:: cpp + + template + class VectorBuilder { + private: + std::unique_ptr buffer_builder; + std::size_t size; + T *data; + + public: + VectorBuilder(size_t size): size(size) { + data = static_cast(malloc(sizeof(T) * size)); + } + + T& operator[](size_t index) { + assert(index < size); + return data[index]; + } + }; + +The builder allocates the necessary memory based on the specified :code:`size` to accommodate +the elements and provides a `[]` operator to populate the data. + +Next, we adapt the above builder as a `ObjectBuilder` in Vineyard, + +.. code:: diff + + template + -class VectorBuilder { + +class VectorBuilder: public vineyard::ObjectBuilder { + private: + std::unique_ptr buffer_builder; + std::size_t size; + T *data; + + public: + VectorBuilder(size_t size): size(size) { + data = static_cast(malloc(sizeof(T) * size)); + } + + + Status Build(Client& client) override { + + RETURN_ON_ERROR(client.CreateBlob(size * sizeof(T), buffer_builder)); + + memcpy(buffer_builder->data(), data, size * sizeof(T)); + + return Status::OK(); + + } + + + + Status _Seal(Client& client, std::shared_ptr &object) override { + + RETURN_ON_ERROR(this->Build(client)); + + + + auto vec = std::make_shared>(); + object = vec; + + std::shared_ptr buffer_object; + + RETURN_ON_ERROR(this->buffer_builder->Seal(client, buffer_object)); + + auto buffer = std::dynamic_pointer_cast(buffer_object); + + vec->size = size; + + vec->data = reinterpret_cast(buffer->data()); + + + + vec->meta_.SetTypeName(vineyard::type_name>()); + + vec->meta_.SetNBytes(size * sizeof(T)); + + vec->meta_.AddKeyValue("size", size); + + vec->meta_.AddMember("buffer", buffer); + + return client.CreateMetaData(vec->meta_, vec->id_); + + } + + + T& operator[](size_t index) { + assert(index < size); + return data[index]; + } + }; + +To access private member fields and methods, the builder may need to be +added as a friend class of the original type declaration. + +.. note:: + + Since the builder requires direct access to the private data members of + :code:`Vector`, it is necessary to declare the builder as a friend class + of our vector type, + +.. code:: diff + + template + class Vector: public vineyard::Registered> { + + const T& operator[](size_t index) { + assert(index < size); + return data[index]; + } + + + + friend class VectorBuilder; + }; + +In the example above, you may notice that the builder and constructor contain numerous +boilerplate snippets. These can be auto-generated based on the layout of the class +:code:`Vector` through static analysis of the user's source code, streamlining +the process and enhancing readability. + +Utilizing Custom Data Types with Vineyard +----------------------------------------- + +At this point, we have successfully defined our custom data types and integrated them +with Vineyard. Now, we can demonstrate how to build these custom data types using the +Vineyard client and retrieve them for further processing. + +.. code:: cpp + + int main(int argc, char** argv) { + std::string ipc_socket = std::string(argv[1]); + + Client client; + VINEYARD_CHECK_OK(client.Connect(ipc_socket)); + LOG(INFO) << "Connected to IPCServer: " << ipc_socket; + + auto builder = VectorBuilder(3); + builder[0] = 1; + builder[1] = 2; + builder[2] = 3; + auto result = builder.Seal(client); + + auto vec = std::dynamic_pointer_cast>(client.GetObject(result->id())); + for (size_t index = 0; index < vec->length(); ++index) { + std::cout << "element at " << index << " is: " << (*vec)[index] << std::endl; + } + } + +Cross-Language Compatibility +---------------------------- + +Vineyard maintains consistent design principles across SDKs in various languages, +such as Java and Python. For an example of Vineyard objects and their builders in +Python, please refer to :ref:`builder-resolver`. + +As demonstrated in the example above, there is a significant amount of boilerplate +code involved in defining constructors and builders. To simplify the integration +with Vineyard, we are developing a code generator that will automatically produce +SDKs in different languages based on a C++-like Domain Specific Language (DSL). +Stay tuned for updates! + +For a sneak peek at how the code generator works, please refer to `array.vineyard-mod`_ +and `arrow.vineyard-mod`_. + +.. _array.vineyard-mod: https://github.com/v6d-io/v6d/blob/main/modules/basic/ds/array.vineyard-mod +.. _arrow.vineyard-mod: https://github.com/v6d-io/v6d/blob/main/modules/basic/ds/arrow.vineyard-mod diff --git a/_sources/tutorials/extending/define-datatypes-python.rst.txt b/_sources/tutorials/extending/define-datatypes-python.rst.txt new file mode 100644 index 0000000000..86045f9acc --- /dev/null +++ b/_sources/tutorials/extending/define-datatypes-python.rst.txt @@ -0,0 +1,169 @@ +.. _define-python-types: + +Define Data Types in Python +--------------------------- + +Objects +^^^^^^^ + +As discussed in :ref:`vineyard-objects`, each object in vineyard comprises two components: + +1. The data payload, which is stored locally within the corresponding vineyard instance +2. The hierarchical meta data, which is shared across the entire vineyard cluster + +Specifically, a ``Blob`` represents the unit where the data payload resides in a vineyard +instance. A blob object contains a segment of memory in the bulk store of the vineyard +instance, allowing users to save their local buffer into a blob and later retrieve the +blob in another process using a zero-copy approach through memory mapping. + +.. code:: python + + >>> payload = b"Hello, World!" + >>> blob_id = client.put(payload) + >>> blob = client.get_object(blob_id) + >>> print(blob.typename, blob.size, blob) + +.. code:: console + + vineyard::Blob 28 Object <"o800000011cfa7040": vineyard::Blob> + +On the other hand, vineyard objects' hierarchical meta data is shared across the entire +cluster. In the following example, for the sake of simplicity, we will launch a vineyard +cluster with two vineyard instances on the same machine. However, in real-world scenarios, +these vineyard instances would typically be distributed across multiple machines within +the cluster. + +.. code:: console + + $ python3 -m vineyard --socket /var/run/vineyard.sock1 + $ python3 -m vineyard --socket /var/run/vineyard.sock2 + +With this setup, we can create a distributed pair of arrays in vineyard, where the first +array is stored in the first vineyard instance (listening to ipc_socket at `/var/run/vineyard.sock1`), +and the second array is stored in the second instance (listening to ipc_socket at +`/var/run/vineyard.sock2`). + +.. code:: python + + >>> import numpy as np + >>> import vineyard + >>> import vineyard.data.tensor + + >>> # build the first array in the first vineyard instance + >>> client1 = vineyard.connect('/var/run/vineyard.sock1') + >>> id1 = client1.put(np.zeros(8)) + >>> # persist the object to make it visible to form the global object + >>> client1.persist(id1) + + >>> # build the second array in the second vineyard instance + >>> client2 = vineyard.connect('/var/run/vineyard.sock2') + >>> id2 = client2.put(np.ones(4)) + >>> # persist the object to make it visible to form the global object + >>> client2.persist(id2) + + >>> # build the pair from client1 + >>> obj1 = client1.get_object(id1) + >>> obj2 = client2.get_object(id2) + >>> id_pair = client1.put((obj1, obj2)) + + >>> # get the pair object from client2 + >>> obj_pair = client2.get_object(id_pair) + >>> print(obj_pair.first.typename, obj_pair.first.size(), obj_pair.second.size()) + +.. code:: console + + vineyard::Array 8 4 + +.. code:: console + + >>> # get the pair value from client2 + >>> value_pair = client2.get(id_pair) + >>> print(value_pair) + +.. code:: console + + (None, [1, 1, 1, 1]) + +In this example, we can access the metadata of the pair object from `client2` even +though it was created by `client1`. However, we cannot retrieve the payload of the +first element of the pair from `client2`, as it is stored locally within the first +vineyard instance. + +Creating Builders and Resolvers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As demonstrated in :ref:`builder-resolver`, vineyard enables users to register +builders and resolvers for constructing and resolving vineyard objects from/to +client-side data types based on specific computational requirements. + +For instance, if we use ``pyarrow`` types in our context, we can define the builder and +resolver for the conversion between ``vineyard::NumericArray`` and ``pyarrow.NumericArray`` +as follows: + +.. code:: python + + >>> def numeric_array_builder(client, array, builder): + >>> meta = ObjectMeta() + >>> meta['typename'] = 'vineyard::NumericArray<%s>' % array.type + >>> meta['length_'] = len(array) + >>> meta['null_count_'] = array.null_count + >>> meta['offset_'] = array.offset + >>> + >>> null_bitmap = buffer_builder(client, array.buffers()[0], builder) + >>> buffer = buffer_builder(client, array.buffers()[1], builder) + >>> + >>> meta.add_member('buffer_', buffer) + >>> meta.add_member('null_bitmap_', null_bitmap) + >>> meta['nbytes'] = array.nbytes + >>> return client.create_metadata(meta) + + >>> def numeric_array_resolver(obj): + >>> meta = obj.meta + >>> typename = obj.typename + >>> value_type = normalize_dtype(re.match(r'vineyard::NumericArray<([^>]+)>', typename).groups()[0]) + >>> dtype = pa.from_numpy_dtype(value_type) + >>> buffer = as_arrow_buffer(obj.member('buffer_')) + >>> null_bitmap = as_arrow_buffer(obj.member('null_bitmap_')) + >>> length = int(meta['length_']) + >>> null_count = int(meta['null_count_']) + >>> offset = int(meta['offset_']) + >>> return pa.lib.Array.from_buffers(dtype, length, [null_bitmap, buffer], null_count, offset) + +Finally, we register the builder and resolver for automatic building and resolving: +.. code:: python + + >>> builder_ctx.register(pa.NumericArray, numeric_array_builder) + >>> resolver_ctx.register('vineyard::NumericArray', numeric_array_resolver) + +In some cases, we may have multiple resolvers or builders for a specific type. +For instance, the `vineyard::Tensor` object can be resolved as either `numpy.ndarray` or +`xgboost::DMatrix`. To accommodate this, we could have: + +.. code:: python + + >>> resolver_ctx.register('vineyard::Tensor', numpy_resolver) + >>> resolver_ctx.register('vineyard::Tensor', xgboost_resolver) + +This flexibility enables seamless integration with various libraries and frameworks by +effectively handling different data types and their corresponding resolvers or builders. + +.. code:: python + + def xgboost_resolver(obj): + ... + + default_resolver_context.register('vineyard::Tensor', xgboost_resolver) + +at the same time. The stackable :code:`resolver_context` could help there, + +.. code:: python + + with resolver_context({'vineyard::Tensor', xgboost_resolver}): + ... + +Assuming the default context resolves `vineyard::Tensor` to `numpy.ndarray`, the +`with resolver_context` allows for temporary resolution of `vineyard::Tensor` to +`xgboost::DMatrix`. Upon exiting the context, the global environment reverts to +its default state. + +The `with resolver_context` can be nested for additional flexibility. diff --git a/_sources/tutorials/kubernetes.rst.txt b/_sources/tutorials/kubernetes.rst.txt new file mode 100644 index 0000000000..fa45072068 --- /dev/null +++ b/_sources/tutorials/kubernetes.rst.txt @@ -0,0 +1,39 @@ +Vineyard on Kubernetes +====================== + +.. toctree:: + :maxdepth: 1 + :caption: TOC + :hidden: + + ./kubernetes/using-vineyard-operator.rst + ./kubernetes/ml-pipeline-mars-pytorch.rst + ./kubernetes/data-sharing-with-vineyard-on-kubernetes.rst + ./kubernetes/efficient-data-sharing-in-kubeflow-with-vineyard-csi-driver.rst + ./kubernetes/vineyard-on-fluid.rst + +Vineyard can be seamlessly deployed on Kubernetes, managed by the :ref:`vineyard-operator`, +to enhance big-data workflows through its data-aware scheduling policy. This policy +orchestrates shared objects and routes jobs to where their input data resides. In the +following tutorials, you will learn how to deploy Vineyard and effectively integrate it +with Kubernetes. + +.. panels:: + :header: text-center + :column: col-lg-12 p-2 + + .. link-button:: ./kubernetes/using-vineyard-operator + :type: ref + :text: Vineyard operator + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + The Vineyard operator serves as the central component for seamless integration with Kubernetes. + + --- + + .. link-button:: ./kubernetes/ml-pipeline-mars-pytorch + :type: ref + :text: ML with Vineyard + :classes: btn-block stretched-link + ^^^^^^^^^^^^ + Vineyard functions as an efficient intermediate data storage solution for machine learning pipelines on Kubernetes. diff --git a/_sources/tutorials/kubernetes/data-sharing-with-vineyard-on-kubernetes.rst.txt b/_sources/tutorials/kubernetes/data-sharing-with-vineyard-on-kubernetes.rst.txt new file mode 100644 index 0000000000..6c998a2a2c --- /dev/null +++ b/_sources/tutorials/kubernetes/data-sharing-with-vineyard-on-kubernetes.rst.txt @@ -0,0 +1,269 @@ +Data sharing with Vineyard on Kubernetes +======================================== + +If you want to share data between different workloads(pods or containers) on kubernetes, it's a good idea to +use vineyard as the data-sharing service. In this tutorial, we will show you how to +share data between different containers or pods on kubernetes step by step. + +.. figure:: ../../images/data_sharing_with_sidecar.jpg + :width: 75% + :alt: Data sharing between containers + + Data sharing between containers + +From the above figure, the `vineyardctl inject` command will inject vineyard container into the app pod and +the app containers will connect to the vineyard container to share the vineyard data. + +.. figure:: ../../images/data_sharing_with_deployment.jpg + :width: 75% + :alt: Data sharing on the vineyard deployment + + Data sharing on the vineyard deployment + +From the above figure, the `vineyardctl deploy vineyard-deployment` command will deploy a vineyard deployment +on the kubernetes cluster (default is 3 replicas) and the app pods will be scheduled to the vineyard deployment +to share the vineyard data via the command `vineyardctl schedule workload`. + +Prerequisites +------------- + +- A kubernetes cluster with version >= 1.25.10. +- Install the latest vineyardctl command line tool refer to `vineyardctl installation`_. + +Data sharing between different containers +----------------------------------------- + +In this section, we will show you how to share data between different containers on kubernetes. +Assuming you have a pod with two containers, one is a producer and the other is a consumer. +The producer will generate some data and write it to vineyard, and the consumer will read the data +from vineyard and do some computation. + +Save the following yaml as `pod.yaml`. + +.. code:: yaml + + $ cat << EOF >> pod.yaml + apiVersion: v1 + kind: Pod + metadata: + name: vineyard-producer-consumer + namespace: vineyard-test + spec: + containers: + - name: producer + image: python:3.10 + command: + - bash + - -c + - | + pip install vineyard numpy pandas; + cat << EOF >> producer.py + import vineyard; + import numpy as np; + import pandas as pd; + client = vineyard.connect(); + # put a pandas dataframe to vineyard + client.put(pd.DataFrame(np.random.randn(100, 4), columns=list('ABCD')), persist=True, name="test_dataframe"); + # put a basic data unit to vineyard + client.put((1, 1.2345, 'xxxxabcd'), persist=True, name="test_basic_data_unit"); + client.close() + EOF + python producer.py; + sleep infinity; + - name: consumer + image: python:3.10 + command: + - bash + - -c + - | + # wait for the producer to finish + sleep 10; + pip install vineyard numpy pandas; + cat << EOF >> consumer.py + import vineyard; + client = vineyard.connect(); + # get the pandas dataframe from vineyard + print(client.get(name="test_dataframe").sum()) + # get the basic data unit from vineyard + print(client.get(name="test_basic_data_unit")) + client.close() + EOF + python consumer.py; + sleep infinity; + EOF + +Use the `vineyardctl` to inject vineyard into the pod and apply them to the kubernetes cluster +as follows. + +.. code:: bash + + # create the namespace + $ kubectl create ns vineyard-test + # get all injected resources + $ python3 -m vineyard.ctl inject -f pod.yaml | kubectl apply -f - + pod/vineyard-sidecar-etcd-0 created + service/vineyard-sidecar-etcd-0 created + service/vineyard-sidecar-etcd-service created + service/vineyard-sidecar-rpc created + pod/vineyard-producer-consumer created + + +Then you can get the logs of the consumer containers as follows. + +.. code:: bash + + # get the logs of the consumer container + $ kubectl logs -f vineyard-producer-consumer -n test -c consumer + A -30.168469 + B -19.269489 + C 6.332533 + D -9.714950 + dtype: float64 + (1, 1.2345000505447388, 'xxxxabcd') + +Data sharing between different pods +----------------------------------- + +In this section, we will show you how to share data between different workloads on kubernetes. +You are supposed to create a vineyard deployment and then deploy the application pods on +the nodes where the vineyard deployment is running. + +Deploy the vineyard deployment (default is 3 replicas) as follows. + +.. code:: bash + + # create the namespace if not exists + $ kubectl create ns vineyard-test + # create the vineyard deployment + $ python3 -m vineyard.ctl deploy vineyard-deployment --name vineyardd-sample -n vineyard-test + 2023-07-21T15:42:25.981+0800 INFO vineyard cluster deployed successfully + +Check the vineyard deployment status and the three vineyardd pods should run on the different nodes. + +.. code:: bash + + # check the pods status + $ kubectl get pod -lapp.vineyard.io/name=vineyardd-sample -n vineyard-test -owide + NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES + vineyardd-sample-5fd45fdd66-fq55z 1/1 Running 0 3m37s 10.244.1.17 kind-worker3 + vineyardd-sample-5fd45fdd66-qjr5c 1/1 Running 0 3m37s 10.244.3.35 kind-worker + vineyardd-sample-5fd45fdd66-ssqb7 1/1 Running 0 3m37s 10.244.2.29 kind-worker2 + vineyardd-sample-etcd-0 1/1 Running 0 3m53s 10.244.1.16 kind-worker3 + +Assume we have two pods, one is a producer and the other is a consumer. + +The producer yaml file is as follows. + +.. code:: bash + + $ cat << EOF >> producer.yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + name: producer + namespace: vineyard-test + spec: + selector: + matchLabels: + app: producer + replicas: 1 + template: + metadata: + labels: + app: producer + spec: + containers: + - name: producer + image: python:3.10 + command: + - bash + - -c + - | + pip install vineyard numpy pandas; + cat << EOF >> producer.py + import vineyard + import numpy as np + import pandas as pd + client = vineyard.connect() + client.put(pd.DataFrame(np.random.randn(100, 4), columns=list('ABCD')), persist=True, name="test_dataframe") + client.put((1, 1.2345, 'xxxxabcd'), persist=True, name="test_basic_data_unit"); + client.close() + EOF + python producer.py; + sleep infinity; + EOF + +The consumer yaml file is as follows. + +.. code:: bash + + $ cat << EOF >> consumer.yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + name: consumer + namespace: vineyard-test + spec: + selector: + matchLabels: + app: consumer + replicas: 1 + template: + metadata: + labels: + app: consumer + spec: + containers: + - name: consumer + image: python:3.10 + command: + - bash + - -c + - | + pip install vineyard numpy pandas; + cat << EOF >> consumer.py + import vineyard + client = vineyard.connect() + dataframe_obj = client.get_name("test_dataframe") + print(client.get(dataframe_obj,fetch=True).sum()) + unit_obj = client.get_name("test_basic_data_unit") + print(client.get(unit_obj,fetch=True)) + client.close() + EOF + python consumer.py; + sleep infinity; + EOF + +Use the `vineyardctl` to schedule the two workloads on the vineyard cluster. + +.. code:: bash + + # schedule the producer workload to the vineyard cluster and apply it to the kubernetes cluster + $ python3 -m vineyard.ctl schedule workload -f producer.yaml --vineyardd-name vineyardd-sample \ + --vineyardd-namespace vineyard-test -o yaml | kubectl apply -f - + deployment.apps/producer created + + # schedule the consumer workload to the vineyard cluster and apply it to the kubernetes cluster + $ python3 -m vineyard.ctl schedule workload -f consumer.yaml --vineyardd-name vineyardd-sample \ + --vineyardd-namespace vineyard-test -o yaml | kubectl apply -f - + deployment.apps/consumer created + +Check the logs of the consumer pods as follows. + +.. code:: bash + + $ kubectl logs -f $(kubectl get pod -lapp=consumer -n vineyard-test -o jsonpath='{.items[0].metadata.name}') -n vineyard-test + A 11.587912 + B 12.059792 + C 4.863514 + D -2.682567 + dtype: float64 + (1, 1.2345000505447388, 'xxxxabcd') + +From the above example, we can see the code of the consumer is quiet different from the previous sidecar example. +As the consumer may be scheduled to different node from the producer with the default kubernetes scheduler, the client +should get the remote object id by name and then fetch it from other vineyard nodes. For more details, please refer to +the `vineyard objects`_. + +.. _vineyardctl installation: https://v6d.io/notes/cloud-native/deploy-kubernetes.html#quick-start +.. _vineyard objects: https://v6d.io/notes/key-concepts/objects.html#transient-vs-persistent \ No newline at end of file diff --git a/_sources/tutorials/kubernetes/efficient-data-sharing-in-kubeflow-with-vineyard-csi-driver.rst.txt b/_sources/tutorials/kubernetes/efficient-data-sharing-in-kubeflow-with-vineyard-csi-driver.rst.txt new file mode 100644 index 0000000000..1c5b6d8506 --- /dev/null +++ b/_sources/tutorials/kubernetes/efficient-data-sharing-in-kubeflow-with-vineyard-csi-driver.rst.txt @@ -0,0 +1,572 @@ +Efficient data sharing in Kubeflow with Vineyard CSI Driver +----------------------------------------------------------- + +If you are using `Kubeflow Pipeline`_ or `Argo Workflow`_ to manage your machine learning workflow, +you may find that the data saving/loading to the volumes is slow. +To speed up the data saving/loading within these volumes, we design the Vineyard CSI Driver to +map each vineyard object to a volume, and the data saving/loading is handled by vineyard. +Next, we will show you how to use the Vineyard CSI Driver to speed up a kubeflow pipeline. + +Prerequisites +============= + +- A kubernetes cluster with version >= 1.25.10. If you don't have one by hand, you can refer to the guide `Initialize Kubernetes Cluster`_ to create one. +- Install the `Vineyardctl`_ by following the official guide. +- Install the argo workflow cli >= 3.4.8. +- Install the kfp package <= 1.8.0 for kubeflow **v1** or >= 2.0.1 for kubeflow **v2**. + +Deploy the Vineyard Cluster +=========================== + +.. code:: bash + + $ python3 -m vineyard.ctl deploy vineyard-cluster --create-namespace + +This command will create a vineyard cluster in the namespace `vineyard-system`. +You can check as follows: + +.. code:: bash + + $ kubectl get pod -n vineyard-system + NAME READY STATUS RESTARTS AGE + vineyard-controller-manager-648fc9b7bf-zwnhd 2/2 Running 0 4d3h + vineyardd-sample-79c8ffb879-6k8mk 1/1 Running 0 4d3h + vineyardd-sample-79c8ffb879-f9kkr 1/1 Running 0 4d3h + vineyardd-sample-79c8ffb879-lzgwz 1/1 Running 0 4d3h + vineyardd-sample-etcd-0 1/1 Running 0 4d3h + +Deploy the Vineyard CSI Driver +============================== + +Before deploying the Vineyard CSI Driver, you are supposed to check the vineyard +deployment is ready as follows: + +.. code:: bash + + $ kubectl get deployment -n vineyard-system + NAME READY UP-TO-DATE AVAILABLE AGE + vineyard-controller-manager 1/1 1 1 4d3h + vineyardd-sample 3/3 3 3 4d3h + +Then deploy the vineyard csi driver which specifies the vineyard cluster to use: + +.. tip:: + + If you want to look into the debug logs of the vineyard csi driver, you can add a + flag ``--verbose`` in the following command. + +.. code:: bash + + $ python3 -m vineyard.ctl deploy csidriver --clusters vineyard-system/vineyardd-sample + +Then check the status of the Vineyard CSI Driver: + +.. code:: bash + + $ kubectl get pod -n vineyard-system + NAME READY STATUS RESTARTS AGE + vineyard-controller-manager-648fc9b7bf-zwnhd 2/2 Running 0 4d3h + vineyard-csi-sample-csi-driver-fb7cb5b5d-nlrxs 4/4 Running 0 4m23s + vineyard-csi-sample-csi-nodes-69j77 3/3 Running 0 4m23s + vineyard-csi-sample-csi-nodes-k85hb 3/3 Running 0 4m23s + vineyard-csi-sample-csi-nodes-zhfz4 3/3 Running 0 4m23s + vineyardd-sample-79c8ffb879-6k8mk 1/1 Running 0 4d3h + vineyardd-sample-79c8ffb879-f9kkr 1/1 Running 0 4d3h + vineyardd-sample-79c8ffb879-lzgwz 1/1 Running 0 4d3h + vineyardd-sample-etcd-0 1/1 Running 0 4d3h + +Running the Kubeflow Pipeline example +===================================== + +We provide two examples using different versions of Kubeflow Pipeline: **v1** and **v2**. +To use the Vineyard CSI Driver, we need to do two modifications: + +1. Change APIs like **pd.read_pickle/write_pickle** to **vineyard.csi.write/read** in the source code. + +2. Add the ``vineyard object`` VolumeOp to the pipeline's dependencies. The path in the API changed +in the first step will be mapped to a volume. Notice, the volume used in any task needs to be +explicitly mounted to the corresponding path in the source code, and the storageclass_name +format of each VolumeOp is ``{vineyard-deployment-namespace}.{vineyard-deployment-name}.csi``. + +There are two ways to add the ``vineyard object`` VolumeOp to the pipeline's dependencies: + +- Each path in the source code is mapped to a volume, and each volume is mounted to the actual path + in the source code. The benefit is that the source path does not need to be modified. + +- Create a volume for the paths with the same prefix in the source code. You can add the prefix ``/vineyard`` for + the paths in the source code, and mount a volume to the path ``/vineyard``. In this way, you can + only create one volume for multiple paths/vineyard objects. + +You may get some insights from the modified pipeline ``pipeline-with-vineyard.py`` and ``pipeline-kfp-v2-with-vineyard``. + +Preparations +^^^^^^^^^^^^ + +Before running the kubflow examples, we need to do some common preparations, and then +you can choose to run **KFP V1** or **KFP V2** example. + +1. First of all, we need to build the docker images for the pipeline: + +.. code:: bash + + $ cd k8s/examples/vineyard-csidriver + $ make docker-build + +Or build the docker images with your docker registry: + +.. code:: bash + + $ make docker-build REGISTRY= + +2. Check the images built successfully: + +.. code:: bash + + $ docker images + train-data latest 5628953ffe08 14 seconds ago 1.47GB + test-data latest 94c8c75b960a 14 seconds ago 1.47GB + prepare-data latest 5aab1b120261 15 seconds ago 1.47GB + preprocess-data latest 5246d09e6f5e 15 seconds ago 1.47GB + +3. Push the image to a docker registry that your kubernetes cluster can access. + +.. code:: bash + + $ make push-images REGISTRY= + +4. Create the namespace for the pipeline: + +.. code:: bash + + $ kubectl create namespace kubeflow + +5. To simulate the data loading/saving of the actual pipeline, we use the nfs volume +to store the data. The nfs volume is mounted to the ``/mnt/data`` directory of the +kind cluster. Then apply the data volume as follows: + +.. tip:: + + If you already have nfs volume that can be accessed by the kubernetes cluster, + you can update the ``prepare-data.yaml`` to use your nfs volume. + +.. code:: bash + + $ kubectl apply -f prepare-data.yaml + +6. Deploy the rbac for the pipeline: + +.. code:: bash + + $ kubectl apply -f rbac.yaml + + +7. **(important)** Download all need images to all kind workers: + +.. code:: bash + + registry="ghcr.io/v6d-io/v6d/kubeflow-example" + kubeflow_registry="gcr.io/ml-pipeline" + worker=($(docker ps | grep kind-worker | awk -F ' ' '{print $1}')) + for c in ${worker[@]}; do + docker exec -it $c sh -c " + crictl pull ${registry}/preprocess-data && \ + crictl pull ${registry}/train-data && \ + crictl pull ${registry}/test-data &&\ + # change the following image to compatible with the installed kubeflow version + crictl pull ${kubeflow_registry}/argoexec:v3.3.10-license-compliance && \ + crictl pull ${kubeflow_registry}/kfp-driver@sha256:0ce9bf20ac9cbb21e84ff0762d5ae508d21e9c85fde2b14b51363bd1b8cd7528 + # change the following image to compatible with the installed argo workflow version + crictl pull quay.io/argoproj/argoexec:v3.4.8 + " + done + + +Running KFP V1 Example +^^^^^^^^^^^^^^^^^^^^^^ + +.. tip:: + + If you want to run the **KFP V2** example, you can skip this section. + +The original **KFP V1** code is shown in ``pipeline.py`` under the directory ``k8s/examples/vineyard-csidriver`` and the +``pipeline-with-vineyard.py`` is modified to be compatible with the Vineyard CSI Driver. As we use the argo workflow to +run the **KFP v1** pipeline, we need to install the argo workflow server as follows. + +1. Install the argo server on Kubernetes: + +.. code:: bash + + $ kubectl create namespace argo + $ kubectl apply -n argo -f https://github.com/argoproj/argo-workflows/releases/download/v3.4.8/install.yaml + +2. Check the status of the argo server: + +.. code:: bash + + $ kubectl get pod -n argo + NAME READY STATUS RESTARTS AGE + argo-server-7698c96655-ft6sj 1/1 Running 0 4d1h + workflow-controller-b888f4458-sfrjd 1/1 Running 0 4d1h + + +3. Submit the kubeflow example without vineyard to the argo server: + +.. code:: bash + + $ for data_multiplier in 4000 5000 6000; do \ + # clean the previous argo workflow + argo delete --all -n kubeflow; \ + # submit the pipeline without vineyard + argo submit --watch pipeline.yaml -n kubeflow \ + -p data_multiplier=${data_multiplier} -p registry="ghcr.io/v6d-io/v6d/kubeflow-example"; \ + # sleep 60s to record the actual execution time + sleep 60; \ + done + +4. Clear the previous resources: + +.. code:: bash + + $ argo delete --all -n kubeflow + +5. Submit the kubeflow example with vineyard to the argo server: + +.. code:: bash + + $ for data_multiplier in 3000 4000 5000; do \ + # clean the previous argo workflow + argo delete --all -n kubeflow; \ + # submit the pipeline without vineyard + argo submit --watch pipeline-with-vineyard.yaml -n kubeflow \ + -p data_multiplier=${data_multiplier} -p registry="ghcr.io/v6d-io/v6d/kubeflow-example"; \ + # sleep 60s to record the actual execution time + sleep 60; \ + done + + +Running KFP V2 Example +^^^^^^^^^^^^^^^^^^^^^^ + +.. tip:: + + If you have installed the argo workflow server, you need to delete it first. As the KFP resources + contain the argo workflow server, and the argo workflow server will conflict with the KFP resources. + +The original **KFP V2** code is shown in ``pipeline-kfp-v2.py`` under the directory ``k8s/examples/vineyard-csidriver`` and the +``pipeline-kfp-v2-with-vineyard.py`` is modified to be compatible with the Vineyard CSI Driver. As it can only be compiled to +the IR YAML, which only recognized by the kubeflow server. Thus, we need to install the kubeflow server as follows. + +1. Install a KFP standalone instance on Kubernetes: + +.. code:: bash + + export PIPELINE_VERSION=2.0.1 + + kubectl apply -k "github.com/kubeflow/pipelines/manifests/kustomize/cluster-scoped-resources?ref=$PIPELINE_VERSION" + kubectl wait --for condition=established --timeout=60s crd/applications.app.k8s.io + kubectl apply -k "github.com/kubeflow/pipelines/manifests/kustomize/env/dev?ref=$PIPELINE_VERSION" + +2. Check the status of the KFP instance: + +.. code:: bash + + $ kubectl get pod -n kubeflow + +.. admonition:: Expected output + :class: admonition-details + + .. code:: bash + + NAME READY STATUS RESTARTS AGE + cache-deployer-deployment-5c95fc7fdd-d65cf 1/1 Running 0 49m + cache-server-6c84679764-k8q6j 1/1 Running 0 49m + controller-manager-86bf69dc54-2brxq 1/1 Running 0 49m + metadata-envoy-deployment-6448d544f5-z4sc8 1/1 Running 0 49m + metadata-grpc-deployment-784b8b5fb4-8mtm7 1/1 Running 2 (49m ago) 49m + metadata-writer-79c5499dd8-6jjmm 1/1 Running 0 49m + minio-65dff76b66-tdtx5 1/1 Running 0 49m + ml-pipeline-6546dcc959-k8t84 1/1 Running 0 48m + ml-pipeline-persistenceagent-79479cdb74-q6lq9 1/1 Running 0 49m + ml-pipeline-scheduledworkflow-5cbdc7d885-lx9r7 1/1 Running 0 49m + ml-pipeline-ui-7c94d6f4b7-z2tvs 1/1 Running 0 49m + ml-pipeline-viewer-crd-685f449686-bz55g 1/1 Running 0 49m + ml-pipeline-visualizationserver-7c8f97864d-sp8p6 1/1 Running 0 49m + mysql-c999c6c8-nwp9d 1/1 Running 0 49m + proxy-agent-77d7b57c99-plrpb 0/1 CrashLoopBackOff 14 (2m17s ago) 49m + workflow-controller-6c85bc4f95-dw889 1/1 Running 0 49m + +3. Delete the proxy deployment as it's not used in the example and will slow down the pipeline: + +.. code:: bash + + $ kubectl delete deployment proxy-agent -n kubeflow + +4. Open a terminal to portforward the KFP UI to your local machine: + +.. code:: bash + + $ kubectl port-forward -n kubeflow svc/ml-pipeline-ui 8088:80 + +5. Upload the ``pipeline-kfp-v2.yaml`` and ``pipeline-kfp-v2-with-vineyard.yaml`` to the KFP instance: + +.. tip:: + + If you use the custom docker registry, you need to update the docker image + in the ``pipeline-kfp-v2.yaml`` and ``pipeline-kfp-v2-with-vineyard.yaml``. + +.. figure:: ../../images/kubeflow_upload_pipeline.png + :width: 75% + :alt: Upload pipeline in the kubeflow Dashboard + + Upload pipeline in the kubeflow Dashboard + +6. **(Important)** Clean the file system cache of the kubeflow server. + +As the KFP V2 doesn't support to configure the ``SecurityContext`` of the container, which means +we can't run the command ``sync; echo 3 > /proc/sys/vm/drop_caches`` in the container to clean the +file system cache. Thus before creating a new run, we need to clean the file system cache manually +as follows: + +.. code:: bash + + # clean all the file system cache and image cache of all kind workers + $ worker=($(docker ps | grep kind-worker | awk -F ' ' '{print $1}')); \ + for c in ${worker[@]}; do docker exec --privileged -it $c \ + sh -c 'sync && echo 3 > /proc/sys/vm/drop_caches'; done; + +If you use the actual kubernetes cluster, you can login to the kubernetes node and clean the file +system cache manually. + +7. Create the runs using the previously uploaded pipelines: + +.. figure:: ../../images/kubeflow_create_run.png + :width: 75% + :alt: Create runs in the kubeflow Dashboard + + Create runs in the kubeflow Dashboard + +KFP V1 Result Analysis +^^^^^^^^^^^^^^^^^^^^^^ + +The data scale are 14000 Mi, 18000 Mi and 21000 Mi, which correspond to +the 4000, 5000 and 6000 in the previous data_multiplier respectively, +and the time of argo workflow execution of the pipeline is as follows: + +Argo workflow duration +"""""""""""""""""""""" + ++------------+------------------+---------------+ +| data scale | without vineyard | with vineyard | ++============+==================+===============+ +| 14000 Mi | 317s | 270s | ++------------+------------------+---------------+ +| 18000 Mi | 403s | 331s | ++------------+------------------+---------------+ +| 21000 Mi | 504s | 389s | ++------------+------------------+---------------+ + + +Actually, the cost time of argo workflow is affected by lots of factors, +such as the network, the cpu and memory of the cluster, the data volume, etc. +So the time of argo workflow execution of the pipeline is not stable. +But we can still find that the time of argo workflow execution of the pipeline +with vineyard reduced by 15%~25%. + +Also, we record the whole execution time via logs. The result is as follows: + +Actual execution time +""""""""""""""""""""" + ++------------+------------------+---------------+ +| data scale | without vineyard | with vineyard | ++============+==================+===============+ +| 14000 Mi | 215.1s | 140.3s | ++------------+------------------+---------------+ +| 18000 Mi | 298.2s | 198.1s | ++------------+------------------+---------------+ +| 21000 Mi | 398.7s | 257.5s | ++------------+------------------+---------------+ + + +According to the above results, we can find that the time of actual +execution of the pipeline with vineyard reduced by 30%~40%. To be specific, +we record the write/read time of the following steps: + +Writing time +"""""""""""" + ++------------+------------------+---------------+ +| data scale | without vineyard | with vineyard | ++============+==================+===============+ +| 14000 Mi | 33.2s | 8.8s | ++------------+------------------+---------------+ +| 18000 Mi | 40.8s | 11.6s | ++------------+------------------+---------------+ +| 21000 Mi | 48.6s | 13.9s | ++------------+------------------+---------------+ + + +From the above results, we can find that the writing time the pipeline +with vineyard reduced by 70%~75%. The reason is that the data is stored +in the vineyard cluster, so it's actually a memory copy operation, which +is faster than the write operation of the nfs volume. + + +Reading time +"""""""""""" + +We delete the time of init data loading, and the results are as follows: + ++------------+------------------+---------------+ +| data scale | without vineyard | with vineyard | ++============+==================+===============+ +| 14000 Mi | 56.6s | 0.02s | ++------------+------------------+---------------+ +| 18000 Mi | 76.3s | 0.02s | ++------------+------------------+---------------+ +| 21000 Mi | 93.7s | 0.02s | ++------------+------------------+---------------+ + +Based on the above results, we can find that the read time of vineyard is +nearly a constant, which is not affected by the data scale. +The reason is that the data is stored in the shared memory of vineyard cluster, +so it's actually a pointer copy operation. + +As a result, we can find that with vineyard, the argo workflow +duration of the pipeline is reduced by 15%~25% and the actual +execution time of the pipeline is reduced by about 30%~40%. + +KFP V2 Result Analysis +^^^^^^^^^^^^^^^^^^^^^^ + +Different with the previous KFP V1, the KFP V2 will compile the pipeline to the IR YAML, +and the IR YAML will be submitted to the kubeflow server. Before each component of the +pipeline is executed, the kubeflow server will create a preprocess container to parse +the IR YAML and generate the relevant kubernetes resources. That means the kubeflow server +will create more pods than the previous argo workflow, thereby slowing down the execution +speed of the entire pipeline. + +The execution time of the pipeline shown in the kubeflow dashboard is as follows: + +Kubeflow dashboard duration +""""""""""""""""""""""""""" + ++------------+------------------+---------------+ +| data scale | without vineyard | with vineyard | ++============+==================+===============+ +| 14000 Mi | 276s | 229s | ++------------+------------------+---------------+ +| 18000 Mi | 365s | 291s | ++------------+------------------+---------------+ +| 21000 Mi | 440s | 352s | ++------------+------------------+---------------+ + +Based on the above result, we can find that the execution time of kubeflow pipeline with vineyard +is reduced by 15%~20% on the dashboard. Compared to the kfp v1, the vineyard effect is slightly +reduced. The reason is that in the kfp v2, CreateVolume and DeleteVolume are regarded as two components, +and that means more worker pods will be created. The time to create these pods is the main factor +that reduces vineyard efficiency. + +Actual execution time +""""""""""""""""""""" + ++------------+------------------+---------------+ +| data scale | without vineyard | with vineyard | ++============+==================+===============+ +| 14000 Mi | 213s | 133.4s | ++------------+------------------+---------------+ +| 18000 Mi | 302.7s | 208.1s | ++------------+------------------+---------------+ +| 21000 Mi | 377.6s | 265.9s | ++------------+------------------+---------------+ + +From the above results, we can find that the actual execution time of the pipeline with vineyard +is reduced by 30%~40%. To be specific, we record the write/read time of the following steps: + + +Writing time +"""""""""""" + ++------------+------------------+---------------+ +| data scale | without vineyard | with vineyard | ++============+==================+===============+ +| 14000 Mi | 33.2s | 8.1s | ++------------+------------------+---------------+ +| 18000 Mi | 41s | 10.8s | ++------------+------------------+---------------+ +| 21000 Mi | 48.3s | 13.7s | ++------------+------------------+---------------+ + +Similarly, since writing to vineyard is just a memory copy operation, its execution time +will be greatly reduced. + + +Reading time +"""""""""""" + +We delete the time of init data loading, and the results are as follows: + ++------------+------------------+---------------+ +| data scale | without vineyard | with vineyard | ++============+==================+===============+ +| 8500 Mi | 54.5s | 0.04s | ++------------+------------------+---------------+ +| 12000 Mi | 73.5s | 0.02s | ++------------+------------------+---------------+ +| 15000 Mi | 88.9s | 0.02s | ++------------+------------------+---------------+ + + +As the result in kfp v1, reading the vineyard data only requires a +single operation to get the memory pointer. So the reading time of +vineyard is almost 0. + +In summary, regardless of whether you are using KFP V1 or KFP V2, and whether the backend +is Argo server or Kubeflow manifests, integrating Vineyard can effectively optimize the data +sharing among Kubeflow components. This optimization, in turn, leads to a significant reduction +in the overall execution time of the Kubeflow pipeline. + +Clean up +======== + +Delete the rbac for the kubeflow example: + +.. code:: bash + + $ kubectl delete -f rbac.yaml + +Delete all argo workflow + +.. code:: bash + + $ argo delete --all + +Delete the argo server: + +.. code:: bash + + $ kubectl delete ns argo + +Delete the vineyard cluster: + +.. code:: bash + + $ python3 -m vineyard.ctl delete vineyard-cluster + +Delete the data volume: + +.. code:: bash + + $ kubectl delete -f prepare-data.yaml + +Delete the kubeflow namespace: + +.. code:: bash + + $ kubectl delete ns kubeflow + +.. _Kubeflow Pipeline: https://github.com/kubeflow/kubeflow +.. _Argo Workflow: https://github.com/argoproj/argo-workflows +.. _Initialize Kubernetes Cluster: https://v6d.io/tutorials/kubernetes/using-vineyard-operator.html#step-0-optional-initialize-kubernetes-cluster +.. _Vineyardctl: https://v6d.io/notes/cloud-native/deploy-kubernetes.html#quick-start +.. _Argo Workflow CLI: https://github.com/argoproj/argo-workflows/releases/ \ No newline at end of file diff --git a/_sources/tutorials/kubernetes/ml-pipeline-mars-pytorch.rst.txt b/_sources/tutorials/kubernetes/ml-pipeline-mars-pytorch.rst.txt new file mode 100644 index 0000000000..1236963832 --- /dev/null +++ b/_sources/tutorials/kubernetes/ml-pipeline-mars-pytorch.rst.txt @@ -0,0 +1,211 @@ +Machine learning with Vineyard on Kubernetes +-------------------------------------------- + +In this demonstration, we will build a fraudulent transaction classifier for +fraudulent transaction data. The process consists of the following +three main steps: + +- :code:`prepare-data`: Utilize Vineyard to read and store data in a distributed manner. +- :code:`process-data`: Employ Mars to process the data across multiple nodes. +- :code:`train-data`: Use Pytorch to train the model on the distributed data. + +We have three tables: user table, product table, and transaction table. +The user and product tables primarily contain user and product IDs, along with +their respective ``Feature`` vectors. Each record in the transaction table indicates +a user purchasing a product, with a ``Fraud`` label identifying whether the +transaction is fraudulent. Additional features related to these transactions are also +stored in the transaction table. You can find the three tables in the `dataset repo`_. +Follow the steps below to reproduce the demonstration. First, create a vineyard cluster +with 3 worker nodes. + +.. code:: bash + + $ cd k8s && make -C k8s/test/e2e install-vineyard-cluster + +.. admonition:: Expected output + :class: admonition-details + + .. code:: bash + + the kubeconfig path is /tmp/e2e-k8s.config + Creating the kind cluster with local registry + a16c878c5091c1e5c9eff0a1fca065665f47edb4c8c75408b3d33e22f0ec0d05 + Creating cluster "kind" ... + ✓ Ensuring node image (kindest/node:v1.24.0) 🖼 + ✓ Preparing nodes 📦 📦 📦 📦 + ✓ Writing configuration 📜 + ✓ Starting control-plane 🕹️ + ✓ Installing CNI 🔌 + ✓ Installing StorageClass 💾 + ✓ Joining worker nodes 🚜 + Set kubectl context to "kind-kind" + You can now use your cluster with: + + kubectl cluster-info --context kind-kind --kubeconfig /tmp/e2e-k8s.config + + Thanks for using kind! 😊 + configmap/local-registry-hosting created + Installing vineyard-operator... + The push refers to repository [localhost:5001/vineyard-operator] + c3a672704524: Pushed + b14a7037d2e7: Pushed + 8d7366c22fd8: Pushed + latest: digest: sha256:ea06c833351f19c5db28163406c55e2108676c27fdafea7652500c55ce333b9d size: 946 + make[1]: Entering directory '/opt/caoye/v6d/k8s' + go: creating new go.mod: module tmp + /home/gsbot/go/bin/controller-gen rbac:roleName=manager-role crd:maxDescLen=0 webhook paths="./..." output:crd:artifacts:config=config/crd/bases + cd config/manager && /usr/local/bin/kustomize edit set image controller=localhost:5001/vineyard-operator:latest + /usr/local/bin/kustomize build config/default | kubectl apply -f - + namespace/vineyard-system created + customresourcedefinition.apiextensions.k8s.io/backups.k8s.v6d.io created + customresourcedefinition.apiextensions.k8s.io/globalobjects.k8s.v6d.io created + customresourcedefinition.apiextensions.k8s.io/localobjects.k8s.v6d.io created + customresourcedefinition.apiextensions.k8s.io/operations.k8s.v6d.io created + customresourcedefinition.apiextensions.k8s.io/recovers.k8s.v6d.io created + customresourcedefinition.apiextensions.k8s.io/sidecars.k8s.v6d.io created + customresourcedefinition.apiextensions.k8s.io/vineyardds.k8s.v6d.io created + serviceaccount/vineyard-manager created + role.rbac.authorization.k8s.io/vineyard-leader-election-role created + clusterrole.rbac.authorization.k8s.io/vineyard-manager-role created + clusterrole.rbac.authorization.k8s.io/vineyard-metrics-reader created + clusterrole.rbac.authorization.k8s.io/vineyard-proxy-role created + clusterrole.rbac.authorization.k8s.io/vineyard-scheduler-plugin-role created + rolebinding.rbac.authorization.k8s.io/vineyard-leader-election-rolebinding created + clusterrolebinding.rbac.authorization.k8s.io/vineyard-kube-scheduler-rolebinding created + clusterrolebinding.rbac.authorization.k8s.io/vineyard-manager-rolebinding created + clusterrolebinding.rbac.authorization.k8s.io/vineyard-proxy-rolebinding created + clusterrolebinding.rbac.authorization.k8s.io/vineyard-scheduler-plugin-rolebinding created + clusterrolebinding.rbac.authorization.k8s.io/vineyard-scheduler-rolebinding created + clusterrolebinding.rbac.authorization.k8s.io/vineyard-volume-scheduler-rolebinding created + service/vineyard-controller-manager-metrics-service created + service/vineyard-webhook-service created + deployment.apps/vineyard-controller-manager created + mutatingwebhookconfiguration.admissionregistration.k8s.io/vineyard-mutating-webhook-configuration created + validatingwebhookconfiguration.admissionregistration.k8s.io/vineyard-validating-webhook-configuration created + make[1]: Leaving directory '/opt/caoye/v6d/k8s' + deployment.apps/vineyard-controller-manager condition met + Vineyard-Operator Ready + Installing vineyard cluster... + vineyardd.k8s.v6d.io/vineyardd-sample created + vineyardd.k8s.v6d.io/vineyardd-sample condition met + Vineyard cluster Ready + +Verify that all Vineyard pods are running. + +.. code:: bash + + $ KUBECONFIG=/tmp/e2e-k8s.config kubectl get pod -n vineyard-system + +.. admonition:: Expected output + :class: admonition-details + + .. code:: bash + + NAME READY STATUS RESTARTS AGE + etcd0 1/1 Running 0 68s + etcd1 1/1 Running 0 68s + etcd2 1/1 Running 0 68s + vineyard-controller-manager-7f569b57c5-46tgq 2/2 Running 0 92s + vineyardd-sample-6ffcb96cbc-gs2v9 1/1 Running 0 67s + vineyardd-sample-6ffcb96cbc-n59gg 1/1 Running 0 67s + vineyardd-sample-6ffcb96cbc-xwpzd 1/1 Running 0 67s + +First, let's prepare the dataset and download it into the kind worker nodes as follows. + +.. code:: bash + + $ worker=($(docker ps | grep kind-worker | awk -F ' ' '{print $1}')) + $ for c in ${worker[@]}; do \ + docker exec $c sh -c "\ + mkdir -p /datasets; \ + cd /datasets/; \ + curl -OL https://raw.githubusercontent.com/GraphScope/gstest/master/vineyard-mars-showcase-dataset/{item,txn,user}.csv" \ + done + +The `prepare-data` job primarily reads the datasets and distributes them across different +Vineyard nodes. For more information, please refer to the `prepare data code`_. To apply +the job, follow the steps below: + +.. note:: + + The `prepare-data` job needs to exec into the other pods. Therefore, you need to + create a service account and bind it to the role under the namespace. + Please make sure you can have permission to create the following role. + + .. code:: text + + - apiGroups: [""] + resources: ["pods", "pods/log", "pods/exec"] + verbs: ["get", "patch", "delete", "create", "watch", "list"] + +.. code:: bash + + $ kubectl create ns vineyard-job && \ + kubectl apply -f showcase/vineyard-mars-pytorch/prepare-data/resources && \ + kubectl wait job -n vineyard-job -l app=prepare-data --for condition=complete --timeout=1200s + +.. admonition:: Expected output + :class: admonition-details + + .. code:: bash + + namespace/vineyard-job created + clusterrolebinding.rbac.authorization.k8s.io/prepare-data-rolebinding created + clusterrole.rbac.authorization.k8s.io/prepare-data-role created + job.batch/prepare-data created + serviceaccount/prepare-data created + job.batch/prepare-data condition met + +.. note:: + + The `process-data` job needs to create a new namespace and deploy several kubernetes + resources in it. Please make sure you can have permission to create the following role. + + .. code:: text + + - apiGroups: [""] + resources: ["pods", "pods/exec", "pods/log", "endpoints", "services"] + verbs: ["get", "patch", "delete", "create", "watch", "list"] + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "create", "delete"] + - apiGroups: [""] + resources: ["nodes"] + verbs: ["get", "list"] + - apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles", "rolebindings"] + verbs: ["patch", "get", "create", "delete"] + - apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["create"] + + Notice, the `process-data` job will require lots of permissions to deal + kubernetes resources, so please check the image of `process-data` job + if it is an official one. + +The `prepare-data` job creates numerous dataframes in Vineyard. To combine these dataframes, +we use the appropriate join method in `mars`_. For more details, refer to the `process data +code`_. Apply the `process-data` job as follows: + +.. code:: bash + + $ kubectl apply -f showcase/vineyard-mars-pytorch/process-data/resources && \ + kubectl wait job -n vineyard-job -l app=process-data --for condition=complete --timeout=1200s + +Finally, apply the `train-data` job to obtain the fraudulent transaction classifier. You can +also view the `train data code`_. + +.. code:: bash + + $ kubectl apply -f k8s/showcase/vineyard-mars-pytorch/train-data/resources && \ + kubectl wait pods -n vineyard-job -l app=train-data --for condition=Ready --timeout=1200s + +If any of the above steps fail, please refer to the `mars showcase e2e test`_ for further guidance. + + +.. _mars: https://github.com/mars-project/mars +.. _mars showcase e2e test: https://github.com/v6d-io/v6d/blob/main/k8s/test/e2e/mars-examples/e2e.yaml +.. _dataset repo: https://github.com/GraphScope/gstest/tree/master/vineyard-mars-showcase-dataset +.. _prepare data code: https://github.com/v6d-io/v6d/blob/main/k8s/examples/vineyard-mars-pytorch/prepare-data/prepare-data.py +.. _process data code: https://github.com/v6d-io/v6d/blob/main/k8s/examples/vineyard-mars-pytorch/process-data/process-data.py +.. _train data code: https://github.com/v6d-io/v6d/blob/main/k8s/examples/vineyard-mars-pytorch/train-data/train-data.py diff --git a/_sources/tutorials/kubernetes/using-vineyard-operator.rst.txt b/_sources/tutorials/kubernetes/using-vineyard-operator.rst.txt new file mode 100644 index 0000000000..95b463c991 --- /dev/null +++ b/_sources/tutorials/kubernetes/using-vineyard-operator.rst.txt @@ -0,0 +1,469 @@ +Use vineyard operator +===================== + +Vineyard operator has been designed to manage vineyard components within Kubernetes. +This tutorial provides a step-by-step guide on how to effectively utilize the vineyard +operator. For more details, please refer to :ref:`vineyard-operator`. + +Step 0: (optional) Initialize Kubernetes Cluster +------------------------------------------------ + +If you don't have a Kubernetes cluster readily available, we highly recommend using `kind`_ to +create one. Before setting up the Kubernetes cluster, please ensure you have the following +tools installed: + +- kubectl: version >= 1.19.2 +- kind: version >= 0.14.0 +- docker: version >= 0.19.0 + +Utilize kind (v0.14.0) to create a Kubernetes cluster consisting of 4 nodes (1 master node and 3 +worker nodes): + +.. code:: bash + + $ cat > kind-config.yaml < 114s v1.24.0 + kind-worker2 Ready 114s v1.24.0 + kind-worker3 Ready 114s v1.24.0 + +Step 1: Deploy the Vineyard Operator +------------------------------------- + +Create a dedicated namespace for the Vineyard Operator. + +.. code:: bash + + $ kubectl create namespace vineyard-system + +.. admonition:: Expected output + :class: admonition-details + + .. code:: bash + + namespace/vineyard-system created + +The Vineyard CRDs、Controllers、Webhooks and Scheduler are packaged by `helm`_, you could +deploy all resources as follows. + +.. note:: + + The vineyard operator needs permission to create several CRDs and kubernetes + resources, before deploying the vineyard operator, please ensure you can create + the `clusterrole`_. + +.. code:: bash + + $ helm repo add vineyard https://vineyard.oss-ap-southeast-1.aliyuncs.com/charts/ + +.. admonition:: Expected output + :class: admonition-details + + .. code:: bash + + "vineyard" has been added to your repositories + +Update the vineyard operator chart to the newest one. + +.. code:: bash + + $ helm repo update + +.. admonition:: Expected output + :class: admonition-details + + .. code:: bash + + Hang tight while we grab the latest from your chart repositories... + ...Successfully got an update from the "vineyard" chart repository + Update Complete. ⎈Happy Helming!⎈ + +Deploy the vineyard operator in the namespace ``vineyard-system``. + +.. code:: bash + + $ helm install vineyard-operator vineyard/vineyard-operator -n vineyard-system + +.. admonition:: Expected output + :class: admonition-details + + .. code:: bash + + NAME: vineyard-operator + LAST DEPLOYED: Wed Jan 4 16:41:45 2023 + NAMESPACE: vineyard-system + STATUS: deployed + REVISION: 1 + TEST SUITE: None + NOTES: + Thanks for installing VINEYARD-OPERATOR, release at namespace: vineyard-system, name: vineyard-operator. + + To learn more about the release, try: + + $ helm status vineyard-operator -n vineyard-system # get status of running vineyard operator + $ helm get all vineyard-operator -n vineyard-system # get all deployment yaml of vineyard operator + + To uninstall the release, try: + + $ helm uninstall vineyard-operator -n vineyard-system + +You could get all details about vineyard operator in the doc :ref:`vineyard-operator`, just have fun with vineyard operator! + +Check the status of all vineyard resources created by helm: + +.. code:: bash + + $ kubectl get all -n vineyard-system + +.. admonition:: Expected output + :class: admonition-details + + .. code:: bash + + NAME READY STATUS RESTARTS AGE + pod/vineyard-operator-controller-manager-5bcbb75fb6-cfdpk 2/2 Running 0 2m30s + + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + service/vineyard-operator-controller-manager-metrics-service ClusterIP 10.96.153.134 8443/TCP 2m30s + service/vineyard-operator-webhook-service ClusterIP 10.96.9.101 443/TCP 2m30s + + NAME READY UP-TO-DATE AVAILABLE AGE + deployment.apps/vineyard-operator-controller-manager 1/1 1 1 2m30s + + NAME DESIRED CURRENT READY AGE + replicaset.apps/vineyard-operator-controller-manager-5bcbb75fb6 1 1 1 2m30s + +Step 2: Deploy a Vineyard Cluster +---------------------------------- + +After successfully installing the Vineyard operator as described in the previous step, +you can now proceed to deploy a Vineyard cluster. To create a cluster with two Vineyard +instances, simply create a `Vineyardd` Custom Resource (CR) as shown below. + +.. code:: bash + + $ cat <", + "value_type_": "float64", + "value_type_meta_": "", + "value_type_": "float64", + "value_type_meta_": "4800)].index) + X = df.drop('SalePrice', axis=1) # Features + y = df['SalePrice'] # Target variable + + del df + + X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) + + del X, y + + vineyard.put(X_train, name="x_train", persist=True) + vineyard.put(X_test, name="x_test", persist=True) + vineyard.put(y_train, name="y_train", persist=True) + vineyard.put(y_test, name="y_test", persist=True) + + # define the model training task + def train(): + from sklearn.linear_model import LinearRegression + + import joblib + import pandas as pd + import vineyard + + x_train_data = vineyard.get(name="x_train", fetch=True) + y_train_data = vineyard.get(name="y_train", fetch=True) + + model = LinearRegression() + model.fit(x_train_data, y_train_data) + + joblib.dump(model, '/data/model.pkl') + + # define the model testing task + def test(): + from sklearn.linear_model import LinearRegression + from sklearn.metrics import mean_squared_error + + import vineyard + import joblib + import pandas as pd + + x_test_data = vineyard.get(name="x_test", fetch=True) + y_test_data = vineyard.get(name="y_test", fetch=True) + + model = joblib.load("/data/model.pkl") + y_pred = model.predict(x_test_data) + + err = mean_squared_error(y_test_data, y_pred) + + with open('/data/output.txt', 'a') as f: + f.write(str(err)) + + packages_to_install = ["numpy", "pandas", "pyarrow", "requests", "vineyard", "scikit-learn==1.4.0", "joblib==1.3.2"] + pip_index_url = "https://pypi.tuna.tsinghua.edu.cn/simple" + + preprocess_processor = create_processor(preprocess, packages_to_install, pip_index_url) + train_processor = create_processor(train, packages_to_install, pip_index_url) + test_processor = create_processor(test, packages_to_install, pip_index_url) + + # Create a linear regression model task workflow: data preprocessing -> model training -> model testing + # The following mount path "/var/run/vineyard" is the default path of the vineyard configuration file + flow = dataset.process(processor=preprocess_processor, dataset_mountpath="/var/run/vineyard") \ + .process(processor=train_processor, dataset_mountpath="/var/run/vineyard") \ + .process(processor=test_processor, dataset_mountpath="/var/run/vineyard") + + # Submit the data processing task workflow of the linear regression model and wait for it to run to completion + run = flow.run(run_id="linear-regression-with-vineyard") + run.wait() + +Here's an overview of each part of the code: + +1. **Create Fluid client**: This code is responsible for establishing +a connection with the Fluid control platform using the default kubeconfig file and +creating a Fluid client instance. + +2. **Create and configure the vineyard dataset and runtime environment**: Next, the code +creates a dataset named ``Vineyard``, then obtains the dataset instance, initializes the vineyard +runtime configuration, and sets up a copy number and memory size to bind the dataset to the +runtime environment. + +3. **Define the data preprocessing function**: This part defines a python function for data +preprocessing, which includes splitting the training set and the test set, as well as +data filtering and other operations. + +4. **Define model training function**: As the name suggests, this code defines another +python function for training a linear regression model. + +5. **Define the model testing function**: This section contains the model testing logic +for evaluating the trained model. + +6. **Create a task template and define task workflow**: The code encapsulates a task +template function named create_processor, which uses the previously defined python functions +to build data preprocessing, model training and model testing steps respectively. +These steps are designed to be executed sequentially, forming a complete workflow in which +data preprocessing is the first step, followed by model training, and finally model testing. +This serial execution sequence ensures that the output of each stage can be used as the input +of the next stage, thereby achieving a coherent and orderly machine learning process. + +7. **[Optional] Enable data affinity scheduling**: After enabling fuse affinity scheduling, +add the tag ``"fuse.serverful.fluid.io/inject": "true"`` to ensure that related tasks run on the +same node first through scheduling. to achieve the best performance in data processing. + +8. **Submit and execute the task workflow**: Submit the entire linear regression model task +workflow to the Fluid platform for execution through the run command. + +9. **Resource Cleanup**: Finally, clean up all resources created on the Fluid platform. + +.. _Install the cloud native AI suite: https://help.aliyun.com/zh/ack/cloud-native-ai-suite/user-guide/deploy-the-cloud-native-ai-suite?spm=a2c4g.11186623.0.i14#task-2038811 +.. _ossutil: https://help.aliyun.com/zh/oss/developer-reference/ossutil +.. _Kubectl: https://github.com/kubernetes/kubectl +.. _Helm: https://github.com/helm/helm \ No newline at end of file diff --git a/_sources/tutorials/tutorials.rst.txt b/_sources/tutorials/tutorials.rst.txt new file mode 100644 index 0000000000..10e83f6d63 --- /dev/null +++ b/_sources/tutorials/tutorials.rst.txt @@ -0,0 +1,7 @@ +.. Gather all tutorials as an index page + +:orphan: + +.. include:: ../tutorials/data-processing.rst +.. include:: ../tutorials/kubernetes.rst +.. include:: ../tutorials/extending.rst diff --git a/_static/airflow_etl.jpg b/_static/airflow_etl.jpg new file mode 100644 index 0000000000..78842377dc Binary files /dev/null and b/_static/airflow_etl.jpg differ diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 0000000000..cfc60b86c7 --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,921 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/check-solid.svg b/_static/check-solid.svg new file mode 100644 index 0000000000..92fad4b5c0 --- /dev/null +++ b/_static/check-solid.svg @@ -0,0 +1,4 @@ + + + + diff --git a/_static/clipboard.min.js b/_static/clipboard.min.js new file mode 100644 index 0000000000..54b3c46381 --- /dev/null +++ b/_static/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.8 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1 \ No newline at end of file diff --git a/_static/cncf-sandbox-color.svg b/_static/cncf-sandbox-color.svg new file mode 100644 index 0000000000..d424565eeb --- /dev/null +++ b/_static/cncf-sandbox-color.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/_static/cncf-small.png b/_static/cncf-small.png new file mode 100644 index 0000000000..27bdc98ff2 Binary files /dev/null and b/_static/cncf-small.png differ diff --git a/_static/cncf-tiny.png b/_static/cncf-tiny.png new file mode 100644 index 0000000000..2d9fe42414 Binary files /dev/null and b/_static/cncf-tiny.png differ diff --git a/_static/cncf.png b/_static/cncf.png new file mode 100644 index 0000000000..97796cba6c Binary files /dev/null and b/_static/cncf.png differ diff --git a/_static/copy-button.svg b/_static/copy-button.svg new file mode 100644 index 0000000000..9c074dae52 --- /dev/null +++ b/_static/copy-button.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/_static/copybutton.css b/_static/copybutton.css new file mode 100644 index 0000000000..f1916ec7d1 --- /dev/null +++ b/_static/copybutton.css @@ -0,0 +1,94 @@ +/* Copy buttons */ +button.copybtn { + position: absolute; + display: flex; + top: .3em; + right: .3em; + width: 1.7em; + height: 1.7em; + opacity: 0; + transition: opacity 0.3s, border .3s, background-color .3s; + user-select: none; + padding: 0; + border: none; + outline: none; + border-radius: 0.4em; + /* The colors that GitHub uses */ + border: #1b1f2426 1px solid; + background-color: #f6f8fa; + color: #57606a; +} + +button.copybtn.success { + border-color: #22863a; + color: #22863a; +} + +button.copybtn svg { + stroke: currentColor; + width: 1.5em; + height: 1.5em; + padding: 0.1em; +} + +div.highlight { + position: relative; +} + +/* Show the copybutton */ +.highlight:hover button.copybtn, button.copybtn.success { + opacity: 1; +} + +.highlight button.copybtn:hover { + background-color: rgb(235, 235, 235); +} + +.highlight button.copybtn:active { + background-color: rgb(187, 187, 187); +} + +/** + * A minimal CSS-only tooltip copied from: + * https://codepen.io/mildrenben/pen/rVBrpK + * + * To use, write HTML like the following: + * + *

Short

+ */ + .o-tooltip--left { + position: relative; + } + + .o-tooltip--left:after { + opacity: 0; + visibility: hidden; + position: absolute; + content: attr(data-tooltip); + padding: .2em; + font-size: .8em; + left: -.2em; + background: grey; + color: white; + white-space: nowrap; + z-index: 2; + border-radius: 2px; + transform: translateX(-102%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); +} + +.o-tooltip--left:hover:after { + display: block; + opacity: 1; + visibility: visible; + transform: translateX(-100%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); + transition-delay: .5s; +} + +/* By default the copy button shouldn't show up when printing a page */ +@media print { + button.copybtn { + display: none; + } +} diff --git a/_static/copybutton.js b/_static/copybutton.js new file mode 100644 index 0000000000..be65e437f9 --- /dev/null +++ b/_static/copybutton.js @@ -0,0 +1,248 @@ +// Localization support +const messages = { + 'en': { + 'copy': 'Copy', + 'copy_to_clipboard': 'Copy to clipboard', + 'copy_success': 'Copied!', + 'copy_failure': 'Failed to copy', + }, + 'es' : { + 'copy': 'Copiar', + 'copy_to_clipboard': 'Copiar al portapapeles', + 'copy_success': '¡Copiado!', + 'copy_failure': 'Error al copiar', + }, + 'de' : { + 'copy': 'Kopieren', + 'copy_to_clipboard': 'In die Zwischenablage kopieren', + 'copy_success': 'Kopiert!', + 'copy_failure': 'Fehler beim Kopieren', + }, + 'fr' : { + 'copy': 'Copier', + 'copy_to_clipboard': 'Copier dans le presse-papier', + 'copy_success': 'Copié !', + 'copy_failure': 'Échec de la copie', + }, + 'ru': { + 'copy': 'Скопировать', + 'copy_to_clipboard': 'Скопировать в буфер', + 'copy_success': 'Скопировано!', + 'copy_failure': 'Не удалось скопировать', + }, + 'zh-CN': { + 'copy': '复制', + 'copy_to_clipboard': '复制到剪贴板', + 'copy_success': '复制成功!', + 'copy_failure': '复制失败', + }, + 'it' : { + 'copy': 'Copiare', + 'copy_to_clipboard': 'Copiato negli appunti', + 'copy_success': 'Copiato!', + 'copy_failure': 'Errore durante la copia', + } +} + +let locale = 'en' +if( document.documentElement.lang !== undefined + && messages[document.documentElement.lang] !== undefined ) { + locale = document.documentElement.lang +} + +let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT; +if (doc_url_root == '#') { + doc_url_root = ''; +} + +/** + * SVG files for our copy buttons + */ +let iconCheck = ` + ${messages[locale]['copy_success']} + + +` + +// If the user specified their own SVG use that, otherwise use the default +let iconCopy = ``; +if (!iconCopy) { + iconCopy = ` + ${messages[locale]['copy_to_clipboard']} + + + +` +} + +/** + * Set up copy/paste for code blocks + */ + +const runWhenDOMLoaded = cb => { + if (document.readyState != 'loading') { + cb() + } else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', cb) + } else { + document.attachEvent('onreadystatechange', function() { + if (document.readyState == 'complete') cb() + }) + } +} + +const codeCellId = index => `codecell${index}` + +// Clears selected text since ClipboardJS will select the text when copying +const clearSelection = () => { + if (window.getSelection) { + window.getSelection().removeAllRanges() + } else if (document.selection) { + document.selection.empty() + } +} + +// Changes tooltip text for a moment, then changes it back +// We want the timeout of our `success` class to be a bit shorter than the +// tooltip and icon change, so that we can hide the icon before changing back. +var timeoutIcon = 2000; +var timeoutSuccessClass = 1500; + +const temporarilyChangeTooltip = (el, oldText, newText) => { + el.setAttribute('data-tooltip', newText) + el.classList.add('success') + // Remove success a little bit sooner than we change the tooltip + // So that we can use CSS to hide the copybutton first + setTimeout(() => el.classList.remove('success'), timeoutSuccessClass) + setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon) +} + +// Changes the copy button icon for two seconds, then changes it back +const temporarilyChangeIcon = (el) => { + el.innerHTML = iconCheck; + setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon) +} + +const addCopyButtonToCodeCells = () => { + // If ClipboardJS hasn't loaded, wait a bit and try again. This + // happens because we load ClipboardJS asynchronously. + if (window.ClipboardJS === undefined) { + setTimeout(addCopyButtonToCodeCells, 250) + return + } + + // Add copybuttons to all of our code cells + const COPYBUTTON_SELECTOR = 'div.notranslate:not(.prompt) div.highlight pre'; + const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR) + codeCells.forEach((codeCell, index) => { + const id = codeCellId(index) + codeCell.setAttribute('id', id) + + const clipboardButton = id => + `` + codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) + }) + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} + + +var copyTargetText = (trigger) => { + var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); + + // get filtered text + let exclude = '.linenos'; + + let text = filterText(target, exclude); + return formatCopyText(text, '', false, true, true, true, '', '') +} + + // Initialize with a callback so we can modify the text before copy + const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) + + // Update UI with error/success messages + clipboard.on('success', event => { + clearSelection() + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success']) + temporarilyChangeIcon(event.trigger) + }) + + clipboard.on('error', event => { + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure']) + }) +} + +runWhenDOMLoaded(addCopyButtonToCodeCells) \ No newline at end of file diff --git a/_static/copybutton_funcs.js b/_static/copybutton_funcs.js new file mode 100644 index 0000000000..dbe1aaad79 --- /dev/null +++ b/_static/copybutton_funcs.js @@ -0,0 +1,73 @@ +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +export function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} diff --git a/_static/css/brands.min.css b/_static/css/brands.min.css new file mode 100644 index 0000000000..714509e6f9 --- /dev/null +++ b/_static/css/brands.min.css @@ -0,0 +1,6 @@ +/*! + * Font Awesome Free 6.1.1 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2022 Fonticons, Inc. + */ +:host,:root{--fa-font-brands:normal 400 1em/1 "Font Awesome 6 Brands"}@font-face{font-family:"Font Awesome 6 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}.fa-brands,.fab{font-family:"Font Awesome 6 Brands";font-weight:400}.fa-42-group:before,.fa-innosoft:before{content:"\e080"}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-alipay:before{content:"\f642"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-amilia:before{content:"\f36d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-pay:before{content:"\f415"}.fa-artstation:before{content:"\f77a"}.fa-asymmetrik:before{content:"\f372"}.fa-atlassian:before{content:"\f77b"}.fa-audible:before{content:"\f373"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-aws:before{content:"\f375"}.fa-bandcamp:before{content:"\f2d5"}.fa-battle-net:before{content:"\f835"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bilibili:before{content:"\e3d9"}.fa-bimobject:before{content:"\f378"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bootstrap:before{content:"\f836"}.fa-bots:before{content:"\e340"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-buromobelexperte:before{content:"\f37f"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-cloudflare:before{content:"\e07d"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cmplid:before{content:"\e360"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cotton-bureau:before{content:"\f89e"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-critical-role:before{content:"\f6c9"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dailymotion:before{content:"\e052"}.fa-dashcube:before{content:"\f210"}.fa-deezer:before{content:"\e077"}.fa-delicious:before{content:"\f1a5"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dhl:before{content:"\f790"}.fa-diaspora:before{content:"\f791"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-draft2digital:before{content:"\f396"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drupal:before{content:"\f1a9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edge-legacy:before{content:"\e078"}.fa-elementor:before{content:"\f430"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envira:before{content:"\f299"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-etsy:before{content:"\f2d7"}.fa-evernote:before{content:"\f839"}.fa-expeditedssl:before{content:"\f23e"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-figma:before{content:"\f799"}.fa-firefox:before{content:"\f269"}.fa-firefox-browser:before{content:"\e007"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-fly:before{content:"\f417"}.fa-font-awesome-flag:before,.fa-font-awesome-logo-full:before,.fa-font-awesome:before{content:"\f2b4"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-fulcrum:before{content:"\f50b"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-gofore:before{content:"\f3a7"}.fa-golang:before{content:"\e40f"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-pay:before{content:"\e079"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guilded:before{content:"\e07e"}.fa-gulp:before{content:"\f3ae"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hashnode:before{content:"\e499"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-hive:before{content:"\e07f"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-hotjar:before{content:"\f3b1"}.fa-houzz:before{content:"\f27c"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-ideal:before{content:"\e013"}.fa-imdb:before{content:"\f2d8"}.fa-instagram:before{content:"\f16d"}.fa-instagram-square:before{content:"\e055"}.fa-instalod:before{content:"\e081"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joomla:before{content:"\f1aa"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaggle:before{content:"\f5fa"}.fa-keybase:before{content:"\f4f5"}.fa-keycdn:before{content:"\f3ba"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-korvue:before{content:"\f42f"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-leanpub:before{content:"\f212"}.fa-less:before{content:"\f41d"}.fa-line:before{content:"\f3c0"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-mailchimp:before{content:"\f59e"}.fa-mandalorian:before{content:"\f50f"}.fa-markdown:before{content:"\f60f"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medapps:before{content:"\f3c6"}.fa-medium-m:before,.fa-medium:before{content:"\f23a"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-mendeley:before{content:"\f7b3"}.fa-microblog:before{content:"\e01a"}.fa-microsoft:before{content:"\f3ca"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mixer:before{content:"\e056"}.fa-mizuni:before{content:"\f3cc"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-nfc-directional:before{content:"\e530"}.fa-nfc-symbol:before{content:"\e531"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-octopus-deploy:before{content:"\e082"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-old-republic:before{content:"\f510"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-padlet:before{content:"\e4a0"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-palfed:before{content:"\f3d8"}.fa-patreon:before{content:"\f3d9"}.fa-paypal:before{content:"\f1ed"}.fa-perbyte:before{content:"\e083"}.fa-periscope:before{content:"\f3da"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-square:before{content:"\e01e"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pix:before{content:"\e43a"}.fa-playstation:before{content:"\f3df"}.fa-product-hunt:before{content:"\f288"}.fa-pushed:before{content:"\f3e1"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-r-project:before{content:"\f4f7"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-renren:before{content:"\f18b"}.fa-replyd:before{content:"\f3e6"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-rev:before{content:"\f5b2"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-rust:before{content:"\e07a"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-schlix:before{content:"\f3ea"}.fa-screenpal:before{content:"\e570"}.fa-scribd:before{content:"\f28a"}.fa-searchengin:before{content:"\f3eb"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-servicestack:before{content:"\f3ec"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shopify:before{content:"\e057"}.fa-shopware:before{content:"\f5b5"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sith:before{content:"\f512"}.fa-sitrox:before{content:"\e44a"}.fa-sketch:before{content:"\f7c6"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack-hash:before,.fa-slack:before{content:"\f198"}.fa-slideshare:before{content:"\f1e7"}.fa-snapchat-ghost:before,.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-square:before{content:"\f2ad"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spotify:before{content:"\f1bc"}.fa-square-font-awesome:before{content:"\f425"}.fa-font-awesome-alt:before,.fa-square-font-awesome-stroke:before{content:"\f35c"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-sticker-mule:before{content:"\f3f7"}.fa-strava:before{content:"\f428"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-superpowers:before{content:"\f2dd"}.fa-supple:before{content:"\f3f9"}.fa-suse:before{content:"\f7d6"}.fa-swift:before{content:"\f8e1"}.fa-symfony:before{content:"\f83d"}.fa-teamspeak:before{content:"\f4f9"}.fa-telegram-plane:before,.fa-telegram:before{content:"\f2c6"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-the-red-yeti:before{content:"\f69d"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-think-peaks:before{content:"\f731"}.fa-tiktok:before{content:"\e07b"}.fa-trade-federation:before{content:"\f513"}.fa-trello:before{content:"\f181"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-uncharted:before{content:"\e084"}.fa-uniregistry:before{content:"\f404"}.fa-unity:before{content:"\e049"}.fa-unsplash:before{content:"\e07c"}.fa-untappd:before{content:"\f405"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-vaadin:before{content:"\f408"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-viber:before{content:"\f409"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-vuejs:before{content:"\f41f"}.fa-watchman-monitoring:before{content:"\e087"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-whmcs:before{content:"\f40d"}.fa-wikipedia-w:before{content:"\f266"}.fa-windows:before{content:"\f17a"}.fa-wirsindhandwerk:before,.fa-wsh:before{content:"\e2d0"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wodu:before{content:"\e088"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"} \ No newline at end of file diff --git a/_static/css/custom.css b/_static/css/custom.css new file mode 100644 index 0000000000..42f56b2532 --- /dev/null +++ b/_static/css/custom.css @@ -0,0 +1,55 @@ +.fa.fa-2x { + font-size: 36px; +} + +/* Decrease the padding of button block in panels */ +div.card-body.card-body-less-padding { + padding: 0.25em; +} + +/* Disable theme toggle */ +div.theme-toggle-container.theme-toggle-content { + display: none; +} + +/* Proper distance at the bottom of TOC tree */ +.sidebar-container > .sidebar-sticky > .sidebar-scroll > .sidebar-tree { + padding-bottom: 1em; +} + +/* Admonition for drop-down like details component */ + +.admonition.admonition-details { + padding: 0 0 0 0; + margin: 0 auto; +} + +.admonition.admonition-details.active > :not(.admonition-title) { + display: inherit; +} + +.admonition.admonition-details > :not(.admonition-title) { + display: none; +} + +.admonition.admonition-details > p.admonition-title, p.topic-title { + margin: 0 0 0 0; +} + +.admonition.admonition-details > blockquote { + padding: 0; +} + +.admonition.admonition-details > blockquote > div > div[class^=highlight-] { + margin: 0; +} + +.admonition.admonition-details > blockquote > div > div[class^=table-wrapper] { + margin-top: 0; + margin-bottom: 0; + padding: 0 0 0 0; +} + +.admonition.admonition-details > blockquote > div > div[class^=table-wrapper] > table { + min-width: 100%; +} diff --git a/_static/css/index.css b/_static/css/index.css new file mode 100644 index 0000000000..bfeef1af4f --- /dev/null +++ b/_static/css/index.css @@ -0,0 +1,442 @@ +body { + font-family: "Lato", sans-serif; + margin: 0; +} + +/* nav { + position: sticky; + top: 0; + transition: background-color .25s; +} */ + +.list-style-none { + list-style: none; +} + +.padding-left-right-20 { + padding: 0 20px; +} + +nav i { + padding: 0 20px; +} + +h4 > i { + padding-right: 10px; +} + +p i { + padding-left: 6px; +} + +a { + text-decoration: none; +} + +.button { + border: none; + width: 120px; + height: 50px; + border-radius: 7px; + font-weight: 500; + font-size: 0.95rem; +} + +.button:hover { + cursor: pointer; +} + +.background-gradient-color { + background: linear-gradient( + 270deg, + rgba(35, 128, 242, 0.5) 15.55%, + rgba(35, 128, 242, 0) 99.31% + ), + rgba(49, 227, 222, 0.7); +} + +/* html:not([data-scroll='0']) { + .navigator.background-gradient-color { + background: #f5f5f5; + box-shadow: 0 0 .5em rgba(0, 0, 0, .5); + } +} */ + +.main-content { + max-width: max(70%, 1080px); +} + +.nav-link { + color: white; +} + +.nav-link:hover { + color: blue; +} + +.flex { + display: flex; +} + +.flex-column { + flex-direction: column; +} + +.align-items-center { + align-items: center; +} + +.vertical-align-center { + margin: 0 auto; +} + +.justify-content-space-around { + justify-content: space-around; +} + +.justify-content-space-between { + justify-content: space-between; +} + +.justify-content-center { + justify-content: center; +} + +.gap-sm { + gap: 20px; +} + +.navbar { + padding: 30px 3%; + margin: 0 auto; +} + +.logo { + width: 140px; + height: 40px; + object-fit: contain; +} + +.logo > a > img { + max-width: 100%; + max-height: 100%; +} + +.text-center { + text-align: center; +} + +.text-bold { + font-size: 1.1rem; + font-weight: 600; +} + +.text-underline { + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: 10px; +} + +.text-white { + color: white; +} + +.text-black { + color: black; +} + +.padding-bottom-20 { + padding-bottom: 20px; +} + +.padding-bottom-10 { + padding-bottom: 10px; +} + +.btn-container { + display: flex; + gap: 15px; + margin-top: 30px; +} + +.btn-primary { + background-color: #2380f2; + color: white; +} + +.btn-secondary { + background-color: #f1f1f1; + color: #476581; +} + +.termynal { + margin-top: 30px; + text-align: left; +} + +.breaking-word-all { + word-break: break-all; +} + +.font-weight-300 { + font-weight: 300; +} + +.hero-container { + padding: 100px 3% 100px; +} + +.hero-text { + font-size: 3rem; + color: #f8f8f8; +} + +.hero-text-secondary { + width: 45%; + margin-top: -15px; + color: white; + font-weight: 300; + font-size: larger; +} + +.feature-section { + padding: 100px 3% 25px; + font-size: larger; +} + +.feature-section > .main-content { + margin: 0 auto; +} + +.feature-item { + width: 45%; +} + +.feature-item > p { + font-weight: 300; +} + +.feature-item-sm > h4 { + display: inline-block; + height: 1rem; +} + +.feature-item-sm > p { + font-weight: 100; +} + +.feature-item-sm > span { + margin: auto 1em 1em 0; + text-align: right; +} + +.feature-item-sm { + width: 18em; + height: 15em; + background-color: #ffffff; + padding: 0 0.5em 0 0.5em; + display: flex; + flex-direction: column; + border-radius: 0 1.25em 0 1.25em; +} + +.use-case-section { + background-color: ghostwhite; +} + +.banner { + padding: 120px 0px 70px; + font-weight: 500; + font-size: 1.5rem; + margin: 0 auto; +} + +.banner-link { + text-decoration: underline; + color: black; +} + +.footer { + padding: 50px 20%; + color: white; + background-color: black; + font-weight: 300; +} + +.footer-info-text { + font-size: 0.85rem; + padding-top: 10px; +} + +.footer-link-text { + color: white; + text-decoration: underline; +} + +.img-container { + width: 300px; + height: 100px; + object-fit: cover; + margin: 0 auto; +} + +.media-section { + display: flex; +} + +.navbar ul { + display: flex; +} + +#mobile-menu { + display: none; +} + +.text-wrapping { + overflow-wrap: break-word; + word-wrap: break-word; + -ms-word-break: break-all; + word-break: break-word; + -ms-hyphens: auto; + -moz-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +@media only screen and (max-width: 786px) { + .media-section { + display: none; + } + + #mobile-menu { + display: block; + } + + .menu-toggle { + justify-self: end; + } + + nav { + position: fixed; + width: 100vw; + } + + .navbar { + width: 90%; + margin: 0 auto; + } + + .navbar-linkss { + display: none; + } + + .menu-toggle, .bar { + display: block; + cursor: pointer; + } + + .menu-toggle .bar { + width: 25px; + height: 3px; + background-color: #3f3f3f; + margin: 5px auto; + -webkit-transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; + transition: all 0.3s ease-in-out; + } + + .navbar ul { + display: none; + } + + .nav-link-container { + display: flex; + flex-direction: column; + position: fixed; + justify-content: start; + top: 60px; + bottom: 0; + background-color: #fff; + width: 100vw; + height: calc(100% - 60px); + transform: translate(-12%); + text-align: center; + overflow: hidden; + } + + .navbar li { + padding: 25px; + } + + .navbar li:first-child { + margin-top: 50px; + } + + .navbar .nav-link { + color: black; + font-size: 1.3rem; + } + + #mobile-menu.is-active .bar:nth-child(2) { + opacity: 0; + } + + #mobile-menu.is-active .bar:nth-child(1) { + -webkit-transform: translateY(8px) rotate(45deg); + -ms-transform: translateY(8px) rotate(45deg); + -o-transform: translateY(8px) rotate(45deg); + transform: translateY(8px) rotate(45deg); + } + + #mobile-menu.is-active .bar:nth-child(3) { + -webkit-transform: translateY(-8px) rotate(-45deg); + -ms-transform: translateY(-8px) rotate(-45deg); + -o-transform: translateY(-8px) rotate(-45deg); + transform: translateY(-8px) rotate(-45deg); + } + + .hero-container { + width: 90%; + } + + .hero-text-secondary { + width: 70%; + } + + .footer { + padding: 50px 5%; + } + + .footer-container { + display: grid; + grid-template-columns: repeat(2, 1fr); + row-gap: 5px; + gap: 5px; + } + + .feature-container { + display: grid; + grid-template-columns: repeat(1, 1fr); + padding-bottom: 1em; + } + + .feature-section-mobile { + padding: 100px 3% 0px; + } + + .feature-section { + padding: 100px 3% 0px; + font-size: initial; + } + + .feature-item { + width: 80%; + margin: 0 auto; + } + + .feature-item-sm { + margin: 0 auto; + width: 80%; + height: auto; + } +} diff --git a/_static/css/panels.css b/_static/css/panels.css new file mode 100644 index 0000000000..5cf775f167 --- /dev/null +++ b/_static/css/panels.css @@ -0,0 +1,147 @@ +/* Referred and derived from https://github.com/flyteorg/furo/blob/main/src/furo/assets/styles/flyte.css */ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: bold; +} + +.caption-text { + font-size: 15px; + /* color: #696969; */ + color: #333333; +} + +div.sphinx-bs .card { + flex-direction: row; +} + +/* sphinx-panels custom styles */ +div.sphinx-bs .card-header { + border-bottom: none; + background-color: var(--color-background-primary); + display: flex; + align-items: center; + justify-content: left; + width: 28%; + float: left; +} + +.sphinx-bs .card-header:first-child { + border-radius: calc(0.25rem - 1px) 0 0 calc(0.25rem - 1px); +} + +div.sphinx-bs .card-header .sphinx-bs.btn, +div.sphinx-bs .card-body .sphinx-bs.btn, +div.sphinx-bs .card-header p.card-text { + font-size: 1rem; + text-decoration: none; + word-spacing: 2.5px; + color: var(--color-sidebar-link-text); +} + +div.sphinx-bs .card-header p.card-text a { + text-align: left; +} + +.sphinx-bs.btn:focus { + box-shadow: none; +} + +div.sphinx-bs .card-body { + width: 72%; + float: left; +} + +.sphinx-bs .card-body .fa { + color: var(--color-sidebar-link-text); +} + +.sphinx-bs .card-body:hover .fa { + color: var(--color-link--hover); +} + +.sphinx-bs .card-body .fa { + font-size: 2rem; +} + +div.sphinx-bs .card:hover { + box-shadow: none !important; + border-color: #cca9ff; +} + +div.sphinx-bs .card:hover .card-header { + background-color: #f2e9ff; + color: #fff; +} + +body[data-theme="dark"] div.sphinx-bs .card:hover { + border-color: #2a144a; +} + +body[data-theme="dark"] div.sphinx-bs .card:hover .card-header { + background-color: #2a144a; + color: #fff; +} + +/* make sure hover style is consistent if user prefers dark theme at OS level */ +@media (prefers-color-scheme: dark) { + body:not([data-theme="light"]) div.sphinx-bs .card:hover { + border-color: #2a144a; + } + body:not([data-theme="light"]) div.sphinx-bs .card:hover .card-header { + background-color: #2a144a; + color: #fff; + } +} + +div.sphinx-bs .card:hover .sphinx-bs.btn { + color: var(--color-link); +} + +div.sphinx-bs .card:hover .card-body .sphinx-bs.btn { + color: var(--color-link--hover); +} + +.getting-started-panels div.sphinx-bs .sphinx-bs.btn:hover { + border-color: var(--color-link); + background-color: #9d68e4; + color: #ffffff; +} + +div.sphinx-bs .card { + background-color: var(--color-background-secondary); + border: 1px solid var(--color-background-border); +} + +.center-card-content p { + margin: auto !important; +} + +.sphinx-tabs { + padding-top: 10px; +} + +.sphinx-tabs-tab { + color: var(--color-link); +} + +/* sphinx tabs */ +.sphinx-tabs-tab[aria-selected="true"] { + background-color: var(--color-background-secondary); + border: 1px solid var(--color-background-border); + border-bottom: 1px solid var(--color-background-secondary); +} + +.sphinx-tabs-panel { + border: 1px solid var(--color-background-border); + background: var(--color-background-secondary); + border-top: 0; +} + +[role="tablist"] { + border-bottom: 1px solid var(--color-background-border); +} diff --git a/_static/css/termynal.css b/_static/css/termynal.css new file mode 100644 index 0000000000..76a7d02cf2 --- /dev/null +++ b/_static/css/termynal.css @@ -0,0 +1,101 @@ +/** + * termynal.js + * + * @author Ines Montani + * @version 0.0.1 + * @license MIT + */ + + :root { + --color-bg: #252a33; + --color-text: #eee; + --color-text-subtle: #a2a2a2; +} + +[data-termynal] { + width: 750px; + max-width: 100%; + background: var(--color-bg); + color: var(--color-text); + font-size: 18px; + font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; + border-radius: 4px; + padding: 75px 45px 35px; + position: relative; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +[data-termynal]:before { + content: ''; + position: absolute; + top: 15px; + left: 15px; + display: inline-block; + width: 15px; + height: 15px; + border-radius: 50%; + /* A little hack to display the window buttons in one pseudo element. */ + background: #d9515d; + -webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; + box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; +} + +[data-termynal]:after { + content: 'bash'; + position: absolute; + color: var(--color-text-subtle); + top: 5px; + left: 0; + width: 100%; + text-align: center; +} + +[data-ty] { + display: block; + line-height: 2; +} + +[data-ty]:before { + /* Set up defaults and ensure empty lines are displayed. */ + content: ''; + display: inline-block; + vertical-align: middle; +} + +[data-ty="input"]:before, +[data-ty-prompt]:before { + margin-right: 0.75em; + color: var(--color-text-subtle); +} + +[data-ty="input"]:before { + content: '$'; +} + +[data-ty][data-ty-prompt]:before { + content: attr(data-ty-prompt); +} + +[data-ty-cursor]:after { + content: attr(data-ty-cursor); + font-family: monospace; + margin-left: 0.5em; + -webkit-animation: blink 1s infinite; + animation: blink 1s infinite; +} + + +/* Cursor animation */ + +@-webkit-keyframes blink { + 50% { + opacity: 0; + } +} + +@keyframes blink { + 50% { + opacity: 0; + } +} diff --git a/_static/css/v4-shims.min.css b/_static/css/v4-shims.min.css new file mode 100644 index 0000000000..f742adcbe9 --- /dev/null +++ b/_static/css/v4-shims.min.css @@ -0,0 +1,6 @@ +/*! + * Font Awesome Free 6.1.1 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2022 Fonticons, Inc. + */ +.fa.fa-glass:before{content:"\f000"}.fa.fa-envelope-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-envelope-o:before{content:"\f0e0"}.fa.fa-star-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-star-o:before{content:"\f005"}.fa.fa-close:before,.fa.fa-remove:before{content:"\f00d"}.fa.fa-gear:before{content:"\f013"}.fa.fa-trash-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-trash-o:before{content:"\f2ed"}.fa.fa-home:before{content:"\f015"}.fa.fa-file-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-o:before{content:"\f15b"}.fa.fa-clock-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-clock-o:before{content:"\f017"}.fa.fa-arrow-circle-o-down{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-arrow-circle-o-down:before{content:"\f358"}.fa.fa-arrow-circle-o-up{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-arrow-circle-o-up:before{content:"\f35b"}.fa.fa-play-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-play-circle-o:before{content:"\f144"}.fa.fa-repeat:before,.fa.fa-rotate-right:before{content:"\f01e"}.fa.fa-refresh:before{content:"\f021"}.fa.fa-list-alt{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-list-alt:before{content:"\f022"}.fa.fa-dedent:before{content:"\f03b"}.fa.fa-video-camera:before{content:"\f03d"}.fa.fa-picture-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-picture-o:before{content:"\f03e"}.fa.fa-photo{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-photo:before{content:"\f03e"}.fa.fa-image{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-image:before{content:"\f03e"}.fa.fa-map-marker:before{content:"\f3c5"}.fa.fa-pencil-square-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-pencil-square-o:before{content:"\f044"}.fa.fa-edit{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-edit:before{content:"\f044"}.fa.fa-share-square-o:before{content:"\f14d"}.fa.fa-check-square-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-check-square-o:before{content:"\f14a"}.fa.fa-arrows:before{content:"\f0b2"}.fa.fa-times-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-times-circle-o:before{content:"\f057"}.fa.fa-check-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-check-circle-o:before{content:"\f058"}.fa.fa-mail-forward:before{content:"\f064"}.fa.fa-expand:before{content:"\f424"}.fa.fa-compress:before{content:"\f422"}.fa.fa-eye,.fa.fa-eye-slash{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-warning:before{content:"\f071"}.fa.fa-calendar:before{content:"\f073"}.fa.fa-arrows-v:before{content:"\f338"}.fa.fa-arrows-h:before{content:"\f337"}.fa.fa-bar-chart-o:before,.fa.fa-bar-chart:before{content:"\e0e3"}.fa.fa-facebook-square,.fa.fa-twitter-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-gears:before{content:"\f085"}.fa.fa-thumbs-o-up{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-thumbs-o-up:before{content:"\f164"}.fa.fa-thumbs-o-down{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-thumbs-o-down:before{content:"\f165"}.fa.fa-heart-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-heart-o:before{content:"\f004"}.fa.fa-sign-out:before{content:"\f2f5"}.fa.fa-linkedin-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-linkedin-square:before{content:"\f08c"}.fa.fa-thumb-tack:before{content:"\f08d"}.fa.fa-external-link:before{content:"\f35d"}.fa.fa-sign-in:before{content:"\f2f6"}.fa.fa-github-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-lemon-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-lemon-o:before{content:"\f094"}.fa.fa-square-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-square-o:before{content:"\f0c8"}.fa.fa-bookmark-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-bookmark-o:before{content:"\f02e"}.fa.fa-facebook,.fa.fa-twitter{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-facebook:before{content:"\f39e"}.fa.fa-facebook-f{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-facebook-f:before{content:"\f39e"}.fa.fa-github{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-credit-card{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-feed:before{content:"\f09e"}.fa.fa-hdd-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hdd-o:before{content:"\f0a0"}.fa.fa-hand-o-right{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-o-right:before{content:"\f0a4"}.fa.fa-hand-o-left{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-o-left:before{content:"\f0a5"}.fa.fa-hand-o-up{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-o-up:before{content:"\f0a6"}.fa.fa-hand-o-down{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-o-down:before{content:"\f0a7"}.fa.fa-globe:before{content:"\f57d"}.fa.fa-tasks:before{content:"\f828"}.fa.fa-arrows-alt:before{content:"\f31e"}.fa.fa-group:before{content:"\f0c0"}.fa.fa-chain:before{content:"\f0c1"}.fa.fa-cut:before{content:"\f0c4"}.fa.fa-files-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-files-o:before{content:"\f0c5"}.fa.fa-floppy-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-floppy-o:before{content:"\f0c7"}.fa.fa-save{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-save:before{content:"\f0c7"}.fa.fa-navicon:before,.fa.fa-reorder:before{content:"\f0c9"}.fa.fa-magic:before{content:"\e2ca"}.fa.fa-google-plus,.fa.fa-google-plus-square,.fa.fa-pinterest,.fa.fa-pinterest-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-google-plus:before{content:"\f0d5"}.fa.fa-money:before{content:"\f3d1"}.fa.fa-unsorted:before{content:"\f0dc"}.fa.fa-sort-desc:before{content:"\f0dd"}.fa.fa-sort-asc:before{content:"\f0de"}.fa.fa-linkedin{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-linkedin:before{content:"\f0e1"}.fa.fa-rotate-left:before{content:"\f0e2"}.fa.fa-legal:before{content:"\f0e3"}.fa.fa-dashboard:before,.fa.fa-tachometer:before{content:"\f625"}.fa.fa-comment-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-comment-o:before{content:"\f075"}.fa.fa-comments-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-comments-o:before{content:"\f086"}.fa.fa-flash:before{content:"\f0e7"}.fa.fa-clipboard:before{content:"\f0ea"}.fa.fa-lightbulb-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-lightbulb-o:before{content:"\f0eb"}.fa.fa-exchange:before{content:"\f362"}.fa.fa-cloud-download:before{content:"\f0ed"}.fa.fa-cloud-upload:before{content:"\f0ee"}.fa.fa-bell-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-bell-o:before{content:"\f0f3"}.fa.fa-cutlery:before{content:"\f2e7"}.fa.fa-file-text-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-text-o:before{content:"\f15c"}.fa.fa-building-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-building-o:before{content:"\f1ad"}.fa.fa-hospital-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hospital-o:before{content:"\f0f8"}.fa.fa-tablet:before{content:"\f3fa"}.fa.fa-mobile-phone:before,.fa.fa-mobile:before{content:"\f3cd"}.fa.fa-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-circle-o:before{content:"\f111"}.fa.fa-mail-reply:before{content:"\f3e5"}.fa.fa-github-alt{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-folder-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-folder-o:before{content:"\f07b"}.fa.fa-folder-open-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-folder-open-o:before{content:"\f07c"}.fa.fa-smile-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-smile-o:before{content:"\f118"}.fa.fa-frown-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-frown-o:before{content:"\f119"}.fa.fa-meh-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-meh-o:before{content:"\f11a"}.fa.fa-keyboard-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-keyboard-o:before{content:"\f11c"}.fa.fa-flag-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-flag-o:before{content:"\f024"}.fa.fa-mail-reply-all:before{content:"\f122"}.fa.fa-star-half-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-star-half-o:before{content:"\f5c0"}.fa.fa-star-half-empty{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-star-half-empty:before{content:"\f5c0"}.fa.fa-star-half-full{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-star-half-full:before{content:"\f5c0"}.fa.fa-code-fork:before{content:"\f126"}.fa.fa-chain-broken:before,.fa.fa-unlink:before{content:"\f127"}.fa.fa-calendar-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-calendar-o:before{content:"\f133"}.fa.fa-css3,.fa.fa-html5,.fa.fa-maxcdn{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-unlock-alt:before{content:"\f09c"}.fa.fa-minus-square-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-minus-square-o:before{content:"\f146"}.fa.fa-level-up:before{content:"\f3bf"}.fa.fa-level-down:before{content:"\f3be"}.fa.fa-pencil-square:before{content:"\f14b"}.fa.fa-external-link-square:before{content:"\f360"}.fa.fa-compass{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-caret-square-o-down{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-caret-square-o-down:before{content:"\f150"}.fa.fa-toggle-down{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-toggle-down:before{content:"\f150"}.fa.fa-caret-square-o-up{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-caret-square-o-up:before{content:"\f151"}.fa.fa-toggle-up{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-toggle-up:before{content:"\f151"}.fa.fa-caret-square-o-right{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-caret-square-o-right:before{content:"\f152"}.fa.fa-toggle-right{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-toggle-right:before{content:"\f152"}.fa.fa-eur:before,.fa.fa-euro:before{content:"\f153"}.fa.fa-gbp:before{content:"\f154"}.fa.fa-dollar:before,.fa.fa-usd:before{content:"\24"}.fa.fa-inr:before,.fa.fa-rupee:before{content:"\e1bc"}.fa.fa-cny:before,.fa.fa-jpy:before,.fa.fa-rmb:before,.fa.fa-yen:before{content:"\f157"}.fa.fa-rouble:before,.fa.fa-rub:before,.fa.fa-ruble:before{content:"\f158"}.fa.fa-krw:before,.fa.fa-won:before{content:"\f159"}.fa.fa-bitcoin,.fa.fa-btc{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-bitcoin:before{content:"\f15a"}.fa.fa-file-text:before{content:"\f15c"}.fa.fa-sort-alpha-asc:before{content:"\f15d"}.fa.fa-sort-alpha-desc:before{content:"\f881"}.fa.fa-sort-amount-asc:before{content:"\f884"}.fa.fa-sort-amount-desc:before{content:"\f160"}.fa.fa-sort-numeric-asc:before{content:"\f162"}.fa.fa-sort-numeric-desc:before{content:"\f886"}.fa.fa-xing,.fa.fa-xing-square,.fa.fa-youtube,.fa.fa-youtube-play,.fa.fa-youtube-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-youtube-play:before{content:"\f167"}.fa.fa-adn,.fa.fa-bitbucket,.fa.fa-bitbucket-square,.fa.fa-dropbox,.fa.fa-flickr,.fa.fa-instagram,.fa.fa-stack-overflow{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-bitbucket-square:before{content:"\f171"}.fa.fa-tumblr,.fa.fa-tumblr-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-long-arrow-down:before{content:"\f309"}.fa.fa-long-arrow-up:before{content:"\f30c"}.fa.fa-long-arrow-left:before{content:"\f30a"}.fa.fa-long-arrow-right:before{content:"\f30b"}.fa.fa-android,.fa.fa-apple,.fa.fa-dribbble,.fa.fa-foursquare,.fa.fa-gittip,.fa.fa-gratipay,.fa.fa-linux,.fa.fa-skype,.fa.fa-trello,.fa.fa-windows{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-gittip:before{content:"\f184"}.fa.fa-sun-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-sun-o:before{content:"\f185"}.fa.fa-moon-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-moon-o:before{content:"\f186"}.fa.fa-pagelines,.fa.fa-renren,.fa.fa-stack-exchange,.fa.fa-vk,.fa.fa-weibo{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-arrow-circle-o-right{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-arrow-circle-o-right:before{content:"\f35a"}.fa.fa-arrow-circle-o-left{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-arrow-circle-o-left:before{content:"\f359"}.fa.fa-caret-square-o-left{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-caret-square-o-left:before{content:"\f191"}.fa.fa-toggle-left{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-toggle-left:before{content:"\f191"}.fa.fa-dot-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-dot-circle-o:before{content:"\f192"}.fa.fa-vimeo-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-try:before,.fa.fa-turkish-lira:before{content:"\e2bb"}.fa.fa-plus-square-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-plus-square-o:before{content:"\f0fe"}.fa.fa-openid,.fa.fa-slack,.fa.fa-wordpress{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-bank:before,.fa.fa-institution:before{content:"\f19c"}.fa.fa-mortar-board:before{content:"\f19d"}.fa.fa-behance,.fa.fa-behance-square,.fa.fa-delicious,.fa.fa-digg,.fa.fa-drupal,.fa.fa-google,.fa.fa-joomla,.fa.fa-pied-piper-alt,.fa.fa-pied-piper-pp,.fa.fa-reddit,.fa.fa-reddit-square,.fa.fa-steam,.fa.fa-steam-square,.fa.fa-stumbleupon,.fa.fa-stumbleupon-circle,.fa.fa-yahoo{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-automobile:before{content:"\f1b9"}.fa.fa-cab:before{content:"\f1ba"}.fa.fa-deviantart,.fa.fa-soundcloud,.fa.fa-spotify{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-file-pdf-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-pdf-o:before{content:"\f1c1"}.fa.fa-file-word-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-word-o:before{content:"\f1c2"}.fa.fa-file-excel-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-excel-o:before{content:"\f1c3"}.fa.fa-file-powerpoint-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-powerpoint-o:before{content:"\f1c4"}.fa.fa-file-image-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-image-o:before{content:"\f1c5"}.fa.fa-file-photo-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-photo-o:before{content:"\f1c5"}.fa.fa-file-picture-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-picture-o:before{content:"\f1c5"}.fa.fa-file-archive-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-archive-o:before{content:"\f1c6"}.fa.fa-file-zip-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-zip-o:before{content:"\f1c6"}.fa.fa-file-audio-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-audio-o:before{content:"\f1c7"}.fa.fa-file-sound-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-sound-o:before{content:"\f1c7"}.fa.fa-file-video-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-video-o:before{content:"\f1c8"}.fa.fa-file-movie-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-movie-o:before{content:"\f1c8"}.fa.fa-file-code-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-code-o:before{content:"\f1c9"}.fa.fa-codepen,.fa.fa-jsfiddle,.fa.fa-vine{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-life-bouy:before,.fa.fa-life-buoy:before,.fa.fa-life-saver:before,.fa.fa-support:before{content:"\f1cd"}.fa.fa-circle-o-notch:before{content:"\f1ce"}.fa.fa-ra,.fa.fa-rebel{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-ra:before{content:"\f1d0"}.fa.fa-resistance{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-resistance:before{content:"\f1d0"}.fa.fa-empire,.fa.fa-ge{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-ge:before{content:"\f1d1"}.fa.fa-git,.fa.fa-git-square,.fa.fa-hacker-news,.fa.fa-y-combinator-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-y-combinator-square:before{content:"\f1d4"}.fa.fa-yc-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-yc-square:before{content:"\f1d4"}.fa.fa-qq,.fa.fa-tencent-weibo,.fa.fa-wechat,.fa.fa-weixin{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-wechat:before{content:"\f1d7"}.fa.fa-send:before{content:"\f1d8"}.fa.fa-paper-plane-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-paper-plane-o:before{content:"\f1d8"}.fa.fa-send-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-send-o:before{content:"\f1d8"}.fa.fa-circle-thin{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-circle-thin:before{content:"\f111"}.fa.fa-header:before{content:"\f1dc"}.fa.fa-futbol-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-futbol-o:before{content:"\f1e3"}.fa.fa-soccer-ball-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-soccer-ball-o:before{content:"\f1e3"}.fa.fa-slideshare,.fa.fa-twitch,.fa.fa-yelp{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-newspaper-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-newspaper-o:before{content:"\f1ea"}.fa.fa-cc-amex,.fa.fa-cc-discover,.fa.fa-cc-mastercard,.fa.fa-cc-paypal,.fa.fa-cc-stripe,.fa.fa-cc-visa,.fa.fa-google-wallet,.fa.fa-paypal{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-bell-slash-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-bell-slash-o:before{content:"\f1f6"}.fa.fa-trash:before{content:"\f2ed"}.fa.fa-copyright{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-eyedropper:before{content:"\f1fb"}.fa.fa-area-chart:before{content:"\f1fe"}.fa.fa-pie-chart:before{content:"\f200"}.fa.fa-line-chart:before{content:"\f201"}.fa.fa-angellist,.fa.fa-ioxhost,.fa.fa-lastfm,.fa.fa-lastfm-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-cc{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-cc:before{content:"\f20a"}.fa.fa-ils:before,.fa.fa-shekel:before,.fa.fa-sheqel:before{content:"\f20b"}.fa.fa-buysellads,.fa.fa-connectdevelop,.fa.fa-dashcube,.fa.fa-forumbee,.fa.fa-leanpub,.fa.fa-sellsy,.fa.fa-shirtsinbulk,.fa.fa-simplybuilt,.fa.fa-skyatlas{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-diamond{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-diamond:before{content:"\f3a5"}.fa.fa-intersex:before,.fa.fa-transgender:before{content:"\f224"}.fa.fa-transgender-alt:before{content:"\f225"}.fa.fa-facebook-official{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-facebook-official:before{content:"\f09a"}.fa.fa-pinterest-p,.fa.fa-whatsapp{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-hotel:before{content:"\f236"}.fa.fa-medium,.fa.fa-viacoin,.fa.fa-y-combinator,.fa.fa-yc{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-yc:before{content:"\f23b"}.fa.fa-expeditedssl,.fa.fa-opencart,.fa.fa-optin-monster{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-battery-4:before,.fa.fa-battery:before{content:"\f240"}.fa.fa-battery-3:before{content:"\f241"}.fa.fa-battery-2:before{content:"\f242"}.fa.fa-battery-1:before{content:"\f243"}.fa.fa-battery-0:before{content:"\f244"}.fa.fa-object-group,.fa.fa-object-ungroup,.fa.fa-sticky-note-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-sticky-note-o:before{content:"\f249"}.fa.fa-cc-diners-club,.fa.fa-cc-jcb{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-clone{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hourglass-o:before{content:"\f252"}.fa.fa-hourglass-1:before{content:"\f251"}.fa.fa-hourglass-2:before,.fa.fa-hourglass-half:before{content:"\f254"}.fa.fa-hourglass-3:before{content:"\f253"}.fa.fa-hand-rock-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-rock-o:before{content:"\f255"}.fa.fa-hand-grab-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-grab-o:before{content:"\f255"}.fa.fa-hand-paper-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-paper-o:before{content:"\f256"}.fa.fa-hand-stop-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-stop-o:before{content:"\f256"}.fa.fa-hand-scissors-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-scissors-o:before{content:"\f257"}.fa.fa-hand-lizard-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-lizard-o:before{content:"\f258"}.fa.fa-hand-spock-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-spock-o:before{content:"\f259"}.fa.fa-hand-pointer-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-pointer-o:before{content:"\f25a"}.fa.fa-hand-peace-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-peace-o:before{content:"\f25b"}.fa.fa-registered{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-chrome,.fa.fa-creative-commons,.fa.fa-firefox,.fa.fa-get-pocket,.fa.fa-gg,.fa.fa-gg-circle,.fa.fa-internet-explorer,.fa.fa-odnoklassniki,.fa.fa-odnoklassniki-square,.fa.fa-opera,.fa.fa-safari,.fa.fa-wikipedia-w{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-television:before{content:"\f26c"}.fa.fa-500px,.fa.fa-amazon,.fa.fa-contao{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-calendar-plus-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-calendar-plus-o:before{content:"\f271"}.fa.fa-calendar-minus-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-calendar-minus-o:before{content:"\f272"}.fa.fa-calendar-times-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-calendar-times-o:before{content:"\f273"}.fa.fa-calendar-check-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-calendar-check-o:before{content:"\f274"}.fa.fa-map-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-map-o:before{content:"\f279"}.fa.fa-commenting:before{content:"\f4ad"}.fa.fa-commenting-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-commenting-o:before{content:"\f4ad"}.fa.fa-houzz,.fa.fa-vimeo{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-vimeo:before{content:"\f27d"}.fa.fa-black-tie,.fa.fa-edge,.fa.fa-fonticons,.fa.fa-reddit-alien{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-credit-card-alt:before{content:"\f09d"}.fa.fa-codiepie,.fa.fa-fort-awesome,.fa.fa-mixcloud,.fa.fa-modx,.fa.fa-product-hunt,.fa.fa-scribd,.fa.fa-usb{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-pause-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-pause-circle-o:before{content:"\f28b"}.fa.fa-stop-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-stop-circle-o:before{content:"\f28d"}.fa.fa-bluetooth,.fa.fa-bluetooth-b,.fa.fa-envira,.fa.fa-gitlab,.fa.fa-wheelchair-alt,.fa.fa-wpbeginner,.fa.fa-wpforms{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-wheelchair-alt:before{content:"\f368"}.fa.fa-question-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-question-circle-o:before{content:"\f059"}.fa.fa-volume-control-phone:before{content:"\f2a0"}.fa.fa-asl-interpreting:before{content:"\f2a3"}.fa.fa-deafness:before,.fa.fa-hard-of-hearing:before{content:"\f2a4"}.fa.fa-glide,.fa.fa-glide-g{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-signing:before{content:"\f2a7"}.fa.fa-snapchat,.fa.fa-snapchat-ghost,.fa.fa-viadeo,.fa.fa-viadeo-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-snapchat-ghost:before{content:"\f2ab"}.fa.fa-first-order,.fa.fa-google-plus-official,.fa.fa-pied-piper,.fa.fa-snapchat-square,.fa.fa-themeisle,.fa.fa-yoast{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-google-plus-official:before{content:"\f2b3"}.fa.fa-google-plus-circle{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-google-plus-circle:before{content:"\f2b3"}.fa.fa-fa,.fa.fa-font-awesome{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-fa:before{content:"\f2b4"}.fa.fa-handshake-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-handshake-o:before{content:"\f2b5"}.fa.fa-envelope-open-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-envelope-open-o:before{content:"\f2b6"}.fa.fa-linode{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-address-book-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-address-book-o:before{content:"\f2b9"}.fa.fa-vcard:before{content:"\f2bb"}.fa.fa-address-card-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-address-card-o:before{content:"\f2bb"}.fa.fa-vcard-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-vcard-o:before{content:"\f2bb"}.fa.fa-user-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-user-circle-o:before{content:"\f2bd"}.fa.fa-user-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-user-o:before{content:"\f007"}.fa.fa-id-badge{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-drivers-license:before{content:"\f2c2"}.fa.fa-id-card-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-id-card-o:before{content:"\f2c2"}.fa.fa-drivers-license-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-drivers-license-o:before{content:"\f2c2"}.fa.fa-free-code-camp,.fa.fa-quora,.fa.fa-telegram{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-thermometer-4:before,.fa.fa-thermometer:before{content:"\f2c7"}.fa.fa-thermometer-3:before{content:"\f2c8"}.fa.fa-thermometer-2:before{content:"\f2c9"}.fa.fa-thermometer-1:before{content:"\f2ca"}.fa.fa-thermometer-0:before{content:"\f2cb"}.fa.fa-bathtub:before,.fa.fa-s15:before{content:"\f2cd"}.fa.fa-window-maximize,.fa.fa-window-restore{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-times-rectangle:before{content:"\f410"}.fa.fa-window-close-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-window-close-o:before{content:"\f410"}.fa.fa-times-rectangle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-times-rectangle-o:before{content:"\f410"}.fa.fa-bandcamp,.fa.fa-eercast,.fa.fa-etsy,.fa.fa-grav,.fa.fa-imdb,.fa.fa-ravelry{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-eercast:before{content:"\f2da"}.fa.fa-snowflake-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-snowflake-o:before{content:"\f2dc"}.fa.fa-meetup,.fa.fa-superpowers,.fa.fa-wpexplorer{font-family:"Font Awesome 6 Brands";font-weight:400} \ No newline at end of file diff --git a/_static/dask-tf.jpg b/_static/dask-tf.jpg new file mode 100644 index 0000000000..e33dddd526 Binary files /dev/null and b/_static/dask-tf.jpg differ diff --git a/_static/data_sharing_with_deployment.jpg b/_static/data_sharing_with_deployment.jpg new file mode 100644 index 0000000000..7053b4875e Binary files /dev/null and b/_static/data_sharing_with_deployment.jpg differ diff --git a/_static/data_sharing_with_sidecar.jpg b/_static/data_sharing_with_sidecar.jpg new file mode 100644 index 0000000000..99f6b909ac Binary files /dev/null and b/_static/data_sharing_with_sidecar.jpg differ diff --git a/_static/debug.css b/_static/debug.css new file mode 100644 index 0000000000..74d4aec33e --- /dev/null +++ b/_static/debug.css @@ -0,0 +1,69 @@ +/* + This CSS file should be overridden by the theme authors. It's + meant for debugging and developing the skeleton that this theme provides. +*/ +body { + font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji"; + background: lavender; +} +.sb-announcement { + background: rgb(131, 131, 131); +} +.sb-announcement__inner { + background: black; + color: white; +} +.sb-header { + background: lightskyblue; +} +.sb-header__inner { + background: royalblue; + color: white; +} +.sb-header-secondary { + background: lightcyan; +} +.sb-header-secondary__inner { + background: cornflowerblue; + color: white; +} +.sb-sidebar-primary { + background: lightgreen; +} +.sb-main { + background: blanchedalmond; +} +.sb-main__inner { + background: antiquewhite; +} +.sb-header-article { + background: lightsteelblue; +} +.sb-article-container { + background: snow; +} +.sb-article-main { + background: white; +} +.sb-footer-article { + background: lightpink; +} +.sb-sidebar-secondary { + background: lightgoldenrodyellow; +} +.sb-footer-content { + background: plum; +} +.sb-footer-content__inner { + background: palevioletred; +} +.sb-footer { + background: pink; +} +.sb-footer__inner { + background: salmon; +} +.sb-article { + background: white; +} diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 0000000000..d06a71d751 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 0000000000..902bb33589 --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,14 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: true, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000000..a858a410e4 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/fraud-detection-job.jpg b/_static/fraud-detection-job.jpg new file mode 100644 index 0000000000..34005ee5a7 Binary files /dev/null and b/_static/fraud-detection-job.jpg differ diff --git a/_static/js/termynal.js b/_static/js/termynal.js new file mode 100644 index 0000000000..77ec6cb01c --- /dev/null +++ b/_static/js/termynal.js @@ -0,0 +1,197 @@ +/** + * termynal.js + * A lightweight, modern and extensible animated terminal window, using + * async/await. + * + * @author Ines Montani + * @version 0.0.1 + * @license MIT + */ + +'use strict'; + +/** Generate a terminal widget. */ +class Termynal { + /** + * Construct the widget's settings. + * @param {(string|Node)=} container - Query selector or container element. + * @param {Object=} options - Custom settings. + * @param {string} options.prefix - Prefix to use for data attributes. + * @param {number} options.startDelay - Delay before animation, in ms. + * @param {number} options.typeDelay - Delay between each typed character, in ms. + * @param {number} options.lineDelay - Delay between each line, in ms. + * @param {number} options.progressLength - Number of characters displayed as progress bar. + * @param {string} options.progressChar – Character to use for progress bar, defaults to █. + * @param {number} options.progressPercent - Max percent of progress. + * @param {string} options.cursor – Character to use for cursor, defaults to ▋. + * @param {Object[]} lineData - Dynamically loaded line data objects. + * @param {boolean} options.noInit - Don't initialise the animation. + */ + constructor(container = '#termynal', options = {}) { + this.container = (typeof container === 'string') ? document.querySelector(container) : container; + this.pfx = `data-${options.prefix || 'ty'}`; + this.startDelay = options.startDelay + || parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 600; + this.typeDelay = options.typeDelay + || parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 90; + this.lineDelay = options.lineDelay + || parseFloat(this.container.getAttribute(`${this.pfx}-lineDelay`)) || 1500; + this.progressLength = options.progressLength + || parseFloat(this.container.getAttribute(`${this.pfx}-progressLength`)) || 40; + this.progressChar = options.progressChar + || this.container.getAttribute(`${this.pfx}-progressChar`) || '█'; + this.progressPercent = options.progressPercent + || parseFloat(this.container.getAttribute(`${this.pfx}-progressPercent`)) || 100; + this.cursor = options.cursor + || this.container.getAttribute(`${this.pfx}-cursor`) || '▋'; + this.lineData = this.lineDataToElements(options.lineData || []); + if (!options.noInit) this.init() + } + + /** + * Initialise the widget, get lines, clear container and start animation. + */ + init() { + // Appends dynamically loaded lines to existing line elements. + this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat(this.lineData); + + /** + * Calculates width and height of Termynal container. + * If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS. + */ + const containerStyle = getComputedStyle(this.container); + this.container.style.width = containerStyle.width !== '0px' ? + containerStyle.width : undefined; + this.container.style.minHeight = containerStyle.height !== '0px' ? + containerStyle.height : undefined; + + this.container.setAttribute('data-termynal', ''); + this.container.innerHTML = ''; + this.start(); + } + + /** + * Start the animation and rener the lines depending on their data attributes. + */ + async start() { + await this._wait(this.startDelay); + + for (let line of this.lines) { + const type = line.getAttribute(this.pfx); + const delay = line.getAttribute(`${this.pfx}-delay`) || this.lineDelay; + + if (type == 'input') { + line.setAttribute(`${this.pfx}-cursor`, this.cursor); + await this.type(line); + await this._wait(delay); + } + + else if (type == 'progress') { + await this.progress(line); + await this._wait(delay); + } + + else { + this.container.appendChild(line); + await this._wait(delay); + } + + line.removeAttribute(`${this.pfx}-cursor`); + } + } + + /** + * Animate a typed line. + * @param {Node} line - The line element to render. + */ + async type(line) { + const chars = [...line.textContent]; + const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay; + line.textContent = ''; + this.container.appendChild(line); + + for (let char of chars) { + await this._wait(delay); + line.textContent += char; + } + } + + /** + * Animate a progress bar. + * @param {Node} line - The line element to render. + */ + async progress(line) { + const progressLength = line.getAttribute(`${this.pfx}-progressLength`) + || this.progressLength; + const progressChar = line.getAttribute(`${this.pfx}-progressChar`) + || this.progressChar; + const chars = progressChar.repeat(progressLength); + const progressPercent = line.getAttribute(`${this.pfx}-progressPercent`) + || this.progressPercent; + line.textContent = ''; + this.container.appendChild(line); + + for (let i = 1; i < chars.length + 1; i++) { + await this._wait(this.typeDelay); + const percent = Math.round(i / chars.length * 100); + line.textContent = `${chars.slice(0, i)} ${percent}%`; + if (percent>progressPercent) { + break; + } + } + } + + /** + * Helper function for animation delays, called with `await`. + * @param {number} time - Timeout, in ms. + */ + _wait(time) { + return new Promise(resolve => setTimeout(resolve, time)); + } + + /** + * Converts line data objects into line elements. + * + * @param {Object[]} lineData - Dynamically loaded lines. + * @param {Object} line - Line data object. + * @returns {Element[]} - Array of line elements. + */ + lineDataToElements(lineData) { + return lineData.map(line => { + let div = document.createElement('div'); + div.innerHTML = `${line.value || ''}`; + + return div.firstElementChild; + }); + } + + /** + * Helper function for generating attributes string. + * + * @param {Object} line - Line data object. + * @returns {string} - String of attributes. + */ + _attributes(line) { + let attrs = ''; + for (let prop in line) { + attrs += this.pfx; + + if (prop === 'type') { + attrs += `="${line[prop]}" ` + } else if (prop !== 'value') { + attrs += `-${prop}="${line[prop]}" ` + } + } + + return attrs; + } +} + +/** +* HTML API: If current script has container(s) specified, initialise Termynal. +*/ +if (document.currentScript.hasAttribute('data-termynal-container')) { + const containers = document.currentScript.getAttribute('data-termynal-container'); + containers.split('|') + .forEach(container => new Termynal(container)) +} diff --git a/_static/kubeflow_create_run.png b/_static/kubeflow_create_run.png new file mode 100644 index 0000000000..84ac8dd621 Binary files /dev/null and b/_static/kubeflow_create_run.png differ diff --git a/_static/kubeflow_upload_pipeline.png b/_static/kubeflow_upload_pipeline.png new file mode 100644 index 0000000000..bd9321fa06 Binary files /dev/null and b/_static/kubeflow_upload_pipeline.png differ diff --git a/_static/language_data.js b/_static/language_data.js new file mode 100644 index 0000000000..250f5665fa --- /dev/null +++ b/_static/language_data.js @@ -0,0 +1,199 @@ +/* + * language_data.js + * ~~~~~~~~~~~~~~~~ + * + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, is available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 0000000000..d96755fdaf Binary files /dev/null and b/_static/minus.png differ diff --git a/_static/nbsphinx-broken-thumbnail.svg b/_static/nbsphinx-broken-thumbnail.svg new file mode 100644 index 0000000000..4919ca8829 --- /dev/null +++ b/_static/nbsphinx-broken-thumbnail.svg @@ -0,0 +1,9 @@ + + + + diff --git a/_static/nbsphinx-code-cells.css b/_static/nbsphinx-code-cells.css new file mode 100644 index 0000000000..a3fb27c30f --- /dev/null +++ b/_static/nbsphinx-code-cells.css @@ -0,0 +1,259 @@ +/* remove conflicting styling from Sphinx themes */ +div.nbinput.container div.prompt *, +div.nboutput.container div.prompt *, +div.nbinput.container div.input_area pre, +div.nboutput.container div.output_area pre, +div.nbinput.container div.input_area .highlight, +div.nboutput.container div.output_area .highlight { + border: none; + padding: 0; + margin: 0; + box-shadow: none; +} + +div.nbinput.container > div[class*=highlight], +div.nboutput.container > div[class*=highlight] { + margin: 0; +} + +div.nbinput.container div.prompt *, +div.nboutput.container div.prompt * { + background: none; +} + +div.nboutput.container div.output_area .highlight, +div.nboutput.container div.output_area pre { + background: unset; +} + +div.nboutput.container div.output_area div.highlight { + color: unset; /* override Pygments text color */ +} + +/* avoid gaps between output lines */ +div.nboutput.container div[class*=highlight] pre { + line-height: normal; +} + +/* input/output containers */ +div.nbinput.container, +div.nboutput.container { + display: -webkit-flex; + display: flex; + align-items: flex-start; + margin: 0; + width: 100%; +} +@media (max-width: 540px) { + div.nbinput.container, + div.nboutput.container { + flex-direction: column; + } +} + +/* input container */ +div.nbinput.container { + padding-top: 5px; +} + +/* last container */ +div.nblast.container { + padding-bottom: 5px; +} + +/* input prompt */ +div.nbinput.container div.prompt pre, +/* for sphinx_immaterial theme: */ +div.nbinput.container div.prompt pre > code { + color: #307FC1; +} + +/* output prompt */ +div.nboutput.container div.prompt pre, +/* for sphinx_immaterial theme: */ +div.nboutput.container div.prompt pre > code { + color: #BF5B3D; +} + +/* all prompts */ +div.nbinput.container div.prompt, +div.nboutput.container div.prompt { + width: 4.5ex; + padding-top: 5px; + position: relative; + user-select: none; +} + +div.nbinput.container div.prompt > div, +div.nboutput.container div.prompt > div { + position: absolute; + right: 0; + margin-right: 0.3ex; +} + +@media (max-width: 540px) { + div.nbinput.container div.prompt, + div.nboutput.container div.prompt { + width: unset; + text-align: left; + padding: 0.4em; + } + div.nboutput.container div.prompt.empty { + padding: 0; + } + + div.nbinput.container div.prompt > div, + div.nboutput.container div.prompt > div { + position: unset; + } +} + +/* disable scrollbars and line breaks on prompts */ +div.nbinput.container div.prompt pre, +div.nboutput.container div.prompt pre { + overflow: hidden; + white-space: pre; +} + +/* input/output area */ +div.nbinput.container div.input_area, +div.nboutput.container div.output_area { + -webkit-flex: 1; + flex: 1; + overflow: auto; +} +@media (max-width: 540px) { + div.nbinput.container div.input_area, + div.nboutput.container div.output_area { + width: 100%; + } +} + +/* input area */ +div.nbinput.container div.input_area { + border: 1px solid #e0e0e0; + border-radius: 2px; + /*background: #f5f5f5;*/ +} + +/* override MathJax center alignment in output cells */ +div.nboutput.container div[class*=MathJax] { + text-align: left !important; +} + +/* override sphinx.ext.imgmath center alignment in output cells */ +div.nboutput.container div.math p { + text-align: left; +} + +/* standard error */ +div.nboutput.container div.output_area.stderr { + background: #fdd; +} + +/* ANSI colors */ +.ansi-black-fg { color: #3E424D; } +.ansi-black-bg { background-color: #3E424D; } +.ansi-black-intense-fg { color: #282C36; } +.ansi-black-intense-bg { background-color: #282C36; } +.ansi-red-fg { color: #E75C58; } +.ansi-red-bg { background-color: #E75C58; } +.ansi-red-intense-fg { color: #B22B31; } +.ansi-red-intense-bg { background-color: #B22B31; } +.ansi-green-fg { color: #00A250; } +.ansi-green-bg { background-color: #00A250; } +.ansi-green-intense-fg { color: #007427; } +.ansi-green-intense-bg { background-color: #007427; } +.ansi-yellow-fg { color: #DDB62B; } +.ansi-yellow-bg { background-color: #DDB62B; } +.ansi-yellow-intense-fg { color: #B27D12; } +.ansi-yellow-intense-bg { background-color: #B27D12; } +.ansi-blue-fg { color: #208FFB; } +.ansi-blue-bg { background-color: #208FFB; } +.ansi-blue-intense-fg { color: #0065CA; } +.ansi-blue-intense-bg { background-color: #0065CA; } +.ansi-magenta-fg { color: #D160C4; } +.ansi-magenta-bg { background-color: #D160C4; } +.ansi-magenta-intense-fg { color: #A03196; } +.ansi-magenta-intense-bg { background-color: #A03196; } +.ansi-cyan-fg { color: #60C6C8; } +.ansi-cyan-bg { background-color: #60C6C8; } +.ansi-cyan-intense-fg { color: #258F8F; } +.ansi-cyan-intense-bg { background-color: #258F8F; } +.ansi-white-fg { color: #C5C1B4; } +.ansi-white-bg { background-color: #C5C1B4; } +.ansi-white-intense-fg { color: #A1A6B2; } +.ansi-white-intense-bg { background-color: #A1A6B2; } + +.ansi-default-inverse-fg { color: #FFFFFF; } +.ansi-default-inverse-bg { background-color: #000000; } + +.ansi-bold { font-weight: bold; } +.ansi-underline { text-decoration: underline; } + + +div.nbinput.container div.input_area div[class*=highlight] > pre, +div.nboutput.container div.output_area div[class*=highlight] > pre, +div.nboutput.container div.output_area div[class*=highlight].math, +div.nboutput.container div.output_area.rendered_html, +div.nboutput.container div.output_area > div.output_javascript, +div.nboutput.container div.output_area:not(.rendered_html) > img{ + padding: 5px; + margin: 0; +} + +/* fix copybtn overflow problem in chromium (needed for 'sphinx_copybutton') */ +div.nbinput.container div.input_area > div[class^='highlight'], +div.nboutput.container div.output_area > div[class^='highlight']{ + overflow-y: hidden; +} + +/* hide copy button on prompts for 'sphinx_copybutton' extension ... */ +.prompt .copybtn, +/* ... and 'sphinx_immaterial' theme */ +.prompt .md-clipboard.md-icon { + display: none; +} + +/* Some additional styling taken form the Jupyter notebook CSS */ +.jp-RenderedHTMLCommon table, +div.rendered_html table { + border: none; + border-collapse: collapse; + border-spacing: 0; + color: black; + font-size: 12px; + table-layout: fixed; +} +.jp-RenderedHTMLCommon thead, +div.rendered_html thead { + border-bottom: 1px solid black; + vertical-align: bottom; +} +.jp-RenderedHTMLCommon tr, +.jp-RenderedHTMLCommon th, +.jp-RenderedHTMLCommon td, +div.rendered_html tr, +div.rendered_html th, +div.rendered_html td { + text-align: right; + vertical-align: middle; + padding: 0.5em 0.5em; + line-height: normal; + white-space: normal; + max-width: none; + border: none; +} +.jp-RenderedHTMLCommon th, +div.rendered_html th { + font-weight: bold; +} +.jp-RenderedHTMLCommon tbody tr:nth-child(odd), +div.rendered_html tbody tr:nth-child(odd) { + background: #f5f5f5; +} +.jp-RenderedHTMLCommon tbody tr:hover, +div.rendered_html tbody tr:hover { + background: rgba(66, 165, 245, 0.2); +} + diff --git a/_static/nbsphinx-gallery.css b/_static/nbsphinx-gallery.css new file mode 100644 index 0000000000..365c27a96b --- /dev/null +++ b/_static/nbsphinx-gallery.css @@ -0,0 +1,31 @@ +.nbsphinx-gallery { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 5px; + margin-top: 1em; + margin-bottom: 1em; +} + +.nbsphinx-gallery > a { + padding: 5px; + border: 1px dotted currentColor; + border-radius: 2px; + text-align: center; +} + +.nbsphinx-gallery > a:hover { + border-style: solid; +} + +.nbsphinx-gallery img { + max-width: 100%; + max-height: 100%; +} + +.nbsphinx-gallery > a > div:first-child { + display: flex; + align-items: start; + justify-content: center; + height: 120px; + margin-bottom: 5px; +} diff --git a/_static/nbsphinx-no-thumbnail.svg b/_static/nbsphinx-no-thumbnail.svg new file mode 100644 index 0000000000..9dca7588fa --- /dev/null +++ b/_static/nbsphinx-no-thumbnail.svg @@ -0,0 +1,9 @@ + + + + diff --git a/_static/panels-bootstrap.5fd3999ee7762ccc51105388f4a9d115.css b/_static/panels-bootstrap.5fd3999ee7762ccc51105388f4a9d115.css new file mode 100644 index 0000000000..1b057df2f2 --- /dev/null +++ b/_static/panels-bootstrap.5fd3999ee7762ccc51105388f4a9d115.css @@ -0,0 +1 @@ +.badge{border-radius:.25rem;display:inline-block;font-size:75%;font-weight:700;line-height:1;padding:.25em .4em;text-align:center;vertical-align:baseline;white-space:nowrap}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{border-radius:10rem;padding-left:.6em;padding-right:.6em}.badge-primary{background-color:#007bff;color:#fff}.badge-primary[href]:focus,.badge-primary[href]:hover{background-color:#0062cc;color:#fff;text-decoration:none}.badge-secondary{background-color:#6c757d;color:#fff}.badge-secondary[href]:focus,.badge-secondary[href]:hover{background-color:#545b62;color:#fff;text-decoration:none}.badge-success{background-color:#28a745;color:#fff}.badge-success[href]:focus,.badge-success[href]:hover{background-color:#1e7e34;color:#fff;text-decoration:none}.badge-info{background-color:#17a2b8;color:#fff}.badge-info[href]:focus,.badge-info[href]:hover{background-color:#117a8b;color:#fff;text-decoration:none}.badge-warning{background-color:#ffc107;color:#212529}.badge-warning[href]:focus,.badge-warning[href]:hover{background-color:#d39e00;color:#212529;text-decoration:none}.badge-danger{background-color:#dc3545;color:#fff}.badge-danger[href]:focus,.badge-danger[href]:hover{background-color:#bd2130;color:#fff;text-decoration:none}.badge-light{background-color:#f8f9fa;color:#212529}.badge-light[href]:focus,.badge-light[href]:hover{background-color:#dae0e5;color:#212529;text-decoration:none}.badge-dark{background-color:#343a40;color:#fff}.badge-dark[href]:focus,.badge-dark[href]:hover{background-color:#1d2124;color:#fff;text-decoration:none}.border-0{border:0 !important}.border-top-0{border-top:0 !important}.border-right-0{border-right:0 !important}.border-bottom-0{border-bottom:0 !important}.border-left-0{border-left:0 !important}.p-0{padding:0 !important}.pt-0,.py-0{padding-top:0 !important}.pr-0,.px-0{padding-right:0 !important}.pb-0,.py-0{padding-bottom:0 !important}.pl-0,.px-0{padding-left:0 !important}.p-1{padding:.25rem !important}.pt-1,.py-1{padding-top:.25rem !important}.pr-1,.px-1{padding-right:.25rem !important}.pb-1,.py-1{padding-bottom:.25rem !important}.pl-1,.px-1{padding-left:.25rem !important}.p-2{padding:.5rem !important}.pt-2,.py-2{padding-top:.5rem !important}.pr-2,.px-2{padding-right:.5rem !important}.pb-2,.py-2{padding-bottom:.5rem !important}.pl-2,.px-2{padding-left:.5rem !important}.p-3{padding:1rem !important}.pt-3,.py-3{padding-top:1rem !important}.pr-3,.px-3{padding-right:1rem !important}.pb-3,.py-3{padding-bottom:1rem !important}.pl-3,.px-3{padding-left:1rem !important}.p-4{padding:1.5rem !important}.pt-4,.py-4{padding-top:1.5rem !important}.pr-4,.px-4{padding-right:1.5rem !important}.pb-4,.py-4{padding-bottom:1.5rem !important}.pl-4,.px-4{padding-left:1.5rem !important}.p-5{padding:3rem !important}.pt-5,.py-5{padding-top:3rem !important}.pr-5,.px-5{padding-right:3rem !important}.pb-5,.py-5{padding-bottom:3rem !important}.pl-5,.px-5{padding-left:3rem !important}.m-0{margin:0 !important}.mt-0,.my-0{margin-top:0 !important}.mr-0,.mx-0{margin-right:0 !important}.mb-0,.my-0{margin-bottom:0 !important}.ml-0,.mx-0{margin-left:0 !important}.m-1{margin:.25rem !important}.mt-1,.my-1{margin-top:.25rem !important}.mr-1,.mx-1{margin-right:.25rem !important}.mb-1,.my-1{margin-bottom:.25rem !important}.ml-1,.mx-1{margin-left:.25rem !important}.m-2{margin:.5rem !important}.mt-2,.my-2{margin-top:.5rem !important}.mr-2,.mx-2{margin-right:.5rem !important}.mb-2,.my-2{margin-bottom:.5rem !important}.ml-2,.mx-2{margin-left:.5rem !important}.m-3{margin:1rem !important}.mt-3,.my-3{margin-top:1rem !important}.mr-3,.mx-3{margin-right:1rem !important}.mb-3,.my-3{margin-bottom:1rem !important}.ml-3,.mx-3{margin-left:1rem !important}.m-4{margin:1.5rem !important}.mt-4,.my-4{margin-top:1.5rem !important}.mr-4,.mx-4{margin-right:1.5rem !important}.mb-4,.my-4{margin-bottom:1.5rem !important}.ml-4,.mx-4{margin-left:1.5rem !important}.m-5{margin:3rem !important}.mt-5,.my-5{margin-top:3rem !important}.mr-5,.mx-5{margin-right:3rem !important}.mb-5,.my-5{margin-bottom:3rem !important}.ml-5,.mx-5{margin-left:3rem !important}.btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;color:#212529;cursor:pointer;display:inline-block;font-size:1rem;font-weight:400;line-height:1.5;padding:.375rem .75rem;text-align:center;transition:color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none;vertical-align:middle}.btn:hover{color:#212529;text-decoration:none}.btn:visited{color:#212529}.btn.focus,.btn:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,0.25);outline:0}.btn.disabled,.btn:disabled{opacity:.65}@media (prefers-reduced-motion: reduce){.btn{transition:none}}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{background-color:#007bff;border-color:#007bff;color:#fff}.btn-primary:visited{color:#fff}.btn-primary:hover{background-color:#0069d9;border-color:#0062cc;color:#fff}.btn-primary.focus,.btn-primary:focus{background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(0,123,255,0.5);color:#fff}.btn-primary.disabled,.btn-primary:disabled{background-color:#007bff;border-color:#007bff;color:#fff}.btn-primary.active:not(:disabled):not(.disabled),.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{background-color:#0062cc;border-color:#005cbf;color:#fff}.btn-primary.active:not(:disabled):not(.disabled):focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,0.5)}.btn-secondary{background-color:#6c757d;border-color:#6c757d;color:#fff}.btn-secondary:visited{color:#fff}.btn-secondary:hover{background-color:#5a6268;border-color:#545b62;color:#fff}.btn-secondary.focus,.btn-secondary:focus{background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(108,117,125,0.5);color:#fff}.btn-secondary.disabled,.btn-secondary:disabled{background-color:#6c757d;border-color:#6c757d;color:#fff}.btn-secondary.active:not(:disabled):not(.disabled),.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{background-color:#545b62;border-color:#4e555b;color:#fff}.btn-secondary.active:not(:disabled):not(.disabled):focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,0.5)}.btn-success{background-color:#28a745;border-color:#28a745;color:#fff}.btn-success:visited{color:#fff}.btn-success:hover{background-color:#218838;border-color:#1e7e34;color:#fff}.btn-success.focus,.btn-success:focus{background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(40,167,69,0.5);color:#fff}.btn-success.disabled,.btn-success:disabled{background-color:#28a745;border-color:#28a745;color:#fff}.btn-success.active:not(:disabled):not(.disabled),.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{background-color:#1e7e34;border-color:#1c7430;color:#fff}.btn-success.active:not(:disabled):not(.disabled):focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,0.5)}.btn-info{background-color:#17a2b8;border-color:#17a2b8;color:#fff}.btn-info:visited{color:#fff}.btn-info:hover{background-color:#138496;border-color:#117a8b;color:#fff}.btn-info.focus,.btn-info:focus{background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(23,162,184,0.5);color:#fff}.btn-info.disabled,.btn-info:disabled{background-color:#17a2b8;border-color:#17a2b8;color:#fff}.btn-info.active:not(:disabled):not(.disabled),.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{background-color:#117a8b;border-color:#10707f;color:#fff}.btn-info.active:not(:disabled):not(.disabled):focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,0.5)}.btn-warning{background-color:#ffc107;border-color:#ffc107;color:#212529}.btn-warning:visited{color:#212529}.btn-warning:hover{background-color:#e0a800;border-color:#d39e00;color:#212529}.btn-warning.focus,.btn-warning:focus{background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(255,193,7,0.5);color:#212529}.btn-warning.disabled,.btn-warning:disabled{background-color:#ffc107;border-color:#ffc107;color:#212529}.btn-warning.active:not(:disabled):not(.disabled),.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{background-color:#d39e00;border-color:#c69500;color:#212529}.btn-warning.active:not(:disabled):not(.disabled):focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,0.5)}.btn-danger{background-color:#dc3545;border-color:#dc3545;color:#fff}.btn-danger:visited{color:#fff}.btn-danger:hover{background-color:#c82333;border-color:#bd2130;color:#fff}.btn-danger.focus,.btn-danger:focus{background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(220,53,69,0.5);color:#fff}.btn-danger.disabled,.btn-danger:disabled{background-color:#dc3545;border-color:#dc3545;color:#fff}.btn-danger.active:not(:disabled):not(.disabled),.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{background-color:#bd2130;border-color:#b21f2d;color:#fff}.btn-danger.active:not(:disabled):not(.disabled):focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,0.5)}.btn-light{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-light:visited{color:#212529}.btn-light:hover{background-color:#e2e6ea;border-color:#dae0e5;color:#212529}.btn-light.focus,.btn-light:focus{background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(248,249,250,0.5);color:#212529}.btn-light.disabled,.btn-light:disabled{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-light.active:not(:disabled):not(.disabled),.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{background-color:#dae0e5;border-color:#d3d9df;color:#212529}.btn-light.active:not(:disabled):not(.disabled):focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,0.5)}.btn-dark{background-color:#343a40;border-color:#343a40;color:#fff}.btn-dark:visited{color:#fff}.btn-dark:hover{background-color:#23272b;border-color:#1d2124;color:#fff}.btn-dark.focus,.btn-dark:focus{background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(52,58,64,0.5);color:#fff}.btn-dark.disabled,.btn-dark:disabled{background-color:#343a40;border-color:#343a40;color:#fff}.btn-dark.active:not(:disabled):not(.disabled),.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{background-color:#1d2124;border-color:#171a1d;color:#fff}.btn-dark.active:not(:disabled):not(.disabled):focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,0.5)}.btn-outline-primary{border-color:#007bff;color:#007bff}.btn-outline-primary:visited{color:#007bff}.btn-outline-primary:hover{background-color:#007bff;border-color:#007bff;color:#fff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,0.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{background-color:transparent;color:#007bff}.btn-outline-primary.active:not(:disabled):not(.disabled),.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{background-color:#007bff;border-color:#007bff;color:#fff}.btn-outline-primary.active:not(:disabled):not(.disabled):focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,0.5)}.btn-outline-secondary{border-color:#6c757d;color:#6c757d}.btn-outline-secondary:visited{color:#6c757d}.btn-outline-secondary:hover{background-color:#6c757d;border-color:#6c757d;color:#fff}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,0.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{background-color:transparent;color:#6c757d}.btn-outline-secondary.active:not(:disabled):not(.disabled),.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{background-color:#6c757d;border-color:#6c757d;color:#fff}.btn-outline-secondary.active:not(:disabled):not(.disabled):focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,0.5)}.btn-outline-success{border-color:#28a745;color:#28a745}.btn-outline-success:visited{color:#28a745}.btn-outline-success:hover{background-color:#28a745;border-color:#28a745;color:#fff}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,0.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{background-color:transparent;color:#28a745}.btn-outline-success.active:not(:disabled):not(.disabled),.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{background-color:#28a745;border-color:#28a745;color:#fff}.btn-outline-success.active:not(:disabled):not(.disabled):focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,0.5)}.btn-outline-info{border-color:#17a2b8;color:#17a2b8}.btn-outline-info:visited{color:#17a2b8}.btn-outline-info:hover{background-color:#17a2b8;border-color:#17a2b8;color:#fff}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,0.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{background-color:transparent;color:#17a2b8}.btn-outline-info.active:not(:disabled):not(.disabled),.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{background-color:#17a2b8;border-color:#17a2b8;color:#fff}.btn-outline-info.active:not(:disabled):not(.disabled):focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,0.5)}.btn-outline-warning{border-color:#ffc107;color:#ffc107}.btn-outline-warning:visited{color:#ffc107}.btn-outline-warning:hover{background-color:#ffc107;border-color:#ffc107;color:#212529}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,0.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{background-color:transparent;color:#ffc107}.btn-outline-warning.active:not(:disabled):not(.disabled),.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{background-color:#ffc107;border-color:#ffc107;color:#212529}.btn-outline-warning.active:not(:disabled):not(.disabled):focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,0.5)}.btn-outline-danger{border-color:#dc3545;color:#dc3545}.btn-outline-danger:visited{color:#dc3545}.btn-outline-danger:hover{background-color:#dc3545;border-color:#dc3545;color:#fff}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,0.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{background-color:transparent;color:#dc3545}.btn-outline-danger.active:not(:disabled):not(.disabled),.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{background-color:#dc3545;border-color:#dc3545;color:#fff}.btn-outline-danger.active:not(:disabled):not(.disabled):focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,0.5)}.btn-outline-light{border-color:#f8f9fa;color:#f8f9fa}.btn-outline-light:visited{color:#f8f9fa}.btn-outline-light:hover{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,0.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{background-color:transparent;color:#f8f9fa}.btn-outline-light.active:not(:disabled):not(.disabled),.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-outline-light.active:not(:disabled):not(.disabled):focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,0.5)}.btn-outline-dark{border-color:#343a40;color:#343a40}.btn-outline-dark:visited{color:#343a40}.btn-outline-dark:hover{background-color:#343a40;border-color:#343a40;color:#fff}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,0.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{background-color:transparent;color:#343a40}.btn-outline-dark.active:not(:disabled):not(.disabled),.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{background-color:#343a40;border-color:#343a40;color:#fff}.btn-outline-dark.active:not(:disabled):not(.disabled):focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,0.5)}.btn-link{color:#007bff;font-weight:400;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{box-shadow:none;text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{border-radius:.3rem;font-size:1.25rem;line-height:1.5;padding:.5rem 1rem}.btn-group-sm>.btn,.btn-sm{border-radius:.2rem;font-size:.875rem;line-height:1.5;padding:.25rem .5rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input.btn-block[type=button],input.btn-block[type=reset],input.btn-block[type=submit]{width:100%}.stretched-link::after{background-color:rgba(0,0,0,0);bottom:0;content:'';left:0;pointer-events:auto;position:absolute;right:0;top:0;z-index:1}.text-wrap{white-space:normal !important}.card{background-clip:border-box;background-color:#fff;border:1px solid rgba(0,0,0,0.125);border-radius:.25rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.card>hr{margin-left:0;margin-right:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-bottom:0;margin-top:-.375rem}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{background-color:rgba(0,0,0,0.03);border-bottom:1px solid rgba(0,0,0,0.125);margin-bottom:0;padding:.75rem 1.25rem}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{background-color:rgba(0,0,0,0.03);border-top:1px solid rgba(0,0,0,0.125);padding:.75rem 1.25rem}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{border-bottom:0;margin-bottom:-.75rem;margin-left:-.625rem;margin-right:-.625rem}.card-header-pills{margin-left:-.625rem;margin-right:-.625rem}.card-img-overlay{bottom:0;left:0;padding:1.25rem;position:absolute;right:0;top:0}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-left-radius:calc(.25rem - 1px);border-bottom-right-radius:calc(.25rem - 1px)}.w-100{width:100% !important}.shadow{box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15) !important}.bg-primary{background-color:#007bff !important}button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc !important}a.bg-primary:focus,a.bg-primary:hover{background-color:#0062cc !important}a.text-primary:focus,a.text-primary:hover{color:#121416 !important}.bg-secondary{background-color:#6c757d !important}button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62 !important}a.bg-secondary:focus,a.bg-secondary:hover{background-color:#545b62 !important}a.text-secondary:focus,a.text-secondary:hover{color:#121416 !important}.bg-success{background-color:#28a745 !important}button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34 !important}a.bg-success:focus,a.bg-success:hover{background-color:#1e7e34 !important}a.text-success:focus,a.text-success:hover{color:#121416 !important}.bg-info{background-color:#17a2b8 !important}button.bg-info:focus,button.bg-info:hover{background-color:#117a8b !important}a.bg-info:focus,a.bg-info:hover{background-color:#117a8b !important}a.text-info:focus,a.text-info:hover{color:#121416 !important}.bg-warning{background-color:#ffc107 !important}button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00 !important}a.bg-warning:focus,a.bg-warning:hover{background-color:#d39e00 !important}a.text-warning:focus,a.text-warning:hover{color:#121416 !important}.bg-danger{background-color:#dc3545 !important}button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130 !important}a.bg-danger:focus,a.bg-danger:hover{background-color:#bd2130 !important}a.text-danger:focus,a.text-danger:hover{color:#121416 !important}.bg-light{background-color:#f8f9fa !important}button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5 !important}a.bg-light:focus,a.bg-light:hover{background-color:#dae0e5 !important}a.text-light:focus,a.text-light:hover{color:#121416 !important}.bg-dark{background-color:#343a40 !important}button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124 !important}a.bg-dark:focus,a.bg-dark:hover{background-color:#1d2124 !important}a.text-dark:focus,a.text-dark:hover{color:#121416 !important}.bg-white{background-color:#fff !important}button.bg-white:focus,button.bg-white:hover{background-color:#e6e6e6 !important}a.bg-white:focus,a.bg-white:hover{background-color:#e6e6e6 !important}a.text-white:focus,a.text-white:hover{color:#121416 !important}.text-primary{color:#007bff !important}.text-secondary{color:#6c757d !important}.text-success{color:#28a745 !important}.text-info{color:#17a2b8 !important}.text-warning{color:#ffc107 !important}.text-danger{color:#dc3545 !important}.text-light{color:#f8f9fa !important}.text-dark{color:#343a40 !important}.text-white{color:#fff !important}.text-body{color:#212529 !important}.text-muted{color:#6c757d !important}.text-black-50{color:rgba(0,0,0,0.5) !important}.text-white-50{color:rgba(255,255,255,0.5) !important}.bg-transparent{background-color:transparent !important}.text-justify{text-align:justify !important}.text-left{text-align:left !important}.text-right{text-align:right !important}.text-center{text-align:center !important}.font-weight-light{font-weight:300 !important}.font-weight-lighter{font-weight:lighter !important}.font-weight-normal{font-weight:400 !important}.font-weight-bold{font-weight:700 !important}.font-weight-bolder{font-weight:bolder !important}.font-italic{font-style:italic !important}.container{margin-left:auto;margin-right:auto;padding-left:15px;padding-right:15px;width:100%}@media (min-width: 576px){.container{max-width:540px}}@media (min-width: 768px){.container{max-width:720px}}@media (min-width: 992px){.container{max-width:960px}}@media (min-width: 1200px){.container{max-width:1140px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{margin-left:auto;margin-right:auto;padding-left:15px;padding-right:15px;width:100%}@media (min-width: 576px){.container,.container-sm{max-width:540px}}@media (min-width: 768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width: 992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width: 1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-15px;margin-right:-15px}.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{padding-left:15px;padding-right:15px;position:relative;width:100%}@media (min-width: 576px){.col-sm{flex-basis:0;flex-grow:1;-ms-flex-positive:1;-ms-flex-preferred-size:0;max-width:100%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;width:auto}.col-sm-1{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-sm-2{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-sm-5{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-sm-8{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-sm-11{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}}@media (min-width: 768px){.col-md{flex-basis:0;flex-grow:1;-ms-flex-positive:1;-ms-flex-preferred-size:0;max-width:100%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;width:auto}.col-md-1{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-md-2{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-md-5{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-md-8{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-md-11{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}}@media (min-width: 992px){.col-lg{flex-basis:0;flex-grow:1;-ms-flex-positive:1;-ms-flex-preferred-size:0;max-width:100%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;width:auto}.col-lg-1{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-lg-2{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-lg-5{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-lg-8{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-lg-11{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}}@media (min-width: 1200px){.col-xl{flex-basis:0;flex-grow:1;-ms-flex-positive:1;-ms-flex-preferred-size:0;max-width:100%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;width:auto}.col-xl-1{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-xl-2{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-xl-5{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-xl-8{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-xl-11{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}}.d-flex{display:-ms-flexbox !important;display:flex !important}.sphinx-bs,.sphinx-bs *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sphinx-bs p{margin-top:0} diff --git a/_static/panels-main.c949a650a448cc0ae9fd3441c0e17fb0.css b/_static/panels-main.c949a650a448cc0ae9fd3441c0e17fb0.css new file mode 100644 index 0000000000..fc14abc85d --- /dev/null +++ b/_static/panels-main.c949a650a448cc0ae9fd3441c0e17fb0.css @@ -0,0 +1 @@ +details.dropdown .summary-title{padding-right:3em !important;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}details.dropdown:hover{cursor:pointer}details.dropdown .summary-content{cursor:default}details.dropdown summary{list-style:none;padding:1em}details.dropdown summary .octicon.no-title{vertical-align:middle}details.dropdown[open] summary .octicon.no-title{visibility:hidden}details.dropdown summary::-webkit-details-marker{display:none}details.dropdown summary:focus{outline:none}details.dropdown summary:hover .summary-up svg,details.dropdown summary:hover .summary-down svg{opacity:1}details.dropdown .summary-up svg,details.dropdown .summary-down svg{display:block;opacity:.6}details.dropdown .summary-up,details.dropdown .summary-down{pointer-events:none;position:absolute;right:1em;top:.75em}details.dropdown[open] .summary-down{visibility:hidden}details.dropdown:not([open]) .summary-up{visibility:hidden}details.dropdown.fade-in[open] summary~*{-moz-animation:panels-fade-in .5s ease-in-out;-webkit-animation:panels-fade-in .5s ease-in-out;animation:panels-fade-in .5s ease-in-out}details.dropdown.fade-in-slide-down[open] summary~*{-moz-animation:panels-fade-in .5s ease-in-out, panels-slide-down .5s ease-in-out;-webkit-animation:panels-fade-in .5s ease-in-out, panels-slide-down .5s ease-in-out;animation:panels-fade-in .5s ease-in-out, panels-slide-down .5s ease-in-out}@keyframes panels-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes panels-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.octicon{display:inline-block;fill:currentColor;vertical-align:text-top}.tabbed-content{box-shadow:0 -.0625rem var(--tabs-color-overline),0 .0625rem var(--tabs-color-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.tabbed-content>:first-child{margin-top:0 !important}.tabbed-content>:last-child{margin-bottom:0 !important}.tabbed-content>.tabbed-set{margin:0}.tabbed-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.tabbed-set>input{opacity:0;position:absolute}.tabbed-set>input:checked+label{border-color:var(--tabs-color-label-active);color:var(--tabs-color-label-active)}.tabbed-set>input:checked+label+.tabbed-content{display:block}.tabbed-set>input:focus+label{outline-style:auto}.tabbed-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.tabbed-set>label{border-bottom:.125rem solid transparent;color:var(--tabs-color-label-inactive);cursor:pointer;font-size:var(--tabs-size-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .tabbed-set>label:hover{color:var(--tabs-color-label-active)} diff --git a/_static/panels-variables.06eb56fa6e07937060861dad626602ad.css b/_static/panels-variables.06eb56fa6e07937060861dad626602ad.css new file mode 100644 index 0000000000..adc6166222 --- /dev/null +++ b/_static/panels-variables.06eb56fa6e07937060861dad626602ad.css @@ -0,0 +1,7 @@ +:root { +--tabs-color-label-active: hsla(231, 99%, 66%, 1); +--tabs-color-label-inactive: rgba(178, 206, 245, 0.62); +--tabs-color-overline: rgb(207, 236, 238); +--tabs-color-underline: rgb(207, 236, 238); +--tabs-size-label: 1rem; +} \ No newline at end of file diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 0000000000..7107cec93a Binary files /dev/null and b/_static/plus.png differ diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 0000000000..02b4b12812 --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,258 @@ +.highlight pre { line-height: 125%; } +.highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +.highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +.highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #8f5902; font-style: italic } /* Comment */ +.highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */ +.highlight .g { color: #000000 } /* Generic */ +.highlight .k { color: #204a87; font-weight: bold } /* Keyword */ +.highlight .l { color: #000000 } /* Literal */ +.highlight .n { color: #000000 } /* Name */ +.highlight .o { color: #ce5c00; font-weight: bold } /* Operator */ +.highlight .x { color: #000000 } /* Other */ +.highlight .p { color: #000000; font-weight: bold } /* Punctuation */ +.highlight .ch { color: #8f5902; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #8f5902; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #8f5902; font-style: italic } /* Comment.Preproc */ +.highlight .cpf { color: #8f5902; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #8f5902; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #a40000 } /* Generic.Deleted */ +.highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ +.highlight .ges { color: #000000; font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #ef2929 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #000000; font-style: italic } /* Generic.Output */ +.highlight .gp { color: #8f5902 } /* Generic.Prompt */ +.highlight .gs { color: #000000; font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */ +.highlight .kc { color: #204a87; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #204a87; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #204a87; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #204a87; font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { color: #204a87; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #204a87; font-weight: bold } /* Keyword.Type */ +.highlight .ld { color: #000000 } /* Literal.Date */ +.highlight .m { color: #0000cf; font-weight: bold } /* Literal.Number */ +.highlight .s { color: #4e9a06 } /* Literal.String */ +.highlight .na { color: #c4a000 } /* Name.Attribute */ +.highlight .nb { color: #204a87 } /* Name.Builtin */ +.highlight .nc { color: #000000 } /* Name.Class */ +.highlight .no { color: #000000 } /* Name.Constant */ +.highlight .nd { color: #5c35cc; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #ce5c00 } /* Name.Entity */ +.highlight .ne { color: #cc0000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #000000 } /* Name.Function */ +.highlight .nl { color: #f57900 } /* Name.Label */ +.highlight .nn { color: #000000 } /* Name.Namespace */ +.highlight .nx { color: #000000 } /* Name.Other */ +.highlight .py { color: #000000 } /* Name.Property */ +.highlight .nt { color: #204a87; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #000000 } /* Name.Variable */ +.highlight .ow { color: #204a87; font-weight: bold } /* Operator.Word */ +.highlight .pm { color: #000000; font-weight: bold } /* Punctuation.Marker */ +.highlight .w { color: #f8f8f8 } /* Text.Whitespace */ +.highlight .mb { color: #0000cf; font-weight: bold } /* Literal.Number.Bin */ +.highlight .mf { color: #0000cf; font-weight: bold } /* Literal.Number.Float */ +.highlight .mh { color: #0000cf; font-weight: bold } /* Literal.Number.Hex */ +.highlight .mi { color: #0000cf; font-weight: bold } /* Literal.Number.Integer */ +.highlight .mo { color: #0000cf; font-weight: bold } /* Literal.Number.Oct */ +.highlight .sa { color: #4e9a06 } /* Literal.String.Affix */ +.highlight .sb { color: #4e9a06 } /* Literal.String.Backtick */ +.highlight .sc { color: #4e9a06 } /* Literal.String.Char */ +.highlight .dl { color: #4e9a06 } /* Literal.String.Delimiter */ +.highlight .sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4e9a06 } /* Literal.String.Double */ +.highlight .se { color: #4e9a06 } /* Literal.String.Escape */ +.highlight .sh { color: #4e9a06 } /* Literal.String.Heredoc */ +.highlight .si { color: #4e9a06 } /* Literal.String.Interpol */ +.highlight .sx { color: #4e9a06 } /* Literal.String.Other */ +.highlight .sr { color: #4e9a06 } /* Literal.String.Regex */ +.highlight .s1 { color: #4e9a06 } /* Literal.String.Single */ +.highlight .ss { color: #4e9a06 } /* Literal.String.Symbol */ +.highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #000000 } /* Name.Function.Magic */ +.highlight .vc { color: #000000 } /* Name.Variable.Class */ +.highlight .vg { color: #000000 } /* Name.Variable.Global */ +.highlight .vi { color: #000000 } /* Name.Variable.Instance */ +.highlight .vm { color: #000000 } /* Name.Variable.Magic */ +.highlight .il { color: #0000cf; font-weight: bold } /* Literal.Number.Integer.Long */ +@media not print { +body[data-theme="dark"] .highlight pre { line-height: 125%; } +body[data-theme="dark"] .highlight td.linenos .normal { color: #aaaaaa; background-color: transparent; padding-left: 5px; padding-right: 5px; } +body[data-theme="dark"] .highlight span.linenos { color: #aaaaaa; background-color: transparent; padding-left: 5px; padding-right: 5px; } +body[data-theme="dark"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +body[data-theme="dark"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +body[data-theme="dark"] .highlight .hll { background-color: #404040 } +body[data-theme="dark"] .highlight { background: #202020; color: #d0d0d0 } +body[data-theme="dark"] .highlight .c { color: #ababab; font-style: italic } /* Comment */ +body[data-theme="dark"] .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +body[data-theme="dark"] .highlight .esc { color: #d0d0d0 } /* Escape */ +body[data-theme="dark"] .highlight .g { color: #d0d0d0 } /* Generic */ +body[data-theme="dark"] .highlight .k { color: #6ebf26; font-weight: bold } /* Keyword */ +body[data-theme="dark"] .highlight .l { color: #d0d0d0 } /* Literal */ +body[data-theme="dark"] .highlight .n { color: #d0d0d0 } /* Name */ +body[data-theme="dark"] .highlight .o { color: #d0d0d0 } /* Operator */ +body[data-theme="dark"] .highlight .x { color: #d0d0d0 } /* Other */ +body[data-theme="dark"] .highlight .p { color: #d0d0d0 } /* Punctuation */ +body[data-theme="dark"] .highlight .ch { color: #ababab; font-style: italic } /* Comment.Hashbang */ +body[data-theme="dark"] .highlight .cm { color: #ababab; font-style: italic } /* Comment.Multiline */ +body[data-theme="dark"] .highlight .cp { color: #ff3a3a; font-weight: bold } /* Comment.Preproc */ +body[data-theme="dark"] .highlight .cpf { color: #ababab; font-style: italic } /* Comment.PreprocFile */ +body[data-theme="dark"] .highlight .c1 { color: #ababab; font-style: italic } /* Comment.Single */ +body[data-theme="dark"] .highlight .cs { color: #e50808; font-weight: bold; background-color: #520000 } /* Comment.Special */ +body[data-theme="dark"] .highlight .gd { color: #ff3a3a } /* Generic.Deleted */ +body[data-theme="dark"] .highlight .ge { color: #d0d0d0; font-style: italic } /* Generic.Emph */ +body[data-theme="dark"] .highlight .ges { color: #d0d0d0; font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +body[data-theme="dark"] .highlight .gr { color: #ff3a3a } /* Generic.Error */ +body[data-theme="dark"] .highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */ +body[data-theme="dark"] .highlight .gi { color: #589819 } /* Generic.Inserted */ +body[data-theme="dark"] .highlight .go { color: #cccccc } /* Generic.Output */ +body[data-theme="dark"] .highlight .gp { color: #aaaaaa } /* Generic.Prompt */ +body[data-theme="dark"] .highlight .gs { color: #d0d0d0; font-weight: bold } /* Generic.Strong */ +body[data-theme="dark"] .highlight .gu { color: #ffffff; text-decoration: underline } /* Generic.Subheading */ +body[data-theme="dark"] .highlight .gt { color: #ff3a3a } /* Generic.Traceback */ +body[data-theme="dark"] .highlight .kc { color: #6ebf26; font-weight: bold } /* Keyword.Constant */ +body[data-theme="dark"] .highlight .kd { color: #6ebf26; font-weight: bold } /* Keyword.Declaration */ +body[data-theme="dark"] .highlight .kn { color: #6ebf26; font-weight: bold } /* Keyword.Namespace */ +body[data-theme="dark"] .highlight .kp { color: #6ebf26 } /* Keyword.Pseudo */ +body[data-theme="dark"] .highlight .kr { color: #6ebf26; font-weight: bold } /* Keyword.Reserved */ +body[data-theme="dark"] .highlight .kt { color: #6ebf26; font-weight: bold } /* Keyword.Type */ +body[data-theme="dark"] .highlight .ld { color: #d0d0d0 } /* Literal.Date */ +body[data-theme="dark"] .highlight .m { color: #51b2fd } /* Literal.Number */ +body[data-theme="dark"] .highlight .s { color: #ed9d13 } /* Literal.String */ +body[data-theme="dark"] .highlight .na { color: #bbbbbb } /* Name.Attribute */ +body[data-theme="dark"] .highlight .nb { color: #2fbccd } /* Name.Builtin */ +body[data-theme="dark"] .highlight .nc { color: #71adff; text-decoration: underline } /* Name.Class */ +body[data-theme="dark"] .highlight .no { color: #40ffff } /* Name.Constant */ +body[data-theme="dark"] .highlight .nd { color: #ffa500 } /* Name.Decorator */ +body[data-theme="dark"] .highlight .ni { color: #d0d0d0 } /* Name.Entity */ +body[data-theme="dark"] .highlight .ne { color: #bbbbbb } /* Name.Exception */ +body[data-theme="dark"] .highlight .nf { color: #71adff } /* Name.Function */ +body[data-theme="dark"] .highlight .nl { color: #d0d0d0 } /* Name.Label */ +body[data-theme="dark"] .highlight .nn { color: #71adff; text-decoration: underline } /* Name.Namespace */ +body[data-theme="dark"] .highlight .nx { color: #d0d0d0 } /* Name.Other */ +body[data-theme="dark"] .highlight .py { color: #d0d0d0 } /* Name.Property */ +body[data-theme="dark"] .highlight .nt { color: #6ebf26; font-weight: bold } /* Name.Tag */ +body[data-theme="dark"] .highlight .nv { color: #40ffff } /* Name.Variable */ +body[data-theme="dark"] .highlight .ow { color: #6ebf26; font-weight: bold } /* Operator.Word */ +body[data-theme="dark"] .highlight .pm { color: #d0d0d0 } /* Punctuation.Marker */ +body[data-theme="dark"] .highlight .w { color: #666666 } /* Text.Whitespace */ +body[data-theme="dark"] .highlight .mb { color: #51b2fd } /* Literal.Number.Bin */ +body[data-theme="dark"] .highlight .mf { color: #51b2fd } /* Literal.Number.Float */ +body[data-theme="dark"] .highlight .mh { color: #51b2fd } /* Literal.Number.Hex */ +body[data-theme="dark"] .highlight .mi { color: #51b2fd } /* Literal.Number.Integer */ +body[data-theme="dark"] .highlight .mo { color: #51b2fd } /* Literal.Number.Oct */ +body[data-theme="dark"] .highlight .sa { color: #ed9d13 } /* Literal.String.Affix */ +body[data-theme="dark"] .highlight .sb { color: #ed9d13 } /* Literal.String.Backtick */ +body[data-theme="dark"] .highlight .sc { color: #ed9d13 } /* Literal.String.Char */ +body[data-theme="dark"] .highlight .dl { color: #ed9d13 } /* Literal.String.Delimiter */ +body[data-theme="dark"] .highlight .sd { color: #ed9d13 } /* Literal.String.Doc */ +body[data-theme="dark"] .highlight .s2 { color: #ed9d13 } /* Literal.String.Double */ +body[data-theme="dark"] .highlight .se { color: #ed9d13 } /* Literal.String.Escape */ +body[data-theme="dark"] .highlight .sh { color: #ed9d13 } /* Literal.String.Heredoc */ +body[data-theme="dark"] .highlight .si { color: #ed9d13 } /* Literal.String.Interpol */ +body[data-theme="dark"] .highlight .sx { color: #ffa500 } /* Literal.String.Other */ +body[data-theme="dark"] .highlight .sr { color: #ed9d13 } /* Literal.String.Regex */ +body[data-theme="dark"] .highlight .s1 { color: #ed9d13 } /* Literal.String.Single */ +body[data-theme="dark"] .highlight .ss { color: #ed9d13 } /* Literal.String.Symbol */ +body[data-theme="dark"] .highlight .bp { color: #2fbccd } /* Name.Builtin.Pseudo */ +body[data-theme="dark"] .highlight .fm { color: #71adff } /* Name.Function.Magic */ +body[data-theme="dark"] .highlight .vc { color: #40ffff } /* Name.Variable.Class */ +body[data-theme="dark"] .highlight .vg { color: #40ffff } /* Name.Variable.Global */ +body[data-theme="dark"] .highlight .vi { color: #40ffff } /* Name.Variable.Instance */ +body[data-theme="dark"] .highlight .vm { color: #40ffff } /* Name.Variable.Magic */ +body[data-theme="dark"] .highlight .il { color: #51b2fd } /* Literal.Number.Integer.Long */ +@media (prefers-color-scheme: dark) { +body:not([data-theme="light"]) .highlight pre { line-height: 125%; } +body:not([data-theme="light"]) .highlight td.linenos .normal { color: #aaaaaa; background-color: transparent; padding-left: 5px; padding-right: 5px; } +body:not([data-theme="light"]) .highlight span.linenos { color: #aaaaaa; background-color: transparent; padding-left: 5px; padding-right: 5px; } +body:not([data-theme="light"]) .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +body:not([data-theme="light"]) .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +body:not([data-theme="light"]) .highlight .hll { background-color: #404040 } +body:not([data-theme="light"]) .highlight { background: #202020; color: #d0d0d0 } +body:not([data-theme="light"]) .highlight .c { color: #ababab; font-style: italic } /* Comment */ +body:not([data-theme="light"]) .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +body:not([data-theme="light"]) .highlight .esc { color: #d0d0d0 } /* Escape */ +body:not([data-theme="light"]) .highlight .g { color: #d0d0d0 } /* Generic */ +body:not([data-theme="light"]) .highlight .k { color: #6ebf26; font-weight: bold } /* Keyword */ +body:not([data-theme="light"]) .highlight .l { color: #d0d0d0 } /* Literal */ +body:not([data-theme="light"]) .highlight .n { color: #d0d0d0 } /* Name */ +body:not([data-theme="light"]) .highlight .o { color: #d0d0d0 } /* Operator */ +body:not([data-theme="light"]) .highlight .x { color: #d0d0d0 } /* Other */ +body:not([data-theme="light"]) .highlight .p { color: #d0d0d0 } /* Punctuation */ +body:not([data-theme="light"]) .highlight .ch { color: #ababab; font-style: italic } /* Comment.Hashbang */ +body:not([data-theme="light"]) .highlight .cm { color: #ababab; font-style: italic } /* Comment.Multiline */ +body:not([data-theme="light"]) .highlight .cp { color: #ff3a3a; font-weight: bold } /* Comment.Preproc */ +body:not([data-theme="light"]) .highlight .cpf { color: #ababab; font-style: italic } /* Comment.PreprocFile */ +body:not([data-theme="light"]) .highlight .c1 { color: #ababab; font-style: italic } /* Comment.Single */ +body:not([data-theme="light"]) .highlight .cs { color: #e50808; font-weight: bold; background-color: #520000 } /* Comment.Special */ +body:not([data-theme="light"]) .highlight .gd { color: #ff3a3a } /* Generic.Deleted */ +body:not([data-theme="light"]) .highlight .ge { color: #d0d0d0; font-style: italic } /* Generic.Emph */ +body:not([data-theme="light"]) .highlight .ges { color: #d0d0d0; font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +body:not([data-theme="light"]) .highlight .gr { color: #ff3a3a } /* Generic.Error */ +body:not([data-theme="light"]) .highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */ +body:not([data-theme="light"]) .highlight .gi { color: #589819 } /* Generic.Inserted */ +body:not([data-theme="light"]) .highlight .go { color: #cccccc } /* Generic.Output */ +body:not([data-theme="light"]) .highlight .gp { color: #aaaaaa } /* Generic.Prompt */ +body:not([data-theme="light"]) .highlight .gs { color: #d0d0d0; font-weight: bold } /* Generic.Strong */ +body:not([data-theme="light"]) .highlight .gu { color: #ffffff; text-decoration: underline } /* Generic.Subheading */ +body:not([data-theme="light"]) .highlight .gt { color: #ff3a3a } /* Generic.Traceback */ +body:not([data-theme="light"]) .highlight .kc { color: #6ebf26; font-weight: bold } /* Keyword.Constant */ +body:not([data-theme="light"]) .highlight .kd { color: #6ebf26; font-weight: bold } /* Keyword.Declaration */ +body:not([data-theme="light"]) .highlight .kn { color: #6ebf26; font-weight: bold } /* Keyword.Namespace */ +body:not([data-theme="light"]) .highlight .kp { color: #6ebf26 } /* Keyword.Pseudo */ +body:not([data-theme="light"]) .highlight .kr { color: #6ebf26; font-weight: bold } /* Keyword.Reserved */ +body:not([data-theme="light"]) .highlight .kt { color: #6ebf26; font-weight: bold } /* Keyword.Type */ +body:not([data-theme="light"]) .highlight .ld { color: #d0d0d0 } /* Literal.Date */ +body:not([data-theme="light"]) .highlight .m { color: #51b2fd } /* Literal.Number */ +body:not([data-theme="light"]) .highlight .s { color: #ed9d13 } /* Literal.String */ +body:not([data-theme="light"]) .highlight .na { color: #bbbbbb } /* Name.Attribute */ +body:not([data-theme="light"]) .highlight .nb { color: #2fbccd } /* Name.Builtin */ +body:not([data-theme="light"]) .highlight .nc { color: #71adff; text-decoration: underline } /* Name.Class */ +body:not([data-theme="light"]) .highlight .no { color: #40ffff } /* Name.Constant */ +body:not([data-theme="light"]) .highlight .nd { color: #ffa500 } /* Name.Decorator */ +body:not([data-theme="light"]) .highlight .ni { color: #d0d0d0 } /* Name.Entity */ +body:not([data-theme="light"]) .highlight .ne { color: #bbbbbb } /* Name.Exception */ +body:not([data-theme="light"]) .highlight .nf { color: #71adff } /* Name.Function */ +body:not([data-theme="light"]) .highlight .nl { color: #d0d0d0 } /* Name.Label */ +body:not([data-theme="light"]) .highlight .nn { color: #71adff; text-decoration: underline } /* Name.Namespace */ +body:not([data-theme="light"]) .highlight .nx { color: #d0d0d0 } /* Name.Other */ +body:not([data-theme="light"]) .highlight .py { color: #d0d0d0 } /* Name.Property */ +body:not([data-theme="light"]) .highlight .nt { color: #6ebf26; font-weight: bold } /* Name.Tag */ +body:not([data-theme="light"]) .highlight .nv { color: #40ffff } /* Name.Variable */ +body:not([data-theme="light"]) .highlight .ow { color: #6ebf26; font-weight: bold } /* Operator.Word */ +body:not([data-theme="light"]) .highlight .pm { color: #d0d0d0 } /* Punctuation.Marker */ +body:not([data-theme="light"]) .highlight .w { color: #666666 } /* Text.Whitespace */ +body:not([data-theme="light"]) .highlight .mb { color: #51b2fd } /* Literal.Number.Bin */ +body:not([data-theme="light"]) .highlight .mf { color: #51b2fd } /* Literal.Number.Float */ +body:not([data-theme="light"]) .highlight .mh { color: #51b2fd } /* Literal.Number.Hex */ +body:not([data-theme="light"]) .highlight .mi { color: #51b2fd } /* Literal.Number.Integer */ +body:not([data-theme="light"]) .highlight .mo { color: #51b2fd } /* Literal.Number.Oct */ +body:not([data-theme="light"]) .highlight .sa { color: #ed9d13 } /* Literal.String.Affix */ +body:not([data-theme="light"]) .highlight .sb { color: #ed9d13 } /* Literal.String.Backtick */ +body:not([data-theme="light"]) .highlight .sc { color: #ed9d13 } /* Literal.String.Char */ +body:not([data-theme="light"]) .highlight .dl { color: #ed9d13 } /* Literal.String.Delimiter */ +body:not([data-theme="light"]) .highlight .sd { color: #ed9d13 } /* Literal.String.Doc */ +body:not([data-theme="light"]) .highlight .s2 { color: #ed9d13 } /* Literal.String.Double */ +body:not([data-theme="light"]) .highlight .se { color: #ed9d13 } /* Literal.String.Escape */ +body:not([data-theme="light"]) .highlight .sh { color: #ed9d13 } /* Literal.String.Heredoc */ +body:not([data-theme="light"]) .highlight .si { color: #ed9d13 } /* Literal.String.Interpol */ +body:not([data-theme="light"]) .highlight .sx { color: #ffa500 } /* Literal.String.Other */ +body:not([data-theme="light"]) .highlight .sr { color: #ed9d13 } /* Literal.String.Regex */ +body:not([data-theme="light"]) .highlight .s1 { color: #ed9d13 } /* Literal.String.Single */ +body:not([data-theme="light"]) .highlight .ss { color: #ed9d13 } /* Literal.String.Symbol */ +body:not([data-theme="light"]) .highlight .bp { color: #2fbccd } /* Name.Builtin.Pseudo */ +body:not([data-theme="light"]) .highlight .fm { color: #71adff } /* Name.Function.Magic */ +body:not([data-theme="light"]) .highlight .vc { color: #40ffff } /* Name.Variable.Class */ +body:not([data-theme="light"]) .highlight .vg { color: #40ffff } /* Name.Variable.Global */ +body:not([data-theme="light"]) .highlight .vi { color: #40ffff } /* Name.Variable.Instance */ +body:not([data-theme="light"]) .highlight .vm { color: #40ffff } /* Name.Variable.Magic */ +body:not([data-theme="light"]) .highlight .il { color: #51b2fd } /* Literal.Number.Integer.Long */ +} +} \ No newline at end of file diff --git a/_static/sandbox-small.png b/_static/sandbox-small.png new file mode 100644 index 0000000000..a7e86244dd Binary files /dev/null and b/_static/sandbox-small.png differ diff --git a/_static/sandbox-tiny.png b/_static/sandbox-tiny.png new file mode 100644 index 0000000000..02965183c0 Binary files /dev/null and b/_static/sandbox-tiny.png differ diff --git a/_static/sandbox.png b/_static/sandbox.png new file mode 100644 index 0000000000..4623d5e5b6 Binary files /dev/null and b/_static/sandbox.png differ diff --git a/_static/scripts/furo-extensions.js b/_static/scripts/furo-extensions.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/_static/scripts/furo.js b/_static/scripts/furo.js new file mode 100644 index 0000000000..0267c7e112 --- /dev/null +++ b/_static/scripts/furo.js @@ -0,0 +1,3 @@ +/*! For license information please see furo.js.LICENSE.txt */ +(()=>{var t={856:function(t,e,n){var o,r;r=void 0!==n.g?n.g:"undefined"!=typeof window?window:this,o=function(){return function(t){"use strict";var e={navClass:"active",contentClass:"active",nested:!1,nestedClass:"active",offset:0,reflow:!1,events:!0},n=function(t,e,n){if(n.settings.events){var o=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:n});e.dispatchEvent(o)}},o=function(t){var e=0;if(t.offsetParent)for(;t;)e+=t.offsetTop,t=t.offsetParent;return e>=0?e:0},r=function(t){t&&t.sort((function(t,e){return o(t.content)=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight,document.body.offsetHeight,document.documentElement.offsetHeight,document.body.clientHeight,document.documentElement.clientHeight)},l=function(t,e){var n=t[t.length-1];if(function(t,e){return!(!s()||!c(t.content,e,!0))}(n,e))return n;for(var o=t.length-1;o>=0;o--)if(c(t[o].content,e))return t[o]},a=function(t,e){if(e.nested&&t.parentNode){var n=t.parentNode.closest("li");n&&(n.classList.remove(e.nestedClass),a(n,e))}},i=function(t,e){if(t){var o=t.nav.closest("li");o&&(o.classList.remove(e.navClass),t.content.classList.remove(e.contentClass),a(o,e),n("gumshoeDeactivate",o,{link:t.nav,content:t.content,settings:e}))}},u=function(t,e){if(e.nested){var n=t.parentNode.closest("li");n&&(n.classList.add(e.nestedClass),u(n,e))}};return function(o,c){var s,a,d,f,m,v={setup:function(){s=document.querySelectorAll(o),a=[],Array.prototype.forEach.call(s,(function(t){var e=document.getElementById(decodeURIComponent(t.hash.substr(1)));e&&a.push({nav:t,content:e})})),r(a)},detect:function(){var t=l(a,m);t?d&&t.content===d.content||(i(d,m),function(t,e){if(t){var o=t.nav.closest("li");o&&(o.classList.add(e.navClass),t.content.classList.add(e.contentClass),u(o,e),n("gumshoeActivate",o,{link:t.nav,content:t.content,settings:e}))}}(t,m),d=t):d&&(i(d,m),d=null)}},h=function(e){f&&t.cancelAnimationFrame(f),f=t.requestAnimationFrame(v.detect)},g=function(e){f&&t.cancelAnimationFrame(f),f=t.requestAnimationFrame((function(){r(a),v.detect()}))};return v.destroy=function(){d&&i(d,m),t.removeEventListener("scroll",h,!1),m.reflow&&t.removeEventListener("resize",g,!1),a=null,s=null,d=null,f=null,m=null},m=function(){var t={};return Array.prototype.forEach.call(arguments,(function(e){for(var n in e){if(!e.hasOwnProperty(n))return;t[n]=e[n]}})),t}(e,c||{}),v.setup(),v.detect(),t.addEventListener("scroll",h,!1),m.reflow&&t.addEventListener("resize",g,!1),v}}(r)}.apply(e,[]),void 0===o||(t.exports=o)}},e={};function n(o){var r=e[o];if(void 0!==r)return r.exports;var c=e[o]={exports:{}};return t[o].call(c.exports,c,c.exports,n),c.exports}n.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var o in e)n.o(e,o)&&!n.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),(()=>{"use strict";var t=n(856),e=n.n(t),o=null,r=null,c=window.pageYOffset||document.documentElement.scrollTop;const s=64;function l(){const t=localStorage.getItem("theme")||"auto";var e;"light"!==(e=window.matchMedia("(prefers-color-scheme: dark)").matches?"auto"===t?"light":"light"==t?"dark":"auto":"auto"===t?"dark":"dark"==t?"light":"auto")&&"dark"!==e&&"auto"!==e&&(console.error(`Got invalid theme mode: ${e}. Resetting to auto.`),e="auto"),document.body.dataset.theme=e,localStorage.setItem("theme",e),console.log(`Changed to ${e} mode.`)}function a(){!function(){const t=document.getElementsByClassName("theme-toggle");Array.from(t).forEach((t=>{t.addEventListener("click",l)}))}(),function(){let t=0,e=!1;window.addEventListener("scroll",(function(n){t=window.scrollY,e||(window.requestAnimationFrame((function(){var n;n=t,0==Math.floor(r.getBoundingClientRect().top)?r.classList.add("scrolled"):r.classList.remove("scrolled"),function(t){tc&&document.documentElement.classList.remove("show-back-to-top"),c=t}(n),function(t){null!==o&&(0==t?o.scrollTo(0,0):Math.ceil(t)>=Math.floor(document.documentElement.scrollHeight-window.innerHeight)?o.scrollTo(0,o.scrollHeight):document.querySelector(".scroll-current"))}(n),e=!1})),e=!0)})),window.scroll()}(),null!==o&&new(e())(".toc-tree a",{reflow:!0,recursive:!0,navClass:"scroll-current",offset:()=>{let t=parseFloat(getComputedStyle(document.documentElement).fontSize);return r.getBoundingClientRect().height+2.5*t+1}})}document.addEventListener("DOMContentLoaded",(function(){document.body.parentNode.classList.remove("no-js"),r=document.querySelector("header"),o=document.querySelector(".toc-scroll"),a()}))})()})(); +//# sourceMappingURL=furo.js.map \ No newline at end of file diff --git a/_static/scripts/furo.js.LICENSE.txt b/_static/scripts/furo.js.LICENSE.txt new file mode 100644 index 0000000000..1632189c7e --- /dev/null +++ b/_static/scripts/furo.js.LICENSE.txt @@ -0,0 +1,7 @@ +/*! + * gumshoejs v5.1.2 (patched by @pradyunsg) + * A simple, framework-agnostic scrollspy script. + * (c) 2019 Chris Ferdinandi + * MIT License + * http://github.com/cferdinandi/gumshoe + */ diff --git a/_static/scripts/furo.js.map b/_static/scripts/furo.js.map new file mode 100644 index 0000000000..c3b37aaa94 --- /dev/null +++ b/_static/scripts/furo.js.map @@ -0,0 +1 @@ +{"version":3,"file":"scripts/furo.js","mappings":";iCAAA,MAQWA,SAWS,IAAX,EAAAC,EACH,EAAAA,EACkB,oBAAXC,OACLA,OACAC,KAbO,EAAF,WACP,OAaJ,SAAUD,GACR,aAMA,IAAIE,EAAW,CAEbC,SAAU,SACVC,aAAc,SAGdC,QAAQ,EACRC,YAAa,SAGbC,OAAQ,EACRC,QAAQ,EAGRC,QAAQ,GA6BNC,EAAY,SAAUC,EAAMC,EAAMC,GAEpC,GAAKA,EAAOC,SAASL,OAArB,CAGA,IAAIM,EAAQ,IAAIC,YAAYL,EAAM,CAChCM,SAAS,EACTC,YAAY,EACZL,OAAQA,IAIVD,EAAKO,cAAcJ,EAVgB,CAWrC,EAOIK,EAAe,SAAUR,GAC3B,IAAIS,EAAW,EACf,GAAIT,EAAKU,aACP,KAAOV,GACLS,GAAYT,EAAKW,UACjBX,EAAOA,EAAKU,aAGhB,OAAOD,GAAY,EAAIA,EAAW,CACpC,EAMIG,EAAe,SAAUC,GACvBA,GACFA,EAASC,MAAK,SAAUC,EAAOC,GAG7B,OAFcR,EAAaO,EAAME,SACnBT,EAAaQ,EAAMC,UACF,EACxB,CACT,GAEJ,EAwCIC,EAAW,SAAUlB,EAAME,EAAUiB,GACvC,IAAIC,EAASpB,EAAKqB,wBACd1B,EAnCU,SAAUO,GAExB,MAA+B,mBAApBA,EAASP,OACX2B,WAAWpB,EAASP,UAItB2B,WAAWpB,EAASP,OAC7B,CA2Be4B,CAAUrB,GACvB,OAAIiB,EAEAK,SAASJ,EAAOD,OAAQ,KACvB/B,EAAOqC,aAAeC,SAASC,gBAAgBC,cAG7CJ,SAASJ,EAAOS,IAAK,KAAOlC,CACrC,EAMImC,EAAa,WACf,OACEC,KAAKC,KAAK5C,EAAOqC,YAAcrC,EAAO6C,cAnCjCF,KAAKG,IACVR,SAASS,KAAKC,aACdV,SAASC,gBAAgBS,aACzBV,SAASS,KAAKE,aACdX,SAASC,gBAAgBU,aACzBX,SAASS,KAAKP,aACdF,SAASC,gBAAgBC,aAkC7B,EAmBIU,EAAY,SAAUzB,EAAUX,GAClC,IAAIqC,EAAO1B,EAASA,EAAS2B,OAAS,GACtC,GAbgB,SAAUC,EAAMvC,GAChC,SAAI4B,MAAgBZ,EAASuB,EAAKxB,QAASf,GAAU,GAEvD,CAUMwC,CAAYH,EAAMrC,GAAW,OAAOqC,EACxC,IAAK,IAAII,EAAI9B,EAAS2B,OAAS,EAAGG,GAAK,EAAGA,IACxC,GAAIzB,EAASL,EAAS8B,GAAG1B,QAASf,GAAW,OAAOW,EAAS8B,EAEjE,EAOIC,EAAmB,SAAUC,EAAK3C,GAEpC,GAAKA,EAAST,QAAWoD,EAAIC,WAA7B,CAGA,IAAIC,EAAKF,EAAIC,WAAWE,QAAQ,MAC3BD,IAGLA,EAAGE,UAAUC,OAAOhD,EAASR,aAG7BkD,EAAiBG,EAAI7C,GAV0B,CAWjD,EAOIiD,EAAa,SAAUC,EAAOlD,GAEhC,GAAKkD,EAAL,CAGA,IAAIL,EAAKK,EAAMP,IAAIG,QAAQ,MACtBD,IAGLA,EAAGE,UAAUC,OAAOhD,EAASX,UAC7B6D,EAAMnC,QAAQgC,UAAUC,OAAOhD,EAASV,cAGxCoD,EAAiBG,EAAI7C,GAGrBJ,EAAU,oBAAqBiD,EAAI,CACjCM,KAAMD,EAAMP,IACZ5B,QAASmC,EAAMnC,QACff,SAAUA,IAjBM,CAmBpB,EAOIoD,EAAiB,SAAUT,EAAK3C,GAElC,GAAKA,EAAST,OAAd,CAGA,IAAIsD,EAAKF,EAAIC,WAAWE,QAAQ,MAC3BD,IAGLA,EAAGE,UAAUM,IAAIrD,EAASR,aAG1B4D,EAAeP,EAAI7C,GAVS,CAW9B,EA6LA,OA1JkB,SAAUsD,EAAUC,GAKpC,IACIC,EAAU7C,EAAU8C,EAASC,EAAS1D,EADtC2D,EAAa,CAUjBA,MAAmB,WAEjBH,EAAWhC,SAASoC,iBAAiBN,GAGrC3C,EAAW,GAGXkD,MAAMC,UAAUC,QAAQC,KAAKR,GAAU,SAAUjB,GAE/C,IAAIxB,EAAUS,SAASyC,eACrBC,mBAAmB3B,EAAK4B,KAAKC,OAAO,KAEjCrD,GAGLJ,EAAS0D,KAAK,CACZ1B,IAAKJ,EACLxB,QAASA,GAEb,IAGAL,EAAaC,EACf,EAKAgD,OAAoB,WAElB,IAAIW,EAASlC,EAAUzB,EAAUX,GAG5BsE,EASDb,GAAWa,EAAOvD,UAAY0C,EAAQ1C,UAG1CkC,EAAWQ,EAASzD,GAzFT,SAAUkD,EAAOlD,GAE9B,GAAKkD,EAAL,CAGA,IAAIL,EAAKK,EAAMP,IAAIG,QAAQ,MACtBD,IAGLA,EAAGE,UAAUM,IAAIrD,EAASX,UAC1B6D,EAAMnC,QAAQgC,UAAUM,IAAIrD,EAASV,cAGrC8D,EAAeP,EAAI7C,GAGnBJ,EAAU,kBAAmBiD,EAAI,CAC/BM,KAAMD,EAAMP,IACZ5B,QAASmC,EAAMnC,QACff,SAAUA,IAjBM,CAmBpB,CAqEIuE,CAASD,EAAQtE,GAGjByD,EAAUa,GAfJb,IACFR,EAAWQ,EAASzD,GACpByD,EAAU,KAchB,GAMIe,EAAgB,SAAUvE,GAExByD,GACFxE,EAAOuF,qBAAqBf,GAI9BA,EAAUxE,EAAOwF,sBAAsBf,EAAWgB,OACpD,EAMIC,EAAgB,SAAU3E,GAExByD,GACFxE,EAAOuF,qBAAqBf,GAI9BA,EAAUxE,EAAOwF,uBAAsB,WACrChE,EAAaC,GACbgD,EAAWgB,QACb,GACF,EAkDA,OA7CAhB,EAAWkB,QAAU,WAEfpB,GACFR,EAAWQ,EAASzD,GAItBd,EAAO4F,oBAAoB,SAAUN,GAAe,GAChDxE,EAASN,QACXR,EAAO4F,oBAAoB,SAAUF,GAAe,GAItDjE,EAAW,KACX6C,EAAW,KACXC,EAAU,KACVC,EAAU,KACV1D,EAAW,IACb,EAOEA,EA3XS,WACX,IAAI+E,EAAS,CAAC,EAOd,OANAlB,MAAMC,UAAUC,QAAQC,KAAKgB,WAAW,SAAUC,GAChD,IAAK,IAAIC,KAAOD,EAAK,CACnB,IAAKA,EAAIE,eAAeD,GAAM,OAC9BH,EAAOG,GAAOD,EAAIC,EACpB,CACF,IACOH,CACT,CAkXeK,CAAOhG,EAAUmE,GAAW,CAAC,GAGxCI,EAAW0B,QAGX1B,EAAWgB,SAGXzF,EAAOoG,iBAAiB,SAAUd,GAAe,GAC7CxE,EAASN,QACXR,EAAOoG,iBAAiB,SAAUV,GAAe,GAS9CjB,CACT,CAOF,CArcW4B,CAAQvG,EAChB,UAFM,SAEN,uBCXDwG,EAA2B,CAAC,EAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBE,IAAjBD,EACH,OAAOA,EAAaE,QAGrB,IAAIC,EAASN,EAAyBE,GAAY,CAGjDG,QAAS,CAAC,GAOX,OAHAE,EAAoBL,GAAU1B,KAAK8B,EAAOD,QAASC,EAAQA,EAAOD,QAASJ,GAGpEK,EAAOD,OACf,CCrBAJ,EAAoBO,EAAKF,IACxB,IAAIG,EAASH,GAAUA,EAAOI,WAC7B,IAAOJ,EAAiB,QACxB,IAAM,EAEP,OADAL,EAAoBU,EAAEF,EAAQ,CAAEG,EAAGH,IAC5BA,CAAM,ECLdR,EAAoBU,EAAI,CAACN,EAASQ,KACjC,IAAI,IAAInB,KAAOmB,EACXZ,EAAoBa,EAAED,EAAYnB,KAASO,EAAoBa,EAAET,EAASX,IAC5EqB,OAAOC,eAAeX,EAASX,EAAK,CAAEuB,YAAY,EAAMC,IAAKL,EAAWnB,IAE1E,ECNDO,EAAoBxG,EAAI,WACvB,GAA0B,iBAAf0H,WAAyB,OAAOA,WAC3C,IACC,OAAOxH,MAAQ,IAAIyH,SAAS,cAAb,EAChB,CAAE,MAAOC,GACR,GAAsB,iBAAX3H,OAAqB,OAAOA,MACxC,CACA,CAPuB,GCAxBuG,EAAoBa,EAAI,CAACrB,EAAK6B,IAAUP,OAAOzC,UAAUqB,eAAenB,KAAKiB,EAAK6B,4CCK9EC,EAAY,KACZC,EAAS,KACTC,EAAgB/H,OAAO6C,aAAeP,SAASC,gBAAgByF,UACnE,MAAMC,EAAmB,GA2EzB,SAASC,IACP,MAAMC,EAAeC,aAAaC,QAAQ,UAAY,OAZxD,IAAkBC,EACH,WADGA,EAaItI,OAAOuI,WAAW,gCAAgCC,QAI/C,SAAjBL,EACO,QACgB,SAAhBA,EACA,OAEA,OAIU,SAAjBA,EACO,OACgB,QAAhBA,EACA,QAEA,SA9BoB,SAATG,GAA4B,SAATA,IACzCG,QAAQC,MAAM,2BAA2BJ,yBACzCA,EAAO,QAGThG,SAASS,KAAK4F,QAAQC,MAAQN,EAC9BF,aAAaS,QAAQ,QAASP,GAC9BG,QAAQK,IAAI,cAAcR,UA0B5B,CAkDA,SAASnC,KART,WAEE,MAAM4C,EAAUzG,SAAS0G,uBAAuB,gBAChDrE,MAAMsE,KAAKF,GAASlE,SAASqE,IAC3BA,EAAI9C,iBAAiB,QAAS8B,EAAe,GAEjD,CAGEiB,GA9CF,WAEE,IAAIC,EAA6B,EAC7BC,GAAU,EAEdrJ,OAAOoG,iBAAiB,UAAU,SAAUuB,GAC1CyB,EAA6BpJ,OAAOsJ,QAE/BD,IACHrJ,OAAOwF,uBAAsB,WAzDnC,IAAuB+D,IA0DDH,EA9GkC,GAAlDzG,KAAK6G,MAAM1B,EAAO7F,wBAAwBQ,KAC5CqF,EAAOjE,UAAUM,IAAI,YAErB2D,EAAOjE,UAAUC,OAAO,YAI5B,SAAmCyF,GAC7BA,EAAYtB,EACd3F,SAASC,gBAAgBsB,UAAUC,OAAO,oBAEtCyF,EAAYxB,EACdzF,SAASC,gBAAgBsB,UAAUM,IAAI,oBAC9BoF,EAAYxB,GACrBzF,SAASC,gBAAgBsB,UAAUC,OAAO,oBAG9CiE,EAAgBwB,CAClB,CAoCEE,CAA0BF,GAlC5B,SAA6BA,GACT,OAAd1B,IAKa,GAAb0B,EACF1B,EAAU6B,SAAS,EAAG,GAGtB/G,KAAKC,KAAK2G,IACV5G,KAAK6G,MAAMlH,SAASC,gBAAgBS,aAAehD,OAAOqC,aAE1DwF,EAAU6B,SAAS,EAAG7B,EAAU7E,cAGhBV,SAASqH,cAAc,mBAc3C,CAKEC,CAAoBL,GAwDdF,GAAU,CACZ,IAEAA,GAAU,EAEd,IACArJ,OAAO6J,QACT,CA6BEC,GA1BkB,OAAdjC,GAKJ,IAAI,IAAJ,CAAY,cAAe,CACzBrH,QAAQ,EACRuJ,WAAW,EACX5J,SAAU,iBACVI,OAAQ,KACN,IAAIyJ,EAAM9H,WAAW+H,iBAAiB3H,SAASC,iBAAiB2H,UAChE,OAAOpC,EAAO7F,wBAAwBkI,OAAS,IAAMH,EAAM,CAAC,GAiBlE,CAcA1H,SAAS8D,iBAAiB,oBAT1B,WACE9D,SAASS,KAAKW,WAAWG,UAAUC,OAAO,SAE1CgE,EAASxF,SAASqH,cAAc,UAChC9B,EAAYvF,SAASqH,cAAc,eAEnCxD,GACF","sources":["webpack:///./src/furo/assets/scripts/gumshoe-patched.js","webpack:///webpack/bootstrap","webpack:///webpack/runtime/compat get default export","webpack:///webpack/runtime/define property getters","webpack:///webpack/runtime/global","webpack:///webpack/runtime/hasOwnProperty shorthand","webpack:///./src/furo/assets/scripts/furo.js"],"sourcesContent":["/*!\n * gumshoejs v5.1.2 (patched by @pradyunsg)\n * A simple, framework-agnostic scrollspy script.\n * (c) 2019 Chris Ferdinandi\n * MIT License\n * http://github.com/cferdinandi/gumshoe\n */\n\n(function (root, factory) {\n if (typeof define === \"function\" && define.amd) {\n define([], function () {\n return factory(root);\n });\n } else if (typeof exports === \"object\") {\n module.exports = factory(root);\n } else {\n root.Gumshoe = factory(root);\n }\n})(\n typeof global !== \"undefined\"\n ? global\n : typeof window !== \"undefined\"\n ? window\n : this,\n function (window) {\n \"use strict\";\n\n //\n // Defaults\n //\n\n var defaults = {\n // Active classes\n navClass: \"active\",\n contentClass: \"active\",\n\n // Nested navigation\n nested: false,\n nestedClass: \"active\",\n\n // Offset & reflow\n offset: 0,\n reflow: false,\n\n // Event support\n events: true,\n };\n\n //\n // Methods\n //\n\n /**\n * Merge two or more objects together.\n * @param {Object} objects The objects to merge together\n * @returns {Object} Merged values of defaults and options\n */\n var extend = function () {\n var merged = {};\n Array.prototype.forEach.call(arguments, function (obj) {\n for (var key in obj) {\n if (!obj.hasOwnProperty(key)) return;\n merged[key] = obj[key];\n }\n });\n return merged;\n };\n\n /**\n * Emit a custom event\n * @param {String} type The event type\n * @param {Node} elem The element to attach the event to\n * @param {Object} detail Any details to pass along with the event\n */\n var emitEvent = function (type, elem, detail) {\n // Make sure events are enabled\n if (!detail.settings.events) return;\n\n // Create a new event\n var event = new CustomEvent(type, {\n bubbles: true,\n cancelable: true,\n detail: detail,\n });\n\n // Dispatch the event\n elem.dispatchEvent(event);\n };\n\n /**\n * Get an element's distance from the top of the Document.\n * @param {Node} elem The element\n * @return {Number} Distance from the top in pixels\n */\n var getOffsetTop = function (elem) {\n var location = 0;\n if (elem.offsetParent) {\n while (elem) {\n location += elem.offsetTop;\n elem = elem.offsetParent;\n }\n }\n return location >= 0 ? location : 0;\n };\n\n /**\n * Sort content from first to last in the DOM\n * @param {Array} contents The content areas\n */\n var sortContents = function (contents) {\n if (contents) {\n contents.sort(function (item1, item2) {\n var offset1 = getOffsetTop(item1.content);\n var offset2 = getOffsetTop(item2.content);\n if (offset1 < offset2) return -1;\n return 1;\n });\n }\n };\n\n /**\n * Get the offset to use for calculating position\n * @param {Object} settings The settings for this instantiation\n * @return {Float} The number of pixels to offset the calculations\n */\n var getOffset = function (settings) {\n // if the offset is a function run it\n if (typeof settings.offset === \"function\") {\n return parseFloat(settings.offset());\n }\n\n // Otherwise, return it as-is\n return parseFloat(settings.offset);\n };\n\n /**\n * Get the document element's height\n * @private\n * @returns {Number}\n */\n var getDocumentHeight = function () {\n return Math.max(\n document.body.scrollHeight,\n document.documentElement.scrollHeight,\n document.body.offsetHeight,\n document.documentElement.offsetHeight,\n document.body.clientHeight,\n document.documentElement.clientHeight,\n );\n };\n\n /**\n * Determine if an element is in view\n * @param {Node} elem The element\n * @param {Object} settings The settings for this instantiation\n * @param {Boolean} bottom If true, check if element is above bottom of viewport instead\n * @return {Boolean} Returns true if element is in the viewport\n */\n var isInView = function (elem, settings, bottom) {\n var bounds = elem.getBoundingClientRect();\n var offset = getOffset(settings);\n if (bottom) {\n return (\n parseInt(bounds.bottom, 10) <\n (window.innerHeight || document.documentElement.clientHeight)\n );\n }\n return parseInt(bounds.top, 10) <= offset;\n };\n\n /**\n * Check if at the bottom of the viewport\n * @return {Boolean} If true, page is at the bottom of the viewport\n */\n var isAtBottom = function () {\n if (\n Math.ceil(window.innerHeight + window.pageYOffset) >=\n getDocumentHeight()\n )\n return true;\n return false;\n };\n\n /**\n * Check if the last item should be used (even if not at the top of the page)\n * @param {Object} item The last item\n * @param {Object} settings The settings for this instantiation\n * @return {Boolean} If true, use the last item\n */\n var useLastItem = function (item, settings) {\n if (isAtBottom() && isInView(item.content, settings, true)) return true;\n return false;\n };\n\n /**\n * Get the active content\n * @param {Array} contents The content areas\n * @param {Object} settings The settings for this instantiation\n * @return {Object} The content area and matching navigation link\n */\n var getActive = function (contents, settings) {\n var last = contents[contents.length - 1];\n if (useLastItem(last, settings)) return last;\n for (var i = contents.length - 1; i >= 0; i--) {\n if (isInView(contents[i].content, settings)) return contents[i];\n }\n };\n\n /**\n * Deactivate parent navs in a nested navigation\n * @param {Node} nav The starting navigation element\n * @param {Object} settings The settings for this instantiation\n */\n var deactivateNested = function (nav, settings) {\n // If nesting isn't activated, bail\n if (!settings.nested || !nav.parentNode) return;\n\n // Get the parent navigation\n var li = nav.parentNode.closest(\"li\");\n if (!li) return;\n\n // Remove the active class\n li.classList.remove(settings.nestedClass);\n\n // Apply recursively to any parent navigation elements\n deactivateNested(li, settings);\n };\n\n /**\n * Deactivate a nav and content area\n * @param {Object} items The nav item and content to deactivate\n * @param {Object} settings The settings for this instantiation\n */\n var deactivate = function (items, settings) {\n // Make sure there are items to deactivate\n if (!items) return;\n\n // Get the parent list item\n var li = items.nav.closest(\"li\");\n if (!li) return;\n\n // Remove the active class from the nav and content\n li.classList.remove(settings.navClass);\n items.content.classList.remove(settings.contentClass);\n\n // Deactivate any parent navs in a nested navigation\n deactivateNested(li, settings);\n\n // Emit a custom event\n emitEvent(\"gumshoeDeactivate\", li, {\n link: items.nav,\n content: items.content,\n settings: settings,\n });\n };\n\n /**\n * Activate parent navs in a nested navigation\n * @param {Node} nav The starting navigation element\n * @param {Object} settings The settings for this instantiation\n */\n var activateNested = function (nav, settings) {\n // If nesting isn't activated, bail\n if (!settings.nested) return;\n\n // Get the parent navigation\n var li = nav.parentNode.closest(\"li\");\n if (!li) return;\n\n // Add the active class\n li.classList.add(settings.nestedClass);\n\n // Apply recursively to any parent navigation elements\n activateNested(li, settings);\n };\n\n /**\n * Activate a nav and content area\n * @param {Object} items The nav item and content to activate\n * @param {Object} settings The settings for this instantiation\n */\n var activate = function (items, settings) {\n // Make sure there are items to activate\n if (!items) return;\n\n // Get the parent list item\n var li = items.nav.closest(\"li\");\n if (!li) return;\n\n // Add the active class to the nav and content\n li.classList.add(settings.navClass);\n items.content.classList.add(settings.contentClass);\n\n // Activate any parent navs in a nested navigation\n activateNested(li, settings);\n\n // Emit a custom event\n emitEvent(\"gumshoeActivate\", li, {\n link: items.nav,\n content: items.content,\n settings: settings,\n });\n };\n\n /**\n * Create the Constructor object\n * @param {String} selector The selector to use for navigation items\n * @param {Object} options User options and settings\n */\n var Constructor = function (selector, options) {\n //\n // Variables\n //\n\n var publicAPIs = {};\n var navItems, contents, current, timeout, settings;\n\n //\n // Methods\n //\n\n /**\n * Set variables from DOM elements\n */\n publicAPIs.setup = function () {\n // Get all nav items\n navItems = document.querySelectorAll(selector);\n\n // Create contents array\n contents = [];\n\n // Loop through each item, get it's matching content, and push to the array\n Array.prototype.forEach.call(navItems, function (item) {\n // Get the content for the nav item\n var content = document.getElementById(\n decodeURIComponent(item.hash.substr(1)),\n );\n if (!content) return;\n\n // Push to the contents array\n contents.push({\n nav: item,\n content: content,\n });\n });\n\n // Sort contents by the order they appear in the DOM\n sortContents(contents);\n };\n\n /**\n * Detect which content is currently active\n */\n publicAPIs.detect = function () {\n // Get the active content\n var active = getActive(contents, settings);\n\n // if there's no active content, deactivate and bail\n if (!active) {\n if (current) {\n deactivate(current, settings);\n current = null;\n }\n return;\n }\n\n // If the active content is the one currently active, do nothing\n if (current && active.content === current.content) return;\n\n // Deactivate the current content and activate the new content\n deactivate(current, settings);\n activate(active, settings);\n\n // Update the currently active content\n current = active;\n };\n\n /**\n * Detect the active content on scroll\n * Debounced for performance\n */\n var scrollHandler = function (event) {\n // If there's a timer, cancel it\n if (timeout) {\n window.cancelAnimationFrame(timeout);\n }\n\n // Setup debounce callback\n timeout = window.requestAnimationFrame(publicAPIs.detect);\n };\n\n /**\n * Update content sorting on resize\n * Debounced for performance\n */\n var resizeHandler = function (event) {\n // If there's a timer, cancel it\n if (timeout) {\n window.cancelAnimationFrame(timeout);\n }\n\n // Setup debounce callback\n timeout = window.requestAnimationFrame(function () {\n sortContents(contents);\n publicAPIs.detect();\n });\n };\n\n /**\n * Destroy the current instantiation\n */\n publicAPIs.destroy = function () {\n // Undo DOM changes\n if (current) {\n deactivate(current, settings);\n }\n\n // Remove event listeners\n window.removeEventListener(\"scroll\", scrollHandler, false);\n if (settings.reflow) {\n window.removeEventListener(\"resize\", resizeHandler, false);\n }\n\n // Reset variables\n contents = null;\n navItems = null;\n current = null;\n timeout = null;\n settings = null;\n };\n\n /**\n * Initialize the current instantiation\n */\n var init = function () {\n // Merge user options into defaults\n settings = extend(defaults, options || {});\n\n // Setup variables based on the current DOM\n publicAPIs.setup();\n\n // Find the currently active content\n publicAPIs.detect();\n\n // Setup event listeners\n window.addEventListener(\"scroll\", scrollHandler, false);\n if (settings.reflow) {\n window.addEventListener(\"resize\", resizeHandler, false);\n }\n };\n\n //\n // Initialize and return the public APIs\n //\n\n init();\n return publicAPIs;\n };\n\n //\n // Return the Constructor\n //\n\n return Constructor;\n },\n);\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","import Gumshoe from \"./gumshoe-patched.js\";\n\n////////////////////////////////////////////////////////////////////////////////\n// Scroll Handling\n////////////////////////////////////////////////////////////////////////////////\nvar tocScroll = null;\nvar header = null;\nvar lastScrollTop = window.pageYOffset || document.documentElement.scrollTop;\nconst GO_TO_TOP_OFFSET = 64;\n\nfunction scrollHandlerForHeader() {\n if (Math.floor(header.getBoundingClientRect().top) == 0) {\n header.classList.add(\"scrolled\");\n } else {\n header.classList.remove(\"scrolled\");\n }\n}\n\nfunction scrollHandlerForBackToTop(positionY) {\n if (positionY < GO_TO_TOP_OFFSET) {\n document.documentElement.classList.remove(\"show-back-to-top\");\n } else {\n if (positionY < lastScrollTop) {\n document.documentElement.classList.add(\"show-back-to-top\");\n } else if (positionY > lastScrollTop) {\n document.documentElement.classList.remove(\"show-back-to-top\");\n }\n }\n lastScrollTop = positionY;\n}\n\nfunction scrollHandlerForTOC(positionY) {\n if (tocScroll === null) {\n return;\n }\n\n // top of page.\n if (positionY == 0) {\n tocScroll.scrollTo(0, 0);\n } else if (\n // bottom of page.\n Math.ceil(positionY) >=\n Math.floor(document.documentElement.scrollHeight - window.innerHeight)\n ) {\n tocScroll.scrollTo(0, tocScroll.scrollHeight);\n } else {\n // somewhere in the middle.\n const current = document.querySelector(\".scroll-current\");\n if (current == null) {\n return;\n }\n\n // https://github.com/pypa/pip/issues/9159 This breaks scroll behaviours.\n // // scroll the currently \"active\" heading in toc, into view.\n // const rect = current.getBoundingClientRect();\n // if (0 > rect.top) {\n // current.scrollIntoView(true); // the argument is \"alignTop\"\n // } else if (rect.bottom > window.innerHeight) {\n // current.scrollIntoView(false);\n // }\n }\n}\n\nfunction scrollHandler(positionY) {\n scrollHandlerForHeader();\n scrollHandlerForBackToTop(positionY);\n scrollHandlerForTOC(positionY);\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Theme Toggle\n////////////////////////////////////////////////////////////////////////////////\nfunction setTheme(mode) {\n if (mode !== \"light\" && mode !== \"dark\" && mode !== \"auto\") {\n console.error(`Got invalid theme mode: ${mode}. Resetting to auto.`);\n mode = \"auto\";\n }\n\n document.body.dataset.theme = mode;\n localStorage.setItem(\"theme\", mode);\n console.log(`Changed to ${mode} mode.`);\n}\n\nfunction cycleThemeOnce() {\n const currentTheme = localStorage.getItem(\"theme\") || \"auto\";\n const prefersDark = window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n\n if (prefersDark) {\n // Auto (dark) -> Light -> Dark\n if (currentTheme === \"auto\") {\n setTheme(\"light\");\n } else if (currentTheme == \"light\") {\n setTheme(\"dark\");\n } else {\n setTheme(\"auto\");\n }\n } else {\n // Auto (light) -> Dark -> Light\n if (currentTheme === \"auto\") {\n setTheme(\"dark\");\n } else if (currentTheme == \"dark\") {\n setTheme(\"light\");\n } else {\n setTheme(\"auto\");\n }\n }\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Setup\n////////////////////////////////////////////////////////////////////////////////\nfunction setupScrollHandler() {\n // Taken from https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event\n let last_known_scroll_position = 0;\n let ticking = false;\n\n window.addEventListener(\"scroll\", function (e) {\n last_known_scroll_position = window.scrollY;\n\n if (!ticking) {\n window.requestAnimationFrame(function () {\n scrollHandler(last_known_scroll_position);\n ticking = false;\n });\n\n ticking = true;\n }\n });\n window.scroll();\n}\n\nfunction setupScrollSpy() {\n if (tocScroll === null) {\n return;\n }\n\n // Scrollspy -- highlight table on contents, based on scroll\n new Gumshoe(\".toc-tree a\", {\n reflow: true,\n recursive: true,\n navClass: \"scroll-current\",\n offset: () => {\n let rem = parseFloat(getComputedStyle(document.documentElement).fontSize);\n return header.getBoundingClientRect().height + 2.5 * rem + 1;\n },\n });\n}\n\nfunction setupTheme() {\n // Attach event handlers for toggling themes\n const buttons = document.getElementsByClassName(\"theme-toggle\");\n Array.from(buttons).forEach((btn) => {\n btn.addEventListener(\"click\", cycleThemeOnce);\n });\n}\n\nfunction setup() {\n setupTheme();\n setupScrollHandler();\n setupScrollSpy();\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Main entrypoint\n////////////////////////////////////////////////////////////////////////////////\nfunction main() {\n document.body.parentNode.classList.remove(\"no-js\");\n\n header = document.querySelector(\"header\");\n tocScroll = document.querySelector(\".toc-scroll\");\n\n setup();\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", main);\n"],"names":["root","g","window","this","defaults","navClass","contentClass","nested","nestedClass","offset","reflow","events","emitEvent","type","elem","detail","settings","event","CustomEvent","bubbles","cancelable","dispatchEvent","getOffsetTop","location","offsetParent","offsetTop","sortContents","contents","sort","item1","item2","content","isInView","bottom","bounds","getBoundingClientRect","parseFloat","getOffset","parseInt","innerHeight","document","documentElement","clientHeight","top","isAtBottom","Math","ceil","pageYOffset","max","body","scrollHeight","offsetHeight","getActive","last","length","item","useLastItem","i","deactivateNested","nav","parentNode","li","closest","classList","remove","deactivate","items","link","activateNested","add","selector","options","navItems","current","timeout","publicAPIs","querySelectorAll","Array","prototype","forEach","call","getElementById","decodeURIComponent","hash","substr","push","active","activate","scrollHandler","cancelAnimationFrame","requestAnimationFrame","detect","resizeHandler","destroy","removeEventListener","merged","arguments","obj","key","hasOwnProperty","extend","setup","addEventListener","factory","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","__webpack_modules__","n","getter","__esModule","d","a","definition","o","Object","defineProperty","enumerable","get","globalThis","Function","e","prop","tocScroll","header","lastScrollTop","scrollTop","GO_TO_TOP_OFFSET","cycleThemeOnce","currentTheme","localStorage","getItem","mode","matchMedia","matches","console","error","dataset","theme","setItem","log","buttons","getElementsByClassName","from","btn","setupTheme","last_known_scroll_position","ticking","scrollY","positionY","floor","scrollHandlerForBackToTop","scrollTo","querySelector","scrollHandlerForTOC","scroll","setupScrollHandler","recursive","rem","getComputedStyle","fontSize","height"],"sourceRoot":""} \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 0000000000..97d56a74d8 --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,566 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = docUrlRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = docUrlRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms) + ); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + `Search finished, found ${resultCount} page(s) matching the search query.` + ); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent !== undefined) return docContent.textContent; + console.warn( + "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + /** + * execute search (requires search index to be loaded) + */ + query: (query) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + // array of [docname, title, anchor, descr, score, filename] + let results = []; + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + results.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id] of foundEntries) { + let score = Math.round(100 * queryLower.length / entry.length) + results.push([ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // lookup as object + objectTerms.forEach((term) => + results.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); + + // now sort the results by score (in opposite order of appearance, since the + // display function below uses pop() to retrieve items) and then + // alphabetically + results.sort((a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; + }); + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + results = results.reverse(); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord) && !terms[word]) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord) && !titleTerms[word]) + arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); + }); + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); + else fileMap.set(file, [word]); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords) => { + const text = Search.htmlToText(htmlText); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/_static/skeleton.css b/_static/skeleton.css new file mode 100644 index 0000000000..467c878c62 --- /dev/null +++ b/_static/skeleton.css @@ -0,0 +1,296 @@ +/* Some sane resets. */ +html { + height: 100%; +} + +body { + margin: 0; + min-height: 100%; +} + +/* All the flexbox magic! */ +body, +.sb-announcement, +.sb-content, +.sb-main, +.sb-container, +.sb-container__inner, +.sb-article-container, +.sb-footer-content, +.sb-header, +.sb-header-secondary, +.sb-footer { + display: flex; +} + +/* These order things vertically */ +body, +.sb-main, +.sb-article-container { + flex-direction: column; +} + +/* Put elements in the center */ +.sb-header, +.sb-header-secondary, +.sb-container, +.sb-content, +.sb-footer, +.sb-footer-content { + justify-content: center; +} +/* Put elements at the ends */ +.sb-article-container { + justify-content: space-between; +} + +/* These elements grow. */ +.sb-main, +.sb-content, +.sb-container, +article { + flex-grow: 1; +} + +/* Because padding making this wider is not fun */ +article { + box-sizing: border-box; +} + +/* The announcements element should never be wider than the page. */ +.sb-announcement { + max-width: 100%; +} + +.sb-sidebar-primary, +.sb-sidebar-secondary { + flex-shrink: 0; + width: 17rem; +} + +.sb-announcement__inner { + justify-content: center; + + box-sizing: border-box; + height: 3rem; + + overflow-x: auto; + white-space: nowrap; +} + +/* Sidebars, with checkbox-based toggle */ +.sb-sidebar-primary, +.sb-sidebar-secondary { + position: fixed; + height: 100%; + top: 0; +} + +.sb-sidebar-primary { + left: -17rem; + transition: left 250ms ease-in-out; +} +.sb-sidebar-secondary { + right: -17rem; + transition: right 250ms ease-in-out; +} + +.sb-sidebar-toggle { + display: none; +} +.sb-sidebar-overlay { + position: fixed; + top: 0; + width: 0; + height: 0; + + transition: width 0ms ease 250ms, height 0ms ease 250ms, opacity 250ms ease; + + opacity: 0; + background-color: rgba(0, 0, 0, 0.54); +} + +#sb-sidebar-toggle--primary:checked + ~ .sb-sidebar-overlay[for="sb-sidebar-toggle--primary"], +#sb-sidebar-toggle--secondary:checked + ~ .sb-sidebar-overlay[for="sb-sidebar-toggle--secondary"] { + width: 100%; + height: 100%; + opacity: 1; + transition: width 0ms ease, height 0ms ease, opacity 250ms ease; +} + +#sb-sidebar-toggle--primary:checked ~ .sb-container .sb-sidebar-primary { + left: 0; +} +#sb-sidebar-toggle--secondary:checked ~ .sb-container .sb-sidebar-secondary { + right: 0; +} + +/* Full-width mode */ +.drop-secondary-sidebar-for-full-width-content + .hide-when-secondary-sidebar-shown { + display: none !important; +} +.drop-secondary-sidebar-for-full-width-content .sb-sidebar-secondary { + display: none !important; +} + +/* Mobile views */ +.sb-page-width { + width: 100%; +} + +.sb-article-container, +.sb-footer-content__inner, +.drop-secondary-sidebar-for-full-width-content .sb-article, +.drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 100vw; +} + +.sb-article, +.match-content-width { + padding: 0 1rem; + box-sizing: border-box; +} + +@media (min-width: 32rem) { + .sb-article, + .match-content-width { + padding: 0 2rem; + } +} + +/* Tablet views */ +@media (min-width: 42rem) { + .sb-article-container { + width: auto; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 42rem; + } + .sb-article, + .match-content-width { + width: 42rem; + } +} +@media (min-width: 46rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 46rem; + } + .sb-article, + .match-content-width { + width: 46rem; + } +} +@media (min-width: 50rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 50rem; + } + .sb-article, + .match-content-width { + width: 50rem; + } +} + +/* Tablet views */ +@media (min-width: 59rem) { + .sb-sidebar-secondary { + position: static; + } + .hide-when-secondary-sidebar-shown { + display: none !important; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 59rem; + } + .sb-article, + .match-content-width { + width: 42rem; + } +} +@media (min-width: 63rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 63rem; + } + .sb-article, + .match-content-width { + width: 46rem; + } +} +@media (min-width: 67rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 67rem; + } + .sb-article, + .match-content-width { + width: 50rem; + } +} + +/* Desktop views */ +@media (min-width: 76rem) { + .sb-sidebar-primary { + position: static; + } + .hide-when-primary-sidebar-shown { + display: none !important; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 59rem; + } + .sb-article, + .match-content-width { + width: 42rem; + } +} + +/* Full desktop views */ +@media (min-width: 80rem) { + .sb-article, + .match-content-width { + width: 46rem; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 63rem; + } +} + +@media (min-width: 84rem) { + .sb-article, + .match-content-width { + width: 50rem; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 67rem; + } +} + +@media (min-width: 88rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 67rem; + } + .sb-page-width { + width: 88rem; + } +} diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js new file mode 100644 index 0000000000..aae669d7ea --- /dev/null +++ b/_static/sphinx_highlight.js @@ -0,0 +1,144 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + parent.insertBefore( + span, + parent.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(SphinxHighlight.highlightSearchWords); +_ready(SphinxHighlight.initEscapeListener); diff --git a/_static/styles/furo-extensions.css b/_static/styles/furo-extensions.css new file mode 100644 index 0000000000..bc447f228f --- /dev/null +++ b/_static/styles/furo-extensions.css @@ -0,0 +1,2 @@ +#furo-sidebar-ad-placement{padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)}#furo-sidebar-ad-placement .ethical-sidebar{background:var(--color-background-secondary);border:none;box-shadow:none}#furo-sidebar-ad-placement .ethical-sidebar:hover{background:var(--color-background-hover)}#furo-sidebar-ad-placement .ethical-sidebar a{color:var(--color-foreground-primary)}#furo-sidebar-ad-placement .ethical-callout a{color:var(--color-foreground-secondary)!important}#furo-readthedocs-versions{background:transparent;display:block;position:static;width:100%}#furo-readthedocs-versions .rst-versions{background:#1a1c1e}#furo-readthedocs-versions .rst-current-version{background:var(--color-sidebar-item-background);cursor:unset}#furo-readthedocs-versions .rst-current-version:hover{background:var(--color-sidebar-item-background)}#furo-readthedocs-versions .rst-current-version .fa-book{color:var(--color-foreground-primary)}#furo-readthedocs-versions>.rst-other-versions{padding:0}#furo-readthedocs-versions>.rst-other-versions small{opacity:1}#furo-readthedocs-versions .injected .rst-versions{position:unset}#furo-readthedocs-versions:focus-within,#furo-readthedocs-versions:hover{box-shadow:0 0 0 1px var(--color-sidebar-background-border)}#furo-readthedocs-versions:focus-within .rst-current-version,#furo-readthedocs-versions:hover .rst-current-version{background:#1a1c1e;font-size:inherit;height:auto;line-height:inherit;padding:12px;text-align:right}#furo-readthedocs-versions:focus-within .rst-current-version .fa-book,#furo-readthedocs-versions:hover .rst-current-version .fa-book{color:#fff;float:left}#furo-readthedocs-versions:focus-within .fa-caret-down,#furo-readthedocs-versions:hover .fa-caret-down{display:none}#furo-readthedocs-versions:focus-within .injected,#furo-readthedocs-versions:focus-within .rst-current-version,#furo-readthedocs-versions:focus-within .rst-other-versions,#furo-readthedocs-versions:hover .injected,#furo-readthedocs-versions:hover .rst-current-version,#furo-readthedocs-versions:hover .rst-other-versions{display:block}#furo-readthedocs-versions:focus-within>.rst-current-version,#furo-readthedocs-versions:hover>.rst-current-version{display:none}.highlight:hover button.copybtn{color:var(--color-code-foreground)}.highlight button.copybtn{align-items:center;background-color:var(--color-code-background);border:none;color:var(--color-background-item);cursor:pointer;height:1.25em;opacity:1;right:.5rem;top:.625rem;transition:color .3s,opacity .3s;width:1.25em}.highlight button.copybtn:hover{background-color:var(--color-code-background);color:var(--color-brand-content)}.highlight button.copybtn:after{background-color:transparent;color:var(--color-code-foreground);display:none}.highlight button.copybtn.success{color:#22863a;transition:color 0ms}.highlight button.copybtn.success:after{display:block}.highlight button.copybtn svg{padding:0}body{--sd-color-primary:var(--color-brand-primary);--sd-color-primary-highlight:var(--color-brand-content);--sd-color-primary-text:var(--color-background-primary);--sd-color-shadow:rgba(0,0,0,.05);--sd-color-card-border:var(--color-card-border);--sd-color-card-border-hover:var(--color-brand-content);--sd-color-card-background:var(--color-card-background);--sd-color-card-text:var(--color-foreground-primary);--sd-color-card-header:var(--color-card-marginals-background);--sd-color-card-footer:var(--color-card-marginals-background);--sd-color-tabs-label-active:var(--color-brand-content);--sd-color-tabs-label-hover:var(--color-foreground-muted);--sd-color-tabs-label-inactive:var(--color-foreground-muted);--sd-color-tabs-underline-active:var(--color-brand-content);--sd-color-tabs-underline-hover:var(--color-foreground-border);--sd-color-tabs-underline-inactive:var(--color-background-border);--sd-color-tabs-overline:var(--color-background-border);--sd-color-tabs-underline:var(--color-background-border)}.sd-tab-content{box-shadow:0 -2px var(--sd-color-tabs-overline),0 1px var(--sd-color-tabs-underline)}.sd-card{box-shadow:0 .1rem .25rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)}.sd-shadow-sm{box-shadow:0 .1rem .25rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-shadow-md{box-shadow:0 .3rem .75rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-shadow-lg{box-shadow:0 .6rem 1.5rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-card-hover:hover{transform:none}.sd-cards-carousel{gap:.25rem;padding:.25rem}body{--tabs--label-text:var(--color-foreground-muted);--tabs--label-text--hover:var(--color-foreground-muted);--tabs--label-text--active:var(--color-brand-content);--tabs--label-text--active--hover:var(--color-brand-content);--tabs--label-background:transparent;--tabs--label-background--hover:transparent;--tabs--label-background--active:transparent;--tabs--label-background--active--hover:transparent;--tabs--padding-x:0.25em;--tabs--margin-x:1em;--tabs--border:var(--color-background-border);--tabs--label-border:transparent;--tabs--label-border--hover:var(--color-foreground-muted);--tabs--label-border--active:var(--color-brand-content);--tabs--label-border--active--hover:var(--color-brand-content)}[role=main] .container{max-width:none;padding-left:0;padding-right:0}.shadow.docutils{border:none;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1)!important}.sphinx-bs .card{background-color:var(--color-background-secondary);color:var(--color-foreground)} +/*# sourceMappingURL=furo-extensions.css.map*/ \ No newline at end of file diff --git a/_static/styles/furo-extensions.css.map b/_static/styles/furo-extensions.css.map new file mode 100644 index 0000000000..9ba5637f9a --- /dev/null +++ b/_static/styles/furo-extensions.css.map @@ -0,0 +1 @@ +{"version":3,"file":"styles/furo-extensions.css","mappings":"AAGA,2BACE,oFACA,4CAKE,6CAHA,YACA,eAEA,CACA,kDACE,yCAEF,8CACE,sCAEJ,8CACE,kDAEJ,2BAGE,uBACA,cAHA,gBACA,UAEA,CAGA,yCACE,mBAEF,gDAEE,gDADA,YACA,CACA,sDACE,gDACF,yDACE,sCAEJ,+CACE,UACA,qDACE,UAGF,mDACE,eAEJ,yEAEE,4DAEA,mHASE,mBAPA,kBAEA,YADA,oBAGA,aADA,gBAIA,CAEA,qIAEE,WADA,UACA,CAEJ,uGACE,aAEF,iUAGE,cAEF,mHACE,aC1EJ,gCACE,mCAEF,0BAKE,mBAUA,8CACA,YAFA,mCAKA,eAZA,cALA,UASA,YADA,YAYA,iCAdA,YAcA,CAEA,gCAEE,8CADA,gCACA,CAEF,gCAGE,6BADA,mCADA,YAEA,CAEF,kCAEE,cADA,oBACA,CACA,wCACE,cAEJ,8BACE,UC5CN,KAEE,6CAA8C,CAC9C,uDAAwD,CACxD,uDAAwD,CAGxD,iCAAsC,CAGtC,+CAAgD,CAChD,uDAAwD,CACxD,uDAAwD,CACxD,oDAAqD,CACrD,6DAA8D,CAC9D,6DAA8D,CAG9D,uDAAwD,CACxD,yDAA0D,CAC1D,4DAA6D,CAC7D,2DAA4D,CAC5D,8DAA+D,CAC/D,iEAAkE,CAClE,uDAAwD,CACxD,wDAAyD,CAG3D,gBACE,qFAGF,SACE,6EAEF,cACE,uFAEF,cACE,uFAEF,cACE,uFAGF,qBACE,eAEF,mBACE,WACA,eChDF,KACE,gDAAiD,CACjD,uDAAwD,CACxD,qDAAsD,CACtD,4DAA6D,CAC7D,oCAAqC,CACrC,2CAA4C,CAC5C,4CAA6C,CAC7C,mDAAoD,CACpD,wBAAyB,CACzB,oBAAqB,CACrB,6CAA8C,CAC9C,gCAAiC,CACjC,yDAA0D,CAC1D,uDAAwD,CACxD,8DAA+D,CCbjE,uBACE,eACA,eACA,gBAGF,iBACE,YACA,+EAGF,iBACE,mDACA","sources":["webpack:///./src/furo/assets/styles/extensions/_readthedocs.sass","webpack:///./src/furo/assets/styles/extensions/_copybutton.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-design.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-inline-tabs.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-panels.sass"],"sourcesContent":["// This file contains the styles used for tweaking how ReadTheDoc's embedded\n// contents would show up inside the theme.\n\n#furo-sidebar-ad-placement\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n .ethical-sidebar\n // Remove the border and box-shadow.\n border: none\n box-shadow: none\n // Manage the background colors.\n background: var(--color-background-secondary)\n &:hover\n background: var(--color-background-hover)\n // Ensure the text is legible.\n a\n color: var(--color-foreground-primary)\n\n .ethical-callout a\n color: var(--color-foreground-secondary) !important\n\n#furo-readthedocs-versions\n position: static\n width: 100%\n background: transparent\n display: block\n\n // Make the background color fit with the theme's aesthetic.\n .rst-versions\n background: rgb(26, 28, 30)\n\n .rst-current-version\n cursor: unset\n background: var(--color-sidebar-item-background)\n &:hover\n background: var(--color-sidebar-item-background)\n .fa-book\n color: var(--color-foreground-primary)\n\n > .rst-other-versions\n padding: 0\n small\n opacity: 1\n\n .injected\n .rst-versions\n position: unset\n\n &:hover,\n &:focus-within\n box-shadow: 0 0 0 1px var(--color-sidebar-background-border)\n\n .rst-current-version\n // Undo the tweaks done in RTD's CSS\n font-size: inherit\n line-height: inherit\n height: auto\n text-align: right\n padding: 12px\n\n // Match the rest of the body\n background: #1a1c1e\n\n .fa-book\n float: left\n color: white\n\n .fa-caret-down\n display: none\n\n .rst-current-version,\n .rst-other-versions,\n .injected\n display: block\n\n > .rst-current-version\n display: none\n",".highlight\n &:hover button.copybtn\n color: var(--color-code-foreground)\n\n button.copybtn\n // Make it visible\n opacity: 1\n\n // Align things correctly\n align-items: center\n\n height: 1.25em\n width: 1.25em\n\n top: 0.625rem // $code-spacing-vertical\n right: 0.5rem\n\n // Make it look better\n color: var(--color-background-item)\n background-color: var(--color-code-background)\n border: none\n\n // Change to cursor to make it obvious that you can click on it\n cursor: pointer\n\n // Transition smoothly, for aesthetics\n transition: color 300ms, opacity 300ms\n\n &:hover\n color: var(--color-brand-content)\n background-color: var(--color-code-background)\n\n &::after\n display: none\n color: var(--color-code-foreground)\n background-color: transparent\n\n &.success\n transition: color 0ms\n color: #22863a\n &::after\n display: block\n\n svg\n padding: 0\n","body\n // Colors\n --sd-color-primary: var(--color-brand-primary)\n --sd-color-primary-highlight: var(--color-brand-content)\n --sd-color-primary-text: var(--color-background-primary)\n\n // Shadows\n --sd-color-shadow: rgba(0, 0, 0, 0.05)\n\n // Cards\n --sd-color-card-border: var(--color-card-border)\n --sd-color-card-border-hover: var(--color-brand-content)\n --sd-color-card-background: var(--color-card-background)\n --sd-color-card-text: var(--color-foreground-primary)\n --sd-color-card-header: var(--color-card-marginals-background)\n --sd-color-card-footer: var(--color-card-marginals-background)\n\n // Tabs\n --sd-color-tabs-label-active: var(--color-brand-content)\n --sd-color-tabs-label-hover: var(--color-foreground-muted)\n --sd-color-tabs-label-inactive: var(--color-foreground-muted)\n --sd-color-tabs-underline-active: var(--color-brand-content)\n --sd-color-tabs-underline-hover: var(--color-foreground-border)\n --sd-color-tabs-underline-inactive: var(--color-background-border)\n --sd-color-tabs-overline: var(--color-background-border)\n --sd-color-tabs-underline: var(--color-background-border)\n\n// Tabs\n.sd-tab-content\n box-shadow: 0 -2px var(--sd-color-tabs-overline), 0 1px var(--sd-color-tabs-underline)\n\n// Shadows\n.sd-card // Have a shadow by default\n box-shadow: 0 0.1rem 0.25rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1)\n\n.sd-shadow-sm\n box-shadow: 0 0.1rem 0.25rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n.sd-shadow-md\n box-shadow: 0 0.3rem 0.75rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n.sd-shadow-lg\n box-shadow: 0 0.6rem 1.5rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n// Cards\n.sd-card-hover:hover // Don't change scale on hover\n transform: none\n\n.sd-cards-carousel // Have a bit of gap in the carousel by default\n gap: 0.25rem\n padding: 0.25rem\n","// This file contains styles to tweak sphinx-inline-tabs to work well with Furo.\n\nbody\n --tabs--label-text: var(--color-foreground-muted)\n --tabs--label-text--hover: var(--color-foreground-muted)\n --tabs--label-text--active: var(--color-brand-content)\n --tabs--label-text--active--hover: var(--color-brand-content)\n --tabs--label-background: transparent\n --tabs--label-background--hover: transparent\n --tabs--label-background--active: transparent\n --tabs--label-background--active--hover: transparent\n --tabs--padding-x: 0.25em\n --tabs--margin-x: 1em\n --tabs--border: var(--color-background-border)\n --tabs--label-border: transparent\n --tabs--label-border--hover: var(--color-foreground-muted)\n --tabs--label-border--active: var(--color-brand-content)\n --tabs--label-border--active--hover: var(--color-brand-content)\n","// This file contains styles to tweak sphinx-panels to work well with Furo.\n\n// sphinx-panels includes Bootstrap 4, which uses .container which can conflict\n// with docutils' `.. container::` directive.\n[role=\"main\"] .container\n max-width: initial\n padding-left: initial\n padding-right: initial\n\n// Make the panels look nicer!\n.shadow.docutils\n border: none\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n// Make panel colors respond to dark mode\n.sphinx-bs .card\n background-color: var(--color-background-secondary)\n color: var(--color-foreground)\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/_static/styles/furo.css b/_static/styles/furo.css new file mode 100644 index 0000000000..e3d4e57b86 --- /dev/null +++ b/_static/styles/furo.css @@ -0,0 +1,2 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}@media print{.content-icon-container,.headerlink,.mobile-header,.related-pages{display:none!important}.highlight{border:.1pt solid var(--color-foreground-border)}a,blockquote,dl,ol,pre,table,ul{page-break-inside:avoid}caption,figure,h1,h2,h3,h4,h5,h6,img{page-break-after:avoid;page-break-inside:avoid}dl,ol,ul{page-break-before:avoid}}.visually-hidden{height:1px!important;margin:-1px!important;overflow:hidden!important;padding:0!important;position:absolute!important;width:1px!important;clip:rect(0,0,0,0)!important;background:var(--color-background-primary);border:0!important;color:var(--color-foreground-primary);white-space:nowrap!important}:-moz-focusring{outline:auto}body{--font-stack:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;--font-stack--monospace:"SFMono-Regular",Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace;--font-stack--headings:var(--font-stack);--font-size--normal:100%;--font-size--small:87.5%;--font-size--small--2:81.25%;--font-size--small--3:75%;--font-size--small--4:62.5%;--sidebar-caption-font-size:var(--font-size--small--2);--sidebar-item-font-size:var(--font-size--small);--sidebar-search-input-font-size:var(--font-size--small);--toc-font-size:var(--font-size--small--3);--toc-font-size--mobile:var(--font-size--normal);--toc-title-font-size:var(--font-size--small--4);--admonition-font-size:0.8125rem;--admonition-title-font-size:0.8125rem;--code-font-size:var(--font-size--small--2);--api-font-size:var(--font-size--small);--header-height:calc(var(--sidebar-item-line-height) + var(--sidebar-item-spacing-vertical)*4);--header-padding:0.5rem;--sidebar-tree-space-above:1.5rem;--sidebar-caption-space-above:1rem;--sidebar-item-line-height:1rem;--sidebar-item-spacing-vertical:0.5rem;--sidebar-item-spacing-horizontal:1rem;--sidebar-item-height:calc(var(--sidebar-item-line-height) + var(--sidebar-item-spacing-vertical)*2);--sidebar-expander-width:var(--sidebar-item-height);--sidebar-search-space-above:0.5rem;--sidebar-search-input-spacing-vertical:0.5rem;--sidebar-search-input-spacing-horizontal:0.5rem;--sidebar-search-input-height:1rem;--sidebar-search-icon-size:var(--sidebar-search-input-height);--toc-title-padding:0.25rem 0;--toc-spacing-vertical:1.5rem;--toc-spacing-horizontal:1.5rem;--toc-item-spacing-vertical:0.4rem;--toc-item-spacing-horizontal:1rem;--icon-search:url('data:image/svg+xml;charset=utf-8,');--icon-pencil:url('data:image/svg+xml;charset=utf-8,');--icon-abstract:url('data:image/svg+xml;charset=utf-8,');--icon-info:url('data:image/svg+xml;charset=utf-8,');--icon-flame:url('data:image/svg+xml;charset=utf-8,');--icon-question:url('data:image/svg+xml;charset=utf-8,');--icon-warning:url('data:image/svg+xml;charset=utf-8,');--icon-failure:url('data:image/svg+xml;charset=utf-8,');--icon-spark:url('data:image/svg+xml;charset=utf-8,');--color-admonition-title--caution:#ff9100;--color-admonition-title-background--caution:rgba(255,145,0,.2);--color-admonition-title--warning:#ff9100;--color-admonition-title-background--warning:rgba(255,145,0,.2);--color-admonition-title--danger:#ff5252;--color-admonition-title-background--danger:rgba(255,82,82,.2);--color-admonition-title--attention:#ff5252;--color-admonition-title-background--attention:rgba(255,82,82,.2);--color-admonition-title--error:#ff5252;--color-admonition-title-background--error:rgba(255,82,82,.2);--color-admonition-title--hint:#00c852;--color-admonition-title-background--hint:rgba(0,200,82,.2);--color-admonition-title--tip:#00c852;--color-admonition-title-background--tip:rgba(0,200,82,.2);--color-admonition-title--important:#00bfa5;--color-admonition-title-background--important:rgba(0,191,165,.2);--color-admonition-title--note:#00b0ff;--color-admonition-title-background--note:rgba(0,176,255,.2);--color-admonition-title--seealso:#448aff;--color-admonition-title-background--seealso:rgba(68,138,255,.2);--color-admonition-title--admonition-todo:grey;--color-admonition-title-background--admonition-todo:hsla(0,0%,50%,.2);--color-admonition-title:#651fff;--color-admonition-title-background:rgba(101,31,255,.2);--icon-admonition-default:var(--icon-abstract);--color-topic-title:#14b8a6;--color-topic-title-background:rgba(20,184,166,.2);--icon-topic-default:var(--icon-pencil);--color-problematic:#b30000;--color-foreground-primary:#000;--color-foreground-secondary:#5a5c63;--color-foreground-muted:#6b6f76;--color-foreground-border:#878787;--color-background-primary:#fff;--color-background-secondary:#f8f9fb;--color-background-hover:#efeff4;--color-background-hover--transparent:#efeff400;--color-background-border:#eeebee;--color-background-item:#ccc;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#0a4bff;--color-brand-content:#2757dd;--color-brand-visited:#872ee0;--color-api-background:var(--color-background-hover--transparent);--color-api-background-hover:var(--color-background-hover);--color-api-overall:var(--color-foreground-secondary);--color-api-name:var(--color-problematic);--color-api-pre-name:var(--color-problematic);--color-api-paren:var(--color-foreground-secondary);--color-api-keyword:var(--color-foreground-primary);--color-api-added:#21632c;--color-api-added-border:#38a84d;--color-api-changed:#046172;--color-api-changed-border:#06a1bc;--color-api-deprecated:#605706;--color-api-deprecated-border:#f0d90f;--color-api-removed:#b30000;--color-api-removed-border:#ff5c5c;--color-highlight-on-target:#ffc;--color-inline-code-background:var(--color-background-secondary);--color-highlighted-background:#def;--color-highlighted-text:var(--color-foreground-primary);--color-guilabel-background:#ddeeff80;--color-guilabel-border:#bedaf580;--color-guilabel-text:var(--color-foreground-primary);--color-admonition-background:transparent;--color-table-header-background:var(--color-background-secondary);--color-table-border:var(--color-background-border);--color-card-border:var(--color-background-secondary);--color-card-background:transparent;--color-card-marginals-background:var(--color-background-secondary);--color-header-background:var(--color-background-primary);--color-header-border:var(--color-background-border);--color-header-text:var(--color-foreground-primary);--color-sidebar-background:var(--color-background-secondary);--color-sidebar-background-border:var(--color-background-border);--color-sidebar-brand-text:var(--color-foreground-primary);--color-sidebar-caption-text:var(--color-foreground-muted);--color-sidebar-link-text:var(--color-foreground-secondary);--color-sidebar-link-text--top-level:var(--color-brand-primary);--color-sidebar-item-background:var(--color-sidebar-background);--color-sidebar-item-background--current:var( --color-sidebar-item-background );--color-sidebar-item-background--hover:linear-gradient(90deg,var(--color-background-hover--transparent) 0%,var(--color-background-hover) var(--sidebar-item-spacing-horizontal),var(--color-background-hover) 100%);--color-sidebar-item-expander-background:transparent;--color-sidebar-item-expander-background--hover:var( --color-background-hover );--color-sidebar-search-text:var(--color-foreground-primary);--color-sidebar-search-background:var(--color-background-secondary);--color-sidebar-search-background--focus:var(--color-background-primary);--color-sidebar-search-border:var(--color-background-border);--color-sidebar-search-icon:var(--color-foreground-muted);--color-toc-background:var(--color-background-primary);--color-toc-title-text:var(--color-foreground-muted);--color-toc-item-text:var(--color-foreground-secondary);--color-toc-item-text--hover:var(--color-foreground-primary);--color-toc-item-text--active:var(--color-brand-primary);--color-content-foreground:var(--color-foreground-primary);--color-content-background:transparent;--color-link:var(--color-brand-content);--color-link-underline:var(--color-background-border);--color-link--hover:var(--color-brand-content);--color-link-underline--hover:var(--color-foreground-border);--color-link--visited:var(--color-brand-visited);--color-link-underline--visited:var(--color-background-border);--color-link--visited--hover:var(--color-brand-visited);--color-link-underline--visited--hover:var(--color-foreground-border)}.only-light{display:block!important}html body .only-dark{display:none!important}@media not print{body[data-theme=dark]{--color-problematic:#ee5151;--color-foreground-primary:#cfd0d0;--color-foreground-secondary:#9ca0a5;--color-foreground-muted:#81868d;--color-foreground-border:#666;--color-background-primary:#131416;--color-background-secondary:#1a1c1e;--color-background-hover:#1e2124;--color-background-hover--transparent:#1e212400;--color-background-border:#303335;--color-background-item:#444;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#3d94ff;--color-brand-content:#5ca5ff;--color-brand-visited:#b27aeb;--color-highlighted-background:#083563;--color-guilabel-background:#08356380;--color-guilabel-border:#13395f80;--color-api-keyword:var(--color-foreground-secondary);--color-highlight-on-target:#330;--color-api-added:#3db854;--color-api-added-border:#267334;--color-api-changed:#09b0ce;--color-api-changed-border:#056d80;--color-api-deprecated:#b1a10b;--color-api-deprecated-border:#6e6407;--color-api-removed:#ff7575;--color-api-removed-border:#b03b3b;--color-admonition-background:#18181a;--color-card-border:var(--color-background-secondary);--color-card-background:#18181a;--color-card-marginals-background:var(--color-background-hover)}html body[data-theme=dark] .only-light{display:none!important}body[data-theme=dark] .only-dark{display:block!important}@media(prefers-color-scheme:dark){body:not([data-theme=light]){--color-problematic:#ee5151;--color-foreground-primary:#cfd0d0;--color-foreground-secondary:#9ca0a5;--color-foreground-muted:#81868d;--color-foreground-border:#666;--color-background-primary:#131416;--color-background-secondary:#1a1c1e;--color-background-hover:#1e2124;--color-background-hover--transparent:#1e212400;--color-background-border:#303335;--color-background-item:#444;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#3d94ff;--color-brand-content:#5ca5ff;--color-brand-visited:#b27aeb;--color-highlighted-background:#083563;--color-guilabel-background:#08356380;--color-guilabel-border:#13395f80;--color-api-keyword:var(--color-foreground-secondary);--color-highlight-on-target:#330;--color-api-added:#3db854;--color-api-added-border:#267334;--color-api-changed:#09b0ce;--color-api-changed-border:#056d80;--color-api-deprecated:#b1a10b;--color-api-deprecated-border:#6e6407;--color-api-removed:#ff7575;--color-api-removed-border:#b03b3b;--color-admonition-background:#18181a;--color-card-border:var(--color-background-secondary);--color-card-background:#18181a;--color-card-marginals-background:var(--color-background-hover)}html body:not([data-theme=light]) .only-light{display:none!important}body:not([data-theme=light]) .only-dark{display:block!important}}}body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-light{display:block}@media(prefers-color-scheme:dark){body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-dark{display:block}body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-light{display:none}}body[data-theme=dark] .theme-toggle svg.theme-icon-when-dark,body[data-theme=light] .theme-toggle svg.theme-icon-when-light{display:block}body{font-family:var(--font-stack)}code,kbd,pre,samp{font-family:var(--font-stack--monospace)}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}article{line-height:1.5}h1,h2,h3,h4,h5,h6{border-radius:.5rem;font-family:var(--font-stack--headings);font-weight:700;line-height:1.25;margin:.5rem -.5rem;padding-left:.5rem;padding-right:.5rem}h1+p,h2+p,h3+p,h4+p,h5+p,h6+p{margin-top:0}h1{font-size:2.5em;margin-bottom:1rem}h1,h2{margin-top:1.75rem}h2{font-size:2em}h3{font-size:1.5em}h4{font-size:1.25em}h5{font-size:1.125em}h6{font-size:1em}small{font-size:80%;opacity:75%}p{margin-bottom:.75rem;margin-top:.5rem}hr.docutils{background-color:var(--color-background-border);border:0;height:1px;margin:2rem 0;padding:0}.centered{text-align:center}a{color:var(--color-link);text-decoration:underline;text-decoration-color:var(--color-link-underline)}a:visited{color:var(--color-link--visited);text-decoration-color:var(--color-link-underline--visited)}a:visited:hover{color:var(--color-link--visited--hover);text-decoration-color:var(--color-link-underline--visited--hover)}a:hover{color:var(--color-link--hover);text-decoration-color:var(--color-link-underline--hover)}a.muted-link{color:inherit}a.muted-link:hover{color:var(--color-link--hover);text-decoration-color:var(--color-link-underline--hover)}a.muted-link:hover:visited{color:var(--color-link--visited--hover);text-decoration-color:var(--color-link-underline--visited--hover)}html{overflow-x:hidden;overflow-y:scroll;scroll-behavior:smooth}.sidebar-scroll,.toc-scroll,article[role=main] *{scrollbar-color:var(--color-foreground-border) transparent;scrollbar-width:thin}.sidebar-scroll::-webkit-scrollbar,.toc-scroll::-webkit-scrollbar,article[role=main] ::-webkit-scrollbar{height:.25rem;width:.25rem}.sidebar-scroll::-webkit-scrollbar-thumb,.toc-scroll::-webkit-scrollbar-thumb,article[role=main] ::-webkit-scrollbar-thumb{background-color:var(--color-foreground-border);border-radius:.125rem}body,html{height:100%}.skip-to-content,body,html{background:var(--color-background-primary);color:var(--color-foreground-primary)}.skip-to-content{border-radius:1rem;left:.25rem;padding:1rem;position:fixed;top:.25rem;transform:translateY(-200%);transition:transform .3s ease-in-out;z-index:40}.skip-to-content:focus-within{transform:translateY(0)}article{background:var(--color-content-background);color:var(--color-content-foreground);overflow-wrap:break-word}.page{display:flex;min-height:100%}.mobile-header{background-color:var(--color-header-background);border-bottom:1px solid var(--color-header-border);color:var(--color-header-text);display:none;height:var(--header-height);width:100%;z-index:10}.mobile-header.scrolled{border-bottom:none;box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2)}.mobile-header .header-center a{color:var(--color-header-text);text-decoration:none}.main{display:flex;flex:1}.sidebar-drawer{background:var(--color-sidebar-background);border-right:1px solid var(--color-sidebar-background-border);box-sizing:border-box;display:flex;justify-content:flex-end;min-width:15em;width:calc(50% - 26em)}.sidebar-container,.toc-drawer{box-sizing:border-box;width:15em}.toc-drawer{background:var(--color-toc-background);padding-right:1rem}.sidebar-sticky,.toc-sticky{display:flex;flex-direction:column;height:min(100%,100vh);height:100vh;position:sticky;top:0}.sidebar-scroll,.toc-scroll{flex-grow:1;flex-shrink:1;overflow:auto;scroll-behavior:smooth}.content{display:flex;flex-direction:column;justify-content:space-between;padding:0 3em;width:46em}.icon{display:inline-block;height:1rem;width:1rem}.icon svg{height:100%;width:100%}.announcement{align-items:center;background-color:var(--color-announcement-background);color:var(--color-announcement-text);display:flex;height:var(--header-height);overflow-x:auto}.announcement+.page{min-height:calc(100% - var(--header-height))}.announcement-content{box-sizing:border-box;min-width:100%;padding:.5rem;text-align:center;white-space:nowrap}.announcement-content a{color:var(--color-announcement-text);text-decoration-color:var(--color-announcement-text)}.announcement-content a:hover{color:var(--color-announcement-text);text-decoration-color:var(--color-link--hover)}.no-js .theme-toggle-container{display:none}.theme-toggle-container{vertical-align:middle}.theme-toggle{background:transparent;border:none;cursor:pointer;padding:0}.theme-toggle svg{color:var(--color-foreground-primary);display:none;height:1.25rem;vertical-align:middle;width:1.25rem}.theme-toggle-header{float:left;padding:1rem .5rem}.nav-overlay-icon,.toc-overlay-icon{cursor:pointer;display:none}.nav-overlay-icon .icon,.toc-overlay-icon .icon{color:var(--color-foreground-secondary);height:1.25rem;width:1.25rem}.nav-overlay-icon,.toc-header-icon{align-items:center;justify-content:center}.toc-content-icon{height:1.5rem;width:1.5rem}.content-icon-container{display:flex;float:right;gap:.5rem;margin-bottom:1rem;margin-left:1rem;margin-top:1.5rem}.content-icon-container .edit-this-page svg,.content-icon-container .view-this-page svg{color:inherit;height:1.25rem;width:1.25rem}.sidebar-toggle{display:none;position:absolute}.sidebar-toggle[name=__toc]{left:20px}.sidebar-toggle:checked{left:40px}.overlay{background-color:rgba(0,0,0,.54);height:0;opacity:0;position:fixed;top:0;transition:width 0ms,height 0ms,opacity .25s ease-out;width:0}.sidebar-overlay{z-index:20}.toc-overlay{z-index:40}.sidebar-drawer{transition:left .25s ease-in-out;z-index:30}.toc-drawer{transition:right .25s ease-in-out;z-index:50}#__navigation:checked~.sidebar-overlay{height:100%;opacity:1;width:100%}#__navigation:checked~.page .sidebar-drawer{left:0;top:0}#__toc:checked~.toc-overlay{height:100%;opacity:1;width:100%}#__toc:checked~.page .toc-drawer{right:0;top:0}.back-to-top{background:var(--color-background-primary);border-radius:1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 1px 0 hsla(220,9%,46%,.502);display:none;font-size:.8125rem;left:0;margin-left:50%;padding:.5rem .75rem .5rem .5rem;position:fixed;text-decoration:none;top:1rem;transform:translateX(-50%);z-index:10}.back-to-top svg{height:1rem;width:1rem;fill:currentColor;display:inline-block}.back-to-top span{margin-left:.25rem}.show-back-to-top .back-to-top{align-items:center;display:flex}@media(min-width:97em){html{font-size:110%}}@media(max-width:82em){.toc-content-icon{display:flex}.toc-drawer{border-left:1px solid var(--color-background-muted);height:100vh;position:fixed;right:-15em;top:0}.toc-tree{border-left:none;font-size:var(--toc-font-size--mobile)}.sidebar-drawer{width:calc(50% - 18.5em)}}@media(max-width:67em){.nav-overlay-icon{display:flex}.sidebar-drawer{height:100vh;left:-15em;position:fixed;top:0;width:15em}.toc-header-icon{display:flex}.theme-toggle-content,.toc-content-icon{display:none}.theme-toggle-header{display:block}.mobile-header{align-items:center;display:flex;justify-content:space-between;position:sticky;top:0}.mobile-header .header-left,.mobile-header .header-right{display:flex;height:var(--header-height);padding:0 var(--header-padding)}.mobile-header .header-left label,.mobile-header .header-right label{height:100%;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.nav-overlay-icon .icon,.theme-toggle svg{height:1.25rem;width:1.25rem}:target{scroll-margin-top:calc(var(--header-height) + 2.5rem)}.back-to-top{top:calc(var(--header-height) + .5rem)}.page{flex-direction:column;justify-content:center}.content{margin-left:auto;margin-right:auto}}@media(max-width:52em){.content{overflow-x:auto;width:100%}}@media(max-width:46em){.content{padding:0 1em}article aside.sidebar{float:none;margin:1rem 0;width:100%}}.admonition,.topic{background:var(--color-admonition-background);border-radius:.2rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1);font-size:var(--admonition-font-size);margin:1rem auto;overflow:hidden;padding:0 .5rem .5rem;page-break-inside:avoid}.admonition>:nth-child(2),.topic>:nth-child(2){margin-top:0}.admonition>:last-child,.topic>:last-child{margin-bottom:0}.admonition p.admonition-title,p.topic-title{font-size:var(--admonition-title-font-size);font-weight:500;line-height:1.3;margin:0 -.5rem .5rem;padding:.4rem .5rem .4rem 2rem;position:relative}.admonition p.admonition-title:before,p.topic-title:before{content:"";height:1rem;left:.5rem;position:absolute;width:1rem}p.admonition-title{background-color:var(--color-admonition-title-background)}p.admonition-title:before{background-color:var(--color-admonition-title);-webkit-mask-image:var(--icon-admonition-default);mask-image:var(--icon-admonition-default);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}p.topic-title{background-color:var(--color-topic-title-background)}p.topic-title:before{background-color:var(--color-topic-title);-webkit-mask-image:var(--icon-topic-default);mask-image:var(--icon-topic-default);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.admonition{border-left:.2rem solid var(--color-admonition-title)}.admonition.caution{border-left-color:var(--color-admonition-title--caution)}.admonition.caution>.admonition-title{background-color:var(--color-admonition-title-background--caution)}.admonition.caution>.admonition-title:before{background-color:var(--color-admonition-title--caution);-webkit-mask-image:var(--icon-spark);mask-image:var(--icon-spark)}.admonition.warning{border-left-color:var(--color-admonition-title--warning)}.admonition.warning>.admonition-title{background-color:var(--color-admonition-title-background--warning)}.admonition.warning>.admonition-title:before{background-color:var(--color-admonition-title--warning);-webkit-mask-image:var(--icon-warning);mask-image:var(--icon-warning)}.admonition.danger{border-left-color:var(--color-admonition-title--danger)}.admonition.danger>.admonition-title{background-color:var(--color-admonition-title-background--danger)}.admonition.danger>.admonition-title:before{background-color:var(--color-admonition-title--danger);-webkit-mask-image:var(--icon-spark);mask-image:var(--icon-spark)}.admonition.attention{border-left-color:var(--color-admonition-title--attention)}.admonition.attention>.admonition-title{background-color:var(--color-admonition-title-background--attention)}.admonition.attention>.admonition-title:before{background-color:var(--color-admonition-title--attention);-webkit-mask-image:var(--icon-warning);mask-image:var(--icon-warning)}.admonition.error{border-left-color:var(--color-admonition-title--error)}.admonition.error>.admonition-title{background-color:var(--color-admonition-title-background--error)}.admonition.error>.admonition-title:before{background-color:var(--color-admonition-title--error);-webkit-mask-image:var(--icon-failure);mask-image:var(--icon-failure)}.admonition.hint{border-left-color:var(--color-admonition-title--hint)}.admonition.hint>.admonition-title{background-color:var(--color-admonition-title-background--hint)}.admonition.hint>.admonition-title:before{background-color:var(--color-admonition-title--hint);-webkit-mask-image:var(--icon-question);mask-image:var(--icon-question)}.admonition.tip{border-left-color:var(--color-admonition-title--tip)}.admonition.tip>.admonition-title{background-color:var(--color-admonition-title-background--tip)}.admonition.tip>.admonition-title:before{background-color:var(--color-admonition-title--tip);-webkit-mask-image:var(--icon-info);mask-image:var(--icon-info)}.admonition.important{border-left-color:var(--color-admonition-title--important)}.admonition.important>.admonition-title{background-color:var(--color-admonition-title-background--important)}.admonition.important>.admonition-title:before{background-color:var(--color-admonition-title--important);-webkit-mask-image:var(--icon-flame);mask-image:var(--icon-flame)}.admonition.note{border-left-color:var(--color-admonition-title--note)}.admonition.note>.admonition-title{background-color:var(--color-admonition-title-background--note)}.admonition.note>.admonition-title:before{background-color:var(--color-admonition-title--note);-webkit-mask-image:var(--icon-pencil);mask-image:var(--icon-pencil)}.admonition.seealso{border-left-color:var(--color-admonition-title--seealso)}.admonition.seealso>.admonition-title{background-color:var(--color-admonition-title-background--seealso)}.admonition.seealso>.admonition-title:before{background-color:var(--color-admonition-title--seealso);-webkit-mask-image:var(--icon-info);mask-image:var(--icon-info)}.admonition.admonition-todo{border-left-color:var(--color-admonition-title--admonition-todo)}.admonition.admonition-todo>.admonition-title{background-color:var(--color-admonition-title-background--admonition-todo)}.admonition.admonition-todo>.admonition-title:before{background-color:var(--color-admonition-title--admonition-todo);-webkit-mask-image:var(--icon-pencil);mask-image:var(--icon-pencil)}.admonition-todo>.admonition-title{text-transform:uppercase}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd{margin-left:2rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd>:first-child{margin-top:.125rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list,dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd>:last-child{margin-bottom:.75rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list>dt{font-size:var(--font-size--small);text-transform:uppercase}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd:empty{margin-bottom:.5rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul{margin-left:-1.2rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul>li>p:nth-child(2){margin-top:0}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul>li>p+p:last-child:empty{margin-bottom:0;margin-top:0}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{color:var(--color-api-overall)}.sig:not(.sig-inline){background:var(--color-api-background);border-radius:.25rem;font-family:var(--font-stack--monospace);font-size:var(--api-font-size);font-weight:700;margin-left:-.25rem;margin-right:-.25rem;padding:.25rem .5rem .25rem 3em;text-indent:-2.5em;transition:background .1s ease-out}.sig:not(.sig-inline):hover{background:var(--color-api-background-hover)}.sig:not(.sig-inline) a.reference .viewcode-link{font-weight:400;width:4.25rem}em.property{font-style:normal}em.property:first-child{color:var(--color-api-keyword)}.sig-name{color:var(--color-api-name)}.sig-prename{color:var(--color-api-pre-name);font-weight:400}.sig-paren{color:var(--color-api-paren)}.sig-param{font-style:normal}div.deprecated,div.versionadded,div.versionchanged,div.versionremoved{border-left:.1875rem solid;border-radius:.125rem;padding-left:.75rem}div.deprecated p,div.versionadded p,div.versionchanged p,div.versionremoved p{margin-bottom:.125rem;margin-top:.125rem}div.versionadded{border-color:var(--color-api-added-border)}div.versionadded .versionmodified{color:var(--color-api-added)}div.versionchanged{border-color:var(--color-api-changed-border)}div.versionchanged .versionmodified{color:var(--color-api-changed)}div.deprecated{border-color:var(--color-api-deprecated-border)}div.deprecated .versionmodified{color:var(--color-api-deprecated)}div.versionremoved{border-color:var(--color-api-removed-border)}div.versionremoved .versionmodified{color:var(--color-api-removed)}.viewcode-back,.viewcode-link{float:right;text-align:right}.line-block{margin-bottom:.75rem;margin-top:.5rem}.line-block .line-block{margin-bottom:0;margin-top:0;padding-left:1rem}.code-block-caption,article p.caption,table>caption{font-size:var(--font-size--small);text-align:center}.toctree-wrapper.compound .caption,.toctree-wrapper.compound :not(.caption)>.caption-text{font-size:var(--font-size--small);margin-bottom:0;text-align:initial;text-transform:uppercase}.toctree-wrapper.compound>ul{margin-bottom:0;margin-top:0}.sig-inline,code.literal{background:var(--color-inline-code-background);border-radius:.2em;font-size:var(--font-size--small--2);padding:.1em .2em}pre.literal-block .sig-inline,pre.literal-block code.literal{font-size:inherit;padding:0}p .sig-inline,p code.literal{border:1px solid var(--color-background-border)}.sig-inline{font-family:var(--font-stack--monospace)}div[class*=" highlight-"],div[class^=highlight-]{display:flex;margin:1em 0}div[class*=" highlight-"] .table-wrapper,div[class^=highlight-] .table-wrapper,pre{margin:0;padding:0}pre{overflow:auto}article[role=main] .highlight pre{line-height:1.5}.highlight pre,pre.literal-block{font-size:var(--code-font-size);padding:.625rem .875rem}pre.literal-block{background-color:var(--color-code-background);border-radius:.2rem;color:var(--color-code-foreground);margin-bottom:1rem;margin-top:1rem}.highlight{border-radius:.2rem;width:100%}.highlight .gp,.highlight span.linenos{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.highlight .hll{display:block;margin-left:-.875rem;margin-right:-.875rem;padding-left:.875rem;padding-right:.875rem}.code-block-caption{background-color:var(--color-code-background);border-bottom:1px solid;border-radius:.25rem;border-bottom-left-radius:0;border-bottom-right-radius:0;border-color:var(--color-background-border);color:var(--color-code-foreground);display:flex;font-weight:300;padding:.625rem .875rem}.code-block-caption+div[class]{margin-top:0}.code-block-caption+div[class] pre{border-top-left-radius:0;border-top-right-radius:0}.highlighttable{display:block;width:100%}.highlighttable tbody{display:block}.highlighttable tr{display:flex}.highlighttable td.linenos{background-color:var(--color-code-background);border-bottom-left-radius:.2rem;border-top-left-radius:.2rem;color:var(--color-code-foreground);padding:.625rem 0 .625rem .875rem}.highlighttable .linenodiv{box-shadow:-.0625rem 0 var(--color-foreground-border) inset;font-size:var(--code-font-size);padding-right:.875rem}.highlighttable td.code{display:block;flex:1;overflow:hidden;padding:0}.highlighttable td.code .highlight{border-bottom-left-radius:0;border-top-left-radius:0}.highlight span.linenos{box-shadow:-.0625rem 0 var(--color-foreground-border) inset;display:inline-block;margin-right:.875rem;padding-left:0;padding-right:.875rem}.footnote-reference{font-size:var(--font-size--small--4);vertical-align:super}dl.footnote.brackets{color:var(--color-foreground-secondary);display:grid;font-size:var(--font-size--small);grid-template-columns:max-content auto}dl.footnote.brackets dt{margin:0}dl.footnote.brackets dt>.fn-backref{margin-left:.25rem}dl.footnote.brackets dt:after{content:":"}dl.footnote.brackets dt .brackets:before{content:"["}dl.footnote.brackets dt .brackets:after{content:"]"}dl.footnote.brackets dd{margin:0;padding:0 1rem}aside.footnote{color:var(--color-foreground-secondary);font-size:var(--font-size--small)}aside.footnote>span,div.citation>span{float:left;font-weight:500;padding-right:.25rem}aside.footnote>:not(span),div.citation>p{margin-left:2rem}img{box-sizing:border-box;height:auto;max-width:100%}article .figure,article figure{border-radius:.2rem;margin:0}article .figure :last-child,article figure :last-child{margin-bottom:0}article .align-left{clear:left;float:left;margin:0 1rem 1rem}article .align-right{clear:right;float:right;margin:0 1rem 1rem}article .align-center,article .align-default{display:block;margin-left:auto;margin-right:auto;text-align:center}article table.align-default{display:table;text-align:initial}.domainindex-jumpbox,.genindex-jumpbox{border-bottom:1px solid var(--color-background-border);border-top:1px solid var(--color-background-border);padding:.25rem}.domainindex-section h2,.genindex-section h2{margin-bottom:.5rem;margin-top:.75rem}.domainindex-section ul,.genindex-section ul{margin-bottom:0;margin-top:0}ol,ul{margin-bottom:1rem;margin-top:1rem;padding-left:1.2rem}ol li>p:first-child,ul li>p:first-child{margin-bottom:.25rem;margin-top:.25rem}ol li>p:last-child,ul li>p:last-child{margin-top:.25rem}ol li>ol,ol li>ul,ul li>ol,ul li>ul{margin-bottom:.5rem;margin-top:.5rem}ol.arabic{list-style:decimal}ol.loweralpha{list-style:lower-alpha}ol.upperalpha{list-style:upper-alpha}ol.lowerroman{list-style:lower-roman}ol.upperroman{list-style:upper-roman}.simple li>ol,.simple li>ul,.toctree-wrapper li>ol,.toctree-wrapper li>ul{margin-bottom:0;margin-top:0}.field-list dt,.option-list dt,dl.footnote dt,dl.glossary dt,dl.simple dt,dl:not([class]) dt{font-weight:500;margin-top:.25rem}.field-list dt+dt,.option-list dt+dt,dl.footnote dt+dt,dl.glossary dt+dt,dl.simple dt+dt,dl:not([class]) dt+dt{margin-top:0}.field-list dt .classifier:before,.option-list dt .classifier:before,dl.footnote dt .classifier:before,dl.glossary dt .classifier:before,dl.simple dt .classifier:before,dl:not([class]) dt .classifier:before{content:":";margin-left:.2rem;margin-right:.2rem}.field-list dd ul,.field-list dd>p:first-child,.option-list dd ul,.option-list dd>p:first-child,dl.footnote dd ul,dl.footnote dd>p:first-child,dl.glossary dd ul,dl.glossary dd>p:first-child,dl.simple dd ul,dl.simple dd>p:first-child,dl:not([class]) dd ul,dl:not([class]) dd>p:first-child{margin-top:.125rem}.field-list dd ul,.option-list dd ul,dl.footnote dd ul,dl.glossary dd ul,dl.simple dd ul,dl:not([class]) dd ul{margin-bottom:.125rem}.math-wrapper{overflow-x:auto;width:100%}div.math{position:relative;text-align:center}div.math .headerlink,div.math:focus .headerlink{display:none}div.math:hover .headerlink{display:inline-block}div.math span.eqno{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);z-index:1}abbr[title]{cursor:help}.problematic{color:var(--color-problematic)}kbd:not(.compound){background-color:var(--color-background-secondary);border:1px solid var(--color-foreground-border);border-radius:.2rem;box-shadow:0 .0625rem 0 rgba(0,0,0,.2),inset 0 0 0 .125rem var(--color-background-primary);color:var(--color-foreground-primary);display:inline-block;font-size:var(--font-size--small--3);margin:0 .2rem;padding:0 .2rem;vertical-align:text-bottom}blockquote{background:var(--color-background-secondary);border-left:4px solid var(--color-background-border);margin-left:0;margin-right:0;padding:.5rem 1rem}blockquote .attribution{font-weight:600;text-align:right}blockquote.highlights,blockquote.pull-quote{font-size:1.25em}blockquote.epigraph,blockquote.pull-quote{border-left-width:0;border-radius:.5rem}blockquote.highlights{background:transparent;border-left-width:0}p .reference img{vertical-align:middle}p.rubric{font-size:1.125em;font-weight:700;line-height:1.25}dd p.rubric{font-size:var(--font-size--small);font-weight:inherit;line-height:inherit;text-transform:uppercase}article .sidebar{background-color:var(--color-background-secondary);border:1px solid var(--color-background-border);border-radius:.2rem;clear:right;float:right;margin-left:1rem;margin-right:0;width:30%}article .sidebar>*{padding-left:1rem;padding-right:1rem}article .sidebar>ol,article .sidebar>ul{padding-left:2.2rem}article .sidebar .sidebar-title{border-bottom:1px solid var(--color-background-border);font-weight:500;margin:0;padding:.5rem 1rem}.table-wrapper{margin-bottom:.5rem;margin-top:1rem;overflow-x:auto;padding:.2rem .2rem .75rem;width:100%}table.docutils{border-collapse:collapse;border-radius:.2rem;border-spacing:0;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1)}table.docutils th{background:var(--color-table-header-background)}table.docutils td,table.docutils th{border-bottom:1px solid var(--color-table-border);border-left:1px solid var(--color-table-border);border-right:1px solid var(--color-table-border);padding:0 .25rem}table.docutils td p,table.docutils th p{margin:.25rem}table.docutils td:first-child,table.docutils th:first-child{border-left:none}table.docutils td:last-child,table.docutils th:last-child{border-right:none}table.docutils td.text-left,table.docutils th.text-left{text-align:left}table.docutils td.text-right,table.docutils th.text-right{text-align:right}table.docutils td.text-center,table.docutils th.text-center{text-align:center}:target{scroll-margin-top:2.5rem}@media(max-width:67em){:target{scroll-margin-top:calc(2.5rem + var(--header-height))}section>span:target{scroll-margin-top:calc(2.8rem + var(--header-height))}}.headerlink{font-weight:100;-webkit-user-select:none;-moz-user-select:none;user-select:none}.code-block-caption>.headerlink,dl dt>.headerlink,figcaption p>.headerlink,h1>.headerlink,h2>.headerlink,h3>.headerlink,h4>.headerlink,h5>.headerlink,h6>.headerlink,p.caption>.headerlink,table>caption>.headerlink{margin-left:.5rem;visibility:hidden}.code-block-caption:hover>.headerlink,dl dt:hover>.headerlink,figcaption p:hover>.headerlink,h1:hover>.headerlink,h2:hover>.headerlink,h3:hover>.headerlink,h4:hover>.headerlink,h5:hover>.headerlink,h6:hover>.headerlink,p.caption:hover>.headerlink,table>caption:hover>.headerlink{visibility:visible}.code-block-caption>.toc-backref,dl dt>.toc-backref,figcaption p>.toc-backref,h1>.toc-backref,h2>.toc-backref,h3>.toc-backref,h4>.toc-backref,h5>.toc-backref,h6>.toc-backref,p.caption>.toc-backref,table>caption>.toc-backref{color:inherit;text-decoration-line:none}figure:hover>figcaption>p>.headerlink,table:hover>caption>.headerlink{visibility:visible}:target>h1:first-of-type,:target>h2:first-of-type,:target>h3:first-of-type,:target>h4:first-of-type,:target>h5:first-of-type,:target>h6:first-of-type,span:target~h1:first-of-type,span:target~h2:first-of-type,span:target~h3:first-of-type,span:target~h4:first-of-type,span:target~h5:first-of-type,span:target~h6:first-of-type{background-color:var(--color-highlight-on-target)}:target>h1:first-of-type code.literal,:target>h2:first-of-type code.literal,:target>h3:first-of-type code.literal,:target>h4:first-of-type code.literal,:target>h5:first-of-type code.literal,:target>h6:first-of-type code.literal,span:target~h1:first-of-type code.literal,span:target~h2:first-of-type code.literal,span:target~h3:first-of-type code.literal,span:target~h4:first-of-type code.literal,span:target~h5:first-of-type code.literal,span:target~h6:first-of-type code.literal{background-color:transparent}.literal-block-wrapper:target .code-block-caption,.this-will-duplicate-information-and-it-is-still-useful-here li :target,figure:target,table:target>caption{background-color:var(--color-highlight-on-target)}dt:target{background-color:var(--color-highlight-on-target)!important}.footnote-reference:target,.footnote>dt:target+dd{background-color:var(--color-highlight-on-target)}.guilabel{background-color:var(--color-guilabel-background);border:1px solid var(--color-guilabel-border);border-radius:.5em;color:var(--color-guilabel-text);font-size:.9em;padding:0 .3em}footer{display:flex;flex-direction:column;font-size:var(--font-size--small);margin-top:2rem}.bottom-of-page{align-items:center;border-top:1px solid var(--color-background-border);color:var(--color-foreground-secondary);display:flex;justify-content:space-between;line-height:1.5;margin-top:1rem;padding-bottom:1rem;padding-top:1rem}@media(max-width:46em){.bottom-of-page{flex-direction:column-reverse;gap:.25rem;text-align:center}}.bottom-of-page .left-details{font-size:var(--font-size--small)}.bottom-of-page .right-details{display:flex;flex-direction:column;gap:.25rem;text-align:right}.bottom-of-page .icons{display:flex;font-size:1rem;gap:.25rem;justify-content:flex-end}.bottom-of-page .icons a{text-decoration:none}.bottom-of-page .icons img,.bottom-of-page .icons svg{font-size:1.125rem;height:1em;width:1em}.related-pages a{align-items:center;display:flex;text-decoration:none}.related-pages a:hover .page-info .title{color:var(--color-link);text-decoration:underline;text-decoration-color:var(--color-link-underline)}.related-pages a svg.furo-related-icon,.related-pages a svg.furo-related-icon>use{color:var(--color-foreground-border);flex-shrink:0;height:.75rem;margin:0 .5rem;width:.75rem}.related-pages a.next-page{clear:right;float:right;max-width:50%;text-align:right}.related-pages a.prev-page{clear:left;float:left;max-width:50%}.related-pages a.prev-page svg{transform:rotate(180deg)}.page-info{display:flex;flex-direction:column;overflow-wrap:anywhere}.next-page .page-info{align-items:flex-end}.page-info .context{align-items:center;color:var(--color-foreground-muted);display:flex;font-size:var(--font-size--small);padding-bottom:.1rem;text-decoration:none}ul.search{list-style:none;padding-left:0}ul.search li{border-bottom:1px solid var(--color-background-border);padding:1rem 0}[role=main] .highlighted{background-color:var(--color-highlighted-background);color:var(--color-highlighted-text)}.sidebar-brand{display:flex;flex-direction:column;flex-shrink:0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-decoration:none}.sidebar-brand-text{color:var(--color-sidebar-brand-text);font-size:1.5rem;overflow-wrap:break-word}.sidebar-brand-text,.sidebar-logo-container{margin:var(--sidebar-item-spacing-vertical) 0}.sidebar-logo{display:block;margin:0 auto;max-width:100%}.sidebar-search-container{align-items:center;background:var(--color-sidebar-search-background);display:flex;margin-top:var(--sidebar-search-space-above);position:relative}.sidebar-search-container:focus-within,.sidebar-search-container:hover{background:var(--color-sidebar-search-background--focus)}.sidebar-search-container:before{background-color:var(--color-sidebar-search-icon);content:"";height:var(--sidebar-search-icon-size);left:var(--sidebar-item-spacing-horizontal);-webkit-mask-image:var(--icon-search);mask-image:var(--icon-search);position:absolute;width:var(--sidebar-search-icon-size)}.sidebar-search{background:transparent;border:none;border-bottom:1px solid var(--color-sidebar-search-border);border-top:1px solid var(--color-sidebar-search-border);box-sizing:border-box;color:var(--color-sidebar-search-foreground);padding:var(--sidebar-search-input-spacing-vertical) var(--sidebar-search-input-spacing-horizontal) var(--sidebar-search-input-spacing-vertical) calc(var(--sidebar-item-spacing-horizontal) + var(--sidebar-search-input-spacing-horizontal) + var(--sidebar-search-icon-size));width:100%;z-index:10}.sidebar-search:focus{outline:none}.sidebar-search::-moz-placeholder{font-size:var(--sidebar-search-input-font-size)}.sidebar-search::placeholder{font-size:var(--sidebar-search-input-font-size)}#searchbox .highlight-link{margin:0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal) 0;text-align:center}#searchbox .highlight-link a{color:var(--color-sidebar-search-icon);font-size:var(--font-size--small--2)}.sidebar-tree{font-size:var(--sidebar-item-font-size);margin-bottom:var(--sidebar-item-spacing-vertical);margin-top:var(--sidebar-tree-space-above)}.sidebar-tree ul{display:flex;flex-direction:column;list-style:none;margin-bottom:0;margin-top:0;padding:0}.sidebar-tree li{margin:0;position:relative}.sidebar-tree li>ul{margin-left:var(--sidebar-item-spacing-horizontal)}.sidebar-tree .icon,.sidebar-tree .reference{color:var(--color-sidebar-link-text)}.sidebar-tree .reference{box-sizing:border-box;display:inline-block;height:100%;line-height:var(--sidebar-item-line-height);overflow-wrap:anywhere;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-decoration:none;width:100%}.sidebar-tree .reference:hover{background:var(--color-sidebar-item-background--hover);color:var(--color-sidebar-link-text)}.sidebar-tree .reference.external:after{color:var(--color-sidebar-link-text);content:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23607D8B' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' viewBox='0 0 24 24'%3E%3Cpath stroke='none' d='M0 0h24v24H0z'/%3E%3Cpath d='M11 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-5M10 14 20 4M15 4h5v5'/%3E%3C/svg%3E");margin:0 .25rem;vertical-align:middle}.sidebar-tree .current-page>.reference{font-weight:700}.sidebar-tree label{align-items:center;cursor:pointer;display:flex;height:var(--sidebar-item-height);justify-content:center;position:absolute;right:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:var(--sidebar-expander-width)}.sidebar-tree .caption,.sidebar-tree :not(.caption)>.caption-text{color:var(--color-sidebar-caption-text);font-size:var(--sidebar-caption-font-size);font-weight:700;margin:var(--sidebar-caption-space-above) 0 0 0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-transform:uppercase}.sidebar-tree li.has-children>.reference{padding-right:var(--sidebar-expander-width)}.sidebar-tree .toctree-l1>.reference,.sidebar-tree .toctree-l1>label .icon{color:var(--color-sidebar-link-text--top-level)}.sidebar-tree label{background:var(--color-sidebar-item-expander-background)}.sidebar-tree label:hover{background:var(--color-sidebar-item-expander-background--hover)}.sidebar-tree .current>.reference{background:var(--color-sidebar-item-background--current)}.sidebar-tree .current>.reference:hover{background:var(--color-sidebar-item-background--hover)}.toctree-checkbox{display:none;position:absolute}.toctree-checkbox~ul{display:none}.toctree-checkbox~label .icon svg{transform:rotate(90deg)}.toctree-checkbox:checked~ul{display:block}.toctree-checkbox:checked~label .icon svg{transform:rotate(-90deg)}.toc-title-container{padding:var(--toc-title-padding);padding-top:var(--toc-spacing-vertical)}.toc-title{color:var(--color-toc-title-text);font-size:var(--toc-title-font-size);padding-left:var(--toc-spacing-horizontal);text-transform:uppercase}.no-toc{display:none}.toc-tree-container{padding-bottom:var(--toc-spacing-vertical)}.toc-tree{border-left:1px solid var(--color-background-border);font-size:var(--toc-font-size);line-height:1.3;padding-left:calc(var(--toc-spacing-horizontal) - var(--toc-item-spacing-horizontal))}.toc-tree>ul>li:first-child{padding-top:0}.toc-tree>ul>li:first-child>ul{padding-left:0}.toc-tree>ul>li:first-child>a{display:none}.toc-tree ul{list-style-type:none;margin-bottom:0;margin-top:0;padding-left:var(--toc-item-spacing-horizontal)}.toc-tree li{padding-top:var(--toc-item-spacing-vertical)}.toc-tree li.scroll-current>.reference{color:var(--color-toc-item-text--active);font-weight:700}.toc-tree a.reference{color:var(--color-toc-item-text);overflow-wrap:anywhere;text-decoration:none}.toc-scroll{max-height:100vh;overflow-y:scroll}.contents:not(.this-will-duplicate-information-and-it-is-still-useful-here){background:rgba(255,0,0,.25);color:var(--color-problematic)}.contents:not(.this-will-duplicate-information-and-it-is-still-useful-here):before{content:"ERROR: Adding a table of contents in Furo-based documentation is unnecessary, and does not work well with existing styling. Add a 'this-will-duplicate-information-and-it-is-still-useful-here' class, if you want an escape hatch."}.text-align\:left>p{text-align:left}.text-align\:center>p{text-align:center}.text-align\:right>p{text-align:right} +/*# sourceMappingURL=furo.css.map*/ \ No newline at end of file diff --git a/_static/styles/furo.css.map b/_static/styles/furo.css.map new file mode 100644 index 0000000000..6e02d0b1aa --- /dev/null +++ b/_static/styles/furo.css.map @@ -0,0 +1 @@ +{"version":3,"file":"styles/furo.css","mappings":"AAAA,2EAA2E,CAU3E,KACE,gBAAiB,CACjB,6BACF,CASA,KACE,QACF,CAMA,KACE,aACF,CAOA,GACE,aAAc,CACd,cACF,CAUA,GACE,sBAAuB,CACvB,QAAS,CACT,gBACF,CAOA,IACE,+BAAiC,CACjC,aACF,CASA,EACE,4BACF,CAOA,YACE,kBAAmB,CACnB,yBAA0B,CAC1B,gCACF,CAMA,SAEE,kBACF,CAOA,cAGE,+BAAiC,CACjC,aACF,CAeA,QAEE,aAAc,CACd,aAAc,CACd,iBAAkB,CAClB,uBACF,CAEA,IACE,aACF,CAEA,IACE,SACF,CASA,IACE,iBACF,CAUA,sCAKE,mBAAoB,CACpB,cAAe,CACf,gBAAiB,CACjB,QACF,CAOA,aAEE,gBACF,CAOA,cAEE,mBACF,CAMA,gDAIE,yBACF,CAMA,wHAIE,iBAAkB,CAClB,SACF,CAMA,4GAIE,6BACF,CAMA,SACE,0BACF,CASA,OACE,qBAAsB,CACtB,aAAc,CACd,aAAc,CACd,cAAe,CACf,SAAU,CACV,kBACF,CAMA,SACE,uBACF,CAMA,SACE,aACF,CAOA,6BAEE,qBAAsB,CACtB,SACF,CAMA,kFAEE,WACF,CAOA,cACE,4BAA6B,CAC7B,mBACF,CAMA,yCACE,uBACF,CAOA,6BACE,yBAA0B,CAC1B,YACF,CASA,QACE,aACF,CAMA,QACE,iBACF,CAiBA,kBACE,YACF,CCvVA,aAcE,kEACE,uBAOF,WACE,iDAMF,gCACE,wBAEF,qCAEE,uBADA,uBACA,CAEF,SACE,wBAtBA,CCpBJ,iBAGE,qBAEA,sBACA,0BAFA,oBAHA,4BACA,oBAKA,6BAIA,2CAFA,mBACA,sCAFA,4BAGA,CAEF,gBACE,aCTF,KCGE,mHAEA,wGAEA,wCAAyC,CAEzC,wBAAyB,CACzB,wBAAyB,CACzB,4BAA6B,CAC7B,yBAA0B,CAC1B,2BAA4B,CAG5B,sDAAuD,CACvD,gDAAiD,CACjD,wDAAyD,CAGzD,0CAA2C,CAC3C,gDAAiD,CACjD,gDAAiD,CAKjD,gCAAiC,CACjC,sCAAuC,CAGvC,2CAA4C,CAG5C,uCAAwC,CCjCxC,+FAGA,uBAAwB,CAGxB,iCAAkC,CAClC,kCAAmC,CAEnC,+BAAgC,CAChC,sCAAuC,CACvC,sCAAuC,CACvC,qGAIA,mDAAoD,CAEpD,mCAAoC,CACpC,8CAA+C,CAC/C,gDAAiD,CACjD,kCAAmC,CACnC,6DAA8D,CAG9D,6BAA8B,CAC9B,6BAA8B,CAC9B,+BAAgC,CAChC,kCAAmC,CACnC,kCAAmC,CCPjC,+jBCYA,iqCAZF,iaCVA,8KAOA,4SAWA,4SAUA,0CACA,gEAGA,0CAGA,gEAGA,yCACA,+DAIA,4CACA,kEAGA,wCAUA,8DACA,uCAGA,4DACA,sCACA,2DAGA,4CACA,kEACA,uCAGA,6DACA,2GAGA,sHAEA,yFAEA,+CACA,+EAGA,4MAOA,gCACA,sHAIA,kCACA,uEACA,gEACA,4DACA,kEAGA,2DACA,sDACA,0CACA,8CACA,wGAGA,0BACA,iCAGA,+DACA,+BACA,sCACA,+DAEA,kGACA,oCACA,yDACA,sCL7HF,kCAEA,sDAIA,0CK2HE,kEAIA,oDACA,sDAGA,oCACA,oEAEA,0DACA,qDAIA,oDACA,6DAIA,iEAIA,2DAIA,2DAGA,4DACA,gEAIA,gEAEA,gFAEA,oNASA,qDLxKE,gFAGE,4DAIF,oEKkHF,yEAEA,6DAGA,0DAEA,uDACA,qDACA,wDAIA,6DAIA,yDACA,2DAIA,uCAGA,wCACA,sDAGA,+CAGA,6DAEA,iDACA,+DAEA,wDAEA,sEAMA,0DACA,sBACA,mEL9JI,wEAEA,iCACE,+BAMN,wEAGA,iCACE,kFAEA,uEAIF,gEACE,8BAGF,qEMvDA,sCAKA,wFAKA,iCAIA,0BAWA,iCACA,4BACA,mCAGA,+BAEA,sCACA,4BAEA,mCAEA,sCAKA,sDAIA,gCAEA,gEAQF,wCAME,sBACA,kCAKA,uBAEA,gEAIA,2BAIA,mCAEA,qCACA,iCAGE,+BACA,wEAEE,iCACA,kFAGF,6BACA,0CACF,kCAEE,8BACE,8BACA,qEAEE,sCACA,wFCjFN,iCAGF,2DAEE,4BACA,oCAGA,mIAGA,4HACE,gEAMJ,+CAGE,sBACA,yCAEF,uBAEE,sEAKA,gDACA,kEAGA,iFAGE,YAGF,EACA,4HAQF,mBACE,6BACA,mBACA,wCACA,wCACA,2CAIA,eAGA,mBAKE,mBAGA,CAJA,uCACA,iBAFF,gBACE,CAKE,mBACA,mBAGJ,oBAIF,+BAGE,kDACA,OADA,kBAGA,CAFA,gBAEA,mBACA,oBAEA,sCACA,OAGF,cAHE,WAGF,GAEE,oBACA,CAHF,gBAGE,CChHc,YDmHd,+CAIF,SAEE,CAPF,UACE,wBAMA,4BAEA,GAGA,uBACA,CAJA,yBAGA,CACA,iDAKA,2CAGA,2DAQA,iBACA,uCAGA,kEAKE,SAKJ,8BACE,yDACA,2BAEA,oBACA,8BAEA,yDAEE,4BAEJ,uCACE,CACA,iEAGA,CAEA,wCACE,uBACA,kDAEA,0DAEE,CAJF,oBAIE,0GASJ,aAEF,CAFE,YAEF,4HASE,+CACA,sBAGF,sBASE,4BAFF,0CAEE,CARA,qCAwBF,CAhBE,iBAEA,kBACE,aADF,4BACE,WAOF,2BAEF,qCAIA,CAbI,UAaJ,+BACE,uBAEA,SAGA,0CAGE,CANF,qCAGA,CAGE,2DACE,gBAKJ,+CAGF,CAEA,kDAME,CARF,8BAEA,CAQE,YAEA,CAlBI,2BAGJ,CAJI,UACA,CAcJ,UAIA,4GAIF,iCAGE,8BAIA,qBACA,mBACF,QACE,gBAOE,0CAGA,CATF,6DAME,CANF,sBASE,qCAKF,CAEE,cACA,CAHF,sBAGE,gCAEA,qBAOJ,wBACE,sCAIA,mBAEA,6BAKA,kCACA,CAHA,sBAEA,cAJA,eACA,MAIA,2FAIA,UACA,YACA,sBACE,8BAEA,CALF,aACA,WAIE,CACA,0BAEF,aACE,qBAEF,qCAgBA,kBACE,CAhBA,qDASA,qCAEJ,CAGI,YACF,CAJF,2BAGI,CAGA,eACE,CAAF,oBAEA,mEAEA,qBACA,eAGF,CAHE,cAIA,kBADF,kBACE,yBAEJ,oCAGI,qDAIA,+BAMF,oCAEA,+CAEA,gCAIA,YACE,yBAEA,qBACA,eAGA,uBAFA,WAEA,CAHA,cACA,CAEA,4BAIE,qCACA,cAFA,eADA,qBACA,cAEA,mDACE,CACA,oCACA,4EAEN,uCAMA,eACE,kDAIA,mBADF,sBACE,mBAIA,aACA,sCAGA,aADA,WACA,CAMA,UAFF,kBAEE,CAJJ,gBAEE,CAJE,iBAMA,yFAQA,aACA,eEpbJ,cACE,iBACA,YAEA,CAFA,iBAEA,+DAGA,mBAKA,gCAGA,CARA,SAIA,SACA,CALA,0EAIA,CAJA,OAQA,0CACE,UAGF,iDAGF,CAHE,UAGF,8CAEE,CAFF,UAEE,CACA,uCAEA,WACA,WAFA,UAEA,6CAIA,yCACA,WAGA,WAJA,UAIA,gDACE,aASF,0CACE,CAFF,mBAEE,wEACA,CATA,YACA,CAKF,kBACA,CALE,MAGJ,CAII,eACA,CAJF,iCALE,cACA,CAHA,oBACA,CAKJ,SAKI,2BADA,UACA,6BAEJ,WACE,0DACA,kBACE,gCACA,mBADA,YACA,oEACA,2CAMF,mDAII,CAJJ,aADF,cACE,kBAII,kEACA,iBACE,mEACA,6BACE,wBADF,cACE,mCACA,qDANN,kCACE,6BAEE,mBADF,0CACE,CAFF,eACA,MACE,0DACA,wCACE,sGACA,WANN,yBACE,uCACA,CAFF,UAEE,2CACE,0FACA,cACE,kEACA,mEANN,yBACE,4DACA,sBACE,+EAEE,iEACA,qEANN,sCACE,CAGE,iBAHF,gBAGE,qBACE,CAJJ,uBACA,gDACE,wDACA,6DAHF,2CACA,CADA,gBACA,eACE,CAGE,sBANN,8BACE,CAII,iBAFF,4DACA,WACE,YADF,uCACE,6EACA,2BANN,8CACE,kDACA,0CACE,8BACA,yFACE,sBACA,sFALJ,mEACA,sBACE,kEACA,6EACE,uCACA,kEALJ,qGAEE,kEACA,6EACE,uCACA,kEALJ,8CACA,uDACE,sEACA,2EACE,sCACA,iEALJ,mGACA,qCACE,oDACA,0DACE,6GACA,gDAGR,yDCrEA,sEACE,CACA,6GACE,gEACF,iGAIF,wFACE,qDAGA,mGAEE,2CAEF,4FACE,gCACF,wGACE,8DAEE,6FAIA,iJAKN,6GACE,gDAKF,yDACA,qCAGA,6BACA,kBACA,qDAKA,oCAEA,+DAGA,2CAGE,oDAIA,oEAEE,qBAGJ,wDAEE,uCAEF,kEAGA,8CAEA,uDAIF,gEAIE,6BACA,gEAIA,+CACE,0EAIF,sDAEE,+DAGF,sCACA,8BACE,oCAEJ,wBACE,4FAEE,gBAEJ,yGAGI,kBAGJ,CCnHE,2MCFF,oBAGE,wGAKA,iCACE,CADF,wBACE,8GAQA,mBCjBJ,2GAIE,mBACA,6HAMA,YACE,mIAYF,eACA,CAHF,YAGE,4FAGE,8BAKF,uBAkBE,sCACA,CADA,qBAbA,wCAIA,CALF,8BACE,CADF,gBAKE,wCACA,CAOA,kDACA,CACA,kCAKF,6BAGA,4CACE,kDACA,eAGF,cACE,aACA,iBACA,yBACA,8BACA,WAGJ,2BACE,cAGA,+BACA,CAHA,eAGA,wCACA,YACA,iBACA,uEAGA,0BACA,2CAEA,8EAGI,qBACA,CAFF,kBAEE,kBAGN,0CAGE,mCAGA,4BAIA,gEACE,qCACA,8BAEA,gBACA,+CACA,iCAEF,iCAEE,gEACA,qCAGF,8BAEE,+BAIA,yCAEE,qBADA,gBACA,yBAKF,eACA,CAFF,YACE,CACA,iBACA,qDAEA,mDCvIJ,2FAOE,iCACA,CAEA,eACA,CAHA,kBAEA,CAFA,wBAGA,8BACA,eACE,CAFF,YAEE,0BACA,8CAGA,oBACE,oCAGA,kBACE,8DAEA,iBAEN,UACE,8BAIJ,+CAEE,qDAEF,kDAIE,YAEF,CAFE,YAEF,CCpCE,mFADA,kBAKE,CAJF,IAGA,aACE,mCAGA,iDACE,+BAEJ,wBAEE,mBAMA,6CAEF,CAJE,mBAEA,CAEF,kCAGE,CARF,kBACE,CAHA,eAUA,YACA,mBACA,CADA,UACA,wCC9BF,oBDkCE,wBCnCJ,uCACE,+BACA,+DACA,sBAGA,qBCDA,6CAIE,CAPF,uBAGA,CDGE,oBACF,yDAEE,CCDE,2CAGF,CAJA,kCACE,CDJJ,YACE,CAIA,eCTF,CDKE,uBCMA,gCACE,YAEF,oCAEE,wBACA,0BAIF,iBAEA,cADF,UACE,uBAEA,iCAEA,wCAEA,6CAMA,CAYF,gCATI,4BASJ,CAZE,mCAEE,iCAUJ,4BAGE,4DADA,+BACA,CAHF,qBAGE,sCACE,OAEF,iBAHA,SAGA,iHACE,2DAKF,CANA,8EAMA,uSAEE,kBAEF,+FACE,yCCjEJ,WACA,yBAGA,uBACA,gBAEA,uCAIA,CAJA,iCAIA,uCAGA,UACE,gBACA,qBAEA,0CClBJ,gBACE,KAGF,qBACE,YAGF,CAHE,cAGF,gCAEE,mBACA,iEAEA,oCACA,wCAEA,sBACA,WAEA,CAFA,YAEA,8EAEA,mCAFA,iBAEA,6BAIA,wEAKA,sDAIE,CARF,mDAIA,CAIE,cAEF,8CAIA,oBAFE,iBAEF,8CAGE,eAEF,CAFE,YAEF,OAEE,kBAGJ,CAJI,eACA,CAFF,mBAKF,yCCjDE,oBACA,CAFA,iBAEA,uCAKE,iBACA,qCAGA,mBCZJ,CDWI,gBCXJ,6BAEE,eACA,sBAGA,eAEA,sBACA,oDACA,iGAMA,gBAFE,YAEF,8FAME,iJClBF,YACA,gNAUE,6BAEF,oTAcI,kBACF,gHAIA,qBACE,eACF,qDACE,kBACF,6DACE,4BCxCJ,oBAEF,qCAEI,+CAGF,uBACE,uDAGJ,oBAiBI,kDACF,CAhBA,+CAaA,CAbA,oBAaA,0FAEE,CAFF,gGAdA,cACA,iBAaA,0BAGA,mQAIA,oNAEE,iBAGJ,CAHI,gBAFF,gBAKF,8CAYI,CAZJ,wCAYI,sVACE,iCAGA,uEAHA,QAGA,qXAKJ,iDAGF,CARM,+CACE,iDAIN,CALI,gBAQN,mHACE,gBAGF,2DACE,0EAOA,0EAGF,gBAEE,6DC/EA,kDACA,gCACA,qDAGA,qBACA,qDCFA,cACA,eAEA,yBAGF,sBAEE,iBACA,sNAWA,iBACE,kBACA,wRAgBA,kBAEA,iOAgBA,uCACE,uEAEA,kBAEF,qUAuBE,iDAIJ,CACA,geCxFF,4BAEE,CAQA,6JACA,iDAIA,sEAGA,mDAOF,iDAGE,4DAIA,8CACA,qDAEE,eAFF,cAEE,oBAEF,uBAFE,kCAGA,eACA,iBACA,mBAIA,mDACA,CAHA,uCAEA,CAJA,0CACA,CAIA,gBAJA,gBACA,oBADA,gBAIA,wBAEJ,gBAGE,6BACA,YAHA,iBAGA,gCACA,iEAEA,6CACA,sDACA,0BADA,wBACA,0BACA,oIAIA,mBAFA,YAEA,qBACA,0CAIE,uBAEF,CAHA,yBACE,CAEF,iDACE,mFAKJ,oCACE,CANE,aAKJ,CACE,qEAIA,YAFA,WAEA,CAHA,aACA,CAEA,gBACE,4BACA,sBADA,aACA,gCAMF,oCACA,yDACA,2CAEA,qBAGE,kBAEA,CACA,mCAIF,CARE,YACA,CAOF,iCAEE,CAPA,oBACA,CAQA,oBACE,uDAEJ,sDAGA,CAHA,cAGA,0BACE,oDAIA,oCACA,4BACA,sBAGA,cAEA,oFAGA,sBAEA,yDACE,CAIF,iBAJE,wBAIF,6CAHE,6CAKA,eACA,aACA,CADA,cACA,yCAGJ,kBACE,CAKA,iDAEA,CARF,aACE,4CAGA,kBAIA,wEAGA,wDAGA,kCAOA,iDAGA,CAPF,WAEE,sCAEA,CAJF,2CACE,CAMA,qCACA,+BARF,kBACE,qCAOA,iBAsBA,sBACE,CAvBF,WAKA,CACE,0DAIF,CALA,uDACE,CANF,sBAqBA,4CACA,CALA,gRAIA,YAEE,6CAEN,mCAEE,+CASA,6EAIA,4BChNA,SDmNA,qFCnNA,gDACA,sCAGA,qCACA,sDACA,CAKA,kDAGA,CARA,0CAQA,kBAGA,YACA,sBACA,iBAFA,gBADF,YACE,CAHA,SAKA,kBAEA,SAFA,iBAEA,uEAGA,CAEE,6CAFF,oCAgBI,CAdF,yBACE,qBACF,CAGF,oBACE,CAIF,WACE,CALA,2CAGA,uBACF,CACE,mFAGE,CALF,qBAEA,UAGE,gCAIF,sDAEA,CALE,oCAKF,yCC7CJ,oCACE,CD+CA,yXAQE,sCCrDJ,wCAGA,oCACE","sources":["webpack:///./node_modules/normalize.css/normalize.css","webpack:///./src/furo/assets/styles/base/_print.sass","webpack:///./src/furo/assets/styles/base/_screen-readers.sass","webpack:///./src/furo/assets/styles/base/_theme.sass","webpack:///./src/furo/assets/styles/variables/_fonts.scss","webpack:///./src/furo/assets/styles/variables/_spacing.scss","webpack:///./src/furo/assets/styles/variables/_icons.scss","webpack:///./src/furo/assets/styles/variables/_admonitions.scss","webpack:///./src/furo/assets/styles/variables/_colors.scss","webpack:///./src/furo/assets/styles/base/_typography.sass","webpack:///./src/furo/assets/styles/_scaffold.sass","webpack:///./src/furo/assets/styles/variables/_layout.scss","webpack:///./src/furo/assets/styles/content/_admonitions.sass","webpack:///./src/furo/assets/styles/content/_api.sass","webpack:///./src/furo/assets/styles/content/_blocks.sass","webpack:///./src/furo/assets/styles/content/_captions.sass","webpack:///./src/furo/assets/styles/content/_code.sass","webpack:///./src/furo/assets/styles/content/_footnotes.sass","webpack:///./src/furo/assets/styles/content/_images.sass","webpack:///./src/furo/assets/styles/content/_indexes.sass","webpack:///./src/furo/assets/styles/content/_lists.sass","webpack:///./src/furo/assets/styles/content/_math.sass","webpack:///./src/furo/assets/styles/content/_misc.sass","webpack:///./src/furo/assets/styles/content/_rubrics.sass","webpack:///./src/furo/assets/styles/content/_sidebar.sass","webpack:///./src/furo/assets/styles/content/_tables.sass","webpack:///./src/furo/assets/styles/content/_target.sass","webpack:///./src/furo/assets/styles/content/_gui-labels.sass","webpack:///./src/furo/assets/styles/components/_footer.sass","webpack:///./src/furo/assets/styles/components/_sidebar.sass","webpack:///./src/furo/assets/styles/components/_table_of_contents.sass","webpack:///./src/furo/assets/styles/_shame.sass"],"sourcesContent":["/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n ========================================================================== */\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n line-height: 1.15; /* 1 */\n -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n margin: 0;\n}\n\n/**\n * Render the `main` element consistently in IE.\n */\n\nmain {\n display: block;\n}\n\n/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n/* Grouping content\n ========================================================================== */\n\n/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */\n\nhr {\n box-sizing: content-box; /* 1 */\n height: 0; /* 1 */\n overflow: visible; /* 2 */\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\npre {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/* Text-level semantics\n ========================================================================== */\n\n/**\n * Remove the gray background on active links in IE 10.\n */\n\na {\n background-color: transparent;\n}\n\n/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */\n\nabbr[title] {\n border-bottom: none; /* 1 */\n text-decoration: underline; /* 2 */\n text-decoration: underline dotted; /* 2 */\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n font-weight: bolder;\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -0.25em;\n}\n\nsup {\n top: -0.5em;\n}\n\n/* Embedded content\n ========================================================================== */\n\n/**\n * Remove the border on images inside links in IE 10.\n */\n\nimg {\n border-style: none;\n}\n\n/* Forms\n ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n font-family: inherit; /* 1 */\n font-size: 100%; /* 1 */\n line-height: 1.15; /* 1 */\n margin: 0; /* 2 */\n}\n\n/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */\n\nbutton,\ninput { /* 1 */\n overflow: visible;\n}\n\n/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n text-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n border-style: none;\n padding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring {\n outline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n * `fieldset` elements in all browsers.\n */\n\nlegend {\n box-sizing: border-box; /* 1 */\n color: inherit; /* 2 */\n display: table; /* 1 */\n max-width: 100%; /* 1 */\n padding: 0; /* 3 */\n white-space: normal; /* 1 */\n}\n\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\n\nprogress {\n vertical-align: baseline;\n}\n\n/**\n * Remove the default vertical scrollbar in IE 10+.\n */\n\ntextarea {\n overflow: auto;\n}\n\n/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */\n\n[type=\"checkbox\"],\n[type=\"radio\"] {\n box-sizing: border-box; /* 1 */\n padding: 0; /* 2 */\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type=\"search\"] {\n -webkit-appearance: textfield; /* 1 */\n outline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n -webkit-appearance: button; /* 1 */\n font: inherit; /* 2 */\n}\n\n/* Interactive\n ========================================================================== */\n\n/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */\n\ndetails {\n display: block;\n}\n\n/*\n * Add the correct display in all browsers.\n */\n\nsummary {\n display: list-item;\n}\n\n/* Misc\n ========================================================================== */\n\n/**\n * Add the correct display in IE 10+.\n */\n\ntemplate {\n display: none;\n}\n\n/**\n * Add the correct display in IE 10.\n */\n\n[hidden] {\n display: none;\n}\n","// This file contains styles for managing print media.\n\n////////////////////////////////////////////////////////////////////////////////\n// Hide elements not relevant to print media.\n////////////////////////////////////////////////////////////////////////////////\n@media print\n // Hide icon container.\n .content-icon-container\n display: none !important\n\n // Hide showing header links if hovering over when printing.\n .headerlink\n display: none !important\n\n // Hide mobile header.\n .mobile-header\n display: none !important\n\n // Hide navigation links.\n .related-pages\n display: none !important\n\n////////////////////////////////////////////////////////////////////////////////\n// Tweaks related to decolorization.\n////////////////////////////////////////////////////////////////////////////////\n@media print\n // Apply a border around code which no longer have a color background.\n .highlight\n border: 0.1pt solid var(--color-foreground-border)\n\n////////////////////////////////////////////////////////////////////////////////\n// Avoid page break in some relevant cases.\n////////////////////////////////////////////////////////////////////////////////\n@media print\n ul, ol, dl, a, table, pre, blockquote\n page-break-inside: avoid\n\n h1, h2, h3, h4, h5, h6, img, figure, caption\n page-break-inside: avoid\n page-break-after: avoid\n\n ul, ol, dl\n page-break-before: avoid\n",".visually-hidden\n position: absolute !important\n width: 1px !important\n height: 1px !important\n padding: 0 !important\n margin: -1px !important\n overflow: hidden !important\n clip: rect(0,0,0,0) !important\n white-space: nowrap !important\n border: 0 !important\n color: var(--color-foreground-primary)\n background: var(--color-background-primary)\n\n:-moz-focusring\n outline: auto\n","// This file serves as the \"skeleton\" of the theming logic.\n//\n// This contains the bulk of the logic for handling dark mode, color scheme\n// toggling and the handling of color-scheme-specific hiding of elements.\n\nbody\n @include fonts\n @include spacing\n @include icons\n @include admonitions\n @include default-admonition(#651fff, \"abstract\")\n @include default-topic(#14B8A6, \"pencil\")\n\n @include colors\n\n.only-light\n display: block !important\nhtml body .only-dark\n display: none !important\n\n// Ignore dark-mode hints if print media.\n@media not print\n // Enable dark-mode, if requested.\n body[data-theme=\"dark\"]\n @include colors-dark\n\n html & .only-light\n display: none !important\n .only-dark\n display: block !important\n\n // Enable dark mode, unless explicitly told to avoid.\n @media (prefers-color-scheme: dark)\n body:not([data-theme=\"light\"])\n @include colors-dark\n\n html & .only-light\n display: none !important\n .only-dark\n display: block !important\n\n//\n// Theme toggle presentation\n//\nbody[data-theme=\"auto\"]\n .theme-toggle svg.theme-icon-when-auto-light\n display: block\n\n @media (prefers-color-scheme: dark)\n .theme-toggle svg.theme-icon-when-auto-dark\n display: block\n .theme-toggle svg.theme-icon-when-auto-light\n display: none\n\nbody[data-theme=\"dark\"]\n .theme-toggle svg.theme-icon-when-dark\n display: block\n\nbody[data-theme=\"light\"]\n .theme-toggle svg.theme-icon-when-light\n display: block\n","// Fonts used by this theme.\n//\n// There are basically two things here -- using the system font stack and\n// defining sizes for various elements in %ages. We could have also used `em`\n// but %age is easier to reason about for me.\n\n@mixin fonts {\n // These are adapted from https://systemfontstack.com/\n --font-stack: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,\n sans-serif, Apple Color Emoji, Segoe UI Emoji;\n --font-stack--monospace: \"SFMono-Regular\", Menlo, Consolas, Monaco,\n Liberation Mono, Lucida Console, monospace;\n --font-stack--headings: var(--font-stack);\n\n --font-size--normal: 100%;\n --font-size--small: 87.5%;\n --font-size--small--2: 81.25%;\n --font-size--small--3: 75%;\n --font-size--small--4: 62.5%;\n\n // Sidebar\n --sidebar-caption-font-size: var(--font-size--small--2);\n --sidebar-item-font-size: var(--font-size--small);\n --sidebar-search-input-font-size: var(--font-size--small);\n\n // Table of Contents\n --toc-font-size: var(--font-size--small--3);\n --toc-font-size--mobile: var(--font-size--normal);\n --toc-title-font-size: var(--font-size--small--4);\n\n // Admonitions\n //\n // These aren't defined in terms of %ages, since nesting these is permitted.\n --admonition-font-size: 0.8125rem;\n --admonition-title-font-size: 0.8125rem;\n\n // Code\n --code-font-size: var(--font-size--small--2);\n\n // API\n --api-font-size: var(--font-size--small);\n}\n","// Spacing for various elements on the page\n//\n// If the user wants to tweak things in a certain way, they are permitted to.\n// They also have to deal with the consequences though!\n\n@mixin spacing {\n // Header!\n --header-height: calc(\n var(--sidebar-item-line-height) + 4 * #{var(--sidebar-item-spacing-vertical)}\n );\n --header-padding: 0.5rem;\n\n // Sidebar\n --sidebar-tree-space-above: 1.5rem;\n --sidebar-caption-space-above: 1rem;\n\n --sidebar-item-line-height: 1rem;\n --sidebar-item-spacing-vertical: 0.5rem;\n --sidebar-item-spacing-horizontal: 1rem;\n --sidebar-item-height: calc(\n var(--sidebar-item-line-height) + 2 *#{var(--sidebar-item-spacing-vertical)}\n );\n\n --sidebar-expander-width: var(--sidebar-item-height); // be square\n\n --sidebar-search-space-above: 0.5rem;\n --sidebar-search-input-spacing-vertical: 0.5rem;\n --sidebar-search-input-spacing-horizontal: 0.5rem;\n --sidebar-search-input-height: 1rem;\n --sidebar-search-icon-size: var(--sidebar-search-input-height);\n\n // Table of Contents\n --toc-title-padding: 0.25rem 0;\n --toc-spacing-vertical: 1.5rem;\n --toc-spacing-horizontal: 1.5rem;\n --toc-item-spacing-vertical: 0.4rem;\n --toc-item-spacing-horizontal: 1rem;\n}\n","// Expose theme icons as CSS variables.\n\n$icons: (\n // Adapted from tabler-icons\n // url: https://tablericons.com/\n \"search\":\n url('data:image/svg+xml;charset=utf-8,'),\n // Factored out from mkdocs-material on 24-Aug-2020.\n // url: https://squidfunk.github.io/mkdocs-material/reference/admonitions/\n \"pencil\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"abstract\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"info\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"flame\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"question\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"warning\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"failure\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"spark\":\n url('data:image/svg+xml;charset=utf-8,')\n);\n\n@mixin icons {\n @each $name, $glyph in $icons {\n --icon-#{$name}: #{$glyph};\n }\n}\n","// Admonitions\n\n// Structure of these is:\n// admonition-class: color \"icon-name\";\n//\n// The colors are translated into CSS variables below. The icons are\n// used directly in the main declarations to set the `mask-image` in\n// the title.\n\n// prettier-ignore\n$admonitions: (\n // Each of these has an reST directives for it.\n \"caution\": #ff9100 \"spark\",\n \"warning\": #ff9100 \"warning\",\n \"danger\": #ff5252 \"spark\",\n \"attention\": #ff5252 \"warning\",\n \"error\": #ff5252 \"failure\",\n \"hint\": #00c852 \"question\",\n \"tip\": #00c852 \"info\",\n \"important\": #00bfa5 \"flame\",\n \"note\": #00b0ff \"pencil\",\n \"seealso\": #448aff \"info\",\n \"admonition-todo\": #808080 \"pencil\"\n);\n\n@mixin default-admonition($color, $icon-name) {\n --color-admonition-title: #{$color};\n --color-admonition-title-background: #{rgba($color, 0.2)};\n\n --icon-admonition-default: var(--icon-#{$icon-name});\n}\n\n@mixin default-topic($color, $icon-name) {\n --color-topic-title: #{$color};\n --color-topic-title-background: #{rgba($color, 0.2)};\n\n --icon-topic-default: var(--icon-#{$icon-name});\n}\n\n@mixin admonitions {\n @each $name, $values in $admonitions {\n --color-admonition-title--#{$name}: #{nth($values, 1)};\n --color-admonition-title-background--#{$name}: #{rgba(\n nth($values, 1),\n 0.2\n )};\n }\n}\n","// Colors used throughout this theme.\n//\n// The aim is to give the user more control. Thus, instead of hard-coding colors\n// in various parts of the stylesheet, the approach taken is to define all\n// colors as CSS variables and reusing them in all the places.\n//\n// `colors-dark` depends on `colors` being included at a lower specificity.\n\n@mixin colors {\n --color-problematic: #b30000;\n\n // Base Colors\n --color-foreground-primary: black; // for main text and headings\n --color-foreground-secondary: #5a5c63; // for secondary text\n --color-foreground-muted: #6b6f76; // for muted text\n --color-foreground-border: #878787; // for content borders\n\n --color-background-primary: white; // for content\n --color-background-secondary: #f8f9fb; // for navigation + ToC\n --color-background-hover: #efeff4ff; // for navigation-item hover\n --color-background-hover--transparent: #efeff400;\n --color-background-border: #eeebee; // for UI borders\n --color-background-item: #ccc; // for \"background\" items (eg: copybutton)\n\n // Announcements\n --color-announcement-background: #000000dd;\n --color-announcement-text: #eeebee;\n\n // Brand colors\n --color-brand-primary: #0a4bff;\n --color-brand-content: #2757dd;\n --color-brand-visited: #872ee0;\n\n // API documentation\n --color-api-background: var(--color-background-hover--transparent);\n --color-api-background-hover: var(--color-background-hover);\n --color-api-overall: var(--color-foreground-secondary);\n --color-api-name: var(--color-problematic);\n --color-api-pre-name: var(--color-problematic);\n --color-api-paren: var(--color-foreground-secondary);\n --color-api-keyword: var(--color-foreground-primary);\n\n --color-api-added: #21632c;\n --color-api-added-border: #38a84d;\n --color-api-changed: #046172;\n --color-api-changed-border: #06a1bc;\n --color-api-deprecated: #605706;\n --color-api-deprecated-border: #f0d90f;\n --color-api-removed: #b30000;\n --color-api-removed-border: #ff5c5c;\n\n --color-highlight-on-target: #ffffcc;\n\n // Inline code background\n --color-inline-code-background: var(--color-background-secondary);\n\n // Highlighted text (search)\n --color-highlighted-background: #ddeeff;\n --color-highlighted-text: var(--color-foreground-primary);\n\n // GUI Labels\n --color-guilabel-background: #ddeeff80;\n --color-guilabel-border: #bedaf580;\n --color-guilabel-text: var(--color-foreground-primary);\n\n // Admonitions!\n --color-admonition-background: transparent;\n\n //////////////////////////////////////////////////////////////////////////////\n // Everything below this should be one of:\n // - var(...)\n // - *-gradient(...)\n // - special literal values (eg: transparent, none)\n //////////////////////////////////////////////////////////////////////////////\n\n // Tables\n --color-table-header-background: var(--color-background-secondary);\n --color-table-border: var(--color-background-border);\n\n // Cards\n --color-card-border: var(--color-background-secondary);\n --color-card-background: transparent;\n --color-card-marginals-background: var(--color-background-secondary);\n\n // Header\n --color-header-background: var(--color-background-primary);\n --color-header-border: var(--color-background-border);\n --color-header-text: var(--color-foreground-primary);\n\n // Sidebar (left)\n --color-sidebar-background: var(--color-background-secondary);\n --color-sidebar-background-border: var(--color-background-border);\n\n --color-sidebar-brand-text: var(--color-foreground-primary);\n --color-sidebar-caption-text: var(--color-foreground-muted);\n --color-sidebar-link-text: var(--color-foreground-secondary);\n --color-sidebar-link-text--top-level: var(--color-brand-primary);\n\n --color-sidebar-item-background: var(--color-sidebar-background);\n --color-sidebar-item-background--current: var(\n --color-sidebar-item-background\n );\n --color-sidebar-item-background--hover: linear-gradient(\n 90deg,\n var(--color-background-hover--transparent) 0%,\n var(--color-background-hover) var(--sidebar-item-spacing-horizontal),\n var(--color-background-hover) 100%\n );\n\n --color-sidebar-item-expander-background: transparent;\n --color-sidebar-item-expander-background--hover: var(\n --color-background-hover\n );\n\n --color-sidebar-search-text: var(--color-foreground-primary);\n --color-sidebar-search-background: var(--color-background-secondary);\n --color-sidebar-search-background--focus: var(--color-background-primary);\n --color-sidebar-search-border: var(--color-background-border);\n --color-sidebar-search-icon: var(--color-foreground-muted);\n\n // Table of Contents (right)\n --color-toc-background: var(--color-background-primary);\n --color-toc-title-text: var(--color-foreground-muted);\n --color-toc-item-text: var(--color-foreground-secondary);\n --color-toc-item-text--hover: var(--color-foreground-primary);\n --color-toc-item-text--active: var(--color-brand-primary);\n\n // Actual page contents\n --color-content-foreground: var(--color-foreground-primary);\n --color-content-background: transparent;\n\n // Links\n --color-link: var(--color-brand-content);\n --color-link-underline: var(--color-background-border);\n --color-link--hover: var(--color-brand-content);\n --color-link-underline--hover: var(--color-foreground-border);\n\n --color-link--visited: var(--color-brand-visited);\n --color-link-underline--visited: var(--color-background-border);\n --color-link--visited--hover: var(--color-brand-visited);\n --color-link-underline--visited--hover: var(--color-foreground-border);\n}\n\n@mixin colors-dark {\n --color-problematic: #ee5151;\n\n // Base Colors\n --color-foreground-primary: #cfd0d0; // for main text and headings\n --color-foreground-secondary: #9ca0a5; // for secondary text\n --color-foreground-muted: #81868d; // for muted text\n --color-foreground-border: #666666; // for content borders\n\n --color-background-primary: #131416; // for content\n --color-background-secondary: #1a1c1e; // for navigation + ToC\n --color-background-hover: #1e2124ff; // for navigation-item hover\n --color-background-hover--transparent: #1e212400;\n --color-background-border: #303335; // for UI borders\n --color-background-item: #444; // for \"background\" items (eg: copybutton)\n\n // Announcements\n --color-announcement-background: #000000dd;\n --color-announcement-text: #eeebee;\n\n // Brand colors\n --color-brand-primary: #3d94ff;\n --color-brand-content: #5ca5ff;\n --color-brand-visited: #b27aeb;\n\n // Highlighted text (search)\n --color-highlighted-background: #083563;\n\n // GUI Labels\n --color-guilabel-background: #08356380;\n --color-guilabel-border: #13395f80;\n\n // API documentation\n --color-api-keyword: var(--color-foreground-secondary);\n --color-highlight-on-target: #333300;\n\n --color-api-added: #3db854;\n --color-api-added-border: #267334;\n --color-api-changed: #09b0ce;\n --color-api-changed-border: #056d80;\n --color-api-deprecated: #b1a10b;\n --color-api-deprecated-border: #6e6407;\n --color-api-removed: #ff7575;\n --color-api-removed-border: #b03b3b;\n\n // Admonitions\n --color-admonition-background: #18181a;\n\n // Cards\n --color-card-border: var(--color-background-secondary);\n --color-card-background: #18181a;\n --color-card-marginals-background: var(--color-background-hover);\n}\n","// This file contains the styling for making the content throughout the page,\n// including fonts, paragraphs, headings and spacing among these elements.\n\nbody\n font-family: var(--font-stack)\npre,\ncode,\nkbd,\nsamp\n font-family: var(--font-stack--monospace)\n\n// Make fonts look slightly nicer.\nbody\n -webkit-font-smoothing: antialiased\n -moz-osx-font-smoothing: grayscale\n\n// Line height from Bootstrap 4.1\narticle\n line-height: 1.5\n\n//\n// Headings\n//\nh1,\nh2,\nh3,\nh4,\nh5,\nh6\n line-height: 1.25\n font-family: var(--font-stack--headings)\n font-weight: bold\n\n border-radius: 0.5rem\n margin-top: 0.5rem\n margin-bottom: 0.5rem\n margin-left: -0.5rem\n margin-right: -0.5rem\n padding-left: 0.5rem\n padding-right: 0.5rem\n\n + p\n margin-top: 0\n\nh1\n font-size: 2.5em\n margin-top: 1.75rem\n margin-bottom: 1rem\nh2\n font-size: 2em\n margin-top: 1.75rem\nh3\n font-size: 1.5em\nh4\n font-size: 1.25em\nh5\n font-size: 1.125em\nh6\n font-size: 1em\n\nsmall\n opacity: 75%\n font-size: 80%\n\n// Paragraph\np\n margin-top: 0.5rem\n margin-bottom: 0.75rem\n\n// Horizontal rules\nhr.docutils\n height: 1px\n padding: 0\n margin: 2rem 0\n background-color: var(--color-background-border)\n border: 0\n\n.centered\n text-align: center\n\n// Links\na\n text-decoration: underline\n\n color: var(--color-link)\n text-decoration-color: var(--color-link-underline)\n\n &:visited\n color: var(--color-link--visited)\n text-decoration-color: var(--color-link-underline--visited)\n &:hover\n color: var(--color-link--visited--hover)\n text-decoration-color: var(--color-link-underline--visited--hover)\n\n &:hover\n color: var(--color-link--hover)\n text-decoration-color: var(--color-link-underline--hover)\n &.muted-link\n color: inherit\n &:hover\n color: var(--color-link--hover)\n text-decoration-color: var(--color-link-underline--hover)\n &:visited\n color: var(--color-link--visited--hover)\n text-decoration-color: var(--color-link-underline--visited--hover)\n","// This file contains the styles for the overall layouting of the documentation\n// skeleton, including the responsive changes as well as sidebar toggles.\n//\n// This is implemented as a mobile-last design, which isn't ideal, but it is\n// reasonably good-enough and I got pretty tired by the time I'd finished this\n// to move the rules around to fix this. Shouldn't take more than 3-4 hours,\n// if you know what you're doing tho.\n\n// HACK: Not all browsers account for the scrollbar width in media queries.\n// This results in horizontal scrollbars in the breakpoint where we go\n// from displaying everything to hiding the ToC. We accomodate for this by\n// adding a bit of padding to the TOC drawer, disabling the horizontal\n// scrollbar and allowing the scrollbars to cover the padding.\n// https://www.456bereastreet.com/archive/201301/media_query_width_and_vertical_scrollbars/\n\n// HACK: Always having the scrollbar visible, prevents certain browsers from\n// causing the content to stutter horizontally between taller-than-viewport and\n// not-taller-than-viewport pages.\n\n$icon-size: 1.25rem\n\nhtml\n overflow-x: hidden\n overflow-y: scroll\n scroll-behavior: smooth\n\n.sidebar-scroll, .toc-scroll, article[role=main] *\n // Override Firefox scrollbar style\n scrollbar-width: thin\n scrollbar-color: var(--color-foreground-border) transparent\n\n // Override Chrome scrollbar styles\n &::-webkit-scrollbar\n width: 0.25rem\n height: 0.25rem\n &::-webkit-scrollbar-thumb\n background-color: var(--color-foreground-border)\n border-radius: 0.125rem\n\n//\n// Overalls\n//\nhtml,\nbody\n height: 100%\n color: var(--color-foreground-primary)\n background: var(--color-background-primary)\n\n.skip-to-content\n position: fixed\n padding: 1rem\n border-radius: 1rem\n left: 0.25rem\n top: 0.25rem\n z-index: 40\n background: var(--color-background-primary)\n color: var(--color-foreground-primary)\n\n transform: translateY(-200%)\n transition: transform 300ms ease-in-out\n\n &:focus-within\n transform: translateY(0%)\n\narticle\n color: var(--color-content-foreground)\n background: var(--color-content-background)\n overflow-wrap: break-word\n\n.page\n display: flex\n // fill the viewport for pages with little content.\n min-height: 100%\n\n.mobile-header\n width: 100%\n height: var(--header-height)\n background-color: var(--color-header-background)\n color: var(--color-header-text)\n border-bottom: 1px solid var(--color-header-border)\n\n // Looks like sub-script/super-script have this, and we need this to\n // be \"on top\" of those.\n z-index: 10\n\n // We don't show the header on large screens.\n display: none\n\n // Add shadow when scrolled\n &.scrolled\n border-bottom: none\n box-shadow: 0 0 0.2rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.4rem rgba(0, 0, 0, 0.2)\n\n .header-center\n a\n color: var(--color-header-text)\n text-decoration: none\n\n.main\n display: flex\n flex: 1\n\n// Sidebar (left) also covers the entire left portion of screen.\n.sidebar-drawer\n box-sizing: border-box\n\n border-right: 1px solid var(--color-sidebar-background-border)\n background: var(--color-sidebar-background)\n\n display: flex\n justify-content: flex-end\n // These next two lines took me two days to figure out.\n width: calc((100% - #{$full-width}) / 2 + #{$sidebar-width})\n min-width: $sidebar-width\n\n// Scroll-along sidebars\n.sidebar-container,\n.toc-drawer\n box-sizing: border-box\n width: $sidebar-width\n\n.toc-drawer\n background: var(--color-toc-background)\n // See HACK described on top of this document\n padding-right: 1rem\n\n.sidebar-sticky,\n.toc-sticky\n position: sticky\n top: 0\n height: min(100%, 100vh)\n height: 100vh\n\n display: flex\n flex-direction: column\n\n.sidebar-scroll,\n.toc-scroll\n flex-grow: 1\n flex-shrink: 1\n\n overflow: auto\n scroll-behavior: smooth\n\n// Central items.\n.content\n padding: 0 $content-padding\n width: $content-width\n\n display: flex\n flex-direction: column\n justify-content: space-between\n\n.icon\n display: inline-block\n height: 1rem\n width: 1rem\n svg\n width: 100%\n height: 100%\n\n//\n// Accommodate announcement banner\n//\n.announcement\n background-color: var(--color-announcement-background)\n color: var(--color-announcement-text)\n\n height: var(--header-height)\n display: flex\n align-items: center\n overflow-x: auto\n & + .page\n min-height: calc(100% - var(--header-height))\n\n.announcement-content\n box-sizing: border-box\n padding: 0.5rem\n min-width: 100%\n white-space: nowrap\n text-align: center\n\n a\n color: var(--color-announcement-text)\n text-decoration-color: var(--color-announcement-text)\n\n &:hover\n color: var(--color-announcement-text)\n text-decoration-color: var(--color-link--hover)\n\n////////////////////////////////////////////////////////////////////////////////\n// Toggles for theme\n////////////////////////////////////////////////////////////////////////////////\n.no-js .theme-toggle-container // don't show theme toggle if there's no JS\n display: none\n\n.theme-toggle-container\n vertical-align: middle\n\n.theme-toggle\n cursor: pointer\n border: none\n padding: 0\n background: transparent\n\n.theme-toggle svg\n vertical-align: middle\n height: $icon-size\n width: $icon-size\n color: var(--color-foreground-primary)\n display: none\n\n.theme-toggle-header\n float: left\n padding: 1rem 0.5rem\n\n////////////////////////////////////////////////////////////////////////////////\n// Toggles for elements\n////////////////////////////////////////////////////////////////////////////////\n.toc-overlay-icon, .nav-overlay-icon\n display: none\n cursor: pointer\n\n .icon\n color: var(--color-foreground-secondary)\n height: $icon-size\n width: $icon-size\n\n.toc-header-icon, .nav-overlay-icon\n // for when we set display: flex\n justify-content: center\n align-items: center\n\n.toc-content-icon\n height: 1.5rem\n width: 1.5rem\n\n.content-icon-container\n float: right\n display: flex\n margin-top: 1.5rem\n margin-left: 1rem\n margin-bottom: 1rem\n gap: 0.5rem\n\n .edit-this-page, .view-this-page\n svg\n color: inherit\n height: $icon-size\n width: $icon-size\n\n.sidebar-toggle\n position: absolute\n display: none\n// \n.sidebar-toggle[name=\"__toc\"]\n left: 20px\n.sidebar-toggle:checked\n left: 40px\n// \n\n.overlay\n position: fixed\n top: 0\n width: 0\n height: 0\n\n transition: width 0ms, height 0ms, opacity 250ms ease-out\n\n opacity: 0\n background-color: rgba(0, 0, 0, 0.54)\n.sidebar-overlay\n z-index: 20\n.toc-overlay\n z-index: 40\n\n// Keep things on top and smooth.\n.sidebar-drawer\n z-index: 30\n transition: left 250ms ease-in-out\n.toc-drawer\n z-index: 50\n transition: right 250ms ease-in-out\n\n// Show the Sidebar\n#__navigation:checked\n & ~ .sidebar-overlay\n width: 100%\n height: 100%\n opacity: 1\n & ~ .page\n .sidebar-drawer\n top: 0\n left: 0\n // Show the toc sidebar\n#__toc:checked\n & ~ .toc-overlay\n width: 100%\n height: 100%\n opacity: 1\n & ~ .page\n .toc-drawer\n top: 0\n right: 0\n\n////////////////////////////////////////////////////////////////////////////////\n// Back to top\n////////////////////////////////////////////////////////////////////////////////\n.back-to-top\n text-decoration: none\n\n display: none\n position: fixed\n left: 0\n top: 1rem\n padding: 0.5rem\n padding-right: 0.75rem\n border-radius: 1rem\n font-size: 0.8125rem\n\n background: var(--color-background-primary)\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), #6b728080 0px 0px 1px 0px\n\n z-index: 10\n\n margin-left: 50%\n transform: translateX(-50%)\n svg\n height: 1rem\n width: 1rem\n fill: currentColor\n display: inline-block\n\n span\n margin-left: 0.25rem\n\n .show-back-to-top &\n display: flex\n align-items: center\n\n////////////////////////////////////////////////////////////////////////////////\n// Responsive layouting\n////////////////////////////////////////////////////////////////////////////////\n// Make things a bit bigger on bigger screens.\n@media (min-width: $full-width + $sidebar-width)\n html\n font-size: 110%\n\n@media (max-width: $full-width)\n // Collapse \"toc\" into the icon.\n .toc-content-icon\n display: flex\n .toc-drawer\n position: fixed\n height: 100vh\n top: 0\n right: -$sidebar-width\n border-left: 1px solid var(--color-background-muted)\n .toc-tree\n border-left: none\n font-size: var(--toc-font-size--mobile)\n\n // Accomodate for a changed content width.\n .sidebar-drawer\n width: calc((100% - #{$full-width - $sidebar-width}) / 2 + #{$sidebar-width})\n\n@media (max-width: $full-width - $sidebar-width)\n // Collapse \"navigation\".\n .nav-overlay-icon\n display: flex\n .sidebar-drawer\n position: fixed\n height: 100vh\n width: $sidebar-width\n\n top: 0\n left: -$sidebar-width\n\n // Swap which icon is visible.\n .toc-header-icon\n display: flex\n .toc-content-icon, .theme-toggle-content\n display: none\n .theme-toggle-header\n display: block\n\n // Show the header.\n .mobile-header\n position: sticky\n top: 0\n display: flex\n justify-content: space-between\n align-items: center\n\n .header-left,\n .header-right\n display: flex\n height: var(--header-height)\n padding: 0 var(--header-padding)\n label\n height: 100%\n width: 100%\n user-select: none\n\n .nav-overlay-icon .icon,\n .theme-toggle svg\n height: $icon-size\n width: $icon-size\n\n // Add a scroll margin for the content\n :target\n scroll-margin-top: calc(var(--header-height) + 2.5rem)\n\n // Show back-to-top below the header\n .back-to-top\n top: calc(var(--header-height) + 0.5rem)\n\n // Center the page, and accommodate for the header.\n .page\n flex-direction: column\n justify-content: center\n .content\n margin-left: auto\n margin-right: auto\n\n@media (max-width: $content-width + 2* $content-padding)\n // Content should respect window limits.\n .content\n width: 100%\n overflow-x: auto\n\n@media (max-width: $content-width)\n .content\n padding: 0 $content-padding--small\n // Don't float sidebars to the right.\n article aside.sidebar\n float: none\n width: 100%\n margin: 1rem 0\n","// Overall Layout Variables\n//\n// Because CSS variables can't be used in media queries. The fact that this\n// makes the layout non-user-configurable is a good thing.\n$content-padding: 3em;\n$content-padding--small: 1em;\n$content-width: 46em;\n$sidebar-width: 15em;\n$full-width: $content-width + 2 * ($content-padding + $sidebar-width);\n","//\n// The design here is strongly inspired by mkdocs-material.\n.admonition, .topic\n margin: 1rem auto\n padding: 0 0.5rem 0.5rem 0.5rem\n\n background: var(--color-admonition-background)\n\n border-radius: 0.2rem\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.0625rem rgba(0, 0, 0, 0.1)\n\n font-size: var(--admonition-font-size)\n\n overflow: hidden\n page-break-inside: avoid\n\n // First element should have no margin, since the title has it.\n > :nth-child(2)\n margin-top: 0\n\n // Last item should have no margin, since we'll control that w/ padding\n > :last-child\n margin-bottom: 0\n\n.admonition p.admonition-title,\np.topic-title\n position: relative\n margin: 0 -0.5rem 0.5rem\n padding-left: 2rem\n padding-right: .5rem\n padding-top: .4rem\n padding-bottom: .4rem\n\n font-weight: 500\n font-size: var(--admonition-title-font-size)\n line-height: 1.3\n\n // Our fancy icon\n &::before\n content: \"\"\n position: absolute\n left: 0.5rem\n width: 1rem\n height: 1rem\n\n// Default styles\np.admonition-title\n background-color: var(--color-admonition-title-background)\n &::before\n background-color: var(--color-admonition-title)\n mask-image: var(--icon-admonition-default)\n mask-repeat: no-repeat\n\np.topic-title\n background-color: var(--color-topic-title-background)\n &::before\n background-color: var(--color-topic-title)\n mask-image: var(--icon-topic-default)\n mask-repeat: no-repeat\n\n//\n// Variants\n//\n.admonition\n border-left: 0.2rem solid var(--color-admonition-title)\n\n @each $type, $value in $admonitions\n &.#{$type}\n border-left-color: var(--color-admonition-title--#{$type})\n > .admonition-title\n background-color: var(--color-admonition-title-background--#{$type})\n &::before\n background-color: var(--color-admonition-title--#{$type})\n mask-image: var(--icon-#{nth($value, 2)})\n\n.admonition-todo > .admonition-title\n text-transform: uppercase\n","// This file stylizes the API documentation (stuff generated by autodoc). It's\n// deeply nested due to how autodoc structures the HTML without enough classes\n// to select the relevant items.\n\n// API docs!\ndl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)\n // Tweak the spacing of all the things!\n dd\n margin-left: 2rem\n > :first-child\n margin-top: 0.125rem\n > :last-child\n margin-bottom: 0.75rem\n\n // This is used for the arguments\n .field-list\n margin-bottom: 0.75rem\n\n // \"Headings\" (like \"Parameters\" and \"Return\")\n > dt\n text-transform: uppercase\n font-size: var(--font-size--small)\n\n dd:empty\n margin-bottom: 0.5rem\n dd > ul\n margin-left: -1.2rem\n > li\n > p:nth-child(2)\n margin-top: 0\n // When the last-empty-paragraph follows a paragraph, it doesn't need\n // to augument the existing spacing.\n > p + p:last-child:empty\n margin-top: 0\n margin-bottom: 0\n\n // Colorize the elements\n > dt\n color: var(--color-api-overall)\n\n.sig:not(.sig-inline)\n font-weight: bold\n\n font-size: var(--api-font-size)\n font-family: var(--font-stack--monospace)\n\n margin-left: -0.25rem\n margin-right: -0.25rem\n padding-top: 0.25rem\n padding-bottom: 0.25rem\n padding-right: 0.5rem\n\n // These are intentionally em, to properly match the font size.\n padding-left: 3em\n text-indent: -2.5em\n\n border-radius: 0.25rem\n\n background: var(--color-api-background)\n transition: background 100ms ease-out\n\n &:hover\n background: var(--color-api-background-hover)\n\n // adjust the size of the [source] link on the right.\n a.reference\n .viewcode-link\n font-weight: normal\n width: 4.25rem\n\nem.property\n font-style: normal\n &:first-child\n color: var(--color-api-keyword)\n.sig-name\n color: var(--color-api-name)\n.sig-prename\n font-weight: normal\n color: var(--color-api-pre-name)\n.sig-paren\n color: var(--color-api-paren)\n.sig-param\n font-style: normal\n\ndiv.versionadded,\ndiv.versionchanged,\ndiv.deprecated,\ndiv.versionremoved\n border-left: 0.1875rem solid\n border-radius: 0.125rem\n\n padding-left: 0.75rem\n\n p\n margin-top: 0.125rem\n margin-bottom: 0.125rem\n\ndiv.versionadded\n border-color: var(--color-api-added-border)\n .versionmodified\n color: var(--color-api-added)\n\ndiv.versionchanged\n border-color: var(--color-api-changed-border)\n .versionmodified\n color: var(--color-api-changed)\n\ndiv.deprecated\n border-color: var(--color-api-deprecated-border)\n .versionmodified\n color: var(--color-api-deprecated)\n\ndiv.versionremoved\n border-color: var(--color-api-removed-border)\n .versionmodified\n color: var(--color-api-removed)\n\n// Align the [docs] and [source] to the right.\n.viewcode-link, .viewcode-back\n float: right\n text-align: right\n",".line-block\n margin-top: 0.5rem\n margin-bottom: 0.75rem\n .line-block\n margin-top: 0rem\n margin-bottom: 0rem\n padding-left: 1rem\n","// Captions\narticle p.caption,\ntable > caption,\n.code-block-caption\n font-size: var(--font-size--small)\n text-align: center\n\n// Caption above a TOCTree\n.toctree-wrapper.compound\n .caption, :not(.caption) > .caption-text\n font-size: var(--font-size--small)\n text-transform: uppercase\n\n text-align: initial\n margin-bottom: 0\n\n > ul\n margin-top: 0\n margin-bottom: 0\n","// Inline code\ncode.literal, .sig-inline\n background: var(--color-inline-code-background)\n border-radius: 0.2em\n // Make the font smaller, and use padding to recover.\n font-size: var(--font-size--small--2)\n padding: 0.1em 0.2em\n\n pre.literal-block &\n font-size: inherit\n padding: 0\n\n p &\n border: 1px solid var(--color-background-border)\n\n.sig-inline\n font-family: var(--font-stack--monospace)\n\n// Code and Literal Blocks\n$code-spacing-vertical: 0.625rem\n$code-spacing-horizontal: 0.875rem\n\n// Wraps every literal block + line numbers.\ndiv[class*=\" highlight-\"],\ndiv[class^=\"highlight-\"]\n margin: 1em 0\n display: flex\n\n .table-wrapper\n margin: 0\n padding: 0\n\npre\n margin: 0\n padding: 0\n overflow: auto\n\n // Needed to have more specificity than pygments' \"pre\" selector. :(\n article[role=\"main\"] .highlight &\n line-height: 1.5\n\n &.literal-block,\n .highlight &\n font-size: var(--code-font-size)\n padding: $code-spacing-vertical $code-spacing-horizontal\n\n // Make it look like all the other blocks.\n &.literal-block\n margin-top: 1rem\n margin-bottom: 1rem\n\n border-radius: 0.2rem\n background-color: var(--color-code-background)\n color: var(--color-code-foreground)\n\n// All code is always contained in this.\n.highlight\n width: 100%\n border-radius: 0.2rem\n\n // Make line numbers and prompts un-selectable.\n .gp, span.linenos\n user-select: none\n pointer-events: none\n\n // Expand the line-highlighting.\n .hll\n display: block\n margin-left: -$code-spacing-horizontal\n margin-right: -$code-spacing-horizontal\n padding-left: $code-spacing-horizontal\n padding-right: $code-spacing-horizontal\n\n/* Make code block captions be nicely integrated */\n.code-block-caption\n display: flex\n padding: $code-spacing-vertical $code-spacing-horizontal\n\n border-radius: 0.25rem\n border-bottom-left-radius: 0\n border-bottom-right-radius: 0\n font-weight: 300\n border-bottom: 1px solid\n\n background-color: var(--color-code-background)\n color: var(--color-code-foreground)\n border-color: var(--color-background-border)\n\n + div[class]\n margin-top: 0\n pre\n border-top-left-radius: 0\n border-top-right-radius: 0\n\n// When `html_codeblock_linenos_style` is table.\n.highlighttable\n width: 100%\n display: block\n tbody\n display: block\n\n tr\n display: flex\n\n // Line numbers\n td.linenos\n background-color: var(--color-code-background)\n color: var(--color-code-foreground)\n padding: $code-spacing-vertical $code-spacing-horizontal\n padding-right: 0\n border-top-left-radius: 0.2rem\n border-bottom-left-radius: 0.2rem\n\n .linenodiv\n padding-right: $code-spacing-horizontal\n font-size: var(--code-font-size)\n box-shadow: -0.0625rem 0 var(--color-foreground-border) inset\n\n // Actual code\n td.code\n padding: 0\n display: block\n flex: 1\n overflow: hidden\n\n .highlight\n border-top-left-radius: 0\n border-bottom-left-radius: 0\n\n// When `html_codeblock_linenos_style` is inline.\n.highlight\n span.linenos\n display: inline-block\n padding-left: 0\n padding-right: $code-spacing-horizontal\n margin-right: $code-spacing-horizontal\n box-shadow: -0.0625rem 0 var(--color-foreground-border) inset\n","// Inline Footnote Reference\n.footnote-reference\n font-size: var(--font-size--small--4)\n vertical-align: super\n\n// Definition list, listing the content of each note.\n// docutils <= 0.17\ndl.footnote.brackets\n font-size: var(--font-size--small)\n color: var(--color-foreground-secondary)\n\n display: grid\n grid-template-columns: max-content auto\n dt\n margin: 0\n > .fn-backref\n margin-left: 0.25rem\n\n &:after\n content: \":\"\n\n .brackets\n &:before\n content: \"[\"\n &:after\n content: \"]\"\n\n dd\n margin: 0\n padding: 0 1rem\n\n// docutils >= 0.18\naside.footnote\n font-size: var(--font-size--small)\n color: var(--color-foreground-secondary)\n\naside.footnote > span,\ndiv.citation > span\n float: left\n font-weight: 500\n padding-right: 0.25rem\n\naside.footnote > *:not(span),\ndiv.citation > p\n margin-left: 2rem\n","//\n// Figures\n//\nimg\n box-sizing: border-box\n max-width: 100%\n height: auto\n\narticle\n figure, .figure\n border-radius: 0.2rem\n\n margin: 0\n :last-child\n margin-bottom: 0\n\n .align-left\n float: left\n clear: left\n margin: 0 1rem 1rem\n\n .align-right\n float: right\n clear: right\n margin: 0 1rem 1rem\n\n .align-default,\n .align-center\n display: block\n text-align: center\n margin-left: auto\n margin-right: auto\n\n // WELL, table needs to be stylised like a table.\n table.align-default\n display: table\n text-align: initial\n",".genindex-jumpbox, .domainindex-jumpbox\n border-top: 1px solid var(--color-background-border)\n border-bottom: 1px solid var(--color-background-border)\n padding: 0.25rem\n\n.genindex-section, .domainindex-section\n h2\n margin-top: 0.75rem\n margin-bottom: 0.5rem\n ul\n margin-top: 0\n margin-bottom: 0\n","ul,\nol\n padding-left: 1.2rem\n\n // Space lists out like paragraphs\n margin-top: 1rem\n margin-bottom: 1rem\n // reduce margins within li.\n li\n > p:first-child\n margin-top: 0.25rem\n margin-bottom: 0.25rem\n\n > p:last-child\n margin-top: 0.25rem\n\n > ul,\n > ol\n margin-top: 0.5rem\n margin-bottom: 0.5rem\n\nol\n &.arabic\n list-style: decimal\n &.loweralpha\n list-style: lower-alpha\n &.upperalpha\n list-style: upper-alpha\n &.lowerroman\n list-style: lower-roman\n &.upperroman\n list-style: upper-roman\n\n// Don't space lists out when they're \"simple\" or in a `.. toctree::`\n.simple,\n.toctree-wrapper\n li\n > ul,\n > ol\n margin-top: 0\n margin-bottom: 0\n\n// Definition Lists\n.field-list,\n.option-list,\ndl:not([class]),\ndl.simple,\ndl.footnote,\ndl.glossary\n dt\n font-weight: 500\n margin-top: 0.25rem\n + dt\n margin-top: 0\n\n .classifier::before\n content: \":\"\n margin-left: 0.2rem\n margin-right: 0.2rem\n\n dd\n > p:first-child,\n ul\n margin-top: 0.125rem\n\n ul\n margin-bottom: 0.125rem\n",".math-wrapper\n width: 100%\n overflow-x: auto\n\ndiv.math\n position: relative\n text-align: center\n\n .headerlink,\n &:focus .headerlink\n display: none\n\n &:hover .headerlink\n display: inline-block\n\n span.eqno\n position: absolute\n right: 0.5rem\n top: 50%\n transform: translate(0, -50%)\n z-index: 1\n","// Abbreviations\nabbr[title]\n cursor: help\n\n// \"Problematic\" content, as identified by Sphinx\n.problematic\n color: var(--color-problematic)\n\n// Keyboard / Mouse \"instructions\"\nkbd:not(.compound)\n margin: 0 0.2rem\n padding: 0 0.2rem\n border-radius: 0.2rem\n border: 1px solid var(--color-foreground-border)\n color: var(--color-foreground-primary)\n vertical-align: text-bottom\n\n font-size: var(--font-size--small--3)\n display: inline-block\n\n box-shadow: 0 0.0625rem 0 rgba(0, 0, 0, 0.2), inset 0 0 0 0.125rem var(--color-background-primary)\n\n background-color: var(--color-background-secondary)\n\n// Blockquote\nblockquote\n border-left: 4px solid var(--color-background-border)\n background: var(--color-background-secondary)\n\n margin-left: 0\n margin-right: 0\n padding: 0.5rem 1rem\n\n .attribution\n font-weight: 600\n text-align: right\n\n &.pull-quote,\n &.highlights\n font-size: 1.25em\n\n &.epigraph,\n &.pull-quote\n border-left-width: 0\n border-radius: 0.5rem\n\n &.highlights\n border-left-width: 0\n background: transparent\n\n// Center align embedded-in-text images\np .reference img\n vertical-align: middle\n","p.rubric\n line-height: 1.25\n font-weight: bold\n font-size: 1.125em\n\n // For Numpy-style documentation that's got rubrics within it.\n // https://github.com/pradyunsg/furo/discussions/505\n dd &\n line-height: inherit\n font-weight: inherit\n\n font-size: var(--font-size--small)\n text-transform: uppercase\n","article .sidebar\n float: right\n clear: right\n width: 30%\n\n margin-left: 1rem\n margin-right: 0\n\n border-radius: 0.2rem\n background-color: var(--color-background-secondary)\n border: var(--color-background-border) 1px solid\n\n > *\n padding-left: 1rem\n padding-right: 1rem\n\n > ul, > ol // lists need additional padding, because bullets.\n padding-left: 2.2rem\n\n .sidebar-title\n margin: 0\n padding: 0.5rem 1rem\n border-bottom: var(--color-background-border) 1px solid\n\n font-weight: 500\n\n// TODO: subtitle\n// TODO: dedicated variables?\n",".table-wrapper\n width: 100%\n overflow-x: auto\n margin-top: 1rem\n margin-bottom: 0.5rem\n padding: 0.2rem 0.2rem 0.75rem\n\ntable.docutils\n border-radius: 0.2rem\n border-spacing: 0\n border-collapse: collapse\n\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.0625rem rgba(0, 0, 0, 0.1)\n\n th\n background: var(--color-table-header-background)\n\n td,\n th\n // Space things out properly\n padding: 0 0.25rem\n\n // Get the borders looking just-right.\n border-left: 1px solid var(--color-table-border)\n border-right: 1px solid var(--color-table-border)\n border-bottom: 1px solid var(--color-table-border)\n\n p\n margin: 0.25rem\n\n &:first-child\n border-left: none\n &:last-child\n border-right: none\n\n // MyST-parser tables set these classes for control of column alignment\n &.text-left\n text-align: left\n &.text-right\n text-align: right\n &.text-center\n text-align: center\n",":target\n scroll-margin-top: 2.5rem\n\n@media (max-width: $full-width - $sidebar-width)\n :target\n scroll-margin-top: calc(2.5rem + var(--header-height))\n\n // When a heading is selected\n section > span:target\n scroll-margin-top: calc(2.8rem + var(--header-height))\n\n// Permalinks\n.headerlink\n font-weight: 100\n user-select: none\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\ndl dt,\np.caption,\nfigcaption p,\ntable > caption,\n.code-block-caption\n > .headerlink\n margin-left: 0.5rem\n visibility: hidden\n &:hover > .headerlink\n visibility: visible\n\n // Don't change to link-like, if someone adds the contents directive.\n > .toc-backref\n color: inherit\n text-decoration-line: none\n\n// Figure and table captions are special.\nfigure:hover > figcaption > p > .headerlink,\ntable:hover > caption > .headerlink\n visibility: visible\n\n:target >, // Regular section[id] style anchors\nspan:target ~ // Non-regular span[id] style \"extra\" anchors\n h1,\n h2,\n h3,\n h4,\n h5,\n h6\n &:nth-of-type(1)\n background-color: var(--color-highlight-on-target)\n // .headerlink\n // visibility: visible\n code.literal\n background-color: transparent\n\ntable:target > caption,\nfigure:target\n background-color: var(--color-highlight-on-target)\n\n// Inline page contents\n.this-will-duplicate-information-and-it-is-still-useful-here li :target\n background-color: var(--color-highlight-on-target)\n\n// Code block permalinks\n.literal-block-wrapper:target .code-block-caption\n background-color: var(--color-highlight-on-target)\n\n// When a definition list item is selected\n//\n// There isn't really an alternative to !important here, due to the\n// high-specificity of API documentation's selector.\ndt:target\n background-color: var(--color-highlight-on-target) !important\n\n// When a footnote reference is selected\n.footnote > dt:target + dd,\n.footnote-reference:target\n background-color: var(--color-highlight-on-target)\n",".guilabel\n background-color: var(--color-guilabel-background)\n border: 1px solid var(--color-guilabel-border)\n color: var(--color-guilabel-text)\n\n padding: 0 0.3em\n border-radius: 0.5em\n font-size: 0.9em\n","// This file contains the styles used for stylizing the footer that's shown\n// below the content.\n\nfooter\n font-size: var(--font-size--small)\n display: flex\n flex-direction: column\n\n margin-top: 2rem\n\n// Bottom of page information\n.bottom-of-page\n display: flex\n align-items: center\n justify-content: space-between\n\n margin-top: 1rem\n padding-top: 1rem\n padding-bottom: 1rem\n\n color: var(--color-foreground-secondary)\n border-top: 1px solid var(--color-background-border)\n\n line-height: 1.5\n\n @media (max-width: $content-width)\n text-align: center\n flex-direction: column-reverse\n gap: 0.25rem\n\n .left-details\n font-size: var(--font-size--small)\n\n .right-details\n display: flex\n flex-direction: column\n gap: 0.25rem\n text-align: right\n\n .icons\n display: flex\n justify-content: flex-end\n gap: 0.25rem\n font-size: 1rem\n\n a\n text-decoration: none\n\n svg,\n img\n font-size: 1.125rem\n height: 1em\n width: 1em\n\n// Next/Prev page information\n.related-pages\n a\n display: flex\n align-items: center\n\n text-decoration: none\n &:hover .page-info .title\n text-decoration: underline\n color: var(--color-link)\n text-decoration-color: var(--color-link-underline)\n\n svg.furo-related-icon,\n svg.furo-related-icon > use\n flex-shrink: 0\n\n color: var(--color-foreground-border)\n\n width: 0.75rem\n height: 0.75rem\n margin: 0 0.5rem\n\n &.next-page\n max-width: 50%\n\n float: right\n clear: right\n text-align: right\n\n &.prev-page\n max-width: 50%\n\n float: left\n clear: left\n\n svg\n transform: rotate(180deg)\n\n.page-info\n display: flex\n flex-direction: column\n overflow-wrap: anywhere\n\n .next-page &\n align-items: flex-end\n\n .context\n display: flex\n align-items: center\n\n padding-bottom: 0.1rem\n\n color: var(--color-foreground-muted)\n font-size: var(--font-size--small)\n text-decoration: none\n","// This file contains the styles for the contents of the left sidebar, which\n// contains the navigation tree, logo, search etc.\n\n////////////////////////////////////////////////////////////////////////////////\n// Brand on top of the scrollable tree.\n////////////////////////////////////////////////////////////////////////////////\n.sidebar-brand\n display: flex\n flex-direction: column\n flex-shrink: 0\n\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n text-decoration: none\n\n.sidebar-brand-text\n color: var(--color-sidebar-brand-text)\n overflow-wrap: break-word\n margin: var(--sidebar-item-spacing-vertical) 0\n font-size: 1.5rem\n\n.sidebar-logo-container\n margin: var(--sidebar-item-spacing-vertical) 0\n\n.sidebar-logo\n margin: 0 auto\n display: block\n max-width: 100%\n\n////////////////////////////////////////////////////////////////////////////////\n// Search\n////////////////////////////////////////////////////////////////////////////////\n.sidebar-search-container\n display: flex\n align-items: center\n margin-top: var(--sidebar-search-space-above)\n\n position: relative\n\n background: var(--color-sidebar-search-background)\n &:hover,\n &:focus-within\n background: var(--color-sidebar-search-background--focus)\n\n &::before\n content: \"\"\n position: absolute\n left: var(--sidebar-item-spacing-horizontal)\n width: var(--sidebar-search-icon-size)\n height: var(--sidebar-search-icon-size)\n\n background-color: var(--color-sidebar-search-icon)\n mask-image: var(--icon-search)\n\n.sidebar-search\n box-sizing: border-box\n\n border: none\n border-top: 1px solid var(--color-sidebar-search-border)\n border-bottom: 1px solid var(--color-sidebar-search-border)\n\n padding-top: var(--sidebar-search-input-spacing-vertical)\n padding-bottom: var(--sidebar-search-input-spacing-vertical)\n padding-right: var(--sidebar-search-input-spacing-horizontal)\n padding-left: calc(var(--sidebar-item-spacing-horizontal) + var(--sidebar-search-input-spacing-horizontal) + var(--sidebar-search-icon-size))\n\n width: 100%\n\n color: var(--color-sidebar-search-foreground)\n background: transparent\n z-index: 10\n\n &:focus\n outline: none\n\n &::placeholder\n font-size: var(--sidebar-search-input-font-size)\n\n//\n// Hide Search Matches link\n//\n#searchbox .highlight-link\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal) 0\n margin: 0\n text-align: center\n\n a\n color: var(--color-sidebar-search-icon)\n font-size: var(--font-size--small--2)\n\n////////////////////////////////////////////////////////////////////////////////\n// Structure/Skeleton of the navigation tree (left)\n////////////////////////////////////////////////////////////////////////////////\n.sidebar-tree\n font-size: var(--sidebar-item-font-size)\n margin-top: var(--sidebar-tree-space-above)\n margin-bottom: var(--sidebar-item-spacing-vertical)\n\n ul\n padding: 0\n margin-top: 0\n margin-bottom: 0\n\n display: flex\n flex-direction: column\n\n list-style: none\n\n li\n position: relative\n margin: 0\n\n > ul\n margin-left: var(--sidebar-item-spacing-horizontal)\n\n .icon\n color: var(--color-sidebar-link-text)\n\n .reference\n box-sizing: border-box\n color: var(--color-sidebar-link-text)\n\n // Fill the parent.\n display: inline-block\n line-height: var(--sidebar-item-line-height)\n text-decoration: none\n\n // Don't allow long words to cause wrapping.\n overflow-wrap: anywhere\n\n height: 100%\n width: 100%\n\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n\n &:hover\n color: var(--color-sidebar-link-text)\n background: var(--color-sidebar-item-background--hover)\n\n // Add a nice little \"external-link\" arrow here.\n &.external::after\n content: url('data:image/svg+xml,')\n margin: 0 0.25rem\n vertical-align: middle\n color: var(--color-sidebar-link-text)\n\n // Make the current page reference bold.\n .current-page > .reference\n font-weight: bold\n\n label\n position: absolute\n top: 0\n right: 0\n height: var(--sidebar-item-height)\n width: var(--sidebar-expander-width)\n\n cursor: pointer\n user-select: none\n\n display: flex\n justify-content: center\n align-items: center\n\n .caption, :not(.caption) > .caption-text\n font-size: var(--sidebar-caption-font-size)\n color: var(--color-sidebar-caption-text)\n\n font-weight: bold\n text-transform: uppercase\n\n margin: var(--sidebar-caption-space-above) 0 0 0\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n\n // If it has children, add a bit more padding to wrap the content to avoid\n // overlapping with the