latch 2.53.7__tar.gz → 2.53.7.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- latch-2.53.7.dev2/PKG-INFO +43 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/ldata/_transfer/upload.py +1 -1
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/ldata/type.py +1 -1
- latch-2.53.7.dev2/latch.egg-info/PKG-INFO +43 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch.egg-info/SOURCES.txt +18 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch.egg-info/requires.txt +2 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/main.py +13 -5
- latch-2.53.7.dev2/latch_cli/services/cp/download/main.py +208 -0
- latch-2.53.7.dev2/latch_cli/services/cp/download/worker.py +121 -0
- latch-2.53.7.dev2/latch_cli/services/cp/http_utils.py +91 -0
- latch-2.53.7.dev2/latch_cli/services/cp/main.py +101 -0
- latch-2.53.7.dev2/latch_cli/services/cp/upload/main.py +124 -0
- latch-2.53.7.dev2/latch_cli/services/cp/upload/worker.py +223 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/cp/utils.py +22 -4
- latch-2.53.7.dev2/latch_cli/services/init/__pycache__/__init__.cpython-310.pyc +0 -0
- latch-2.53.7.dev2/latch_cli/services/init/__pycache__/__init__.cpython-311.pyc +0 -0
- latch-2.53.7.dev2/latch_cli/services/init/__pycache__/__init__.cpython-38.pyc +0 -0
- latch-2.53.7.dev2/latch_cli/services/init/__pycache__/__init__.cpython-39.pyc +0 -0
- latch-2.53.7.dev2/latch_cli/services/init/__pycache__/init.cpython-310.pyc +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/__pycache__/init.cpython-311.pyc +0 -0
- latch-2.53.7.dev2/latch_cli/services/init/__pycache__/init.cpython-38.pyc +0 -0
- latch-2.53.7.dev2/latch_cli/services/init/__pycache__/init.cpython-39.pyc +0 -0
- latch-2.53.7.dev2/latch_cli/services/init/assemble_and_sort/__pycache__/__init__.cpython-310.pyc +0 -0
- latch-2.53.7.dev2/latch_cli/services/init/example_conda/__pycache__/__init__.cpython-310.pyc +0 -0
- latch-2.53.7.dev2/latch_cli/services/init/example_nf_integration/latch_metadata/__pycache__/__init__.cpython-311.pyc +0 -0
- latch-2.53.7.dev2/latch_cli/services/init/example_r/__pycache__/__init__.cpython-310.pyc +0 -0
- latch-2.53.7.dev2/latch_cli/services/init/template/__pycache__/__init__.cpython-310.pyc +0 -0
- latch-2.53.7.dev2/latch_cli/tui/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/utils/__init__.py +2 -1
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/utils/path.py +27 -21
- {latch-2.53.7 → latch-2.53.7.dev2}/setup.py +4 -1
- latch-2.53.7.dev2/tests/__init__.py +0 -0
- latch-2.53.7/PKG-INFO +0 -13
- latch-2.53.7/latch.egg-info/PKG-INFO +0 -13
- latch-2.53.7/latch_cli/services/cp/main.py +0 -106
- latch-2.53.7/latch_cli/services/init/__pycache__/__init__.cpython-311.pyc +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/LICENSE +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/MANIFEST.in +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/README.md +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/account.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/executions.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/functions/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/functions/messages.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/functions/operators.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/functions/secrets.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/ldata/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/ldata/_transfer/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/ldata/_transfer/download.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/ldata/_transfer/manager.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/ldata/_transfer/node.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/ldata/_transfer/progress.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/ldata/_transfer/remote_copy.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/ldata/_transfer/throttle.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/ldata/_transfer/utils.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/ldata/path.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/registry/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/registry/project.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/registry/record.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/registry/table.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/registry/types.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/registry/upstream_types/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/registry/upstream_types/types.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/registry/upstream_types/values.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/registry/utils.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/resources/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/resources/conditional.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/resources/dynamic.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/resources/launch_plan.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/resources/map_tasks.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/resources/reference_workflow.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/resources/tasks.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/resources/workflow.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/types/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/types/directory.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/types/file.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/types/glob.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/types/json.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/types/metadata.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/types/utils.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/utils.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/verified/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/verified/deseq2.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/verified/mafft.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/verified/pathway.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/verified/rnaseq.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch/verified/trim_galore.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch.egg-info/dependency_links.txt +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch.egg-info/entry_points.txt +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch.egg-info/top_level.txt +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/auth/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/auth/csrf.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/auth/oauth2.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/auth/pkce.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/auth/utils.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/centromere/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/centromere/ctx.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/centromere/utils.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/click_utils.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/constants.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/docker_utils/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/exceptions/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/exceptions/cache.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/exceptions/errors.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/exceptions/handler.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/exceptions/traceback.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/menus.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/nextflow/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/nextflow/config.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/nextflow/utils.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/nextflow/workflow.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/cp/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/cp/autocomplete.py +0 -0
- {latch-2.53.7/latch_cli/services/execute → latch-2.53.7.dev2/latch_cli/services/cp/download}/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/cp/glob.py +0 -0
- {latch-2.53.7/latch_cli/services/test_data → latch-2.53.7.dev2/latch_cli/services/cp/upload}/__init__.py +0 -0
- {latch-2.53.7/latch_cli/snakemake → latch-2.53.7.dev2/latch_cli/services/execute}/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/execute/main.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/execute/utils.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/get.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/get_executions.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/get_params.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/assemble_and_sort/.env +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/assemble_and_sort/LICENSE +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/assemble_and_sort/README.md +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/assemble_and_sort/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/assemble_and_sort/assemble.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/assemble_and_sort/sort.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/assemble_and_sort/system-requirements.txt +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/common/.dockerignore +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_conda/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_conda/conda_task.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_conda/environment.yaml +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_docker/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_docker/task.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_nfcore/Dockerfile +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_nfcore/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_nfcore/task.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_r/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_r/environment.R +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_r/r_task.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_snakemake/.latch/latch_entrypoint +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_snakemake/Dockerfile +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_snakemake/Snakefile +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_snakemake/config.yaml +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_snakemake/environment.yaml +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_snakemake/latch_metadata.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_snakemake/scripts/plot-quals.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/example_snakemake/version +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/init.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/template/LICENSE +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/template/README.md +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/template/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/init/template/task.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/launch.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/local_dev.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/local_dev_old.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/login.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/ls.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/mkdir.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/move.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/preview.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/register/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/register/constants.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/register/register.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/register/utils.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/rm.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/stop_pod.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/sync.py +0 -0
- {latch-2.53.7/latch_cli/snakemake/config → latch-2.53.7.dev2/latch_cli/services/test_data}/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/test_data/ls.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/test_data/remove.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/test_data/upload.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/test_data/utils.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/services/workspace.py +0 -0
- {latch-2.53.7/latch_cli/tui → latch-2.53.7.dev2/latch_cli/snakemake}/__init__.py +0 -0
- {latch-2.53.7/tests → latch-2.53.7.dev2/latch_cli/snakemake/config}/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/snakemake/config/parser.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/snakemake/config/utils.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/snakemake/serialize.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/snakemake/serialize_utils.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/snakemake/single_task_snakemake.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/snakemake/utils.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/snakemake/workflow.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/tinyrequests.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/latch_cli/workflow_config.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/pyproject.toml +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/setup.cfg +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/tests/cp/__init__.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/tests/fixtures.py +0 -0
- {latch-2.53.7 → latch-2.53.7.dev2}/tests/test_ls.py +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: latch
|
|
3
|
+
Version: 2.53.7.dev2
|
|
4
|
+
Summary: The Latch SDK
|
|
5
|
+
Author-email: kenny@latch.bio
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Requires-Python: >=3.8,<3.12
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: kubernetes>=24.2.0
|
|
13
|
+
Requires-Dist: pyjwt>=0.2.0
|
|
14
|
+
Requires-Dist: requests>=2.28.1
|
|
15
|
+
Requires-Dist: click>=8.0
|
|
16
|
+
Requires-Dist: docker>=7.1.0
|
|
17
|
+
Requires-Dist: paramiko>=3.2.0
|
|
18
|
+
Requires-Dist: cryptography<43.0.0
|
|
19
|
+
Requires-Dist: scp>=0.14.0
|
|
20
|
+
Requires-Dist: boto3>=1.26.0
|
|
21
|
+
Requires-Dist: tqdm>=4.63.0
|
|
22
|
+
Requires-Dist: lytekit==0.15.12
|
|
23
|
+
Requires-Dist: lytekitplugins-pods==0.6.2
|
|
24
|
+
Requires-Dist: typing-extensions==4.7.1
|
|
25
|
+
Requires-Dist: apscheduler==3.9.1
|
|
26
|
+
Requires-Dist: gql==3.4.0
|
|
27
|
+
Requires-Dist: graphql-core==3.2.3
|
|
28
|
+
Requires-Dist: requests-toolbelt==0.10.1
|
|
29
|
+
Requires-Dist: latch-sdk-gql==0.0.6
|
|
30
|
+
Requires-Dist: latch-sdk-config==0.0.4
|
|
31
|
+
Requires-Dist: python-dateutil>=2.8
|
|
32
|
+
Requires-Dist: GitPython==3.1.40
|
|
33
|
+
Requires-Dist: aioconsole==0.6.1
|
|
34
|
+
Requires-Dist: asyncssh==2.13.2
|
|
35
|
+
Requires-Dist: websockets==11.0.3
|
|
36
|
+
Requires-Dist: watchfiles==0.19.0
|
|
37
|
+
Requires-Dist: uvloop==0.19.0
|
|
38
|
+
Requires-Dist: aiohttp==3.9.5
|
|
39
|
+
Provides-Extra: snakemake
|
|
40
|
+
Requires-Dist: snakemake<7.30.2,>=7.18.0; extra == "snakemake"
|
|
41
|
+
Requires-Dist: pulp<2.8,>=2.0; extra == "snakemake"
|
|
42
|
+
Provides-Extra: pandas
|
|
43
|
+
Requires-Dist: pandas>=2.0.0; extra == "pandas"
|
|
@@ -71,7 +71,7 @@ def upload(
|
|
|
71
71
|
dest_data = node_data.data[dest]
|
|
72
72
|
|
|
73
73
|
if not (dest_data.exists() or dest_data.is_direct_parent()) and not create_parents:
|
|
74
|
-
raise LatchPathError("no such Latch file or directory", dest)
|
|
74
|
+
raise LatchPathError("no such Latch file or directory", dest, node_data.acc_id)
|
|
75
75
|
|
|
76
76
|
dest_is_dir = dest_data.type in {
|
|
77
77
|
LDataNodeType.account_root,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: latch
|
|
3
|
+
Version: 2.53.7.dev2
|
|
4
|
+
Summary: The Latch SDK
|
|
5
|
+
Author-email: kenny@latch.bio
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Requires-Python: >=3.8,<3.12
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: kubernetes>=24.2.0
|
|
13
|
+
Requires-Dist: pyjwt>=0.2.0
|
|
14
|
+
Requires-Dist: requests>=2.28.1
|
|
15
|
+
Requires-Dist: click>=8.0
|
|
16
|
+
Requires-Dist: docker>=7.1.0
|
|
17
|
+
Requires-Dist: paramiko>=3.2.0
|
|
18
|
+
Requires-Dist: cryptography<43.0.0
|
|
19
|
+
Requires-Dist: scp>=0.14.0
|
|
20
|
+
Requires-Dist: boto3>=1.26.0
|
|
21
|
+
Requires-Dist: tqdm>=4.63.0
|
|
22
|
+
Requires-Dist: lytekit==0.15.12
|
|
23
|
+
Requires-Dist: lytekitplugins-pods==0.6.2
|
|
24
|
+
Requires-Dist: typing-extensions==4.7.1
|
|
25
|
+
Requires-Dist: apscheduler==3.9.1
|
|
26
|
+
Requires-Dist: gql==3.4.0
|
|
27
|
+
Requires-Dist: graphql-core==3.2.3
|
|
28
|
+
Requires-Dist: requests-toolbelt==0.10.1
|
|
29
|
+
Requires-Dist: latch-sdk-gql==0.0.6
|
|
30
|
+
Requires-Dist: latch-sdk-config==0.0.4
|
|
31
|
+
Requires-Dist: python-dateutil>=2.8
|
|
32
|
+
Requires-Dist: GitPython==3.1.40
|
|
33
|
+
Requires-Dist: aioconsole==0.6.1
|
|
34
|
+
Requires-Dist: asyncssh==2.13.2
|
|
35
|
+
Requires-Dist: websockets==11.0.3
|
|
36
|
+
Requires-Dist: watchfiles==0.19.0
|
|
37
|
+
Requires-Dist: uvloop==0.19.0
|
|
38
|
+
Requires-Dist: aiohttp==3.9.5
|
|
39
|
+
Provides-Extra: snakemake
|
|
40
|
+
Requires-Dist: snakemake<7.30.2,>=7.18.0; extra == "snakemake"
|
|
41
|
+
Requires-Dist: pulp<2.8,>=2.0; extra == "snakemake"
|
|
42
|
+
Provides-Extra: pandas
|
|
43
|
+
Requires-Dist: pandas>=2.0.0; extra == "pandas"
|
|
@@ -103,15 +103,28 @@ latch_cli/services/workspace.py
|
|
|
103
103
|
latch_cli/services/cp/__init__.py
|
|
104
104
|
latch_cli/services/cp/autocomplete.py
|
|
105
105
|
latch_cli/services/cp/glob.py
|
|
106
|
+
latch_cli/services/cp/http_utils.py
|
|
106
107
|
latch_cli/services/cp/main.py
|
|
107
108
|
latch_cli/services/cp/utils.py
|
|
109
|
+
latch_cli/services/cp/download/__init__.py
|
|
110
|
+
latch_cli/services/cp/download/main.py
|
|
111
|
+
latch_cli/services/cp/download/worker.py
|
|
112
|
+
latch_cli/services/cp/upload/__init__.py
|
|
113
|
+
latch_cli/services/cp/upload/main.py
|
|
114
|
+
latch_cli/services/cp/upload/worker.py
|
|
108
115
|
latch_cli/services/execute/__init__.py
|
|
109
116
|
latch_cli/services/execute/main.py
|
|
110
117
|
latch_cli/services/execute/utils.py
|
|
111
118
|
latch_cli/services/init/__init__.py
|
|
112
119
|
latch_cli/services/init/init.py
|
|
120
|
+
latch_cli/services/init/__pycache__/__init__.cpython-310.pyc
|
|
113
121
|
latch_cli/services/init/__pycache__/__init__.cpython-311.pyc
|
|
122
|
+
latch_cli/services/init/__pycache__/__init__.cpython-38.pyc
|
|
123
|
+
latch_cli/services/init/__pycache__/__init__.cpython-39.pyc
|
|
124
|
+
latch_cli/services/init/__pycache__/init.cpython-310.pyc
|
|
114
125
|
latch_cli/services/init/__pycache__/init.cpython-311.pyc
|
|
126
|
+
latch_cli/services/init/__pycache__/init.cpython-38.pyc
|
|
127
|
+
latch_cli/services/init/__pycache__/init.cpython-39.pyc
|
|
115
128
|
latch_cli/services/init/assemble_and_sort/.env
|
|
116
129
|
latch_cli/services/init/assemble_and_sort/LICENSE
|
|
117
130
|
latch_cli/services/init/assemble_and_sort/README.md
|
|
@@ -119,18 +132,22 @@ latch_cli/services/init/assemble_and_sort/__init__.py
|
|
|
119
132
|
latch_cli/services/init/assemble_and_sort/assemble.py
|
|
120
133
|
latch_cli/services/init/assemble_and_sort/sort.py
|
|
121
134
|
latch_cli/services/init/assemble_and_sort/system-requirements.txt
|
|
135
|
+
latch_cli/services/init/assemble_and_sort/__pycache__/__init__.cpython-310.pyc
|
|
122
136
|
latch_cli/services/init/common/.dockerignore
|
|
123
137
|
latch_cli/services/init/example_conda/__init__.py
|
|
124
138
|
latch_cli/services/init/example_conda/conda_task.py
|
|
125
139
|
latch_cli/services/init/example_conda/environment.yaml
|
|
140
|
+
latch_cli/services/init/example_conda/__pycache__/__init__.cpython-310.pyc
|
|
126
141
|
latch_cli/services/init/example_docker/__init__.py
|
|
127
142
|
latch_cli/services/init/example_docker/task.py
|
|
143
|
+
latch_cli/services/init/example_nf_integration/latch_metadata/__pycache__/__init__.cpython-311.pyc
|
|
128
144
|
latch_cli/services/init/example_nfcore/Dockerfile
|
|
129
145
|
latch_cli/services/init/example_nfcore/__init__.py
|
|
130
146
|
latch_cli/services/init/example_nfcore/task.py
|
|
131
147
|
latch_cli/services/init/example_r/__init__.py
|
|
132
148
|
latch_cli/services/init/example_r/environment.R
|
|
133
149
|
latch_cli/services/init/example_r/r_task.py
|
|
150
|
+
latch_cli/services/init/example_r/__pycache__/__init__.cpython-310.pyc
|
|
134
151
|
latch_cli/services/init/example_snakemake/Dockerfile
|
|
135
152
|
latch_cli/services/init/example_snakemake/Snakefile
|
|
136
153
|
latch_cli/services/init/example_snakemake/config.yaml
|
|
@@ -143,6 +160,7 @@ latch_cli/services/init/template/LICENSE
|
|
|
143
160
|
latch_cli/services/init/template/README.md
|
|
144
161
|
latch_cli/services/init/template/__init__.py
|
|
145
162
|
latch_cli/services/init/template/task.py
|
|
163
|
+
latch_cli/services/init/template/__pycache__/__init__.cpython-310.pyc
|
|
146
164
|
latch_cli/services/register/__init__.py
|
|
147
165
|
latch_cli/services/register/constants.py
|
|
148
166
|
latch_cli/services/register/register.py
|
|
@@ -4,15 +4,13 @@ import os
|
|
|
4
4
|
import sys
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from textwrap import dedent
|
|
7
|
-
from typing import Callable, List, Optional, Tuple, TypeVar, Union
|
|
7
|
+
from typing import Callable, List, Literal, Optional, Tuple, TypeVar, Union
|
|
8
8
|
|
|
9
9
|
import click
|
|
10
10
|
from packaging.version import parse as parse_version
|
|
11
11
|
from typing_extensions import ParamSpec
|
|
12
12
|
|
|
13
13
|
import latch_cli.click_utils
|
|
14
|
-
from latch.ldata._transfer.progress import Progress as _Progress
|
|
15
|
-
from latch_cli.click_utils import EnumChoice
|
|
16
14
|
from latch_cli.exceptions.handler import CrashHandler
|
|
17
15
|
from latch_cli.services.cp.autocomplete import complete as cp_complete
|
|
18
16
|
from latch_cli.services.cp.autocomplete import remote_complete
|
|
@@ -693,7 +691,7 @@ LDATA COMMANDS
|
|
|
693
691
|
@click.option(
|
|
694
692
|
"--progress",
|
|
695
693
|
help="Type of progress information to show while copying",
|
|
696
|
-
type=
|
|
694
|
+
type=click.Choice(["none", "total", "tasks"]),
|
|
697
695
|
default="tasks",
|
|
698
696
|
show_default=True,
|
|
699
697
|
)
|
|
@@ -705,6 +703,14 @@ LDATA COMMANDS
|
|
|
705
703
|
default=False,
|
|
706
704
|
show_default=True,
|
|
707
705
|
)
|
|
706
|
+
@click.option(
|
|
707
|
+
"--force",
|
|
708
|
+
"-f",
|
|
709
|
+
help="Don't ask to confirm when overwriting files",
|
|
710
|
+
is_flag=True,
|
|
711
|
+
default=False,
|
|
712
|
+
show_default=True,
|
|
713
|
+
)
|
|
708
714
|
@click.option(
|
|
709
715
|
"--no-glob",
|
|
710
716
|
"-G",
|
|
@@ -727,8 +733,9 @@ LDATA COMMANDS
|
|
|
727
733
|
def cp(
|
|
728
734
|
src: List[str],
|
|
729
735
|
dest: str,
|
|
730
|
-
progress:
|
|
736
|
+
progress: Literal["none", "total", "tasks"],
|
|
731
737
|
verbose: bool,
|
|
738
|
+
force: bool,
|
|
732
739
|
no_glob: bool,
|
|
733
740
|
cores: Optional[int] = None,
|
|
734
741
|
chunk_size_mib: Optional[int] = None,
|
|
@@ -747,6 +754,7 @@ def cp(
|
|
|
747
754
|
src,
|
|
748
755
|
dest,
|
|
749
756
|
progress=progress,
|
|
757
|
+
force=force,
|
|
750
758
|
verbose=verbose,
|
|
751
759
|
expand_globs=not no_glob,
|
|
752
760
|
cores=cores,
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import queue
|
|
3
|
+
import shutil
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from textwrap import dedent
|
|
7
|
+
from typing import Dict, List, Literal, Optional, TypedDict
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
import requests
|
|
11
|
+
import requests.adapters
|
|
12
|
+
import tqdm
|
|
13
|
+
import uvloop
|
|
14
|
+
|
|
15
|
+
from ....utils import get_auth_header, human_readable_time, with_si_suffix
|
|
16
|
+
from ....utils.path import normalize_path
|
|
17
|
+
from ..glob import expand_pattern
|
|
18
|
+
from .worker import Work, run_workers
|
|
19
|
+
|
|
20
|
+
http_session = requests.Session()
|
|
21
|
+
|
|
22
|
+
_adapter = requests.adapters.HTTPAdapter(
|
|
23
|
+
max_retries=requests.adapters.Retry(
|
|
24
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
|
25
|
+
backoff_factor=1,
|
|
26
|
+
allowed_methods=["GET", "PUT", "POST"],
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
http_session.mount("https://", _adapter)
|
|
30
|
+
http_session.mount("http://", _adapter)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class GetSignedUrlData(TypedDict):
|
|
34
|
+
url: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class GetSignedUrlsRecursiveData(TypedDict):
|
|
38
|
+
urls: Dict[str, str]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def download(
|
|
42
|
+
srcs: List[str],
|
|
43
|
+
dest: Path,
|
|
44
|
+
progress: Literal["none", "total", "tasks"],
|
|
45
|
+
verbose: bool,
|
|
46
|
+
force: bool,
|
|
47
|
+
expand_globs: bool,
|
|
48
|
+
cores: Optional[int],
|
|
49
|
+
chunk_size_mib: Optional[int],
|
|
50
|
+
):
|
|
51
|
+
if cores is None:
|
|
52
|
+
cores = 4
|
|
53
|
+
if chunk_size_mib is None:
|
|
54
|
+
chunk_size_mib = 16
|
|
55
|
+
|
|
56
|
+
start = time.monotonic()
|
|
57
|
+
|
|
58
|
+
if not dest.parent.exists():
|
|
59
|
+
click.secho(
|
|
60
|
+
f"Invalid copy destination {dest}. Parent directory {dest.parent} does"
|
|
61
|
+
" not exist.",
|
|
62
|
+
fg="red",
|
|
63
|
+
)
|
|
64
|
+
raise click.exceptions.Exit(1)
|
|
65
|
+
|
|
66
|
+
if len(srcs) > 1 and not (dest.exists() and dest.is_dir()):
|
|
67
|
+
click.secho(
|
|
68
|
+
f"Copy destination {dest} does not exist. Multi-source copies must write to"
|
|
69
|
+
" a pre-existing directory.",
|
|
70
|
+
fg="red",
|
|
71
|
+
)
|
|
72
|
+
raise click.exceptions.Exit(1)
|
|
73
|
+
|
|
74
|
+
from latch.ldata.path import _get_node_data
|
|
75
|
+
from latch.ldata.type import LDataNodeType
|
|
76
|
+
|
|
77
|
+
all_node_data = _get_node_data(*srcs)
|
|
78
|
+
work_queue = asyncio.Queue[Work]()
|
|
79
|
+
total = 0
|
|
80
|
+
|
|
81
|
+
if expand_globs:
|
|
82
|
+
new_srcs = []
|
|
83
|
+
for src in srcs:
|
|
84
|
+
new_srcs.extend(expand_pattern(src))
|
|
85
|
+
|
|
86
|
+
srcs = new_srcs
|
|
87
|
+
|
|
88
|
+
# todo(ayush): parallelize
|
|
89
|
+
for src in srcs:
|
|
90
|
+
node_data = all_node_data.data[src]
|
|
91
|
+
normalized = normalize_path(src)
|
|
92
|
+
|
|
93
|
+
can_have_children = node_data.type in {
|
|
94
|
+
LDataNodeType.account_root,
|
|
95
|
+
LDataNodeType.dir,
|
|
96
|
+
LDataNodeType.mount,
|
|
97
|
+
LDataNodeType.mount_gcp,
|
|
98
|
+
LDataNodeType.mount_azure,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if not can_have_children:
|
|
102
|
+
endpoint = "https://nucleus.latch.bio/ldata/get-signed-url"
|
|
103
|
+
else:
|
|
104
|
+
endpoint = "https://nucleus.latch.bio/ldata/get-signed-urls-recursive"
|
|
105
|
+
|
|
106
|
+
res = http_session.post(
|
|
107
|
+
endpoint,
|
|
108
|
+
headers={"Authorization": get_auth_header()},
|
|
109
|
+
json={"path": normalized},
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
json = res.json()
|
|
113
|
+
|
|
114
|
+
if not can_have_children:
|
|
115
|
+
gsud: GetSignedUrlData = json["data"]
|
|
116
|
+
total += 1
|
|
117
|
+
|
|
118
|
+
work_dest = dest
|
|
119
|
+
if dest.exists() and dest.is_dir():
|
|
120
|
+
work_dest = dest / node_data.name
|
|
121
|
+
|
|
122
|
+
if (
|
|
123
|
+
work_dest.exists()
|
|
124
|
+
and not force
|
|
125
|
+
and not click.confirm(
|
|
126
|
+
f"Copy destination path {work_dest} already exists and its contents"
|
|
127
|
+
" may be overwritten. Proceed?"
|
|
128
|
+
)
|
|
129
|
+
):
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
work_dest.unlink(missing_ok=True)
|
|
134
|
+
work_queue.put_nowait(Work(gsud["url"], work_dest, chunk_size_mib))
|
|
135
|
+
except OSError:
|
|
136
|
+
click.secho(
|
|
137
|
+
f"Cannot write file to {work_dest} - directory exists.", fg="red"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
else:
|
|
141
|
+
gsurd: GetSignedUrlsRecursiveData = json["data"]
|
|
142
|
+
total += len(gsurd["urls"])
|
|
143
|
+
|
|
144
|
+
work_dest = dest
|
|
145
|
+
if dest.exists() and not normalized.endswith("/"):
|
|
146
|
+
work_dest = dest / node_data.name
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
work_dest.exists()
|
|
150
|
+
and work_dest.is_dir()
|
|
151
|
+
and not force
|
|
152
|
+
and not click.confirm(
|
|
153
|
+
f"Copy destination path {work_dest} already exists and its contents"
|
|
154
|
+
" may be overwritten. Proceed?"
|
|
155
|
+
)
|
|
156
|
+
):
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
for rel, url in gsurd["urls"].items():
|
|
160
|
+
res = work_dest / rel
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
res.parent.mkdir(exist_ok=True, parents=True)
|
|
164
|
+
if res.is_dir():
|
|
165
|
+
click.secho(
|
|
166
|
+
f"Cannot write file to {work_dest / rel} - directory"
|
|
167
|
+
" exists.",
|
|
168
|
+
fg="red",
|
|
169
|
+
)
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
work_queue.put_nowait(Work(url, work_dest / rel, chunk_size_mib))
|
|
173
|
+
except (NotADirectoryError, FileExistsError):
|
|
174
|
+
click.secho(
|
|
175
|
+
f"Cannot write file to {work_dest / rel} - upstream file"
|
|
176
|
+
" exists.",
|
|
177
|
+
fg="red",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
tbar = tqdm.tqdm(
|
|
181
|
+
total=total,
|
|
182
|
+
leave=False,
|
|
183
|
+
colour="green",
|
|
184
|
+
smoothing=0,
|
|
185
|
+
unit="B",
|
|
186
|
+
unit_scale=True,
|
|
187
|
+
disable=progress == "none",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
num_workers = min(total, cores)
|
|
191
|
+
uvloop.install()
|
|
192
|
+
|
|
193
|
+
loop = uvloop.new_event_loop()
|
|
194
|
+
res = loop.run_until_complete(
|
|
195
|
+
run_workers(work_queue, num_workers, tbar, progress != "none", verbose)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
total_bytes = sum(res)
|
|
199
|
+
|
|
200
|
+
tbar.clear()
|
|
201
|
+
total_time = time.monotonic() - start
|
|
202
|
+
|
|
203
|
+
if progress != "none":
|
|
204
|
+
click.echo(dedent(f"""\
|
|
205
|
+
{click.style("Download Complete", fg="green")}
|
|
206
|
+
{click.style("Time Elapsed:", fg="blue")} {human_readable_time(total_time)}
|
|
207
|
+
{click.style("Files Downloaded:", fg="blue")} {total} ({with_si_suffix(total_bytes)})\
|
|
208
|
+
"""))
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import queue
|
|
4
|
+
import shutil
|
|
5
|
+
import time
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from http import HTTPStatus
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Awaitable, List
|
|
10
|
+
|
|
11
|
+
import aiohttp
|
|
12
|
+
import tqdm
|
|
13
|
+
import uvloop
|
|
14
|
+
|
|
15
|
+
from latch_cli.services.cp.utils import chunked
|
|
16
|
+
|
|
17
|
+
from ....constants import Units
|
|
18
|
+
from ..http_utils import RetryClientSession
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Work:
|
|
23
|
+
url: str
|
|
24
|
+
dest: Path
|
|
25
|
+
chunk_size_mib: int = 5
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def download_chunk(
|
|
29
|
+
sess: aiohttp.ClientSession,
|
|
30
|
+
url: str,
|
|
31
|
+
fd: int,
|
|
32
|
+
index: int,
|
|
33
|
+
chunk_size: int,
|
|
34
|
+
pbar: tqdm.tqdm,
|
|
35
|
+
):
|
|
36
|
+
start = index * chunk_size
|
|
37
|
+
end = start + chunk_size - 1
|
|
38
|
+
|
|
39
|
+
res = await sess.get(url, headers={"Range": f"bytes={start}-{end}"})
|
|
40
|
+
content = await res.read()
|
|
41
|
+
pbar.update(os.pwrite(fd, content, start))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def worker(
|
|
45
|
+
work_queue: asyncio.Queue[Work],
|
|
46
|
+
tbar: tqdm.tqdm,
|
|
47
|
+
show_task_progress: bool,
|
|
48
|
+
print_file_on_completion: bool,
|
|
49
|
+
) -> int:
|
|
50
|
+
pbar = tqdm.tqdm(
|
|
51
|
+
total=0,
|
|
52
|
+
leave=False,
|
|
53
|
+
smoothing=0,
|
|
54
|
+
unit="B",
|
|
55
|
+
unit_scale=True,
|
|
56
|
+
disable=not show_task_progress,
|
|
57
|
+
)
|
|
58
|
+
total_bytes = 0
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
async with RetryClientSession(read_timeout=90, conn_timeout=10) as sess:
|
|
62
|
+
while True:
|
|
63
|
+
try:
|
|
64
|
+
work: Work = work_queue.get_nowait()
|
|
65
|
+
except asyncio.QueueEmpty:
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
pbar.reset()
|
|
69
|
+
pbar.desc = work.dest.name
|
|
70
|
+
|
|
71
|
+
res = await sess.get(work.url, headers={"Range": "bytes=0-0"})
|
|
72
|
+
|
|
73
|
+
# s3 throws a REQUESTED_RANGE_NOT_SATISFIABLE if the file is empty
|
|
74
|
+
if res.status == 416:
|
|
75
|
+
total_size = 0
|
|
76
|
+
else:
|
|
77
|
+
content_range = res.headers["Content-Range"]
|
|
78
|
+
total_size = int(content_range.replace("bytes 0-0/", ""))
|
|
79
|
+
|
|
80
|
+
assert total_size is not None
|
|
81
|
+
|
|
82
|
+
total_bytes += total_size
|
|
83
|
+
pbar.total = total_size
|
|
84
|
+
|
|
85
|
+
chunk_size = work.chunk_size_mib * Units.MiB
|
|
86
|
+
|
|
87
|
+
with work.dest.open("wb") as f:
|
|
88
|
+
coros: List[Awaitable] = []
|
|
89
|
+
|
|
90
|
+
cur = 0
|
|
91
|
+
while cur * chunk_size < total_size:
|
|
92
|
+
coros.append(
|
|
93
|
+
download_chunk(
|
|
94
|
+
sess, work.url, f.fileno(), cur, chunk_size, pbar
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
cur += 1
|
|
98
|
+
|
|
99
|
+
await asyncio.gather(*coros)
|
|
100
|
+
|
|
101
|
+
if print_file_on_completion:
|
|
102
|
+
pbar.write(str(work.dest))
|
|
103
|
+
|
|
104
|
+
tbar.update(1)
|
|
105
|
+
|
|
106
|
+
return total_bytes
|
|
107
|
+
finally:
|
|
108
|
+
pbar.clear()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
async def run_workers(
|
|
112
|
+
work_queue: asyncio.Queue[Work],
|
|
113
|
+
num_workers: int,
|
|
114
|
+
tbar: tqdm.tqdm,
|
|
115
|
+
show_task_progress: bool,
|
|
116
|
+
print_file_on_completion: bool,
|
|
117
|
+
) -> List[int]:
|
|
118
|
+
return await asyncio.gather(*[
|
|
119
|
+
worker(work_queue, tbar, show_task_progress, print_file_on_completion)
|
|
120
|
+
for _ in range(num_workers)
|
|
121
|
+
])
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from http import HTTPStatus
|
|
3
|
+
from typing import Awaitable, Callable, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
import aiohttp
|
|
6
|
+
import aiohttp.typedefs
|
|
7
|
+
from typing_extensions import ParamSpec
|
|
8
|
+
|
|
9
|
+
P = ParamSpec("P")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RetriesExhaustedException(RuntimeError): ...
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RateLimitExceeded(RuntimeError): ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RetryClientSession(aiohttp.ClientSession):
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
status_list: Optional[List[HTTPStatus]] = None,
|
|
22
|
+
retries: int = 10,
|
|
23
|
+
backoff: float = 0.1,
|
|
24
|
+
*args,
|
|
25
|
+
**kwargs,
|
|
26
|
+
):
|
|
27
|
+
|
|
28
|
+
self.status_list = (
|
|
29
|
+
status_list
|
|
30
|
+
if status_list is not None
|
|
31
|
+
else [
|
|
32
|
+
HTTPStatus.TOO_MANY_REQUESTS, # 429
|
|
33
|
+
HTTPStatus.INTERNAL_SERVER_ERROR, # 500
|
|
34
|
+
HTTPStatus.BAD_GATEWAY, # 502
|
|
35
|
+
HTTPStatus.SERVICE_UNAVAILABLE, # 503
|
|
36
|
+
HTTPStatus.GATEWAY_TIMEOUT, # 504
|
|
37
|
+
]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
self.retries = retries
|
|
41
|
+
self.backoff = backoff
|
|
42
|
+
|
|
43
|
+
self.semas: Dict[aiohttp.typedefs.StrOrURL, asyncio.BoundedSemaphore] = {
|
|
44
|
+
"https://nucleus.latch.bio/ldata/start-upload": asyncio.BoundedSemaphore(2),
|
|
45
|
+
"https://nucleus.latch.bio/ldata/end-upload": asyncio.BoundedSemaphore(2),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
super().__init__(*args, **kwargs)
|
|
49
|
+
|
|
50
|
+
async def _request(
|
|
51
|
+
self,
|
|
52
|
+
method: str,
|
|
53
|
+
str_or_url: aiohttp.typedefs.StrOrURL,
|
|
54
|
+
**kwargs,
|
|
55
|
+
) -> aiohttp.ClientResponse:
|
|
56
|
+
sema = self.semas.get(str_or_url)
|
|
57
|
+
|
|
58
|
+
error: Optional[Exception] = None
|
|
59
|
+
last_res: Optional[aiohttp.ClientResponse] = None
|
|
60
|
+
|
|
61
|
+
cur = 0
|
|
62
|
+
while cur < self.retries:
|
|
63
|
+
if cur > 0:
|
|
64
|
+
await asyncio.sleep(max(self.backoff * 2**cur, 10))
|
|
65
|
+
|
|
66
|
+
cur += 1
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
if sema is None:
|
|
70
|
+
res = await super()._request(method, str_or_url, **kwargs)
|
|
71
|
+
else:
|
|
72
|
+
async with sema:
|
|
73
|
+
res = await super()._request(method, str_or_url, **kwargs)
|
|
74
|
+
|
|
75
|
+
if res.status in self.status_list:
|
|
76
|
+
last_res = res
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
return res
|
|
80
|
+
except Exception as e:
|
|
81
|
+
error = e
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
if last_res is not None:
|
|
85
|
+
return last_res
|
|
86
|
+
|
|
87
|
+
if error is not None:
|
|
88
|
+
raise error
|
|
89
|
+
|
|
90
|
+
# we'll never get here but putting here anyway so the type checker is happy
|
|
91
|
+
raise RetriesExhaustedException
|