├── .github └── workflows │ └── python-package.yml ├── .gitignore ├── .gitlab-ci.yml ├── .readthedocs.yaml ├── CONTRIBUTORS_NOTICE.md ├── LICENCE ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── conf.py ├── images │ ├── logo.jpg │ └── logo.png ├── index.rst ├── installation.rst ├── make.bat ├── modules.rst ├── quickstart.rst ├── requirements.txt ├── setup.rst ├── sevenbridges.http.rst ├── sevenbridges.meta.rst ├── sevenbridges.models.compound.billing.rst ├── sevenbridges.models.compound.files.rst ├── sevenbridges.models.compound.jobs.rst ├── sevenbridges.models.compound.limits.rst ├── sevenbridges.models.compound.markers.rst ├── sevenbridges.models.compound.projects.rst ├── sevenbridges.models.compound.rst ├── sevenbridges.models.compound.tasks.rst ├── sevenbridges.models.compound.volumes.rst ├── sevenbridges.models.rst ├── sevenbridges.rst ├── sevenbridges.tests.rst └── sevenbridges.transfer.rst ├── requirements-dev.txt ├── requirements.txt ├── setup.cfg ├── setup.py ├── sevenbridges ├── __init__.py ├── api.py ├── config.py ├── decorators.py ├── errors.py ├── http │ ├── __init__.py │ ├── client.py │ └── error_handlers.py ├── meta │ ├── __init__.py │ ├── collection.py │ ├── comp_mutable_dict.py │ ├── data.py │ ├── fields.py │ ├── resource.py │ └── transformer.py ├── models │ ├── __init__.py │ ├── actions.py │ ├── app.py │ ├── async_jobs.py │ ├── automation.py │ ├── billing_analysis_breakdown.py │ ├── billing_egress_breakdown.py │ ├── billing_group.py │ ├── billing_storage_breakdown.py │ ├── bulk.py │ ├── compound │ │ ├── __init__.py │ │ ├── analysis_cost.py │ │ ├── analysis_cost_breakdown.py │ │ ├── billing │ │ │ ├── __init__.py │ │ │ ├── invoice_period.py │ │ │ ├── project_breakdown.py │ │ │ └── task_breakdown.py │ │ ├── egress_cost.py │ │ ├── error.py │ │ ├── files │ │ │ ├── __init__.py │ │ │ ├── download_info.py │ │ │ ├── file_origin.py │ │ │ ├── file_storage.py │ │ │ └── metadata.py │ │ ├── import_result.py │ │ ├── jobs │ │ │ ├── __init__.py │ │ │ ├── job.py │ │ │ ├── job_docker.py │ │ │ ├── job_instance.py │ │ │ ├── job_instance_disk.py │ │ │ └── job_log.py │ │ ├── limits │ │ │ ├── __init__.py │ │ │ └── rate.py │ │ ├── markers │ │ │ ├── __init__.py │ │ │ └── position.py │ │ ├── measurement.py │ │ ├── price.py │ │ ├── price_breakdown.py │ │ ├── projects │ │ │ ├── __init__.py │ │ │ ├── permissions.py │ │ │ └── settings.py │ │ ├── tasks │ │ │ ├── __init__.py │ │ │ ├── batch_by.py │ │ │ ├── batch_group.py │ │ │ ├── execution_status.py │ │ │ ├── input.py │ │ │ └── output.py │ │ └── volumes │ │ │ ├── __init__.py │ │ │ ├── import_destination.py │ │ │ ├── properties.py │ │ │ ├── service.py │ │ │ ├── volume_file.py │ │ │ ├── volume_object.py │ │ │ └── volume_prefix.py │ ├── dataset.py │ ├── division.py │ ├── drs_import.py │ ├── endpoints.py │ ├── enums.py │ ├── execution_details.py │ ├── file.py │ ├── invoice.py │ ├── link.py │ ├── marker.py │ ├── member.py │ ├── project.py │ ├── rate_limit.py │ ├── storage_export.py │ ├── storage_import.py │ ├── task.py │ ├── team.py │ ├── team_member.py │ ├── user.py │ └── volume.py ├── transfer │ ├── __init__.py │ ├── download.py │ ├── upload.py │ └── utils.py └── version.py ├── sonar-project.properties └── tests ├── __init__.py ├── conftest.py ├── providers.py ├── test_actions.py ├── test_apps.py ├── test_async_jobs.py ├── test_automations.py ├── test_billing_groups.py ├── test_config.py ├── test_datasets.py ├── test_decorators.py ├── test_divisions.py ├── test_drs_import.py ├── test_endpoints_rate.py ├── test_error_handlers.py ├── test_exports.py ├── test_files.py ├── test_imports.py ├── test_marker.py ├── test_pagination.py ├── test_projects.py ├── test_tasks.py ├── test_teams.py ├── test_transformer.py ├── test_upload.py ├── test_users.py ├── test_utils.py ├── test_volumes.py └── verifiers.py /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | pull_request: 8 | branches: 9 | - develop 10 | release: 11 | types: 12 | - published 13 | 14 | jobs: 15 | tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | python-version: 20 | - 3.8 21 | - 3.9 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Set up Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v2 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | - name: Install dependencies 29 | run: | 30 | pip install -r requirements-dev.txt 31 | - name: Test with pytest 32 | run: | 33 | flake8 34 | pytest --verbose --cov-config setup.cfg 35 | 36 | release: 37 | needs: tests 38 | if: github.event_name == 'release' && github.event.action == 'published' 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v2 42 | - name: Set up Python 43 | uses: actions/setup-python@v2 44 | with: 45 | python-version: 3.9 46 | - name: Build and publish 47 | env: 48 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 49 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 50 | run: | 51 | pip install wheel twine 52 | export PACKAGE_VERSION=${GITHUB_REF:10} 53 | echo "__version__ = '$PACKAGE_VERSION'" > sevenbridges/version.py 54 | python setup.py sdist bdist_wheel 55 | twine upload dist/* 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # Distribution / packaging # 8 | ############################ 9 | build/ 10 | dist/ 11 | *.egg-info/ 12 | *.egg 13 | 14 | # Installer logs # 15 | ################## 16 | pip-log.txt 17 | pip-delete-this-directory.txt 18 | 19 | # Unit test / coverage reports # 20 | ################################ 21 | .tox/ 22 | .coverage 23 | .coverage.* 24 | coverage_report/ 25 | .cache 26 | .pytest_cache 27 | nosetests.xml 28 | coverage.xml 29 | htmlcov 30 | 31 | # Sphinx documentation # 32 | ######################## 33 | docs/_build/ 34 | 35 | # IPython Notebook # 36 | #################### 37 | .ipynb_checkpoints 38 | 39 | # Distutils # 40 | ############# 41 | MANIFEST 42 | 43 | # OS generated files # 44 | ###################### 45 | .DS_Store* 46 | ehthumbs.db 47 | Icon? 48 | Thumbs.db 49 | 50 | # Editor Files # 51 | ################ 52 | *~ 53 | *.swp 54 | 55 | # IntelliJ specific files/directories # 56 | ####################################### 57 | .idea 58 | *.ipr 59 | *.iws 60 | *.iml 61 | 62 | # Eclipse specific files/directories # 63 | ###################################### 64 | .project 65 | .pydevproject 66 | .python-version 67 | .settings 68 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: python:3.9-slim-buster 2 | 3 | stages: 4 | - test 5 | - sonar 6 | 7 | .test: &test 8 | stage: test 9 | before_script: 10 | - python --version 11 | - pip install -r requirements-dev.txt 12 | - export PYTHONPATH=$(pwd):$PYTHONPATH 13 | script: 14 | - flake8 15 | - pytest --verbose --cov-config setup.cfg 16 | artifacts: 17 | paths: 18 | - test-report/* 19 | coverage: '/TOTAL.*\s+(\d+\%)/' 20 | 21 | # Test with python 3.6 22 | test:3.6: 23 | <<: *test 24 | image: python:3.6-slim-buster 25 | 26 | # Test with python 3.7 27 | test:3.7: 28 | <<: *test 29 | image: python:3.7-slim-buster 30 | 31 | # Test with python 3.8 32 | test:3.8: 33 | <<: *test 34 | image: python:3.8-slim-buster 35 | 36 | # Test with python 3.9 37 | test:3.9: 38 | <<: *test 39 | image: python:3.9-slim-buster 40 | 41 | sonar: 42 | stage: sonar 43 | image: emeraldsquad/sonar-scanner:2.2.0 44 | script: infinity sonar scanner 45 | allow_failure: true 46 | dependencies: 47 | - test:3.9 48 | rules: 49 | - when: always 50 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | version: 2 5 | 6 | build: 7 | os: ubuntu-22.04 8 | tools: 9 | python: "3.9" 10 | 11 | # Build documentation in the "docs/" directory with Sphinx 12 | sphinx: 13 | configuration: docs/conf.py 14 | 15 | formats: 16 | - pdf 17 | 18 | python: 19 | install: 20 | - requirements: docs/requirements.txt 21 | -------------------------------------------------------------------------------- /CONTRIBUTORS_NOTICE.md: -------------------------------------------------------------------------------- 1 | Notice to Contributors to Seven Bridges Open Source Projects 2 | ------------------------------------------------------------ 3 | 4 | We require contributors to our open-source projects to sign a Seven 5 | Bridges Contributor Agreement (SBCA). The SBCA doesn’t require you to 6 | lose rights to your work, but only to share the copyright with Seven 7 | Bridges jointly, and when applicable to grant Seven Bridges and other 8 | collaborators and users of the project a patent license. 9 | 10 | This is a common arrangement in larger open source projects, and 11 | provides several benefits to both the project and its contributors. The 12 | SBCA allows Seven Bridges to defend the project from hostile 13 | intellectual property litigation--for example, if a contributor or their 14 | employer attempted to assert control of the project, or rescind their 15 | contribution. Seven Bridges could not do this without the consolidated 16 | ownership of the copyright that the SBCA provides. Since the Agreement 17 | establishes joint ownership of copyright, you and any other contributor 18 | will have the right to whatever you want to with your contribution, 19 | including contribute it to other open source projects or distribute it 20 | under proprietary licenses. If we distribute your contribution in any 21 | way, the SBCA obliges us to also make it available under an open source 22 | license approved by the Free Software Foundation (FSF) or Open Source 23 | Initiative (OSI). 24 | 25 | If you’re contributing on behalf of your employer, make sure that anyone 26 | signing the agreement is authorized to do so by your employer. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENCE 2 | include README.md 3 | -------------------------------------------------------------------------------- /docs/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbg/sevenbridges-python/ed3de94bab744a33f614111c1845af68b5433c54/docs/images/logo.jpg -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbg/sevenbridges-python/ed3de94bab744a33f614111c1845af68b5433c54/docs/images/logo.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. sbg documentation master file, created by 2 | sphinx-quickstart on Mon Jan 18 19:03:18 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | 7 | 8 | sevenbridges-python documentation! 9 | ================================== 10 | 11 | sevenbridges-python is a `Python `_ library that provides an interface for the `Seven Bridges Platform `_ and `Cancer Genomics Cloud `_ public APIs. 12 | 13 | The Seven Bridges Platform is a cloud-based environment for conducting bioinformatic analyses. It is a central hub for teams to store, analyze, and jointly interpret their bioinformatic data. The Platform co-locates analysis pipelines alongside the largest genomic datasets to optimize processing, allocating storage and compute resources on demand. 14 | 15 | The The Cancer Genomics Cloud (CGC), powered by Seven Bridges, is also a cloud-based computation environment. It was built as one of three pilot systems funded by the National Cancer Institute to explore the paradigm of colocalizing massive genomics datasets, like The Cancer Genomics Atlas (TCGA), alongside secure and scalable computational resources to analyze them. The CGC makes more than a petabyte of multi-dimensional data available immediately to authorized researchers. You can add your own data to analyze alongside TCGA using predefined analytical workflows or your own tools. 16 | 17 | Content 18 | ------- 19 | 20 | .. toctree:: 21 | :maxdepth: 2 22 | 23 | installation 24 | quickstart 25 | sevenbridges 26 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | The easiest way to install sevenbridges-python is using pip. 5 | :: 6 | 7 | $ pip install sevenbridges-python 8 | 9 | 10 | Get the Code 11 | ============ 12 | 13 | sevenbridges-python is actively developed on GitHub, where the `code `_ is always available. 14 | 15 | The easiest way to obtain the source is to clone the public repository: 16 | :: 17 | 18 | $ git clone git://github.com/sbg/sevenbridges-python.git 19 | 20 | Once you have a copy of the source, you can embed it in your Python package, 21 | or install it into your site-packages by invoking: 22 | :: 23 | 24 | $ python setup.py install 25 | 26 | If you are interested in reviewing this documentation locally, clone the repository and 27 | invoke: 28 | 29 | :: 30 | $ make html 31 | 32 | from the docs folder. 33 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | sevenbridges 2 | ============ 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | sevenbridges 8 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx==5.3.0 2 | sphinx_rtd_theme==0.5.1 3 | -------------------------------------------------------------------------------- /docs/setup.rst: -------------------------------------------------------------------------------- 1 | setup module 2 | ============ 3 | 4 | .. automodule:: setup 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/sevenbridges.http.rst: -------------------------------------------------------------------------------- 1 | sevenbridges\.http package 2 | ========================== 3 | 4 | .. automodule:: sevenbridges.http 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | sevenbridges\.http\.client module 13 | --------------------------------- 14 | 15 | .. automodule:: sevenbridges.http.client 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | sevenbridges\.http\.error\_handlers module 21 | ------------------------------------------ 22 | 23 | .. automodule:: sevenbridges.http.error_handlers 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/sevenbridges.meta.rst: -------------------------------------------------------------------------------- 1 | sevenbridges\.meta package 2 | ========================== 3 | 4 | .. automodule:: sevenbridges.meta 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | sevenbridges\.meta\.collection module 13 | ------------------------------------- 14 | 15 | .. automodule:: sevenbridges.meta.collection 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | sevenbridges\.meta\.comp\_mutable\_dict module 21 | ---------------------------------------------- 22 | 23 | .. automodule:: sevenbridges.meta.comp_mutable_dict 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | sevenbridges\.meta\.data module 29 | ------------------------------- 30 | 31 | .. automodule:: sevenbridges.meta.data 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | sevenbridges\.meta\.fields module 37 | --------------------------------- 38 | 39 | .. automodule:: sevenbridges.meta.fields 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | sevenbridges\.meta\.resource module 45 | ----------------------------------- 46 | 47 | .. automodule:: sevenbridges.meta.resource 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | sevenbridges\.meta\.transformer module 53 | -------------------------------------- 54 | 55 | .. automodule:: sevenbridges.meta.transformer 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | 61 | -------------------------------------------------------------------------------- /docs/sevenbridges.models.compound.billing.rst: -------------------------------------------------------------------------------- 1 | sevenbridges\.models\.compound\.billing package 2 | =============================================== 3 | 4 | .. automodule:: sevenbridges.models.compound.billing 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | sevenbridges\.models\.compound\.billing\.invoice\_period module 13 | --------------------------------------------------------------- 14 | 15 | .. automodule:: sevenbridges.models.compound.billing.invoice_period 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | sevenbridges\.models\.compound\.billing\.project\_breakdown module 21 | ------------------------------------------------------------------ 22 | 23 | .. automodule:: sevenbridges.models.compound.billing.project_breakdown 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | sevenbridges\.models\.compound\.billing\.task\_breakdown module 29 | --------------------------------------------------------------- 30 | 31 | .. automodule:: sevenbridges.models.compound.billing.task_breakdown 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | 37 | -------------------------------------------------------------------------------- /docs/sevenbridges.models.compound.files.rst: -------------------------------------------------------------------------------- 1 | sevenbridges\.models\.compound\.files package 2 | ============================================= 3 | 4 | .. automodule:: sevenbridges.models.compound.files 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | sevenbridges\.models\.compound\.files\.download\_info module 13 | ------------------------------------------------------------ 14 | 15 | .. automodule:: sevenbridges.models.compound.files.download_info 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | sevenbridges\.models\.compound\.files\.file\_origin module 21 | ---------------------------------------------------------- 22 | 23 | .. automodule:: sevenbridges.models.compound.files.file_origin 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | sevenbridges\.models\.compound\.files\.file\_storage module 29 | ----------------------------------------------------------- 30 | 31 | .. automodule:: sevenbridges.models.compound.files.file_storage 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | sevenbridges\.models\.compound\.files\.metadata module 37 | ------------------------------------------------------ 38 | 39 | .. automodule:: sevenbridges.models.compound.files.metadata 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/sevenbridges.models.compound.jobs.rst: -------------------------------------------------------------------------------- 1 | sevenbridges\.models\.compound\.jobs package 2 | ============================================ 3 | 4 | .. automodule:: sevenbridges.models.compound.jobs 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | sevenbridges\.models\.compound\.jobs\.job module 13 | ------------------------------------------------ 14 | 15 | .. automodule:: sevenbridges.models.compound.jobs.job 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | sevenbridges\.models\.compound\.jobs\.job\_docker module 21 | -------------------------------------------------------- 22 | 23 | .. automodule:: sevenbridges.models.compound.jobs.job_docker 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | sevenbridges\.models\.compound\.jobs\.job\_instance module 29 | ---------------------------------------------------------- 30 | 31 | .. automodule:: sevenbridges.models.compound.jobs.job_instance 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | sevenbridges\.models\.compound\.jobs\.job\_instance\_disk module 37 | ---------------------------------------------------------------- 38 | 39 | .. automodule:: sevenbridges.models.compound.jobs.job_instance_disk 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | sevenbridges\.models\.compound\.jobs\.job\_log module 45 | ----------------------------------------------------- 46 | 47 | .. automodule:: sevenbridges.models.compound.jobs.job_log 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | 53 | -------------------------------------------------------------------------------- /docs/sevenbridges.models.compound.limits.rst: -------------------------------------------------------------------------------- 1 | sevenbridges\.models\.compound\.limits package 2 | ============================================== 3 | 4 | .. automodule:: sevenbridges.models.compound.limits 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | sevenbridges\.models\.compound\.limits\.rate module 13 | --------------------------------------------------- 14 | 15 | .. automodule:: sevenbridges.models.compound.limits.rate 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/sevenbridges.models.compound.markers.rst: -------------------------------------------------------------------------------- 1 | sevenbridges\.models\.compound\.markers package 2 | =============================================== 3 | 4 | .. automodule:: sevenbridges.models.compound.markers 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | sevenbridges\.models\.compound\.markers\.position module 13 | -------------------------------------------------------- 14 | 15 | .. automodule:: sevenbridges.models.compound.markers.position 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/sevenbridges.models.compound.projects.rst: -------------------------------------------------------------------------------- 1 | sevenbridges\.models\.compound\.projects package 2 | ================================================ 3 | 4 | .. automodule:: sevenbridges.models.compound.projects 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | sevenbridges\.models\.compound\.projects\.permissions module 13 | ------------------------------------------------------------ 14 | 15 | .. automodule:: sevenbridges.models.compound.projects.permissions 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | sevenbridges\.models\.compound\.projects\.settings module 21 | --------------------------------------------------------- 22 | 23 | .. automodule:: sevenbridges.models.compound.projects.settings 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/sevenbridges.models.compound.rst: -------------------------------------------------------------------------------- 1 | sevenbridges\.models\.compound package 2 | ====================================== 3 | 4 | .. automodule:: sevenbridges.models.compound 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Subpackages 10 | ----------- 11 | 12 | .. toctree:: 13 | 14 | sevenbridges.models.compound.billing 15 | sevenbridges.models.compound.files 16 | sevenbridges.models.compound.jobs 17 | sevenbridges.models.compound.limits 18 | sevenbridges.models.compound.markers 19 | sevenbridges.models.compound.projects 20 | sevenbridges.models.compound.tasks 21 | sevenbridges.models.compound.volumes 22 | 23 | Submodules 24 | ---------- 25 | 26 | sevenbridges\.models\.compound\.error module 27 | -------------------------------------------- 28 | 29 | .. automodule:: sevenbridges.models.compound.error 30 | :members: 31 | :undoc-members: 32 | :show-inheritance: 33 | 34 | sevenbridges\.models\.compound\.price module 35 | -------------------------------------------- 36 | 37 | .. automodule:: sevenbridges.models.compound.price 38 | :members: 39 | :undoc-members: 40 | :show-inheritance: 41 | 42 | sevenbridges\.models\.compound\.price\_breakdown module 43 | ------------------------------------------------------- 44 | 45 | .. automodule:: sevenbridges.models.compound.price_breakdown 46 | :members: 47 | :undoc-members: 48 | :show-inheritance: 49 | 50 | 51 | -------------------------------------------------------------------------------- /docs/sevenbridges.models.compound.tasks.rst: -------------------------------------------------------------------------------- 1 | sevenbridges\.models\.compound\.tasks package 2 | ============================================= 3 | 4 | .. automodule:: sevenbridges.models.compound.tasks 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | sevenbridges\.models\.compound\.tasks\.batch\_by module 13 | ------------------------------------------------------- 14 | 15 | .. automodule:: sevenbridges.models.compound.tasks.batch_by 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | sevenbridges\.models\.compound\.tasks\.batch\_group module 21 | ---------------------------------------------------------- 22 | 23 | .. automodule:: sevenbridges.models.compound.tasks.batch_group 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | sevenbridges\.models\.compound\.tasks\.execution\_status module 29 | --------------------------------------------------------------- 30 | 31 | .. automodule:: sevenbridges.models.compound.tasks.execution_status 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | sevenbridges\.models\.compound\.tasks\.input module 37 | --------------------------------------------------- 38 | 39 | .. automodule:: sevenbridges.models.compound.tasks.input 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | sevenbridges\.models\.compound\.tasks\.output module 45 | ---------------------------------------------------- 46 | 47 | .. automodule:: sevenbridges.models.compound.tasks.output 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | 53 | -------------------------------------------------------------------------------- /docs/sevenbridges.models.compound.volumes.rst: -------------------------------------------------------------------------------- 1 | sevenbridges\.models\.compound\.volumes package 2 | =============================================== 3 | 4 | .. automodule:: sevenbridges.models.compound.volumes 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | sevenbridges\.models\.compound\.volumes\.import\_destination module 13 | ------------------------------------------------------------------- 14 | 15 | .. automodule:: sevenbridges.models.compound.volumes.import_destination 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | sevenbridges\.models\.compound\.volumes\.properties module 21 | ---------------------------------------------------------- 22 | 23 | .. automodule:: sevenbridges.models.compound.volumes.properties 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | sevenbridges\.models\.compound\.volumes\.service module 29 | ------------------------------------------------------- 30 | 31 | .. automodule:: sevenbridges.models.compound.volumes.service 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | sevenbridges\.models\.compound\.volumes\.volume\_file module 37 | ------------------------------------------------------------ 38 | 39 | .. automodule:: sevenbridges.models.compound.volumes.volume_file 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | sevenbridges\.models\.compound\.volumes\.volume\_object module 45 | -------------------------------------------------------------- 46 | 47 | .. automodule:: sevenbridges.models.compound.volumes.volume_object 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | sevenbridges\.models\.compound\.volumes\.volume\_prefix module 53 | -------------------------------------------------------------- 54 | 55 | .. automodule:: sevenbridges.models.compound.volumes.volume_prefix 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | 61 | -------------------------------------------------------------------------------- /docs/sevenbridges.models.rst: -------------------------------------------------------------------------------- 1 | sevenbridges\.models package 2 | ============================ 3 | 4 | .. automodule:: sevenbridges.models 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Subpackages 10 | ----------- 11 | 12 | .. toctree:: 13 | 14 | sevenbridges.models.compound 15 | 16 | Submodules 17 | ---------- 18 | 19 | sevenbridges\.models\.actions module 20 | ------------------------------------ 21 | 22 | .. automodule:: sevenbridges.models.actions 23 | :members: 24 | :undoc-members: 25 | :show-inheritance: 26 | 27 | sevenbridges\.models\.app module 28 | -------------------------------- 29 | 30 | .. automodule:: sevenbridges.models.app 31 | :members: 32 | :undoc-members: 33 | :show-inheritance: 34 | 35 | sevenbridges\.models\.billing\_egress\_breakdown module 36 | ------------------------------------------------------- 37 | 38 | .. automodule:: sevenbridges.models.billing_egress_breakdown 39 | :members: 40 | :undoc-members: 41 | :show-inheritance: 42 | 43 | sevenbridges\.models\.billing\_storage\_breakdown module 44 | -------------------------------------------------------- 45 | 46 | .. automodule:: sevenbridges.models.billing_storage_breakdown 47 | :members: 48 | :undoc-members: 49 | :show-inheritance: 50 | 51 | sevenbridges\.models\.billing\_group module 52 | ------------------------------------------- 53 | 54 | .. automodule:: sevenbridges.models.billing_group 55 | :members: 56 | :undoc-members: 57 | :show-inheritance: 58 | 59 | sevenbridges\.models\.division module 60 | ------------------------------------- 61 | 62 | .. automodule:: sevenbridges.models.division 63 | :members: 64 | :undoc-members: 65 | :show-inheritance: 66 | 67 | sevenbridges\.models\.endpoints module 68 | -------------------------------------- 69 | 70 | .. automodule:: sevenbridges.models.endpoints 71 | :members: 72 | :undoc-members: 73 | :show-inheritance: 74 | 75 | sevenbridges\.models\.enums module 76 | ---------------------------------- 77 | 78 | .. automodule:: sevenbridges.models.enums 79 | :members: 80 | :undoc-members: 81 | :show-inheritance: 82 | 83 | sevenbridges\.models\.execution\_details module 84 | ----------------------------------------------- 85 | 86 | .. automodule:: sevenbridges.models.execution_details 87 | :members: 88 | :undoc-members: 89 | :show-inheritance: 90 | 91 | sevenbridges\.models\.file module 92 | --------------------------------- 93 | 94 | .. automodule:: sevenbridges.models.file 95 | :members: 96 | :undoc-members: 97 | :show-inheritance: 98 | 99 | sevenbridges\.models\.invoice module 100 | ------------------------------------ 101 | 102 | .. automodule:: sevenbridges.models.invoice 103 | :members: 104 | :undoc-members: 105 | :show-inheritance: 106 | 107 | sevenbridges\.models\.link module 108 | --------------------------------- 109 | 110 | .. automodule:: sevenbridges.models.link 111 | :members: 112 | :undoc-members: 113 | :show-inheritance: 114 | 115 | sevenbridges\.models\.marker module 116 | ----------------------------------- 117 | 118 | .. automodule:: sevenbridges.models.marker 119 | :members: 120 | :undoc-members: 121 | :show-inheritance: 122 | 123 | sevenbridges\.models\.member module 124 | ----------------------------------- 125 | 126 | .. automodule:: sevenbridges.models.member 127 | :members: 128 | :undoc-members: 129 | :show-inheritance: 130 | 131 | sevenbridges\.models\.project module 132 | ------------------------------------ 133 | 134 | .. automodule:: sevenbridges.models.project 135 | :members: 136 | :undoc-members: 137 | :show-inheritance: 138 | 139 | sevenbridges\.models\.rate\_limit module 140 | ---------------------------------------- 141 | 142 | .. automodule:: sevenbridges.models.rate_limit 143 | :members: 144 | :undoc-members: 145 | :show-inheritance: 146 | 147 | sevenbridges\.models\.storage\_export module 148 | -------------------------------------------- 149 | 150 | .. automodule:: sevenbridges.models.storage_export 151 | :members: 152 | :undoc-members: 153 | :show-inheritance: 154 | 155 | sevenbridges\.models\.storage\_import module 156 | -------------------------------------------- 157 | 158 | .. automodule:: sevenbridges.models.storage_import 159 | :members: 160 | :undoc-members: 161 | :show-inheritance: 162 | 163 | sevenbridges\.models\.task module 164 | --------------------------------- 165 | 166 | .. automodule:: sevenbridges.models.task 167 | :members: 168 | :undoc-members: 169 | :show-inheritance: 170 | 171 | sevenbridges\.models\.team module 172 | --------------------------------- 173 | 174 | .. automodule:: sevenbridges.models.team 175 | :members: 176 | :undoc-members: 177 | :show-inheritance: 178 | 179 | sevenbridges\.models\.team\_member module 180 | ----------------------------------------- 181 | 182 | .. automodule:: sevenbridges.models.team_member 183 | :members: 184 | :undoc-members: 185 | :show-inheritance: 186 | 187 | sevenbridges\.models\.user module 188 | --------------------------------- 189 | 190 | .. automodule:: sevenbridges.models.user 191 | :members: 192 | :undoc-members: 193 | :show-inheritance: 194 | 195 | sevenbridges\.models\.volume module 196 | ----------------------------------- 197 | 198 | .. automodule:: sevenbridges.models.volume 199 | :members: 200 | :undoc-members: 201 | :show-inheritance: 202 | 203 | 204 | -------------------------------------------------------------------------------- /docs/sevenbridges.rst: -------------------------------------------------------------------------------- 1 | sevenbridges package 2 | ==================== 3 | 4 | .. automodule:: sevenbridges 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Subpackages 10 | ----------- 11 | 12 | .. toctree:: 13 | 14 | sevenbridges.http 15 | sevenbridges.meta 16 | sevenbridges.models 17 | sevenbridges.tests 18 | sevenbridges.transfer 19 | 20 | Submodules 21 | ---------- 22 | 23 | sevenbridges\.api module 24 | ------------------------ 25 | 26 | .. automodule:: sevenbridges.api 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | sevenbridges\.config module 32 | --------------------------- 33 | 34 | .. automodule:: sevenbridges.config 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | sevenbridges\.decorators module 40 | ------------------------------- 41 | 42 | .. automodule:: sevenbridges.decorators 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | sevenbridges\.errors module 48 | --------------------------- 49 | 50 | .. automodule:: sevenbridges.errors 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | 56 | -------------------------------------------------------------------------------- /docs/sevenbridges.tests.rst: -------------------------------------------------------------------------------- 1 | sevenbridges\.tests package 2 | =========================== 3 | 4 | .. automodule:: sevenbridges.tests 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | sevenbridges\.tests\.conftest module 13 | ------------------------------------ 14 | 15 | .. automodule:: sevenbridges.tests.conftest 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | sevenbridges\.tests\.providers module 21 | ------------------------------------- 22 | 23 | .. automodule:: sevenbridges.tests.providers 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | sevenbridges\.tests\.test\_actions module 29 | ----------------------------------------- 30 | 31 | .. automodule:: sevenbridges.tests.test_actions 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | sevenbridges\.tests\.test\_apps module 37 | -------------------------------------- 38 | 39 | .. automodule:: sevenbridges.tests.test_apps 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | sevenbridges\.tests\.test\_config module 45 | ---------------------------------------- 46 | 47 | .. automodule:: sevenbridges.tests.test_config 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | sevenbridges\.tests\.test\_divisions module 53 | ------------------------------------------- 54 | 55 | .. automodule:: sevenbridges.tests.test_divisions 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | sevenbridges\.tests\.test\_endpoints\_rate module 61 | ------------------------------------------------- 62 | 63 | .. automodule:: sevenbridges.tests.test_endpoints_rate 64 | :members: 65 | :undoc-members: 66 | :show-inheritance: 67 | 68 | sevenbridges\.tests\.test\_exports module 69 | ----------------------------------------- 70 | 71 | .. automodule:: sevenbridges.tests.test_exports 72 | :members: 73 | :undoc-members: 74 | :show-inheritance: 75 | 76 | sevenbridges\.tests\.test\_files module 77 | --------------------------------------- 78 | 79 | .. automodule:: sevenbridges.tests.test_files 80 | :members: 81 | :undoc-members: 82 | :show-inheritance: 83 | 84 | sevenbridges\.tests\.test\_imports module 85 | ----------------------------------------- 86 | 87 | .. automodule:: sevenbridges.tests.test_imports 88 | :members: 89 | :undoc-members: 90 | :show-inheritance: 91 | 92 | sevenbridges\.tests\.test\_marker module 93 | ---------------------------------------- 94 | 95 | .. automodule:: sevenbridges.tests.test_marker 96 | :members: 97 | :undoc-members: 98 | :show-inheritance: 99 | 100 | sevenbridges\.tests\.test\_pagination module 101 | -------------------------------------------- 102 | 103 | .. automodule:: sevenbridges.tests.test_pagination 104 | :members: 105 | :undoc-members: 106 | :show-inheritance: 107 | 108 | sevenbridges\.tests\.test\_projects module 109 | ------------------------------------------ 110 | 111 | .. automodule:: sevenbridges.tests.test_projects 112 | :members: 113 | :undoc-members: 114 | :show-inheritance: 115 | 116 | sevenbridges\.tests\.test\_tasks module 117 | --------------------------------------- 118 | 119 | .. automodule:: sevenbridges.tests.test_tasks 120 | :members: 121 | :undoc-members: 122 | :show-inheritance: 123 | 124 | sevenbridges\.tests\.test\_teams module 125 | --------------------------------------- 126 | 127 | .. automodule:: sevenbridges.tests.test_teams 128 | :members: 129 | :undoc-members: 130 | :show-inheritance: 131 | 132 | sevenbridges\.tests\.test\_transformer module 133 | --------------------------------------------- 134 | 135 | .. automodule:: sevenbridges.tests.test_transformer 136 | :members: 137 | :undoc-members: 138 | :show-inheritance: 139 | 140 | sevenbridges\.tests\.test\_users module 141 | --------------------------------------- 142 | 143 | .. automodule:: sevenbridges.tests.test_users 144 | :members: 145 | :undoc-members: 146 | :show-inheritance: 147 | 148 | sevenbridges\.tests\.test\_utils module 149 | --------------------------------------- 150 | 151 | .. automodule:: sevenbridges.tests.test_utils 152 | :members: 153 | :undoc-members: 154 | :show-inheritance: 155 | 156 | sevenbridges\.tests\.test\_volumes module 157 | ----------------------------------------- 158 | 159 | .. automodule:: sevenbridges.tests.test_volumes 160 | :members: 161 | :undoc-members: 162 | :show-inheritance: 163 | 164 | sevenbridges\.tests\.verifiers module 165 | ------------------------------------- 166 | 167 | .. automodule:: sevenbridges.tests.verifiers 168 | :members: 169 | :undoc-members: 170 | :show-inheritance: 171 | 172 | 173 | -------------------------------------------------------------------------------- /docs/sevenbridges.transfer.rst: -------------------------------------------------------------------------------- 1 | sevenbridges\.transfer package 2 | ============================== 3 | 4 | .. automodule:: sevenbridges.transfer 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | sevenbridges\.transfer\.download module 13 | --------------------------------------- 14 | 15 | .. automodule:: sevenbridges.transfer.download 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | sevenbridges\.transfer\.upload module 21 | ------------------------------------- 22 | 23 | .. automodule:: sevenbridges.transfer.upload 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | sevenbridges\.transfer\.utils module 29 | ------------------------------------ 30 | 31 | .. automodule:: sevenbridges.transfer.utils 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | 37 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | 3 | flake8==4.0.0 4 | pytest==6.2.4 5 | pytest-cov==2.11.1 6 | requests-mock==1.8.0 7 | faker==8.1.3 8 | sphinx==3.5.1 9 | sphinx_rtd_theme==0.5.1 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.25.1 2 | urllib3>=1.26.2 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [tool:pytest] 2 | addopts = 3 | --cov=sevenbridges 4 | --cov-report=html:test-report/htmlcov 5 | --cov-report=xml:test-report/coverage.xml 6 | --cov-report=term 7 | 8 | [run] 9 | omit = 10 | tests/* 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from setuptools import setup, find_packages 4 | from sevenbridges.version import __version__ 5 | 6 | setup( 7 | name='sevenbridges-python', 8 | version=__version__, 9 | description='SBG API python client bindings', 10 | install_requires=Path('requirements.txt').read_text().split(), 11 | long_description=Path('README.md').read_text(encoding='utf-8'), 12 | long_description_content_type='text/markdown', 13 | platforms=['Windows', 'POSIX', 'MacOS'], 14 | maintainer='Seven Bridges Genomics Inc.', 15 | maintainer_email='dejan.knezevic@sbgenomics.com', 16 | url='https://github.com/sbg/sevenbridges-python', 17 | license='Apache Software License 2.0', 18 | include_package_data=True, 19 | packages=find_packages(exclude=["tests"]), 20 | keywords=[ 21 | 'sevenbridges', 'sbg', 'api', 'cgc', 22 | 'cancer', 'genomics', 'cloud', 23 | ], 24 | classifiers=[ 25 | 'License :: OSI Approved :: Apache Software License', 26 | 'Operating System :: OS Independent', 27 | 'Programming Language :: Python', 28 | 'Programming Language :: Python :: 3', 29 | 'Topic :: Software Development', 30 | 'Topic :: Scientific/Engineering :: Bio-Informatics', 31 | 'Topic :: Software Development :: Libraries :: Python Modules', 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /sevenbridges/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | sevenbridges-python 3 | ~~~~~~~~~~~~~~~~~~~ 4 | :copyright: 2020 Seven Bridges Genomics Inc. 5 | :license: Apache 2.0 6 | """ 7 | import ssl 8 | import logging 9 | 10 | # Read and set version globally 11 | # needs to be imported before other modules 12 | from sevenbridges.version import __version__ 13 | 14 | from sevenbridges.api import Api 15 | from sevenbridges.config import Config 16 | 17 | from sevenbridges.models.invoice import Invoice 18 | from sevenbridges.models.billing_group import BillingGroup 19 | from sevenbridges.models.user import User 20 | from sevenbridges.models.endpoints import Endpoints 21 | from sevenbridges.models.project import Project 22 | from sevenbridges.models.task import Task 23 | from sevenbridges.models.app import App 24 | from sevenbridges.models.dataset import Dataset 25 | from sevenbridges.models.drs_import import DRSImportBulk 26 | from sevenbridges.models.bulk import BulkRecord 27 | from sevenbridges.models.team import Team, TeamMember 28 | from sevenbridges.models.member import Member, Permissions 29 | from sevenbridges.models.file import File 30 | from sevenbridges.models.storage_export import Export 31 | from sevenbridges.models.storage_import import Import 32 | from sevenbridges.models.volume import Volume 33 | from sevenbridges.models.marker import Marker 34 | from sevenbridges.models.division import Division 35 | from sevenbridges.models.automation import ( 36 | Automation, AutomationRun, AutomationPackage, AutomationMember 37 | ) 38 | from sevenbridges.models.async_jobs import AsyncJob 39 | from sevenbridges.models.compound.volumes.volume_object import VolumeObject 40 | 41 | from sevenbridges.models.enums import ( 42 | AppCopyStrategy, AppRawFormat, AsyncFileOperations, AsyncJobStates, 43 | AutomationRunActions, DivisionRole, FileStorageType, ImportExportState, 44 | TaskStatus, TransferState, VolumeAccessMode, VolumeType, PartSize, 45 | AutomationStatus 46 | ) 47 | from sevenbridges.errors import ( 48 | SbgError, ResourceNotModified, ReadOnlyPropertyError, ValidationError, 49 | TaskValidationError, PaginationError, BadRequest, Unauthorized, Forbidden, 50 | NotFound, Conflict, TooManyRequests, ServerError, ServiceUnavailable, 51 | MethodNotAllowed, RequestTimeout, LocalFileAlreadyExists, 52 | ExecutionDetailsInvalidTaskType 53 | ) 54 | 55 | logging.getLogger(__name__).addHandler(logging.NullHandler()) 56 | 57 | __all__ = [ 58 | # Models 59 | 'Api', 'AsyncJob', 'Automation', 'AutomationRun', 'AutomationMember', 60 | 'AutomationPackage', 'Config', 'Invoice', 'BillingGroup', 'User', 61 | 'Endpoints', 'Project', 'Task', 'App', 'Member', 'Permissions', 'File', 62 | 'Export', 'Import', 'Volume', 'VolumeObject', 'Marker', 'Division', 'Team', 63 | 'TeamMember', 'Dataset', 'DRSImportBulk', 'BulkRecord', 64 | # Enums 65 | 'AppCopyStrategy', 'AppRawFormat', 'AppCopyStrategy', 66 | 'AsyncFileOperations', 'AsyncJobStates', 'AutomationRunActions', 67 | 'DivisionRole', 'FileStorageType', 'ImportExportState', 68 | 'TaskStatus', 'TransferState', 'VolumeAccessMode', 'VolumeType', 69 | 'PartSize', 'AutomationStatus', 70 | # Errors 71 | 'SbgError', 'ResourceNotModified', 'ReadOnlyPropertyError', 72 | 'ValidationError', 'TaskValidationError', 'PaginationError', 'BadRequest', 73 | 'Unauthorized', 'Forbidden', 'NotFound', 'Conflict', 'TooManyRequests', 74 | 'ServerError', 'ServiceUnavailable', 'MethodNotAllowed', 'RequestTimeout', 75 | 'LocalFileAlreadyExists', 'ExecutionDetailsInvalidTaskType', 76 | # Version 77 | '__version__', 78 | ] 79 | 80 | required_ssl_version = (1, 0, 1) 81 | if ssl.OPENSSL_VERSION_INFO < required_ssl_version: 82 | raise SbgError( 83 | 'OpenSSL version included in this python version must be ' 84 | 'at least 1.0.1 or greater. Please update your environment build.' 85 | ) 86 | -------------------------------------------------------------------------------- /sevenbridges/api.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import ThreadPoolExecutor 2 | 3 | from requests.adapters import DEFAULT_POOLSIZE 4 | 5 | from sevenbridges.errors import SbgError 6 | from sevenbridges.http.client import HttpClient 7 | 8 | from sevenbridges.models.app import App 9 | from sevenbridges.models.file import File 10 | from sevenbridges.models.task import Task 11 | from sevenbridges.models.team import Team 12 | from sevenbridges.models.user import User 13 | from sevenbridges.models.marker import Marker 14 | from sevenbridges.models.volume import Volume 15 | from sevenbridges.models.actions import Actions 16 | from sevenbridges.models.dataset import Dataset 17 | from sevenbridges.models.invoice import Invoice 18 | from sevenbridges.models.project import Project 19 | from sevenbridges.models.division import Division 20 | from sevenbridges.models.async_jobs import AsyncJob 21 | from sevenbridges.models.endpoints import Endpoints 22 | from sevenbridges.models.rate_limit import RateLimit 23 | from sevenbridges.models.storage_export import Export 24 | from sevenbridges.models.storage_import import Import 25 | from sevenbridges.models.drs_import import DRSImportBulk 26 | from sevenbridges.models.enums import RequestParameters 27 | from sevenbridges.models.billing_group import BillingGroup 28 | from sevenbridges.models.automation import ( 29 | Automation, AutomationRun, AutomationPackage 30 | ) 31 | 32 | 33 | class Api(HttpClient): 34 | """ 35 | Api aggregates all resource classes into single place 36 | """ 37 | 38 | actions = Actions 39 | apps = App 40 | async_jobs = AsyncJob 41 | automations = Automation 42 | automation_runs = AutomationRun 43 | automation_packages = AutomationPackage 44 | billing_groups = BillingGroup 45 | datasets = Dataset 46 | divisions = Division 47 | drs_imports = DRSImportBulk 48 | endpoints = Endpoints 49 | exports = Export 50 | files = File 51 | imports = Import 52 | invoices = Invoice 53 | markers = Marker 54 | projects = Project 55 | rate_limit = RateLimit 56 | tasks = Task 57 | teams = Team 58 | users = User 59 | volumes = Volume 60 | 61 | def __init__( 62 | self, url=None, token=None, oauth_token=None, config=None, 63 | timeout=None, download_max_workers=16, upload_max_workers=16, 64 | proxies=None, error_handlers=None, advance_access=False, 65 | pool_connections=DEFAULT_POOLSIZE, pool_maxsize=100, 66 | pool_block=True, max_parallel_requests=100, 67 | retry_count=RequestParameters.DEFAULT_RETRY_COUNT, 68 | backoff_factor=RequestParameters.DEFAULT_BACKOFF_FACTOR, 69 | debug=False, 70 | ): 71 | """ 72 | Initializes api object. 73 | 74 | :param url: Api url. 75 | :param token: Secure token. 76 | :param oauth_token: Oauth token. 77 | :param config: Configuration profile. 78 | :param timeout: Client timeout. 79 | :param download_max_workers: Max number of threads for download. 80 | :param upload_max_workers: Max number of threads for upload. 81 | :param proxies: Proxy settings if any. 82 | :param error_handlers: List of error handlers - callables. 83 | :param advance_access: If True advance access features will be enabled. 84 | :param pool_connections: The number of urllib3 connection pools to 85 | cache. 86 | :param pool_maxsize: The maximum number of connections to save in the 87 | pool. 88 | :param pool_block: Whether the connection pool should block for 89 | connections. 90 | :param max_parallel_requests: Number which indicates number of parallel 91 | requests, only useful for multi thread applications. 92 | :return: Api object instance. 93 | """ 94 | if not debug and url and url.startswith('http:'): 95 | raise SbgError( 96 | 'Seven Bridges api client requires https, ' 97 | f'cannot initialize with url {url}' 98 | ) 99 | 100 | super().__init__( 101 | url=url, token=token, oauth_token=oauth_token, config=config, 102 | timeout=timeout, proxies=proxies, error_handlers=error_handlers, 103 | advance_access=advance_access, pool_connections=pool_connections, 104 | pool_maxsize=pool_maxsize, pool_block=pool_block, 105 | max_parallel_requests=max_parallel_requests, 106 | retry_count=retry_count, backoff_factor=backoff_factor, 107 | ) 108 | 109 | self.download_pool = ThreadPoolExecutor( 110 | max_workers=download_max_workers 111 | ) 112 | self.upload_pool = ThreadPoolExecutor(max_workers=upload_max_workers) 113 | -------------------------------------------------------------------------------- /sevenbridges/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import configparser 4 | 5 | from sevenbridges.errors import SbgError 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | def format_proxies(proxies): 11 | """ 12 | Helper method for request proxy key compatibility. 13 | :param proxies: Proxies dictionary 14 | :return: Dict compatible with request proxy format. 15 | """ 16 | if proxies: 17 | return { 18 | 'http': proxies.get('http_proxy', None), 19 | 'https': proxies.get('https_proxy', None) 20 | } 21 | return {} 22 | 23 | 24 | class UserProfile: 25 | CREDENTIALS = os.path.join( 26 | os.path.expanduser('~'), 27 | '.sevenbridges', 28 | 'credentials' 29 | ) 30 | CONFIG = os.path.join( 31 | os.path.expanduser('~'), 32 | '.sevenbridges', 33 | 'sevenbridges-python', 34 | 'config' 35 | ) 36 | 37 | def __init__(self, profile): 38 | if not os.path.isfile(self.CREDENTIALS): 39 | raise SbgError('Missing credentials file.') 40 | 41 | self.profile = profile 42 | 43 | # noinspection PyTypeChecker 44 | self.credentials_parser = configparser.ConfigParser({ 45 | 'auth_token': None, 46 | 'api_endpoint': None, 47 | }, allow_no_value=True) 48 | # noinspection PyTypeChecker 49 | self.config_parser = configparser.ConfigParser({ 50 | 'http_proxy': None, 51 | 'https_proxy': None, 52 | 'advance_access': False, 53 | }, allow_no_value=True) 54 | self.credentials_parser.read(self.CREDENTIALS) 55 | 56 | if not os.path.isfile(self.CONFIG): 57 | self.config_parser = None 58 | logging.info('No custom configuration present. Skipping...') 59 | else: 60 | # noinspection PyTypeChecker 61 | self.config_parser = configparser.ConfigParser({ 62 | 'http_proxy': None, 63 | 'https_proxy': None, 64 | 'advance_access': False, 65 | }, allow_no_value=True) 66 | self.config_parser.read(self.CONFIG) 67 | 68 | @property 69 | def api_endpoint(self): 70 | return self.credentials_parser.get(self.profile, 'api_endpoint') 71 | 72 | @property 73 | def auth_token(self): 74 | return self.credentials_parser.get(self.profile, 'auth_token') 75 | 76 | @property 77 | def proxies(self): 78 | try: 79 | return { 80 | 'http_proxy': self.config_parser.get( 81 | 'proxies', 'http_proxy' 82 | ) if self.config_parser else None, 83 | 'https_proxy': self.config_parser.get( 84 | 'proxies', 'https_proxy' 85 | ) if self.config_parser else None 86 | } 87 | except KeyError: 88 | return format_proxies({}) 89 | except configparser.NoSectionError: 90 | return format_proxies({}) 91 | 92 | @property 93 | def advance_access(self): 94 | try: 95 | return ( 96 | self.config_parser.get('mode', 'advance_access') 97 | if self.config_parser 98 | else False 99 | ) 100 | except KeyError: 101 | return False 102 | except configparser.NoSectionError: 103 | return False 104 | 105 | 106 | class Config: 107 | """ 108 | Utility configuration class. 109 | """ 110 | 111 | def __init__(self, profile=None, proxies=None, advance_access=None): 112 | """ 113 | Configures the bindings to use api url and token specified 114 | in the .ini like configuration file. 115 | :param profile: ini section, if not supplied [default] profile is used. 116 | """ 117 | 118 | self.profile = profile 119 | 120 | cfg_profile = None 121 | 122 | if not self.profile: 123 | # Try os.environ 124 | self.auth_token = os.environ.get('SB_AUTH_TOKEN') 125 | self.api_endpoint = os.environ.get('SB_API_ENDPOINT') 126 | self.proxies = { 127 | 'http': os.environ.get('HTTP_PROXY'), 128 | 'https': os.environ.get('HTTPS_PROXY') 129 | } 130 | if not self.auth_token: 131 | logger.warning('Missing SB_AUTH_TOKEN os variable.') 132 | raise SbgError('Missing SB_AUTH_TOKEN') 133 | if not self.api_endpoint: 134 | logger.warning('Missing SB_API_ENDPOINT os variable.') 135 | raise SbgError('Missing SB_API_ENDPOINT') 136 | self.advance_access = advance_access if advance_access else False 137 | else: 138 | cfg_profile = UserProfile(profile) 139 | self.auth_token = cfg_profile.auth_token 140 | self.api_endpoint = cfg_profile.api_endpoint 141 | self.advance_access = cfg_profile.advance_access 142 | 143 | if proxies: 144 | self.proxies = format_proxies(proxies) 145 | elif cfg_profile: 146 | self.proxies = format_proxies(cfg_profile.proxies) 147 | 148 | if advance_access: 149 | self.advance_access = advance_access 150 | elif cfg_profile: 151 | self.advance_access = cfg_profile.advance_access 152 | 153 | logger.info( 154 | 'Client settings: [url=%s] [token=%s] [proxy=%s]', 155 | self.api_endpoint, 156 | '*****', 157 | self.proxies 158 | ) 159 | -------------------------------------------------------------------------------- /sevenbridges/decorators.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import logging 3 | import functools 4 | from json import JSONDecodeError 5 | 6 | import requests 7 | 8 | from sevenbridges.errors import ( 9 | BadRequest, Unauthorized, Forbidden, NotFound, MethodNotAllowed, 10 | RequestTimeout, Conflict, TooManyRequests, SbgError, ServerError, 11 | ServiceUnavailable, NonJSONResponseError 12 | ) 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | def inplace_reload(method): 18 | """ 19 | Executes the wrapped function and reloads the object 20 | with data returned from the server. 21 | """ 22 | 23 | # noinspection PyProtectedMember 24 | def wrapped(obj, *args, **kwargs): 25 | in_place = True if kwargs.get('inplace') in (True, None) else False 26 | api_object = method(obj, *args, **kwargs) 27 | if in_place and api_object: 28 | obj._data = api_object._data 29 | obj._dirty = api_object._dirty 30 | obj._old = copy.deepcopy(api_object._data.data) 31 | obj._data.fetched = False 32 | return obj 33 | elif api_object: 34 | return api_object 35 | else: 36 | return obj 37 | 38 | return wrapped 39 | 40 | 41 | def throttle(func): 42 | """Throttles number of parallel requests made by threads from single 43 | HttpClient session.""" 44 | 45 | # noinspection PyProtectedMember 46 | @functools.wraps(func) 47 | def wrapper(http_client, *args, **kwargs): 48 | if http_client._throttle_limit: 49 | with http_client._throttle_limit: 50 | return func(http_client, *args, **kwargs) 51 | else: 52 | return func(http_client, *args, **kwargs) 53 | return wrapper 54 | 55 | 56 | def check_for_error(func): 57 | """ 58 | Executes the wrapped function and inspects the response object 59 | for specific errors. 60 | """ 61 | 62 | @functools.wraps(func) 63 | def wrapper(*args, **kwargs): 64 | try: 65 | response = func(*args, **kwargs) 66 | except requests.RequestException as e: 67 | raise SbgError(message=str(e)) 68 | try: 69 | status_code = response.status_code 70 | if status_code in range(200, 204): 71 | return response 72 | if status_code == 204: 73 | return 74 | e = { 75 | 400: BadRequest, 76 | 401: Unauthorized, 77 | 403: Forbidden, 78 | 404: NotFound, 79 | 405: MethodNotAllowed, 80 | 408: RequestTimeout, 81 | 409: Conflict, 82 | 429: TooManyRequests, 83 | 500: ServerError, 84 | 503: ServiceUnavailable, 85 | }.get(status_code, SbgError)() 86 | data = response.json() 87 | if 'message' in data: 88 | e.message = data['message'] 89 | if 'code' in data: 90 | e.code = data['code'] 91 | if 'status' in data: 92 | e.status = data['status'] 93 | if 'more_info' in data: 94 | e.more_info = data['more_info'] 95 | raise e 96 | except JSONDecodeError: 97 | raise NonJSONResponseError( 98 | status=response.status_code, 99 | message=str(response.text) 100 | ) from None 101 | except ValueError as e: 102 | raise SbgError(message=str(e)) 103 | 104 | return wrapper 105 | -------------------------------------------------------------------------------- /sevenbridges/errors.py: -------------------------------------------------------------------------------- 1 | class SbgError(Exception): 2 | """Base class for SBG errors. 3 | 4 | Provides a base exception for all errors 5 | that are thrown by sevenbridges-python library. 6 | """ 7 | 8 | def __init__(self, message=None, code=None, status=None, more_info=None): 9 | """ 10 | :param code: Custom error code 11 | :param status: Http status code. 12 | :param message: Message describing the error. 13 | """ 14 | self.code = code 15 | self.status = status 16 | self.message = message 17 | self.more_info = more_info 18 | 19 | def __str__(self): 20 | return str(self.message) 21 | 22 | 23 | class ResourceNotModified(SbgError): 24 | def __init__(self): 25 | super().__init__( 26 | code=-1, 27 | status=-1, 28 | message=( 29 | 'No relevant changes were detected in order to update the ' 30 | 'resource on the server.' 31 | ) 32 | ) 33 | 34 | 35 | class NonJSONResponseError(SbgError): 36 | def __init__(self, status, code=None, message=None, more_info=None): 37 | super().__init__( 38 | code=code, status=status, message=message, more_info=more_info 39 | ) 40 | 41 | 42 | class ReadOnlyPropertyError(SbgError): 43 | def __init__(self, message): 44 | super().__init__( 45 | code=-1, status=-1, message=message 46 | ) 47 | 48 | 49 | class ValidationError(SbgError): 50 | def __init__(self, message): 51 | super().__init__( 52 | code=-1, status=-1, message=message 53 | ) 54 | 55 | 56 | class TaskValidationError(SbgError): 57 | def __init__(self, message, task=None): 58 | self.task = task 59 | super().__init__( 60 | code=-1, status=-1, message=message 61 | ) 62 | 63 | 64 | class PaginationError(SbgError): 65 | def __init__(self, message): 66 | super().__init__( 67 | code=-1, status=-1, message=message 68 | ) 69 | 70 | 71 | class AdvanceAccessError(SbgError): 72 | def __init__(self, message=None): 73 | super().__init__( 74 | code=-1, status=-1, message=message 75 | ) 76 | 77 | 78 | class BadRequest(SbgError): 79 | def __init__(self, code=None, message=None, more_info=None): 80 | super().__init__( 81 | code=code, status=400, message=message, more_info=more_info) 82 | 83 | 84 | class Unauthorized(SbgError): 85 | def __init__(self, code=None, message=None, more_info=None): 86 | super().__init__( 87 | code=code, status=401, message=message, more_info=more_info 88 | ) 89 | 90 | 91 | class Forbidden(SbgError): 92 | def __init__(self, code=None, message=None, more_info=None): 93 | super().__init__( 94 | code=code, status=403, message=message, more_info=more_info 95 | ) 96 | 97 | 98 | class NotFound(SbgError): 99 | def __init__(self, code=None, message=None, more_info=None): 100 | super().__init__( 101 | code=code, status=404, message=message, more_info=more_info 102 | ) 103 | 104 | 105 | class Conflict(SbgError): 106 | def __init__(self, code=None, message=None, more_info=None): 107 | super().__init__( 108 | code=code, status=409, message=message, more_info=more_info 109 | ) 110 | 111 | 112 | class TooManyRequests(SbgError): 113 | def __init__(self, code=None, message=None, more_info=None): 114 | super().__init__( 115 | code=code, status=429, message=message, more_info=more_info 116 | ) 117 | 118 | 119 | class ServerError(SbgError): 120 | def __init__(self, code=None, message=None, more_info=None): 121 | super().__init__( 122 | code=code, status=500, message=message, more_info=more_info 123 | ) 124 | 125 | 126 | class ServiceUnavailable(SbgError): 127 | def __init__(self, code=None, message=None, more_info=None): 128 | super().__init__( 129 | code=code, status=503, message=message, more_info=more_info 130 | ) 131 | 132 | 133 | class MethodNotAllowed(SbgError): 134 | def __init__(self, code=None, message=None, more_info=None): 135 | super().__init__( 136 | code=code, status=405, message=message, more_info=more_info 137 | ) 138 | 139 | 140 | class RequestTimeout(SbgError): 141 | def __init__(self, code=None, message=None, more_info=None): 142 | super().__init__( 143 | code=code, status=408, message=message, more_info=more_info 144 | ) 145 | 146 | 147 | class LocalFileAlreadyExists(SbgError): 148 | def __init__(self, code=None, message=None, more_info=None): 149 | super().__init__( 150 | code=code, status=-1, message=message, more_info=more_info 151 | ) 152 | 153 | 154 | class ExecutionDetailsInvalidTaskType(SbgError): 155 | def __init__(self, code=None, message=None, more_info=None): 156 | super().__init__( 157 | code=code, status=-1, message=message, more_info=more_info 158 | ) 159 | 160 | 161 | class URITooLong(SbgError): 162 | def __init__(self, code=None, message=None, more_info=None): 163 | super().__init__( 164 | code=code, status=414, message=message, more_info=more_info 165 | ) 166 | -------------------------------------------------------------------------------- /sevenbridges/http/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbg/sevenbridges-python/ed3de94bab744a33f614111c1845af68b5433c54/sevenbridges/http/__init__.py -------------------------------------------------------------------------------- /sevenbridges/http/error_handlers.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | 8 | def repeatable_handler(f): 9 | """ 10 | Marks 'repeatable' error handlers. Error handler is repeatable if propagate 11 | input response in case of no error handling occurred. 12 | """ 13 | f.is_repeatable = True 14 | return f 15 | 16 | 17 | @repeatable_handler 18 | def rate_limit_sleeper(api, response): 19 | """ 20 | Pauses the execution if rate limit is breached. 21 | :param api: Api instance. 22 | :param response: requests.Response object 23 | 24 | """ 25 | while response.status_code == 429: 26 | headers = response.headers 27 | remaining_time = headers.get('X-RateLimit-Reset') 28 | sleep = max(int(remaining_time) - int(time.time()), 0) 29 | 30 | logger.warning('Rate limit reached! Waiting for [%s]s', sleep) 31 | time.sleep(sleep) 32 | 33 | response = api.session.send(response.request) 34 | return response 35 | 36 | 37 | @repeatable_handler 38 | def maintenance_sleeper(api, response, sleep=300): 39 | """ 40 | Pauses the execution if sevenbridges api is under maintenance. 41 | :param api: Api instance. 42 | :param response: requests.Response object. 43 | :param sleep: Time to sleep in between the requests. 44 | """ 45 | while response.status_code == 503: 46 | content_type = response.headers.get('Content-Type') 47 | if content_type is None or 'application/json' not in content_type: 48 | return response 49 | logger.info( 50 | 'Service unavailable: Response=[%s]', 51 | response.__dict__ 52 | ) 53 | response_body = response.json() 54 | if 'code' in response_body: 55 | if response_body['code'] == 0: 56 | logger.warning( 57 | 'API Maintenance in progress! Waiting for [%s]s', 58 | sleep 59 | ) 60 | time.sleep(sleep) 61 | response = api.session.send(response.request) 62 | else: 63 | return response 64 | else: 65 | return response 66 | return response 67 | 68 | 69 | @repeatable_handler 70 | def general_error_sleeper(api, response, sleep=300): 71 | """ 72 | Pauses the execution if response status code is > 500. 73 | :param api: Api instance. 74 | :param response: requests.Response object 75 | :param sleep: Time to sleep in between the requests. 76 | 77 | """ 78 | while response.status_code >= 500: 79 | logger.warning( 80 | 'Caught [%s] status code! Waiting for [%s]s', 81 | response.status_code, 82 | sleep 83 | ) 84 | time.sleep(sleep) 85 | response = api.session.send(response.request) 86 | return response 87 | -------------------------------------------------------------------------------- /sevenbridges/meta/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbg/sevenbridges-python/ed3de94bab744a33f614111c1845af68b5433c54/sevenbridges/meta/__init__.py -------------------------------------------------------------------------------- /sevenbridges/meta/collection.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.errors import PaginationError, SbgError 2 | from sevenbridges.models.compound.volumes.volume_object import VolumeObject 3 | from sevenbridges.models.compound.volumes.volume_prefix import VolumePrefix 4 | from sevenbridges.models.link import Link, VolumeLink 5 | 6 | 7 | class Collection(list): 8 | """ 9 | Wrapper for SevenBridges pageable resources. 10 | Among the actual collection items it contains information regarding 11 | the total number of entries available in on the server and resource href. 12 | """ 13 | 14 | resource = None 15 | 16 | def __init__(self, resource, href, total, items, links, api): 17 | super().__init__(items) 18 | self.resource = resource 19 | self.href = href 20 | self.links = links 21 | self._items = items 22 | self._total = total 23 | self._api = api 24 | 25 | @property 26 | def total(self): 27 | return int(self._total) 28 | 29 | def all(self): 30 | """ 31 | Fetches all available items. 32 | :return: Collection object. 33 | """ 34 | page = self._load(self.href) 35 | while True: 36 | try: 37 | for item in page._items: 38 | yield item 39 | page = page.next_page() 40 | except PaginationError: 41 | break 42 | 43 | def _load(self, url): 44 | if self.resource is None: 45 | raise SbgError('Undefined collection resource.') 46 | else: 47 | response = self._api.get(url, append_base=False) 48 | data = response.json() 49 | total = response.headers['x-total-matching-query'] 50 | items = [ 51 | self.resource(api=self._api, **group) 52 | for group in data['items'] 53 | ] 54 | links = [Link(**link) for link in data['links']] 55 | href = data['href'] 56 | return Collection( 57 | resource=self.resource, href=href, total=total, 58 | items=items, links=links, api=self._api 59 | ) 60 | 61 | def next_page(self): 62 | """ 63 | Fetches next result set. 64 | :return: Collection object. 65 | """ 66 | for link in self.links: 67 | if link.rel.lower() == 'next': 68 | return self._load(link.href) 69 | raise PaginationError('No more entries.') 70 | 71 | def previous_page(self): 72 | """ 73 | Fetches previous result set. 74 | :return: Collection object. 75 | """ 76 | for link in self.links: 77 | if link.rel.lower() == 'prev': 78 | return self._load(link.href) 79 | raise PaginationError('No more entries.') 80 | 81 | def __repr__(self): 82 | return ( 83 | f'' 84 | ) 85 | 86 | 87 | class VolumeCollection(Collection): 88 | def __init__(self, href, items, links, prefixes, api): 89 | super().__init__( 90 | VolumeObject, href, 0, items, links, api) 91 | self.prefixes = prefixes 92 | 93 | @property 94 | def total(self): 95 | return -1 96 | 97 | def next_page(self): 98 | """ 99 | Fetches next result set. 100 | :return: VolumeCollection object. 101 | """ 102 | for link in self.links: 103 | if link.next: 104 | return self._load(link.next) 105 | raise PaginationError('No more entries.') 106 | 107 | def previous_page(self): 108 | raise PaginationError('Cannot paginate backwards') 109 | 110 | def _load(self, url): 111 | if self.resource is None: 112 | raise SbgError('Undefined collection resource.') 113 | else: 114 | response = self._api.get(url, append_base=False) 115 | data = response.json() 116 | items = [ 117 | self.resource(api=self._api, **group) for group in 118 | data['items'] 119 | ] 120 | prefixes = [ 121 | VolumePrefix(api=self._api, **prefix) for prefix in 122 | data['prefixes'] 123 | ] 124 | links = [VolumeLink(**link) for link in data['links']] 125 | href = data['href'] 126 | return VolumeCollection( 127 | href=href, items=items, links=links, 128 | prefixes=prefixes, api=self._api 129 | ) 130 | 131 | def __repr__(self): 132 | return f'' 133 | -------------------------------------------------------------------------------- /sevenbridges/meta/comp_mutable_dict.py: -------------------------------------------------------------------------------- 1 | # noinspection PyProtectedMember,PyUnresolvedReferences 2 | class CompoundMutableDict(dict): 3 | """ 4 | Resource used for mutable compound dictionaries. 5 | """ 6 | 7 | # noinspection PyMissingConstructor 8 | def __init__(self, **kwargs): 9 | self._parent = kwargs.pop('_parent') 10 | self._api = kwargs.pop('api') 11 | for k, v in kwargs.items(): 12 | super().__setitem__(k, v) 13 | 14 | def __setitem__(self, key, value): 15 | super().__setitem__(key, value) 16 | if self._name not in self._parent._dirty: 17 | self._parent._dirty.update({self._name: {}}) 18 | if key in self._parent._data[self._name]: 19 | if self._parent._data[self._name][key] != value: 20 | self._parent._dirty[self._name][key] = value 21 | self._parent._data[self._name][key] = value 22 | else: 23 | self._parent._data[self._name][key] = value 24 | self._parent._dirty[self._name][key] = value 25 | 26 | def __repr__(self): 27 | values = {} 28 | for k, _ in self.items(): 29 | values[k] = self[k] 30 | return str(values) 31 | 32 | __str__ = __repr__ 33 | 34 | def update(self, e=None, **f): 35 | other = {} 36 | if e: 37 | other.update(e, **f) 38 | else: 39 | other.update(**f) 40 | for k, v in other.items(): 41 | if other[k] != self[k]: 42 | self[k] = other[k] 43 | 44 | def items(self): 45 | values = [] 46 | for k in self.keys(): 47 | values.append((k, self[k])) 48 | return values 49 | 50 | def equals(self, other): 51 | if not type(other) == type(self): 52 | return False 53 | return ( 54 | self is other or 55 | self._parent._data[self._name] == other._parent._data[self._name] 56 | ) 57 | -------------------------------------------------------------------------------- /sevenbridges/meta/data.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | 7 | class DataContainer: 8 | """ 9 | Utility for fetching data from the API server using, 10 | resource identifier or href. 11 | """ 12 | 13 | def __init__(self, urls, api, parent): 14 | self.data = {} 15 | self._URL = urls 16 | self.api = api 17 | self.parent = parent 18 | self.fetched = False 19 | 20 | def fetch(self, item=None): 21 | 22 | logger.debug( 23 | 'Property "%s" is not set, fetching resource from server', item 24 | ) if item else logger.debug( 25 | 'Requested property is not set, fetching resource from server', 26 | ) 27 | 28 | href = self.data.get('href', None) 29 | headers = dict(self.api.headers) 30 | 31 | if href: 32 | self.data = self.api.get( 33 | href, 34 | headers=headers, 35 | append_base=False 36 | ).json() 37 | logger.debug('Resource fetched using the "href" property.') 38 | elif self._URL is not None and 'get' in self._URL: 39 | resource_id = self.data.get('id', None) 40 | if resource_id is None: 41 | logger.debug( 42 | 'Failed to fetch resource, "id" or "href" property ' 43 | 'not set' 44 | ) 45 | return 46 | self.data = self.api.get( 47 | self._URL['get'].format(id=resource_id), 48 | headers=headers, 49 | append_base=True 50 | ).json() 51 | 52 | logger.debug('Resource fetched using the id property.') 53 | else: 54 | logger.debug( 55 | 'Skipping resource fetch, retrieval for this resource is ' 56 | 'not available.' 57 | ) 58 | return 59 | self.parent.update_old() 60 | self.fetched = True 61 | 62 | def __getitem__(self, item): 63 | if item not in self.data and not self.fetched: 64 | self.fetch(item=item) 65 | try: 66 | return self.data[item] 67 | except KeyError: 68 | return None 69 | 70 | def __setitem__(self, key, value): 71 | self.data[key] = value 72 | -------------------------------------------------------------------------------- /sevenbridges/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbg/sevenbridges-python/ed3de94bab744a33f614111c1845af68b5433c54/sevenbridges/models/__init__.py -------------------------------------------------------------------------------- /sevenbridges/models/actions.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sevenbridges.http.client import client_info 4 | from sevenbridges.meta.resource import Resource 5 | from sevenbridges.meta.transformer import Transform 6 | from sevenbridges.models.enums import FeedbackType 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class Actions(Resource): 12 | _URL = { 13 | 'send_feedback': '/action/notifications/feedback', 14 | 'bulk_copy': '/action/files/copy' 15 | } 16 | 17 | def __str__(self): 18 | return '' 19 | 20 | # noinspection PyShadowingBuiltins 21 | @classmethod 22 | def send_feedback(cls, type=FeedbackType.IDEA, referrer=None, text=None, 23 | api=None): 24 | """ 25 | Sends feedback to sevenbridges. 26 | :param type: FeedbackType wither IDEA, PROBLEM or THOUGHT. 27 | :param text: Feedback text. 28 | :param referrer: Feedback referrer. 29 | :param api: Api instance. 30 | """ 31 | api = api if api else cls._API 32 | data = { 33 | 'type': type, 34 | 'text': text, 35 | 'referrer': referrer if referrer else str(client_info) 36 | } 37 | 38 | extra = { 39 | 'resource': cls.__name__, 40 | 'query': data 41 | } 42 | logger.info('Sending feedback', extra=extra) 43 | api.post(url=cls._URL['send_feedback'], data=data) 44 | 45 | @classmethod 46 | def bulk_copy_files(cls, files, destination_project, api=None): 47 | """ 48 | Bulk copy of files. 49 | :param files: List containing files to be copied. 50 | :param destination_project: Destination project. 51 | :param api: Api instance. 52 | :return: MultiStatus copy result. 53 | """ 54 | api = api if api else cls._API 55 | files = [Transform.to_file(file) for file in files] 56 | data = { 57 | 'project': destination_project, 58 | 'file_ids': files 59 | } 60 | extra = { 61 | 'resource': cls.__name__, 62 | 'query': data 63 | } 64 | logger.info('Performing bulk copy', extra=extra) 65 | return api.post(url=cls._URL['bulk_copy'], data=data).json() 66 | -------------------------------------------------------------------------------- /sevenbridges/models/async_jobs.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sevenbridges.meta.resource import Resource 4 | from sevenbridges.models.file import FileBulkRecord 5 | from sevenbridges.meta.transformer import Transform 6 | from sevenbridges.meta.fields import ( 7 | BasicListField, DateTimeField, IntegerField, StringField, HrefField 8 | ) 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class AsyncFileBulkRecord(FileBulkRecord): 14 | 15 | @classmethod 16 | def parse_records(cls, result, api=None): 17 | api = api or cls._API 18 | 19 | records = [] 20 | for item in result: 21 | record = cls(api=api) 22 | if 'error' in item: 23 | record.error = item['error'] 24 | if 'resource' in item: 25 | record.resource = item['resource'] 26 | records.append(record) 27 | return records 28 | 29 | 30 | class AsyncJob(Resource): 31 | """ 32 | Central resource for managing async jobs 33 | """ 34 | 35 | _URL = { 36 | 'list_file_jobs': '/async/files', 37 | 'get_file_copy_job': '/async/files/copy/{id}', 38 | 'get_file_delete_job': '/async/files/delete/{id}', 39 | 'bulk_copy_files': '/async/files/copy', 40 | 'bulk_delete_files': '/async/files/delete', 41 | 'get_file_move_job': '/async/files/move/{id}', 42 | 'bulk_move_files': '/async/files/move', 43 | } 44 | 45 | href = HrefField(read_only=True) 46 | id = StringField(read_only=True) 47 | type = StringField(read_only=True) 48 | state = StringField(read_only=True) 49 | result = BasicListField(read_only=True) 50 | total_files = IntegerField(read_only=True) 51 | failed_files = IntegerField(read_only=True) 52 | completed_files = IntegerField(read_only=True) 53 | started_on = DateTimeField(read_only=True) 54 | finished_on = DateTimeField(read_only=True) 55 | 56 | def __str__(self): 57 | return f'' 58 | 59 | def __eq__(self, other): 60 | if type(other) is not type(self): 61 | return False 62 | return self is other or self.id == other.id 63 | 64 | @classmethod 65 | def get_file_copy_job(cls, id, api=None): 66 | """ 67 | Retrieve file copy async job 68 | :param id: Async job identifier 69 | :param api: Api instance 70 | :return: 71 | """ 72 | id = Transform.to_async_job(id) 73 | 74 | api = api if api else cls._API 75 | async_job = api.get( 76 | url=cls._URL['get_file_copy_job'].format(id=id) 77 | ).json() 78 | return AsyncJob(api=api, **async_job) 79 | 80 | @classmethod 81 | def get_file_move_job(cls, id, api=None): 82 | """ 83 | Retrieve file move async job 84 | :param id: Async job identifier 85 | :param api: Api instance 86 | :return: 87 | """ 88 | id = Transform.to_async_job(id) 89 | 90 | api = api if api else cls._API 91 | async_job = api.get( 92 | url=cls._URL['get_file_move_job'].format(id=id) 93 | ).json() 94 | return AsyncJob(api=api, **async_job) 95 | 96 | @classmethod 97 | def get_file_delete_job(cls, id, api=None): 98 | """ 99 | :param id: Async job identifier 100 | :param api: Api instance 101 | :return: 102 | """ 103 | id = Transform.to_async_job(id) 104 | 105 | api = api if api else cls._API 106 | async_job = api.get( 107 | url=cls._URL['get_file_delete_job'].format(id=id) 108 | ).json() 109 | return AsyncJob(api=api, **async_job) 110 | 111 | def get_result(self, api=None): 112 | """ 113 | Get async job result in bulk format 114 | :return: List of AsyncFileBulkRecord objects 115 | """ 116 | api = api or self._API 117 | if not self.result: 118 | return [] 119 | return AsyncFileBulkRecord.parse_records( 120 | result=self.result, 121 | api=api 122 | ) 123 | 124 | @classmethod 125 | def list_file_jobs(cls, offset=None, limit=None, api=None): 126 | """Query ( List ) async jobs 127 | :param offset: Pagination offset 128 | :param limit: Pagination limit 129 | :param api: Api instance 130 | :return: Collection object 131 | """ 132 | api = api or cls._API 133 | return super()._query( 134 | api=api, 135 | url=cls._URL['list_file_jobs'], 136 | offset=offset, 137 | limit=limit, 138 | ) 139 | 140 | @classmethod 141 | def file_bulk_copy(cls, files, api=None): 142 | api = api or cls._API 143 | data = {'items': files} 144 | logger.info('Submitting async job for copying files in bulk') 145 | response = api.post( 146 | url=cls._URL['bulk_copy_files'], data=data 147 | ).json() 148 | return AsyncJob(api=api, **response) 149 | 150 | @classmethod 151 | def file_bulk_move(cls, files, api=None): 152 | api = api or cls._API 153 | data = {'items': files} 154 | logger.info('Submitting async job for moving files in bulk') 155 | response = api.post( 156 | url=cls._URL['bulk_move_files'], data=data 157 | ).json() 158 | return AsyncJob(api=api, **response) 159 | 160 | @classmethod 161 | def file_bulk_delete(cls, files, api=None): 162 | api = api or cls._API 163 | data = {'items': files} 164 | logger.info('Submitting async job for deleting files in bulk') 165 | response = api.post( 166 | url=cls._URL['bulk_delete_files'], data=data 167 | ).json() 168 | return AsyncJob(api=api, **response) 169 | -------------------------------------------------------------------------------- /sevenbridges/models/billing_analysis_breakdown.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import ( 3 | UuidField, CompoundField, StringField, 4 | DateTimeField, FloatField, BooleanField 5 | ) 6 | from sevenbridges.models.compound.analysis_cost import AnalysisCost 7 | 8 | 9 | class BillingGroupAnalysisBreakdown(Resource): 10 | _URL = { 11 | 'query': '/billing/groups/{id}/breakdown/analysis' 12 | } 13 | 14 | project_name = StringField(read_only=True) 15 | analysis_app_name = StringField(read_only=True) 16 | analysis_name = StringField(read_only=True) 17 | analysis_type = StringField(read_only=True) 18 | analysis_id = UuidField(read_only=True) 19 | ran_by = StringField(read_only=True) 20 | analysis_status = StringField(read_only=True) 21 | analysis_cost = CompoundField(AnalysisCost, read_only=True) 22 | refunded_amount = FloatField(read_only=True) 23 | time_started = DateTimeField(read_only=True) 24 | time_finished = DateTimeField(read_only=True) 25 | project_locked = BooleanField(read_only=True) 26 | 27 | @classmethod 28 | def query(cls, bg_id, api=None, date_from=None, date_to=None, 29 | invoice_id=None, fields=None, offset=None, limit=None): 30 | """ 31 | Query (List) billing group analysis breakdown. Date parameters must be 32 | string in format MM-DD-YYYY 33 | 34 | :param fields: 35 | :param invoice_id: 36 | :param date_to: include all analysis transactions charged before and 37 | including date_to 38 | :param date_from: include all analysis transactions charged after and 39 | including date_from 40 | :param bg_id: Billing Group ID 41 | :param offset: Pagination offset. 42 | :param limit: Pagination limit. 43 | :param api: Api instance. 44 | :return: Collection object. 45 | """ 46 | api = api or cls._API 47 | 48 | return super(BillingGroupAnalysisBreakdown, cls)._query( 49 | url=cls._URL['query'].format(id=bg_id), offset=offset, limit=limit, 50 | date_from=date_from, date_to=date_to, invoice_id=invoice_id, 51 | fields=fields, api=api 52 | ) 53 | 54 | def __str__(self): 55 | return '' 56 | -------------------------------------------------------------------------------- /sevenbridges/models/billing_egress_breakdown.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import ( 3 | StringField, CompoundField, BooleanField 4 | ) 5 | from sevenbridges.models.compound.measurement import Measurement 6 | from sevenbridges.models.compound.egress_cost import EgressCost 7 | 8 | 9 | class BillingGroupEgressBreakdown(Resource): 10 | _URL = { 11 | 'query': '/billing/groups/{id}/breakdown/egress' 12 | } 13 | 14 | project_name = StringField(read_only=True) 15 | downloaded_by = StringField(read_only=True) 16 | downloaded = CompoundField(Measurement, read_only=True) 17 | egress_cost = CompoundField(EgressCost, read_only=True) 18 | project_locked = BooleanField(read_only=True) 19 | 20 | @classmethod 21 | def query(cls, bg_id, api=None, date_from=None, date_to=None, 22 | invoice_id=None, fields=None, offset=None, limit=None): 23 | """ 24 | Query (List) billing group egress breakdown. Date parameters must be 25 | string in format MM-DD-YYYY 26 | 27 | :param fields: 28 | :param invoice_id: 29 | :param date_to: include all egress transactions charged before and 30 | including date_to 31 | :param date_from: include all egress transactions charged after and 32 | including date_from 33 | :param bg_id: Billing Group ID 34 | :param offset: Pagination offset. 35 | :param limit: Pagination limit. 36 | :param api: Api instance. 37 | :return: Collection object. 38 | """ 39 | api = api or cls._API 40 | 41 | return super(BillingGroupEgressBreakdown, cls)._query( 42 | url=cls._URL['query'].format(id=bg_id), offset=offset, limit=limit, 43 | date_from=date_from, date_to=date_to, invoice_id=invoice_id, 44 | fields=fields, api=api 45 | ) 46 | 47 | def __str__(self): 48 | return '' 49 | -------------------------------------------------------------------------------- /sevenbridges/models/billing_group.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.fields import ( 2 | HrefField, UuidField, StringField, BooleanField, CompoundField 3 | ) 4 | from sevenbridges.meta.resource import Resource 5 | from sevenbridges.models.billing_analysis_breakdown import ( 6 | BillingGroupAnalysisBreakdown 7 | ) 8 | from sevenbridges.models.billing_storage_breakdown import ( 9 | BillingGroupStorageBreakdown 10 | ) 11 | from sevenbridges.models.billing_egress_breakdown import ( 12 | BillingGroupEgressBreakdown 13 | ) 14 | from sevenbridges.models.compound.price import Price 15 | 16 | 17 | class BillingGroup(Resource): 18 | """ 19 | Central resource for managing billing groups. 20 | """ 21 | _URL = { 22 | 'query': '/billing/groups', 23 | 'get': '/billing/groups/{id}' 24 | } 25 | href = HrefField(read_only=True) 26 | id = UuidField(read_only=True) 27 | owner = StringField(read_only=True) 28 | name = StringField(read_only=True) 29 | type = StringField(read_only=True) 30 | pending = BooleanField(read_only=True) 31 | disabled = BooleanField(read_only=False) 32 | balance = CompoundField(Price, read_only=True) 33 | 34 | def __str__(self): 35 | return f'' 36 | 37 | def __eq__(self, other): 38 | if type(other) is not type(self): 39 | return False 40 | return self is other or self.id == other.id 41 | 42 | @classmethod 43 | def query(cls, offset=None, limit=None, api=None): 44 | """ 45 | Query (List) billing group. 46 | :param offset: Pagination offset. 47 | :param limit: Pagination limit. 48 | :return: Collection object. 49 | :param api: Api instance. 50 | """ 51 | api = api or cls._API 52 | return super()._query( 53 | url=cls._URL['query'], offset=offset, limit=limit, fields='_all', 54 | api=api 55 | ) 56 | 57 | def analysis_breakdown(self, date_from=None, date_to=None, invoice_id=None, 58 | fields=None, offset=None, limit=None): 59 | """ 60 | Get Billing group analysis breakdown for the current billing group. 61 | """ 62 | return BillingGroupAnalysisBreakdown.query( 63 | bg_id=self.id, api=self._api, date_from=date_from, date_to=date_to, 64 | invoice_id=invoice_id, fields=fields, offset=offset, limit=limit 65 | ) 66 | 67 | def storage_breakdown(self, date_from=None, date_to=None, invoice_id=None, 68 | fields=None, offset=None, limit=None): 69 | """ 70 | Get Billing group storage breakdown for the current billing group. 71 | """ 72 | return BillingGroupStorageBreakdown.query( 73 | bg_id=self.id, api=self._api, date_from=date_from, date_to=date_to, 74 | invoice_id=invoice_id, fields=fields, offset=offset, limit=limit 75 | ) 76 | 77 | def egress_breakdown(self, date_from=None, date_to=None, invoice_id=None, 78 | fields=None, offset=None, limit=None): 79 | """ 80 | Get Billing group egress breakdown for the current billing group. 81 | """ 82 | return BillingGroupEgressBreakdown.query( 83 | bg_id=self.id, api=self._api, date_from=date_from, date_to=date_to, 84 | invoice_id=invoice_id, fields=fields, offset=offset, limit=limit 85 | ) 86 | -------------------------------------------------------------------------------- /sevenbridges/models/billing_storage_breakdown.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import ( 3 | CompoundField, StringField, BooleanField 4 | ) 5 | from sevenbridges.models.compound.measurement import Measurement 6 | 7 | 8 | class BillingGroupStorageBreakdown(Resource): 9 | _URL = { 10 | 'query': '/billing/groups/{id}/breakdown/storage' 11 | } 12 | 13 | project_name = StringField(read_only=True) 14 | project_created_by = StringField(read_only=True) 15 | location = StringField(read_only=True) 16 | active = CompoundField(Measurement, read_only=True) 17 | archived = CompoundField(Measurement, read_only=True) 18 | project_locked = BooleanField(read_only=True) 19 | 20 | @classmethod 21 | def query(cls, bg_id, api=None, date_from=None, date_to=None, 22 | invoice_id=None, fields=None, offset=None, limit=None): 23 | """ 24 | Query (List) billing group storage breakdown. Date parameters must be 25 | string in format MM-DD-YYYY 26 | 27 | :param fields: 28 | :param invoice_id: 29 | :param date_to: include all storage transactions charged before and 30 | including date_to 31 | :param date_from: include all storage transactions charged after and 32 | including date_from 33 | :param bg_id: Billing Group ID 34 | :param offset: Pagination offset. 35 | :param limit: Pagination limit. 36 | :param api: Api instance. 37 | :return: Collection object. 38 | """ 39 | api = api or cls._API 40 | 41 | return super(BillingGroupStorageBreakdown, cls)._query( 42 | url=cls._URL['query'].format(id=bg_id), offset=offset, limit=limit, 43 | date_from=date_from, date_to=date_to, invoice_id=invoice_id, 44 | fields=fields, api=api 45 | ) 46 | 47 | def __str__(self): 48 | return '' 49 | -------------------------------------------------------------------------------- /sevenbridges/models/bulk.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import CompoundField 3 | from sevenbridges.models.compound.error import Error 4 | 5 | 6 | class BulkRecord(Resource): 7 | error = CompoundField(cls=Error, read_only=False) 8 | resource = CompoundField(cls=Resource, read_only=False) 9 | 10 | def __str__(self): 11 | return f'' 12 | 13 | @property 14 | def valid(self): 15 | return self.error is None 16 | 17 | @classmethod 18 | def parse_records(cls, response, api=None): 19 | api = api or cls._API 20 | records = [] 21 | data = response.json() 22 | for item in data.get('items', []): 23 | record = cls(api=api) 24 | record._set('error', item.get('error')) 25 | record._set('resource', item.get('resource')) 26 | records.append(record) 27 | return records 28 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbg/sevenbridges-python/ed3de94bab744a33f614111c1845af68b5433c54/sevenbridges/models/compound/__init__.py -------------------------------------------------------------------------------- /sevenbridges/models/compound/analysis_cost.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import StringField, CompoundField 3 | from sevenbridges.models.compound.analysis_cost_breakdown import ( 4 | AnalysisCostBreakdown 5 | ) 6 | 7 | 8 | class AnalysisCost(Resource): 9 | """ 10 | AnalysisCost resource contains an information regarding the currency, the 11 | monet value and breakdown of a analysis cost. 12 | """ 13 | currency = StringField(read_only=True) 14 | amount = StringField(read_only=True) 15 | breakdown = CompoundField(AnalysisCostBreakdown, read_only=True) 16 | 17 | def __str__(self): 18 | return ( 19 | f'' 21 | ) 22 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/analysis_cost_breakdown.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import FloatField 3 | 4 | 5 | class AnalysisCostBreakdown(Resource): 6 | """ 7 | AnalysisCostBreakdown resource contains price breakdown by storage and 8 | computation. 9 | """ 10 | storage = FloatField(read_only=True) 11 | computation = FloatField(read_only=True) 12 | data_transfer_in = FloatField(read_only=True) 13 | 14 | def __str__(self): 15 | return ( 16 | f'' 16 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/billing/project_breakdown.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.models.compound.price import Price 3 | from sevenbridges.models.compound.billing.task_breakdown import TaskBreakdown 4 | from sevenbridges.meta.fields import ( 5 | HrefField, CompoundField, CompoundListField 6 | ) 7 | 8 | 9 | class ProjectBreakdown(Resource): 10 | """ 11 | Project breakdown resource contains information regarding 12 | billing group project breakdown costs. 13 | """ 14 | href = HrefField(read_only=True) 15 | analysis_spending = CompoundField(Price, read_only=True) 16 | task_breakdown = CompoundListField(TaskBreakdown, read_only=True) 17 | 18 | def __str__(self): 19 | return f'' 20 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/billing/task_breakdown.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.models.compound.price import Price 3 | from sevenbridges.meta.fields import ( 4 | StringField, HrefField, CompoundField, DateTimeField 5 | ) 6 | 7 | 8 | class TaskBreakdown(Resource): 9 | """ 10 | Task breakdown resource contains information regarding 11 | billing group analysis breakdown costs. 12 | """ 13 | href = HrefField(read_only=True) 14 | runner_username = StringField(read_only=True) 15 | time_started = DateTimeField(read_only=True) 16 | time_finished = DateTimeField(read_only=True) 17 | task_cost = CompoundField(Price, read_only=True) 18 | 19 | def __str__(self): 20 | return f'' 21 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/egress_cost.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import StringField 3 | 4 | 5 | class EgressCost(Resource): 6 | """ 7 | EgressCost resource contains an information regarding the currency and the 8 | monet value of a egress cost. 9 | """ 10 | currency = StringField(read_only=True) 11 | amount = StringField(read_only=True) 12 | 13 | def __str__(self): 14 | return f'' 15 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/error.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import IntegerField, StringField 3 | 4 | 5 | class Error(Resource): 6 | """ 7 | Error resource describes the error that happened and provides 8 | http status, custom codes and messages as well as the link to 9 | online resources. 10 | """ 11 | status = IntegerField(read_only=True) 12 | code = IntegerField(read_only=True) 13 | message = StringField(read_only=True) 14 | more_info = StringField(read_only=True) 15 | 16 | def __str__(self): 17 | return f'' 18 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/files/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbg/sevenbridges-python/ed3de94bab744a33f614111c1845af68b5433c54/sevenbridges/models/compound/files/__init__.py -------------------------------------------------------------------------------- /sevenbridges/models/compound/files/download_info.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import HrefField 3 | 4 | 5 | class DownloadInfo(Resource): 6 | """ 7 | Download info resource contains download url for the file. 8 | """ 9 | url = HrefField(read_only=True) 10 | 11 | def __str__(self): 12 | return f'' 13 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/files/file_origin.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import StringField 3 | 4 | 5 | class FileOrigin(Resource): 6 | """ 7 | File origin resource contains information about origin of a file. 8 | Among others it contains information about the task if the file 9 | was produced during executions of a analysis. 10 | """ 11 | task = StringField(read_only=True) 12 | 13 | def __str__(self): 14 | return f'' 15 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/files/file_storage.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import StringField 3 | 4 | 5 | class FileStorage(Resource): 6 | """ 7 | File storage resource contains information about the storage location 8 | of the file if the file is imported on or exported to an external volume. 9 | """ 10 | type = StringField(read_only=True) 11 | volume = StringField(read_only=True) 12 | location = StringField(read_only=True) 13 | 14 | def __str__(self): 15 | return f'' 16 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/files/metadata.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.comp_mutable_dict import CompoundMutableDict 2 | from sevenbridges.meta.resource import Resource 3 | 4 | 5 | # noinspection PyProtectedMember 6 | class Metadata(CompoundMutableDict, Resource): 7 | """ 8 | File metadata resource. 9 | """ 10 | _name = 'metadata' 11 | 12 | def __init__(self, **kwargs): 13 | super().__init__(**kwargs) 14 | 15 | def __getitem__(self, item): 16 | try: 17 | return self._parent._data[self._name][item] 18 | except KeyError: 19 | return None 20 | 21 | def __eq__(self, other): 22 | if type(other) is not type(self): 23 | return False 24 | return self is other or dict(self) == dict(other) 25 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/import_result.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.fields import CompoundField 2 | from sevenbridges.meta.resource import Resource 3 | from sevenbridges.models.file import File 4 | from sevenbridges.models.compound.error import Error 5 | 6 | 7 | class FileImportResult(Resource): 8 | """ 9 | File result resource used for actions that may return resource or error out 10 | """ 11 | error = CompoundField(Error, read_only=True) 12 | resource = CompoundField(File, read_only=True) 13 | 14 | def __str__(self): 15 | return f'{str(self.error) if self.error else str(self.resource)}' 16 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/jobs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbg/sevenbridges-python/ed3de94bab744a33f614111c1845af68b5433c54/sevenbridges/models/compound/jobs/__init__.py -------------------------------------------------------------------------------- /sevenbridges/models/compound/jobs/job.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.fields import ( 2 | StringField, DateTimeField, CompoundField, BooleanField 3 | ) 4 | from sevenbridges.meta.resource import Resource 5 | from sevenbridges.models.compound.jobs.job_docker import JobDocker 6 | from sevenbridges.models.compound.jobs.job_instance import Instance 7 | from sevenbridges.models.compound.jobs.job_log import Logs 8 | 9 | 10 | class Job(Resource): 11 | """ 12 | Job resource contains information for a single executed node 13 | in the analysis. 14 | """ 15 | name = StringField(read_only=True) 16 | start_time = DateTimeField(read_only=True) 17 | end_time = DateTimeField(read_only=True) 18 | status = StringField(read_only=True) 19 | command_line = StringField(read_only=True) 20 | retried = BooleanField(read_only=True) 21 | instance = CompoundField(Instance, read_only=True) 22 | docker = CompoundField(JobDocker, read_only=True) 23 | logs = CompoundField(Logs, read_only=True) 24 | 25 | def __str__(self): 26 | return f'' 27 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/jobs/job_docker.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import StringField 3 | 4 | 5 | class JobDocker(Resource): 6 | """ 7 | JobDocker resource contains information for a docker image that was 8 | used for execution of a single job. 9 | """ 10 | checksum = StringField(read_only=True) 11 | 12 | def __str__(self): 13 | return f'' 20 | ) 21 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/jobs/job_instance_disk.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.fields import StringField, IntegerField 2 | from sevenbridges.meta.resource import Resource 3 | 4 | 5 | class Disk(Resource): 6 | """ 7 | Disk resource contains information about EBS disk size. 8 | """ 9 | size = IntegerField(read_only=True) 10 | unit = StringField(read_only=True) 11 | type = StringField(read_only=True) 12 | 13 | def __str__(self): 14 | return f'' 15 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/jobs/job_log.py: -------------------------------------------------------------------------------- 1 | import re 2 | import logging 3 | 4 | 5 | from sevenbridges.errors import SbgError, ReadOnlyPropertyError 6 | from sevenbridges.meta.comp_mutable_dict import CompoundMutableDict 7 | from sevenbridges.meta.resource import Resource 8 | from sevenbridges.models.file import File 9 | 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | # noinspection PyProtectedMember 15 | class Logs(CompoundMutableDict, Resource): 16 | """ 17 | Task output resource. 18 | """ 19 | _name = 'logs' 20 | 21 | def __init__(self, **kwargs): 22 | super().__init__(**kwargs) 23 | 24 | def __getitem__(self, item): 25 | try: 26 | log = self._parent._data[self._name][item] 27 | match = re.match(r'.*files/(.*)/.*', log) 28 | if match: 29 | file_id = match.groups()[0] 30 | return File(id=file_id, api=self._api) 31 | else: 32 | raise SbgError(f'Unable to fetch {item} log file!') 33 | except Exception as e: 34 | logger.debug( 35 | 'Failed to retrieve log file due to an error: %s', str(e) 36 | ) 37 | 38 | def __setitem__(self, key, value): 39 | raise ReadOnlyPropertyError('Can not modify read only properties.') 40 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/limits/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbg/sevenbridges-python/ed3de94bab744a33f614111c1845af68b5433c54/sevenbridges/models/compound/limits/__init__.py -------------------------------------------------------------------------------- /sevenbridges/models/compound/limits/rate.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.fields import IntegerField, DateTimeField 2 | from sevenbridges.meta.resource import Resource 3 | 4 | 5 | class Rate(Resource): 6 | """ 7 | Rate resource. 8 | """ 9 | limit = IntegerField(read_only=True) 10 | remaining = IntegerField(read_only=True) 11 | reset = DateTimeField(read_only=True) 12 | 13 | def __str__(self): 14 | return f'' 15 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/markers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbg/sevenbridges-python/ed3de94bab744a33f614111c1845af68b5433c54/sevenbridges/models/compound/markers/__init__.py -------------------------------------------------------------------------------- /sevenbridges/models/compound/markers/position.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.comp_mutable_dict import CompoundMutableDict 2 | from sevenbridges.meta.resource import Resource 3 | 4 | 5 | class MarkerPosition(CompoundMutableDict, Resource): 6 | """ 7 | Marker position resource 8 | """ 9 | _name = 'position' 10 | 11 | def __init__(self, **kwargs): 12 | super().__init__(**kwargs) 13 | 14 | def __getitem__(self, item): 15 | try: 16 | # noinspection PyProtectedMember 17 | return self._parent._data[self._name][item] 18 | except KeyError: 19 | return None 20 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/measurement.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import StringField, FloatField, CompoundField 3 | 4 | 5 | class StorageCost(Resource): 6 | """ 7 | Storage cost resource contains breakdown information regarding the amount 8 | of cost and the currency used. 9 | """ 10 | amount = FloatField(read_only=True) 11 | currency = StringField(read_only=True) 12 | 13 | def __str__(self): 14 | return ( 15 | f'' 17 | ) 18 | 19 | 20 | class Measurement(Resource): 21 | """ 22 | Measurement resource contains an information regarding the size and the 23 | unit of a certain resource. 24 | """ 25 | size = FloatField(read_only=True) 26 | unit = StringField(read_only=True) 27 | cost = CompoundField(StorageCost, read_only=True) 28 | 29 | def __str__(self): 30 | return f'' 31 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/price.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import StringField, FloatField, CompoundField 3 | from sevenbridges.models.compound.price_breakdown import Breakdown 4 | 5 | 6 | class Price(Resource): 7 | """ 8 | Price resource contains an information regarding the currency and the 9 | monet value of a certain resource. 10 | """ 11 | currency = StringField(read_only=True) 12 | amount = FloatField(read_only=True) 13 | breakdown = CompoundField(Breakdown, read_only=True) 14 | 15 | def __str__(self): 16 | return f'' 17 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/price_breakdown.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import StringField 3 | 4 | 5 | class Breakdown(Resource): 6 | """ 7 | Breakdown resource contains price breakdown by storage and computation. 8 | """ 9 | storage = StringField(read_only=True) 10 | computation = StringField(read_only=True) 11 | data_transfer = StringField(read_only=True) 12 | 13 | def __str__(self): 14 | if self.data_transfer: 15 | return ( 16 | f'' 19 | ) 20 | return ( 21 | f'' 23 | ) 24 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/projects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbg/sevenbridges-python/ed3de94bab744a33f614111c1845af68b5433c54/sevenbridges/models/compound/projects/__init__.py -------------------------------------------------------------------------------- /sevenbridges/models/compound/projects/permissions.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.comp_mutable_dict import CompoundMutableDict 2 | from sevenbridges.meta.resource import Resource 3 | 4 | 5 | # noinspection PyProtectedMember 6 | class Permissions(CompoundMutableDict, Resource): 7 | """ 8 | Members permissions resource. 9 | """ 10 | _name = 'permissions' 11 | 12 | def __init__(self, **kwargs): 13 | super().__init__(**kwargs) 14 | 15 | def __getitem__(self, item): 16 | try: 17 | return self._parent._data[self._name][item] 18 | except KeyError: 19 | return None 20 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/projects/settings.py: -------------------------------------------------------------------------------- 1 | # noinspection PyProtectedMember 2 | from sevenbridges.meta.comp_mutable_dict import CompoundMutableDict 3 | from sevenbridges.meta.resource import Resource 4 | 5 | 6 | class Settings(CompoundMutableDict, Resource): 7 | """ 8 | Project settings resource. 9 | """ 10 | _name = 'settings' 11 | 12 | def __init__(self, **kwargs): 13 | super().__init__(**kwargs) 14 | 15 | def __getitem__(self, item): 16 | try: 17 | # noinspection PyProtectedMember 18 | return self._parent._data[self._name][item] 19 | except KeyError: 20 | return None 21 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/tasks/__init__.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.models.enums import FileApiFormats 2 | from sevenbridges.models.file import File 3 | 4 | 5 | def map_input_output(item, api): 6 | """ 7 | Maps item to appropriate sevebridges object. 8 | :param item: Input/Output value. 9 | :param api: Api instance. 10 | :return: Mapped object. 11 | """ 12 | if isinstance(item, list): 13 | return [map_input_output(it, api) for it in item] 14 | 15 | elif isinstance(item, dict) and 'class' in item: 16 | file_class_list = [ 17 | FileApiFormats.FILE.lower(), 18 | FileApiFormats.FOLDER.lower() 19 | ] 20 | if item['class'].lower() in file_class_list: 21 | _secondary_files = [] 22 | for _file in item.get('secondaryFiles', []): 23 | _secondary_files.append({'id': _file['path']}) 24 | data = { 25 | 'id': item['path'] 26 | } 27 | # map class to type 28 | if item['class'].lower() == FileApiFormats.FOLDER.lower(): 29 | data['type'] = 'folder' 30 | else: 31 | data['type'] = 'file' 32 | 33 | # map cwl 1 file name 34 | if 'basename' in item: 35 | data['name'] = item['basename'] 36 | 37 | data.update( 38 | {k: item[k] for k in item if k not in ['path', 'basename']} 39 | ) 40 | data.update({ 41 | '_secondary_files': _secondary_files or None, 42 | 'fetched': True 43 | }) 44 | return File(api=api, **data) 45 | else: 46 | return item 47 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/tasks/batch_by.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | 3 | 4 | # noinspection PyUnresolvedReferences,PyProtectedMember 5 | class BatchBy(Resource, dict): 6 | """ 7 | Task batch by resource. 8 | """ 9 | _name = 'batch_by' 10 | 11 | # noinspection PyMissingConstructor 12 | def __init__(self, **kwargs): 13 | self.parent = kwargs.pop('_parent') 14 | self.api = kwargs.pop('api') 15 | for k, v in kwargs.items(): 16 | super().__setitem__(k, v) 17 | 18 | def __setitem__(self, key, value): 19 | super().__setitem__(key, value) 20 | self.parent._data[self._name][key] = value 21 | if self._name not in self.parent._dirty: 22 | self.parent._dirty.update({self._name: {}}) 23 | self.parent._dirty[self._name][key] = value 24 | 25 | def __getitem__(self, item): 26 | try: 27 | return self.parent._data[self._name][item] 28 | except KeyError: 29 | return None 30 | 31 | def __repr__(self): 32 | values = {} 33 | for k, _ in self.items(): 34 | values[k] = self[k] 35 | return str(values) 36 | 37 | __str__ = __repr__ 38 | 39 | def update(self, e=None, **f): 40 | other = {} 41 | if e: 42 | other.update(e, **f) 43 | else: 44 | other.update(**f) 45 | for k, v in other.items(): 46 | if other[k] != self[k]: 47 | self[k] = other[k] 48 | 49 | def equals(self, other): 50 | if not type(other) == type(self): 51 | return False 52 | return ( 53 | self is other or 54 | self._parent._data[self._name] == other._parent._data[self._name] 55 | ) 56 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/tasks/batch_group.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import StringField, DictField 3 | 4 | 5 | class BatchGroup(Resource): 6 | """ 7 | Batch group for a batch task. 8 | Represents the group that is assigned to the child task 9 | from the batching criteria that was used when the task was started. 10 | """ 11 | value = StringField(read_only=True) 12 | fields = DictField(read_only=True) 13 | 14 | def __str__(self): 15 | return '' 16 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/tasks/execution_status.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.fields import IntegerField, StringField, BooleanField 2 | from sevenbridges.meta.resource import Resource 3 | 4 | 5 | class ExecutionStatus(Resource): 6 | """ 7 | Task execution status resource. 8 | 9 | Contains information about the number of completed task steps, 10 | total number of task steps, current execution message and information 11 | regarding computation limits. 12 | 13 | In case of a batch task it also contains the number of queued, running, 14 | completed, failed and aborted tasks. 15 | """ 16 | steps_completed = IntegerField(read_only=True) 17 | steps_total = IntegerField(read_only=True) 18 | message = StringField(read_only=True) 19 | message_code = StringField(read_only=True) 20 | queued = IntegerField(read_only=True) 21 | running = IntegerField(read_only=True) 22 | completed = IntegerField(read_only=True) 23 | failed = IntegerField(read_only=True) 24 | aborted = IntegerField(read_only=True) 25 | system_limit = BooleanField(read_only=True) 26 | account_limit = BooleanField(read_only=True) 27 | instance_init = BooleanField(read_only=True) 28 | queued_duration = IntegerField(read_only=True) 29 | running_duration = IntegerField(read_only=True) 30 | execution_duration = IntegerField(read_only=True) 31 | duration = IntegerField(read_only=True) 32 | 33 | def __str__(self): 34 | return '' 35 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/tasks/input.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.comp_mutable_dict import CompoundMutableDict 2 | from sevenbridges.meta.resource import Resource 3 | 4 | # noinspection PyProtectedMember 5 | from sevenbridges.models.compound.tasks import map_input_output 6 | 7 | 8 | # noinspection PyProtectedMember 9 | class Input(CompoundMutableDict, Resource): 10 | """ 11 | Task input resource. 12 | """ 13 | _name = 'inputs' 14 | 15 | def __init__(self, **kwargs): 16 | super().__init__(**kwargs) 17 | 18 | def __getitem__(self, item): 19 | # noinspection PyBroadException 20 | try: 21 | inputs = self._parent._data[self._name][item] 22 | return map_input_output(inputs, self._api) 23 | except Exception: 24 | return None 25 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/tasks/output.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.errors import ReadOnlyPropertyError 2 | from sevenbridges.meta.comp_mutable_dict import CompoundMutableDict 3 | from sevenbridges.meta.resource import Resource 4 | from sevenbridges.models.compound.tasks import map_input_output 5 | 6 | 7 | # noinspection PyProtectedMember 8 | class Output(CompoundMutableDict, Resource): 9 | """ 10 | Task output resource. 11 | """ 12 | _name = 'outputs' 13 | 14 | def __init__(self, **kwargs): 15 | super().__init__(**kwargs) 16 | 17 | def __getitem__(self, item): 18 | # noinspection PyBroadException 19 | try: 20 | output = self._parent._data[self._name][item] 21 | return map_input_output(output, self._api) 22 | except Exception: 23 | return None 24 | 25 | def __setitem__(self, key, value): 26 | raise ReadOnlyPropertyError('Can not modify read only properties.') 27 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/volumes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbg/sevenbridges-python/ed3de94bab744a33f614111c1845af68b5433c54/sevenbridges/models/compound/volumes/__init__.py -------------------------------------------------------------------------------- /sevenbridges/models/compound/volumes/import_destination.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.meta.fields import StringField 3 | 4 | 5 | class ImportDestination(Resource): 6 | """ 7 | ImportDestination resource describes the location of the file 8 | imported on to SevenBridges platform or related product. 9 | """ 10 | project = StringField(read_only=True) 11 | parent = StringField(read_only=True) 12 | name = StringField(read_only=True) 13 | 14 | def __str__(self): 15 | return f'' 16 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/volumes/properties.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.comp_mutable_dict import CompoundMutableDict 2 | from sevenbridges.meta.resource import Resource 3 | 4 | 5 | # noinspection PyProtectedMember 6 | class VolumeProperties(CompoundMutableDict, Resource): 7 | """ 8 | Volume permissions resource. 9 | """ 10 | _name = 'properties' 11 | 12 | def __init__(self, **kwargs): 13 | super().__init__(**kwargs) 14 | 15 | def __getitem__(self, item): 16 | try: 17 | return self._parent._data[self._name][item] 18 | except KeyError: 19 | return None 20 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/volumes/service.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.comp_mutable_dict import CompoundMutableDict 2 | 3 | from sevenbridges.meta.resource import Resource 4 | 5 | 6 | class VolumeService(CompoundMutableDict, Resource): 7 | """ 8 | Volume Service resource. Contains information about external 9 | storage provider. 10 | """ 11 | _name = 'service' 12 | 13 | def __init__(self, **kwargs): 14 | super().__init__(**kwargs) 15 | 16 | def __getitem__(self, item): 17 | try: 18 | # noinspection PyProtectedMember 19 | return self._parent._data[self._name][item] 20 | except KeyError: 21 | return None 22 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/volumes/volume_file.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.fields import StringField 2 | from sevenbridges.meta.resource import Resource 3 | 4 | 5 | class VolumeFile(Resource): 6 | """ 7 | VolumeFile resource describes the location of the file 8 | on the external volume. 9 | """ 10 | volume = StringField(read_only=True) 11 | location = StringField(read_only=True) 12 | 13 | def __str__(self): 14 | return f'' 15 | 16 | def __eq__(self, other): 17 | if type(other) is not type(self): 18 | return False 19 | return self is other or self.location == other.location 20 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/volumes/volume_object.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.fields import StringField, DictField 2 | from sevenbridges.meta.resource import Resource 3 | 4 | 5 | class VolumeObject(Resource): 6 | """ 7 | Volume object resource contains information about single 8 | file (object) entry in a specific volume. 9 | """ 10 | href = StringField(read_only=True) 11 | location = StringField(read_only=True) 12 | volume = StringField(read_only=True) 13 | type = StringField(read_only=True) 14 | metadata = DictField(read_only=True) 15 | 16 | def __str__(self): 17 | return f'' 18 | -------------------------------------------------------------------------------- /sevenbridges/models/compound/volumes/volume_prefix.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.fields import StringField 2 | from sevenbridges.meta.resource import Resource 3 | 4 | 5 | class VolumePrefix(Resource): 6 | """ 7 | Volume prefix resource contains information about volume prefixes 8 | """ 9 | href = StringField(read_only=True) 10 | prefix = StringField(read_only=True) 11 | volume = StringField(read_only=True) 12 | 13 | def __str__(self): 14 | return f'' 15 | -------------------------------------------------------------------------------- /sevenbridges/models/dataset.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sevenbridges.models.link import Link 4 | from sevenbridges.models.member import Member 5 | from sevenbridges.decorators import inplace_reload 6 | from sevenbridges.meta.resource import Resource 7 | from sevenbridges.meta.collection import Collection 8 | from sevenbridges.meta.transformer import Transform 9 | from sevenbridges.meta.fields import HrefField, StringField 10 | 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class Dataset(Resource): 16 | """Central resource for managing datasets.""" 17 | 18 | _URL = { 19 | 'query': '/datasets', 20 | 'owned_by': '/datasets/{username}', 21 | 'get': '/datasets/{id}', 22 | 'delete': '/datasets/{id}', 23 | 'members': '/datasets/{id}/members', 24 | 'member': '/datasets/{id}/members/{username}', 25 | 'permissions': '/datasets/{id}/members/{username}/permissions', 26 | } 27 | 28 | href = HrefField(read_only=True) 29 | id = StringField(read_only=True) 30 | name = StringField(read_only=False) 31 | description = StringField(read_only=False) 32 | 33 | def __str__(self): 34 | return f'' 35 | 36 | def __eq__(self, other): 37 | if type(other) is not type(self): 38 | return False 39 | return self is other or self.id == other.id 40 | 41 | @classmethod 42 | def query(cls, visibility=None, api=None): 43 | """Query ( List ) datasets 44 | :param visibility: If provided as 'public', retrieves public datasets 45 | :param api: Api instance 46 | :return: Collection object 47 | """ 48 | api = api if api else cls._API 49 | 50 | return super()._query( 51 | url=cls._URL['query'], 52 | visibility=visibility, 53 | fields='_all', api=api 54 | ) 55 | 56 | @classmethod 57 | def get_owned_by(cls, username, api=None): 58 | """Query ( List ) datasets by owner 59 | :param api: Api instance 60 | :param username: Owner username 61 | :return: Collection object 62 | """ 63 | api = api if api else cls._API 64 | 65 | return super()._query( 66 | url=cls._URL['owned_by'].format(username=username), 67 | fields='_all', 68 | api=api 69 | ) 70 | 71 | @inplace_reload 72 | def save(self, inplace=True): 73 | """Save all modification to the dataset on the server. 74 | :param inplace: Apply edits on the current instance or get a new one. 75 | :return: Dataset instance. 76 | """ 77 | modified_data = self._modified_data() 78 | if modified_data: 79 | dataset_request_data = {} 80 | 81 | name = modified_data.pop('name', None) 82 | description = modified_data.pop('description', None) 83 | dataset_request_data.update(modified_data) 84 | 85 | if name: 86 | dataset_request_data['name'] = name 87 | 88 | if description: 89 | dataset_request_data['description'] = description 90 | 91 | response = self._api.patch( 92 | url=self._URL['get'].format(id=self.id), 93 | data=dataset_request_data 94 | ) 95 | data = response.json() 96 | 97 | dataset = Dataset(api=self._api, **data) 98 | return dataset 99 | 100 | def get_members(self, api=None): 101 | """Retrieve dataset members 102 | :param api: Api instance 103 | :return: Collection object 104 | """ 105 | api = api or self._API 106 | 107 | response = api.get(url=self._URL['members'].format(id=self.id)) 108 | 109 | data = response.json() 110 | total = response.headers['x-total-matching-query'] 111 | members = [Member(api=api, **member) for member in data['items']] 112 | links = [Link(**link) for link in data['links']] 113 | href = data['href'] 114 | 115 | return Collection( 116 | resource=Member, 117 | href=href, 118 | total=total, 119 | items=members, 120 | links=links, 121 | api=api 122 | ) 123 | 124 | def get_member(self, username, api=None): 125 | """Retrieve dataset member 126 | :param username: Member name 127 | :param api: Api instance 128 | :return: Member object 129 | """ 130 | api = api if api else self._API 131 | 132 | response = api.get( 133 | url=self._URL['member'].format(id=self.id, username=username), 134 | ) 135 | data = response.json() 136 | return Member(api=api, **data) 137 | 138 | def add_member(self, username, permissions, api=None): 139 | """Add member to a dataset 140 | :param username: Member username 141 | :param permissions: Permissions dict 142 | :param api: Api instance 143 | :return: New member instance 144 | """ 145 | api = api or self._API 146 | data = { 147 | 'username': username, 148 | 'permissions': permissions 149 | } 150 | 151 | response = api.post( 152 | url=self._URL['members'].format(id=self.id), 153 | data=data 154 | ) 155 | data = response.json() 156 | return Member(api=api, **data) 157 | 158 | def remove_member(self, member, api=None): 159 | """Remove member from a dataset 160 | :param member: Member username 161 | :param api: Api instance 162 | :return: None 163 | """ 164 | api = api or self._API 165 | username = Transform.to_member(member) 166 | 167 | api.delete( 168 | url=self._URL['member'].format( 169 | id=self.id, 170 | username=username 171 | ) 172 | ) 173 | -------------------------------------------------------------------------------- /sevenbridges/models/division.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.fields import HrefField, StringField 2 | from sevenbridges.meta.resource import Resource 3 | 4 | 5 | class Division(Resource): 6 | """ 7 | Central resource for managing divisions. 8 | """ 9 | _URL = { 10 | 'query': '/divisions', 11 | 'get': '/divisions/{id}', 12 | } 13 | 14 | href = HrefField(read_only=True) 15 | id = StringField(read_only=True) 16 | name = StringField(read_only=True) 17 | 18 | def __str__(self): 19 | return f'' 20 | 21 | def __eq__(self, other): 22 | if type(other) is not type(self): 23 | return False 24 | return self is other or self.id == other.id 25 | 26 | @classmethod 27 | def query(cls, offset=None, limit=None, api=None): 28 | """ 29 | Query (List) divisions. 30 | 31 | :param offset: Pagination offset. 32 | :param limit: Pagination limit. 33 | :param api: Api instance. 34 | :return: Collection object. 35 | """ 36 | api = api if api else cls._API 37 | return super()._query( 38 | url=cls._URL['query'], offset=offset, limit=limit, 39 | fields='_all', api=api 40 | ) 41 | 42 | def get_teams(self, offset=None, limit=None): 43 | return self._api.teams.query( 44 | division=self.id, offset=offset, limit=limit 45 | ) 46 | 47 | def get_members(self, role=None, offset=None, limit=None): 48 | return self._api.users.query(self, role=role, offset=offset, 49 | limit=limit) 50 | -------------------------------------------------------------------------------- /sevenbridges/models/drs_import.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sevenbridges.errors import SbgError 4 | from sevenbridges.meta.fields import ( 5 | HrefField, StringField, DateTimeField, CompoundListField 6 | ) 7 | from sevenbridges.meta.resource import Resource 8 | from sevenbridges.meta.transformer import Transform 9 | from sevenbridges.models.compound.import_result import FileImportResult 10 | from sevenbridges.models.file import File 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class DRSImportBulk(Resource): 16 | """ 17 | Central resource for managing DRS imports. 18 | """ 19 | _URL = { 20 | 'get': '/bulk/drs/imports/{id}', 21 | 'create': '/bulk/drs/imports/create', 22 | } 23 | 24 | id = StringField(read_only=True) 25 | href = HrefField(read_only=True) 26 | result = CompoundListField(FileImportResult, read_only=True) 27 | _result_files = [] # cache for result_files property 28 | state = StringField(read_only=True) 29 | started_on = DateTimeField(read_only=True) 30 | finished_on = DateTimeField(read_only=True) 31 | 32 | def __str__(self): 33 | return f'' 34 | 35 | def __eq__(self, other): 36 | if type(other) is not type(self): 37 | return False 38 | return self is other or self.id == other.id 39 | 40 | @property 41 | def result_files(self): 42 | """ 43 | Retrieve files that were successfully imported. 44 | :return: List of File objects 45 | """ 46 | try: 47 | cached_file_ids = set([ 48 | file.resource.id for file in self._result_files 49 | ]) 50 | 51 | imported_file_ids = set([ 52 | file.resource.id 53 | for file in self.result if file.resource 54 | ]) 55 | file_ids_to_retrieve = imported_file_ids - cached_file_ids 56 | if file_ids_to_retrieve: 57 | files = File.bulk_get( 58 | files=file_ids_to_retrieve, api=self._api 59 | ) 60 | self._result_files.extend(files) 61 | return self._result_files if self._result_files else None 62 | except TypeError: 63 | return None 64 | 65 | @classmethod 66 | def bulk_get(cls, import_job_id, api=None): 67 | """ 68 | Retrieve DRS bulk import details 69 | :param import_job_id: Import id to be retrieved. 70 | :param api: Api instance. 71 | :return: DRSImportBulk object. 72 | """ 73 | api = api or cls._API 74 | 75 | if not import_job_id: 76 | raise SbgError('DRS import is required!') 77 | elif not isinstance(import_job_id, str): 78 | raise SbgError('Invalid DRS import parameter!') 79 | 80 | response = api.get( 81 | url=cls._URL['get'].format(id=import_job_id) 82 | ).json() 83 | return DRSImportBulk(api=api, **response) 84 | 85 | @classmethod 86 | def bulk_submit( 87 | cls, imports, tags=None, conflict_resolution='SKIP', api=None 88 | ): 89 | """ 90 | Submit DRS bulk import 91 | :param imports: List of dicts describing a wanted import. 92 | :param tags: list of tags to be applied. 93 | :param conflict_resolution: Type of file naming conflict resolution. 94 | :param api: Api instance. 95 | :return: DRSImportBulk object. 96 | """ 97 | if not imports: 98 | raise SbgError('Imports are required') 99 | 100 | api = api or cls._API 101 | 102 | items = [] 103 | for import_ in imports: 104 | project = import_.get('project') 105 | parent = import_.get('parent') 106 | 107 | if project and parent: 108 | raise SbgError( 109 | 'Project and parent identifiers are mutually exclusive' 110 | ) 111 | elif project: 112 | import_['project'] = Transform.to_project(project) 113 | elif parent: 114 | import_['parent'] = Transform.to_file(parent) 115 | else: 116 | raise SbgError('Project or parent identifier is required.') 117 | 118 | items.append(import_) 119 | 120 | data = { 121 | 'conflict_resolution': conflict_resolution, 122 | 'tags': tags, 123 | 'items': items 124 | } 125 | response = api.post(url=cls._URL['create'], data=data).json() 126 | return DRSImportBulk(api=api, **response) 127 | -------------------------------------------------------------------------------- /sevenbridges/models/endpoints.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sevenbridges.meta.resource import Resource 4 | from sevenbridges.meta.fields import HrefField 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class Endpoints(Resource): 10 | """ 11 | Central resource for managing Endpoints. 12 | """ 13 | _URL = { 14 | 'get': '/' 15 | } 16 | 17 | rate_limit_url = HrefField(read_only=True) 18 | user_url = HrefField(read_only=True) 19 | users_url = HrefField(read_only=True) 20 | billing_url = HrefField(read_only=True) 21 | projects_url = HrefField(read_only=True) 22 | files_url = HrefField(read_only=True) 23 | tasks_url = HrefField(read_only=True) 24 | apps_url = HrefField(read_only=True) 25 | action_url = HrefField(read_only=True) 26 | upload_url = HrefField(read_only=True) 27 | 28 | @classmethod 29 | def get(cls, api=None, **kwargs): 30 | """ 31 | Get api links. 32 | :param api: Api instance. 33 | :return: Endpoints object. 34 | """ 35 | api = api if api else cls._API 36 | extra = { 37 | 'resource': cls.__name__, 38 | 'query': {} 39 | } 40 | logger.info('Getting resources', extra=extra) 41 | endpoints = api.get(url=cls._URL['get']).json() 42 | return Endpoints(api=api, **endpoints) 43 | 44 | def __str__(self): 45 | return '' 46 | -------------------------------------------------------------------------------- /sevenbridges/models/enums.py: -------------------------------------------------------------------------------- 1 | class RequestParameters: 2 | MAX_URL_LENGTH = 6000 3 | DEFAULT_TIMEOUT = 300 4 | DEFAULT_RETRY_COUNT = 6 5 | DEFAULT_BACKOFF_FACTOR = 1 6 | DEFAULT_BULK_LIMIT = 100 7 | 8 | 9 | class PartSize: 10 | KB = 1024 11 | MB = 1024 * KB 12 | GB = 1024 * MB 13 | TB = 1024 * GB 14 | MAXIMUM_UPLOAD_SIZE = 5 * GB 15 | MAXIMUM_OBJECT_SIZE = 5 * TB 16 | MAXIMUM_TOTAL_PARTS = 10000 17 | 18 | DOWNLOAD_MINIMUM_PART_SIZE = 5 * MB 19 | UPLOAD_MINIMUM_PART_SIZE = 5 * MB 20 | UPLOAD_RECOMMENDED_SIZE = 32 * MB 21 | 22 | 23 | class TransferState: 24 | ABORTED = 'ABORTED' 25 | RUNNING = 'RUNNING' 26 | PAUSED = 'PAUSED' 27 | COMPLETED = 'COMPLETED' 28 | PREPARING = 'PREPARING' 29 | STOPPED = 'STOPPED' 30 | FAILED = 'FAILED' 31 | 32 | 33 | class VolumeType: 34 | S3 = 'S3' 35 | GOOGLE = 'GCS' 36 | OSS = 'OSS' 37 | 38 | 39 | class VolumeAccessMode: 40 | READ_ONLY = 'RO' 41 | READ_WRITE = 'RW' 42 | 43 | 44 | class FileStorageType: 45 | VOLUME = 'VOLUME' 46 | PLATFORM = 'PLATFORM' 47 | 48 | 49 | class FileApiFormats: 50 | FILE = 'File' 51 | FOLDER = 'Directory' 52 | 53 | 54 | class ImportExportState: 55 | PENDING = 'PENDING' 56 | RUNNING = 'RUNNING' 57 | COMPLETED = 'COMPLETED' 58 | FAILED = 'FAILED' 59 | 60 | 61 | class TaskStatus: 62 | DRAFT = 'DRAFT' 63 | CREATING = 'CREATING' 64 | QUEUED = 'QUEUED' 65 | RUNNING = 'RUNNING' 66 | COMPLETED = 'COMPLETED' 67 | ABORTED = 'ABORTED' 68 | ABORTING = 'ABORTING' 69 | FAILED = 'FAILED' 70 | 71 | terminal_states = [COMPLETED, FAILED, ABORTED] 72 | 73 | 74 | class FeedbackType: 75 | IDEA = 'IDEA' 76 | THOUGHT = 'THOUGHT' 77 | PROBLEM = 'PROBLEM' 78 | 79 | 80 | class AppRawFormat: 81 | JSON = 'json' 82 | YAML = 'yaml' 83 | 84 | 85 | class AppCopyStrategy: 86 | CLONE = 'clone' 87 | DIRECT = 'direct' 88 | TRANSIENT = 'transient' 89 | CLONE_DIRECT = 'clone_direct' 90 | 91 | 92 | class AutomationRunActions: 93 | STOP = 'stop' 94 | RERUN = 'rerun' 95 | 96 | 97 | class AsyncJobStates: 98 | RUNNING = 'RUNNING' 99 | FINISHED = 'FINISHED' 100 | SUBMITTED = 'SUBMITTED' 101 | RESOLVING = 'RESOLVING' 102 | 103 | 104 | class AsyncFileOperations: 105 | COPY = 'copy' 106 | DELETE = 'delete' 107 | MOVE = 'move' 108 | 109 | 110 | class DivisionRole: 111 | MEMBER = 'member' 112 | ADMIN = 'admin' 113 | EXTERNAL_COLLABORATOR = 'external_collaborator' 114 | 115 | 116 | class AutomationStatus: 117 | CREATED = 'CREATED' 118 | FINISHED = 'FINISHED' 119 | ABORTED = 'ABORTED' 120 | FAILED = 'FAILED' 121 | RUNNING = 'RUNNING' 122 | SENT_TO_EXECUTION = 'SENT_TO_EXECUTION' 123 | QUEUED_FOR_EXECUTION = 'QUEUED_FOR_EXECUTION' 124 | QUEUED_FOR_TERMINATION = 'QUEUED_FOR_TERMINATION' 125 | 126 | terminal_states = [FINISHED, FAILED, ABORTED] 127 | -------------------------------------------------------------------------------- /sevenbridges/models/execution_details.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.resource import Resource 2 | from sevenbridges.models.compound.jobs.job import Job 3 | from sevenbridges.meta.fields import ( 4 | HrefField, DateTimeField, StringField, CompoundListField 5 | ) 6 | 7 | 8 | class ExecutionDetails(Resource): 9 | """ 10 | Task execution details. 11 | """ 12 | href = HrefField(read_only=True) 13 | start_time = DateTimeField(read_only=True) 14 | end_time = DateTimeField(read_only=True) 15 | status = StringField(read_only=True) 16 | message = StringField(read_only=True) 17 | jobs = CompoundListField(Job, read_only=True) 18 | 19 | def __str__(self): 20 | return '' 21 | -------------------------------------------------------------------------------- /sevenbridges/models/invoice.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.fields import ( 2 | HrefField, StringField, BooleanField, CompoundField 3 | ) 4 | from sevenbridges.meta.resource import Resource 5 | from sevenbridges.models.compound.billing.invoice_period import InvoicePeriod 6 | from sevenbridges.models.compound.price import Price 7 | 8 | 9 | class Invoice(Resource): 10 | """ 11 | Central resource for managing invoices. 12 | """ 13 | _URL = { 14 | 'query': '/billing/invoices', 15 | 'get': '/billing/invoices/{id}' 16 | } 17 | href = HrefField(read_only=True) 18 | id = StringField(read_only=True) 19 | pending = BooleanField(read_only=True) 20 | analysis_costs = CompoundField(Price, read_only=True) 21 | storage_costs = CompoundField(Price, read_only=True) 22 | total = CompoundField(Price, read_only=True) 23 | invoice_period = CompoundField(InvoicePeriod, read_only=True) 24 | 25 | def __str__(self): 26 | return f'' 27 | 28 | def __eq__(self, other): 29 | if type(other) is not type(self): 30 | return False 31 | return self is other or self.id == other.id 32 | 33 | @classmethod 34 | def query(cls, offset=None, limit=None, api=None): 35 | """ 36 | Query (List) invoices. 37 | :param offset: Pagination offset. 38 | :param limit: Pagination limit. 39 | :param api: Api instance. 40 | :return: Collection object. 41 | """ 42 | api = api if api else cls._API 43 | return super()._query( 44 | url=cls._URL['query'], offset=offset, limit=limit, fields='_all', 45 | api=api 46 | ) 47 | -------------------------------------------------------------------------------- /sevenbridges/models/link.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.fields import HrefField, StringField 2 | from sevenbridges.meta.resource import Resource 3 | 4 | 5 | class Link(Resource): 6 | """ 7 | Pagination links. 8 | """ 9 | href = HrefField(read_only=True) 10 | rel = StringField(read_only=True) 11 | method = StringField(read_only=True) 12 | 13 | def __str__(self): 14 | return ( 15 | f'' 16 | ) 17 | 18 | 19 | class VolumeLink(Resource): 20 | """ 21 | Pagination links for volumes. 22 | """ 23 | next = HrefField(read_only=True) 24 | 25 | def __str__(self): 26 | return f'' 27 | -------------------------------------------------------------------------------- /sevenbridges/models/marker.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sevenbridges.decorators import inplace_reload 4 | from sevenbridges.errors import ResourceNotModified 5 | from sevenbridges.meta.fields import ( 6 | HrefField, StringField, CompoundField, DateTimeField 7 | ) 8 | from sevenbridges.meta.resource import Resource 9 | from sevenbridges.meta.transformer import Transform 10 | from sevenbridges.models.compound.markers.position import MarkerPosition 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class Marker(Resource): 16 | _URL = { 17 | 'query': '/genome/markers', 18 | 'get': '/genome/markers/{id}', 19 | 'delete': '/genome/markers/{id}' 20 | } 21 | 22 | href = HrefField(read_only=True) 23 | id = StringField(read_only=True) 24 | file = StringField(read_only=True) 25 | name = StringField(read_only=False) 26 | chromosome = StringField(read_only=False) 27 | position = CompoundField(MarkerPosition, read_only=False) 28 | created_time = DateTimeField(read_only=True) 29 | created_by = StringField(read_only=True) 30 | 31 | def __str__(self): 32 | return f'' 33 | 34 | def __eq__(self, other): 35 | if type(other) is not type(self): 36 | return False 37 | return self is other or self.id == other.id 38 | 39 | @classmethod 40 | def query(cls, file, offset=None, limit=None, api=None): 41 | """ 42 | Queries genome markers on a file. 43 | :param file: Genome file - Usually bam file. 44 | :param offset: Pagination offset. 45 | :param limit: Pagination limit. 46 | :param api: Api instance. 47 | :return: Collection object. 48 | """ 49 | api = api if api else cls._API 50 | 51 | file = Transform.to_file(file) 52 | return super()._query( 53 | url=cls._URL['query'], offset=offset, limit=limit, 54 | file=file, fields='_all', api=api 55 | ) 56 | 57 | @classmethod 58 | def create(cls, file, name, position, chromosome, private=True, api=None): 59 | """ 60 | Create a marker on a file. 61 | :param file: File object or identifier. 62 | :param name: Marker name. 63 | :param position: Marker position object. 64 | :param chromosome: Chromosome number. 65 | :param private: Whether the marker is private or public. 66 | :param api: Api instance. 67 | :return: Marker object. 68 | """ 69 | api = api if api else cls._API 70 | 71 | file = Transform.to_file(file) 72 | data = { 73 | 'file': file, 74 | 'name': name, 75 | 'position': position, 76 | 'chromosome': chromosome, 77 | 'private': private 78 | } 79 | 80 | extra = { 81 | 'resource': cls.__name__, 82 | 'query': data 83 | } 84 | logger.info('Creating marker', extra=extra) 85 | marker_data = api.post(url=cls._URL['query'], data=data).json() 86 | return Marker(api=api, **marker_data) 87 | 88 | @inplace_reload 89 | def save(self, inplace=True): 90 | """ 91 | Saves all modification to the marker on the server. 92 | :param inplace Apply edits on the current instance or get a new one. 93 | :return: Marker instance. 94 | """ 95 | modified_data = self._modified_data() 96 | if modified_data: 97 | extra = { 98 | 'resource': type(self).__name__, 99 | 'query': { 100 | 'id': self.id, 101 | 'modified_data': modified_data 102 | } 103 | } 104 | logger.info('Saving marker', extra=extra) 105 | data = self._api.patch(url=self._URL['get'].format(id=self.id), 106 | data=modified_data).json() 107 | marker = Marker(api=self._api, **data) 108 | return marker 109 | else: 110 | raise ResourceNotModified() 111 | -------------------------------------------------------------------------------- /sevenbridges/models/member.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sevenbridges.decorators import inplace_reload 4 | from sevenbridges.errors import ResourceNotModified 5 | from sevenbridges.meta.fields import HrefField, StringField, CompoundField 6 | from sevenbridges.meta.resource import Resource 7 | from sevenbridges.models.compound.projects.permissions import Permissions 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class Member(Resource): 13 | """ 14 | Central resource for managing members. 15 | This resource is reused on both projects and volumes. 16 | """ 17 | _URL = { 18 | 'permissions': '/permissions' 19 | } 20 | href = HrefField(read_only=True) 21 | id = StringField(read_only=True) 22 | username = StringField(read_only=False) 23 | email = StringField(read_only=False) 24 | type = StringField(read_only=False) 25 | permissions = CompoundField(Permissions, read_only=False) 26 | 27 | def __str__(self): 28 | return f'' 29 | 30 | def __eq__(self, other): 31 | if type(other) is not type(self): 32 | return False 33 | return ( 34 | self is other or 35 | self.id == other.id or 36 | self.username == other.username 37 | ) 38 | 39 | @inplace_reload 40 | def save(self, inplace=True): 41 | """ 42 | Saves modification to the api server. 43 | """ 44 | data = self._modified_data() 45 | data = data['permissions'] 46 | if data: 47 | url = self.href + self._URL['permissions'] 48 | extra = {'resource': type(self).__name__, 'query': data} 49 | logger.info('Modifying permissions', extra=extra) 50 | self._api.patch(url=url, data=data, append_base=False) 51 | else: 52 | raise ResourceNotModified() 53 | -------------------------------------------------------------------------------- /sevenbridges/models/rate_limit.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.meta.fields import CompoundField 2 | from sevenbridges.meta.resource import Resource 3 | from sevenbridges.models.compound.limits.rate import Rate 4 | 5 | 6 | class RateLimit(Resource): 7 | """ 8 | Rate limit resource contains info regarding request and computation 9 | rate limits. 10 | """ 11 | 12 | _URL = { 13 | 'get': '/rate_limit' 14 | } 15 | rate = CompoundField(Rate, read_only=True) 16 | instance_limit = CompoundField(Rate, read_only=True) 17 | 18 | @classmethod 19 | def get(cls, id=None, api=None): 20 | api = api if api else cls._API 21 | resource = api.get(url=cls._URL['get']).json() 22 | return cls(api=api, **resource) 23 | -------------------------------------------------------------------------------- /sevenbridges/models/team.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sevenbridges.decorators import inplace_reload 4 | from sevenbridges.errors import ResourceNotModified 5 | from sevenbridges.meta.collection import Collection 6 | from sevenbridges.meta.fields import HrefField, StringField 7 | from sevenbridges.meta.resource import Resource 8 | from sevenbridges.meta.transformer import Transform 9 | from sevenbridges.models.link import Link 10 | from sevenbridges.models.team_member import TeamMember 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class Team(Resource): 16 | """ 17 | Central resource for managing teams. 18 | """ 19 | _URL = { 20 | 'query': '/teams', 21 | 'get': '/teams/{id}', 22 | 'members_query': '/teams/{id}/members', 23 | 'members_get': '/teams/{id}/members/{member}', 24 | } 25 | 26 | href = HrefField(read_only=True) 27 | id = StringField(read_only=True) 28 | name = StringField(read_only=False) 29 | 30 | def __str__(self): 31 | return f'' 32 | 33 | def __eq__(self, other): 34 | if type(other) is not type(self): 35 | return False 36 | return self is other or self.id == other.id 37 | 38 | @classmethod 39 | def query(cls, division, list_all=False, offset=None, limit=None, 40 | api=None): 41 | """ 42 | :param division: Division slug. 43 | :param list_all: List all teams in division. 44 | :param offset: Pagination offset. 45 | :param limit: Pagination limit. 46 | :param api: Api instance. 47 | :return: Collection object. 48 | """ 49 | division = Transform.to_division(division) 50 | api = api if api else cls._API 51 | return super()._query( 52 | url=cls._URL['query'], division=division, _all=list_all, 53 | offset=offset, limit=limit, fields='_all', api=api 54 | ) 55 | 56 | @classmethod 57 | def create(cls, name, division, api=None): 58 | """ 59 | Create team within a division 60 | :param name: Team name. 61 | :param division: Parent division. 62 | :param api: Api instance. 63 | :return: Team object. 64 | """ 65 | 66 | division = Transform.to_division(division) 67 | api = api if api else cls._API 68 | data = { 69 | 'name': name, 70 | 'division': division 71 | } 72 | 73 | extra = { 74 | 'resource': cls.__name__, 75 | 'query': data 76 | } 77 | logger.info('Creating team', extra=extra) 78 | created_team = api.post(cls._URL['query'], data=data).json() 79 | return Team(api=api, **created_team) 80 | 81 | @inplace_reload 82 | def save(self, inplace=True): 83 | """ 84 | Saves all modification to the team on the server. 85 | :param inplace Apply edits on the current instance or get a new one. 86 | :return: Team instance. 87 | """ 88 | modified_data = self._modified_data() 89 | if modified_data: 90 | extra = { 91 | 'resource': type(self).__name__, 92 | 'query': { 93 | 'id': self.id, 94 | 'modified_data': modified_data 95 | } 96 | } 97 | logger.info('Saving team', extra=extra) 98 | data = self._api.patch(url=self._URL['get'].format(id=self.id), 99 | data=modified_data).json() 100 | team = Team(api=self._api, **data) 101 | return team 102 | else: 103 | raise ResourceNotModified() 104 | 105 | def get_members(self, offset=None, limit=None): 106 | """ 107 | Fetch team members for current team. 108 | :param offset: Pagination offset. 109 | :param limit: Pagination limit. 110 | :return: Collection object. 111 | """ 112 | extra = { 113 | 'resource': type(self).__name__, 114 | 'query': {'id': self.id} 115 | } 116 | logger.info('Get team members', extra=extra) 117 | response = self._api.get( 118 | url=self._URL['members_query'].format(id=self.id), 119 | params={'offset': offset, 'limit': limit} 120 | ) 121 | data = response.json() 122 | total = response.headers['x-total-matching-query'] 123 | members = [TeamMember(api=self._api, **member) for member in 124 | data['items']] 125 | links = [Link(**link) for link in data['links']] 126 | href = data['href'] 127 | return Collection(resource=TeamMember, href=href, total=total, 128 | items=members, links=links, api=self._api) 129 | 130 | def add_member(self, user): 131 | """ 132 | Add member to team 133 | :param user: User object or user's username 134 | :return: Added user. 135 | """ 136 | user = Transform.to_user(user) 137 | data = { 138 | 'id': user 139 | } 140 | extra = { 141 | 'resource': type(self).__name__, 142 | 'query': { 143 | 'id': self.id, 144 | 'data': data, 145 | } 146 | } 147 | logger.info('Adding team member using id', extra=extra) 148 | response = self._api.post( 149 | url=self._URL['members_query'].format(id=self.id), data=data) 150 | member_data = response.json() 151 | return TeamMember(api=self._api, **member_data) 152 | 153 | def remove_member(self, user): 154 | """ 155 | Remove member from the team. 156 | :param user: User to be removed. 157 | """ 158 | member = Transform.to_user(user) 159 | extra = { 160 | 'resource': type(self).__name__, 161 | 'query': { 162 | 'id': self.id, 163 | 'user': user, 164 | } 165 | } 166 | logger.info('Removing team member', extra=extra) 167 | self._api.delete( 168 | url=self._URL['members_get'].format(id=self.id, member=member) 169 | ) 170 | -------------------------------------------------------------------------------- /sevenbridges/models/team_member.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sevenbridges.meta.fields import HrefField, StringField 4 | from sevenbridges.meta.resource import Resource 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class TeamMember(Resource): 10 | """ 11 | Central resource for managing team members. 12 | """ 13 | href = HrefField(read_only=True) 14 | id = StringField(read_only=True) 15 | username = StringField(read_only=False) 16 | role = StringField(read_only=True) 17 | 18 | def __eq__(self, other): 19 | if type(other) is not type(self): 20 | return False 21 | return self is other or self.id == other.id 22 | 23 | def __str__(self): 24 | return f'' 25 | -------------------------------------------------------------------------------- /sevenbridges/models/user.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sevenbridges.meta.resource import Resource 4 | from sevenbridges.meta.fields import HrefField, StringField 5 | from sevenbridges.meta.transformer import Transform 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class User(Resource): 11 | """ 12 | Central resource for managing users. 13 | """ 14 | _URL = { 15 | 'me': '/user', 16 | 'get': '/users/{id}', 17 | 'query': '/users', 18 | 'delete': '/users/{username}' 19 | } 20 | 21 | href = HrefField(read_only=True) 22 | username = StringField(read_only=True) 23 | email = StringField(read_only=True) 24 | first_name = StringField(read_only=True) 25 | last_name = StringField(read_only=True) 26 | affiliation = StringField(read_only=True) 27 | phone = StringField(read_only=True) 28 | address = StringField(read_only=True) 29 | state = StringField(read_only=True) 30 | country = StringField(read_only=True) 31 | zip_code = StringField(read_only=True) 32 | city = StringField(read_only=True) 33 | role = StringField(read_only=True) 34 | 35 | def __eq__(self, other): 36 | if type(other) is not type(self): 37 | return False 38 | return self is other or self.username == other.username 39 | 40 | def __str__(self): 41 | return f'' 42 | 43 | @classmethod 44 | def me(cls, api=None): 45 | """ 46 | Retrieves current user information. 47 | :param api: Api instance. 48 | :return: User object. 49 | """ 50 | api = api if api else cls._API 51 | extra = { 52 | 'resource': cls.__name__, 53 | 'query': {} 54 | } 55 | logger.info('Fetching user information', extra=extra) 56 | user_data = api.get(cls._URL['me']).json() 57 | return User(api=api, **user_data) 58 | 59 | @classmethod 60 | def get(cls, user, api=None): 61 | api = api if api else cls._API 62 | user = Transform.to_user(user) 63 | return super().get(id=user, api=api) 64 | 65 | @classmethod 66 | def query(cls, division, role=None, offset=None, limit=None, api=None): 67 | """Query division users 68 | :param division: Division slug. 69 | :param role: User role in division. 70 | :param offset: Pagination offset. 71 | :param limit: Pagination limit. 72 | :param api: Api instance. 73 | :return: Collection object. 74 | """ 75 | api = api or cls._API 76 | params = { 77 | 'division': Transform.to_division(division), 78 | } 79 | if role: 80 | params['role'] = role 81 | 82 | return super()._query( 83 | url=cls._URL['query'], 84 | api=api, 85 | offset=offset, 86 | limit=limit, 87 | **params 88 | ) 89 | 90 | def disable(self, api=None): 91 | """ 92 | Disable user 93 | :param api: Api instance. 94 | :return: 95 | """ 96 | api = api or self._API 97 | api.delete( 98 | url=self._URL['delete'].format(username=self.username) 99 | ) 100 | -------------------------------------------------------------------------------- /sevenbridges/transfer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbg/sevenbridges-python/ed3de94bab744a33f614111c1845af68b5433c54/sevenbridges/transfer/__init__.py -------------------------------------------------------------------------------- /sevenbridges/transfer/utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import math 4 | 5 | 6 | class Part: 7 | def __init__(self, start=None, size=None): 8 | self._start = start 9 | self._size = size 10 | 11 | @property 12 | def start(self): 13 | return self._start 14 | 15 | @property 16 | def size(self): 17 | return self._size 18 | 19 | 20 | class Progress: 21 | def __init__(self, num_of_parts, parts_done, bytes_done, 22 | file_size, duration): 23 | self._num_of_parts = num_of_parts 24 | self._parts_done = parts_done 25 | self._bytes_done = bytes_done 26 | self._file_size = file_size 27 | self._duration = duration 28 | 29 | @property 30 | def num_of_parts(self): 31 | return self._num_of_parts 32 | 33 | @property 34 | def parts_done(self): 35 | return self._parts_done 36 | 37 | @property 38 | def bytes_done(self): 39 | return self._bytes_done 40 | 41 | @property 42 | def file_size(self): 43 | return self._file_size 44 | 45 | @property 46 | def duration(self): 47 | return self._duration 48 | 49 | @property 50 | def progress(self): 51 | progress = (self._bytes_done / float(self._file_size)) * 100 52 | progress = progress if progress <= 100 else 100 53 | return progress 54 | 55 | @property 56 | def bandwidth(self): 57 | return (self._bytes_done / 1000000) / self.duration 58 | 59 | 60 | def total_parts(file_size, part_size): 61 | return int(math.ceil(file_size / float(part_size))) 62 | 63 | 64 | def simple_progress_bar(progress): 65 | sys.stdout.write( 66 | '\rTransfer: Progress[%.2f%%], Bandwidth[%.2fMB/s], Parts[total=%s, ' 67 | 'Done=%s], Duration[%.2fs]\b' % ( 68 | progress.progress, progress.bandwidth, progress.num_of_parts, 69 | progress.parts_done, progress.duration 70 | ) 71 | ) 72 | sys.stdout.flush() 73 | -------------------------------------------------------------------------------- /sevenbridges/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.0.1.dev0+local' 2 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=sevenbridges 2 | sonar.projectName=sevenbridges-python 3 | 4 | sonar.sources=sevenbridges 5 | sonar.exclusions=tests/**/*.py,test-report/*.xml 6 | 7 | sonar.test=tests/**/*.py 8 | 9 | sonar.python.coverage.reportPaths=test-report/coverage.xml 10 | sonar.coverage.exclusions=tests/* 11 | 12 | sonar.issue.ignore.multicriteria=r1 13 | 14 | # Ignore warning about too many parameters 15 | sonar.issue.ignore.multicriteria.r1.ruleKey=python:S107 16 | sonar.issue.ignore.multicriteria.r1.resourceKey=sevenbridges/**/*.py 17 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbg/sevenbridges-python/ed3de94bab744a33f614111c1845af68b5433c54/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import faker 2 | import pytest 3 | import requests_mock 4 | 5 | from sevenbridges import Api 6 | from tests import providers, verifiers 7 | 8 | generator = faker.Factory.create() 9 | 10 | requests_mock.mock.case_sensitive = True 11 | 12 | 13 | @pytest.fixture 14 | def request_mocker(request): 15 | """ 16 | :param request: pytest request object for cleaning up. 17 | :return: Returns instance of requests mocker used to mock HTTP calls. 18 | """ 19 | m = requests_mock.Mocker() 20 | m.start() 21 | request.addfinalizer(m.stop) 22 | return m 23 | 24 | 25 | class Precondition: 26 | """ 27 | Aggregated data provided for all server side data mocking. 28 | """ 29 | 30 | def __init__(self, request_mocker, base_url): 31 | self.user = providers.UserProvider(request_mocker, base_url) 32 | self.endpoints = providers.EndpointProvider(request_mocker, base_url) 33 | self.rate = providers.RateLimitProvider(request_mocker, base_url) 34 | self.project = providers.ProjectProvider(request_mocker, base_url) 35 | self.member = providers.MemberProvider(request_mocker, base_url) 36 | self.file = providers.FileProvider(request_mocker, base_url) 37 | self.app = providers.AppProvider(request_mocker, base_url) 38 | self.task = providers.TaskProvider(request_mocker, base_url) 39 | self.volume = providers.VolumeProvider(request_mocker, base_url) 40 | self.action = providers.ActionProvider(request_mocker, base_url) 41 | self.division = providers.DivisionProvider(request_mocker, base_url) 42 | self.team = providers.TeamProvider(request_mocker, base_url) 43 | self.marker = providers.MarkerProvider(request_mocker, base_url) 44 | self.imports = providers.ImportsProvider(request_mocker, base_url) 45 | self.exports = providers.ExportsProvider(request_mocker, base_url) 46 | self.team_member = providers.TeamMemberProvider( 47 | request_mocker, base_url) 48 | self.datasets = providers.DatasetProvider(request_mocker, base_url) 49 | self.automations = providers.AutomationProvider( 50 | request_mocker, base_url) 51 | self.automation_runs = providers.AutomationRunProvider( 52 | request_mocker, base_url) 53 | self.automation_members = providers.AutomationMemberProvider( 54 | request_mocker, base_url 55 | ) 56 | self.automation_packages = providers.AutomationPackageProvider( 57 | request_mocker, base_url 58 | ) 59 | self.async_jobs = providers.AsyncJobProvider(request_mocker, base_url) 60 | self.cp_uploads = providers.CodePackageUploadProvider( 61 | request_mocker, base_url 62 | ) 63 | self.uploads = providers.FileUploadProvider(request_mocker, base_url) 64 | self.drs_imports = providers.DRSImportProvider( 65 | request_mocker, base_url 66 | ) 67 | self.billing_group = providers.BillingGroupProvider( 68 | request_mocker, base_url 69 | ) 70 | self.billing_group_storage_breakdown = ( 71 | providers.BillingGroupStorageBreakdownProvider( 72 | request_mocker, base_url 73 | ) 74 | ) 75 | 76 | 77 | class Verifier: 78 | """ 79 | Aggregated action verificator. 80 | """ 81 | 82 | def __init__(self, request_mocker): 83 | self.user = verifiers.UserVerifier(request_mocker) 84 | self.endpoints = verifiers.EndpointVerifier(request_mocker) 85 | self.project = verifiers.ProjectVerifier(request_mocker) 86 | self.member = verifiers.MemberVerifier(request_mocker) 87 | self.file = verifiers.FileVerifier(request_mocker) 88 | self.app = verifiers.AppVerifier(request_mocker) 89 | self.task = verifiers.TaskVerifier(request_mocker) 90 | self.volume = verifiers.VolumeVerifier(request_mocker) 91 | self.action = verifiers.ActionVerifier(request_mocker) 92 | self.division = verifiers.DivisionVerifier(request_mocker) 93 | self.team = verifiers.TeamVerifier(request_mocker) 94 | self.marker = verifiers.MarkerVerifier(request_mocker) 95 | self.imports = verifiers.ImportsVerifier(request_mocker) 96 | self.exports = verifiers.ExportsVerifier(request_mocker) 97 | self.datasets = verifiers.DatasetVerifier(request_mocker) 98 | self.automations = verifiers.AutomationVerifier(request_mocker) 99 | self.automation_runs = verifiers.AutomationRunVerifier(request_mocker) 100 | self.automation_members = verifiers.AutomationMemberVerifier( 101 | request_mocker 102 | ) 103 | self.async_jobs = verifiers.AsyncJobVerifier(request_mocker) 104 | self.automation_packages = verifiers.AutomationPackageVerifier( 105 | request_mocker 106 | ) 107 | self.drs_imports = verifiers.DRSImportsVerifier(request_mocker) 108 | self.billing_group = verifiers.BillingGroupVerifier(request_mocker) 109 | self.billing_group_storage_breakdown = ( 110 | verifiers.BillingGroupStorageBreakdownVerifier(request_mocker) 111 | ) 112 | 113 | 114 | @pytest.fixture 115 | def given(request_mocker, base_url): 116 | """ 117 | Fixture returning Precondition. 118 | """ 119 | return Precondition(request_mocker, base_url) 120 | 121 | 122 | @pytest.fixture 123 | def verifier(request_mocker): 124 | """ 125 | Fixture returning Verificator. 126 | """ 127 | return Verifier(request_mocker) 128 | 129 | 130 | @pytest.fixture 131 | def api(base_url): 132 | """ 133 | Fixture returning instance of Api with randomly generated endpoint URL 134 | and authentication token. 135 | """ 136 | return Api(url=base_url, token=generator.uuid4()) 137 | 138 | 139 | @pytest.fixture 140 | def base_url(): 141 | return generator.url(schemes=['https'])[:-1] 142 | 143 | 144 | @pytest.fixture 145 | def config_parser(): 146 | class ConfigParser: 147 | def __init__(self, data): 148 | self.data = data 149 | 150 | def get(self, profile, item): 151 | return self.data[profile][item] 152 | 153 | def read(self, stream): 154 | pass 155 | 156 | class Mock: 157 | def __init__(self, data): 158 | self.data = data 159 | 160 | def __call__(self, *args, **kwargs): 161 | data = dict(kwargs) 162 | data.update(self.data) 163 | return ConfigParser(data) 164 | 165 | return Mock 166 | -------------------------------------------------------------------------------- /tests/test_actions.py: -------------------------------------------------------------------------------- 1 | import faker 2 | 3 | generator = faker.Factory.create() 4 | 5 | 6 | def test_action_feedback(api, given, verifier): 7 | # given 8 | given.action.feedback_set() 9 | 10 | # send feedback 11 | api.actions.send_feedback(text=generator.name()) 12 | 13 | # verify 14 | verifier.action.feedback_received() 15 | 16 | 17 | def test_action_bulk_copy(api, given, verifier): 18 | 19 | file_id = generator.uuid4() 20 | # given 21 | given.action.can_bulk_copy(id=file_id) 22 | 23 | # copy files 24 | result = api.actions.bulk_copy_files([file_id], "test") 25 | 26 | # verify 27 | assert file_id in result.values() 28 | verifier.action.bulk_copy_done() 29 | -------------------------------------------------------------------------------- /tests/test_apps.py: -------------------------------------------------------------------------------- 1 | import faker 2 | import pytest 3 | 4 | from sevenbridges.errors import SbgError 5 | 6 | 7 | generator = faker.Factory.create() 8 | 9 | 10 | @pytest.mark.parametrize("visibility", ["private", "public", None]) 11 | def test_apps_query(api, given, verifier, visibility): 12 | # preconditions 13 | total = 10 14 | given.app.apps_exist(visibility, total) 15 | 16 | # action 17 | apps = api.apps.query(visibility=visibility) 18 | 19 | # verification 20 | assert apps.total == total 21 | assert len(apps) == total 22 | 23 | verifier.app.apps_fetched(visibility) 24 | 25 | 26 | def test_apps_get_revision(api, given, verifier): 27 | # preconditions 28 | app_id = 'me/my-project/my-app' 29 | app_revision = 1 30 | given.app.app_with_revision_exists(id=app_id, revision=app_revision) 31 | 32 | # action 33 | app = api.apps.get_revision(app_id, 1) 34 | 35 | # verification 36 | assert app.id == app_id 37 | assert app_id in repr(app) 38 | assert app.revision == app_revision 39 | 40 | verifier.app.app_fetched(app_id, app_revision) 41 | 42 | 43 | @pytest.mark.parametrize("app_id", ["me/my-project/app"]) 44 | def test_app_copy(api, given, verifier, app_id): 45 | # preconditions 46 | copied_name = 'new-app' 47 | app_revision = 1 48 | given.app.app_with_revision_exists(id=app_id, revision=app_revision) 49 | given.app.app_can_be_copied(id=app_id, new_name=copied_name) 50 | 51 | # action 52 | app = api.apps.get_revision(app_id, 1) 53 | app_copy = app.copy('me/my-project/', copied_name) 54 | 55 | # verification 56 | assert app_copy.name == copied_name 57 | 58 | verifier.app.app_copied(app_id) 59 | 60 | 61 | def test_install_app(api, given, verifier): 62 | # preconditions 63 | app_id = "me/my-project/my-app" 64 | given.app.app_exists(id=app_id) 65 | given.app.app_can_be_installed(id=app_id) 66 | 67 | raw = {'sbg:id': app_id} 68 | 69 | # action 70 | app = api.apps.install_app(app_id, raw) 71 | 72 | # verification 73 | assert app.id == app_id 74 | verifier.app.app_installed(app_id) 75 | 76 | 77 | def test_install_app_formats(api, given, verifier): 78 | # preconditions 79 | app_json_id = "me/my-project/my-app-json" 80 | app_yaml_id = "me/my-project/my-app-yaml" 81 | app_invalid_id = "me/my-project/my-app-invalid" 82 | 83 | given.app.app_exists(id=app_json_id) 84 | given.app.app_exists(id=app_yaml_id) 85 | given.app.app_exists(id=app_invalid_id) 86 | given.app.app_can_be_installed(id=app_json_id) 87 | given.app.app_can_be_installed(id=app_yaml_id) 88 | given.app.app_can_be_installed(id=app_invalid_id) 89 | 90 | raw_json = {'sbg:id': app_json_id} 91 | raw_yaml = {'sbg:id': app_yaml_id} 92 | raw_invalid = {'sbg:id': app_invalid_id} 93 | 94 | # action 95 | app_json = api.apps.install_app(app_json_id, raw_json, raw_format='json') 96 | app_yaml = api.apps.install_app(app_yaml_id, raw_yaml, raw_format='yaml') 97 | 98 | with pytest.raises(SbgError): 99 | api.apps.install_app(app_invalid_id, raw_invalid, raw_format='invalid') 100 | 101 | # verification 102 | assert app_json.id == app_json_id 103 | assert app_yaml.id == app_yaml_id 104 | verifier.app.app_installed(app_json_id) 105 | verifier.app.app_installed(app_yaml_id) 106 | 107 | 108 | def test_create_app_revision(api, given, verifier): 109 | # preconditions 110 | app_id = "me/my-project/my-app" 111 | revision = 1 112 | given.app.app_exists(id=app_id, revision=1) 113 | given.app.revision_can_be_created(id=app_id, revision=revision) 114 | 115 | raw = {'sbg:id': app_id, 'revision': revision} 116 | 117 | # action 118 | app = api.apps.create_revision(app_id, revision, raw) 119 | 120 | # verification 121 | assert app.id == app_id 122 | assert app.revision == revision 123 | 124 | verifier.app.revision_created(app_id, revision) 125 | -------------------------------------------------------------------------------- /tests/test_async_jobs.py: -------------------------------------------------------------------------------- 1 | import faker 2 | import pytest 3 | 4 | from sevenbridges.models.async_jobs import AsyncFileBulkRecord 5 | from sevenbridges.models.enums import AsyncFileOperations, AsyncJobStates 6 | 7 | generator = faker.Factory.create() 8 | 9 | 10 | def test_list_file_jobs(api, given, verifier): 11 | # preconditions 12 | total = 10 13 | given.async_jobs.list_file_jobs(total) 14 | 15 | # action 16 | jobs = api.async_jobs.list_file_jobs() 17 | 18 | # verification 19 | assert len(jobs) == total 20 | verifier.async_jobs.listed() 21 | 22 | 23 | def test_get_copy_files_job(api, given, verifier): 24 | # preconditions 25 | id = generator.uuid4() 26 | given.async_jobs.exists(id=id, type=AsyncFileOperations.COPY) 27 | 28 | # action 29 | job = api.async_jobs.get_file_copy_job(id=id) 30 | 31 | # verification 32 | assert job.id == id 33 | verifier.async_jobs.file_copy_job_fetched(id=id) 34 | 35 | 36 | def test_get_move_files_job(api, given, verifier): 37 | # preconditions 38 | id = generator.uuid4() 39 | given.async_jobs.exists(id=id, type=AsyncFileOperations.MOVE) 40 | 41 | # action 42 | job = api.async_jobs.get_file_move_job(id=id) 43 | 44 | # verification 45 | assert job.id == id 46 | verifier.async_jobs.file_move_job_fetched(id=id) 47 | 48 | 49 | def test_get_delete_files_job(api, given, verifier): 50 | # preconditions 51 | id = generator.uuid4() 52 | given.async_jobs.exists(id=id, type=AsyncFileOperations.DELETE) 53 | 54 | # action 55 | job = api.async_jobs.get_file_delete_job(id=id) 56 | 57 | # verification 58 | assert job.id == id 59 | verifier.async_jobs.file_delete_job_fetched(id=id) 60 | 61 | 62 | def test_get_results(api, given, verifier): 63 | # preconditions 64 | id = generator.uuid4() 65 | 66 | expected_result = [ 67 | {'resource': {'id': generator.uuid4()}}, 68 | {'error': {'status': 404}}, 69 | ] 70 | given.async_jobs.exists( 71 | id=id, 72 | result=expected_result, 73 | type=AsyncFileOperations.COPY, 74 | ) 75 | 76 | # action 77 | job = api.async_jobs.get_file_copy_job(id=id) 78 | result = job.get_result() 79 | 80 | # verification 81 | verifier.async_jobs.file_copy_job_fetched(id=id) 82 | assert all(isinstance(r, AsyncFileBulkRecord) for r in result) 83 | assert len(result) == 2 84 | assert result[0].valid 85 | assert not result[1].valid 86 | 87 | 88 | @pytest.mark.parametrize("location", ['parent', 'project']) 89 | def test_async_copy_files(api, given, verifier, location): 90 | # preconditions 91 | total = 10 92 | files = [ 93 | { 94 | 'file': generator.uuid4(), 95 | 'location': generator.uuid4(), 96 | 'name': generator.slug() 97 | } for _ in range(total) 98 | ] 99 | given.async_jobs.can_copy_files(files=files) 100 | 101 | # action 102 | job = api.async_jobs.file_bulk_copy(files=files) 103 | 104 | # verification 105 | assert job.state == AsyncJobStates.SUBMITTED 106 | assert len(job.result) == total 107 | verifier.async_jobs.async_files_copied() 108 | 109 | 110 | @pytest.mark.parametrize("location", ['parent', 'project']) 111 | def test_async_move_files(api, given, verifier, location): 112 | # preconditions 113 | total = 10 114 | files = [ 115 | { 116 | 'file': generator.uuid4(), 117 | 'location': generator.uuid4(), 118 | 'name': generator.slug() 119 | } for _ in range(total) 120 | ] 121 | given.async_jobs.can_move_files(files=files) 122 | 123 | # action 124 | job = api.async_jobs.file_bulk_move(files=files) 125 | 126 | # verification 127 | assert job.state == AsyncJobStates.SUBMITTED 128 | assert len(job.result) == total 129 | verifier.async_jobs.async_files_moved() 130 | 131 | 132 | def test_async_delete_files(api, given, verifier): 133 | # preconditions 134 | total = 10 135 | files = [ 136 | { 137 | 'file': generator.uuid4(), 138 | } for _id in range(total) 139 | ] 140 | given.async_jobs.can_delete_files(files=files) 141 | 142 | # action 143 | job = api.async_jobs.file_bulk_delete(files=files) 144 | 145 | # verification 146 | assert job.state == AsyncJobStates.SUBMITTED 147 | assert len(job.result) == total 148 | verifier.async_jobs.async_files_deleted() 149 | -------------------------------------------------------------------------------- /tests/test_billing_groups.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import faker 3 | 4 | generator = faker.Factory.create() 5 | 6 | 7 | def test_billing_group_query(api, given, verifier): 8 | # preconditions 9 | total = 10 10 | given.billing_group.exist(total) 11 | 12 | # action 13 | billing_groups = api.billing_groups.query() 14 | 15 | # verification 16 | assert billing_groups.total == total 17 | assert len(billing_groups) == total 18 | 19 | verifier.billing_group.groups_fetched() 20 | 21 | 22 | @pytest.mark.parametrize('with_cost', [True, False]) 23 | def test_billing_storage_breakdown(api, given, verifier, with_cost): 24 | # precondition 25 | given.billing_group.exist(1) 26 | 27 | # action 28 | billing_groups = api.billing_groups.query() 29 | billing_group = billing_groups[0] 30 | 31 | # precondition 32 | total = 5 33 | given.billing_group_storage_breakdown.exist( 34 | bg_id=billing_group.id, 35 | num_of_objects=total, 36 | with_cost=with_cost 37 | ) 38 | 39 | # action 40 | storage_breakdown = billing_group.storage_breakdown() 41 | 42 | # verification 43 | verifier.billing_group.groups_fetched() 44 | verifier.billing_group_storage_breakdown.fetched( 45 | billing_group=billing_group 46 | ) 47 | 48 | verifier.billing_group_storage_breakdown.fetched( 49 | billing_group=billing_group 50 | ) 51 | assert len(storage_breakdown) == total 52 | for breakdown in storage_breakdown: 53 | if with_cost: 54 | assert breakdown.active is not None 55 | assert breakdown.archived is not None 56 | else: 57 | assert breakdown.active is None 58 | assert breakdown.archived is None 59 | -------------------------------------------------------------------------------- /tests/test_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import faker 4 | import configparser 5 | 6 | from sevenbridges import Config, Api 7 | from sevenbridges.config import UserProfile 8 | 9 | generator = faker.Factory.create() 10 | 11 | 12 | def test_os_environ_config(base_url, monkeypatch): 13 | mock_env = { 14 | 'SB_AUTH_TOKEN': 'token', 15 | 'SB_API_ENDPOINT': base_url 16 | } 17 | monkeypatch.setattr(os, 'environ', mock_env) 18 | config = Config() 19 | assert config.api_endpoint == base_url 20 | assert config.auth_token == 'token' 21 | 22 | 23 | def test_os_environ_config_with_api(base_url, monkeypatch): 24 | mock_env = { 25 | 'SB_AUTH_TOKEN': 'token', 26 | 'SB_API_ENDPOINT': base_url, 27 | 'HTTP_PROXY': base_url, 28 | 'HTTPS_PROXY': base_url, 29 | } 30 | monkeypatch.setattr(os, 'environ', mock_env) 31 | 32 | api = Api() 33 | assert api.url == base_url 34 | assert api.token == 'token' 35 | api.session.proxies['http'] = mock_env['HTTP_PROXY'] 36 | api.session.proxies['https'] = mock_env['HTTPS_PROXY'] 37 | 38 | 39 | def test_default_config(base_url, monkeypatch, config_parser): 40 | data = { 41 | 'default': { 42 | 'auth_token': 'token', 43 | 'api_endpoint': base_url 44 | }, 45 | 'proxies': { 46 | 'http_proxy': 'http', 47 | 'https_proxy': 'https' 48 | } 49 | } 50 | parser = config_parser(data) 51 | monkeypatch.setattr(configparser, 'ConfigParser', parser) 52 | monkeypatch.setattr(os.path, 'isfile', lambda x: True) 53 | 54 | api = Api() 55 | assert api.session.proxies.get('http') == data['proxies']['http_proxy'] 56 | assert api.session.proxies.get('https') == data['proxies']['https_proxy'] 57 | 58 | 59 | def test_config_profile(base_url, monkeypatch, config_parser): 60 | data = { 61 | 'profile': { 62 | 'auth_token': 'token', 63 | 'api_endpoint': base_url 64 | }, 65 | 'proxies': { 66 | 'http_proxy': 'http', 67 | 'https_proxy': 'https' 68 | } 69 | } 70 | parser = config_parser(data) 71 | monkeypatch.setattr(configparser, 'ConfigParser', parser) 72 | monkeypatch.setattr(os.path, 'isfile', lambda x: True) 73 | 74 | api = Api(config=Config('profile')) 75 | assert api.url == data['profile']['api_endpoint'] 76 | assert api.token == data['profile']['auth_token'] 77 | 78 | 79 | def test_config_profile_no_proxy(base_url, monkeypatch, config_parser): 80 | def is_file(f): 81 | if f == UserProfile.CREDENTIALS: 82 | return True 83 | else: 84 | return False 85 | 86 | data = { 87 | 'profile': { 88 | 'auth_token': 'token', 89 | 'api_endpoint': base_url 90 | } 91 | } 92 | parser = config_parser(data) 93 | monkeypatch.setattr(configparser, 'ConfigParser', parser) 94 | monkeypatch.setattr(os.path, 'isfile', is_file) 95 | 96 | api = Api(config=Config('profile')) 97 | assert api.url == data['profile']['api_endpoint'] 98 | assert api.token == data['profile']['auth_token'] 99 | 100 | 101 | def test_config_profile_explicit_proxy(base_url, monkeypatch, config_parser): 102 | def is_file(f): 103 | if f == UserProfile.CREDENTIALS: 104 | return True 105 | else: 106 | return False 107 | 108 | data = { 109 | 'profile': { 110 | 'auth_token': 'token', 111 | 'api_endpoint': base_url 112 | } 113 | } 114 | 115 | proxies = { 116 | 'http_proxy': 'http', 117 | 'https_proxy': 'https' 118 | } 119 | parser = config_parser(data) 120 | monkeypatch.setattr(configparser, 'ConfigParser', parser) 121 | monkeypatch.setattr(os.path, 'isfile', is_file) 122 | 123 | api = Api(config=Config('profile', proxies=proxies)) 124 | assert api.url == data['profile']['api_endpoint'] 125 | assert api.token == data['profile']['auth_token'] 126 | assert api.session.proxies.get('http') == proxies['http_proxy'] 127 | assert api.session.proxies.get('https') == proxies['https_proxy'] 128 | 129 | 130 | def test_config_advance_access(base_url, monkeypatch, config_parser): 131 | def is_file(f): 132 | if f in [UserProfile.CREDENTIALS, UserProfile.CONFIG]: 133 | return True 134 | else: 135 | return False 136 | 137 | data = { 138 | 'profile': { 139 | 'auth_token': 'token', 140 | 'api_endpoint': base_url 141 | }, 142 | 'mode': { 143 | 'advance_access': True 144 | } 145 | } 146 | parser = config_parser(data) 147 | monkeypatch.setattr(configparser, 'ConfigParser', parser) 148 | monkeypatch.setattr(os.path, 'isfile', is_file) 149 | 150 | api = Api(config=Config('profile')) 151 | assert api.aa is True 152 | 153 | 154 | def test_config_explicit_advance_access(base_url, monkeypatch, config_parser): 155 | def is_file(f): 156 | if f in [UserProfile.CREDENTIALS, UserProfile.CONFIG]: 157 | return True 158 | else: 159 | return False 160 | 161 | data = { 162 | 'profile': { 163 | 'auth_token': 'token', 164 | 'api_endpoint': base_url 165 | } 166 | } 167 | parser = config_parser(data) 168 | monkeypatch.setattr(configparser, 'ConfigParser', parser) 169 | monkeypatch.setattr(os.path, 'isfile', is_file) 170 | 171 | api = Api(config=Config('profile'), advance_access=True) 172 | assert api.aa is True 173 | -------------------------------------------------------------------------------- /tests/test_datasets.py: -------------------------------------------------------------------------------- 1 | import faker 2 | 3 | generator = faker.Factory.create() 4 | 5 | 6 | def test_get(api, given, verifier): 7 | # precondition 8 | api.aa = True 9 | username = generator.user_name() 10 | dataset_name = generator.slug() 11 | id = f'{username}/{dataset_name}' 12 | given.datasets.exists(id=id) 13 | 14 | # action 15 | dataset = api.datasets.get(id=id) 16 | 17 | # verification 18 | assert dataset.id == id 19 | verifier.datasets.fetched(id=id) 20 | 21 | 22 | def test_query(api, given, verifier): 23 | # preconditions 24 | api.aa = True 25 | total = 10 26 | given.datasets.query(total) 27 | 28 | # action 29 | datasets = api.datasets.query() 30 | 31 | # verification 32 | assert len(datasets) == total 33 | 34 | verifier.datasets.queried() 35 | 36 | 37 | def test_query_by_owner(api, given, verifier): 38 | # preconditions 39 | api.aa = True 40 | total = 10 41 | username = generator.user_name() 42 | dataset_name = generator.slug() 43 | id = f'{username}/{dataset_name}' 44 | given.datasets.exists(id=id) 45 | given.datasets.owned_by(total, username) 46 | 47 | # action 48 | datasets = api.datasets.get_owned_by(username) 49 | 50 | # verification 51 | assert len(datasets) == total 52 | 53 | verifier.datasets.owned_by(username) 54 | 55 | 56 | def test_save(api, given, verifier): 57 | # precondition 58 | api.aa = True 59 | username = generator.user_name() 60 | dataset_name = generator.slug() 61 | id = f'{username}/{dataset_name}' 62 | given.datasets.exists(id=id) 63 | given.datasets.can_be_saved(id=id) 64 | 65 | # action 66 | dataset = api.datasets.get(id=id) 67 | dataset.name = generator.slug() 68 | dataset.description = generator.slug() 69 | dataset.save() 70 | 71 | # verification 72 | verifier.datasets.saved(id) 73 | 74 | 75 | def test_get_members(api, given, verifier): 76 | # precondition 77 | api.aa = True 78 | total = 10 79 | username = generator.user_name() 80 | dataset_name = generator.slug() 81 | id = f'{username}/{dataset_name}' 82 | given.datasets.exists(id=id) 83 | given.datasets.has_members(id, dataset_name, total) 84 | 85 | # action 86 | dataset = api.datasets.get(id) 87 | members = dataset.get_members() 88 | 89 | # verification 90 | assert len(members) == total 91 | verifier.datasets.members_retrieved(id) 92 | 93 | 94 | def test_get_member(api, given, verifier): 95 | # precondition 96 | api.aa = True 97 | username = generator.user_name() 98 | member_username = generator.user_name() 99 | dataset_name = generator.slug() 100 | id = f'{username}/{dataset_name}' 101 | given.datasets.exists(id=id) 102 | given.datasets.has_member(id, dataset_name, member_username) 103 | 104 | # action 105 | dataset = api.datasets.get(id) 106 | member = dataset.get_member(member_username) 107 | 108 | # verification 109 | assert member.username == member_username 110 | verifier.datasets.member_retrieved(id, member_username) 111 | 112 | 113 | def test_add_member(api, given, verifier): 114 | # precondition 115 | api.aa = True 116 | username = generator.user_name() 117 | member_username = generator.user_name() 118 | member_permissions = { 119 | "write": True, 120 | "read": True, 121 | "copy": True, 122 | "execute": True, 123 | "admin": True 124 | } 125 | dataset_name = generator.slug() 126 | id = f'{username}/{dataset_name}' 127 | given.datasets.exists(id=id) 128 | given.datasets.can_add_member(id, member_username) 129 | 130 | # action 131 | dataset = api.datasets.get(id) 132 | dataset.add_member(member_username, member_permissions) 133 | 134 | # verification 135 | verifier.datasets.member_added(id) 136 | 137 | 138 | def test_remove_member(api, given, verifier): 139 | # precondition 140 | api.aa = True 141 | username = generator.user_name() 142 | member_username = generator.user_name() 143 | dataset_name = generator.slug() 144 | id = f'{username}/{dataset_name}' 145 | given.datasets.exists(id=id) 146 | given.datasets.has_member(id, dataset_name, member_username) 147 | given.datasets.can_remove_member(id, member_username) 148 | 149 | # action 150 | dataset = api.datasets.get(id) 151 | dataset.remove_member(member_username) 152 | 153 | # verification 154 | verifier.datasets.member_removed(id, member_username) 155 | -------------------------------------------------------------------------------- /tests/test_decorators.py: -------------------------------------------------------------------------------- 1 | import faker 2 | import pytest 3 | 4 | from sevenbridges.errors import NonJSONResponseError 5 | 6 | generator = faker.Factory.create() 7 | 8 | 9 | @pytest.mark.parametrize("status_code", [200, 500]) 10 | def test_non_json_response(api, given, status_code): 11 | # preconditions 12 | given.app.app_exist_non_json(status_code=status_code) 13 | 14 | # action 15 | with pytest.raises(NonJSONResponseError): 16 | api.apps.query(visibility="private") 17 | -------------------------------------------------------------------------------- /tests/test_divisions.py: -------------------------------------------------------------------------------- 1 | import faker 2 | 3 | generator = faker.Factory.create() 4 | 5 | 6 | def test_get_division(api, given, verifier): 7 | # precondition 8 | api.aa = True 9 | id = generator.uuid4() 10 | given.division.exists(id=id) 11 | 12 | # action 13 | division = api.divisions.get(id=id) 14 | 15 | # verification 16 | assert division.id == id 17 | verifier.division.division_fetched(id=id) 18 | 19 | 20 | def test_division_query(api, given, verifier): 21 | # preconditions 22 | api.aa = True 23 | total = 10 24 | given.division.query(total) 25 | 26 | # action 27 | divisions = api.divisions.query() 28 | 29 | # verification 30 | assert len(divisions) == total 31 | 32 | verifier.division.divisions_fetched() 33 | 34 | 35 | def test_get_teams(api, given, verifier): 36 | # preconditions 37 | api.aa = True 38 | total = 10 39 | id = generator.uuid4() 40 | 41 | given.division.exists(id=id) 42 | given.division.teams_exist(id, total) 43 | 44 | division = api.divisions.get(id=id) 45 | teams = division.get_teams() 46 | 47 | assert len(teams) == total 48 | 49 | 50 | def test_get_members(api, given, verifier): 51 | # preconditions 52 | api.aa = True 53 | total = 10 54 | id = generator.uuid4() 55 | 56 | given.division.exists(id=id) 57 | given.division.members_exist(id, total) 58 | 59 | division = api.divisions.get(id=id) 60 | members = division.get_members() 61 | 62 | assert len(members) == total 63 | -------------------------------------------------------------------------------- /tests/test_drs_import.py: -------------------------------------------------------------------------------- 1 | import faker 2 | 3 | generator = faker.Factory.create() 4 | 5 | 6 | def test_imports_bulk_get(api, given, verifier): 7 | # preconditions 8 | total = 10 9 | file_ids = [generator.uuid4() for _ in range(total)] 10 | _import = { 11 | 'id': generator.uuid4(), 12 | 'result': [ 13 | {'resource': {'id': _id, 'href': generator.url()}} 14 | for _id in file_ids 15 | ] 16 | } 17 | 18 | given.drs_imports.can_be_retrieved_in_bulk(_import) 19 | 20 | # action 21 | response = api.drs_imports.bulk_get(_import['id']) 22 | 23 | # verification 24 | assert len(response.result) == total 25 | verifier.drs_imports.bulk_retrieved(response.id) 26 | 27 | 28 | def test_imports_bulk_submit(api, given, verifier): 29 | # preconditions 30 | total = 10 31 | 32 | imports = [ 33 | { 34 | "drs_uri": generator.name(), 35 | "project": generator.name(), 36 | "metadata": { 37 | generator.name(): generator.name(), 38 | generator.name(): generator.name() 39 | }, 40 | "name": generator.name() 41 | } 42 | for _ in range(total) 43 | ] 44 | tags = [generator.name()] 45 | 46 | given.drs_imports.can_be_submitted_in_bulk(imports) 47 | 48 | # action 49 | response = api.drs_imports.bulk_submit(imports, tags) 50 | 51 | # verification 52 | assert len(response.result) == total 53 | verifier.drs_imports.bulk_submitted() 54 | -------------------------------------------------------------------------------- /tests/test_endpoints_rate.py: -------------------------------------------------------------------------------- 1 | import faker 2 | 3 | generator = faker.Factory.create() 4 | 5 | 6 | # Endpoints 7 | def test_endpoints(api, given, verifier): 8 | # preconditions 9 | given.endpoints.defined() 10 | 11 | # action 12 | endpoints = api.endpoints.get() 13 | 14 | # verification 15 | assert 'upload' in endpoints.upload_url 16 | assert 'projects' in endpoints.projects_url 17 | assert 'action' in endpoints.action_url 18 | assert 'user' in endpoints.user_url 19 | assert 'users' in endpoints.users_url 20 | assert 'tasks' in endpoints.tasks_url 21 | assert 'apps' in endpoints.apps_url 22 | verifier.endpoints.fetched() 23 | 24 | 25 | # Rate 26 | def test_rate_limit(api, given): 27 | # preconditions 28 | mock = {'rate': {'limit': 100, 'remaining': 20}, 29 | 'instance_limit': {'limit': 100, 'remaining': 20}} 30 | given.rate.limit_available(**mock) 31 | 32 | # action 33 | result = api.rate_limit.get() 34 | 35 | assert result.rate.limit == mock['rate']['limit'] 36 | assert result.rate.remaining == mock['rate']['remaining'] 37 | assert result.instance_limit.limit == mock['instance_limit']['limit'] 38 | assert result.instance_limit.remaining == mock['instance_limit'][ 39 | 'remaining'] 40 | -------------------------------------------------------------------------------- /tests/test_error_handlers.py: -------------------------------------------------------------------------------- 1 | import time 2 | from json import JSONDecodeError 3 | 4 | import faker 5 | import pytest 6 | import requests 7 | 8 | from sevenbridges.http.error_handlers import ( 9 | rate_limit_sleeper, maintenance_sleeper, general_error_sleeper) 10 | 11 | generator = faker.Factory.create() 12 | 13 | 14 | class MockSession: 15 | def __init__(self, mock): 16 | self.mock = mock 17 | 18 | def send(self, response): 19 | return self.mock.pop() 20 | 21 | 22 | def test_rate_limit_sleeper(api): 23 | resp429 = requests.Response() 24 | resp429.headers = { 25 | 'X-RateLimit-Reset': time.time() + 1 26 | } 27 | resp429.status_code = 429 28 | resp200 = requests.Response() 29 | resp200.status_code = 200 30 | 31 | api._session = MockSession([resp429, resp200]) 32 | resp = rate_limit_sleeper(api, resp429) 33 | 34 | assert resp.status_code == resp200.status_code 35 | 36 | 37 | def test_maintenance_sleeper_invalid_json(api): 38 | resp503 = requests.Response() 39 | resp503.status_code = 503 40 | resp503.headers = {'Content-Type': 'application/json'} 41 | 42 | api._session = MockSession([resp503]) 43 | with pytest.raises(JSONDecodeError): 44 | maintenance_sleeper(api, resp503, 1) 45 | 46 | 47 | def test_maintenance_sleeper(api): 48 | resp503 = requests.Response() 49 | resp503.status_code = 503 50 | resp503.headers = {'Content-Type': 'application/json'} 51 | resp503._content = b'{"code": 0}' 52 | resp200 = requests.Response() 53 | resp200.status_code = 200 54 | 55 | api._session = MockSession([resp503, resp200]) 56 | resp = maintenance_sleeper(api, resp503, 1) 57 | 58 | assert resp.status_code == resp200.status_code 59 | 60 | 61 | def test_general_error_sleeper(api): 62 | resp500 = requests.Response() 63 | resp500.status_code = 500 64 | resp200 = requests.Response() 65 | resp200.status_code = 200 66 | 67 | api._session = MockSession([resp500, resp200]) 68 | resp = general_error_sleeper(api, resp500, 1) 69 | 70 | assert resp.status_code == resp200.status_code 71 | -------------------------------------------------------------------------------- /tests/test_exports.py: -------------------------------------------------------------------------------- 1 | import faker 2 | import pytest 3 | 4 | generator = faker.Factory.create() 5 | 6 | 7 | def test_exports_query(api, given, verifier): 8 | # preconditions 9 | total = 10 10 | given.exports.query(total=10) 11 | 12 | # action 13 | exports = api.exports.query(limit=10) 14 | 15 | # verification 16 | assert exports.total == total 17 | assert len(exports) == total 18 | 19 | verifier.exports.queried() 20 | 21 | 22 | def test_exports_submit(api, given, verifier): 23 | # preconditions 24 | id = generator.uuid4() 25 | file = generator.name() 26 | volume = generator.name() 27 | location = f'{generator.name()}/{generator.name()}' 28 | given.exports.can_be_submitted(id=id) 29 | 30 | # action 31 | exports = api.exports.submit_export(file, volume, location) 32 | 33 | assert exports.id == id 34 | verifier.exports.submitted() 35 | 36 | 37 | def test_exports_bulk_get(api, given, verifier): 38 | # preconditions 39 | total = 10 40 | 41 | export_ids = [generator.uuid4() for _ in range(total)] 42 | exports = [{'id': id_} for id_ in export_ids] 43 | given.exports.can_be_retrieved_in_bulk(exports) 44 | 45 | # action 46 | response = api.exports.bulk_get(export_ids) 47 | 48 | # verification 49 | assert len(response) == total 50 | verifier.exports.bulk_retrieved() 51 | 52 | 53 | @pytest.mark.parametrize('copy_only', [True, False]) 54 | def test_exports_bulk_submit(api, given, verifier, copy_only): 55 | # preconditions 56 | total = 10 57 | 58 | exports = [ 59 | { 60 | 'file': generator.name(), 61 | 'volume': generator.name(), 62 | 'location': generator.name(), 63 | 'properties': {}, 64 | 'overwrite': True 65 | } 66 | for _ in range(total) 67 | ] 68 | given.exports.can_be_submitted_in_bulk(exports) 69 | 70 | # action 71 | response = api.exports.bulk_submit(exports, copy_only=copy_only) 72 | 73 | # verification 74 | assert len(response) == total 75 | verifier.exports.bulk_submitted(copy_only) 76 | -------------------------------------------------------------------------------- /tests/test_imports.py: -------------------------------------------------------------------------------- 1 | import faker 2 | 3 | generator = faker.Factory.create() 4 | 5 | 6 | def test_imports_query(api, given, verifier): 7 | # preconditions 8 | total = 10 9 | given.imports.query(total=10) 10 | 11 | # action 12 | imports = api.imports.query(limit=10) 13 | 14 | # verification 15 | assert imports.total == total 16 | assert len(imports) == total 17 | 18 | verifier.imports.queried() 19 | 20 | 21 | def test_import_submit(api, given, verifier): 22 | # preconditions 23 | id = generator.uuid4() 24 | volume = generator.name() 25 | location = generator.name() 26 | project = f'{generator.name()}/{generator.name()}' 27 | given.imports.can_be_submitted(id=id) 28 | 29 | # action 30 | imports = api.imports.submit_import( 31 | volume=volume, 32 | location=location, 33 | project=project 34 | ) 35 | 36 | assert imports.id == id 37 | verifier.imports.submitted() 38 | 39 | 40 | def test_import_to_folder_submit(api, given, verifier): 41 | # preconditions 42 | id = generator.uuid4() 43 | volume = generator.name() 44 | location = generator.name() 45 | parent = f'{generator.name()}/' 46 | given.imports.can_be_submitted(id=id) 47 | preserve_folder_structure = False 48 | 49 | # action 50 | imports = api.imports.submit_import( 51 | volume=volume, 52 | location=location, 53 | parent=parent, 54 | preserve_folder_structure=preserve_folder_structure 55 | ) 56 | 57 | assert imports.id == id 58 | verifier.imports.submitted() 59 | 60 | 61 | def test_imports_bulk_get(api, given, verifier): 62 | # preconditions 63 | total = 10 64 | 65 | import_ids = [generator.uuid4() for _ in range(total)] 66 | imports = [{'id': id_} for id_ in import_ids] 67 | given.imports.can_be_retrieved_in_bulk(imports) 68 | 69 | # action 70 | response = api.imports.bulk_get(import_ids) 71 | 72 | # verification 73 | assert len(response) == total 74 | verifier.imports.bulk_retrieved() 75 | 76 | 77 | def test_imports_bulk_submit(api, given, verifier): 78 | # preconditions 79 | total = 10 80 | 81 | imports = [ 82 | { 83 | 'volume': generator.name(), 84 | 'location': generator.name(), 85 | 'project': generator.name(), 86 | 'name': generator.name(), 87 | 'overwrite': True 88 | } 89 | for _ in range(total) 90 | ] 91 | given.imports.can_be_submitted_in_bulk(imports) 92 | 93 | # action 94 | response = api.imports.bulk_submit(imports) 95 | 96 | # verification 97 | assert len(response) == total 98 | verifier.imports.bulk_submitted() 99 | -------------------------------------------------------------------------------- /tests/test_marker.py: -------------------------------------------------------------------------------- 1 | import faker 2 | 3 | generator = faker.Factory.create() 4 | 5 | 6 | def test_markers_query(api, given, verifier): 7 | # preconditions 8 | total = 10 9 | file = generator.name() 10 | given.marker.query(total=10, file=file) 11 | 12 | # action 13 | markers = api.markers.query(file=file) 14 | 15 | # verification 16 | assert markers.total == total 17 | assert len(markers) == total 18 | 19 | verifier.marker.queried() 20 | 21 | 22 | def test_marker_create(api, given, verifier): 23 | # preconditions 24 | file = generator.uuid4() 25 | name = generator.name() 26 | chromosome = "chr1" 27 | position = { 28 | "start": 0, 29 | "end": 10 30 | } 31 | given.marker.created(name=name, file=file) 32 | 33 | marker = api.markers.create(file, name, position, chromosome) 34 | 35 | # verifier 36 | assert marker.name == name 37 | verifier.marker.created() 38 | 39 | 40 | def test_modify_marker(api, given, verifier): 41 | # preconditions 42 | _id = 'my/marker' 43 | name = generator.name() 44 | given.marker.exists(id=_id) 45 | given.marker.modified(id=_id, name=name) 46 | 47 | # action 48 | marker = api.markers.get(_id) 49 | marker.name = name 50 | marker.save() 51 | 52 | # verifier 53 | assert marker.name == name 54 | verifier.marker.modified(marker.id) 55 | -------------------------------------------------------------------------------- /tests/test_pagination.py: -------------------------------------------------------------------------------- 1 | import faker 2 | 3 | generator = faker.Factory.create() 4 | 5 | 6 | def test_single_page_pagination(api, given, verifier): 7 | # preconditions 8 | limit = 2 9 | total = 10 10 | given.project.paginated_projects(limit, total) 11 | # action 12 | projects = api.projects.query(offset=0, limit=limit) 13 | 14 | # verification 15 | assert projects.total == total 16 | assert len(projects) == limit 17 | verifier.project.queried(0, limit) 18 | 19 | 20 | def test_all_pages_pagination(api, given, verifier): 21 | # preconditions 22 | limit = 2 23 | total = 10 24 | given.project.paginated_projects(limit, total) 25 | 26 | # action 27 | projects = api.projects.query(offset=0, limit=limit) 28 | 29 | # verification 30 | assert len(list(projects.all())) == total 31 | for i in range(0, limit, total): 32 | verifier.project.queried(i, limit) 33 | 34 | 35 | def test_single_page_back(api, given, verifier): 36 | # preconditions 37 | limit = 2 38 | total = 10 39 | given.project.paginated_projects(limit, total) 40 | 41 | # action 42 | projects = api.projects.query(offset=4, limit=limit) 43 | 44 | # verification 45 | projects.previous_page() 46 | verifier.project.queried(2, limit) 47 | -------------------------------------------------------------------------------- /tests/test_teams.py: -------------------------------------------------------------------------------- 1 | import faker 2 | 3 | generator = faker.Factory.create() 4 | 5 | 6 | def test_get_team(api, given, verifier): 7 | # precondition 8 | api.aa = True 9 | id = generator.uuid4() 10 | given.team.exists(id=id) 11 | 12 | # action 13 | team = api.teams.get(id=id) 14 | 15 | # verification 16 | assert team.id == id 17 | verifier.team.team_fetched(id=id) 18 | 19 | 20 | def test_team_query(api, given, verifier): 21 | # preconditions 22 | api.aa = True 23 | total = 10 24 | given.team.query(total) 25 | 26 | # action 27 | teams = api.teams.query(division=generator.name()) 28 | 29 | # verification 30 | assert len(teams) == total 31 | verifier.team.teams_fetched() 32 | 33 | 34 | def test_team_modify(api, given, verifier): 35 | api.aa = True 36 | 37 | id = generator.uuid4() 38 | new_name = generator.name() 39 | 40 | given.team.exists(id=id) 41 | given.team.modified(id=id, name=new_name) 42 | 43 | team = api.teams.get(id) 44 | team.name = new_name 45 | team.save() 46 | 47 | assert team.name == new_name 48 | verifier.team.modified(id=id) 49 | 50 | 51 | def test_team_created(api, given, verifier): 52 | # preconditions 53 | api.aa = True 54 | name = generator.name() 55 | given.team.created(name) 56 | 57 | # action 58 | team = api.teams.create(name, division=generator.name()) 59 | 60 | # verification 61 | assert team.name == name 62 | verifier.team.created() 63 | 64 | 65 | def test_team_get_members(api, given, verifier): 66 | # preconditions 67 | api.aa = True 68 | total = 10 69 | id = generator.uuid4() 70 | 71 | given.team.exists(id=id) 72 | given.team_member.queried(id, total) 73 | 74 | # action 75 | team = api.teams.get(id) 76 | members = team.get_members() 77 | 78 | assert len(members) == total 79 | verifier.team.members_fetched(id) 80 | -------------------------------------------------------------------------------- /tests/test_transformer.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from datetime import datetime 3 | 4 | import faker 5 | import pytest 6 | 7 | from sevenbridges import ( 8 | Project, Task, App, File, 9 | User, BillingGroup, Marker, Division 10 | ) 11 | from sevenbridges.errors import SbgError 12 | from sevenbridges.meta.transformer import Transform 13 | from sevenbridges.models.team import Team 14 | from sevenbridges.models.volume import Volume 15 | 16 | generator = faker.Factory.create() 17 | 18 | 19 | def random_uuid(): 20 | return str(uuid.uuid4()) 21 | 22 | 23 | @pytest.mark.parametrize("project", ['u/p', Project(id='u/p')]) 24 | def test_transform_project(project): 25 | Transform.to_project(project) 26 | 27 | 28 | @pytest.mark.parametrize("project", [[], {}, b'', None, type]) 29 | def test_transform_project_invalid_values(project): 30 | with pytest.raises(SbgError): 31 | Transform.to_project(project) 32 | 33 | 34 | @pytest.mark.parametrize("task", [random_uuid(), Task(id=random_uuid())]) 35 | def test_transform_task(task): 36 | Transform.to_task(task) 37 | 38 | 39 | @pytest.mark.parametrize("task", [[], {}, b'', None, type]) 40 | def test_transform_task_invalid_values(task): 41 | with pytest.raises(SbgError): 42 | Transform.to_task(task) 43 | 44 | 45 | @pytest.mark.parametrize("app", ['u/p/a/0', App(id='u/p/a/1')]) 46 | def test_transform_app(app): 47 | Transform.to_app(app) 48 | 49 | 50 | @pytest.mark.parametrize("app", [[], {}, b'', None, type]) 51 | def test_transform_app_invalid_values(app): 52 | with pytest.raises(SbgError): 53 | Transform.to_task(app) 54 | 55 | 56 | @pytest.mark.parametrize("file", [random_uuid(), File(id=random_uuid())]) 57 | def test_transform_file(file): 58 | Transform.to_file(file) 59 | 60 | 61 | @pytest.mark.parametrize("file", [[], {}, b'', None, type]) 62 | def test_transform_file_invalid_values(file): 63 | with pytest.raises(SbgError): 64 | Transform.to_file(file) 65 | 66 | 67 | @pytest.mark.parametrize("user", ['u', User(username='u')]) 68 | def test_transform_user(user): 69 | Transform.to_user(user) 70 | 71 | 72 | @pytest.mark.parametrize("user", [[], {}, b'', None, type]) 73 | def test_transform_user_invalid_values(user): 74 | with pytest.raises(SbgError): 75 | Transform.to_user(user) 76 | 77 | 78 | @pytest.mark.parametrize( 79 | "group", [random_uuid(), BillingGroup(id=random_uuid())] 80 | ) 81 | def test_transform_billing_group(group): 82 | Transform.to_billing_group(group) 83 | 84 | 85 | @pytest.mark.parametrize("group", [[], {}, b'', None, type]) 86 | def test_transform_billing_group_invalid_values(group): 87 | with pytest.raises(SbgError): 88 | Transform.to_billing_group(group) 89 | 90 | 91 | @pytest.mark.parametrize("volume", ['u/p', Volume(id='u/p')]) 92 | def test_transform_volume(volume): 93 | Transform.to_volume(volume) 94 | 95 | 96 | @pytest.mark.parametrize("volume", [[], {}, b'', None, type]) 97 | def test_transform_volume_invalid_values(volume): 98 | with pytest.raises(SbgError): 99 | Transform.to_volume(volume) 100 | 101 | 102 | @pytest.mark.parametrize("datestring,expected", [ 103 | ('2017-01-10T14:04:23', '2017-01-10T14:04:23'), 104 | (datetime(2017, 1, 10, 14, 4, 23, 838459), '2017-01-10T14:04:23'), 105 | ]) 106 | def test_transform_datestring(datestring, expected): 107 | assert Transform.to_datestring(datestring) == expected 108 | 109 | 110 | @pytest.mark.parametrize("datestring", ['', None, [], {}]) 111 | def test_transform_datestring_invalid(datestring): 112 | with pytest.raises(SbgError): 113 | Transform.to_datestring(datestring) 114 | 115 | 116 | @pytest.mark.parametrize("marker", [str(generator.uuid4()), 117 | Marker(id=generator.uuid4())]) 118 | def test_transform_marker(marker): 119 | Transform.to_marker(marker) 120 | 121 | 122 | @pytest.mark.parametrize("division", [generator.name(), 123 | Division(id=generator.name())]) 124 | def test_transform_divisions(division): 125 | Transform.to_division(division) 126 | 127 | 128 | @pytest.mark.parametrize("team", [str(generator.uuid4()), 129 | Team(id=generator.uuid4())]) 130 | def test_transform_teams(team): 131 | Transform.to_team(team) 132 | -------------------------------------------------------------------------------- /tests/test_users.py: -------------------------------------------------------------------------------- 1 | # Users 2 | def test_get_my_info(api, given, verifier): 3 | # preconditions 4 | given.user.authenticated() 5 | 6 | # action 7 | api.users.me() 8 | 9 | # verification 10 | verifier.user.authenticated_user_fetched() 11 | 12 | 13 | def test_get_users_info(api, given, verifier): 14 | # preconditions 15 | username = 'test' 16 | given.user.exists(username=username) 17 | 18 | # action 19 | user = api.users.get(username) 20 | 21 | # verification 22 | assert user.username == username 23 | assert username in repr(user) 24 | verifier.user.fetched(username) 25 | 26 | 27 | def test_user_equality(api, given, verifier): 28 | # preconditions 29 | username = 'test' 30 | given.user.authenticated() 31 | given.user.exists(username=username) 32 | 33 | # action 34 | me = api.users.me() 35 | other = api.users.get(username) 36 | 37 | # verification 38 | assert me != other 39 | verifier.user.authenticated_user_fetched() 40 | verifier.user.fetched(username) 41 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from sevenbridges.transfer.utils import Part, Progress 2 | 3 | 4 | def test_transfer_utils(): 5 | start = 10 6 | size = 20 7 | part = Part(start=start, size=size) 8 | assert part.start == start and part.size == size 9 | 10 | num_of_parts = 10 11 | parts_done = 2 12 | bytes_done = 2000000 13 | file_size = 10 14 | duration = 2 15 | p = Progress(num_of_parts, parts_done, bytes_done, file_size, duration) 16 | 17 | assert p.num_of_parts == num_of_parts 18 | assert p.file_size == file_size 19 | assert p.duration == duration 20 | assert p.bytes_done == bytes_done 21 | assert p.bandwidth == 1 22 | assert p.progress > 0 23 | -------------------------------------------------------------------------------- /tests/test_volumes.py: -------------------------------------------------------------------------------- 1 | import faker 2 | 3 | 4 | generator = faker.Factory.create() 5 | 6 | 7 | def test_volumes_query(api, given, verifier): 8 | # preconditions 9 | total = 10 10 | given.volume.can_be_queried(total) 11 | 12 | # action 13 | volumes = api.volumes.query() 14 | 15 | # verification 16 | assert volumes.total == total 17 | assert len(volumes) == total 18 | 19 | verifier.volume.queried() 20 | 21 | 22 | def test_create_s3_volume(api, given, verifier): 23 | # preconditions 24 | name = generator.name() 25 | bucket = generator.name() 26 | access_key_id = generator.name() 27 | access_key_secret = generator.name() 28 | access_mode = 'RO' 29 | given.volume.volume_created(name='test') 30 | 31 | # action 32 | volume = api.volumes.create_s3_volume(name, bucket, access_key_id, 33 | access_key_secret, access_mode) 34 | 35 | # verifier 36 | assert volume.name == 'test' 37 | verifier.volume.created() 38 | 39 | 40 | def test_create_s3_volume_role_auth(api, given, verifier): 41 | # preconditions 42 | name = generator.name() 43 | bucket = generator.name() 44 | role_arn = generator.name() 45 | external_id = generator.name() 46 | access_mode = 'RO' 47 | given.volume.volume_created(name='test') 48 | 49 | # action 50 | volume = api.volumes.create_s3_volume_role_auth( 51 | name, bucket, role_arn, external_id, access_mode) 52 | 53 | # verifier 54 | assert volume.name == 'test' 55 | verifier.volume.created() 56 | 57 | 58 | def test_create_google_volume(api, given, verifier): 59 | # preconditions 60 | name = generator.name() 61 | bucket = generator.name() 62 | access_key_id = generator.name() 63 | access_key_secret = generator.name() 64 | access_mode = 'RO' 65 | given.volume.volume_created(name='test') 66 | 67 | # action 68 | volume = api.volumes.create_google_volume(name, bucket, access_key_id, 69 | access_key_secret, access_mode) 70 | 71 | # verifier 72 | assert volume.name == 'test' 73 | verifier.volume.created() 74 | 75 | 76 | def test_create_oss_volume(api, given, verifier): 77 | # preconditions 78 | name = generator.name() 79 | bucket = generator.name() 80 | endpoint = generator.name() 81 | access_key_id = generator.name() 82 | secret_access_key = generator.name() 83 | access_mode = 'RO' 84 | description = generator.text() 85 | given.volume.volume_created(name='test') 86 | 87 | # action 88 | volume = api.volumes.create_oss_volume( 89 | name, bucket, endpoint, access_key_id, 90 | secret_access_key, access_mode, description, 91 | ) 92 | 93 | # verifier 94 | assert volume.name == 'test' 95 | verifier.volume.created() 96 | 97 | 98 | def test_modify_volume(api, given, verifier): 99 | # preconditions 100 | _id = 'my/volume' 101 | name = generator.name() 102 | description = generator.name() 103 | given.volume.exist(id=_id, name=name, description=generator.name()) 104 | given.volume.can_be_modified(id=_id, description=description) 105 | 106 | # action 107 | volume = api.volumes.get(_id) 108 | volume.description = description 109 | volume.save() 110 | 111 | # verifier 112 | assert volume.description == description 113 | verifier.volume.modified(volume.id) 114 | 115 | 116 | def test_volume_pagination(api, given): 117 | # preconditions 118 | limit = 2 119 | total = 10 120 | volume_id = 'test_volume' 121 | volume_data = {'id': volume_id} 122 | 123 | given.volume.paginated_file_list( 124 | limit=limit, 125 | volume_id=volume_id, 126 | num_of_files=total, 127 | volume_data=volume_data 128 | ) 129 | volume = api.volumes.get(id=volume_id) 130 | 131 | # action 132 | item_list = volume.list(limit=limit) 133 | items = [item for item in item_list.all()] 134 | 135 | # verifier 136 | assert len(item_list) == limit 137 | assert len(items) == total 138 | 139 | 140 | def test_volume_get_member(api, given, verifier): 141 | # precondition 142 | member_username = generator.user_name() 143 | id = generator.slug() 144 | given.volume.exist(id=id) 145 | given.volume.has_member(id, member_username) 146 | 147 | # action 148 | volume = api.volumes.get(id) 149 | member = volume.get_member(member_username) 150 | 151 | # verification 152 | assert member.username == member_username 153 | verifier.volume.member_retrieved(id, member_username) 154 | --------------------------------------------------------------------------------