├── .coveragerc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── feature_request.md │ └── question_help.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── coverage.yml │ ├── pypiupload.yml │ └── spec_update.yml ├── .gitignore ├── .gitmodules ├── .pylintrc ├── .readthedocs.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.rst ├── UPGRADING.md ├── codecov.yml ├── docs ├── Makefile ├── api │ ├── async.rst │ ├── auth.rst │ ├── common.rst │ ├── contacts.rst │ ├── dropbox.rst │ ├── exceptions.rst │ ├── file_properties.rst │ ├── file_requests.rst │ ├── files.rst │ ├── oauth.rst │ ├── paper.rst │ ├── seen_state.rst │ ├── sharing.rst │ ├── team.rst │ ├── team_common.rst │ ├── team_log.rst │ ├── team_policies.rst │ ├── users.rst │ └── users_common.rst ├── conf.py ├── index.rst ├── make.bat └── tutorial.rst ├── dropbox ├── __init__.py ├── account.py ├── async.py ├── async_.py ├── auth.py ├── base.py ├── base_team.py ├── check.py ├── common.py ├── contacts.py ├── dropbox_client.py ├── exceptions.py ├── file_properties.py ├── file_requests.py ├── files.py ├── oauth.py ├── openid.py ├── paper.py ├── secondary_emails.py ├── seen_state.py ├── session.py ├── sharing.py ├── stone_base.py ├── stone_serializers.py ├── stone_validators.py ├── team.py ├── team_common.py ├── team_log.py ├── team_policies.py ├── users.py └── users_common.py ├── example ├── back-up-and-restore │ ├── README.md │ ├── backup-and-restore-example.py │ └── my-file.txt ├── oauth │ ├── commandline-oauth-pkce.py │ ├── commandline-oauth-scopes.py │ └── commandline-oauth.py └── updown.py ├── ez_setup.py ├── generate_base_client.py ├── requirements.txt ├── scripts ├── release_note_generator.sh └── update_version.sh ├── setup.cfg ├── setup.py ├── test ├── fixtures │ ├── Costa Rican Frog.jpg │ ├── dropbox_song.mp3 │ └── foo.txt ├── integration │ ├── expired-certs.crt │ ├── test_dropbox.py │ └── trusted-certs.crt ├── requirements.txt └── unit │ └── test_dropbox_unit.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = dropbox/ -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a report to help us improve the SDK 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of the bug. 12 | 13 | **To Reproduce** 14 | The steps to reproduce the behavior 15 | 16 | **Expected Behavior** 17 | A clear description of what you expected to happen. 18 | 19 | **Actual Behavior** 20 | A clear description of what actually happened 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Versions** 26 | * What version of the SDK are you using? 27 | * What version of the language are you using? 28 | * What platform are you using? (if applicable) 29 | 30 | **Additional context** 31 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature Request" 3 | about: Suggest an idea for this SDK 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Why is this feature valuable to you? Does it solve a problem you're having?** 11 | A clear and concise description of why this feature is valuable. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. (if applicable) 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question_help.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4AC Questions / Help" 3 | about: Get help with issues you are experiencing 4 | title: '' 5 | labels: help-wanted, question 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Before you start** 11 | Have you checked StackOverflow, previous issues, and Dropbox Developer Forums for help? 12 | 13 | **What is your question?** 14 | A clear and concise description of the question. 15 | 16 | **Screenshots** 17 | If applicable, add screenshots to help explain your question. 18 | 19 | **Versions** 20 | * What version of the SDK are you using? 21 | * What version of the language are you using? 22 | * What platform are you using? (if applicable) 23 | 24 | **Additional context** 25 | Add any other context about the question here. 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "monthly" 12 | 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | interval: "monthly" 17 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ## **Checklist** 6 | 7 | 8 | **General Contributing** 9 | - [ ] Have you read the Code of Conduct and signed the [CLA](https://opensource.dropbox.com/cla/)? 10 | 11 | **Is This a Code Change?** 12 | - [ ] Non-code related change (markdown/git settings etc) 13 | - [ ] SDK Code Change 14 | - [ ] Example/Test Code Change 15 | 16 | **Validation** 17 | - [ ] Does `tox` pass? 18 | - [ ] Do the tests pass? -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | CI: 7 | continue-on-error: true 8 | runs-on: ${{ matrix.os }} 9 | # Supported Versions: 10 | # https://github.com/actions/python-versions/blob/main/versions-manifest.json 11 | strategy: 12 | matrix: 13 | os: [macos-13, windows-latest] 14 | python-version: [3.6, 3.7, 3.8, pypy-3.7] 15 | exclude: 16 | - os: windows-latest 17 | python-version: 3.6 18 | include: 19 | - os: ubuntu-20.04 20 | python-version: 3.7 21 | - os: ubuntu-20.04 22 | python-version: 2.7 23 | steps: 24 | - uses: actions/checkout@v2 25 | - if: ${{ matrix.python-version == '2.7' }} 26 | name: Setup Python environment (2.7) 27 | run: | 28 | sudo apt-get install python-is-python2 29 | curl -sSL https://bootstrap.pypa.io/pip/2.7/get-pip.py -o get-pip.py 30 | python get-pip.py 31 | - if: ${{ matrix.python-version != '2.7' }} 32 | name: Setup Python environment 33 | uses: actions/setup-python@v3.1.4 34 | with: 35 | python-version: ${{ matrix.python-version }} 36 | - name: Install Requirements 37 | run: | 38 | python -m pip install --upgrade pip 39 | pip install flake8 pytest 40 | pip install -r requirements.txt 41 | pip install -r test/requirements.txt 42 | python setup.py install --user 43 | - name: Run Linter 44 | run: | 45 | flake8 setup.py dropbox example test 46 | - name: Run Unit Tests 47 | run: | 48 | pytest -v test/unit/test_dropbox_unit.py 49 | Docs: 50 | runs-on: ubuntu-20.04 51 | steps: 52 | - uses: actions/checkout@v2 53 | - name: Setup Python environment 54 | uses: actions/setup-python@v3.1.4 55 | with: 56 | python-version: '3.7' 57 | - name: Install Requirements 58 | run: | 59 | python -m pip install --upgrade pip 60 | pip install twine sphinx 61 | pip install -r requirements.txt 62 | pip install -r test/requirements.txt 63 | python setup.py install 64 | - name: Test Doc Generation 65 | run: | 66 | sphinx-build -b html docs build/html 67 | - name: Test Dist Generation 68 | run: | 69 | python setup.py sdist bdist_wheel 70 | twine check dist/* 71 | Integration: 72 | continue-on-error: true 73 | runs-on: ${{ matrix.os }} 74 | strategy: 75 | matrix: 76 | os: [macos-13, windows-latest] 77 | python-version: [3.6, 3.7, 3.8, pypy-3.7] 78 | exclude: 79 | - os: windows-latest 80 | python-version: 3.6 81 | include: 82 | - os: ubuntu-20.04 83 | python-version: 3.7 84 | - os: ubuntu-20.04 85 | python-version: 2.7 86 | steps: 87 | - uses: actions/checkout@v2.3.4 88 | - if: ${{ matrix.python-version == '2.7' }} 89 | name: Setup Python environment (2.7) 90 | run: | 91 | sudo apt-get install python-is-python2 92 | curl -sSL https://bootstrap.pypa.io/pip/2.7/get-pip.py -o get-pip.py 93 | python get-pip.py 94 | - if: ${{ matrix.python-version != '2.7' }} 95 | name: Setup Python environment 96 | uses: actions/setup-python@v3.1.4 97 | with: 98 | python-version: ${{ matrix.python-version }} 99 | - name: Install Requirements 100 | run: | 101 | python -m pip install --upgrade pip 102 | pip install flake8 pytest 103 | pip install -r requirements.txt 104 | pip install -r test/requirements.txt 105 | python setup.py install --user 106 | - name: Run Integration Tests 107 | env: 108 | LEGACY_USER_DROPBOX_TOKEN: ${{ secrets.LEGACY_USER_DROPBOX_TOKEN }} 109 | LEGACY_USER_CLIENT_ID: ${{ secrets.LEGACY_USER_CLIENT_ID }} 110 | LEGACY_USER_CLIENT_SECRET: ${{ secrets.LEGACY_USER_CLIENT_SECRET }} 111 | LEGACY_USER_REFRESH_TOKEN: ${{ secrets.LEGACY_USER_REFRESH_TOKEN }} 112 | SCOPED_USER_DROPBOX_TOKEN: ${{ secrets.SCOPED_USER_DROPBOX_TOKEN }} 113 | SCOPED_USER_CLIENT_ID: ${{ secrets.SCOPED_USER_CLIENT_ID }} 114 | SCOPED_USER_CLIENT_SECRET: ${{ secrets.SCOPED_USER_CLIENT_SECRET }} 115 | SCOPED_USER_REFRESH_TOKEN: ${{ secrets.SCOPED_USER_REFRESH_TOKEN }} 116 | SCOPED_TEAM_DROPBOX_TOKEN: ${{ secrets.SCOPED_TEAM_DROPBOX_TOKEN }} 117 | SCOPED_TEAM_CLIENT_ID: ${{ secrets.SCOPED_TEAM_CLIENT_ID }} 118 | SCOPED_TEAM_CLIENT_SECRET: ${{ secrets.SCOPED_TEAM_CLIENT_SECRET }} 119 | SCOPED_TEAM_REFRESH_TOKEN: ${{ secrets.SCOPED_TEAM_REFRESH_TOKEN }} 120 | DROPBOX_SHARED_LINK: ${{ secrets.DROPBOX_SHARED_LINK }} 121 | run: | 122 | pytest -v test/integration/test_dropbox.py 123 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | schedule: 8 | - cron: 0 0 * * * 9 | 10 | jobs: 11 | Coverage: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Setup Python environment 16 | uses: actions/setup-python@v3.1.4 17 | with: 18 | python-version: '3.7' 19 | - name: Install Requirements 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install coverage pytest 23 | pip install -r requirements.txt 24 | pip install -r test/requirements.txt 25 | python setup.py install 26 | - name: Generate Unit Test Coverage 27 | run: | 28 | coverage run --rcfile=.coveragerc -m pytest test/unit/test_dropbox_unit.py 29 | coverage xml 30 | - name: Publish Coverage 31 | uses: codecov/codecov-action@v3.1.6 32 | with: 33 | token: ${{ secrets.CODECOV_TOKEN }} 34 | flags: unit 35 | fail_ci_if_error: true 36 | IntegrationCoverage: 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v2 40 | - name: Setup Python environment 41 | uses: actions/setup-python@v3.1.4 42 | with: 43 | python-version: '3.7' 44 | - name: Install Requirements 45 | run: | 46 | python -m pip install --upgrade pip 47 | pip install coverage pytest 48 | pip install -r requirements.txt 49 | pip install -r test/requirements.txt 50 | python setup.py install 51 | - name: Generate Unit Test Coverage 52 | env: 53 | LEGACY_USER_DROPBOX_TOKEN: ${{ secrets.LEGACY_USER_DROPBOX_TOKEN }} 54 | LEGACY_USER_CLIENT_ID: ${{ secrets.LEGACY_USER_CLIENT_ID }} 55 | LEGACY_USER_CLIENT_SECRET: ${{ secrets.LEGACY_USER_CLIENT_SECRET }} 56 | LEGACY_USER_REFRESH_TOKEN: ${{ secrets.LEGACY_USER_REFRESH_TOKEN }} 57 | SCOPED_USER_DROPBOX_TOKEN: ${{ secrets.SCOPED_USER_DROPBOX_TOKEN }} 58 | SCOPED_USER_CLIENT_ID: ${{ secrets.SCOPED_USER_CLIENT_ID }} 59 | SCOPED_USER_CLIENT_SECRET: ${{ secrets.SCOPED_USER_CLIENT_SECRET }} 60 | SCOPED_USER_REFRESH_TOKEN: ${{ secrets.SCOPED_USER_REFRESH_TOKEN }} 61 | SCOPED_TEAM_DROPBOX_TOKEN: ${{ secrets.SCOPED_TEAM_DROPBOX_TOKEN }} 62 | SCOPED_TEAM_CLIENT_ID: ${{ secrets.SCOPED_TEAM_CLIENT_ID }} 63 | SCOPED_TEAM_CLIENT_SECRET: ${{ secrets.SCOPED_TEAM_CLIENT_SECRET }} 64 | SCOPED_TEAM_REFRESH_TOKEN: ${{ secrets.SCOPED_TEAM_REFRESH_TOKEN }} 65 | DROPBOX_SHARED_LINK: ${{ secrets.DROPBOX_SHARED_LINK }} 66 | run: | 67 | coverage run --rcfile=.coveragerc -m pytest test/integration/test_dropbox.py 68 | coverage xml 69 | - name: Publish Coverage 70 | uses: codecov/codecov-action@v3.1.6 71 | with: 72 | token: ${{ secrets.CODECOV_TOKEN }} 73 | flags: integration 74 | fail_ci_if_error: true 75 | -------------------------------------------------------------------------------- /.github/workflows/pypiupload.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Publish to PyPi 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | runs-on: ubuntu-20.04 13 | strategy: 14 | matrix: 15 | python-version: [2.7, 3.7] 16 | 17 | steps: 18 | - uses: actions/checkout@v2.3.4 19 | - if: ${{ matrix.python-version == '2.7' }} 20 | name: Setup Python environment (2.7) 21 | run: | 22 | sudo apt-get install python-is-python2 23 | curl -sSL https://bootstrap.pypa.io/pip/2.7/get-pip.py -o get-pip.py 24 | python get-pip.py 25 | - if: ${{ matrix.python-version != '2.7' }} 26 | name: Setup Python environment 27 | uses: actions/setup-python@v3.1.4 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install setuptools wheel twine 34 | - name: Build 35 | run: | 36 | python setup.py bdist_wheel 37 | - name: Build Sources (Python 3) 38 | run: python setup.py sdist 39 | if: ${{ matrix.python-version != '2.7' }} 40 | - name: Publish 41 | env: 42 | TWINE_USERNAME: __token__ 43 | TWINE_PASSWORD: ${{ secrets.pypi_secret }} 44 | run: | 45 | twine check dist/* 46 | twine upload dist/* 47 | -------------------------------------------------------------------------------- /.github/workflows/spec_update.yml: -------------------------------------------------------------------------------- 1 | name: Spec Update 2 | on: 3 | workflow_dispatch: 4 | repository_dispatch: 5 | types: [spec_update] 6 | 7 | jobs: 8 | Update: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Setup Python environment 13 | uses: actions/setup-python@v3.1.4 14 | with: 15 | python-version: 3.7 16 | - name: Get current time 17 | uses: 1466587594/get-current-time@v2 18 | id: current-time 19 | with: 20 | format: YYYY_MM_DD 21 | utcOffset: "-08:00" 22 | - name: Update Modules 23 | run: | 24 | git submodule init 25 | git submodule update --remote --recursive 26 | - name: Generate Branch Name 27 | id: git-branch 28 | run: | 29 | echo "::set-output name=branch::spec_update_${{ steps.current-time.outputs.formattedTime }}" 30 | - name: Generate Num Diffs 31 | id: git-diff-num 32 | run: | 33 | diffs=$(git diff --submodule spec | grep ">" | wc -l) 34 | echo "Number of Spec diffs: $diffs" 35 | echo "::set-output name=num-diff::$diffs" 36 | - name: Generate Diff 37 | id: git-diff 38 | run: | 39 | cd spec 40 | gitdiff=$(git log -n ${{ steps.git-diff-num.outputs.num-diff }} --pretty="format:%n %H %n%n %b") 41 | commit="Automated Spec Update $gitdiff" 42 | commit="${commit//'%'/'%25'}" 43 | commit="${commit//$'\n'/'%0A'}" 44 | commit="${commit//$'\r'/'%0D'}" 45 | echo "Commit Message: $commit" 46 | echo "::set-output name=commit::$commit" 47 | cd .. 48 | - name: Generate New Routes 49 | run: | 50 | python -m pip install --upgrade pip 51 | pip install -r requirements.txt 52 | python generate_base_client.py 53 | - name: Create Pull Request 54 | uses: peter-evans/create-pull-request@v5.0.0 55 | if: steps.git-diff-num.outputs.num-diff != 0 56 | with: 57 | token: ${{ secrets.SPEC_UPDATE_TOKEN }} 58 | commit-message: | 59 | ${{ steps.git-diff.outputs.commit}} 60 | branch: ${{ steps.git-branch.outputs.branch }} 61 | delete-branch: true 62 | title: 'Automated Spec Update' 63 | body: | 64 | ${{ steps.git-diff.outputs.commit}} 65 | base: 'main' 66 | team-reviewers: | 67 | owners 68 | maintainers 69 | draft: false 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Coverage Files 2 | .coverage 3 | coverage.xml 4 | 5 | # Generic Python files 6 | /.cache/ 7 | /.eggs/ 8 | /.idea/ 9 | /.tox/ 10 | /build/ 11 | /dist/ 12 | /docs/_build/ 13 | __pycache__/ 14 | .DS_Store 15 | *.egg 16 | *.egg-info/ 17 | *.pyc 18 | *.pyo 19 | *~ 20 | 21 | # Virtual Environments 22 | .venv 23 | venv/ 24 | venv3/ 25 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "spec"] 2 | path = spec 3 | url = https://github.com/dropbox/dropbox-api-spec.git 4 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MESSAGES CONTROL] 2 | disable=C,R,file-ignored,fixme,locally-disabled,protected-access,useless-else-on-loop,unnecessary-pass,raise-missing-from 3 | enable=useless-suppression 4 | 5 | [REPORTS] 6 | reports=n 7 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | version: 2 6 | 7 | build: 8 | os: ubuntu-20.04 9 | tools: 10 | python: "3.7" 11 | 12 | python: 13 | install: 14 | - requirements: requirements.txt 15 | - method: setuptools 16 | path: . 17 | 18 | sphinx: 19 | configuration: docs/conf.py 20 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Dropbox Code Of Conduct 2 | 3 | *Dropbox believes that an inclusive development environment fosters greater technical achievement. To encourage a diverse group of contributors we've adopted this code of conduct.* 4 | 5 | Please read the Official Dropbox [Code of Conduct](https://opensource.dropbox.com/coc/) before contributing. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Dropbox SDK for Python 2 | We value and rely on the feedback from our community. This comes in the form of bug reports, feature requests, and general guidance. We welcome your issues and pull requests and try our hardest to be timely in both response and resolution. Please read through this document before submitting issues or pull requests to ensure we have the necessary information to help you resolve your issue. 3 | 4 | ## Filing Bug Reports 5 | You can file a bug report on the [GitHub Issues][issues] page. 6 | 7 | 1. Search through existing issues to ensure that your issue has not been reported. If it is a common issue, there is likely already an issue. 8 | 9 | 2. Please ensure you are using the latest version of the SDK. While this may be a valid issue, we only will fix bugs affecting the latest version and your bug may have been fixed in a newer version. 10 | 11 | 3. Provide as much information as you can regarding the language version, SDK version, and any other relevant information about your environment so we can help resolve the issue as quickly as possible. 12 | 13 | ## Submitting Pull Requests 14 | 15 | We are more than happy to recieve pull requests helping us improve the state of our SDK. You can open a new pull request on the [GitHub Pull Requests][pr] page. 16 | 17 | 1. Please ensure that you have read the [License][license], [Code of Conduct][coc] and have signed the [Contributing License Agreement (CLA)][cla]. 18 | 19 | 2. Please add tests confirming the new functionality works. Pull requests will not be merged without passing continuous integration tests unless the pull requests aims to fix existing issues with these tests. 20 | 21 | ## Updating Generated Code 22 | 23 | Generated code can be updated by running the following code: 24 | 25 | ``` 26 | $ pip install -r requirements.txt 27 | $ git submodule init 28 | $ git submodule update --remote --recursive 29 | $ python generate_base_client.py 30 | ``` 31 | 32 | Note: Stone updates must be made by updating `requirements.txt` as it is no longer a submodule. 33 | 34 | ## Testing the Code 35 | 36 | We use the [`tox`](https://tox.readthedocs.org/) package to run tests. To install and run the unit tests, you can use: 37 | 38 | ``` 39 | $ pip install tox 40 | $ tox 41 | ``` 42 | 43 | Or if you would like to specify a specific target to run you can run this: 44 | 45 | ``` 46 | $ tox -e {TARGET} 47 | ``` 48 | 49 | If you want to run the integration tests, you can use the following: 50 | 51 | ``` 52 | $ export DROPBOX_REFRESH_TOKEN={fill in refresh token} 53 | $ export DROPBOX_APP_KEY={fill in app key} 54 | $ export DROPBOX_APP_SECRET={fill in app secret} 55 | $ export DROPBOX_TEAM_TOKEN={fill in team token} 56 | $ export DROPBOX_TOKEN={fill in access token} 57 | $ tox -e test_integration 58 | ``` 59 | Note: If you do not have all of these tokens available, we run integration tests as a part of pull request validation and you are able to rely on those if you are unable to obtain yourself. 60 | 61 | We do recommend developing in a virtual environment in order to ensure you have a clean testing environment. 62 | 63 | If you want to build the documentation locally, you can run this: 64 | 65 | ``` 66 | $ tox -e docs 67 | ``` 68 | 69 | The documentation will be built into `build/html`. 70 | 71 | [issues]: https://github.com/dropbox/dropbox-sdk-python/issues 72 | [pr]: https://github.com/dropbox/dropbox-sdk-python/pulls 73 | [coc]: https://github.com/dropbox/dropbox-sdk-python/blob/main/CODE_OF_CONDUCT.md 74 | [license]: https://github.com/dropbox/dropbox-sdk-python/blob/main/LICENSE 75 | [cla]: https://opensource.dropbox.com/cla/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Dropbox Inc., http://www.dropbox.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include ez_setup.py 2 | include LICENSE 3 | include *.rst 4 | include test/requirements.txt 5 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://cfl.dropboxstatic.com/static/images/sdk/python_banner.png 2 | :target: https://github.com/dropbox/dropbox-sdk-python 3 | 4 | .. image:: https://img.shields.io/pypi/pyversions/dropbox.svg 5 | :target: https://pypi.python.org/pypi/dropbox 6 | 7 | .. image:: https://img.shields.io/pypi/v/dropbox.svg 8 | :target: https://pypi.python.org/pypi/dropbox 9 | 10 | .. image:: https://codecov.io/gh/dropbox/dropbox-sdk-python/branch/main/graph/badge.svg 11 | :target: https://codecov.io/gh/dropbox/dropbox-sdk-python 12 | 13 | The offical Dropbox SDK for Python. 14 | 15 | Documentation can be found on `Read The Docs`_. 16 | 17 | Installation 18 | ============ 19 | 20 | Create an app via the `Developer Console`_. 21 | 22 | Install via `pip `_: 23 | 24 | .. code-block:: console 25 | 26 | $ pip install dropbox 27 | 28 | Install from source: 29 | 30 | .. code-block:: console 31 | 32 | $ git clone git://github.com/dropbox/dropbox-sdk-python.git 33 | $ cd dropbox-sdk-python 34 | $ python setup.py install 35 | 36 | After installation, follow one of our `Examples`_ or read the documentation on `Read The Docs`_. 37 | 38 | You can also view our `OAuth Guide`_. 39 | 40 | Examples 41 | ======== 42 | 43 | We provide `Examples`_ to help get you started with a lot of the basic functionality in the SDK. 44 | 45 | - **OAuth** 46 | - `Commandline OAuth Basic `_ - Shows a simple example of commandline oauth (no redirect). 47 | - `Commandline OAuth Scopes `_ - Shows a simple example of commandline oauth using scopes. 48 | - `Commandline OAuth PKCE `_ - Shows a simple example of commandline oauth using PKCE. 49 | - **Other Examples** 50 | - `Updown `_ - Sample application that uploads the contents of your ``Downloads`` folder to Dropbox. 51 | - `Backup and Restore `_ - Sample application that shows how you can backup a file and restore previous versions if the file was modified/corrupted in any way. 52 | 53 | Getting Help 54 | ============ 55 | 56 | If you find a bug, please see `CONTRIBUTING.md`_ for information on how to report it. 57 | 58 | If you need help that is not specific to this SDK, please reach out to `Dropbox Support`_. 59 | 60 | License 61 | ======= 62 | 63 | This SDK is distributed under the MIT license, please see `LICENSE`_ for more information. 64 | 65 | .. _logo: {logo_link} 66 | .. _repo: https://github.com/dropbox/dropbox-sdk-python 67 | .. _`Read The Docs`: http://dropbox-sdk-python.readthedocs.org 68 | .. _`Examples`: https://github.com/dropbox/dropbox-sdk-python/tree/main/example 69 | .. _LICENSE: https://github.com/dropbox/dropbox-sdk-python/blob/main/LICENSE 70 | .. _CONTRIBUTING.md: https://github.com/dropbox/dropbox-sdk-python/blob/main/CONTRIBUTING.md 71 | .. _Developer Console: https://dropbox.com/developers/apps 72 | .. _OAuth Guide: https://www.dropbox.com/lp/developers/reference/oauth-guide 73 | .. _`Dropbox Support`: https://www.dropbox.com/developers/contact -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | # Upgrading the Dropbox SDK 2 | 3 | This document is designed to show you how to upgrade to the latest version of the SDK accomodating any breaking changes introduced by major version updates. 4 | If you find any issues with either this guide on upgrading or the changes introduced in the new version, please see [CONTRIBUTING.md](CONTRIBUTING.md) 5 | 6 | # Upgrading to v12.0.0 7 | * The SDK no longer provides its own CA bundle to verify TLS connections. It will continue to verify connections through the `requests` library, which makes use of [`certifi`](https://github.com/certifi/python-certifi). You may still provide your own bundle through the `ca_certs` parameter of the `Dropbox` classes and of the `create_session` function (see the [documentation](https://dropbox-sdk-python.readthedocs.io/en/latest/api/dropbox.html) for details). 8 | * This will be the last major version to support Python 2. 9 | 10 | # Upgrading from v10.X.X to v11.0.0 11 | The major change that happened in this new version is that we regenerated the client files using Stone 3.2.0, 12 | so relative imports are removed from the generated client files. 13 | This created some issues with the imports in the non-generated files in `dropbox/`. 14 | As a result, we renamed `dropbox.dropbox` to 15 | `dropbox.dropbox_client`. If you used to do imports like `dropbox.dropbox.foo`, such imports need to be changed to `dropbox.dropbox_client.foo`. 16 | However, we preserved the imports in `dropbox/__init__.py`, so imports like `from dropbox import DropboxOAuth2FlowNoRedirect`, 17 | `from dropbox import Dropbox` will continue to work. 18 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: auto 6 | threshold: 1% 7 | base: auto 8 | flags: 9 | - unittest 10 | paths: 11 | - "dropbox" 12 | if_not_found: success 13 | if_ci_failed: error 14 | informational: true 15 | only_pulls: false -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | 28 | clean: 29 | rm -rf $(BUILDDIR)/* 30 | 31 | html: 32 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 33 | @echo 34 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 35 | -------------------------------------------------------------------------------- /docs/api/async.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.async_` -- Async 2 | ============================== 3 | *As of Python 3.5,* ``async`` *is a reserved keyword. For versions of Python prior to 3.5, the objects documented can be imported from either* ``dropbox.async`` *or* ``dropbox.async_``. *For Python 3.5+, only* ``dropbox.async_`` *is supported.* 4 | 5 | .. automodule:: dropbox.async_ 6 | :members: 7 | :show-inheritance: 8 | :special-members: __init__ 9 | :undoc-members: 10 | -------------------------------------------------------------------------------- /docs/api/auth.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.auth` -- Auth 2 | =========================== 3 | .. automodule:: dropbox.auth 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/common.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.common` -- Common 2 | =============================== 3 | .. automodule:: dropbox.common 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/contacts.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.contacts` -- Contacts 2 | =================================== 3 | .. automodule:: dropbox.contacts 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/dropbox.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.dropbox` -- Dropbox 2 | ================================= 3 | .. automodule:: dropbox.dropbox_client 4 | :members: 5 | :show-inheritance: 6 | :inherited-members: 7 | :special-members: __init__ 8 | :undoc-members: 9 | -------------------------------------------------------------------------------- /docs/api/exceptions.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.exceptions` -- Exceptions 2 | ======================================= 3 | .. automodule:: dropbox.exceptions 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/file_properties.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.file_properties` -- File Properties 2 | ================================================= 3 | .. automodule:: dropbox.file_properties 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/file_requests.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.file_requests` -- File Requests 2 | ============================================= 3 | .. automodule:: dropbox.file_requests 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/files.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.files` -- Files 2 | ============================= 3 | .. automodule:: dropbox.files 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/oauth.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.oauth` -- OAuth 2 | ============================= 3 | .. automodule:: dropbox.oauth 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/paper.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.paper` -- Paper 2 | ============================= 3 | .. automodule:: dropbox.paper 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/seen_state.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.seen_state` -- Seen State 2 | ======================================= 3 | .. automodule:: dropbox.seen_state 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/sharing.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.sharing` -- Sharing 2 | ================================= 3 | .. automodule:: dropbox.sharing 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/team.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.team` -- Team 2 | =========================== 3 | .. automodule:: dropbox.team 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/team_common.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.team_common` -- Team Common 2 | ========================================= 3 | .. automodule:: dropbox.team_common 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/team_log.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.team_log` -- Team Log 2 | =================================== 3 | .. automodule:: dropbox.team_log 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/team_policies.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.team_policies` -- Team Policies 2 | ============================================= 3 | .. automodule:: dropbox.team_policies 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/users.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.users` -- Users 2 | ============================= 3 | .. automodule:: dropbox.users 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/api/users_common.rst: -------------------------------------------------------------------------------- 1 | :mod:`dropbox.users_common` -- Users Common 2 | =========================================== 3 | .. automodule:: dropbox.users_common 4 | :members: 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Dropbox for Python documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Oct 24 13:42:45 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # Assumes that the dropbox package to generate docs for is one-level above in 19 | # the directory hierarchy. This takes precendence over other dropbox packages 20 | # that may be in the sys.path. 21 | sys.path.insert(0, os.path.abspath('..')) 22 | 23 | import dropbox 24 | 25 | # If extensions (or modules to document with autodoc) are in another directory, 26 | # add these directories to sys.path here. If the directory is relative to the 27 | # documentation root, use os.path.abspath to make it absolute, like shown here. 28 | #sys.path.insert(0, os.path.abspath('.')) 29 | 30 | # -- General configuration ------------------------------------------------ 31 | 32 | # If your documentation needs a minimal Sphinx version, state it here. 33 | #needs_sphinx = '1.0' 34 | 35 | # Add any Sphinx extension module names here, as strings. They can be 36 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 37 | # ones. 38 | extensions = [ 39 | 'sphinx.ext.autodoc', 40 | 'sphinx.ext.mathjax', 41 | 'sphinx_rtd_theme', 42 | ] 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ['_templates'] 46 | 47 | # The suffix of source filenames. 48 | source_suffix = '.rst' 49 | 50 | # The encoding of source files. 51 | #source_encoding = 'utf-8-sig' 52 | 53 | # The master toctree document. 54 | master_doc = 'index' 55 | 56 | # General information about the project. 57 | project = u'Dropbox for Python' 58 | copyright = u'2015-2024, Dropbox, Inc.' 59 | 60 | # The version info for the project you're documenting, acts as replacement for 61 | # |version| and |release|, also used in various other places throughout the 62 | # built documents. 63 | # 64 | # The short X.Y version. 65 | version = dropbox.__version__ 66 | # The full version, including alpha/beta/rc tags. 67 | release = dropbox.__version__ 68 | 69 | # The language for content autogenerated by Sphinx. Refer to documentation 70 | # for a list of supported languages. 71 | #language = None 72 | 73 | # There are two options for replacing |today|: either, you set today to some 74 | # non-false value, then it is used: 75 | #today = '' 76 | # Else, today_fmt is used as the format for a strftime call. 77 | #today_fmt = '%B %d, %Y' 78 | 79 | # List of patterns, relative to source directory, that match files and 80 | # directories to ignore when looking for source files. 81 | exclude_patterns = ['_build'] 82 | 83 | # The reST default role (used for this markup: `text`) to use for all 84 | # documents. 85 | #default_role = None 86 | 87 | # If true, '()' will be appended to :func: etc. cross-reference text. 88 | #add_function_parentheses = True 89 | 90 | # If true, the current module name will be prepended to all description 91 | # unit titles (such as .. function::). 92 | #add_module_names = True 93 | 94 | # If true, sectionauthor and moduleauthor directives will be shown in the 95 | # output. They are ignored by default. 96 | #show_authors = False 97 | 98 | # The name of the Pygments (syntax highlighting) style to use. 99 | pygments_style = 'sphinx' 100 | 101 | # A list of ignored prefixes for module index sorting. 102 | #modindex_common_prefix = [] 103 | 104 | # If true, keep warnings as "system message" paragraphs in the built documents. 105 | #keep_warnings = False 106 | 107 | 108 | # -- Options for HTML output ---------------------------------------------- 109 | 110 | # The theme to use for HTML and HTML Help pages. See the documentation for 111 | # a list of builtin themes. 112 | html_theme = 'sphinx_rtd_theme' 113 | 114 | # Theme options are theme-specific and customize the look and feel of a theme 115 | # further. For a list of options available for each theme, see the 116 | # documentation. 117 | #html_theme_options = {} 118 | 119 | # Add any paths that contain custom themes here, relative to this directory. 120 | #html_theme_path = [] 121 | 122 | # The name for this set of Sphinx documents. If None, it defaults to 123 | # " v documentation". 124 | #html_title = None 125 | 126 | # A shorter title for the navigation bar. Default is the same as html_title. 127 | #html_short_title = None 128 | 129 | # The name of an image file (relative to this directory) to place at the top 130 | # of the sidebar. 131 | #html_logo = None 132 | 133 | # The name of an image file (within the static path) to use as favicon of the 134 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 135 | # pixels large. 136 | #html_favicon = None 137 | 138 | # Add any paths that contain custom static files (such as style sheets) here, 139 | # relative to this directory. They are copied after the builtin static files, 140 | # so a file named "default.css" will overwrite the builtin "default.css". 141 | html_static_path = ['_static'] 142 | 143 | # Add any extra paths that contain custom files (such as robots.txt or 144 | # .htaccess) here, relative to this directory. These files are copied 145 | # directly to the root of the documentation. 146 | #html_extra_path = [] 147 | 148 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 149 | # using the given strftime format. 150 | #html_last_updated_fmt = '%b %d, %Y' 151 | 152 | # If true, SmartyPants will be used to convert quotes and dashes to 153 | # typographically correct entities. 154 | #html_use_smartypants = True 155 | 156 | # Custom sidebar templates, maps document names to template names. 157 | #html_sidebars = {} 158 | 159 | # Additional templates that should be rendered to pages, maps page names to 160 | # template names. 161 | #html_additional_pages = {} 162 | 163 | # If false, no module index is generated. 164 | #html_domain_indices = True 165 | 166 | # If false, no index is generated. 167 | #html_use_index = True 168 | 169 | # If true, the index is split into individual pages for each letter. 170 | #html_split_index = False 171 | 172 | # If true, links to the reST sources are added to the pages. 173 | #html_show_sourcelink = True 174 | 175 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 176 | #html_show_sphinx = True 177 | 178 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 179 | #html_show_copyright = True 180 | 181 | # If true, an OpenSearch description file will be output, and all pages will 182 | # contain a tag referring to it. The value of this option must be the 183 | # base URL from which the finished HTML is served. 184 | #html_use_opensearch = '' 185 | 186 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 187 | #html_file_suffix = None 188 | 189 | # Output file base name for HTML help builder. 190 | htmlhelp_basename = 'DropboxforPythondoc' 191 | 192 | 193 | # -- Options for LaTeX output --------------------------------------------- 194 | 195 | #latex_elements = { 196 | # The paper size ('letterpaper' or 'a4paper'). 197 | #'papersize': 'letterpaper', 198 | 199 | # The font size ('10pt', '11pt' or '12pt'). 200 | #'pointsize': '10pt', 201 | 202 | # Additional stuff for the LaTeX preamble. 203 | #'preamble': '', 204 | #} 205 | 206 | # Grouping the document tree into LaTeX files. List of tuples 207 | # (source start file, target name, title, 208 | # author, documentclass [howto, manual, or own class]). 209 | #latex_documents = [ 210 | # ('index2', 'DropboxforPython.tex', u'Dropbox for Python Documentation', 211 | # u'Ken Elkabany', 'manual'), 212 | #] 213 | 214 | # The name of an image file (relative to this directory) to place at the top of 215 | # the title page. 216 | #latex_logo = None 217 | 218 | # For "manual" documents, if this is true, then toplevel headings are parts, 219 | # not chapters. 220 | #latex_use_parts = False 221 | 222 | # If true, show page references after internal links. 223 | #latex_show_pagerefs = False 224 | 225 | # If true, show URL addresses after external links. 226 | #latex_show_urls = False 227 | 228 | # Documents to append as an appendix to all manuals. 229 | #latex_appendices = [] 230 | 231 | # If false, no module index is generated. 232 | #latex_domain_indices = True 233 | 234 | 235 | # -- Options for manual page output --------------------------------------- 236 | 237 | # One entry per manual page. List of tuples 238 | # (source start file, name, description, authors, manual section). 239 | #man_pages = [ 240 | # ('index2', 'dropboxforpython', u'Dropbox for Python Documentation', 241 | # [u'Dropbox, Inc.'], 1) 242 | #] 243 | 244 | # If true, show URL addresses after external links. 245 | #man_show_urls = False 246 | 247 | 248 | # -- Options for Texinfo output ------------------------------------------- 249 | 250 | # Grouping the document tree into Texinfo files. List of tuples 251 | # (source start file, target name, title, author, 252 | # dir menu entry, description, category) 253 | #texinfo_documents = [ 254 | # ('index2', 'DropboxforPython', u'Dropbox for Python Documentation', 255 | # u'Dropbox, Inc.', 'DropboxforPython', 'One line description of project.', 256 | # 'Miscellaneous'), 257 | #] 258 | 259 | # Documents to append as an appendix to all manuals. 260 | #texinfo_appendices = [] 261 | 262 | # If false, no module index is generated. 263 | #texinfo_domain_indices = True 264 | 265 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 266 | #texinfo_show_urls = 'footnote' 267 | 268 | # If true, do not generate a @detailmenu in the "Top" node's menu. 269 | #texinfo_no_detailmenu = False 270 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Dropbox for Python Documentation 2 | ================================ 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | tutorial 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | 12 | api/async 13 | api/auth 14 | api/common 15 | api/contacts 16 | api/dropbox 17 | api/exceptions 18 | api/file_properties 19 | api/file_requests 20 | api/files 21 | api/oauth 22 | api/paper 23 | api/seen_state 24 | api/sharing 25 | api/team 26 | api/team_common 27 | api/team_log 28 | api/team_policies 29 | api/users 30 | api/users_common 31 | 32 | Indices and tables 33 | ================== 34 | 35 | * :ref:`genindex` 36 | * :ref:`modindex` 37 | * :ref:`search` 38 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | goto end 23 | ) 24 | 25 | if "%1" == "clean" ( 26 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 27 | del /q /s %BUILDDIR%\* 28 | goto end 29 | ) 30 | 31 | 32 | %SPHINXBUILD% 2> nul 33 | if errorlevel 9009 ( 34 | echo. 35 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 36 | echo.installed, then set the SPHINXBUILD environment variable to point 37 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 38 | echo.may add the Sphinx directory to PATH. 39 | echo. 40 | echo.If you don't have Sphinx installed, grab it from 41 | echo.http://sphinx-doc.org/ 42 | exit /b 1 43 | ) 44 | 45 | if "%1" == "html" ( 46 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 47 | if errorlevel 1 exit /b 1 48 | echo. 49 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 50 | goto end 51 | ) 52 | 53 | :end 54 | -------------------------------------------------------------------------------- /docs/tutorial.rst: -------------------------------------------------------------------------------- 1 | ******** 2 | Tutorial 3 | ******** 4 | 5 | For a tutorial on how to get started, please visit our 6 | `developers page 7 | `_. 8 | -------------------------------------------------------------------------------- /dropbox/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from dropbox.dropbox_client import ( # noqa: F401 # pylint: disable=unused-import 4 | __version__, Dropbox, DropboxTeam, create_session 5 | ) 6 | from dropbox.oauth import ( # noqa: F401 # pylint: disable=unused-import 7 | DropboxOAuth2Flow, DropboxOAuth2FlowNoRedirect 8 | ) 9 | -------------------------------------------------------------------------------- /dropbox/account.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Auto-generated by Stone, do not modify. 3 | # @generated 4 | # flake8: noqa 5 | # pylint: skip-file 6 | from __future__ import unicode_literals 7 | from stone.backends.python_rsrc import stone_base as bb 8 | from stone.backends.python_rsrc import stone_validators as bv 9 | 10 | class PhotoSourceArg(bb.Union): 11 | """ 12 | This class acts as a tagged union. Only one of the ``is_*`` methods will 13 | return true. To get the associated value of a tag (if one exists), use the 14 | corresponding ``get_*`` method. 15 | 16 | :ivar str account.PhotoSourceArg.base64_data: Image data in base64-encoded 17 | bytes. 18 | """ 19 | 20 | _catch_all = 'other' 21 | # Attribute is overwritten below the class definition 22 | other = None 23 | 24 | @classmethod 25 | def base64_data(cls, val): 26 | """ 27 | Create an instance of this class set to the ``base64_data`` tag with 28 | value ``val``. 29 | 30 | :param str val: 31 | :rtype: PhotoSourceArg 32 | """ 33 | return cls('base64_data', val) 34 | 35 | def is_base64_data(self): 36 | """ 37 | Check if the union tag is ``base64_data``. 38 | 39 | :rtype: bool 40 | """ 41 | return self._tag == 'base64_data' 42 | 43 | def is_other(self): 44 | """ 45 | Check if the union tag is ``other``. 46 | 47 | :rtype: bool 48 | """ 49 | return self._tag == 'other' 50 | 51 | def get_base64_data(self): 52 | """ 53 | Image data in base64-encoded bytes. 54 | 55 | Only call this if :meth:`is_base64_data` is true. 56 | 57 | :rtype: str 58 | """ 59 | if not self.is_base64_data(): 60 | raise AttributeError("tag 'base64_data' not set") 61 | return self._value 62 | 63 | def _process_custom_annotations(self, annotation_type, field_path, processor): 64 | super(PhotoSourceArg, self)._process_custom_annotations(annotation_type, field_path, processor) 65 | 66 | PhotoSourceArg_validator = bv.Union(PhotoSourceArg) 67 | 68 | class SetProfilePhotoArg(bb.Struct): 69 | """ 70 | :ivar account.SetProfilePhotoArg.photo: Image to set as the user's new 71 | profile photo. 72 | """ 73 | 74 | __slots__ = [ 75 | '_photo_value', 76 | ] 77 | 78 | _has_required_fields = True 79 | 80 | def __init__(self, 81 | photo=None): 82 | self._photo_value = bb.NOT_SET 83 | if photo is not None: 84 | self.photo = photo 85 | 86 | # Instance attribute type: PhotoSourceArg (validator is set below) 87 | photo = bb.Attribute("photo", user_defined=True) 88 | 89 | def _process_custom_annotations(self, annotation_type, field_path, processor): 90 | super(SetProfilePhotoArg, self)._process_custom_annotations(annotation_type, field_path, processor) 91 | 92 | SetProfilePhotoArg_validator = bv.Struct(SetProfilePhotoArg) 93 | 94 | class SetProfilePhotoError(bb.Union): 95 | """ 96 | This class acts as a tagged union. Only one of the ``is_*`` methods will 97 | return true. To get the associated value of a tag (if one exists), use the 98 | corresponding ``get_*`` method. 99 | 100 | :ivar account.SetProfilePhotoError.file_type_error: File cannot be set as 101 | profile photo. 102 | :ivar account.SetProfilePhotoError.file_size_error: File cannot exceed 10 103 | MB. 104 | :ivar account.SetProfilePhotoError.dimension_error: Image must be larger 105 | than 128 x 128. 106 | :ivar account.SetProfilePhotoError.thumbnail_error: Image could not be 107 | thumbnailed. 108 | :ivar account.SetProfilePhotoError.transient_error: Temporary infrastructure 109 | failure, please retry. 110 | """ 111 | 112 | _catch_all = 'other' 113 | # Attribute is overwritten below the class definition 114 | file_type_error = None 115 | # Attribute is overwritten below the class definition 116 | file_size_error = None 117 | # Attribute is overwritten below the class definition 118 | dimension_error = None 119 | # Attribute is overwritten below the class definition 120 | thumbnail_error = None 121 | # Attribute is overwritten below the class definition 122 | transient_error = None 123 | # Attribute is overwritten below the class definition 124 | other = None 125 | 126 | def is_file_type_error(self): 127 | """ 128 | Check if the union tag is ``file_type_error``. 129 | 130 | :rtype: bool 131 | """ 132 | return self._tag == 'file_type_error' 133 | 134 | def is_file_size_error(self): 135 | """ 136 | Check if the union tag is ``file_size_error``. 137 | 138 | :rtype: bool 139 | """ 140 | return self._tag == 'file_size_error' 141 | 142 | def is_dimension_error(self): 143 | """ 144 | Check if the union tag is ``dimension_error``. 145 | 146 | :rtype: bool 147 | """ 148 | return self._tag == 'dimension_error' 149 | 150 | def is_thumbnail_error(self): 151 | """ 152 | Check if the union tag is ``thumbnail_error``. 153 | 154 | :rtype: bool 155 | """ 156 | return self._tag == 'thumbnail_error' 157 | 158 | def is_transient_error(self): 159 | """ 160 | Check if the union tag is ``transient_error``. 161 | 162 | :rtype: bool 163 | """ 164 | return self._tag == 'transient_error' 165 | 166 | def is_other(self): 167 | """ 168 | Check if the union tag is ``other``. 169 | 170 | :rtype: bool 171 | """ 172 | return self._tag == 'other' 173 | 174 | def _process_custom_annotations(self, annotation_type, field_path, processor): 175 | super(SetProfilePhotoError, self)._process_custom_annotations(annotation_type, field_path, processor) 176 | 177 | SetProfilePhotoError_validator = bv.Union(SetProfilePhotoError) 178 | 179 | class SetProfilePhotoResult(bb.Struct): 180 | """ 181 | :ivar account.SetProfilePhotoResult.profile_photo_url: URL for the photo 182 | representing the user, if one is set. 183 | """ 184 | 185 | __slots__ = [ 186 | '_profile_photo_url_value', 187 | ] 188 | 189 | _has_required_fields = True 190 | 191 | def __init__(self, 192 | profile_photo_url=None): 193 | self._profile_photo_url_value = bb.NOT_SET 194 | if profile_photo_url is not None: 195 | self.profile_photo_url = profile_photo_url 196 | 197 | # Instance attribute type: str (validator is set below) 198 | profile_photo_url = bb.Attribute("profile_photo_url") 199 | 200 | def _process_custom_annotations(self, annotation_type, field_path, processor): 201 | super(SetProfilePhotoResult, self)._process_custom_annotations(annotation_type, field_path, processor) 202 | 203 | SetProfilePhotoResult_validator = bv.Struct(SetProfilePhotoResult) 204 | 205 | PhotoSourceArg._base64_data_validator = bv.String() 206 | PhotoSourceArg._other_validator = bv.Void() 207 | PhotoSourceArg._tagmap = { 208 | 'base64_data': PhotoSourceArg._base64_data_validator, 209 | 'other': PhotoSourceArg._other_validator, 210 | } 211 | 212 | PhotoSourceArg.other = PhotoSourceArg('other') 213 | 214 | SetProfilePhotoArg.photo.validator = PhotoSourceArg_validator 215 | SetProfilePhotoArg._all_field_names_ = set(['photo']) 216 | SetProfilePhotoArg._all_fields_ = [('photo', SetProfilePhotoArg.photo.validator)] 217 | 218 | SetProfilePhotoError._file_type_error_validator = bv.Void() 219 | SetProfilePhotoError._file_size_error_validator = bv.Void() 220 | SetProfilePhotoError._dimension_error_validator = bv.Void() 221 | SetProfilePhotoError._thumbnail_error_validator = bv.Void() 222 | SetProfilePhotoError._transient_error_validator = bv.Void() 223 | SetProfilePhotoError._other_validator = bv.Void() 224 | SetProfilePhotoError._tagmap = { 225 | 'file_type_error': SetProfilePhotoError._file_type_error_validator, 226 | 'file_size_error': SetProfilePhotoError._file_size_error_validator, 227 | 'dimension_error': SetProfilePhotoError._dimension_error_validator, 228 | 'thumbnail_error': SetProfilePhotoError._thumbnail_error_validator, 229 | 'transient_error': SetProfilePhotoError._transient_error_validator, 230 | 'other': SetProfilePhotoError._other_validator, 231 | } 232 | 233 | SetProfilePhotoError.file_type_error = SetProfilePhotoError('file_type_error') 234 | SetProfilePhotoError.file_size_error = SetProfilePhotoError('file_size_error') 235 | SetProfilePhotoError.dimension_error = SetProfilePhotoError('dimension_error') 236 | SetProfilePhotoError.thumbnail_error = SetProfilePhotoError('thumbnail_error') 237 | SetProfilePhotoError.transient_error = SetProfilePhotoError('transient_error') 238 | SetProfilePhotoError.other = SetProfilePhotoError('other') 239 | 240 | SetProfilePhotoResult.profile_photo_url.validator = bv.String() 241 | SetProfilePhotoResult._all_field_names_ = set(['profile_photo_url']) 242 | SetProfilePhotoResult._all_fields_ = [('profile_photo_url', SetProfilePhotoResult.profile_photo_url.validator)] 243 | 244 | set_profile_photo = bb.Route( 245 | 'set_profile_photo', 246 | 1, 247 | False, 248 | SetProfilePhotoArg_validator, 249 | SetProfilePhotoResult_validator, 250 | SetProfilePhotoError_validator, 251 | {'auth': 'user', 252 | 'host': 'api', 253 | 'style': 'rpc'}, 254 | ) 255 | 256 | ROUTES = { 257 | 'set_profile_photo': set_profile_photo, 258 | } 259 | 260 | -------------------------------------------------------------------------------- /dropbox/async.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Auto-generated by Stone, do not modify. 3 | # @generated 4 | # flake8: noqa 5 | # pylint: skip-file 6 | # If you have issues importing this module because Python recognizes it as a keyword, use async_ instead. 7 | from .async_ import * 8 | -------------------------------------------------------------------------------- /dropbox/async_.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Auto-generated by Stone, do not modify. 3 | # @generated 4 | # flake8: noqa 5 | # pylint: skip-file 6 | from __future__ import unicode_literals 7 | from stone.backends.python_rsrc import stone_base as bb 8 | from stone.backends.python_rsrc import stone_validators as bv 9 | 10 | class LaunchResultBase(bb.Union): 11 | """ 12 | Result returned by methods that launch an asynchronous job. A method who may 13 | either launch an asynchronous job, or complete the request synchronously, 14 | can use this union by extending it, and adding a 'complete' field with the 15 | type of the synchronous response. See :class:`LaunchEmptyResult` for an 16 | example. 17 | 18 | This class acts as a tagged union. Only one of the ``is_*`` methods will 19 | return true. To get the associated value of a tag (if one exists), use the 20 | corresponding ``get_*`` method. 21 | 22 | :ivar str async.LaunchResultBase.async_job_id: This response indicates that 23 | the processing is asynchronous. The string is an id that can be used to 24 | obtain the status of the asynchronous job. 25 | """ 26 | 27 | _catch_all = None 28 | 29 | @classmethod 30 | def async_job_id(cls, val): 31 | """ 32 | Create an instance of this class set to the ``async_job_id`` tag with 33 | value ``val``. 34 | 35 | :param str val: 36 | :rtype: LaunchResultBase 37 | """ 38 | return cls('async_job_id', val) 39 | 40 | def is_async_job_id(self): 41 | """ 42 | Check if the union tag is ``async_job_id``. 43 | 44 | :rtype: bool 45 | """ 46 | return self._tag == 'async_job_id' 47 | 48 | def get_async_job_id(self): 49 | """ 50 | This response indicates that the processing is asynchronous. The string 51 | is an id that can be used to obtain the status of the asynchronous job. 52 | 53 | Only call this if :meth:`is_async_job_id` is true. 54 | 55 | :rtype: str 56 | """ 57 | if not self.is_async_job_id(): 58 | raise AttributeError("tag 'async_job_id' not set") 59 | return self._value 60 | 61 | def _process_custom_annotations(self, annotation_type, field_path, processor): 62 | super(LaunchResultBase, self)._process_custom_annotations(annotation_type, field_path, processor) 63 | 64 | LaunchResultBase_validator = bv.Union(LaunchResultBase) 65 | 66 | class LaunchEmptyResult(LaunchResultBase): 67 | """ 68 | Result returned by methods that may either launch an asynchronous job or 69 | complete synchronously. Upon synchronous completion of the job, no 70 | additional information is returned. 71 | 72 | This class acts as a tagged union. Only one of the ``is_*`` methods will 73 | return true. To get the associated value of a tag (if one exists), use the 74 | corresponding ``get_*`` method. 75 | 76 | :ivar async.LaunchEmptyResult.complete: The job finished synchronously and 77 | successfully. 78 | """ 79 | 80 | # Attribute is overwritten below the class definition 81 | complete = None 82 | 83 | def is_complete(self): 84 | """ 85 | Check if the union tag is ``complete``. 86 | 87 | :rtype: bool 88 | """ 89 | return self._tag == 'complete' 90 | 91 | def _process_custom_annotations(self, annotation_type, field_path, processor): 92 | super(LaunchEmptyResult, self)._process_custom_annotations(annotation_type, field_path, processor) 93 | 94 | LaunchEmptyResult_validator = bv.Union(LaunchEmptyResult) 95 | 96 | class PollArg(bb.Struct): 97 | """ 98 | Arguments for methods that poll the status of an asynchronous job. 99 | 100 | :ivar async.PollArg.async_job_id: Id of the asynchronous job. This is the 101 | value of a response returned from the method that launched the job. 102 | """ 103 | 104 | __slots__ = [ 105 | '_async_job_id_value', 106 | ] 107 | 108 | _has_required_fields = True 109 | 110 | def __init__(self, 111 | async_job_id=None): 112 | self._async_job_id_value = bb.NOT_SET 113 | if async_job_id is not None: 114 | self.async_job_id = async_job_id 115 | 116 | # Instance attribute type: str (validator is set below) 117 | async_job_id = bb.Attribute("async_job_id") 118 | 119 | def _process_custom_annotations(self, annotation_type, field_path, processor): 120 | super(PollArg, self)._process_custom_annotations(annotation_type, field_path, processor) 121 | 122 | PollArg_validator = bv.Struct(PollArg) 123 | 124 | class PollResultBase(bb.Union): 125 | """ 126 | Result returned by methods that poll for the status of an asynchronous job. 127 | Unions that extend this union should add a 'complete' field with a type of 128 | the information returned upon job completion. See :class:`PollEmptyResult` 129 | for an example. 130 | 131 | This class acts as a tagged union. Only one of the ``is_*`` methods will 132 | return true. To get the associated value of a tag (if one exists), use the 133 | corresponding ``get_*`` method. 134 | 135 | :ivar async.PollResultBase.in_progress: The asynchronous job is still in 136 | progress. 137 | """ 138 | 139 | _catch_all = None 140 | # Attribute is overwritten below the class definition 141 | in_progress = None 142 | 143 | def is_in_progress(self): 144 | """ 145 | Check if the union tag is ``in_progress``. 146 | 147 | :rtype: bool 148 | """ 149 | return self._tag == 'in_progress' 150 | 151 | def _process_custom_annotations(self, annotation_type, field_path, processor): 152 | super(PollResultBase, self)._process_custom_annotations(annotation_type, field_path, processor) 153 | 154 | PollResultBase_validator = bv.Union(PollResultBase) 155 | 156 | class PollEmptyResult(PollResultBase): 157 | """ 158 | Result returned by methods that poll for the status of an asynchronous job. 159 | Upon completion of the job, no additional information is returned. 160 | 161 | This class acts as a tagged union. Only one of the ``is_*`` methods will 162 | return true. To get the associated value of a tag (if one exists), use the 163 | corresponding ``get_*`` method. 164 | 165 | :ivar async.PollEmptyResult.complete: The asynchronous job has completed 166 | successfully. 167 | """ 168 | 169 | # Attribute is overwritten below the class definition 170 | complete = None 171 | 172 | def is_complete(self): 173 | """ 174 | Check if the union tag is ``complete``. 175 | 176 | :rtype: bool 177 | """ 178 | return self._tag == 'complete' 179 | 180 | def _process_custom_annotations(self, annotation_type, field_path, processor): 181 | super(PollEmptyResult, self)._process_custom_annotations(annotation_type, field_path, processor) 182 | 183 | PollEmptyResult_validator = bv.Union(PollEmptyResult) 184 | 185 | class PollError(bb.Union): 186 | """ 187 | Error returned by methods for polling the status of asynchronous job. 188 | 189 | This class acts as a tagged union. Only one of the ``is_*`` methods will 190 | return true. To get the associated value of a tag (if one exists), use the 191 | corresponding ``get_*`` method. 192 | 193 | :ivar async.PollError.invalid_async_job_id: The job ID is invalid. 194 | :ivar async.PollError.internal_error: Something went wrong with the job on 195 | Dropbox's end. You'll need to verify that the action you were taking 196 | succeeded, and if not, try again. This should happen very rarely. 197 | """ 198 | 199 | _catch_all = 'other' 200 | # Attribute is overwritten below the class definition 201 | invalid_async_job_id = None 202 | # Attribute is overwritten below the class definition 203 | internal_error = None 204 | # Attribute is overwritten below the class definition 205 | other = None 206 | 207 | def is_invalid_async_job_id(self): 208 | """ 209 | Check if the union tag is ``invalid_async_job_id``. 210 | 211 | :rtype: bool 212 | """ 213 | return self._tag == 'invalid_async_job_id' 214 | 215 | def is_internal_error(self): 216 | """ 217 | Check if the union tag is ``internal_error``. 218 | 219 | :rtype: bool 220 | """ 221 | return self._tag == 'internal_error' 222 | 223 | def is_other(self): 224 | """ 225 | Check if the union tag is ``other``. 226 | 227 | :rtype: bool 228 | """ 229 | return self._tag == 'other' 230 | 231 | def _process_custom_annotations(self, annotation_type, field_path, processor): 232 | super(PollError, self)._process_custom_annotations(annotation_type, field_path, processor) 233 | 234 | PollError_validator = bv.Union(PollError) 235 | 236 | AsyncJobId_validator = bv.String(min_length=1) 237 | LaunchResultBase._async_job_id_validator = AsyncJobId_validator 238 | LaunchResultBase._tagmap = { 239 | 'async_job_id': LaunchResultBase._async_job_id_validator, 240 | } 241 | 242 | LaunchEmptyResult._complete_validator = bv.Void() 243 | LaunchEmptyResult._tagmap = { 244 | 'complete': LaunchEmptyResult._complete_validator, 245 | } 246 | LaunchEmptyResult._tagmap.update(LaunchResultBase._tagmap) 247 | 248 | LaunchEmptyResult.complete = LaunchEmptyResult('complete') 249 | 250 | PollArg.async_job_id.validator = AsyncJobId_validator 251 | PollArg._all_field_names_ = set(['async_job_id']) 252 | PollArg._all_fields_ = [('async_job_id', PollArg.async_job_id.validator)] 253 | 254 | PollResultBase._in_progress_validator = bv.Void() 255 | PollResultBase._tagmap = { 256 | 'in_progress': PollResultBase._in_progress_validator, 257 | } 258 | 259 | PollResultBase.in_progress = PollResultBase('in_progress') 260 | 261 | PollEmptyResult._complete_validator = bv.Void() 262 | PollEmptyResult._tagmap = { 263 | 'complete': PollEmptyResult._complete_validator, 264 | } 265 | PollEmptyResult._tagmap.update(PollResultBase._tagmap) 266 | 267 | PollEmptyResult.complete = PollEmptyResult('complete') 268 | 269 | PollError._invalid_async_job_id_validator = bv.Void() 270 | PollError._internal_error_validator = bv.Void() 271 | PollError._other_validator = bv.Void() 272 | PollError._tagmap = { 273 | 'invalid_async_job_id': PollError._invalid_async_job_id_validator, 274 | 'internal_error': PollError._internal_error_validator, 275 | 'other': PollError._other_validator, 276 | } 277 | 278 | PollError.invalid_async_job_id = PollError('invalid_async_job_id') 279 | PollError.internal_error = PollError('internal_error') 280 | PollError.other = PollError('other') 281 | 282 | ROUTES = { 283 | } 284 | 285 | -------------------------------------------------------------------------------- /dropbox/check.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Auto-generated by Stone, do not modify. 3 | # @generated 4 | # flake8: noqa 5 | # pylint: skip-file 6 | from __future__ import unicode_literals 7 | from stone.backends.python_rsrc import stone_base as bb 8 | from stone.backends.python_rsrc import stone_validators as bv 9 | 10 | class EchoArg(bb.Struct): 11 | """ 12 | Contains the arguments to be sent to the Dropbox servers. 13 | 14 | :ivar check.EchoArg.query: The string that you'd like to be echoed back to 15 | you. 16 | """ 17 | 18 | __slots__ = [ 19 | '_query_value', 20 | ] 21 | 22 | _has_required_fields = False 23 | 24 | def __init__(self, 25 | query=None): 26 | self._query_value = bb.NOT_SET 27 | if query is not None: 28 | self.query = query 29 | 30 | # Instance attribute type: str (validator is set below) 31 | query = bb.Attribute("query") 32 | 33 | def _process_custom_annotations(self, annotation_type, field_path, processor): 34 | super(EchoArg, self)._process_custom_annotations(annotation_type, field_path, processor) 35 | 36 | EchoArg_validator = bv.Struct(EchoArg) 37 | 38 | class EchoResult(bb.Struct): 39 | """ 40 | EchoResult contains the result returned from the Dropbox servers. 41 | 42 | :ivar check.EchoResult.result: If everything worked correctly, this would be 43 | the same as query. 44 | """ 45 | 46 | __slots__ = [ 47 | '_result_value', 48 | ] 49 | 50 | _has_required_fields = False 51 | 52 | def __init__(self, 53 | result=None): 54 | self._result_value = bb.NOT_SET 55 | if result is not None: 56 | self.result = result 57 | 58 | # Instance attribute type: str (validator is set below) 59 | result = bb.Attribute("result") 60 | 61 | def _process_custom_annotations(self, annotation_type, field_path, processor): 62 | super(EchoResult, self)._process_custom_annotations(annotation_type, field_path, processor) 63 | 64 | EchoResult_validator = bv.Struct(EchoResult) 65 | 66 | EchoArg.query.validator = bv.String(max_length=500) 67 | EchoArg._all_field_names_ = set(['query']) 68 | EchoArg._all_fields_ = [('query', EchoArg.query.validator)] 69 | 70 | EchoResult.result.validator = bv.String() 71 | EchoResult._all_field_names_ = set(['result']) 72 | EchoResult._all_fields_ = [('result', EchoResult.result.validator)] 73 | 74 | EchoArg.query.default = '' 75 | EchoResult.result.default = '' 76 | app = bb.Route( 77 | 'app', 78 | 1, 79 | False, 80 | EchoArg_validator, 81 | EchoResult_validator, 82 | bv.Void(), 83 | {'auth': 'app', 84 | 'host': 'api', 85 | 'style': 'rpc'}, 86 | ) 87 | user = bb.Route( 88 | 'user', 89 | 1, 90 | False, 91 | EchoArg_validator, 92 | EchoResult_validator, 93 | bv.Void(), 94 | {'auth': 'user', 95 | 'host': 'api', 96 | 'style': 'rpc'}, 97 | ) 98 | 99 | ROUTES = { 100 | 'app': app, 101 | 'user': user, 102 | } 103 | 104 | -------------------------------------------------------------------------------- /dropbox/common.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Auto-generated by Stone, do not modify. 3 | # @generated 4 | # flake8: noqa 5 | # pylint: skip-file 6 | from __future__ import unicode_literals 7 | from stone.backends.python_rsrc import stone_base as bb 8 | from stone.backends.python_rsrc import stone_validators as bv 9 | 10 | class PathRoot(bb.Union): 11 | """ 12 | This class acts as a tagged union. Only one of the ``is_*`` methods will 13 | return true. To get the associated value of a tag (if one exists), use the 14 | corresponding ``get_*`` method. 15 | 16 | :ivar common.PathRoot.home: Paths are relative to the authenticating user's 17 | home namespace, whether or not that user belongs to a team. 18 | :ivar str common.PathRoot.root: Paths are relative to the authenticating 19 | user's root namespace (This results in 20 | :field:`PathRootError.invalid_root` if the user's root namespace has 21 | changed.). 22 | :ivar str common.PathRoot.namespace_id: Paths are relative to given 23 | namespace id (This results in :field:`PathRootError.no_permission` if 24 | you don't have access to this namespace.). 25 | """ 26 | 27 | _catch_all = 'other' 28 | # Attribute is overwritten below the class definition 29 | home = None 30 | # Attribute is overwritten below the class definition 31 | other = None 32 | 33 | @classmethod 34 | def root(cls, val): 35 | """ 36 | Create an instance of this class set to the ``root`` tag with value 37 | ``val``. 38 | 39 | :param str val: 40 | :rtype: PathRoot 41 | """ 42 | return cls('root', val) 43 | 44 | @classmethod 45 | def namespace_id(cls, val): 46 | """ 47 | Create an instance of this class set to the ``namespace_id`` tag with 48 | value ``val``. 49 | 50 | :param str val: 51 | :rtype: PathRoot 52 | """ 53 | return cls('namespace_id', val) 54 | 55 | def is_home(self): 56 | """ 57 | Check if the union tag is ``home``. 58 | 59 | :rtype: bool 60 | """ 61 | return self._tag == 'home' 62 | 63 | def is_root(self): 64 | """ 65 | Check if the union tag is ``root``. 66 | 67 | :rtype: bool 68 | """ 69 | return self._tag == 'root' 70 | 71 | def is_namespace_id(self): 72 | """ 73 | Check if the union tag is ``namespace_id``. 74 | 75 | :rtype: bool 76 | """ 77 | return self._tag == 'namespace_id' 78 | 79 | def is_other(self): 80 | """ 81 | Check if the union tag is ``other``. 82 | 83 | :rtype: bool 84 | """ 85 | return self._tag == 'other' 86 | 87 | def get_root(self): 88 | """ 89 | Paths are relative to the authenticating user's root namespace (This 90 | results in ``PathRootError.invalid_root`` if the user's root namespace 91 | has changed.). 92 | 93 | Only call this if :meth:`is_root` is true. 94 | 95 | :rtype: str 96 | """ 97 | if not self.is_root(): 98 | raise AttributeError("tag 'root' not set") 99 | return self._value 100 | 101 | def get_namespace_id(self): 102 | """ 103 | Paths are relative to given namespace id (This results in 104 | ``PathRootError.no_permission`` if you don't have access to this 105 | namespace.). 106 | 107 | Only call this if :meth:`is_namespace_id` is true. 108 | 109 | :rtype: str 110 | """ 111 | if not self.is_namespace_id(): 112 | raise AttributeError("tag 'namespace_id' not set") 113 | return self._value 114 | 115 | def _process_custom_annotations(self, annotation_type, field_path, processor): 116 | super(PathRoot, self)._process_custom_annotations(annotation_type, field_path, processor) 117 | 118 | PathRoot_validator = bv.Union(PathRoot) 119 | 120 | class PathRootError(bb.Union): 121 | """ 122 | This class acts as a tagged union. Only one of the ``is_*`` methods will 123 | return true. To get the associated value of a tag (if one exists), use the 124 | corresponding ``get_*`` method. 125 | 126 | :ivar RootInfo PathRootError.invalid_root: The root namespace id in 127 | Dropbox-API-Path-Root header is not valid. The value of this error is 128 | the user's latest root info. 129 | :ivar common.PathRootError.no_permission: You don't have permission to 130 | access the namespace id in Dropbox-API-Path-Root header. 131 | """ 132 | 133 | _catch_all = 'other' 134 | # Attribute is overwritten below the class definition 135 | no_permission = None 136 | # Attribute is overwritten below the class definition 137 | other = None 138 | 139 | @classmethod 140 | def invalid_root(cls, val): 141 | """ 142 | Create an instance of this class set to the ``invalid_root`` tag with 143 | value ``val``. 144 | 145 | :param RootInfo val: 146 | :rtype: PathRootError 147 | """ 148 | return cls('invalid_root', val) 149 | 150 | def is_invalid_root(self): 151 | """ 152 | Check if the union tag is ``invalid_root``. 153 | 154 | :rtype: bool 155 | """ 156 | return self._tag == 'invalid_root' 157 | 158 | def is_no_permission(self): 159 | """ 160 | Check if the union tag is ``no_permission``. 161 | 162 | :rtype: bool 163 | """ 164 | return self._tag == 'no_permission' 165 | 166 | def is_other(self): 167 | """ 168 | Check if the union tag is ``other``. 169 | 170 | :rtype: bool 171 | """ 172 | return self._tag == 'other' 173 | 174 | def get_invalid_root(self): 175 | """ 176 | The root namespace id in Dropbox-API-Path-Root header is not valid. The 177 | value of this error is the user's latest root info. 178 | 179 | Only call this if :meth:`is_invalid_root` is true. 180 | 181 | :rtype: RootInfo 182 | """ 183 | if not self.is_invalid_root(): 184 | raise AttributeError("tag 'invalid_root' not set") 185 | return self._value 186 | 187 | def _process_custom_annotations(self, annotation_type, field_path, processor): 188 | super(PathRootError, self)._process_custom_annotations(annotation_type, field_path, processor) 189 | 190 | PathRootError_validator = bv.Union(PathRootError) 191 | 192 | class RootInfo(bb.Struct): 193 | """ 194 | Information about current user's root. 195 | 196 | :ivar common.RootInfo.root_namespace_id: The namespace ID for user's root 197 | namespace. It will be the namespace ID of the shared team root if the 198 | user is member of a team with a separate team root. Otherwise it will be 199 | same as ``RootInfo.home_namespace_id``. 200 | :ivar common.RootInfo.home_namespace_id: The namespace ID for user's home 201 | namespace. 202 | """ 203 | 204 | __slots__ = [ 205 | '_root_namespace_id_value', 206 | '_home_namespace_id_value', 207 | ] 208 | 209 | _has_required_fields = True 210 | 211 | def __init__(self, 212 | root_namespace_id=None, 213 | home_namespace_id=None): 214 | self._root_namespace_id_value = bb.NOT_SET 215 | self._home_namespace_id_value = bb.NOT_SET 216 | if root_namespace_id is not None: 217 | self.root_namespace_id = root_namespace_id 218 | if home_namespace_id is not None: 219 | self.home_namespace_id = home_namespace_id 220 | 221 | # Instance attribute type: str (validator is set below) 222 | root_namespace_id = bb.Attribute("root_namespace_id") 223 | 224 | # Instance attribute type: str (validator is set below) 225 | home_namespace_id = bb.Attribute("home_namespace_id") 226 | 227 | def _process_custom_annotations(self, annotation_type, field_path, processor): 228 | super(RootInfo, self)._process_custom_annotations(annotation_type, field_path, processor) 229 | 230 | RootInfo_validator = bv.StructTree(RootInfo) 231 | 232 | class TeamRootInfo(RootInfo): 233 | """ 234 | Root info when user is member of a team with a separate root namespace ID. 235 | 236 | :ivar common.TeamRootInfo.home_path: The path for user's home directory 237 | under the shared team root. 238 | """ 239 | 240 | __slots__ = [ 241 | '_home_path_value', 242 | ] 243 | 244 | _has_required_fields = True 245 | 246 | def __init__(self, 247 | root_namespace_id=None, 248 | home_namespace_id=None, 249 | home_path=None): 250 | super(TeamRootInfo, self).__init__(root_namespace_id, 251 | home_namespace_id) 252 | self._home_path_value = bb.NOT_SET 253 | if home_path is not None: 254 | self.home_path = home_path 255 | 256 | # Instance attribute type: str (validator is set below) 257 | home_path = bb.Attribute("home_path") 258 | 259 | def _process_custom_annotations(self, annotation_type, field_path, processor): 260 | super(TeamRootInfo, self)._process_custom_annotations(annotation_type, field_path, processor) 261 | 262 | TeamRootInfo_validator = bv.Struct(TeamRootInfo) 263 | 264 | class UserRootInfo(RootInfo): 265 | """ 266 | Root info when user is not member of a team or the user is a member of a 267 | team and the team does not have a separate root namespace. 268 | """ 269 | 270 | __slots__ = [ 271 | ] 272 | 273 | _has_required_fields = True 274 | 275 | def __init__(self, 276 | root_namespace_id=None, 277 | home_namespace_id=None): 278 | super(UserRootInfo, self).__init__(root_namespace_id, 279 | home_namespace_id) 280 | 281 | def _process_custom_annotations(self, annotation_type, field_path, processor): 282 | super(UserRootInfo, self)._process_custom_annotations(annotation_type, field_path, processor) 283 | 284 | UserRootInfo_validator = bv.Struct(UserRootInfo) 285 | 286 | Date_validator = bv.Timestamp('%Y-%m-%d') 287 | DisplayName_validator = bv.String(pattern='[^/:?*<>"|]*') 288 | DisplayNameLegacy_validator = bv.String() 289 | DropboxTimestamp_validator = bv.Timestamp('%Y-%m-%dT%H:%M:%SZ') 290 | EmailAddress_validator = bv.String(max_length=255, pattern="^['#&A-Za-z0-9._%+-]+@[A-Za-z0-9-][A-Za-z0-9.-]*\\.[A-Za-z]{2,15}$") 291 | # A ISO639-1 code. 292 | LanguageCode_validator = bv.String(min_length=2) 293 | NamePart_validator = bv.String(min_length=1, max_length=100, pattern='[^/:?*<>"|]*') 294 | NamespaceId_validator = bv.String(pattern='[-_0-9a-zA-Z:]+') 295 | OptionalNamePart_validator = bv.String(max_length=100, pattern='[^/:?*<>"|]*') 296 | SessionId_validator = bv.String() 297 | SharedFolderId_validator = NamespaceId_validator 298 | PathRoot._home_validator = bv.Void() 299 | PathRoot._root_validator = NamespaceId_validator 300 | PathRoot._namespace_id_validator = NamespaceId_validator 301 | PathRoot._other_validator = bv.Void() 302 | PathRoot._tagmap = { 303 | 'home': PathRoot._home_validator, 304 | 'root': PathRoot._root_validator, 305 | 'namespace_id': PathRoot._namespace_id_validator, 306 | 'other': PathRoot._other_validator, 307 | } 308 | 309 | PathRoot.home = PathRoot('home') 310 | PathRoot.other = PathRoot('other') 311 | 312 | PathRootError._invalid_root_validator = RootInfo_validator 313 | PathRootError._no_permission_validator = bv.Void() 314 | PathRootError._other_validator = bv.Void() 315 | PathRootError._tagmap = { 316 | 'invalid_root': PathRootError._invalid_root_validator, 317 | 'no_permission': PathRootError._no_permission_validator, 318 | 'other': PathRootError._other_validator, 319 | } 320 | 321 | PathRootError.no_permission = PathRootError('no_permission') 322 | PathRootError.other = PathRootError('other') 323 | 324 | RootInfo.root_namespace_id.validator = NamespaceId_validator 325 | RootInfo.home_namespace_id.validator = NamespaceId_validator 326 | RootInfo._field_names_ = set([ 327 | 'root_namespace_id', 328 | 'home_namespace_id', 329 | ]) 330 | RootInfo._all_field_names_ = RootInfo._field_names_ 331 | RootInfo._fields_ = [ 332 | ('root_namespace_id', RootInfo.root_namespace_id.validator), 333 | ('home_namespace_id', RootInfo.home_namespace_id.validator), 334 | ] 335 | RootInfo._all_fields_ = RootInfo._fields_ 336 | 337 | RootInfo._tag_to_subtype_ = { 338 | ('team',): TeamRootInfo_validator, 339 | ('user',): UserRootInfo_validator, 340 | } 341 | RootInfo._pytype_to_tag_and_subtype_ = { 342 | TeamRootInfo: (('team',), TeamRootInfo_validator), 343 | UserRootInfo: (('user',), UserRootInfo_validator), 344 | } 345 | RootInfo._is_catch_all_ = True 346 | 347 | TeamRootInfo.home_path.validator = bv.String() 348 | TeamRootInfo._field_names_ = set(['home_path']) 349 | TeamRootInfo._all_field_names_ = RootInfo._all_field_names_.union(TeamRootInfo._field_names_) 350 | TeamRootInfo._fields_ = [('home_path', TeamRootInfo.home_path.validator)] 351 | TeamRootInfo._all_fields_ = RootInfo._all_fields_ + TeamRootInfo._fields_ 352 | 353 | UserRootInfo._field_names_ = set([]) 354 | UserRootInfo._all_field_names_ = RootInfo._all_field_names_.union(UserRootInfo._field_names_) 355 | UserRootInfo._fields_ = [] 356 | UserRootInfo._all_fields_ = RootInfo._all_fields_ + UserRootInfo._fields_ 357 | 358 | ROUTES = { 359 | } 360 | 361 | -------------------------------------------------------------------------------- /dropbox/contacts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Auto-generated by Stone, do not modify. 3 | # @generated 4 | # flake8: noqa 5 | # pylint: skip-file 6 | from __future__ import unicode_literals 7 | from stone.backends.python_rsrc import stone_base as bb 8 | from stone.backends.python_rsrc import stone_validators as bv 9 | 10 | from dropbox import common 11 | 12 | class DeleteManualContactsArg(bb.Struct): 13 | """ 14 | :ivar contacts.DeleteManualContactsArg.email_addresses: List of manually 15 | added contacts to be deleted. 16 | """ 17 | 18 | __slots__ = [ 19 | '_email_addresses_value', 20 | ] 21 | 22 | _has_required_fields = True 23 | 24 | def __init__(self, 25 | email_addresses=None): 26 | self._email_addresses_value = bb.NOT_SET 27 | if email_addresses is not None: 28 | self.email_addresses = email_addresses 29 | 30 | # Instance attribute type: list of [str] (validator is set below) 31 | email_addresses = bb.Attribute("email_addresses") 32 | 33 | def _process_custom_annotations(self, annotation_type, field_path, processor): 34 | super(DeleteManualContactsArg, self)._process_custom_annotations(annotation_type, field_path, processor) 35 | 36 | DeleteManualContactsArg_validator = bv.Struct(DeleteManualContactsArg) 37 | 38 | class DeleteManualContactsError(bb.Union): 39 | """ 40 | This class acts as a tagged union. Only one of the ``is_*`` methods will 41 | return true. To get the associated value of a tag (if one exists), use the 42 | corresponding ``get_*`` method. 43 | 44 | :ivar list of [str] contacts.DeleteManualContactsError.contacts_not_found: 45 | Can't delete contacts from this list. Make sure the list only has 46 | manually added contacts. The deletion was cancelled. 47 | """ 48 | 49 | _catch_all = 'other' 50 | # Attribute is overwritten below the class definition 51 | other = None 52 | 53 | @classmethod 54 | def contacts_not_found(cls, val): 55 | """ 56 | Create an instance of this class set to the ``contacts_not_found`` tag 57 | with value ``val``. 58 | 59 | :param list of [str] val: 60 | :rtype: DeleteManualContactsError 61 | """ 62 | return cls('contacts_not_found', val) 63 | 64 | def is_contacts_not_found(self): 65 | """ 66 | Check if the union tag is ``contacts_not_found``. 67 | 68 | :rtype: bool 69 | """ 70 | return self._tag == 'contacts_not_found' 71 | 72 | def is_other(self): 73 | """ 74 | Check if the union tag is ``other``. 75 | 76 | :rtype: bool 77 | """ 78 | return self._tag == 'other' 79 | 80 | def get_contacts_not_found(self): 81 | """ 82 | Can't delete contacts from this list. Make sure the list only has 83 | manually added contacts. The deletion was cancelled. 84 | 85 | Only call this if :meth:`is_contacts_not_found` is true. 86 | 87 | :rtype: list of [str] 88 | """ 89 | if not self.is_contacts_not_found(): 90 | raise AttributeError("tag 'contacts_not_found' not set") 91 | return self._value 92 | 93 | def _process_custom_annotations(self, annotation_type, field_path, processor): 94 | super(DeleteManualContactsError, self)._process_custom_annotations(annotation_type, field_path, processor) 95 | 96 | DeleteManualContactsError_validator = bv.Union(DeleteManualContactsError) 97 | 98 | DeleteManualContactsArg.email_addresses.validator = bv.List(common.EmailAddress_validator) 99 | DeleteManualContactsArg._all_field_names_ = set(['email_addresses']) 100 | DeleteManualContactsArg._all_fields_ = [('email_addresses', DeleteManualContactsArg.email_addresses.validator)] 101 | 102 | DeleteManualContactsError._contacts_not_found_validator = bv.List(common.EmailAddress_validator) 103 | DeleteManualContactsError._other_validator = bv.Void() 104 | DeleteManualContactsError._tagmap = { 105 | 'contacts_not_found': DeleteManualContactsError._contacts_not_found_validator, 106 | 'other': DeleteManualContactsError._other_validator, 107 | } 108 | 109 | DeleteManualContactsError.other = DeleteManualContactsError('other') 110 | 111 | delete_manual_contacts = bb.Route( 112 | 'delete_manual_contacts', 113 | 1, 114 | False, 115 | bv.Void(), 116 | bv.Void(), 117 | bv.Void(), 118 | {'auth': 'user', 119 | 'host': 'api', 120 | 'style': 'rpc'}, 121 | ) 122 | delete_manual_contacts_batch = bb.Route( 123 | 'delete_manual_contacts_batch', 124 | 1, 125 | False, 126 | DeleteManualContactsArg_validator, 127 | bv.Void(), 128 | DeleteManualContactsError_validator, 129 | {'auth': 'user', 130 | 'host': 'api', 131 | 'style': 'rpc'}, 132 | ) 133 | 134 | ROUTES = { 135 | 'delete_manual_contacts': delete_manual_contacts, 136 | 'delete_manual_contacts_batch': delete_manual_contacts_batch, 137 | } 138 | 139 | -------------------------------------------------------------------------------- /dropbox/exceptions.py: -------------------------------------------------------------------------------- 1 | class DropboxException(Exception): 2 | """All errors related to making an API request extend this.""" 3 | 4 | def __init__(self, request_id, *args, **kwargs): 5 | # A request_id can be shared with Dropbox Support to pinpoint the exact 6 | # request that returns an error. 7 | super(DropboxException, self).__init__(request_id, *args, **kwargs) 8 | self.request_id = request_id 9 | 10 | def __str__(self): 11 | return repr(self) 12 | 13 | 14 | class ApiError(DropboxException): 15 | """Errors produced by the Dropbox API.""" 16 | 17 | def __init__(self, request_id, error, user_message_text, user_message_locale): 18 | """ 19 | :param (str) request_id: A request_id can be shared with Dropbox 20 | Support to pinpoint the exact request that returns an error. 21 | :param error: An instance of the error data type for the route. 22 | :param (str) user_message_text: A human-readable message that can be 23 | displayed to the end user. Is None, if unavailable. 24 | :param (str) user_message_locale: The locale of ``user_message_text``, 25 | if present. 26 | """ 27 | super(ApiError, self).__init__(request_id, error) 28 | self.error = error 29 | self.user_message_text = user_message_text 30 | self.user_message_locale = user_message_locale 31 | 32 | def __repr__(self): 33 | return 'ApiError({!r}, {})'.format(self.request_id, self.error) 34 | 35 | 36 | class HttpError(DropboxException): 37 | """Errors produced at the HTTP layer.""" 38 | 39 | def __init__(self, request_id, status_code, body): 40 | super(HttpError, self).__init__(request_id, status_code, body) 41 | self.status_code = status_code 42 | self.body = body 43 | 44 | def __repr__(self): 45 | return 'HttpError({!r}, {}, {!r})'.format(self.request_id, 46 | self.status_code, self.body) 47 | 48 | 49 | class PathRootError(HttpError): 50 | """Error caused by an invalid path root.""" 51 | 52 | def __init__(self, request_id, error=None): 53 | super(PathRootError, self).__init__(request_id, 422, None) 54 | self.error = error 55 | 56 | def __repr__(self): 57 | return 'PathRootError({!r}, {!r})'.format(self.request_id, self.error) 58 | 59 | 60 | class BadInputError(HttpError): 61 | """Errors due to bad input parameters to an API Operation.""" 62 | 63 | def __init__(self, request_id, message): 64 | super(BadInputError, self).__init__(request_id, 400, message) 65 | self.message = message 66 | 67 | def __repr__(self): 68 | return 'BadInputError({!r}, {!r})'.format(self.request_id, self.message) 69 | 70 | 71 | class AuthError(HttpError): 72 | """Errors due to invalid authentication credentials.""" 73 | 74 | def __init__(self, request_id, error): 75 | super(AuthError, self).__init__(request_id, 401, None) 76 | self.error = error 77 | 78 | def __repr__(self): 79 | return 'AuthError({!r}, {!r})'.format(self.request_id, self.error) 80 | 81 | 82 | class RateLimitError(HttpError): 83 | """Error caused by rate limiting.""" 84 | 85 | def __init__(self, request_id, error=None, backoff=None): 86 | super(RateLimitError, self).__init__(request_id, 429, None) 87 | self.error = error 88 | self.backoff = backoff 89 | 90 | def __repr__(self): 91 | return 'RateLimitError({!r}, {!r}, {!r})'.format( 92 | self.request_id, self.error, self.backoff) 93 | 94 | 95 | class InternalServerError(HttpError): 96 | """Errors due to a problem on Dropbox.""" 97 | 98 | def __repr__(self): 99 | return 'InternalServerError({!r}, {}, {!r})'.format( 100 | self.request_id, self.status_code, self.body) 101 | -------------------------------------------------------------------------------- /dropbox/openid.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Auto-generated by Stone, do not modify. 3 | # @generated 4 | # flake8: noqa 5 | # pylint: skip-file 6 | from __future__ import unicode_literals 7 | from stone.backends.python_rsrc import stone_base as bb 8 | from stone.backends.python_rsrc import stone_validators as bv 9 | 10 | class OpenIdError(bb.Union): 11 | """ 12 | This class acts as a tagged union. Only one of the ``is_*`` methods will 13 | return true. To get the associated value of a tag (if one exists), use the 14 | corresponding ``get_*`` method. 15 | 16 | :ivar openid.OpenIdError.incorrect_openid_scopes: Missing openid claims for 17 | the associated access token. 18 | """ 19 | 20 | _catch_all = 'other' 21 | # Attribute is overwritten below the class definition 22 | incorrect_openid_scopes = None 23 | # Attribute is overwritten below the class definition 24 | other = None 25 | 26 | def is_incorrect_openid_scopes(self): 27 | """ 28 | Check if the union tag is ``incorrect_openid_scopes``. 29 | 30 | :rtype: bool 31 | """ 32 | return self._tag == 'incorrect_openid_scopes' 33 | 34 | def is_other(self): 35 | """ 36 | Check if the union tag is ``other``. 37 | 38 | :rtype: bool 39 | """ 40 | return self._tag == 'other' 41 | 42 | def _process_custom_annotations(self, annotation_type, field_path, processor): 43 | super(OpenIdError, self)._process_custom_annotations(annotation_type, field_path, processor) 44 | 45 | OpenIdError_validator = bv.Union(OpenIdError) 46 | 47 | class UserInfoArgs(bb.Struct): 48 | """ 49 | No Parameters 50 | """ 51 | 52 | __slots__ = [ 53 | ] 54 | 55 | _has_required_fields = False 56 | 57 | def __init__(self): 58 | pass 59 | 60 | def _process_custom_annotations(self, annotation_type, field_path, processor): 61 | super(UserInfoArgs, self)._process_custom_annotations(annotation_type, field_path, processor) 62 | 63 | UserInfoArgs_validator = bv.Struct(UserInfoArgs) 64 | 65 | class UserInfoError(bb.Union): 66 | """ 67 | This class acts as a tagged union. Only one of the ``is_*`` methods will 68 | return true. To get the associated value of a tag (if one exists), use the 69 | corresponding ``get_*`` method. 70 | """ 71 | 72 | _catch_all = 'other' 73 | # Attribute is overwritten below the class definition 74 | other = None 75 | 76 | @classmethod 77 | def openid_error(cls, val): 78 | """ 79 | Create an instance of this class set to the ``openid_error`` tag with 80 | value ``val``. 81 | 82 | :param OpenIdError val: 83 | :rtype: UserInfoError 84 | """ 85 | return cls('openid_error', val) 86 | 87 | def is_openid_error(self): 88 | """ 89 | Check if the union tag is ``openid_error``. 90 | 91 | :rtype: bool 92 | """ 93 | return self._tag == 'openid_error' 94 | 95 | def is_other(self): 96 | """ 97 | Check if the union tag is ``other``. 98 | 99 | :rtype: bool 100 | """ 101 | return self._tag == 'other' 102 | 103 | def get_openid_error(self): 104 | """ 105 | Only call this if :meth:`is_openid_error` is true. 106 | 107 | :rtype: OpenIdError 108 | """ 109 | if not self.is_openid_error(): 110 | raise AttributeError("tag 'openid_error' not set") 111 | return self._value 112 | 113 | def _process_custom_annotations(self, annotation_type, field_path, processor): 114 | super(UserInfoError, self)._process_custom_annotations(annotation_type, field_path, processor) 115 | 116 | UserInfoError_validator = bv.Union(UserInfoError) 117 | 118 | class UserInfoResult(bb.Struct): 119 | """ 120 | :ivar openid.UserInfoResult.family_name: Last name of user. 121 | :ivar openid.UserInfoResult.given_name: First name of user. 122 | :ivar openid.UserInfoResult.email: Email address of user. 123 | :ivar openid.UserInfoResult.email_verified: If user is email verified. 124 | :ivar openid.UserInfoResult.iss: Issuer of token (in this case Dropbox). 125 | :ivar openid.UserInfoResult.sub: An identifier for the user. This is the 126 | Dropbox account_id, a string value such as 127 | dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc. 128 | """ 129 | 130 | __slots__ = [ 131 | '_family_name_value', 132 | '_given_name_value', 133 | '_email_value', 134 | '_email_verified_value', 135 | '_iss_value', 136 | '_sub_value', 137 | ] 138 | 139 | _has_required_fields = False 140 | 141 | def __init__(self, 142 | family_name=None, 143 | given_name=None, 144 | email=None, 145 | email_verified=None, 146 | iss=None, 147 | sub=None): 148 | self._family_name_value = bb.NOT_SET 149 | self._given_name_value = bb.NOT_SET 150 | self._email_value = bb.NOT_SET 151 | self._email_verified_value = bb.NOT_SET 152 | self._iss_value = bb.NOT_SET 153 | self._sub_value = bb.NOT_SET 154 | if family_name is not None: 155 | self.family_name = family_name 156 | if given_name is not None: 157 | self.given_name = given_name 158 | if email is not None: 159 | self.email = email 160 | if email_verified is not None: 161 | self.email_verified = email_verified 162 | if iss is not None: 163 | self.iss = iss 164 | if sub is not None: 165 | self.sub = sub 166 | 167 | # Instance attribute type: str (validator is set below) 168 | family_name = bb.Attribute("family_name", nullable=True) 169 | 170 | # Instance attribute type: str (validator is set below) 171 | given_name = bb.Attribute("given_name", nullable=True) 172 | 173 | # Instance attribute type: str (validator is set below) 174 | email = bb.Attribute("email", nullable=True) 175 | 176 | # Instance attribute type: bool (validator is set below) 177 | email_verified = bb.Attribute("email_verified", nullable=True) 178 | 179 | # Instance attribute type: str (validator is set below) 180 | iss = bb.Attribute("iss") 181 | 182 | # Instance attribute type: str (validator is set below) 183 | sub = bb.Attribute("sub") 184 | 185 | def _process_custom_annotations(self, annotation_type, field_path, processor): 186 | super(UserInfoResult, self)._process_custom_annotations(annotation_type, field_path, processor) 187 | 188 | UserInfoResult_validator = bv.Struct(UserInfoResult) 189 | 190 | OpenIdError._incorrect_openid_scopes_validator = bv.Void() 191 | OpenIdError._other_validator = bv.Void() 192 | OpenIdError._tagmap = { 193 | 'incorrect_openid_scopes': OpenIdError._incorrect_openid_scopes_validator, 194 | 'other': OpenIdError._other_validator, 195 | } 196 | 197 | OpenIdError.incorrect_openid_scopes = OpenIdError('incorrect_openid_scopes') 198 | OpenIdError.other = OpenIdError('other') 199 | 200 | UserInfoArgs._all_field_names_ = set([]) 201 | UserInfoArgs._all_fields_ = [] 202 | 203 | UserInfoError._openid_error_validator = OpenIdError_validator 204 | UserInfoError._other_validator = bv.Void() 205 | UserInfoError._tagmap = { 206 | 'openid_error': UserInfoError._openid_error_validator, 207 | 'other': UserInfoError._other_validator, 208 | } 209 | 210 | UserInfoError.other = UserInfoError('other') 211 | 212 | UserInfoResult.family_name.validator = bv.Nullable(bv.String()) 213 | UserInfoResult.given_name.validator = bv.Nullable(bv.String()) 214 | UserInfoResult.email.validator = bv.Nullable(bv.String()) 215 | UserInfoResult.email_verified.validator = bv.Nullable(bv.Boolean()) 216 | UserInfoResult.iss.validator = bv.String() 217 | UserInfoResult.sub.validator = bv.String() 218 | UserInfoResult._all_field_names_ = set([ 219 | 'family_name', 220 | 'given_name', 221 | 'email', 222 | 'email_verified', 223 | 'iss', 224 | 'sub', 225 | ]) 226 | UserInfoResult._all_fields_ = [ 227 | ('family_name', UserInfoResult.family_name.validator), 228 | ('given_name', UserInfoResult.given_name.validator), 229 | ('email', UserInfoResult.email.validator), 230 | ('email_verified', UserInfoResult.email_verified.validator), 231 | ('iss', UserInfoResult.iss.validator), 232 | ('sub', UserInfoResult.sub.validator), 233 | ] 234 | 235 | UserInfoResult.iss.default = '' 236 | UserInfoResult.sub.default = '' 237 | userinfo = bb.Route( 238 | 'userinfo', 239 | 1, 240 | False, 241 | UserInfoArgs_validator, 242 | UserInfoResult_validator, 243 | UserInfoError_validator, 244 | {'auth': 'user', 245 | 'host': 'api', 246 | 'style': 'rpc'}, 247 | ) 248 | 249 | ROUTES = { 250 | 'userinfo': userinfo, 251 | } 252 | 253 | -------------------------------------------------------------------------------- /dropbox/secondary_emails.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Auto-generated by Stone, do not modify. 3 | # @generated 4 | # flake8: noqa 5 | # pylint: skip-file 6 | from __future__ import unicode_literals 7 | from stone.backends.python_rsrc import stone_base as bb 8 | from stone.backends.python_rsrc import stone_validators as bv 9 | 10 | from dropbox import common 11 | 12 | class SecondaryEmail(bb.Struct): 13 | """ 14 | :ivar secondary_emails.SecondaryEmail.email: Secondary email address. 15 | :ivar secondary_emails.SecondaryEmail.is_verified: Whether or not the 16 | secondary email address is verified to be owned by a user. 17 | """ 18 | 19 | __slots__ = [ 20 | '_email_value', 21 | '_is_verified_value', 22 | ] 23 | 24 | _has_required_fields = True 25 | 26 | def __init__(self, 27 | email=None, 28 | is_verified=None): 29 | self._email_value = bb.NOT_SET 30 | self._is_verified_value = bb.NOT_SET 31 | if email is not None: 32 | self.email = email 33 | if is_verified is not None: 34 | self.is_verified = is_verified 35 | 36 | # Instance attribute type: str (validator is set below) 37 | email = bb.Attribute("email") 38 | 39 | # Instance attribute type: bool (validator is set below) 40 | is_verified = bb.Attribute("is_verified") 41 | 42 | def _process_custom_annotations(self, annotation_type, field_path, processor): 43 | super(SecondaryEmail, self)._process_custom_annotations(annotation_type, field_path, processor) 44 | 45 | SecondaryEmail_validator = bv.Struct(SecondaryEmail) 46 | 47 | SecondaryEmail.email.validator = common.EmailAddress_validator 48 | SecondaryEmail.is_verified.validator = bv.Boolean() 49 | SecondaryEmail._all_field_names_ = set([ 50 | 'email', 51 | 'is_verified', 52 | ]) 53 | SecondaryEmail._all_fields_ = [ 54 | ('email', SecondaryEmail.email.validator), 55 | ('is_verified', SecondaryEmail.is_verified.validator), 56 | ] 57 | 58 | ROUTES = { 59 | } 60 | 61 | -------------------------------------------------------------------------------- /dropbox/seen_state.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Auto-generated by Stone, do not modify. 3 | # @generated 4 | # flake8: noqa 5 | # pylint: skip-file 6 | from __future__ import unicode_literals 7 | from stone.backends.python_rsrc import stone_base as bb 8 | from stone.backends.python_rsrc import stone_validators as bv 9 | 10 | class PlatformType(bb.Union): 11 | """ 12 | Possible platforms on which a user may view content. 13 | 14 | This class acts as a tagged union. Only one of the ``is_*`` methods will 15 | return true. To get the associated value of a tag (if one exists), use the 16 | corresponding ``get_*`` method. 17 | 18 | :ivar seen_state.PlatformType.web: The content was viewed on the web. 19 | :ivar seen_state.PlatformType.desktop: The content was viewed on a desktop 20 | client. 21 | :ivar seen_state.PlatformType.mobile_ios: The content was viewed on a mobile 22 | iOS client. 23 | :ivar seen_state.PlatformType.mobile_android: The content was viewed on a 24 | mobile android client. 25 | :ivar seen_state.PlatformType.api: The content was viewed from an API 26 | client. 27 | :ivar seen_state.PlatformType.unknown: The content was viewed on an unknown 28 | platform. 29 | :ivar seen_state.PlatformType.mobile: The content was viewed on a mobile 30 | client. DEPRECATED: Use mobile_ios or mobile_android instead. 31 | """ 32 | 33 | _catch_all = 'other' 34 | # Attribute is overwritten below the class definition 35 | web = None 36 | # Attribute is overwritten below the class definition 37 | desktop = None 38 | # Attribute is overwritten below the class definition 39 | mobile_ios = None 40 | # Attribute is overwritten below the class definition 41 | mobile_android = None 42 | # Attribute is overwritten below the class definition 43 | api = None 44 | # Attribute is overwritten below the class definition 45 | unknown = None 46 | # Attribute is overwritten below the class definition 47 | mobile = None 48 | # Attribute is overwritten below the class definition 49 | other = None 50 | 51 | def is_web(self): 52 | """ 53 | Check if the union tag is ``web``. 54 | 55 | :rtype: bool 56 | """ 57 | return self._tag == 'web' 58 | 59 | def is_desktop(self): 60 | """ 61 | Check if the union tag is ``desktop``. 62 | 63 | :rtype: bool 64 | """ 65 | return self._tag == 'desktop' 66 | 67 | def is_mobile_ios(self): 68 | """ 69 | Check if the union tag is ``mobile_ios``. 70 | 71 | :rtype: bool 72 | """ 73 | return self._tag == 'mobile_ios' 74 | 75 | def is_mobile_android(self): 76 | """ 77 | Check if the union tag is ``mobile_android``. 78 | 79 | :rtype: bool 80 | """ 81 | return self._tag == 'mobile_android' 82 | 83 | def is_api(self): 84 | """ 85 | Check if the union tag is ``api``. 86 | 87 | :rtype: bool 88 | """ 89 | return self._tag == 'api' 90 | 91 | def is_unknown(self): 92 | """ 93 | Check if the union tag is ``unknown``. 94 | 95 | :rtype: bool 96 | """ 97 | return self._tag == 'unknown' 98 | 99 | def is_mobile(self): 100 | """ 101 | Check if the union tag is ``mobile``. 102 | 103 | :rtype: bool 104 | """ 105 | return self._tag == 'mobile' 106 | 107 | def is_other(self): 108 | """ 109 | Check if the union tag is ``other``. 110 | 111 | :rtype: bool 112 | """ 113 | return self._tag == 'other' 114 | 115 | def _process_custom_annotations(self, annotation_type, field_path, processor): 116 | super(PlatformType, self)._process_custom_annotations(annotation_type, field_path, processor) 117 | 118 | PlatformType_validator = bv.Union(PlatformType) 119 | 120 | PlatformType._web_validator = bv.Void() 121 | PlatformType._desktop_validator = bv.Void() 122 | PlatformType._mobile_ios_validator = bv.Void() 123 | PlatformType._mobile_android_validator = bv.Void() 124 | PlatformType._api_validator = bv.Void() 125 | PlatformType._unknown_validator = bv.Void() 126 | PlatformType._mobile_validator = bv.Void() 127 | PlatformType._other_validator = bv.Void() 128 | PlatformType._tagmap = { 129 | 'web': PlatformType._web_validator, 130 | 'desktop': PlatformType._desktop_validator, 131 | 'mobile_ios': PlatformType._mobile_ios_validator, 132 | 'mobile_android': PlatformType._mobile_android_validator, 133 | 'api': PlatformType._api_validator, 134 | 'unknown': PlatformType._unknown_validator, 135 | 'mobile': PlatformType._mobile_validator, 136 | 'other': PlatformType._other_validator, 137 | } 138 | 139 | PlatformType.web = PlatformType('web') 140 | PlatformType.desktop = PlatformType('desktop') 141 | PlatformType.mobile_ios = PlatformType('mobile_ios') 142 | PlatformType.mobile_android = PlatformType('mobile_android') 143 | PlatformType.api = PlatformType('api') 144 | PlatformType.unknown = PlatformType('unknown') 145 | PlatformType.mobile = PlatformType('mobile') 146 | PlatformType.other = PlatformType('other') 147 | 148 | ROUTES = { 149 | } 150 | 151 | -------------------------------------------------------------------------------- /dropbox/session.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ssl 3 | 4 | import requests 5 | from requests.adapters import HTTPAdapter 6 | from urllib3.poolmanager import PoolManager 7 | 8 | API_DOMAIN = os.environ.get('DROPBOX_API_DOMAIN', 9 | os.environ.get('DROPBOX_DOMAIN', '.dropboxapi.com')) 10 | 11 | WEB_DOMAIN = os.environ.get('DROPBOX_WEB_DOMAIN', 12 | os.environ.get('DROPBOX_DOMAIN', '.dropbox.com')) 13 | 14 | # Default short hostname for RPC-style routes. 15 | HOST_API = 'api' 16 | 17 | # Default short hostname for upload and download-style routes. 18 | HOST_CONTENT = 'content' 19 | 20 | # Default short hostname for longpoll routes. 21 | HOST_NOTIFY = 'notify' 22 | 23 | # Default short hostname for the Drobox website. 24 | HOST_WWW = 'www' 25 | 26 | API_HOST = os.environ.get('DROPBOX_API_HOST', HOST_API + API_DOMAIN) 27 | API_CONTENT_HOST = os.environ.get('DROPBOX_API_CONTENT_HOST', HOST_CONTENT + API_DOMAIN) 28 | API_NOTIFICATION_HOST = os.environ.get('DROPBOX_API_NOTIFY_HOST', HOST_NOTIFY + API_DOMAIN) 29 | WEB_HOST = os.environ.get('DROPBOX_WEB_HOST', HOST_WWW + WEB_DOMAIN) 30 | 31 | # This is the default longest time we'll block on receiving data from the server 32 | DEFAULT_TIMEOUT = 100 33 | 34 | 35 | # TODO(kelkabany): We probably only want to instantiate this once so that even 36 | # if multiple Dropbox objects are instantiated, they all share the same pool. 37 | class _SSLAdapter(HTTPAdapter): 38 | _ca_certs = None 39 | 40 | def __init__(self, *args, **kwargs): 41 | self._ca_certs = kwargs.pop("ca_certs", None) 42 | super(_SSLAdapter, self).__init__(*args, **kwargs) 43 | 44 | def init_poolmanager(self, connections, maxsize, block=False, **_): 45 | self.poolmanager = PoolManager( 46 | num_pools=connections, 47 | maxsize=maxsize, 48 | block=block, 49 | cert_reqs=ssl.CERT_REQUIRED, 50 | ca_certs=self._ca_certs, 51 | ) 52 | 53 | def pinned_session(pool_maxsize=8, ca_certs=None): 54 | # always verify, use cert bundle if provided 55 | 56 | _session = requests.session() 57 | 58 | # requests 59 | if ca_certs is not None: 60 | _session.verify = ca_certs 61 | else: 62 | _session.verify = True 63 | 64 | # urllib3 within requests 65 | http_adapter = _SSLAdapter(pool_connections=4, pool_maxsize=pool_maxsize, ca_certs=ca_certs) 66 | _session.mount('https://', http_adapter) 67 | return _session 68 | 69 | SSLError = requests.exceptions.SSLError # raised on verification errors 70 | -------------------------------------------------------------------------------- /dropbox/stone_base.py: -------------------------------------------------------------------------------- 1 | from stone.backends.python_rsrc.stone_base import * 2 | -------------------------------------------------------------------------------- /dropbox/stone_serializers.py: -------------------------------------------------------------------------------- 1 | from stone.backends.python_rsrc.stone_serializers import * 2 | -------------------------------------------------------------------------------- /dropbox/stone_validators.py: -------------------------------------------------------------------------------- 1 | from stone.backends.python_rsrc.stone_validators import * 2 | -------------------------------------------------------------------------------- /dropbox/team_common.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Auto-generated by Stone, do not modify. 3 | # @generated 4 | # flake8: noqa 5 | # pylint: skip-file 6 | from __future__ import unicode_literals 7 | from stone.backends.python_rsrc import stone_base as bb 8 | from stone.backends.python_rsrc import stone_validators as bv 9 | 10 | from dropbox import common 11 | 12 | class GroupManagementType(bb.Union): 13 | """ 14 | The group type determines how a group is managed. 15 | 16 | This class acts as a tagged union. Only one of the ``is_*`` methods will 17 | return true. To get the associated value of a tag (if one exists), use the 18 | corresponding ``get_*`` method. 19 | 20 | :ivar team_common.GroupManagementType.user_managed: A group which is managed 21 | by selected users. 22 | :ivar team_common.GroupManagementType.company_managed: A group which is 23 | managed by team admins only. 24 | :ivar team_common.GroupManagementType.system_managed: A group which is 25 | managed automatically by Dropbox. 26 | """ 27 | 28 | _catch_all = 'other' 29 | # Attribute is overwritten below the class definition 30 | user_managed = None 31 | # Attribute is overwritten below the class definition 32 | company_managed = None 33 | # Attribute is overwritten below the class definition 34 | system_managed = None 35 | # Attribute is overwritten below the class definition 36 | other = None 37 | 38 | def is_user_managed(self): 39 | """ 40 | Check if the union tag is ``user_managed``. 41 | 42 | :rtype: bool 43 | """ 44 | return self._tag == 'user_managed' 45 | 46 | def is_company_managed(self): 47 | """ 48 | Check if the union tag is ``company_managed``. 49 | 50 | :rtype: bool 51 | """ 52 | return self._tag == 'company_managed' 53 | 54 | def is_system_managed(self): 55 | """ 56 | Check if the union tag is ``system_managed``. 57 | 58 | :rtype: bool 59 | """ 60 | return self._tag == 'system_managed' 61 | 62 | def is_other(self): 63 | """ 64 | Check if the union tag is ``other``. 65 | 66 | :rtype: bool 67 | """ 68 | return self._tag == 'other' 69 | 70 | def _process_custom_annotations(self, annotation_type, field_path, processor): 71 | super(GroupManagementType, self)._process_custom_annotations(annotation_type, field_path, processor) 72 | 73 | GroupManagementType_validator = bv.Union(GroupManagementType) 74 | 75 | class GroupSummary(bb.Struct): 76 | """ 77 | Information about a group. 78 | 79 | :ivar team_common.GroupSummary.group_external_id: External ID of group. This 80 | is an arbitrary ID that an admin can attach to a group. 81 | :ivar team_common.GroupSummary.member_count: The number of members in the 82 | group. 83 | :ivar team_common.GroupSummary.group_management_type: Who is allowed to 84 | manage the group. 85 | """ 86 | 87 | __slots__ = [ 88 | '_group_name_value', 89 | '_group_id_value', 90 | '_group_external_id_value', 91 | '_member_count_value', 92 | '_group_management_type_value', 93 | ] 94 | 95 | _has_required_fields = True 96 | 97 | def __init__(self, 98 | group_name=None, 99 | group_id=None, 100 | group_management_type=None, 101 | group_external_id=None, 102 | member_count=None): 103 | self._group_name_value = bb.NOT_SET 104 | self._group_id_value = bb.NOT_SET 105 | self._group_external_id_value = bb.NOT_SET 106 | self._member_count_value = bb.NOT_SET 107 | self._group_management_type_value = bb.NOT_SET 108 | if group_name is not None: 109 | self.group_name = group_name 110 | if group_id is not None: 111 | self.group_id = group_id 112 | if group_external_id is not None: 113 | self.group_external_id = group_external_id 114 | if member_count is not None: 115 | self.member_count = member_count 116 | if group_management_type is not None: 117 | self.group_management_type = group_management_type 118 | 119 | # Instance attribute type: str (validator is set below) 120 | group_name = bb.Attribute("group_name") 121 | 122 | # Instance attribute type: str (validator is set below) 123 | group_id = bb.Attribute("group_id") 124 | 125 | # Instance attribute type: str (validator is set below) 126 | group_external_id = bb.Attribute("group_external_id", nullable=True) 127 | 128 | # Instance attribute type: int (validator is set below) 129 | member_count = bb.Attribute("member_count", nullable=True) 130 | 131 | # Instance attribute type: GroupManagementType (validator is set below) 132 | group_management_type = bb.Attribute("group_management_type", user_defined=True) 133 | 134 | def _process_custom_annotations(self, annotation_type, field_path, processor): 135 | super(GroupSummary, self)._process_custom_annotations(annotation_type, field_path, processor) 136 | 137 | GroupSummary_validator = bv.Struct(GroupSummary) 138 | 139 | class GroupType(bb.Union): 140 | """ 141 | The group type determines how a group is created and managed. 142 | 143 | This class acts as a tagged union. Only one of the ``is_*`` methods will 144 | return true. To get the associated value of a tag (if one exists), use the 145 | corresponding ``get_*`` method. 146 | 147 | :ivar team_common.GroupType.team: A group to which team members are 148 | automatically added. Applicable to `team folders 149 | `_ only. 150 | :ivar team_common.GroupType.user_managed: A group is created and managed by 151 | a user. 152 | """ 153 | 154 | _catch_all = 'other' 155 | # Attribute is overwritten below the class definition 156 | team = None 157 | # Attribute is overwritten below the class definition 158 | user_managed = None 159 | # Attribute is overwritten below the class definition 160 | other = None 161 | 162 | def is_team(self): 163 | """ 164 | Check if the union tag is ``team``. 165 | 166 | :rtype: bool 167 | """ 168 | return self._tag == 'team' 169 | 170 | def is_user_managed(self): 171 | """ 172 | Check if the union tag is ``user_managed``. 173 | 174 | :rtype: bool 175 | """ 176 | return self._tag == 'user_managed' 177 | 178 | def is_other(self): 179 | """ 180 | Check if the union tag is ``other``. 181 | 182 | :rtype: bool 183 | """ 184 | return self._tag == 'other' 185 | 186 | def _process_custom_annotations(self, annotation_type, field_path, processor): 187 | super(GroupType, self)._process_custom_annotations(annotation_type, field_path, processor) 188 | 189 | GroupType_validator = bv.Union(GroupType) 190 | 191 | class MemberSpaceLimitType(bb.Union): 192 | """ 193 | The type of the space limit imposed on a team member. 194 | 195 | This class acts as a tagged union. Only one of the ``is_*`` methods will 196 | return true. To get the associated value of a tag (if one exists), use the 197 | corresponding ``get_*`` method. 198 | 199 | :ivar team_common.MemberSpaceLimitType.off: The team member does not have 200 | imposed space limit. 201 | :ivar team_common.MemberSpaceLimitType.alert_only: The team member has soft 202 | imposed space limit - the limit is used for display and for 203 | notifications. 204 | :ivar team_common.MemberSpaceLimitType.stop_sync: The team member has hard 205 | imposed space limit - Dropbox file sync will stop after the limit is 206 | reached. 207 | """ 208 | 209 | _catch_all = 'other' 210 | # Attribute is overwritten below the class definition 211 | off = None 212 | # Attribute is overwritten below the class definition 213 | alert_only = None 214 | # Attribute is overwritten below the class definition 215 | stop_sync = None 216 | # Attribute is overwritten below the class definition 217 | other = None 218 | 219 | def is_off(self): 220 | """ 221 | Check if the union tag is ``off``. 222 | 223 | :rtype: bool 224 | """ 225 | return self._tag == 'off' 226 | 227 | def is_alert_only(self): 228 | """ 229 | Check if the union tag is ``alert_only``. 230 | 231 | :rtype: bool 232 | """ 233 | return self._tag == 'alert_only' 234 | 235 | def is_stop_sync(self): 236 | """ 237 | Check if the union tag is ``stop_sync``. 238 | 239 | :rtype: bool 240 | """ 241 | return self._tag == 'stop_sync' 242 | 243 | def is_other(self): 244 | """ 245 | Check if the union tag is ``other``. 246 | 247 | :rtype: bool 248 | """ 249 | return self._tag == 'other' 250 | 251 | def _process_custom_annotations(self, annotation_type, field_path, processor): 252 | super(MemberSpaceLimitType, self)._process_custom_annotations(annotation_type, field_path, processor) 253 | 254 | MemberSpaceLimitType_validator = bv.Union(MemberSpaceLimitType) 255 | 256 | class TimeRange(bb.Struct): 257 | """ 258 | Time range. 259 | 260 | :ivar team_common.TimeRange.start_time: Optional starting time (inclusive). 261 | :ivar team_common.TimeRange.end_time: Optional ending time (exclusive). 262 | """ 263 | 264 | __slots__ = [ 265 | '_start_time_value', 266 | '_end_time_value', 267 | ] 268 | 269 | _has_required_fields = False 270 | 271 | def __init__(self, 272 | start_time=None, 273 | end_time=None): 274 | self._start_time_value = bb.NOT_SET 275 | self._end_time_value = bb.NOT_SET 276 | if start_time is not None: 277 | self.start_time = start_time 278 | if end_time is not None: 279 | self.end_time = end_time 280 | 281 | # Instance attribute type: datetime.datetime (validator is set below) 282 | start_time = bb.Attribute("start_time", nullable=True) 283 | 284 | # Instance attribute type: datetime.datetime (validator is set below) 285 | end_time = bb.Attribute("end_time", nullable=True) 286 | 287 | def _process_custom_annotations(self, annotation_type, field_path, processor): 288 | super(TimeRange, self)._process_custom_annotations(annotation_type, field_path, processor) 289 | 290 | TimeRange_validator = bv.Struct(TimeRange) 291 | 292 | GroupExternalId_validator = bv.String() 293 | GroupId_validator = bv.String() 294 | MemberExternalId_validator = bv.String(max_length=64) 295 | ResellerId_validator = bv.String() 296 | TeamId_validator = bv.String() 297 | TeamMemberId_validator = bv.String() 298 | GroupManagementType._user_managed_validator = bv.Void() 299 | GroupManagementType._company_managed_validator = bv.Void() 300 | GroupManagementType._system_managed_validator = bv.Void() 301 | GroupManagementType._other_validator = bv.Void() 302 | GroupManagementType._tagmap = { 303 | 'user_managed': GroupManagementType._user_managed_validator, 304 | 'company_managed': GroupManagementType._company_managed_validator, 305 | 'system_managed': GroupManagementType._system_managed_validator, 306 | 'other': GroupManagementType._other_validator, 307 | } 308 | 309 | GroupManagementType.user_managed = GroupManagementType('user_managed') 310 | GroupManagementType.company_managed = GroupManagementType('company_managed') 311 | GroupManagementType.system_managed = GroupManagementType('system_managed') 312 | GroupManagementType.other = GroupManagementType('other') 313 | 314 | GroupSummary.group_name.validator = bv.String() 315 | GroupSummary.group_id.validator = GroupId_validator 316 | GroupSummary.group_external_id.validator = bv.Nullable(GroupExternalId_validator) 317 | GroupSummary.member_count.validator = bv.Nullable(bv.UInt32()) 318 | GroupSummary.group_management_type.validator = GroupManagementType_validator 319 | GroupSummary._all_field_names_ = set([ 320 | 'group_name', 321 | 'group_id', 322 | 'group_external_id', 323 | 'member_count', 324 | 'group_management_type', 325 | ]) 326 | GroupSummary._all_fields_ = [ 327 | ('group_name', GroupSummary.group_name.validator), 328 | ('group_id', GroupSummary.group_id.validator), 329 | ('group_external_id', GroupSummary.group_external_id.validator), 330 | ('member_count', GroupSummary.member_count.validator), 331 | ('group_management_type', GroupSummary.group_management_type.validator), 332 | ] 333 | 334 | GroupType._team_validator = bv.Void() 335 | GroupType._user_managed_validator = bv.Void() 336 | GroupType._other_validator = bv.Void() 337 | GroupType._tagmap = { 338 | 'team': GroupType._team_validator, 339 | 'user_managed': GroupType._user_managed_validator, 340 | 'other': GroupType._other_validator, 341 | } 342 | 343 | GroupType.team = GroupType('team') 344 | GroupType.user_managed = GroupType('user_managed') 345 | GroupType.other = GroupType('other') 346 | 347 | MemberSpaceLimitType._off_validator = bv.Void() 348 | MemberSpaceLimitType._alert_only_validator = bv.Void() 349 | MemberSpaceLimitType._stop_sync_validator = bv.Void() 350 | MemberSpaceLimitType._other_validator = bv.Void() 351 | MemberSpaceLimitType._tagmap = { 352 | 'off': MemberSpaceLimitType._off_validator, 353 | 'alert_only': MemberSpaceLimitType._alert_only_validator, 354 | 'stop_sync': MemberSpaceLimitType._stop_sync_validator, 355 | 'other': MemberSpaceLimitType._other_validator, 356 | } 357 | 358 | MemberSpaceLimitType.off = MemberSpaceLimitType('off') 359 | MemberSpaceLimitType.alert_only = MemberSpaceLimitType('alert_only') 360 | MemberSpaceLimitType.stop_sync = MemberSpaceLimitType('stop_sync') 361 | MemberSpaceLimitType.other = MemberSpaceLimitType('other') 362 | 363 | TimeRange.start_time.validator = bv.Nullable(common.DropboxTimestamp_validator) 364 | TimeRange.end_time.validator = bv.Nullable(common.DropboxTimestamp_validator) 365 | TimeRange._all_field_names_ = set([ 366 | 'start_time', 367 | 'end_time', 368 | ]) 369 | TimeRange._all_fields_ = [ 370 | ('start_time', TimeRange.start_time.validator), 371 | ('end_time', TimeRange.end_time.validator), 372 | ] 373 | 374 | ROUTES = { 375 | } 376 | 377 | -------------------------------------------------------------------------------- /dropbox/users_common.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Auto-generated by Stone, do not modify. 3 | # @generated 4 | # flake8: noqa 5 | # pylint: skip-file 6 | """ 7 | This namespace contains common data types used within the users namespace. 8 | """ 9 | 10 | from __future__ import unicode_literals 11 | from stone.backends.python_rsrc import stone_base as bb 12 | from stone.backends.python_rsrc import stone_validators as bv 13 | 14 | class AccountType(bb.Union): 15 | """ 16 | What type of account this user has. 17 | 18 | This class acts as a tagged union. Only one of the ``is_*`` methods will 19 | return true. To get the associated value of a tag (if one exists), use the 20 | corresponding ``get_*`` method. 21 | 22 | :ivar users_common.AccountType.basic: The basic account type. 23 | :ivar users_common.AccountType.pro: The Dropbox Pro account type. 24 | :ivar users_common.AccountType.business: The Dropbox Business account type. 25 | """ 26 | 27 | _catch_all = None 28 | # Attribute is overwritten below the class definition 29 | basic = None 30 | # Attribute is overwritten below the class definition 31 | pro = None 32 | # Attribute is overwritten below the class definition 33 | business = None 34 | 35 | def is_basic(self): 36 | """ 37 | Check if the union tag is ``basic``. 38 | 39 | :rtype: bool 40 | """ 41 | return self._tag == 'basic' 42 | 43 | def is_pro(self): 44 | """ 45 | Check if the union tag is ``pro``. 46 | 47 | :rtype: bool 48 | """ 49 | return self._tag == 'pro' 50 | 51 | def is_business(self): 52 | """ 53 | Check if the union tag is ``business``. 54 | 55 | :rtype: bool 56 | """ 57 | return self._tag == 'business' 58 | 59 | def _process_custom_annotations(self, annotation_type, field_path, processor): 60 | super(AccountType, self)._process_custom_annotations(annotation_type, field_path, processor) 61 | 62 | AccountType_validator = bv.Union(AccountType) 63 | 64 | AccountId_validator = bv.String(min_length=40, max_length=40) 65 | AccountType._basic_validator = bv.Void() 66 | AccountType._pro_validator = bv.Void() 67 | AccountType._business_validator = bv.Void() 68 | AccountType._tagmap = { 69 | 'basic': AccountType._basic_validator, 70 | 'pro': AccountType._pro_validator, 71 | 'business': AccountType._business_validator, 72 | } 73 | 74 | AccountType.basic = AccountType('basic') 75 | AccountType.pro = AccountType('pro') 76 | AccountType.business = AccountType('business') 77 | 78 | ROUTES = { 79 | } 80 | 81 | -------------------------------------------------------------------------------- /example/back-up-and-restore/README.md: -------------------------------------------------------------------------------- 1 | # Backup And Restore 2 | ## A Dropbox API sample app for the Python SDK 3 | 4 | # How to run 5 | Make sure you've installed the Dropbox Python SDK by following the installation instructions ( https://www.dropbox.com/developers/documentation/python#install ). 6 | 7 | Then, find this line in backup-and-restore-sample.py and modify it to include your own access token. 8 | ```TOKEN = ''``` 9 | 10 | From the example/backup-and-restore directory, run the script. 11 | ```python backup-and-restore-sample.py``` 12 | 13 | You should see the file my-file-backup.txt show up in the Dropbox account you used to get the access token. 14 | 15 | # Description 16 | ## Functionality 17 | 1. Back up a file ("my-file.txt") 18 | The my-file.txt file is a simple file that could be any kind of app or user data. 19 | In this example, it contains a simple string of text (initially "original"). 20 | 21 | 2. Restore a file to a previous version (using /revisions) 22 | 23 | # API usage 24 | ## New v2 endpoints 25 | This app uses the Dropbox API v2 endpoints files_upload, files_restore, files_download_to_file, and files_list_revisions. See more here: http://dropbox-sdk-python.readthedocs.org/en/main/ 26 | 27 | ## Error checking 28 | This example also shows you how to do specific error handling with the new API v2 exceptions. In the backup() function, you can see how to check for a specific kind of API Error. -------------------------------------------------------------------------------- /example/back-up-and-restore/backup-and-restore-example.py: -------------------------------------------------------------------------------- 1 | """ 2 | Backs up and restores a settings file to Dropbox. 3 | This is an example app for API v2. 4 | """ 5 | 6 | import sys 7 | import dropbox 8 | from dropbox.files import WriteMode 9 | from dropbox.exceptions import ApiError, AuthError 10 | 11 | # Add OAuth2 access token here. 12 | # You can generate one for yourself in the App Console. 13 | # See 14 | TOKEN = '' 15 | 16 | LOCALFILE = 'my-file.txt' 17 | BACKUPPATH = '/my-file-backup.txt' 18 | 19 | # Uploads contents of LOCALFILE to Dropbox 20 | def backup(): 21 | with open(LOCALFILE, 'rb') as f: 22 | # We use WriteMode=overwrite to make sure that the settings in the file 23 | # are changed on upload 24 | print("Uploading " + LOCALFILE + " to Dropbox as " + BACKUPPATH + "...") 25 | try: 26 | dbx.files_upload(f.read(), BACKUPPATH, mode=WriteMode('overwrite')) 27 | except ApiError as err: 28 | # This checks for the specific error where a user doesn't have 29 | # enough Dropbox space quota to upload this file 30 | if (err.error.is_path() and 31 | err.error.get_path().reason.is_insufficient_space()): 32 | sys.exit("ERROR: Cannot back up; insufficient space.") 33 | elif err.user_message_text: 34 | print(err.user_message_text) 35 | sys.exit() 36 | else: 37 | print(err) 38 | sys.exit() 39 | 40 | # Change the text string in LOCALFILE to be new_content 41 | # @param new_content is a string 42 | def change_local_file(new_content): 43 | print("Changing contents of " + LOCALFILE + " on local machine...") 44 | with open(LOCALFILE, 'wb') as f: 45 | f.write(new_content) 46 | 47 | # Restore the local and Dropbox files to a certain revision 48 | def restore(rev=None): 49 | # Restore the file on Dropbox to a certain revision 50 | print("Restoring " + BACKUPPATH + " to revision " + rev + " on Dropbox...") 51 | dbx.files_restore(BACKUPPATH, rev) 52 | 53 | # Download the specific revision of the file at BACKUPPATH to LOCALFILE 54 | print("Downloading current " + BACKUPPATH + " from Dropbox, overwriting " + LOCALFILE + "...") 55 | dbx.files_download_to_file(LOCALFILE, BACKUPPATH, rev) 56 | 57 | # Look at all of the available revisions on Dropbox, and return the oldest one 58 | def select_revision(): 59 | # Get the revisions for a file (and sort by the datetime object, "server_modified") 60 | print("Finding available revisions on Dropbox...") 61 | entries = dbx.files_list_revisions(BACKUPPATH, limit=30).entries 62 | revisions = sorted(entries, key=lambda entry: entry.server_modified) 63 | 64 | for revision in revisions: 65 | print(revision.rev, revision.server_modified) 66 | 67 | # Return the oldest revision (first entry, because revisions was sorted oldest:newest) 68 | return revisions[0].rev 69 | 70 | if __name__ == '__main__': 71 | # Check for an access token 72 | if (len(TOKEN) == 0): 73 | sys.exit("ERROR: Looks like you didn't add your access token. " 74 | "Open up backup-and-restore-example.py in a text editor and " 75 | "paste in your token in line 14.") 76 | 77 | # Create an instance of a Dropbox class, which can make requests to the API. 78 | print("Creating a Dropbox object...") 79 | with dropbox.Dropbox(TOKEN) as dbx: 80 | 81 | # Check that the access token is valid 82 | try: 83 | dbx.users_get_current_account() 84 | except AuthError: 85 | sys.exit("ERROR: Invalid access token; try re-generating an " 86 | "access token from the app console on the web.") 87 | 88 | # Create a backup of the current settings file 89 | backup() 90 | 91 | # Change the user's file, create another backup 92 | change_local_file(b"updated") 93 | backup() 94 | 95 | # Restore the local and Dropbox files to a certain revision 96 | to_rev = select_revision() 97 | restore(to_rev) 98 | 99 | print("Done!") 100 | -------------------------------------------------------------------------------- /example/back-up-and-restore/my-file.txt: -------------------------------------------------------------------------------- 1 | original -------------------------------------------------------------------------------- /example/oauth/commandline-oauth-pkce.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import dropbox 4 | from dropbox import DropboxOAuth2FlowNoRedirect 5 | 6 | ''' 7 | Populate your app key in order to run this locally 8 | ''' 9 | APP_KEY = "" 10 | 11 | auth_flow = DropboxOAuth2FlowNoRedirect(APP_KEY, use_pkce=True, token_access_type='offline') 12 | 13 | authorize_url = auth_flow.start() 14 | print("1. Go to: " + authorize_url) 15 | print("2. Click \"Allow\" (you might have to log in first).") 16 | print("3. Copy the authorization code.") 17 | auth_code = input("Enter the authorization code here: ").strip() 18 | 19 | try: 20 | oauth_result = auth_flow.finish(auth_code) 21 | except Exception as e: 22 | print('Error: %s' % (e,)) 23 | exit(1) 24 | 25 | with dropbox.Dropbox(oauth2_refresh_token=oauth_result.refresh_token, app_key=APP_KEY) as dbx: 26 | dbx.users_get_current_account() 27 | print("Successfully set up client!") 28 | -------------------------------------------------------------------------------- /example/oauth/commandline-oauth-scopes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import dropbox 4 | from dropbox import DropboxOAuth2FlowNoRedirect 5 | 6 | ''' 7 | It goes through an example of requesting a starting scope, 8 | and requesting more throughout the process 9 | ''' 10 | APP_KEY = "" 11 | APP_SECRET = "" 12 | 13 | auth_flow = DropboxOAuth2FlowNoRedirect(APP_KEY, 14 | consumer_secret=APP_SECRET, 15 | token_access_type='offline', 16 | scope=['files.metadata.read']) 17 | 18 | authorize_url = auth_flow.start() 19 | print("1. Go to: " + authorize_url) 20 | print("2. Click \"Allow\" (you might have to log in first).") 21 | print("3. Copy the authorization code.") 22 | auth_code = input("Enter the authorization code here: ").strip() 23 | 24 | try: 25 | oauth_result = auth_flow.finish(auth_code) 26 | # authorization has files.metadata.read scope only 27 | assert oauth_result.scope == 'files.metadata.read' 28 | except Exception as e: 29 | print('Error: %s' % (e,)) 30 | exit(1) 31 | 32 | # If an application needs write scopes now we can request the new scope with the auth flow 33 | auth_flow2 = DropboxOAuth2FlowNoRedirect(APP_KEY, 34 | consumer_secret=APP_SECRET, 35 | token_access_type='offline', 36 | scope=['account_info.read']) 37 | 38 | authorize_url = auth_flow2.start() 39 | print("1. Go to: " + authorize_url) 40 | print("2. Click \"Allow\" (you might have to log in first).") 41 | print("3. Copy the authorization code.") 42 | auth_code = input("Enter the authorization code here: ").strip() 43 | 44 | try: 45 | oauth_result = auth_flow2.finish(auth_code) 46 | # authorization has account_info.read scope only 47 | assert oauth_result.scope == 'account_info.read' 48 | except Exception as e: 49 | print('Error: %s' % (e,)) 50 | exit(1) 51 | 52 | # If an application needs a new scope but wants to keep the existing scopes, 53 | # you can add include_granted_scopes parameter 54 | auth_flow3 = DropboxOAuth2FlowNoRedirect(APP_KEY, 55 | consumer_secret=APP_SECRET, 56 | token_access_type='offline', 57 | scope=['files.content.read', 'files.content.write'], 58 | include_granted_scopes='user') 59 | 60 | authorize_url = auth_flow3.start() 61 | print("1. Go to: " + authorize_url) 62 | print("2. Click \"Allow\" (you might have to log in first).") 63 | print("3. Copy the authorization code.") 64 | auth_code = input("Enter the authorization code here: ").strip() 65 | 66 | try: 67 | oauth_result = auth_flow3.finish(auth_code) 68 | print(oauth_result) 69 | # authorization has all granted user scopes 70 | assert 'account_info.read' in oauth_result.scope 71 | assert 'files.metadata.read' in oauth_result.scope 72 | assert 'files.content.read' in oauth_result.scope 73 | assert 'files.content.write' in oauth_result.scope 74 | print(oauth_result.scope) # Printing for example 75 | except Exception as e: 76 | print('Error: %s' % (e,)) 77 | exit(1) 78 | 79 | with dropbox.Dropbox(oauth2_access_token=oauth_result.access_token, 80 | oauth2_access_token_expiration=oauth_result.expires_at, 81 | oauth2_refresh_token=oauth_result.refresh_token, 82 | app_key=APP_KEY, 83 | app_secret=APP_SECRET) as dbx: 84 | dbx.users_get_current_account() 85 | print("Successfully set up client!") 86 | -------------------------------------------------------------------------------- /example/oauth/commandline-oauth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import dropbox 4 | from dropbox import DropboxOAuth2FlowNoRedirect 5 | 6 | ''' 7 | This example walks through a basic oauth flow using the existing long-lived token type 8 | Populate your app key and app secret in order to run this locally 9 | ''' 10 | APP_KEY = "" 11 | APP_SECRET = "" 12 | 13 | auth_flow = DropboxOAuth2FlowNoRedirect(APP_KEY, APP_SECRET) 14 | 15 | authorize_url = auth_flow.start() 16 | print("1. Go to: " + authorize_url) 17 | print("2. Click \"Allow\" (you might have to log in first).") 18 | print("3. Copy the authorization code.") 19 | auth_code = input("Enter the authorization code here: ").strip() 20 | 21 | try: 22 | oauth_result = auth_flow.finish(auth_code) 23 | except Exception as e: 24 | print('Error: %s' % (e,)) 25 | exit(1) 26 | 27 | with dropbox.Dropbox(oauth2_access_token=oauth_result.access_token) as dbx: 28 | dbx.users_get_current_account() 29 | print("Successfully set up client!") 30 | -------------------------------------------------------------------------------- /example/updown.py: -------------------------------------------------------------------------------- 1 | """Upload the contents of your Downloads folder to Dropbox. 2 | 3 | This is an example app for API v2. 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | import argparse 9 | import contextlib 10 | import datetime 11 | import os 12 | import six 13 | import sys 14 | import time 15 | import unicodedata 16 | 17 | if sys.version.startswith('2'): 18 | input = raw_input # noqa: E501,F821; pylint: disable=redefined-builtin,undefined-variable,useless-suppression 19 | 20 | import dropbox 21 | 22 | # OAuth2 access token. TODO: login etc. 23 | TOKEN = '' 24 | 25 | parser = argparse.ArgumentParser(description='Sync ~/Downloads to Dropbox') 26 | parser.add_argument('folder', nargs='?', default='Downloads', 27 | help='Folder name in your Dropbox') 28 | parser.add_argument('rootdir', nargs='?', default='~/Downloads', 29 | help='Local directory to upload') 30 | parser.add_argument('--token', default=TOKEN, 31 | help='Access token ' 32 | '(see https://www.dropbox.com/developers/apps)') 33 | parser.add_argument('--yes', '-y', action='store_true', 34 | help='Answer yes to all questions') 35 | parser.add_argument('--no', '-n', action='store_true', 36 | help='Answer no to all questions') 37 | parser.add_argument('--default', '-d', action='store_true', 38 | help='Take default answer on all questions') 39 | 40 | def main(): 41 | """Main program. 42 | 43 | Parse command line, then iterate over files and directories under 44 | rootdir and upload all files. Skips some temporary files and 45 | directories, and avoids duplicate uploads by comparing size and 46 | mtime with the server. 47 | """ 48 | args = parser.parse_args() 49 | if sum([bool(b) for b in (args.yes, args.no, args.default)]) > 1: 50 | print('At most one of --yes, --no, --default is allowed') 51 | sys.exit(2) 52 | if not args.token: 53 | print('--token is mandatory') 54 | sys.exit(2) 55 | 56 | folder = args.folder 57 | rootdir = os.path.expanduser(args.rootdir) 58 | print('Dropbox folder name:', folder) 59 | print('Local directory:', rootdir) 60 | if not os.path.exists(rootdir): 61 | print(rootdir, 'does not exist on your filesystem') 62 | sys.exit(1) 63 | elif not os.path.isdir(rootdir): 64 | print(rootdir, 'is not a folder on your filesystem') 65 | sys.exit(1) 66 | 67 | dbx = dropbox.Dropbox(args.token) 68 | 69 | for dn, dirs, files in os.walk(rootdir): 70 | subfolder = dn[len(rootdir):].strip(os.path.sep) 71 | listing = list_folder(dbx, folder, subfolder) 72 | print('Descending into', subfolder, '...') 73 | 74 | # First do all the files. 75 | for name in files: 76 | fullname = os.path.join(dn, name) 77 | if not isinstance(name, six.text_type): 78 | name = name.decode('utf-8') 79 | nname = unicodedata.normalize('NFC', name) 80 | if name.startswith('.'): 81 | print('Skipping dot file:', name) 82 | elif name.startswith('@') or name.endswith('~'): 83 | print('Skipping temporary file:', name) 84 | elif name.endswith('.pyc') or name.endswith('.pyo'): 85 | print('Skipping generated file:', name) 86 | elif nname in listing: 87 | md = listing[nname] 88 | mtime = os.path.getmtime(fullname) 89 | mtime_dt = datetime.datetime(*time.gmtime(mtime)[:6]) 90 | size = os.path.getsize(fullname) 91 | if (isinstance(md, dropbox.files.FileMetadata) and 92 | mtime_dt == md.client_modified and size == md.size): 93 | print(name, 'is already synced [stats match]') 94 | else: 95 | print(name, 'exists with different stats, downloading') 96 | res = download(dbx, folder, subfolder, name) 97 | with open(fullname, 'rb') as f: 98 | data = f.read() 99 | if res == data: 100 | print(name, 'is already synced [content match]') 101 | else: 102 | print(name, 'has changed since last sync') 103 | if yesno('Refresh %s' % name, False, args): 104 | upload(dbx, fullname, folder, subfolder, name, 105 | overwrite=True) 106 | elif yesno('Upload %s' % name, True, args): 107 | upload(dbx, fullname, folder, subfolder, name) 108 | 109 | # Then choose which subdirectories to traverse. 110 | keep = [] 111 | for name in dirs: 112 | if name.startswith('.'): 113 | print('Skipping dot directory:', name) 114 | elif name.startswith('@') or name.endswith('~'): 115 | print('Skipping temporary directory:', name) 116 | elif name == '__pycache__': 117 | print('Skipping generated directory:', name) 118 | elif yesno('Descend into %s' % name, True, args): 119 | print('Keeping directory:', name) 120 | keep.append(name) 121 | else: 122 | print('OK, skipping directory:', name) 123 | dirs[:] = keep 124 | 125 | dbx.close() 126 | 127 | def list_folder(dbx, folder, subfolder): 128 | """List a folder. 129 | 130 | Return a dict mapping unicode filenames to 131 | FileMetadata|FolderMetadata entries. 132 | """ 133 | path = '/%s/%s' % (folder, subfolder.replace(os.path.sep, '/')) 134 | while '//' in path: 135 | path = path.replace('//', '/') 136 | path = path.rstrip('/') 137 | try: 138 | with stopwatch('list_folder'): 139 | res = dbx.files_list_folder(path) 140 | except dropbox.exceptions.ApiError as err: 141 | print('Folder listing failed for', path, '-- assumed empty:', err) 142 | return {} 143 | else: 144 | rv = {} 145 | for entry in res.entries: 146 | rv[entry.name] = entry 147 | return rv 148 | 149 | def download(dbx, folder, subfolder, name): 150 | """Download a file. 151 | 152 | Return the bytes of the file, or None if it doesn't exist. 153 | """ 154 | path = '/%s/%s/%s' % (folder, subfolder.replace(os.path.sep, '/'), name) 155 | while '//' in path: 156 | path = path.replace('//', '/') 157 | with stopwatch('download'): 158 | try: 159 | md, res = dbx.files_download(path) 160 | except dropbox.exceptions.HttpError as err: 161 | print('*** HTTP error', err) 162 | return None 163 | data = res.content 164 | print(len(data), 'bytes; md:', md) 165 | return data 166 | 167 | def upload(dbx, fullname, folder, subfolder, name, overwrite=False): 168 | """Upload a file. 169 | 170 | Return the request response, or None in case of error. 171 | """ 172 | path = '/%s/%s/%s' % (folder, subfolder.replace(os.path.sep, '/'), name) 173 | while '//' in path: 174 | path = path.replace('//', '/') 175 | mode = (dropbox.files.WriteMode.overwrite 176 | if overwrite 177 | else dropbox.files.WriteMode.add) 178 | mtime = os.path.getmtime(fullname) 179 | with open(fullname, 'rb') as f: 180 | data = f.read() 181 | with stopwatch('upload %d bytes' % len(data)): 182 | try: 183 | res = dbx.files_upload( 184 | data, path, mode, 185 | client_modified=datetime.datetime(*time.gmtime(mtime)[:6]), 186 | mute=True) 187 | except dropbox.exceptions.ApiError as err: 188 | print('*** API error', err) 189 | return None 190 | print('uploaded as', res.name.encode('utf8')) 191 | return res 192 | 193 | def yesno(message, default, args): 194 | """Handy helper function to ask a yes/no question. 195 | 196 | Command line arguments --yes or --no force the answer; 197 | --default to force the default answer. 198 | 199 | Otherwise a blank line returns the default, and answering 200 | y/yes or n/no returns True or False. 201 | 202 | Retry on unrecognized answer. 203 | 204 | Special answers: 205 | - q or quit exits the program 206 | - p or pdb invokes the debugger 207 | """ 208 | if args.default: 209 | print(message + '? [auto]', 'Y' if default else 'N') 210 | return default 211 | if args.yes: 212 | print(message + '? [auto] YES') 213 | return True 214 | if args.no: 215 | print(message + '? [auto] NO') 216 | return False 217 | if default: 218 | message += '? [Y/n] ' 219 | else: 220 | message += '? [N/y] ' 221 | while True: 222 | answer = input(message).strip().lower() 223 | if not answer: 224 | return default 225 | if answer in ('y', 'yes'): 226 | return True 227 | if answer in ('n', 'no'): 228 | return False 229 | if answer in ('q', 'quit'): 230 | print('Exit') 231 | raise SystemExit(0) 232 | if answer in ('p', 'pdb'): 233 | import pdb 234 | pdb.set_trace() 235 | print('Please answer YES or NO.') 236 | 237 | @contextlib.contextmanager 238 | def stopwatch(message): 239 | """Context manager to print how long a block of code took.""" 240 | t0 = time.time() 241 | try: 242 | yield 243 | finally: 244 | t1 = time.time() 245 | print('Total elapsed time for %s: %.3f' % (message, t1 - t0)) 246 | 247 | if __name__ == '__main__': 248 | main() 249 | -------------------------------------------------------------------------------- /ez_setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Bootstrap setuptools installation 3 | 4 | To use setuptools in your package's setup.py, include this 5 | file in the same directory and add this to the top of your setup.py:: 6 | 7 | from ez_setup import use_setuptools 8 | use_setuptools() 9 | 10 | To require a specific version of setuptools, set a download 11 | mirror, or use an alternate download directory, simply supply 12 | the appropriate options to ``use_setuptools()``. 13 | 14 | This file can also be run as a script to install or upgrade setuptools. 15 | """ 16 | import os 17 | import shutil 18 | import sys 19 | import tempfile 20 | import zipfile 21 | import optparse 22 | import subprocess 23 | import platform 24 | import textwrap 25 | import contextlib 26 | 27 | from distutils import log 28 | 29 | try: 30 | from site import USER_SITE 31 | except ImportError: 32 | USER_SITE = None 33 | 34 | DEFAULT_VERSION = "3.1" 35 | DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" 36 | 37 | def _python_cmd(*args): 38 | """ 39 | Return True if the command succeeded. 40 | """ 41 | args = (sys.executable,) + args 42 | return subprocess.call(args) == 0 43 | 44 | 45 | def _install(archive_filename, install_args=()): 46 | with archive_context(archive_filename): 47 | # installing 48 | log.warn('Installing Setuptools') 49 | if not _python_cmd('setup.py', 'install', *install_args): 50 | log.warn('Something went wrong during the installation.') 51 | log.warn('See the error message above.') 52 | # exitcode will be 2 53 | return 2 54 | 55 | 56 | def _build_egg(egg, archive_filename, to_dir): 57 | with archive_context(archive_filename): 58 | # building an egg 59 | log.warn('Building a Setuptools egg in %s', to_dir) 60 | _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) 61 | # returning the result 62 | log.warn(egg) 63 | if not os.path.exists(egg): 64 | raise IOError('Could not build the egg.') 65 | 66 | 67 | def get_zip_class(): 68 | """ 69 | Supplement ZipFile class to support context manager for Python 2.6 70 | """ 71 | class ContextualZipFile(zipfile.ZipFile): 72 | def __enter__(self): 73 | return self 74 | def __exit__(self, type, value, traceback): 75 | self.close 76 | return zipfile.ZipFile if hasattr(zipfile.ZipFile, '__exit__') else \ 77 | ContextualZipFile 78 | 79 | 80 | @contextlib.contextmanager 81 | def archive_context(filename): 82 | # extracting the archive 83 | tmpdir = tempfile.mkdtemp() 84 | log.warn('Extracting in %s', tmpdir) 85 | old_wd = os.getcwd() 86 | try: 87 | os.chdir(tmpdir) 88 | with get_zip_class()(filename) as archive: 89 | archive.extractall() 90 | 91 | # going in the directory 92 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) 93 | os.chdir(subdir) 94 | log.warn('Now working in %s', subdir) 95 | yield 96 | 97 | finally: 98 | os.chdir(old_wd) 99 | shutil.rmtree(tmpdir) 100 | 101 | 102 | def _do_download(version, download_base, to_dir, download_delay): 103 | egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' 104 | % (version, sys.version_info[0], sys.version_info[1])) 105 | if not os.path.exists(egg): 106 | archive = download_setuptools(version, download_base, 107 | to_dir, download_delay) 108 | _build_egg(egg, archive, to_dir) 109 | sys.path.insert(0, egg) 110 | 111 | # Remove previously-imported pkg_resources if present (see 112 | # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details). 113 | if 'pkg_resources' in sys.modules: 114 | del sys.modules['pkg_resources'] 115 | 116 | import setuptools 117 | setuptools.bootstrap_install_from = egg 118 | 119 | 120 | def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, 121 | to_dir=os.curdir, download_delay=15): 122 | to_dir = os.path.abspath(to_dir) 123 | rep_modules = 'pkg_resources', 'setuptools' 124 | imported = set(sys.modules).intersection(rep_modules) 125 | try: 126 | import pkg_resources 127 | except ImportError: 128 | return _do_download(version, download_base, to_dir, download_delay) 129 | try: 130 | pkg_resources.require("setuptools>=" + version) 131 | return 132 | except pkg_resources.DistributionNotFound: 133 | return _do_download(version, download_base, to_dir, download_delay) 134 | except pkg_resources.VersionConflict as VC_err: 135 | if imported: 136 | msg = textwrap.dedent(""" 137 | The required version of setuptools (>={version}) is not available, 138 | and can't be installed while this script is running. Please 139 | install a more recent version first, using 140 | 'easy_install -U setuptools'. 141 | 142 | (Currently using {VC_err.args[0]!r}) 143 | """).format(VC_err=VC_err, version=version) 144 | sys.stderr.write(msg) 145 | sys.exit(2) 146 | 147 | # otherwise, reload ok 148 | del pkg_resources, sys.modules['pkg_resources'] 149 | return _do_download(version, download_base, to_dir, download_delay) 150 | 151 | def _clean_check(cmd, target): 152 | """ 153 | Run the command to download target. If the command fails, clean up before 154 | re-raising the error. 155 | """ 156 | try: 157 | subprocess.check_call(cmd) 158 | except subprocess.CalledProcessError: 159 | if os.access(target, os.F_OK): 160 | os.unlink(target) 161 | raise 162 | 163 | def download_file_powershell(url, target): 164 | """ 165 | Download the file at url to target using Powershell (which will validate 166 | trust). Raise an exception if the command cannot complete. 167 | """ 168 | target = os.path.abspath(target) 169 | cmd = [ 170 | 'powershell', 171 | '-Command', 172 | "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" % vars(), 173 | ] 174 | _clean_check(cmd, target) 175 | 176 | def has_powershell(): 177 | if platform.system() != 'Windows': 178 | return False 179 | cmd = ['powershell', '-Command', 'echo test'] 180 | devnull = open(os.path.devnull, 'wb') 181 | try: 182 | try: 183 | subprocess.check_call(cmd, stdout=devnull, stderr=devnull) 184 | except: 185 | return False 186 | finally: 187 | devnull.close() 188 | return True 189 | 190 | download_file_powershell.viable = has_powershell 191 | 192 | def download_file_curl(url, target): 193 | cmd = ['curl', url, '--silent', '--output', target] 194 | _clean_check(cmd, target) 195 | 196 | def has_curl(): 197 | cmd = ['curl', '--version'] 198 | devnull = open(os.path.devnull, 'wb') 199 | try: 200 | try: 201 | subprocess.check_call(cmd, stdout=devnull, stderr=devnull) 202 | except: 203 | return False 204 | finally: 205 | devnull.close() 206 | return True 207 | 208 | download_file_curl.viable = has_curl 209 | 210 | def download_file_wget(url, target): 211 | cmd = ['wget', url, '--quiet', '--output-document', target] 212 | _clean_check(cmd, target) 213 | 214 | def has_wget(): 215 | cmd = ['wget', '--version'] 216 | devnull = open(os.path.devnull, 'wb') 217 | try: 218 | try: 219 | subprocess.check_call(cmd, stdout=devnull, stderr=devnull) 220 | except: 221 | return False 222 | finally: 223 | devnull.close() 224 | return True 225 | 226 | download_file_wget.viable = has_wget 227 | 228 | def download_file_insecure(url, target): 229 | """ 230 | Use Python to download the file, even though it cannot authenticate the 231 | connection. 232 | """ 233 | try: 234 | from urllib.request import urlopen 235 | except ImportError: 236 | from urllib2 import urlopen 237 | src = dst = None 238 | try: 239 | src = urlopen(url) 240 | # Read/write all in one block, so we don't create a corrupt file 241 | # if the download is interrupted. 242 | data = src.read() 243 | dst = open(target, "wb") 244 | dst.write(data) 245 | finally: 246 | if src: 247 | src.close() 248 | if dst: 249 | dst.close() 250 | 251 | download_file_insecure.viable = lambda: True 252 | 253 | def get_best_downloader(): 254 | downloaders = [ 255 | download_file_powershell, 256 | download_file_curl, 257 | download_file_wget, 258 | download_file_insecure, 259 | ] 260 | 261 | for dl in downloaders: 262 | if dl.viable(): 263 | return dl 264 | 265 | def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, 266 | to_dir=os.curdir, delay=15, downloader_factory=get_best_downloader): 267 | """ 268 | Download setuptools from a specified location and return its filename 269 | 270 | `version` should be a valid setuptools version number that is available 271 | as an egg for download under the `download_base` URL (which should end 272 | with a '/'). `to_dir` is the directory where the egg will be downloaded. 273 | `delay` is the number of seconds to pause before an actual download 274 | attempt. 275 | 276 | ``downloader_factory`` should be a function taking no arguments and 277 | returning a function for downloading a URL to a target. 278 | """ 279 | # making sure we use the absolute path 280 | to_dir = os.path.abspath(to_dir) 281 | zip_name = "setuptools-%s.zip" % version 282 | url = download_base + zip_name 283 | saveto = os.path.join(to_dir, zip_name) 284 | if not os.path.exists(saveto): # Avoid repeated downloads 285 | log.warn("Downloading %s", url) 286 | downloader = downloader_factory() 287 | downloader(url, saveto) 288 | return os.path.realpath(saveto) 289 | 290 | def _build_install_args(options): 291 | """ 292 | Build the arguments to 'python setup.py install' on the setuptools package 293 | """ 294 | return ['--user'] if options.user_install else [] 295 | 296 | def _parse_args(): 297 | """ 298 | Parse the command line for options 299 | """ 300 | parser = optparse.OptionParser() 301 | parser.add_option( 302 | '--user', dest='user_install', action='store_true', default=False, 303 | help='install in user site package (requires Python 2.6 or later)') 304 | parser.add_option( 305 | '--download-base', dest='download_base', metavar="URL", 306 | default=DEFAULT_URL, 307 | help='alternative URL from where to download the setuptools package') 308 | parser.add_option( 309 | '--insecure', dest='downloader_factory', action='store_const', 310 | const=lambda: download_file_insecure, default=get_best_downloader, 311 | help='Use internal, non-validating downloader' 312 | ) 313 | parser.add_option( 314 | '--version', help="Specify which version to download", 315 | default=DEFAULT_VERSION, 316 | ) 317 | options, args = parser.parse_args() 318 | # positional arguments are ignored 319 | return options 320 | 321 | def main(): 322 | """Install or upgrade setuptools and EasyInstall""" 323 | options = _parse_args() 324 | archive = download_setuptools( 325 | version=options.version, 326 | download_base=options.download_base, 327 | downloader_factory=options.downloader_factory, 328 | ) 329 | return _install(archive, _build_install_args(options)) 330 | 331 | if __name__ == '__main__': 332 | sys.exit(main()) 333 | -------------------------------------------------------------------------------- /generate_base_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | import argparse 5 | import glob 6 | import os 7 | import subprocess 8 | import sys 9 | 10 | cmdline_desc = """\ 11 | Runs Stone to generate Python types and client for the Dropbox client. 12 | """ 13 | 14 | _cmdline_parser = argparse.ArgumentParser(description=cmdline_desc) 15 | _cmdline_parser.add_argument( 16 | '-v', 17 | '--verbose', 18 | action='store_true', 19 | help='Print debugging statements.', 20 | ) 21 | _cmdline_parser.add_argument( 22 | 'spec', 23 | nargs='*', 24 | type=str, 25 | help='Path to API specifications. Each must have a .stone extension.', 26 | ) 27 | 28 | def main(): 29 | """The entry point for the program.""" 30 | 31 | args = _cmdline_parser.parse_args() 32 | verbose = args.verbose 33 | 34 | if args.spec: 35 | specs = args.spec 36 | else: 37 | # If no specs were specified, default to the spec submodule. 38 | specs = glob.glob('spec/*.stone') # Arbitrary sorting 39 | specs.sort() 40 | 41 | specs = [os.path.join(os.getcwd(), s) for s in specs] 42 | 43 | dropbox_pkg_path = os.path.abspath( 44 | os.path.join(os.path.dirname(sys.argv[0]), 'dropbox')) 45 | if verbose: 46 | print('Dropbox package path: %s' % dropbox_pkg_path) 47 | 48 | if verbose: 49 | print('Generating Python types') 50 | subprocess.check_output( 51 | (['python', '-m', 'stone.cli', 'python_types', dropbox_pkg_path] + 52 | specs + ['-a', 'host', '-a', 'style', '-a', 'auth'] + 53 | ['--', '-r', 'dropbox.dropbox_client.Dropbox.{ns}_{route}', '-p', 'dropbox'])) 54 | 55 | if verbose: 56 | print('Generating Python client') 57 | 58 | o = subprocess.check_output( 59 | (['python', '-m', 'stone.cli', 'python_client', dropbox_pkg_path] + 60 | specs + ['-a', 'host', '-a', 'style', '-a', 'auth', '-a', 'scope'] + 61 | [ 62 | '--', 63 | '-w', 'user,app,noauth', 64 | '-m', 'base', 65 | '-c', 'DropboxBase', 66 | '-t', 'dropbox', 67 | '-a', 'scope' 68 | ])) 69 | if o: 70 | print('Output:', o) 71 | 72 | o = subprocess.check_output( 73 | (['python', '-m', 'stone.cli', 'python_client', dropbox_pkg_path] + 74 | specs + ['-a', 'host', '-a', 'style', '-a', 'auth', '-a', 'scope'] + 75 | [ 76 | '--', 77 | '-w', 'team', 78 | '-m', 'base_team', 79 | '-c', 'DropboxTeamBase', 80 | '-t', 'dropbox', 81 | '-a', 'scope' 82 | ])) 83 | if o: 84 | print('Output:', o) 85 | 86 | if __name__ == '__main__': 87 | main() 88 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Dependencies required for installation (keep in sync with setup.py) 2 | requests>=2.16.2 3 | six >= 1.12.0 4 | stone>=2,<3.3.3 5 | # Other dependencies for development 6 | ply 7 | pytest 8 | pytest-runner==5.2.0 9 | sphinx 10 | sphinx_rtd_theme 11 | twine 12 | wheel 13 | -------------------------------------------------------------------------------- /scripts/release_note_generator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git fetch origin --tags 4 | last_version=$(git tag --sort v:refname | tail -n 2 | head -n 1) 5 | echo "Getting commit history since $last_version" 6 | num_commits=$(git rev-list --count $last_version..HEAD) 7 | echo "Found $num_commits commits since last revision" 8 | git_log=$(git log -n $num_commits --pretty="format:* %s %n") 9 | linked_log=$(echo "Release Notes: \n\n$git_log" | sed -e 's/#\([0-9]*\)/[#\1](https:\/\/github.com\/dropbox\/dropbox-sdk-python\/pull\/\1)/g') 10 | echo "\n\n$linked_log" -------------------------------------------------------------------------------- /scripts/update_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # The script is meant for SDK release on Github. You need write permission on https://github.com/dropbox/dropbox-sdk-python to run this script properly. 3 | 4 | if [ -z $1 ]; then 5 | echo "error: $0 needs a version number as argument."; 6 | exit 1 7 | else 8 | set -ex 9 | NEW_VERSION=$1 10 | 11 | git checkout main 12 | git reset --hard HEAD 13 | git checkout -b "tmp-release-${NEW_VERSION}" 14 | 15 | perl -pi -e "s/^__version__ = .*$/__version__ = '$1'/g" dropbox/dropbox_client.py 16 | perl -pi -e 's/(\?branch=)main$/\1\v'$1'/g ;' -e 's/(\?version=)latest$/\1\stable/g ;' -e 's/(\/en\/)latest(\/)$/\1\stable\2/g ;' -e 's/(\[Latest) (Documentation\])$/\1 Release \2/g ;' README.rst 17 | 18 | git add -u 19 | git commit -m "${NEW_VERSION} release" 20 | git tag "v${NEW_VERSION}" -m "${NEW_VERSION} release" 21 | 22 | git checkout main 23 | git branch -D "tmp-release-${NEW_VERSION}" 24 | 25 | git push origin 26 | git push origin --tags 27 | fi 28 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # See http://doc.pytest.org/en/latest/goodpractices.html#integrating-with-setuptools-python-setup-py-test-pytest-runner 2 | [aliases] 3 | test=pytest 4 | 5 | [metadata] 6 | description_file=README.rst 7 | 8 | [tool:pytest] 9 | norecursedirs = .tox .venv .venv-* stone 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Don't import unicode_literals because of a bug in py2 setuptools 2 | # where package_data is expected to be str and not unicode. 3 | from __future__ import absolute_import, division, print_function 4 | 5 | import codecs 6 | import os 7 | import sys 8 | 9 | # Ensure setuptools is available 10 | try: 11 | from ez_setup import use_setuptools 12 | use_setuptools() 13 | except ImportError: 14 | # Try to use ez_setup, but if not, continue anyway. The import is known 15 | # to fail when installing from a tar.gz. 16 | print('Could not import ez_setup', file=sys.stderr) 17 | 18 | from setuptools import setup 19 | 20 | dbx_mod_path = os.path.join(os.path.dirname(__file__), 'dropbox/dropbox_client.py') 21 | line = '= "UNKNOWN"' 22 | for line in open(dbx_mod_path): 23 | if line.startswith('__version__'): 24 | break 25 | version = eval(line.split('=', 1)[1].strip()) # pylint: disable=eval-used 26 | 27 | install_reqs = [ 28 | 'requests>=2.16.2', 29 | 'six >= 1.12.0', 30 | 'stone>=2,<3.3.3', 31 | ] 32 | 33 | setup_requires = [ 34 | # Pin pytest-runner to 5.2.0, since 5.3.0 uses `find_namespaces` directive, not supported in 35 | # Python 2.7 36 | 'pytest-runner==5.2.0', 37 | ] 38 | 39 | # WARNING: This imposes limitations on test/requirements.txt such that the 40 | # full Pip syntax is not supported. See also 41 | # . 42 | test_reqs = [] 43 | with open('test/requirements.txt') as f: 44 | test_reqs += f.read().splitlines() 45 | 46 | with codecs.open('README.rst', encoding='utf-8') as f: 47 | README = f.read() 48 | 49 | dist = setup( 50 | name='dropbox', 51 | version=version, 52 | install_requires=install_reqs, 53 | setup_requires=setup_requires, 54 | tests_require=test_reqs, 55 | packages=['dropbox'], 56 | zip_safe=False, 57 | author_email='dev-platform@dropbox.com', 58 | author='Dropbox', 59 | description='Official Dropbox API Client', 60 | license='MIT License', 61 | long_description=README, 62 | url='http://www.dropbox.com/developers', 63 | project_urls={ 64 | 'Source': 'https://github.com/dropbox/dropbox-sdk-python', 65 | }, 66 | # From 67 | classifiers=[ 68 | 'Development Status :: 5 - Production/Stable', 69 | 'Intended Audience :: Developers', 70 | 'License :: OSI Approved :: MIT License', 71 | 'Operating System :: OS Independent', 72 | 'Programming Language :: Python', 73 | 'Programming Language :: Python :: 2.7', 74 | 'Programming Language :: Python :: 3.4', 75 | 'Programming Language :: Python :: 3.5', 76 | 'Programming Language :: Python :: 3.6', 77 | 'Programming Language :: Python :: 3.7', 78 | 'Programming Language :: Python :: 3.8', 79 | 'Programming Language :: Python :: Implementation :: CPython', 80 | 'Programming Language :: Python :: Implementation :: PyPy', 81 | 'Topic :: Software Development :: Libraries :: Python Modules', 82 | ], 83 | ) 84 | -------------------------------------------------------------------------------- /test/fixtures/Costa Rican Frog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropbox/dropbox-sdk-python/be4a41c7e7e88aa010784d57da065a25091efb0e/test/fixtures/Costa Rican Frog.jpg -------------------------------------------------------------------------------- /test/fixtures/dropbox_song.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropbox/dropbox-sdk-python/be4a41c7e7e88aa010784d57da065a25091efb0e/test/fixtures/dropbox_song.mp3 -------------------------------------------------------------------------------- /test/fixtures/foo.txt: -------------------------------------------------------------------------------- 1 | abcdefghijklmnopqrstuvwxyz 2 | -------------------------------------------------------------------------------- /test/integration/expired-certs.crt: -------------------------------------------------------------------------------- 1 | # GeoTrust Global CA.pem 2 | # Certificate: 3 | # Data: 4 | # Version: 3 (0x2) 5 | # Serial Number: 144470 (0x23456) 6 | # Signature Algorithm: sha1WithRSAEncryption 7 | # Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA 8 | # Validity 9 | # Not Before: May 21 04:00:00 2002 GMT 10 | # Not After : May 21 04:00:00 2022 GMT 11 | # Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA 12 | # Subject Public Key Info: 13 | # Public Key Algorithm: rsaEncryption 14 | # Public-Key: (2048 bit) 15 | # Modulus: 16 | # 00:da:cc:18:63:30:fd:f4:17:23:1a:56:7e:5b:df: 17 | # 3c:6c:38:e4:71:b7:78:91:d4:bc:a1:d8:4c:f8:a8: 18 | # 43:b6:03:e9:4d:21:07:08:88:da:58:2f:66:39:29: 19 | # bd:05:78:8b:9d:38:e8:05:b7:6a:7e:71:a4:e6:c4: 20 | # 60:a6:b0:ef:80:e4:89:28:0f:9e:25:d6:ed:83:f3: 21 | # ad:a6:91:c7:98:c9:42:18:35:14:9d:ad:98:46:92: 22 | # 2e:4f:ca:f1:87:43:c1:16:95:57:2d:50:ef:89:2d: 23 | # 80:7a:57:ad:f2:ee:5f:6b:d2:00:8d:b9:14:f8:14: 24 | # 15:35:d9:c0:46:a3:7b:72:c8:91:bf:c9:55:2b:cd: 25 | # d0:97:3e:9c:26:64:cc:df:ce:83:19:71:ca:4e:e6: 26 | # d4:d5:7b:a9:19:cd:55:de:c8:ec:d2:5e:38:53:e5: 27 | # 5c:4f:8c:2d:fe:50:23:36:fc:66:e6:cb:8e:a4:39: 28 | # 19:00:b7:95:02:39:91:0b:0e:fe:38:2e:d1:1d:05: 29 | # 9a:f6:4d:3e:6f:0f:07:1d:af:2c:1e:8f:60:39:e2: 30 | # fa:36:53:13:39:d4:5e:26:2b:db:3d:a8:14:bd:32: 31 | # eb:18:03:28:52:04:71:e5:ab:33:3d:e1:38:bb:07: 32 | # 36:84:62:9c:79:ea:16:30:f4:5f:c0:2b:e8:71:6b: 33 | # e4:f9 34 | # Exponent: 65537 (0x10001) 35 | # X509v3 extensions: 36 | # X509v3 Basic Constraints: critical 37 | # CA:TRUE 38 | # X509v3 Subject Key Identifier: 39 | # C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E 40 | # X509v3 Authority Key Identifier: 41 | # keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E 42 | # 43 | # Signature Algorithm: sha1WithRSAEncryption 44 | # 35:e3:29:6a:e5:2f:5d:54:8e:29:50:94:9f:99:1a:14:e4:8f: 45 | # 78:2a:62:94:a2:27:67:9e:d0:cf:1a:5e:47:e9:c1:b2:a4:cf: 46 | # dd:41:1a:05:4e:9b:4b:ee:4a:6f:55:52:b3:24:a1:37:0a:eb: 47 | # 64:76:2a:2e:2c:f3:fd:3b:75:90:bf:fa:71:d8:c7:3d:37:d2: 48 | # b5:05:95:62:b9:a6:de:89:3d:36:7b:38:77:48:97:ac:a6:20: 49 | # 8f:2e:a6:c9:0c:c2:b2:99:45:00:c7:ce:11:51:22:22:e0:a5: 50 | # ea:b6:15:48:09:64:ea:5e:4f:74:f7:05:3e:c7:8a:52:0c:db: 51 | # 15:b4:bd:6d:9b:e5:c6:b1:54:68:a9:e3:69:90:b6:9a:a5:0f: 52 | # b8:b9:3f:20:7d:ae:4a:b5:b8:9c:e4:1d:b6:ab:e6:94:a5:c1: 53 | # c7:83:ad:db:f5:27:87:0e:04:6c:d5:ff:dd:a0:5d:ed:87:52: 54 | # b7:2b:15:02:ae:39:a6:6a:74:e9:da:c4:e7:bc:4d:34:1e:a9: 55 | # 5c:4d:33:5f:92:09:2f:88:66:5d:77:97:c7:1d:76:13:a9:d5: 56 | # e5:f1:16:09:11:35:d5:ac:db:24:71:70:2c:98:56:0b:d9:17: 57 | # b4:d1:e3:51:2b:5e:75:e8:d5:d0:dc:4f:34:ed:c2:05:66:80: 58 | # a1:cb:e6:33 59 | -----BEGIN CERTIFICATE----- 60 | MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT 61 | MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i 62 | YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG 63 | EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg 64 | R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 65 | 9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq 66 | fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv 67 | iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU 68 | 1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ 69 | bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW 70 | MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA 71 | ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l 72 | uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn 73 | Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS 74 | tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF 75 | PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un 76 | hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV 77 | 5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== 78 | -----END CERTIFICATE----- 79 | -------------------------------------------------------------------------------- /test/integration/test_dropbox.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import absolute_import, division, print_function, unicode_literals 4 | 5 | import datetime 6 | import os 7 | import random 8 | import re 9 | import string 10 | import sys 11 | import pytest 12 | 13 | try: 14 | from io import BytesIO 15 | except ImportError: 16 | from StringIO import StringIO as BytesIO 17 | 18 | from dropbox import ( 19 | create_session, 20 | Dropbox, 21 | DropboxOAuth2Flow, 22 | DropboxTeam, 23 | session, 24 | stone_serializers, 25 | ) 26 | from dropbox.dropbox_client import PATH_ROOT_HEADER, SELECT_USER_HEADER 27 | from dropbox.exceptions import ( 28 | ApiError, 29 | AuthError, 30 | # BadInputError, 31 | PathRootError, 32 | ) 33 | from dropbox.files import ( 34 | DeleteResult, 35 | ListFolderError, 36 | PathOrLink, 37 | SharedLinkFileInfo, 38 | ) 39 | from dropbox.common import ( 40 | PathRoot, 41 | PathRoot_validator, 42 | ) 43 | from dropbox.session import SSLError 44 | 45 | # Key Types 46 | REFRESH_TOKEN_KEY = "REFRESH_TOKEN" 47 | ACCESS_TOKEN_KEY = "DROPBOX_TOKEN" 48 | CLIENT_ID_KEY = "CLIENT_ID" 49 | CLIENT_SECRET_KEY = "CLIENT_SECRET" 50 | # App Types 51 | SCOPED_KEY = "SCOPED" 52 | LEGACY_KEY = "LEGACY" 53 | # User Types 54 | USER_KEY = "USER" 55 | TEAM_KEY = "TEAM" 56 | # Misc types 57 | SHARED_LINK_KEY = "DROPBOX_SHARED_LINK" 58 | 59 | def format_env_name(app_type=SCOPED_KEY, user_type=USER_KEY, key_type=ACCESS_TOKEN_KEY): 60 | return '{}_{}_{}'.format(app_type, user_type, key_type) 61 | 62 | def _value_from_env_or_die(env_name): 63 | value = os.environ.get(env_name) 64 | if value is None: 65 | print('Set {} environment variable to a valid value.'.format(env_name), 66 | file=sys.stderr) 67 | sys.exit(1) 68 | return value 69 | 70 | _TRUSTED_CERTS_FILE = os.path.join(os.path.dirname(__file__), "trusted-certs.crt") 71 | _EXPIRED_CERTS_FILE = os.path.join(os.path.dirname(__file__), "expired-certs.crt") 72 | 73 | # enables testing both with and without a manually-provided CA bundle 74 | @pytest.fixture(params=[None, _TRUSTED_CERTS_FILE], ids=["no-pinning", "pinning"]) 75 | def dbx_session(request): 76 | return create_session(ca_certs=request.param) 77 | 78 | 79 | @pytest.fixture() 80 | def dbx_from_env(dbx_session): 81 | oauth2_token = _value_from_env_or_die(format_env_name()) 82 | return Dropbox(oauth2_token, session=dbx_session) 83 | 84 | 85 | @pytest.fixture() 86 | def refresh_dbx_from_env(dbx_session): 87 | refresh_token = _value_from_env_or_die(format_env_name(SCOPED_KEY, USER_KEY, REFRESH_TOKEN_KEY)) 88 | app_key = _value_from_env_or_die(format_env_name(SCOPED_KEY, USER_KEY, CLIENT_ID_KEY)) 89 | app_secret = _value_from_env_or_die(format_env_name(SCOPED_KEY, USER_KEY, CLIENT_SECRET_KEY)) 90 | return Dropbox(oauth2_refresh_token=refresh_token, 91 | app_key=app_key, app_secret=app_secret, 92 | session=dbx_session) 93 | 94 | 95 | @pytest.fixture() 96 | def dbx_team_from_env(dbx_session): 97 | team_oauth2_token = _value_from_env_or_die( 98 | format_env_name(SCOPED_KEY, TEAM_KEY, ACCESS_TOKEN_KEY)) 99 | return DropboxTeam(team_oauth2_token, session=dbx_session) 100 | 101 | 102 | @pytest.fixture() 103 | def dbx_app_auth_from_env(dbx_session): 104 | app_key = _value_from_env_or_die(format_env_name(SCOPED_KEY, USER_KEY, CLIENT_ID_KEY)) 105 | app_secret = _value_from_env_or_die(format_env_name(SCOPED_KEY, USER_KEY, CLIENT_SECRET_KEY)) 106 | return Dropbox(app_key=app_key, app_secret=app_secret, session=dbx_session) 107 | 108 | 109 | @pytest.fixture() 110 | def dbx_share_url_from_env(): 111 | return _value_from_env_or_die(SHARED_LINK_KEY) 112 | 113 | 114 | MALFORMED_TOKEN = 'asdf' 115 | INVALID_TOKEN = 'z' * 62 116 | 117 | # Need bytes type for Python3 118 | DUMMY_PAYLOAD = string.ascii_letters.encode('ascii') 119 | 120 | RANDOM_FOLDER = random.sample(string.ascii_letters, 15) 121 | TIMESTAMP = str(datetime.datetime.utcnow()) 122 | STATIC_FILE = "/test.txt" 123 | 124 | @pytest.fixture(scope='module') 125 | def pytest_setup(): 126 | print("Setup") 127 | dbx = Dropbox(_value_from_env_or_die(format_env_name())) 128 | 129 | try: 130 | dbx.files_delete(STATIC_FILE) 131 | except Exception: 132 | print("File not found") 133 | 134 | try: 135 | dbx.files_delete('/Test/%s' % TIMESTAMP) 136 | except Exception: 137 | print("File not found") 138 | 139 | @pytest.mark.usefixtures( 140 | "pytest_setup", 141 | "dbx_from_env", 142 | "refresh_dbx_from_env", 143 | "dbx_app_auth_from_env", 144 | "dbx_share_url_from_env", 145 | ) 146 | class TestDropbox: 147 | def test_multi_auth(self, dbx_from_env, dbx_app_auth_from_env, dbx_share_url_from_env): 148 | # Test for user (with oauth token) 149 | preview_result, resp = dbx_from_env.files_get_thumbnail_v2( 150 | PathOrLink.link(SharedLinkFileInfo(url=dbx_share_url_from_env)) 151 | ) 152 | assert resp.status_code == 200 153 | 154 | # Test for app (with app key and secret) 155 | preview_result, resp = dbx_from_env.files_get_thumbnail_v2( 156 | PathOrLink.link(SharedLinkFileInfo(url=dbx_share_url_from_env)) 157 | ) 158 | assert resp.status_code == 200 159 | 160 | def test_refresh(self, refresh_dbx_from_env): 161 | refresh_dbx_from_env.users_get_current_account() 162 | 163 | def test_app_auth(self, dbx_app_auth_from_env): 164 | dbx_app_auth_from_env.check_app(query="hello world") 165 | 166 | def test_downscope(self, refresh_dbx_from_env): 167 | refresh_dbx_from_env.users_get_current_account() 168 | refresh_dbx_from_env.refresh_access_token(scope=['files.metadata.read']) 169 | with pytest.raises(AuthError): 170 | # Should fail because downscoped to not include needed scope 171 | refresh_dbx_from_env.users_get_current_account() 172 | 173 | def test_rpc(self, dbx_from_env): 174 | dbx_from_env.files_list_folder('') 175 | 176 | # Test API error 177 | random_folder_path = '/' + \ 178 | ''.join(RANDOM_FOLDER) 179 | with pytest.raises(ApiError) as cm: 180 | dbx_from_env.files_list_folder(random_folder_path) 181 | assert isinstance(cm.value.error, ListFolderError) 182 | 183 | def test_upload_download(self, dbx_from_env): 184 | # Upload file 185 | random_filename = ''.join(RANDOM_FOLDER) 186 | random_path = '/Test/%s/%s' % (TIMESTAMP, random_filename) 187 | test_contents = DUMMY_PAYLOAD 188 | dbx_from_env.files_upload(test_contents, random_path) 189 | 190 | # Download file 191 | _, resp = dbx_from_env.files_download(random_path) 192 | assert DUMMY_PAYLOAD == resp.content 193 | 194 | # Cleanup folder 195 | dbx_from_env.files_delete('/Test/%s' % TIMESTAMP) 196 | 197 | def test_bad_upload_types(self, dbx_from_env): 198 | with pytest.raises(TypeError): 199 | dbx_from_env.files_upload(BytesIO(b'test'), '/Test') 200 | 201 | def test_clone_when_user_linked(self, dbx_from_env): 202 | new_dbx = dbx_from_env.clone() 203 | assert dbx_from_env is not new_dbx 204 | assert isinstance(new_dbx, dbx_from_env.__class__) 205 | 206 | def test_with_path_root_constructor(self, dbx_from_env): 207 | # Verify valid mode types 208 | for path_root in ( 209 | PathRoot.home, 210 | PathRoot.root("123"), 211 | PathRoot.namespace_id("123"), 212 | ): 213 | dbx_new = dbx_from_env.with_path_root(path_root) 214 | assert dbx_new is not dbx_from_env 215 | 216 | expected = stone_serializers.json_encode(PathRoot_validator, path_root) 217 | assert dbx_new._headers.get(PATH_ROOT_HEADER) == expected 218 | 219 | # verify invalid mode raises ValueError 220 | with pytest.raises(ValueError): 221 | dbx_from_env.with_path_root(None) 222 | 223 | def test_path_root(self, dbx_from_env): 224 | root_info = dbx_from_env.users_get_current_account().root_info 225 | root_ns = root_info.root_namespace_id 226 | home_ns = root_info.home_namespace_id 227 | 228 | # verify home mode 229 | dbxpr = dbx_from_env.with_path_root(PathRoot.home) 230 | dbxpr.files_list_folder('') 231 | 232 | # verify root mode 233 | dbxpr = dbx_from_env.with_path_root(PathRoot.root(root_ns)) 234 | dbxpr.files_list_folder('') 235 | 236 | # verify namespace_id mode 237 | dbxpr = dbx_from_env.with_path_root(PathRoot.namespace_id(home_ns)) 238 | dbxpr.files_list_folder('') 239 | 240 | def test_path_root_err(self, dbx_from_env): 241 | # verify invalid namespace return is_no_permission error 242 | dbxpr = dbx_from_env.with_path_root(PathRoot.namespace_id("1234567890")) 243 | with pytest.raises(PathRootError) as cm: 244 | dbxpr.files_list_folder('') 245 | assert cm.value.error.is_no_permission() 246 | 247 | dbxpr = dbx_from_env.with_path_root(PathRoot.root("1234567890")) 248 | with pytest.raises(PathRootError) as cm: 249 | dbxpr.files_list_folder('') 250 | assert cm.value.error.is_invalid_root() 251 | 252 | def test_versioned_route(self, dbx_from_env): 253 | # Upload a test file 254 | dbx_from_env.files_upload(DUMMY_PAYLOAD, STATIC_FILE) 255 | 256 | # Delete the file with v2 route 257 | resp = dbx_from_env.files_delete_v2(STATIC_FILE) 258 | # Verify response type is of v2 route 259 | assert isinstance(resp, DeleteResult) 260 | 261 | @pytest.mark.usefixtures( 262 | "pytest_setup", 263 | "dbx_team_from_env", 264 | ) 265 | class TestDropboxTeam: 266 | def test_team(self, dbx_team_from_env): 267 | dbx_team_from_env.team_groups_list() 268 | r = dbx_team_from_env.team_members_list() 269 | if r.members: 270 | # Only test assuming a member if there is a member 271 | team_member_id = r.members[0].profile.team_member_id 272 | dbx_team_from_env.as_user(team_member_id).files_list_folder('') 273 | 274 | def test_as_user(self, dbx_team_from_env): 275 | dbx_as_user = dbx_team_from_env.as_user('1') 276 | path_root = PathRoot.root("123") 277 | 278 | dbx_new = dbx_as_user.with_path_root(path_root) 279 | 280 | assert isinstance(dbx_new, Dropbox) 281 | assert dbx_new._headers.get(SELECT_USER_HEADER) == '1' 282 | 283 | expected = stone_serializers.json_encode(PathRoot_validator, path_root) 284 | assert dbx_new._headers.get(PATH_ROOT_HEADER) == expected 285 | 286 | def test_as_admin(self, dbx_team_from_env): 287 | dbx_as_admin = dbx_team_from_env.as_admin('1') 288 | assert isinstance(dbx_as_admin, Dropbox) 289 | 290 | def test_clone_when_team_linked(self, dbx_team_from_env): 291 | new_dbxt = dbx_team_from_env.clone() 292 | assert dbx_team_from_env is not new_dbxt 293 | assert isinstance(new_dbxt, dbx_team_from_env.__class__) 294 | 295 | def test_default_oauth2_urls(): 296 | flow_obj = DropboxOAuth2Flow('dummy_app_key', 'dummy_app_secret', 297 | 'http://localhost/dummy', 'dummy_session', 'dbx-auth-csrf-token') 298 | 299 | assert re.match( 300 | r'^https://{}/oauth2/authorize\?'.format(re.escape(session.WEB_HOST)), 301 | flow_obj._get_authorize_url('http://localhost/redirect', 'state', 'legacy'), 302 | ) 303 | 304 | assert flow_obj.build_url( 305 | '/oauth2/authorize' 306 | ) == 'https://{}/oauth2/authorize'.format(session.API_HOST) 307 | 308 | assert flow_obj.build_url( 309 | '/oauth2/authorize', host=session.WEB_HOST 310 | ) == 'https://{}/oauth2/authorize'.format(session.WEB_HOST) 311 | 312 | def test_bad_auth(dbx_session): 313 | # Test malformed token 314 | malformed_token_dbx = Dropbox(MALFORMED_TOKEN, session=dbx_session) 315 | # TODO: backend is no longer returning `BadInputError` 316 | # with pytest.raises(BadInputError,) as cm: 317 | # malformed_token_dbx.files_list_folder('') 318 | # assert 'token is malformed' in cm.value.message 319 | with pytest.raises(AuthError): 320 | malformed_token_dbx.files_list_folder('') 321 | 322 | # Test reasonable-looking invalid token 323 | invalid_token_dbx = Dropbox(INVALID_TOKEN, session=dbx_session) 324 | with pytest.raises(AuthError) as cm: 325 | invalid_token_dbx.files_list_folder('') 326 | assert cm.value.error.is_invalid_access_token() 327 | 328 | def test_bad_pins(): 329 | # sanity-check: if we're pinning using expired pins, we should fail w/ an SSL error 330 | _dbx = Dropbox("dummy_token", ca_certs=_EXPIRED_CERTS_FILE) 331 | with pytest.raises(SSLError,): 332 | _dbx.files_list_folder('') 333 | 334 | def test_bad_pins_session(): 335 | _session = create_session(ca_certs=_EXPIRED_CERTS_FILE) 336 | _dbx = Dropbox("dummy_token2", session=_session) 337 | with pytest.raises(SSLError,): 338 | _dbx.files_list_folder('') 339 | -------------------------------------------------------------------------------- /test/integration/trusted-certs.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF 3 | ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 4 | b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL 5 | MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv 6 | b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj 7 | ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM 8 | 9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw 9 | IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 10 | VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L 11 | 93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm 12 | jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC 13 | AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA 14 | A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI 15 | U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs 16 | N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv 17 | o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU 18 | 5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy 19 | rqXRfboQnoZsG4q5WTP468SQvvG5 20 | -----END CERTIFICATE----- 21 | -----BEGIN CERTIFICATE----- 22 | MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF 23 | ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 24 | b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL 25 | MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv 26 | b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK 27 | gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ 28 | W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg 29 | 1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K 30 | 8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r 31 | 2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me 32 | z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR 33 | 8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj 34 | mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz 35 | 7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 36 | +XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI 37 | 0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB 38 | Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm 39 | UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 40 | LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY 41 | +gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS 42 | k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl 43 | 7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm 44 | btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl 45 | urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ 46 | fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 47 | n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE 48 | 76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H 49 | 9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT 50 | 4PsJYGw= 51 | -----END CERTIFICATE----- 52 | -----BEGIN CERTIFICATE----- 53 | MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 54 | MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g 55 | Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG 56 | A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg 57 | Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl 58 | ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j 59 | QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr 60 | ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr 61 | BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM 62 | YyRIHN8wfdVoOw== 63 | -----END CERTIFICATE----- 64 | -----BEGIN CERTIFICATE----- 65 | MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 66 | MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g 67 | Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG 68 | A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg 69 | Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi 70 | 9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk 71 | M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB 72 | /zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB 73 | MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw 74 | CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW 75 | 1KyLa2tJElMzrdfkviT8tQp21KW8EA== 76 | -----END CERTIFICATE----- 77 | -----BEGIN CERTIFICATE----- 78 | MIICETCCAZegAwIBAgIQDfPZN2WjecWVZuqS4iRPNDAKBggqhkjOPQQDAzBKMQsw 79 | CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xIjAgBgNVBAMTGURp 80 | Z2lDZXJ0IEVDQyBQMzg0IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0 81 | MjM1OTU5WjBKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4x 82 | IjAgBgNVBAMTGURpZ2lDZXJ0IEVDQyBQMzg0IFJvb3QgRzUwdjAQBgcqhkjOPQIB 83 | BgUrgQQAIgNiAAT8WR/OmWx/mw62KWNvxoXzCtPWm65XFUwO7V3jCX5tKqOGqrp4 84 | oKdxvUT6CMBKBtZv3SxKOHTl0L3/ev/lOU69vRceH0Ot1bwn2Eu/dowwMqT7+VPl 85 | 2Ko4U12ooDegZwqjQjBAMB0GA1UdDgQWBBSSlvfmutURuvkiLnt+WtnwJeUFGzAO 86 | BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBl 87 | AjEA/cBN8aSn26cMJhH0Sb0HOGMrRGIGeQjHw9TPmz6rOieqkMf9WaK4MlLbyo4X 88 | CwqQAjBdGuxRidRk3PnlHji9Wy7j5UTkOxh61/CVQI/y68/0+dBlokHysOZ8wTYs 89 | j1453Tc= 90 | -----END CERTIFICATE----- 91 | -----BEGIN CERTIFICATE----- 92 | MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh 93 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 94 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD 95 | QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT 96 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j 97 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG 98 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB 99 | CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 100 | nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt 101 | 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P 102 | T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 103 | gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO 104 | BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR 105 | TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw 106 | DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr 107 | hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg 108 | 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF 109 | PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls 110 | YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk 111 | CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= 112 | -----END CERTIFICATE----- 113 | -----BEGIN CERTIFICATE----- 114 | MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh 115 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 116 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH 117 | MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT 118 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j 119 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG 120 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI 121 | 2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx 122 | 1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ 123 | q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz 124 | tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ 125 | vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP 126 | BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV 127 | 5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY 128 | 1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 129 | NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG 130 | Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 131 | 8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe 132 | pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl 133 | MrY= 134 | -----END CERTIFICATE----- 135 | -----BEGIN CERTIFICATE----- 136 | MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw 137 | CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu 138 | ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe 139 | Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw 140 | EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x 141 | IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF 142 | K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG 143 | fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO 144 | Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd 145 | BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx 146 | AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ 147 | oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 148 | sycX 149 | -----END CERTIFICATE----- 150 | -----BEGIN CERTIFICATE----- 151 | MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs 152 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 153 | d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j 154 | ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL 155 | MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 156 | LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug 157 | RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm 158 | +9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW 159 | PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM 160 | xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB 161 | Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 162 | hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg 163 | EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF 164 | MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA 165 | FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec 166 | nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z 167 | eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF 168 | hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 169 | Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe 170 | vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep 171 | +OkuE6N36B9K 172 | -----END CERTIFICATE----- 173 | -----BEGIN CERTIFICATE----- 174 | MIIFXjCCA0agAwIBAgIQCL+ib5o/M2WirPCmOMQBcDANBgkqhkiG9w0BAQwFADBJ 175 | MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xITAfBgNVBAMT 176 | GERpZ2lDZXJ0IFJTQTQwOTYgUm9vdCBHNTAeFw0yMTAxMTUwMDAwMDBaFw00NjAx 177 | MTQyMzU5NTlaMEkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5j 178 | LjEhMB8GA1UEAxMYRGlnaUNlcnQgUlNBNDA5NiBSb290IEc1MIICIjANBgkqhkiG 179 | 9w0BAQEFAAOCAg8AMIICCgKCAgEAqr4NsgZ9JvlH6uQb50JpuJnCue4ksUaQy1kk 180 | UlQ1piTCX5EZyLZC1vNHZZVk54VlZ6mufABP4HgDUK3zf464EeeBYrGL3/JJJgne 181 | Dxa82iibociXL5OQ2iAq44TU/6mesC2/tADemx/IoGNTaIVvTYXGqmP5jbI1dmJ0 182 | A9yTmGgFns2QZd3SejGrJC1tQC6QP2NsLOv6HoBUjXkCkBSztU9O9YgEQ4DDSLMm 183 | L6xRlTJVJS9BlrBWoQg73JgfcoUsd8qYzDj7jnLJbewF7O1NtzxbFFCF3Zf7WfeQ 184 | EvQTv4NNgLIVZRGXYOXWXOYEtVDmcTO2IJOpaAA4zknbtFw7ctdFXFS/zTwBIx58 185 | 1vhpLKUACmwySLTecC06ExfBf2TL8zDtoT2WZ/GUtWBsW2lo9YIzCaK22fOFsm6g 186 | lPDCxH2hLMpz9a7gUpyiZuYDzurf7RjUuWOL9+j/+7Nbj0PFr7d0lFA1Za7WL/GF 187 | j1OhcPSNMl28lsMewgQEnAQPs11+iSDKXicNiUoSI7T2xN3YH/hoszb4HrzG94S2 188 | 6IpOiDA4wCbYcAoJOjQOa4ISlhwv5p6t2HE1gbGMBm70bmb/S0quvfD+11xfU7sy 189 | PM1i0RSgKR8Q3qlyT7GtZOWDKo+L6oSV7pglmJqzcTzBp1DyrEJiMcKhkMbu4reK 190 | qLW2GzsCAwEAAaNCMEAwHQYDVR0OBBYEFGJtt5FPxOqjYmCPoNC+tY8GfGgAMA4G 191 | A1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4IC 192 | AQBh6PsnbdbiuLMJr6rwsYJM/j0XiU0tFZ377tC7hOyEddtDE96Mn8cp74d0yxNw 193 | gVYAdPyu9Nk63iIIUaWgXIJmtntMqdqPq6wcQZZm1p3eVua/TrGyXl/Aw27UwoSQ 194 | 9X2xuhbRKYrInenP0McZOz/P7vfhM65CyJjACJ7zWvPf1Cs7jqgoVhnHTnc8JVTc 195 | uEhI0fknaj7sE6+yBYn9VV/zfY4NnAldLIp+hc744b8RPTKMWtd+PfQzWM+iBZij 196 | s/vOib/9whbdbtyISQ0LoAP/50XpBMHp/aqddfi4H4eD2es501qny5isE4kA/G+V 197 | TuF9EUZt9jhGoxOgLAH1Ys+/HFCRJ3Rdt+xHfNDRdct77tFNIwrDYKV3LYDaZw+O 198 | a3YH8KYP6oSuHnm/CIraCfP07rU289R6Q7qUNeH6wTsblpmkV2PrtaiC9634d9d2 199 | hvN2U1Zb/CZChM6fg5GRr/S+cBWApdjoabHYkVS4GbJi+aL6Ve0Ev7lEhuTP8ZsA 200 | vxEPvrV0JFH/dzRj7EgjDugR63dt2sqCkb6khJNM2qH+zAaE6CHoVLrm0x1jPcJa 201 | /ObJg55yZKmGWQCMwvcTg7bQpDHGrJGOe6QiVhPGdccjvItb/EY9/l1SKa+v6MnD 202 | dkvoq0cC8poN0yyIgAeGwGMPAkyOBFN2uVhCb3wpcF2/Jw== 203 | -----END CERTIFICATE----- 204 | -----BEGIN CERTIFICATE----- 205 | MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw 206 | CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp 207 | Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 208 | MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ 209 | bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG 210 | ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS 211 | 7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp 212 | 0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS 213 | B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 214 | BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ 215 | LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 216 | DXZDjC5Ty3zfDBeWUA== 217 | -----END CERTIFICATE----- 218 | -----BEGIN CERTIFICATE----- 219 | MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN 220 | MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT 221 | HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN 222 | NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs 223 | IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi 224 | MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+ 225 | ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0 226 | 2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp 227 | wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM 228 | pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD 229 | nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po 230 | sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx 231 | Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd 232 | Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX 233 | KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe 234 | XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL 235 | tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv 236 | TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN 237 | AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw 238 | GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H 239 | PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF 240 | O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ 241 | REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik 242 | AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv 243 | /PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+ 244 | p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw 245 | MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF 246 | qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK 247 | ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+ 248 | -----END CERTIFICATE----- 249 | -------------------------------------------------------------------------------- /test/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | mock 3 | pytest-mock 4 | coverage 5 | stone>=2,<3.3.3 6 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | 3 | envlist = py{27,36,37,38,39-dev,py,py3},check,lint,docs,test_unit,coverage 4 | skip_missing_interpreters = true 5 | 6 | [flake8] 7 | 8 | # See 9 | ignore = E128,E301,E302,E305,E402,W503,W504 10 | max-line-length = 100 11 | per-file-ignores = 12 | dropbox/stone_base.py: F401, F403 13 | dropbox/stone_validators.py: F401, F403 14 | dropbox/stone_serializers.py: F401, F403 15 | 16 | 17 | [testenv:test_integration] 18 | 19 | commands = 20 | pytest test/integration/ 21 | 22 | passenv = 23 | DROPBOX_REFRESH_TOKEN 24 | DROPBOX_APP_KEY 25 | DROPBOX_APP_SECRET 26 | DROPBOX_DOMAIN 27 | DROPBOX_TEAM_TOKEN 28 | DROPBOX_TOKEN 29 | DROPBOX_WEB_HOST 30 | DROPBOX_SHARED_LINK 31 | 32 | deps = 33 | pip 34 | -rtest/requirements.txt 35 | 36 | [testenv:check] 37 | 38 | commands = 39 | python setup.py sdist bdist_wheel 40 | twine check dist/* 41 | 42 | deps = 43 | twine 44 | 45 | usedevelop = true 46 | 47 | 48 | [testenv:lint] 49 | description = format the code base to adhere to our styles, and complain about what we cannot do automatically 50 | commands = 51 | flake8 setup.py dropbox example test 52 | deps = 53 | flake8 54 | -rtest/requirements.txt 55 | 56 | usedevelop = true 57 | 58 | [testenv:coverage] 59 | 60 | commands = 61 | coverage run --rcfile=.coveragerc -m pytest test/unit/test_dropbox_unit.py 62 | coverage report 63 | 64 | deps = 65 | -rtest/requirements.txt 66 | 67 | [testenv:codecov] 68 | 69 | commands = 70 | coverage run --rcfile=.coveragerc -m pytest test/unit/test_dropbox_unit.py 71 | coverage xml 72 | 73 | deps = 74 | -rtest/requirements.txt 75 | 76 | [testenv:test_unit] 77 | 78 | commands = 79 | pytest test/unit/ 80 | 81 | deps = 82 | -rtest/requirements.txt 83 | 84 | [testenv:docs] 85 | description = invoke sphinx-build to build the HTML docs 86 | extras = docs 87 | commands = sphinx-build -b html docs build/html 88 | deps = 89 | sphinx 90 | --------------------------------------------------------------------------------