├── .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 |
--------------------------------------------------------------------------------