mirror of
https://github.com/robweber/xbmcbackup.git
synced 2024-11-15 04:45:49 +01:00
Merge branch 'master' into leia
This commit is contained in:
commit
e91037208b
@ -6,7 +6,7 @@
|
|||||||
<import addon="script.module.kodi-six" version="0.1.0"/>
|
<import addon="script.module.kodi-six" version="0.1.0"/>
|
||||||
<import addon="script.module.future" version="0.16.0.4"/>
|
<import addon="script.module.future" version="0.16.0.4"/>
|
||||||
<import addon="script.module.six" version="1.11.0"/>
|
<import addon="script.module.six" version="1.11.0"/>
|
||||||
<import addon="script.module.requests" version="2.9.1" />
|
<import addon="script.module.dropbox" version="8.4.2"/>
|
||||||
</requires>
|
</requires>
|
||||||
<extension point="xbmc.python.script" library="default.py">
|
<extension point="xbmc.python.script" library="default.py">
|
||||||
<provides>executable</provides>
|
<provides>executable</provides>
|
||||||
@ -92,8 +92,10 @@
|
|||||||
<screenshot>resources/images/screenshot3.png</screenshot>
|
<screenshot>resources/images/screenshot3.png</screenshot>
|
||||||
<screenshot>resources/images/screenshot4.png</screenshot>
|
<screenshot>resources/images/screenshot4.png</screenshot>
|
||||||
</assets>
|
</assets>
|
||||||
<news>Version 1.5.1
|
<news>Version 1.5.2
|
||||||
- fix guisettings restores not working - thanks Bluerayx
|
- Added script.module.dropbox import as a dependency for Dropbox filesystem
|
||||||
|
- Fixed issue getting xbmcbackup.val file from non-zipped remote directories. Was being copied as though it was a local file so it was failing.
|
||||||
|
- Use linux path separator (/) all the time, Kodi will interpret this correctly on windows. Was causing issues with remote file systems since os.path.sep
|
||||||
</news>
|
</news>
|
||||||
</extension>
|
</extension>
|
||||||
</addon>
|
</addon>
|
||||||
|
375
changelog.md
Normal file
375
changelog.md
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [Version 1.5.2](https://github.com/robweber/xbmcbackup/compare/krypton-1.5.1...robweber:krypton-1.5.2) - 2019-09-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Updated Changelog format to the one suggested by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||||
|
- Added script.module.dropbox import as a dependency for Dropbox filesystem
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Fixed issue getting xbmcbackup.val file from non-zipped remote directories. Was being copied as though it was a local file so it was failing.
|
||||||
|
- Use linux path separator (/) all the time, Kodi will interpret this correctly on windows. Was causing issues with remote file systems since os.path.sep
|
||||||
|
- Fixed minor python code style changes based on kodi-addon-checker output
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- files releated to dropbox library, using script.module.dropbox import now
|
||||||
|
|
||||||
|
## Version 1.5.1 - 2019-09-10
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Fixed guisettings restores not working - thanks Bluerayx
|
||||||
|
|
||||||
|
## Version 1.5.0 - 2019-08-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added new Advanced file editor and file selection based on a .json
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- File backups and restores will not work with old version - breaking change with previous versions PR117
|
||||||
|
|
||||||
|
## Version 1.1.3 - 2017-12-29
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- added file chunk support for Dropbox uploads
|
||||||
|
- added scheduler delay to assist with time sync (rpi mostly), will delay startup by 2 min
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- fixed settings duplicate ids, thanks aster-anto
|
||||||
|
|
||||||
|
## Version 1.1.2
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Fixes to the Dropbox lib for python 2.6
|
||||||
|
|
||||||
|
## Version 1.1.1
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- added ability to "catchup" on missed scheduled backup
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- fixed error on authorizers (missing secret/key)
|
||||||
|
- updated google oauth and client versions
|
||||||
|
- merged in dropbox v2 library code
|
||||||
|
|
||||||
|
## Version 1.1.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- added tinyurl generation for oauth urls
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- moved authorize to settings area for cloud storage
|
||||||
|
|
||||||
|
## Version 1.0.9
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- fixed dropbox rest.py for Python 2.6 - thanks koying!
|
||||||
|
|
||||||
|
## Version 1.0.8
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- updated dropbox api
|
||||||
|
|
||||||
|
## Version 1.0.7
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- updated google client api version
|
||||||
|
|
||||||
|
## Version 1.0.6
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- added progress for zip extraction - hopefully helps with extract errors
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- fix for custom directories not working recursively
|
||||||
|
|
||||||
|
## Version 1.0.5
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- added google drive support
|
||||||
|
- added settings dialog option - thanks ed_davidson
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- make compression setting compatible with python 2.6 and above
|
||||||
|
- fix for growing backups - thanks brokeh
|
||||||
|
|
||||||
|
## Version 1.0.4
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- exit if we can't delete the old archive, non recoverable
|
||||||
|
|
||||||
|
## Version 1.0.3
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- added "delete auth" dialog to delete oauth files in settings
|
||||||
|
|
||||||
|
## Version 1.0.2
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- updated xbmc.python version to 2.19.0 - should be helix only
|
||||||
|
|
||||||
|
## Version 1.0.0
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- rebranded as "Backup"
|
||||||
|
- removed XBMC references and replaced with Kodi
|
||||||
|
- tweaked file walking for Helix
|
||||||
|
|
||||||
|
## Version 0.5.9
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- create restored version of guisettings for easy local restoration
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- fixed dropbox unicode error
|
||||||
|
|
||||||
|
## Version 0.5.8.7
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- allow limited updating of guisettings file through json
|
||||||
|
|
||||||
|
## Version 0.5.8.6
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- show notification if some files failed
|
||||||
|
- check if destination is writeable - thanks war59312
|
||||||
|
|
||||||
|
## Version 0.5.8.5
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- added custom library nodes to config backup options - thanks Ned Scott
|
||||||
|
|
||||||
|
## Version 0.5.8.4
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- backup compression should use zip64 as sizes may be over 2GB
|
||||||
|
- need to expand out path -bugfix
|
||||||
|
|
||||||
|
## Version 0.5.8
|
||||||
|
|
||||||
|
- fixes path substitution errors
|
||||||
|
|
||||||
|
## Version 0.5.7
|
||||||
|
|
||||||
|
- added option to compress backups, uses local source for staging the
|
||||||
|
zip before sending to remote
|
||||||
|
|
||||||
|
## Version 0.5.6
|
||||||
|
|
||||||
|
- fix dropbox delete recursion error - thanks durd updated language
|
||||||
|
files
|
||||||
|
|
||||||
|
## Version 0.5.5
|
||||||
|
|
||||||
|
- fix for dropbox errors during repeated file upload attempts
|
||||||
|
|
||||||
|
## Version 0.5.4
|
||||||
|
|
||||||
|
- check xbmc version when doing a restore
|
||||||
|
|
||||||
|
## Version 0.5.3
|
||||||
|
|
||||||
|
- updated python version
|
||||||
|
|
||||||
|
## Version 0.5.2
|
||||||
|
|
||||||
|
- added additional script and window parameters, thanks Samu-rai
|
||||||
|
- critical error in backup rotation
|
||||||
|
- updated progress bar display
|
||||||
|
|
||||||
|
## Version 0.5.1
|
||||||
|
|
||||||
|
- updated for new Gotham xbmc python updates
|
||||||
|
|
||||||
|
## Version 0.5.0
|
||||||
|
|
||||||
|
- New Version for Gotham
|
||||||
|
|
||||||
|
## Version 0.4.6
|
||||||
|
|
||||||
|
- modified backup folder names to include time, also modified display
|
||||||
|
listing
|
||||||
|
|
||||||
|
## Version 0.4.5
|
||||||
|
|
||||||
|
- added version info to logs
|
||||||
|
- added try/catch for unicode errors
|
||||||
|
|
||||||
|
## Version 0.4.4
|
||||||
|
|
||||||
|
- modified the check for invalid file types
|
||||||
|
|
||||||
|
## Version 0.4.3
|
||||||
|
|
||||||
|
- added error message if remote directory is blank
|
||||||
|
- added license tag
|
||||||
|
|
||||||
|
## Version 0.4.2
|
||||||
|
|
||||||
|
- Added support for userdata/profiles folder - thanks TUSSFC
|
||||||
|
|
||||||
|
## Version 0.4.1
|
||||||
|
|
||||||
|
- added encode() around notifications
|
||||||
|
|
||||||
|
## Version 0.4.0
|
||||||
|
|
||||||
|
- fixed settings display error - thanks zer04c
|
||||||
|
|
||||||
|
## Version 0.3.9
|
||||||
|
|
||||||
|
- added "just once" scheduler for one-off type backups
|
||||||
|
- show notification on scheduler
|
||||||
|
- update updated language files from Transifex
|
||||||
|
|
||||||
|
## Version 0.3.8
|
||||||
|
|
||||||
|
- added advancedsettings check on restore. prompts user to restore only this file and restart xbmc to continue. This fixes issues where path substitution was not working during restores - thanks ctrlbru
|
||||||
|
|
||||||
|
## [Version 0.3.7]
|
||||||
|
|
||||||
|
- added optional addon.xml tags
|
||||||
|
- update language files from Transifex
|
||||||
|
|
||||||
|
## Version 0.3.6
|
||||||
|
|
||||||
|
- added up to 2 custom directories, can be toggled on/off
|
||||||
|
- added a check for backup verification before rotation - no more
|
||||||
|
deleting non backup related files
|
||||||
|
- use monitor class for onSettingsChanged method
|
||||||
|
|
||||||
|
## Version 0.3.5
|
||||||
|
|
||||||
|
- test of custom directories - only 1 at the moment
|
||||||
|
|
||||||
|
## Version 0.3.4
|
||||||
|
|
||||||
|
- added ability to take parameters via RunScript() or
|
||||||
|
JSONRPC.Addons.ExecuteAddon()
|
||||||
|
|
||||||
|
## Version 0.3.3
|
||||||
|
|
||||||
|
- updated xbmc python version (2.1.0)
|
||||||
|
|
||||||
|
## Version 0.3.2
|
||||||
|
|
||||||
|
- added settings for user provided Dropbox key and secret
|
||||||
|
|
||||||
|
## Version 0.3.1
|
||||||
|
|
||||||
|
- added try/except for multiple character encodings
|
||||||
|
- remove token.txt file if Dropbox Authorization is revoked
|
||||||
|
- can shutdown xbmc after scheduled backup
|
||||||
|
|
||||||
|
## Version 0.3.0
|
||||||
|
|
||||||
|
- major vfs rewrite
|
||||||
|
- Added Dropbox as storage target
|
||||||
|
- updated gui/removed settings - thanks SFX Group for idea!
|
||||||
|
|
||||||
|
## Version 0.2.3
|
||||||
|
|
||||||
|
- first official frodo build
|
||||||
|
|
||||||
|
## Version 0.2.2
|
||||||
|
|
||||||
|
- fix for backup rotation sort
|
||||||
|
|
||||||
|
## Version 0.2.1
|
||||||
|
|
||||||
|
- added ability to rotate backups, keeping a set number of days
|
||||||
|
|
||||||
|
## Version 0.2.0
|
||||||
|
|
||||||
|
- removed the vfs.py helper library
|
||||||
|
- default.py file now uses xbmcvfs python library exclusively for
|
||||||
|
listing directories and copy operations
|
||||||
|
|
||||||
|
## Version 0.1.7
|
||||||
|
|
||||||
|
- minor bug fixes and translations updates
|
||||||
|
|
||||||
|
## Version 0.1.6
|
||||||
|
|
||||||
|
- merged scheduler branch with master, can now schedule backups on an
|
||||||
|
interval
|
||||||
|
|
||||||
|
## Version 0.1.5
|
||||||
|
|
||||||
|
- pulled xbmcbackup class into separate library
|
||||||
|
|
||||||
|
## Version 0.1.4
|
||||||
|
|
||||||
|
- added more verbose error message for incorrect paths
|
||||||
|
|
||||||
|
## Version 0.1.3
|
||||||
|
|
||||||
|
- backup folder format - thanks zeroram
|
||||||
|
- added German translations - thanks dersphere
|
||||||
|
- removed need for separate verbose logging setting
|
||||||
|
- updated utf-8 encoding for all logging
|
||||||
|
- backup now uses date as folder name, restore allows user to type date
|
||||||
|
of last backup
|
||||||
|
|
||||||
|
## Version 0.1.2
|
||||||
|
|
||||||
|
- added French language translation - thanks mikebzh44
|
||||||
|
- added some utf-8 encoding tags to filenames
|
||||||
|
|
||||||
|
## Version 0.1.1
|
||||||
|
|
||||||
|
- added check for key in vfs.py - Thanks Martijn!
|
||||||
|
|
||||||
|
## Version 0.1.0
|
||||||
|
|
||||||
|
- removed transparency from icon.png
|
||||||
|
|
||||||
|
## Version 0.0.9
|
||||||
|
|
||||||
|
- modified vfs.py again to filter out xsp files (smart playlists).
|
||||||
|
Created running list for these types of compressed files
|
||||||
|
- added enable/disable logging toggle in settings
|
||||||
|
|
||||||
|
## Version 0.0.8
|
||||||
|
|
||||||
|
- modified vfs.py script to exclude handling zip files as directories,
|
||||||
|
added keymap and peripheral data folders in the "config" section
|
||||||
|
|
||||||
|
## Version 0.0.7
|
||||||
|
|
||||||
|
- removed "restore.txt" file and now write file listing to memory list
|
||||||
|
instead
|
||||||
|
|
||||||
|
## Version 0.0.6
|
||||||
|
|
||||||
|
- Added the vfs module created by paddycarey
|
||||||
|
- File Selection is now followed for both backup and restore options
|
||||||
|
|
||||||
|
## Version 0.0.5
|
||||||
|
|
||||||
|
- Added option to manually type a path rather than browse for one (only
|
||||||
|
one used)
|
||||||
|
- Show progress bar right away so you know this is doing something
|
||||||
|
|
||||||
|
## Version 0.0.4
|
||||||
|
|
||||||
|
- Finished code for restore mode.
|
||||||
|
|
||||||
|
## Version 0.0.3
|
||||||
|
|
||||||
|
- Added progress bar and "silent" option for running on startup or as a
|
||||||
|
script
|
||||||
|
|
||||||
|
## Version 0.0.2
|
||||||
|
|
||||||
|
- First version, should backup directories as needed
|
322
changelog.txt
322
changelog.txt
@ -1,322 +0,0 @@
|
|||||||
Version 1.5.1
|
|
||||||
|
|
||||||
fix guisettings restores not working - thanks Bluerayx
|
|
||||||
|
|
||||||
Version 1.5.0
|
|
||||||
|
|
||||||
Overhaul of file selection and restore procedures. Breaking Change with previous versions PR117
|
|
||||||
|
|
||||||
Version 1.1.3
|
|
||||||
|
|
||||||
added file chunk support for dropbox uploads
|
|
||||||
fixed settings duplicate ids, thanks aster-anto
|
|
||||||
added scheduler delay to assist with time sync (rpi mostly)
|
|
||||||
|
|
||||||
Version 1.1.2
|
|
||||||
|
|
||||||
added fixes to the Dropbox lib for python 2.6
|
|
||||||
|
|
||||||
Version 1.1.1
|
|
||||||
|
|
||||||
fixed error on authorizers (missing secret/key)
|
|
||||||
added ability to "catchup" on missed scheduled backup
|
|
||||||
updated google oauth and client versions
|
|
||||||
merged in dropbox v2 code
|
|
||||||
|
|
||||||
Version 1.1.0
|
|
||||||
|
|
||||||
added tinyurl for oauth urls
|
|
||||||
moved authorize to settings area for cloud storage
|
|
||||||
bug fixes
|
|
||||||
|
|
||||||
Version 1.0.9
|
|
||||||
|
|
||||||
fixed dropbox rest.py for Python 2.6 - thanks koying!
|
|
||||||
|
|
||||||
Version 1.0.8
|
|
||||||
|
|
||||||
bug fixes
|
|
||||||
updated dropbox api
|
|
||||||
|
|
||||||
Version 1.0.7
|
|
||||||
|
|
||||||
updated google client api version
|
|
||||||
|
|
||||||
Version 1.0.6
|
|
||||||
|
|
||||||
fix for custom directories not working recursively
|
|
||||||
added progress for zip extraction - hopefully helps with extract errors
|
|
||||||
|
|
||||||
Version 1.0.5
|
|
||||||
|
|
||||||
added google drive support
|
|
||||||
make compression setting compatible with python 2.6 and above
|
|
||||||
added settings dialog option - thanks ed_davidson
|
|
||||||
fix for growing backups - thanks brokeh
|
|
||||||
|
|
||||||
Version 1.0.4
|
|
||||||
|
|
||||||
exit if we can't delete the old archive, non recoverable
|
|
||||||
|
|
||||||
Version 1.0.3
|
|
||||||
|
|
||||||
added "delete auth" dialog to delete oauth files in settings
|
|
||||||
|
|
||||||
Version 1.0.2
|
|
||||||
|
|
||||||
updated xbmc.python version to 2.19.0 - should be helix only
|
|
||||||
|
|
||||||
Version 1.0.0
|
|
||||||
|
|
||||||
rebranded as "Backup"
|
|
||||||
removed XBMC references and replaced with Kodi
|
|
||||||
tweaked file walking for Helix
|
|
||||||
|
|
||||||
Version 0.5.9
|
|
||||||
|
|
||||||
fixed dropbox unicode error
|
|
||||||
create restored version of guisettings for easy local restoration
|
|
||||||
|
|
||||||
Version 0.5.8.7
|
|
||||||
|
|
||||||
allow limited updating of guisettings file through json
|
|
||||||
|
|
||||||
Version 0.5.8.6
|
|
||||||
|
|
||||||
show notification if some files failed
|
|
||||||
check if destination is writeable - thanks war59312
|
|
||||||
|
|
||||||
Version 0.5.8.5
|
|
||||||
|
|
||||||
added custom library nodes to config backup options - thanks Ned Scott
|
|
||||||
|
|
||||||
Version 0.5.8.4
|
|
||||||
|
|
||||||
backup compression should use zip64 as sizes may be over 2GB
|
|
||||||
need to expand out path -bugfix
|
|
||||||
|
|
||||||
Version 0.5.8
|
|
||||||
|
|
||||||
fixes path substitution errors
|
|
||||||
|
|
||||||
Version 0.5.7
|
|
||||||
|
|
||||||
added option to compress backups, uses local source for staging the zip before sending to remote
|
|
||||||
|
|
||||||
Version 0.5.6
|
|
||||||
|
|
||||||
fix dropbox delete recursion error - thanks durd
|
|
||||||
updated language files
|
|
||||||
|
|
||||||
Version 0.5.5
|
|
||||||
|
|
||||||
fix for dropbox errors during repeated file upload attempts
|
|
||||||
|
|
||||||
Version 0.5.4
|
|
||||||
|
|
||||||
check xbmc version when doing a restore
|
|
||||||
|
|
||||||
Version 0.5.3
|
|
||||||
|
|
||||||
updated python version
|
|
||||||
|
|
||||||
Version 0.5.2
|
|
||||||
|
|
||||||
added additional script and window parameters, thanks Samu-rai
|
|
||||||
|
|
||||||
critical error in backup rotation
|
|
||||||
|
|
||||||
updated progress bar display
|
|
||||||
|
|
||||||
Version 0.5.1
|
|
||||||
|
|
||||||
updated for new Gotham xbmc python updates
|
|
||||||
|
|
||||||
Version 0.5.0
|
|
||||||
|
|
||||||
New Version for Gotham
|
|
||||||
|
|
||||||
Version 0.4.6
|
|
||||||
|
|
||||||
modified backup folder names to include time, also modified display listing
|
|
||||||
|
|
||||||
Version 0.4.5
|
|
||||||
|
|
||||||
added version info to logs
|
|
||||||
|
|
||||||
added try/catch for unicode errors
|
|
||||||
|
|
||||||
Version 0.4.4
|
|
||||||
|
|
||||||
modified the check for invalid file types
|
|
||||||
|
|
||||||
Version 0.4.3
|
|
||||||
|
|
||||||
added error message if remote directory is blank
|
|
||||||
|
|
||||||
added license tag
|
|
||||||
|
|
||||||
Version 0.4.2
|
|
||||||
|
|
||||||
Added support for userdata/profiles folder - thanks TUSSFC
|
|
||||||
|
|
||||||
Version 0.4.1
|
|
||||||
|
|
||||||
added encode() around notifications
|
|
||||||
|
|
||||||
Version 0.4.0
|
|
||||||
|
|
||||||
fixed settings display error - thanks zer04c
|
|
||||||
|
|
||||||
Version 0.3.9
|
|
||||||
|
|
||||||
added "just once" scheduler for one-off type backups
|
|
||||||
show notification on scheduler update
|
|
||||||
updated language files from Transifex
|
|
||||||
|
|
||||||
Version 0.3.8
|
|
||||||
|
|
||||||
added advancedsettings check on restore. prompts user to restore only this file and restart xbmc to continue. This fixes issues where path substitution was not working during restores - thanks ctrlbru
|
|
||||||
|
|
||||||
Version 0.3.7
|
|
||||||
|
|
||||||
added optional addon.xml tags
|
|
||||||
|
|
||||||
update language files from Transifex
|
|
||||||
|
|
||||||
Version 0.3.6
|
|
||||||
|
|
||||||
added up to 2 custom directories, can be toggled on/off
|
|
||||||
|
|
||||||
added a check for backup verification before rotation - no more deleting non backup related files
|
|
||||||
|
|
||||||
use monitor class for onSettingsChanged method
|
|
||||||
|
|
||||||
Version 0.3.5
|
|
||||||
|
|
||||||
test of custom directories - only 1 at the moment
|
|
||||||
|
|
||||||
Version 0.3.4
|
|
||||||
|
|
||||||
added ability to take parameters via RunScript() or JSONRPC.Addons.ExecuteAddon()
|
|
||||||
|
|
||||||
Version 0.3.3
|
|
||||||
|
|
||||||
updated xbmc python version (2.1.0)
|
|
||||||
|
|
||||||
Version 0.3.2
|
|
||||||
|
|
||||||
added settings for user provided Dropbox key and secret
|
|
||||||
|
|
||||||
Version 0.3.1
|
|
||||||
|
|
||||||
added try/except for multiple character encodings
|
|
||||||
|
|
||||||
remove token.txt file if Dropbox Authorization is revoked
|
|
||||||
|
|
||||||
can shutdown xbmc after scheduled backup
|
|
||||||
|
|
||||||
Version 0.3.0
|
|
||||||
|
|
||||||
major vfs rewrite
|
|
||||||
|
|
||||||
Added Dropbox as storage target
|
|
||||||
|
|
||||||
updated gui/removed settings - thanks SFX Group for idea!
|
|
||||||
|
|
||||||
Version 0.2.3
|
|
||||||
|
|
||||||
first official frodo build
|
|
||||||
|
|
||||||
Version 0.2.2
|
|
||||||
|
|
||||||
fix for backup rotation sort
|
|
||||||
|
|
||||||
Version 0.2.1
|
|
||||||
|
|
||||||
added ability to rotate backups, keeping a set number of days
|
|
||||||
|
|
||||||
Version 0.2.0
|
|
||||||
|
|
||||||
removed the vfs.py helper library
|
|
||||||
|
|
||||||
default.py file now uses xbmcvfs python library exclusively for listing directories and copy operations
|
|
||||||
|
|
||||||
Version 0.1.7
|
|
||||||
|
|
||||||
minor bug fixes and translations updates
|
|
||||||
|
|
||||||
Version 0.1.6
|
|
||||||
|
|
||||||
merged scheduler branch with master, can now schedule backups on an interval
|
|
||||||
|
|
||||||
Version 0.1.5
|
|
||||||
|
|
||||||
pulled xbmcbackup class into separate library
|
|
||||||
|
|
||||||
Version 0.1.4
|
|
||||||
|
|
||||||
added more verbose error message for incorrect paths
|
|
||||||
|
|
||||||
Version 0.1.3
|
|
||||||
|
|
||||||
backup folder format - thanks zeroram
|
|
||||||
|
|
||||||
added German translations - thanks dersphere
|
|
||||||
|
|
||||||
removed need for separate verbose logging setting
|
|
||||||
|
|
||||||
updated utf-8 encoding for all logging
|
|
||||||
|
|
||||||
backup now uses date as folder name, restore allows user to type date of last backup
|
|
||||||
|
|
||||||
Version 0.1.2
|
|
||||||
|
|
||||||
added French language translation - thanks mikebzh44
|
|
||||||
|
|
||||||
added some utf-8 encoding tags to filenames
|
|
||||||
|
|
||||||
Version 0.1.1
|
|
||||||
|
|
||||||
added check for key in vfs.py - Thanks Martijn!
|
|
||||||
|
|
||||||
Version 0.1.0
|
|
||||||
|
|
||||||
removed transparency from icon.png
|
|
||||||
|
|
||||||
Version 0.0.9
|
|
||||||
|
|
||||||
modified vfs.py again to filter out xsp files (smart playlists). Created running list for these types of compressed files
|
|
||||||
|
|
||||||
added enable/disable logging toggle in settings
|
|
||||||
|
|
||||||
Version 0.0.8
|
|
||||||
|
|
||||||
modified vfs.py script to exclude handling zip files as directories, added keymap and peripheral data folders in the "config" section
|
|
||||||
|
|
||||||
Version 0.0.7
|
|
||||||
|
|
||||||
removed "restore.txt" file and now write file listing to memory list instead
|
|
||||||
|
|
||||||
Version 0.0.6
|
|
||||||
|
|
||||||
Added the vfs module created by paddycarey
|
|
||||||
File Selection is now followed for both backup and restore options
|
|
||||||
|
|
||||||
Version 0.0.5
|
|
||||||
|
|
||||||
Added option to manually type a path rather than browse for one (only one used)
|
|
||||||
Show progress bar right away so you know this is doing something
|
|
||||||
|
|
||||||
Version 0.0.4
|
|
||||||
|
|
||||||
Finished code for restore mode.
|
|
||||||
|
|
||||||
Version 0.0.3
|
|
||||||
|
|
||||||
Added progress bar and "silent" option for running on startup or as a script
|
|
||||||
|
|
||||||
Version 0.0.2
|
|
||||||
|
|
||||||
First version, should backup directories as needed
|
|
||||||
|
|
BIN
resources/images/screenshot1.PNG
Normal file
BIN
resources/images/screenshot1.PNG
Normal file
Binary file not shown.
After Width: | Height: | Size: 125 KiB |
BIN
resources/images/screenshot2.PNG
Normal file
BIN
resources/images/screenshot2.PNG
Normal file
Binary file not shown.
After Width: | Height: | Size: 129 KiB |
BIN
resources/images/screenshot3.PNG
Normal file
BIN
resources/images/screenshot3.PNG
Normal file
Binary file not shown.
After Width: | Height: | Size: 270 KiB |
BIN
resources/images/screenshot4.PNG
Normal file
BIN
resources/images/screenshot4.PNG
Normal file
Binary file not shown.
After Width: | Height: | Size: 150 KiB |
2
resources/lib/.gitignore
vendored
2
resources/lib/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
*.pyo
|
|
@ -138,7 +138,7 @@ class XbmcBackup:
|
|||||||
selectedDirs = self._readBackupConfig(utils.data_dir() + "/custom_paths.json")
|
selectedDirs = self._readBackupConfig(utils.data_dir() + "/custom_paths.json")
|
||||||
|
|
||||||
#get the set names
|
#get the set names
|
||||||
keys = selectedDirs.keys()
|
keys = list(selectedDirs.keys())
|
||||||
|
|
||||||
#go through the custom sets
|
#go through the custom sets
|
||||||
for aKey in keys:
|
for aKey in keys:
|
||||||
@ -496,6 +496,9 @@ class XbmcBackup:
|
|||||||
result = None
|
result = None
|
||||||
|
|
||||||
#copy the file and open it
|
#copy the file and open it
|
||||||
|
if(isinstance(self.remote_vfs,DropboxFileSystem) or isinstance(self.remote_vfs,GoogleDriveFilesystem)):
|
||||||
|
self.remote_vfs.get_file(path + "xbmcbackup.val", xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
|
||||||
|
else:
|
||||||
self.xbmc_vfs.put(path + "xbmcbackup.val",xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
|
self.xbmc_vfs.put(path + "xbmcbackup.val",xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"))
|
||||||
|
|
||||||
vFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"),'r')
|
vFile = xbmcvfs.File(xbmc.translatePath(utils.data_dir() + "xbmcbackup_restore.val"),'r')
|
||||||
@ -535,6 +538,7 @@ class FileManager:
|
|||||||
not_dir = ['.zip','.xsp','.rar']
|
not_dir = ['.zip','.xsp','.rar']
|
||||||
exclude_dir = []
|
exclude_dir = []
|
||||||
root_dirs = []
|
root_dirs = []
|
||||||
|
pathSep = '/'
|
||||||
|
|
||||||
def __init__(self,vfs):
|
def __init__(self,vfs):
|
||||||
self.vfs = vfs
|
self.vfs = vfs
|
||||||
@ -553,13 +557,13 @@ class FileManager:
|
|||||||
if(directory[-1:] == '/' or directory[-1:] == '\\'):
|
if(directory[-1:] == '/' or directory[-1:] == '\\'):
|
||||||
directory = directory[:-1]
|
directory = directory[:-1]
|
||||||
|
|
||||||
if(self.vfs.exists(directory + os.path.sep)):
|
if(self.vfs.exists(directory + self.pathSep)):
|
||||||
dirs,files = self.vfs.listdir(directory)
|
dirs,files = self.vfs.listdir(directory)
|
||||||
|
|
||||||
if(recurse):
|
if(recurse):
|
||||||
#create all the subdirs first
|
#create all the subdirs first
|
||||||
for aDir in dirs:
|
for aDir in dirs:
|
||||||
dirPath = xbmc.validatePath(xbmc.translatePath(directory + os.path.sep + aDir))
|
dirPath = xbmc.validatePath(xbmc.translatePath(directory + self.pathSep + aDir))
|
||||||
file_ext = aDir.split('.')[-1]
|
file_ext = aDir.split('.')[-1]
|
||||||
|
|
||||||
#check if directory is excluded
|
#check if directory is excluded
|
||||||
@ -579,7 +583,7 @@ class FileManager:
|
|||||||
|
|
||||||
#copy all the files
|
#copy all the files
|
||||||
for aFile in files:
|
for aFile in files:
|
||||||
filePath = xbmc.translatePath(directory + os.path.sep + aFile)
|
filePath = xbmc.translatePath(directory + self.pathSep + aFile)
|
||||||
self.addFile(filePath)
|
self.addFile(filePath)
|
||||||
|
|
||||||
def addDir(self,dirMeta):
|
def addDir(self,dirMeta):
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
from .dropbox import __version__, Dropbox, DropboxTeam, create_session # noqa: F401
|
|
||||||
from .oauth import DropboxOAuth2Flow, DropboxOAuth2FlowNoRedirect # noqa: F401
|
|
@ -1,7 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Auto-generated by Stone, do not modify.
|
|
||||||
# @generated
|
|
||||||
# flake8: noqa
|
|
||||||
# pylint: skip-file
|
|
||||||
# If you have issues importing this module because Python recognizes it as a keyword, use async_ instead.
|
|
||||||
from .async_ import *
|
|
@ -1,862 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Auto-generated by Stone, do not modify.
|
|
||||||
# @generated
|
|
||||||
# flake8: noqa
|
|
||||||
# pylint: skip-file
|
|
||||||
try:
|
|
||||||
from . import stone_validators as bv
|
|
||||||
from . import stone_base as bb
|
|
||||||
except (ImportError, SystemError, ValueError):
|
|
||||||
# Catch errors raised when importing a relative module when not in a package.
|
|
||||||
# This makes testing this file directly (outside of a package) easier.
|
|
||||||
import stone_validators as bv
|
|
||||||
import stone_base as bb
|
|
||||||
|
|
||||||
class AccessError(bb.Union):
|
|
||||||
"""
|
|
||||||
Error occurred because the account doesn't have permission to access the
|
|
||||||
resource.
|
|
||||||
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar InvalidAccountTypeError AccessError.invalid_account_type: Current
|
|
||||||
account type cannot access the resource.
|
|
||||||
:ivar PaperAccessError AccessError.paper_access_denied: Current account
|
|
||||||
cannot access Paper.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_catch_all = 'other'
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
other = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def invalid_account_type(cls, val):
|
|
||||||
"""
|
|
||||||
Create an instance of this class set to the ``invalid_account_type`` tag
|
|
||||||
with value ``val``.
|
|
||||||
|
|
||||||
:param InvalidAccountTypeError val:
|
|
||||||
:rtype: AccessError
|
|
||||||
"""
|
|
||||||
return cls('invalid_account_type', val)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def paper_access_denied(cls, val):
|
|
||||||
"""
|
|
||||||
Create an instance of this class set to the ``paper_access_denied`` tag
|
|
||||||
with value ``val``.
|
|
||||||
|
|
||||||
:param PaperAccessError val:
|
|
||||||
:rtype: AccessError
|
|
||||||
"""
|
|
||||||
return cls('paper_access_denied', val)
|
|
||||||
|
|
||||||
def is_invalid_account_type(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``invalid_account_type``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'invalid_account_type'
|
|
||||||
|
|
||||||
def is_paper_access_denied(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``paper_access_denied``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'paper_access_denied'
|
|
||||||
|
|
||||||
def is_other(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``other``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'other'
|
|
||||||
|
|
||||||
def get_invalid_account_type(self):
|
|
||||||
"""
|
|
||||||
Current account type cannot access the resource.
|
|
||||||
|
|
||||||
Only call this if :meth:`is_invalid_account_type` is true.
|
|
||||||
|
|
||||||
:rtype: InvalidAccountTypeError
|
|
||||||
"""
|
|
||||||
if not self.is_invalid_account_type():
|
|
||||||
raise AttributeError("tag 'invalid_account_type' not set")
|
|
||||||
return self._value
|
|
||||||
|
|
||||||
def get_paper_access_denied(self):
|
|
||||||
"""
|
|
||||||
Current account cannot access Paper.
|
|
||||||
|
|
||||||
Only call this if :meth:`is_paper_access_denied` is true.
|
|
||||||
|
|
||||||
:rtype: PaperAccessError
|
|
||||||
"""
|
|
||||||
if not self.is_paper_access_denied():
|
|
||||||
raise AttributeError("tag 'paper_access_denied' not set")
|
|
||||||
return self._value
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(AccessError, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'AccessError(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
AccessError_validator = bv.Union(AccessError)
|
|
||||||
|
|
||||||
class AuthError(bb.Union):
|
|
||||||
"""
|
|
||||||
Errors occurred during authentication.
|
|
||||||
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar auth.AuthError.invalid_access_token: The access token is invalid.
|
|
||||||
:ivar auth.AuthError.invalid_select_user: The user specified in
|
|
||||||
'Dropbox-API-Select-User' is no longer on the team.
|
|
||||||
:ivar auth.AuthError.invalid_select_admin: The user specified in
|
|
||||||
'Dropbox-API-Select-Admin' is not a Dropbox Business team admin.
|
|
||||||
:ivar auth.AuthError.user_suspended: The user has been suspended.
|
|
||||||
:ivar auth.AuthError.expired_access_token: The access token has expired.
|
|
||||||
:ivar TokenScopeError AuthError.missing_scope: The access token does not
|
|
||||||
have the required scope to access the route.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_catch_all = 'other'
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
invalid_access_token = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
invalid_select_user = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
invalid_select_admin = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
user_suspended = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
expired_access_token = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
other = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def missing_scope(cls, val):
|
|
||||||
"""
|
|
||||||
Create an instance of this class set to the ``missing_scope`` tag with
|
|
||||||
value ``val``.
|
|
||||||
|
|
||||||
:param TokenScopeError val:
|
|
||||||
:rtype: AuthError
|
|
||||||
"""
|
|
||||||
return cls('missing_scope', val)
|
|
||||||
|
|
||||||
def is_invalid_access_token(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``invalid_access_token``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'invalid_access_token'
|
|
||||||
|
|
||||||
def is_invalid_select_user(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``invalid_select_user``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'invalid_select_user'
|
|
||||||
|
|
||||||
def is_invalid_select_admin(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``invalid_select_admin``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'invalid_select_admin'
|
|
||||||
|
|
||||||
def is_user_suspended(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``user_suspended``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'user_suspended'
|
|
||||||
|
|
||||||
def is_expired_access_token(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``expired_access_token``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'expired_access_token'
|
|
||||||
|
|
||||||
def is_missing_scope(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``missing_scope``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'missing_scope'
|
|
||||||
|
|
||||||
def is_other(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``other``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'other'
|
|
||||||
|
|
||||||
def get_missing_scope(self):
|
|
||||||
"""
|
|
||||||
The access token does not have the required scope to access the route.
|
|
||||||
|
|
||||||
Only call this if :meth:`is_missing_scope` is true.
|
|
||||||
|
|
||||||
:rtype: TokenScopeError
|
|
||||||
"""
|
|
||||||
if not self.is_missing_scope():
|
|
||||||
raise AttributeError("tag 'missing_scope' not set")
|
|
||||||
return self._value
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(AuthError, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'AuthError(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
AuthError_validator = bv.Union(AuthError)
|
|
||||||
|
|
||||||
class InvalidAccountTypeError(bb.Union):
|
|
||||||
"""
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar auth.InvalidAccountTypeError.endpoint: Current account type doesn't
|
|
||||||
have permission to access this route endpoint.
|
|
||||||
:ivar auth.InvalidAccountTypeError.feature: Current account type doesn't
|
|
||||||
have permission to access this feature.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_catch_all = 'other'
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
endpoint = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
feature = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
other = None
|
|
||||||
|
|
||||||
def is_endpoint(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``endpoint``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'endpoint'
|
|
||||||
|
|
||||||
def is_feature(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``feature``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'feature'
|
|
||||||
|
|
||||||
def is_other(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``other``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'other'
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(InvalidAccountTypeError, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'InvalidAccountTypeError(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
InvalidAccountTypeError_validator = bv.Union(InvalidAccountTypeError)
|
|
||||||
|
|
||||||
class PaperAccessError(bb.Union):
|
|
||||||
"""
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar auth.PaperAccessError.paper_disabled: Paper is disabled.
|
|
||||||
:ivar auth.PaperAccessError.not_paper_user: The provided user has not used
|
|
||||||
Paper yet.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_catch_all = 'other'
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
paper_disabled = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
not_paper_user = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
other = None
|
|
||||||
|
|
||||||
def is_paper_disabled(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``paper_disabled``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'paper_disabled'
|
|
||||||
|
|
||||||
def is_not_paper_user(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``not_paper_user``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'not_paper_user'
|
|
||||||
|
|
||||||
def is_other(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``other``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'other'
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(PaperAccessError, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'PaperAccessError(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
PaperAccessError_validator = bv.Union(PaperAccessError)
|
|
||||||
|
|
||||||
class RateLimitError(bb.Struct):
|
|
||||||
"""
|
|
||||||
Error occurred because the app is being rate limited.
|
|
||||||
|
|
||||||
:ivar auth.RateLimitError.reason: The reason why the app is being rate
|
|
||||||
limited.
|
|
||||||
:ivar auth.RateLimitError.retry_after: The number of seconds that the app
|
|
||||||
should wait before making another request.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
'_reason_value',
|
|
||||||
'_reason_present',
|
|
||||||
'_retry_after_value',
|
|
||||||
'_retry_after_present',
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
reason=None,
|
|
||||||
retry_after=None):
|
|
||||||
self._reason_value = None
|
|
||||||
self._reason_present = False
|
|
||||||
self._retry_after_value = None
|
|
||||||
self._retry_after_present = False
|
|
||||||
if reason is not None:
|
|
||||||
self.reason = reason
|
|
||||||
if retry_after is not None:
|
|
||||||
self.retry_after = retry_after
|
|
||||||
|
|
||||||
@property
|
|
||||||
def reason(self):
|
|
||||||
"""
|
|
||||||
The reason why the app is being rate limited.
|
|
||||||
|
|
||||||
:rtype: RateLimitReason
|
|
||||||
"""
|
|
||||||
if self._reason_present:
|
|
||||||
return self._reason_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'reason'")
|
|
||||||
|
|
||||||
@reason.setter
|
|
||||||
def reason(self, val):
|
|
||||||
self._reason_validator.validate_type_only(val)
|
|
||||||
self._reason_value = val
|
|
||||||
self._reason_present = True
|
|
||||||
|
|
||||||
@reason.deleter
|
|
||||||
def reason(self):
|
|
||||||
self._reason_value = None
|
|
||||||
self._reason_present = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def retry_after(self):
|
|
||||||
"""
|
|
||||||
The number of seconds that the app should wait before making another
|
|
||||||
request.
|
|
||||||
|
|
||||||
:rtype: int
|
|
||||||
"""
|
|
||||||
if self._retry_after_present:
|
|
||||||
return self._retry_after_value
|
|
||||||
else:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
@retry_after.setter
|
|
||||||
def retry_after(self, val):
|
|
||||||
val = self._retry_after_validator.validate(val)
|
|
||||||
self._retry_after_value = val
|
|
||||||
self._retry_after_present = True
|
|
||||||
|
|
||||||
@retry_after.deleter
|
|
||||||
def retry_after(self):
|
|
||||||
self._retry_after_value = None
|
|
||||||
self._retry_after_present = False
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(RateLimitError, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'RateLimitError(reason={!r}, retry_after={!r})'.format(
|
|
||||||
self._reason_value,
|
|
||||||
self._retry_after_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
RateLimitError_validator = bv.Struct(RateLimitError)
|
|
||||||
|
|
||||||
class RateLimitReason(bb.Union):
|
|
||||||
"""
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar auth.RateLimitReason.too_many_requests: You are making too many
|
|
||||||
requests in the past few minutes.
|
|
||||||
:ivar auth.RateLimitReason.too_many_write_operations: There are currently
|
|
||||||
too many write operations happening in the user's Dropbox.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_catch_all = 'other'
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
too_many_requests = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
too_many_write_operations = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
other = None
|
|
||||||
|
|
||||||
def is_too_many_requests(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``too_many_requests``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'too_many_requests'
|
|
||||||
|
|
||||||
def is_too_many_write_operations(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``too_many_write_operations``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'too_many_write_operations'
|
|
||||||
|
|
||||||
def is_other(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``other``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'other'
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(RateLimitReason, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'RateLimitReason(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
RateLimitReason_validator = bv.Union(RateLimitReason)
|
|
||||||
|
|
||||||
class TokenFromOAuth1Arg(bb.Struct):
|
|
||||||
"""
|
|
||||||
:ivar auth.TokenFromOAuth1Arg.oauth1_token: The supplied OAuth 1.0 access
|
|
||||||
token.
|
|
||||||
:ivar auth.TokenFromOAuth1Arg.oauth1_token_secret: The token secret
|
|
||||||
associated with the supplied access token.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
'_oauth1_token_value',
|
|
||||||
'_oauth1_token_present',
|
|
||||||
'_oauth1_token_secret_value',
|
|
||||||
'_oauth1_token_secret_present',
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
oauth1_token=None,
|
|
||||||
oauth1_token_secret=None):
|
|
||||||
self._oauth1_token_value = None
|
|
||||||
self._oauth1_token_present = False
|
|
||||||
self._oauth1_token_secret_value = None
|
|
||||||
self._oauth1_token_secret_present = False
|
|
||||||
if oauth1_token is not None:
|
|
||||||
self.oauth1_token = oauth1_token
|
|
||||||
if oauth1_token_secret is not None:
|
|
||||||
self.oauth1_token_secret = oauth1_token_secret
|
|
||||||
|
|
||||||
@property
|
|
||||||
def oauth1_token(self):
|
|
||||||
"""
|
|
||||||
The supplied OAuth 1.0 access token.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._oauth1_token_present:
|
|
||||||
return self._oauth1_token_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'oauth1_token'")
|
|
||||||
|
|
||||||
@oauth1_token.setter
|
|
||||||
def oauth1_token(self, val):
|
|
||||||
val = self._oauth1_token_validator.validate(val)
|
|
||||||
self._oauth1_token_value = val
|
|
||||||
self._oauth1_token_present = True
|
|
||||||
|
|
||||||
@oauth1_token.deleter
|
|
||||||
def oauth1_token(self):
|
|
||||||
self._oauth1_token_value = None
|
|
||||||
self._oauth1_token_present = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def oauth1_token_secret(self):
|
|
||||||
"""
|
|
||||||
The token secret associated with the supplied access token.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._oauth1_token_secret_present:
|
|
||||||
return self._oauth1_token_secret_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'oauth1_token_secret'")
|
|
||||||
|
|
||||||
@oauth1_token_secret.setter
|
|
||||||
def oauth1_token_secret(self, val):
|
|
||||||
val = self._oauth1_token_secret_validator.validate(val)
|
|
||||||
self._oauth1_token_secret_value = val
|
|
||||||
self._oauth1_token_secret_present = True
|
|
||||||
|
|
||||||
@oauth1_token_secret.deleter
|
|
||||||
def oauth1_token_secret(self):
|
|
||||||
self._oauth1_token_secret_value = None
|
|
||||||
self._oauth1_token_secret_present = False
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(TokenFromOAuth1Arg, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'TokenFromOAuth1Arg(oauth1_token={!r}, oauth1_token_secret={!r})'.format(
|
|
||||||
self._oauth1_token_value,
|
|
||||||
self._oauth1_token_secret_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
TokenFromOAuth1Arg_validator = bv.Struct(TokenFromOAuth1Arg)
|
|
||||||
|
|
||||||
class TokenFromOAuth1Error(bb.Union):
|
|
||||||
"""
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar auth.TokenFromOAuth1Error.invalid_oauth1_token_info: Part or all of
|
|
||||||
the OAuth 1.0 access token info is invalid.
|
|
||||||
:ivar auth.TokenFromOAuth1Error.app_id_mismatch: The authorized app does not
|
|
||||||
match the app associated with the supplied access token.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_catch_all = 'other'
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
invalid_oauth1_token_info = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
app_id_mismatch = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
other = None
|
|
||||||
|
|
||||||
def is_invalid_oauth1_token_info(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``invalid_oauth1_token_info``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'invalid_oauth1_token_info'
|
|
||||||
|
|
||||||
def is_app_id_mismatch(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``app_id_mismatch``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'app_id_mismatch'
|
|
||||||
|
|
||||||
def is_other(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``other``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'other'
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(TokenFromOAuth1Error, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'TokenFromOAuth1Error(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
TokenFromOAuth1Error_validator = bv.Union(TokenFromOAuth1Error)
|
|
||||||
|
|
||||||
class TokenFromOAuth1Result(bb.Struct):
|
|
||||||
"""
|
|
||||||
:ivar auth.TokenFromOAuth1Result.oauth2_token: The OAuth 2.0 token generated
|
|
||||||
from the supplied OAuth 1.0 token.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
'_oauth2_token_value',
|
|
||||||
'_oauth2_token_present',
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
oauth2_token=None):
|
|
||||||
self._oauth2_token_value = None
|
|
||||||
self._oauth2_token_present = False
|
|
||||||
if oauth2_token is not None:
|
|
||||||
self.oauth2_token = oauth2_token
|
|
||||||
|
|
||||||
@property
|
|
||||||
def oauth2_token(self):
|
|
||||||
"""
|
|
||||||
The OAuth 2.0 token generated from the supplied OAuth 1.0 token.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._oauth2_token_present:
|
|
||||||
return self._oauth2_token_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'oauth2_token'")
|
|
||||||
|
|
||||||
@oauth2_token.setter
|
|
||||||
def oauth2_token(self, val):
|
|
||||||
val = self._oauth2_token_validator.validate(val)
|
|
||||||
self._oauth2_token_value = val
|
|
||||||
self._oauth2_token_present = True
|
|
||||||
|
|
||||||
@oauth2_token.deleter
|
|
||||||
def oauth2_token(self):
|
|
||||||
self._oauth2_token_value = None
|
|
||||||
self._oauth2_token_present = False
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(TokenFromOAuth1Result, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'TokenFromOAuth1Result(oauth2_token={!r})'.format(
|
|
||||||
self._oauth2_token_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
TokenFromOAuth1Result_validator = bv.Struct(TokenFromOAuth1Result)
|
|
||||||
|
|
||||||
class TokenScopeError(bb.Struct):
|
|
||||||
"""
|
|
||||||
:ivar auth.TokenScopeError.required_scope: The required scope to access the
|
|
||||||
route.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
'_required_scope_value',
|
|
||||||
'_required_scope_present',
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
required_scope=None):
|
|
||||||
self._required_scope_value = None
|
|
||||||
self._required_scope_present = False
|
|
||||||
if required_scope is not None:
|
|
||||||
self.required_scope = required_scope
|
|
||||||
|
|
||||||
@property
|
|
||||||
def required_scope(self):
|
|
||||||
"""
|
|
||||||
The required scope to access the route.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._required_scope_present:
|
|
||||||
return self._required_scope_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'required_scope'")
|
|
||||||
|
|
||||||
@required_scope.setter
|
|
||||||
def required_scope(self, val):
|
|
||||||
val = self._required_scope_validator.validate(val)
|
|
||||||
self._required_scope_value = val
|
|
||||||
self._required_scope_present = True
|
|
||||||
|
|
||||||
@required_scope.deleter
|
|
||||||
def required_scope(self):
|
|
||||||
self._required_scope_value = None
|
|
||||||
self._required_scope_present = False
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(TokenScopeError, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'TokenScopeError(required_scope={!r})'.format(
|
|
||||||
self._required_scope_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
TokenScopeError_validator = bv.Struct(TokenScopeError)
|
|
||||||
|
|
||||||
AccessError._invalid_account_type_validator = InvalidAccountTypeError_validator
|
|
||||||
AccessError._paper_access_denied_validator = PaperAccessError_validator
|
|
||||||
AccessError._other_validator = bv.Void()
|
|
||||||
AccessError._tagmap = {
|
|
||||||
'invalid_account_type': AccessError._invalid_account_type_validator,
|
|
||||||
'paper_access_denied': AccessError._paper_access_denied_validator,
|
|
||||||
'other': AccessError._other_validator,
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessError.other = AccessError('other')
|
|
||||||
|
|
||||||
AuthError._invalid_access_token_validator = bv.Void()
|
|
||||||
AuthError._invalid_select_user_validator = bv.Void()
|
|
||||||
AuthError._invalid_select_admin_validator = bv.Void()
|
|
||||||
AuthError._user_suspended_validator = bv.Void()
|
|
||||||
AuthError._expired_access_token_validator = bv.Void()
|
|
||||||
AuthError._missing_scope_validator = TokenScopeError_validator
|
|
||||||
AuthError._other_validator = bv.Void()
|
|
||||||
AuthError._tagmap = {
|
|
||||||
'invalid_access_token': AuthError._invalid_access_token_validator,
|
|
||||||
'invalid_select_user': AuthError._invalid_select_user_validator,
|
|
||||||
'invalid_select_admin': AuthError._invalid_select_admin_validator,
|
|
||||||
'user_suspended': AuthError._user_suspended_validator,
|
|
||||||
'expired_access_token': AuthError._expired_access_token_validator,
|
|
||||||
'missing_scope': AuthError._missing_scope_validator,
|
|
||||||
'other': AuthError._other_validator,
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthError.invalid_access_token = AuthError('invalid_access_token')
|
|
||||||
AuthError.invalid_select_user = AuthError('invalid_select_user')
|
|
||||||
AuthError.invalid_select_admin = AuthError('invalid_select_admin')
|
|
||||||
AuthError.user_suspended = AuthError('user_suspended')
|
|
||||||
AuthError.expired_access_token = AuthError('expired_access_token')
|
|
||||||
AuthError.other = AuthError('other')
|
|
||||||
|
|
||||||
InvalidAccountTypeError._endpoint_validator = bv.Void()
|
|
||||||
InvalidAccountTypeError._feature_validator = bv.Void()
|
|
||||||
InvalidAccountTypeError._other_validator = bv.Void()
|
|
||||||
InvalidAccountTypeError._tagmap = {
|
|
||||||
'endpoint': InvalidAccountTypeError._endpoint_validator,
|
|
||||||
'feature': InvalidAccountTypeError._feature_validator,
|
|
||||||
'other': InvalidAccountTypeError._other_validator,
|
|
||||||
}
|
|
||||||
|
|
||||||
InvalidAccountTypeError.endpoint = InvalidAccountTypeError('endpoint')
|
|
||||||
InvalidAccountTypeError.feature = InvalidAccountTypeError('feature')
|
|
||||||
InvalidAccountTypeError.other = InvalidAccountTypeError('other')
|
|
||||||
|
|
||||||
PaperAccessError._paper_disabled_validator = bv.Void()
|
|
||||||
PaperAccessError._not_paper_user_validator = bv.Void()
|
|
||||||
PaperAccessError._other_validator = bv.Void()
|
|
||||||
PaperAccessError._tagmap = {
|
|
||||||
'paper_disabled': PaperAccessError._paper_disabled_validator,
|
|
||||||
'not_paper_user': PaperAccessError._not_paper_user_validator,
|
|
||||||
'other': PaperAccessError._other_validator,
|
|
||||||
}
|
|
||||||
|
|
||||||
PaperAccessError.paper_disabled = PaperAccessError('paper_disabled')
|
|
||||||
PaperAccessError.not_paper_user = PaperAccessError('not_paper_user')
|
|
||||||
PaperAccessError.other = PaperAccessError('other')
|
|
||||||
|
|
||||||
RateLimitError._reason_validator = RateLimitReason_validator
|
|
||||||
RateLimitError._retry_after_validator = bv.UInt64()
|
|
||||||
RateLimitError._all_field_names_ = set([
|
|
||||||
'reason',
|
|
||||||
'retry_after',
|
|
||||||
])
|
|
||||||
RateLimitError._all_fields_ = [
|
|
||||||
('reason', RateLimitError._reason_validator),
|
|
||||||
('retry_after', RateLimitError._retry_after_validator),
|
|
||||||
]
|
|
||||||
|
|
||||||
RateLimitReason._too_many_requests_validator = bv.Void()
|
|
||||||
RateLimitReason._too_many_write_operations_validator = bv.Void()
|
|
||||||
RateLimitReason._other_validator = bv.Void()
|
|
||||||
RateLimitReason._tagmap = {
|
|
||||||
'too_many_requests': RateLimitReason._too_many_requests_validator,
|
|
||||||
'too_many_write_operations': RateLimitReason._too_many_write_operations_validator,
|
|
||||||
'other': RateLimitReason._other_validator,
|
|
||||||
}
|
|
||||||
|
|
||||||
RateLimitReason.too_many_requests = RateLimitReason('too_many_requests')
|
|
||||||
RateLimitReason.too_many_write_operations = RateLimitReason('too_many_write_operations')
|
|
||||||
RateLimitReason.other = RateLimitReason('other')
|
|
||||||
|
|
||||||
TokenFromOAuth1Arg._oauth1_token_validator = bv.String(min_length=1)
|
|
||||||
TokenFromOAuth1Arg._oauth1_token_secret_validator = bv.String(min_length=1)
|
|
||||||
TokenFromOAuth1Arg._all_field_names_ = set([
|
|
||||||
'oauth1_token',
|
|
||||||
'oauth1_token_secret',
|
|
||||||
])
|
|
||||||
TokenFromOAuth1Arg._all_fields_ = [
|
|
||||||
('oauth1_token', TokenFromOAuth1Arg._oauth1_token_validator),
|
|
||||||
('oauth1_token_secret', TokenFromOAuth1Arg._oauth1_token_secret_validator),
|
|
||||||
]
|
|
||||||
|
|
||||||
TokenFromOAuth1Error._invalid_oauth1_token_info_validator = bv.Void()
|
|
||||||
TokenFromOAuth1Error._app_id_mismatch_validator = bv.Void()
|
|
||||||
TokenFromOAuth1Error._other_validator = bv.Void()
|
|
||||||
TokenFromOAuth1Error._tagmap = {
|
|
||||||
'invalid_oauth1_token_info': TokenFromOAuth1Error._invalid_oauth1_token_info_validator,
|
|
||||||
'app_id_mismatch': TokenFromOAuth1Error._app_id_mismatch_validator,
|
|
||||||
'other': TokenFromOAuth1Error._other_validator,
|
|
||||||
}
|
|
||||||
|
|
||||||
TokenFromOAuth1Error.invalid_oauth1_token_info = TokenFromOAuth1Error('invalid_oauth1_token_info')
|
|
||||||
TokenFromOAuth1Error.app_id_mismatch = TokenFromOAuth1Error('app_id_mismatch')
|
|
||||||
TokenFromOAuth1Error.other = TokenFromOAuth1Error('other')
|
|
||||||
|
|
||||||
TokenFromOAuth1Result._oauth2_token_validator = bv.String(min_length=1)
|
|
||||||
TokenFromOAuth1Result._all_field_names_ = set(['oauth2_token'])
|
|
||||||
TokenFromOAuth1Result._all_fields_ = [('oauth2_token', TokenFromOAuth1Result._oauth2_token_validator)]
|
|
||||||
|
|
||||||
TokenScopeError._required_scope_validator = bv.String()
|
|
||||||
TokenScopeError._all_field_names_ = set(['required_scope'])
|
|
||||||
TokenScopeError._all_fields_ = [('required_scope', TokenScopeError._required_scope_validator)]
|
|
||||||
|
|
||||||
token_from_oauth1 = bb.Route(
|
|
||||||
'token/from_oauth1',
|
|
||||||
1,
|
|
||||||
False,
|
|
||||||
TokenFromOAuth1Arg_validator,
|
|
||||||
TokenFromOAuth1Result_validator,
|
|
||||||
TokenFromOAuth1Error_validator,
|
|
||||||
{'host': u'api',
|
|
||||||
'style': u'rpc'},
|
|
||||||
)
|
|
||||||
token_revoke = bb.Route(
|
|
||||||
'token/revoke',
|
|
||||||
1,
|
|
||||||
False,
|
|
||||||
bv.Void(),
|
|
||||||
bv.Void(),
|
|
||||||
bv.Void(),
|
|
||||||
{'host': u'api',
|
|
||||||
'style': u'rpc'},
|
|
||||||
)
|
|
||||||
|
|
||||||
ROUTES = {
|
|
||||||
'token/from_oauth1': token_from_oauth1,
|
|
||||||
'token/revoke': token_revoke,
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,458 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Auto-generated by Stone, do not modify.
|
|
||||||
# @generated
|
|
||||||
# flake8: noqa
|
|
||||||
# pylint: skip-file
|
|
||||||
try:
|
|
||||||
from . import stone_validators as bv
|
|
||||||
from . import stone_base as bb
|
|
||||||
except (ImportError, SystemError, ValueError):
|
|
||||||
# Catch errors raised when importing a relative module when not in a package.
|
|
||||||
# This makes testing this file directly (outside of a package) easier.
|
|
||||||
import stone_validators as bv
|
|
||||||
import stone_base as bb
|
|
||||||
|
|
||||||
class PathRoot(bb.Union):
|
|
||||||
"""
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar common.PathRoot.home: Paths are relative to the authenticating user's
|
|
||||||
home namespace, whether or not that user belongs to a team.
|
|
||||||
:ivar str common.PathRoot.root: Paths are relative to the authenticating
|
|
||||||
user's root namespace (This results in
|
|
||||||
:field:`PathRootError.invalid_root` if the user's root namespace has
|
|
||||||
changed.).
|
|
||||||
:ivar str common.PathRoot.namespace_id: Paths are relative to given
|
|
||||||
namespace id (This results in :field:`PathRootError.no_permission` if
|
|
||||||
you don't have access to this namespace.).
|
|
||||||
"""
|
|
||||||
|
|
||||||
_catch_all = 'other'
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
home = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
other = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def root(cls, val):
|
|
||||||
"""
|
|
||||||
Create an instance of this class set to the ``root`` tag with value
|
|
||||||
``val``.
|
|
||||||
|
|
||||||
:param str val:
|
|
||||||
:rtype: PathRoot
|
|
||||||
"""
|
|
||||||
return cls('root', val)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def namespace_id(cls, val):
|
|
||||||
"""
|
|
||||||
Create an instance of this class set to the ``namespace_id`` tag with
|
|
||||||
value ``val``.
|
|
||||||
|
|
||||||
:param str val:
|
|
||||||
:rtype: PathRoot
|
|
||||||
"""
|
|
||||||
return cls('namespace_id', val)
|
|
||||||
|
|
||||||
def is_home(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``home``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'home'
|
|
||||||
|
|
||||||
def is_root(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``root``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'root'
|
|
||||||
|
|
||||||
def is_namespace_id(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``namespace_id``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'namespace_id'
|
|
||||||
|
|
||||||
def is_other(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``other``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'other'
|
|
||||||
|
|
||||||
def get_root(self):
|
|
||||||
"""
|
|
||||||
Paths are relative to the authenticating user's root namespace (This
|
|
||||||
results in ``PathRootError.invalid_root`` if the user's root namespace
|
|
||||||
has changed.).
|
|
||||||
|
|
||||||
Only call this if :meth:`is_root` is true.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if not self.is_root():
|
|
||||||
raise AttributeError("tag 'root' not set")
|
|
||||||
return self._value
|
|
||||||
|
|
||||||
def get_namespace_id(self):
|
|
||||||
"""
|
|
||||||
Paths are relative to given namespace id (This results in
|
|
||||||
``PathRootError.no_permission`` if you don't have access to this
|
|
||||||
namespace.).
|
|
||||||
|
|
||||||
Only call this if :meth:`is_namespace_id` is true.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if not self.is_namespace_id():
|
|
||||||
raise AttributeError("tag 'namespace_id' not set")
|
|
||||||
return self._value
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(PathRoot, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'PathRoot(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
PathRoot_validator = bv.Union(PathRoot)
|
|
||||||
|
|
||||||
class PathRootError(bb.Union):
|
|
||||||
"""
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar RootInfo PathRootError.invalid_root: The root namespace id in
|
|
||||||
Dropbox-API-Path-Root header is not valid. The value of this error is
|
|
||||||
use's latest root info.
|
|
||||||
:ivar common.PathRootError.no_permission: You don't have permission to
|
|
||||||
access the namespace id in Dropbox-API-Path-Root header.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_catch_all = 'other'
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
no_permission = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
other = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def invalid_root(cls, val):
|
|
||||||
"""
|
|
||||||
Create an instance of this class set to the ``invalid_root`` tag with
|
|
||||||
value ``val``.
|
|
||||||
|
|
||||||
:param RootInfo val:
|
|
||||||
:rtype: PathRootError
|
|
||||||
"""
|
|
||||||
return cls('invalid_root', val)
|
|
||||||
|
|
||||||
def is_invalid_root(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``invalid_root``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'invalid_root'
|
|
||||||
|
|
||||||
def is_no_permission(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``no_permission``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'no_permission'
|
|
||||||
|
|
||||||
def is_other(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``other``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'other'
|
|
||||||
|
|
||||||
def get_invalid_root(self):
|
|
||||||
"""
|
|
||||||
The root namespace id in Dropbox-API-Path-Root header is not valid. The
|
|
||||||
value of this error is use's latest root info.
|
|
||||||
|
|
||||||
Only call this if :meth:`is_invalid_root` is true.
|
|
||||||
|
|
||||||
:rtype: RootInfo
|
|
||||||
"""
|
|
||||||
if not self.is_invalid_root():
|
|
||||||
raise AttributeError("tag 'invalid_root' not set")
|
|
||||||
return self._value
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(PathRootError, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'PathRootError(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
PathRootError_validator = bv.Union(PathRootError)
|
|
||||||
|
|
||||||
class RootInfo(bb.Struct):
|
|
||||||
"""
|
|
||||||
Information about current user's root.
|
|
||||||
|
|
||||||
:ivar common.RootInfo.root_namespace_id: The namespace ID for user's root
|
|
||||||
namespace. It will be the namespace ID of the shared team root if the
|
|
||||||
user is member of a team with a separate team root. Otherwise it will be
|
|
||||||
same as ``RootInfo.home_namespace_id``.
|
|
||||||
:ivar common.RootInfo.home_namespace_id: The namespace ID for user's home
|
|
||||||
namespace.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
'_root_namespace_id_value',
|
|
||||||
'_root_namespace_id_present',
|
|
||||||
'_home_namespace_id_value',
|
|
||||||
'_home_namespace_id_present',
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
root_namespace_id=None,
|
|
||||||
home_namespace_id=None):
|
|
||||||
self._root_namespace_id_value = None
|
|
||||||
self._root_namespace_id_present = False
|
|
||||||
self._home_namespace_id_value = None
|
|
||||||
self._home_namespace_id_present = False
|
|
||||||
if root_namespace_id is not None:
|
|
||||||
self.root_namespace_id = root_namespace_id
|
|
||||||
if home_namespace_id is not None:
|
|
||||||
self.home_namespace_id = home_namespace_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def root_namespace_id(self):
|
|
||||||
"""
|
|
||||||
The namespace ID for user's root namespace. It will be the namespace ID
|
|
||||||
of the shared team root if the user is member of a team with a separate
|
|
||||||
team root. Otherwise it will be same as ``RootInfo.home_namespace_id``.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._root_namespace_id_present:
|
|
||||||
return self._root_namespace_id_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'root_namespace_id'")
|
|
||||||
|
|
||||||
@root_namespace_id.setter
|
|
||||||
def root_namespace_id(self, val):
|
|
||||||
val = self._root_namespace_id_validator.validate(val)
|
|
||||||
self._root_namespace_id_value = val
|
|
||||||
self._root_namespace_id_present = True
|
|
||||||
|
|
||||||
@root_namespace_id.deleter
|
|
||||||
def root_namespace_id(self):
|
|
||||||
self._root_namespace_id_value = None
|
|
||||||
self._root_namespace_id_present = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def home_namespace_id(self):
|
|
||||||
"""
|
|
||||||
The namespace ID for user's home namespace.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._home_namespace_id_present:
|
|
||||||
return self._home_namespace_id_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'home_namespace_id'")
|
|
||||||
|
|
||||||
@home_namespace_id.setter
|
|
||||||
def home_namespace_id(self, val):
|
|
||||||
val = self._home_namespace_id_validator.validate(val)
|
|
||||||
self._home_namespace_id_value = val
|
|
||||||
self._home_namespace_id_present = True
|
|
||||||
|
|
||||||
@home_namespace_id.deleter
|
|
||||||
def home_namespace_id(self):
|
|
||||||
self._home_namespace_id_value = None
|
|
||||||
self._home_namespace_id_present = False
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(RootInfo, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'RootInfo(root_namespace_id={!r}, home_namespace_id={!r})'.format(
|
|
||||||
self._root_namespace_id_value,
|
|
||||||
self._home_namespace_id_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
RootInfo_validator = bv.StructTree(RootInfo)
|
|
||||||
|
|
||||||
class TeamRootInfo(RootInfo):
|
|
||||||
"""
|
|
||||||
Root info when user is member of a team with a separate root namespace ID.
|
|
||||||
|
|
||||||
:ivar common.TeamRootInfo.home_path: The path for user's home directory
|
|
||||||
under the shared team root.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
'_home_path_value',
|
|
||||||
'_home_path_present',
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
root_namespace_id=None,
|
|
||||||
home_namespace_id=None,
|
|
||||||
home_path=None):
|
|
||||||
super(TeamRootInfo, self).__init__(root_namespace_id,
|
|
||||||
home_namespace_id)
|
|
||||||
self._home_path_value = None
|
|
||||||
self._home_path_present = False
|
|
||||||
if home_path is not None:
|
|
||||||
self.home_path = home_path
|
|
||||||
|
|
||||||
@property
|
|
||||||
def home_path(self):
|
|
||||||
"""
|
|
||||||
The path for user's home directory under the shared team root.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._home_path_present:
|
|
||||||
return self._home_path_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'home_path'")
|
|
||||||
|
|
||||||
@home_path.setter
|
|
||||||
def home_path(self, val):
|
|
||||||
val = self._home_path_validator.validate(val)
|
|
||||||
self._home_path_value = val
|
|
||||||
self._home_path_present = True
|
|
||||||
|
|
||||||
@home_path.deleter
|
|
||||||
def home_path(self):
|
|
||||||
self._home_path_value = None
|
|
||||||
self._home_path_present = False
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(TeamRootInfo, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'TeamRootInfo(root_namespace_id={!r}, home_namespace_id={!r}, home_path={!r})'.format(
|
|
||||||
self._root_namespace_id_value,
|
|
||||||
self._home_namespace_id_value,
|
|
||||||
self._home_path_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
TeamRootInfo_validator = bv.Struct(TeamRootInfo)
|
|
||||||
|
|
||||||
class UserRootInfo(RootInfo):
|
|
||||||
"""
|
|
||||||
Root info when user is not member of a team or the user is a member of a
|
|
||||||
team and the team does not have a separate root namespace.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
root_namespace_id=None,
|
|
||||||
home_namespace_id=None):
|
|
||||||
super(UserRootInfo, self).__init__(root_namespace_id,
|
|
||||||
home_namespace_id)
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(UserRootInfo, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'UserRootInfo(root_namespace_id={!r}, home_namespace_id={!r})'.format(
|
|
||||||
self._root_namespace_id_value,
|
|
||||||
self._home_namespace_id_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
UserRootInfo_validator = bv.Struct(UserRootInfo)
|
|
||||||
|
|
||||||
Date_validator = bv.Timestamp(u'%Y-%m-%d')
|
|
||||||
DisplayName_validator = bv.String(min_length=1, pattern=u'[^/:?*<>"|]*')
|
|
||||||
DisplayNameLegacy_validator = bv.String()
|
|
||||||
DropboxTimestamp_validator = bv.Timestamp(u'%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
EmailAddress_validator = bv.String(max_length=255, pattern=u"^['&A-Za-z0-9._%+-]+@[A-Za-z0-9-][A-Za-z0-9.-]*\\.[A-Za-z]{2,15}$")
|
|
||||||
# A ISO639-1 code.
|
|
||||||
LanguageCode_validator = bv.String(min_length=2)
|
|
||||||
NamePart_validator = bv.String(min_length=1, max_length=100, pattern=u'[^/:?*<>"|]*')
|
|
||||||
NamespaceId_validator = bv.String(pattern=u'[-_0-9a-zA-Z:]+')
|
|
||||||
OptionalNamePart_validator = bv.String(max_length=100, pattern=u'[^/:?*<>"|]*')
|
|
||||||
SessionId_validator = bv.String()
|
|
||||||
SharedFolderId_validator = NamespaceId_validator
|
|
||||||
PathRoot._home_validator = bv.Void()
|
|
||||||
PathRoot._root_validator = NamespaceId_validator
|
|
||||||
PathRoot._namespace_id_validator = NamespaceId_validator
|
|
||||||
PathRoot._other_validator = bv.Void()
|
|
||||||
PathRoot._tagmap = {
|
|
||||||
'home': PathRoot._home_validator,
|
|
||||||
'root': PathRoot._root_validator,
|
|
||||||
'namespace_id': PathRoot._namespace_id_validator,
|
|
||||||
'other': PathRoot._other_validator,
|
|
||||||
}
|
|
||||||
|
|
||||||
PathRoot.home = PathRoot('home')
|
|
||||||
PathRoot.other = PathRoot('other')
|
|
||||||
|
|
||||||
PathRootError._invalid_root_validator = RootInfo_validator
|
|
||||||
PathRootError._no_permission_validator = bv.Void()
|
|
||||||
PathRootError._other_validator = bv.Void()
|
|
||||||
PathRootError._tagmap = {
|
|
||||||
'invalid_root': PathRootError._invalid_root_validator,
|
|
||||||
'no_permission': PathRootError._no_permission_validator,
|
|
||||||
'other': PathRootError._other_validator,
|
|
||||||
}
|
|
||||||
|
|
||||||
PathRootError.no_permission = PathRootError('no_permission')
|
|
||||||
PathRootError.other = PathRootError('other')
|
|
||||||
|
|
||||||
RootInfo._root_namespace_id_validator = NamespaceId_validator
|
|
||||||
RootInfo._home_namespace_id_validator = NamespaceId_validator
|
|
||||||
RootInfo._field_names_ = set([
|
|
||||||
'root_namespace_id',
|
|
||||||
'home_namespace_id',
|
|
||||||
])
|
|
||||||
RootInfo._all_field_names_ = RootInfo._field_names_
|
|
||||||
RootInfo._fields_ = [
|
|
||||||
('root_namespace_id', RootInfo._root_namespace_id_validator),
|
|
||||||
('home_namespace_id', RootInfo._home_namespace_id_validator),
|
|
||||||
]
|
|
||||||
RootInfo._all_fields_ = RootInfo._fields_
|
|
||||||
|
|
||||||
RootInfo._tag_to_subtype_ = {
|
|
||||||
(u'team',): TeamRootInfo_validator,
|
|
||||||
(u'user',): UserRootInfo_validator,
|
|
||||||
}
|
|
||||||
RootInfo._pytype_to_tag_and_subtype_ = {
|
|
||||||
TeamRootInfo: ((u'team',), TeamRootInfo_validator),
|
|
||||||
UserRootInfo: ((u'user',), UserRootInfo_validator),
|
|
||||||
}
|
|
||||||
RootInfo._is_catch_all_ = True
|
|
||||||
|
|
||||||
TeamRootInfo._home_path_validator = bv.String()
|
|
||||||
TeamRootInfo._field_names_ = set(['home_path'])
|
|
||||||
TeamRootInfo._all_field_names_ = RootInfo._all_field_names_.union(TeamRootInfo._field_names_)
|
|
||||||
TeamRootInfo._fields_ = [('home_path', TeamRootInfo._home_path_validator)]
|
|
||||||
TeamRootInfo._all_fields_ = RootInfo._all_fields_ + TeamRootInfo._fields_
|
|
||||||
|
|
||||||
UserRootInfo._field_names_ = set([])
|
|
||||||
UserRootInfo._all_field_names_ = RootInfo._all_field_names_.union(UserRootInfo._field_names_)
|
|
||||||
UserRootInfo._fields_ = []
|
|
||||||
UserRootInfo._all_fields_ = RootInfo._all_fields_ + UserRootInfo._fields_
|
|
||||||
|
|
||||||
ROUTES = {
|
|
||||||
}
|
|
||||||
|
|
@ -1,609 +0,0 @@
|
|||||||
__all__ = [
|
|
||||||
'Dropbox',
|
|
||||||
'DropboxTeam',
|
|
||||||
'create_session',
|
|
||||||
]
|
|
||||||
|
|
||||||
# This should always be 0.0.0 in master. Only update this after tagging
|
|
||||||
# before release.
|
|
||||||
__version__ = '0.0.0'
|
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import random
|
|
||||||
import time
|
|
||||||
|
|
||||||
import requests
|
|
||||||
import six
|
|
||||||
|
|
||||||
from . import files, stone_serializers
|
|
||||||
from .auth import (
|
|
||||||
AuthError_validator,
|
|
||||||
RateLimitError_validator,
|
|
||||||
)
|
|
||||||
from .common import (
|
|
||||||
PathRoot,
|
|
||||||
PathRoot_validator,
|
|
||||||
PathRootError_validator
|
|
||||||
)
|
|
||||||
from .base import DropboxBase
|
|
||||||
from .base_team import DropboxTeamBase
|
|
||||||
from .exceptions import (
|
|
||||||
ApiError,
|
|
||||||
AuthError,
|
|
||||||
BadInputError,
|
|
||||||
HttpError,
|
|
||||||
PathRootError,
|
|
||||||
InternalServerError,
|
|
||||||
RateLimitError,
|
|
||||||
)
|
|
||||||
from .session import (
|
|
||||||
API_HOST,
|
|
||||||
API_CONTENT_HOST,
|
|
||||||
API_NOTIFICATION_HOST,
|
|
||||||
HOST_API,
|
|
||||||
HOST_CONTENT,
|
|
||||||
HOST_NOTIFY,
|
|
||||||
pinned_session,
|
|
||||||
)
|
|
||||||
|
|
||||||
PATH_ROOT_HEADER = 'Dropbox-API-Path-Root'
|
|
||||||
HTTP_STATUS_INVALID_PATH_ROOT = 422
|
|
||||||
|
|
||||||
class RouteResult(object):
|
|
||||||
"""The successful result of a call to a route."""
|
|
||||||
|
|
||||||
def __init__(self, obj_result, http_resp=None):
|
|
||||||
"""
|
|
||||||
:param str obj_result: The result of a route not including the binary
|
|
||||||
payload portion, if one exists. Must be serialized JSON.
|
|
||||||
:param requests.models.Response http_resp: A raw HTTP response. It will
|
|
||||||
be used to stream the binary-body payload of the response.
|
|
||||||
"""
|
|
||||||
assert isinstance(obj_result, six.string_types), \
|
|
||||||
'obj_result: expected string, got %r' % type(obj_result)
|
|
||||||
if http_resp is not None:
|
|
||||||
assert isinstance(http_resp, requests.models.Response), \
|
|
||||||
'http_resp: expected requests.models.Response, got %r' % \
|
|
||||||
type(http_resp)
|
|
||||||
self.obj_result = obj_result
|
|
||||||
self.http_resp = http_resp
|
|
||||||
|
|
||||||
class RouteErrorResult(object):
|
|
||||||
"""The error result of a call to a route."""
|
|
||||||
|
|
||||||
def __init__(self, request_id, obj_result):
|
|
||||||
"""
|
|
||||||
:param str request_id: A request_id can be shared with Dropbox Support
|
|
||||||
to pinpoint the exact request that returns an error.
|
|
||||||
:param str obj_result: The result of a route not including the binary
|
|
||||||
payload portion, if one exists.
|
|
||||||
"""
|
|
||||||
self.request_id = request_id
|
|
||||||
self.obj_result = obj_result
|
|
||||||
|
|
||||||
def create_session(max_connections=8, proxies=None):
|
|
||||||
"""
|
|
||||||
Creates a session object that can be used by multiple :class:`Dropbox` and
|
|
||||||
:class:`DropboxTeam` instances. This lets you share a connection pool
|
|
||||||
amongst them, as well as proxy parameters.
|
|
||||||
|
|
||||||
:param int max_connections: Maximum connection pool size.
|
|
||||||
:param dict proxies: See the `requests module
|
|
||||||
<http://docs.python-requests.org/en/latest/user/advanced/#proxies>`_
|
|
||||||
for more details.
|
|
||||||
:rtype: :class:`requests.sessions.Session`. `See the requests module
|
|
||||||
<http://docs.python-requests.org/en/latest/user/advanced/#session-objects>`_
|
|
||||||
for more details.
|
|
||||||
"""
|
|
||||||
# We only need as many pool_connections as we have unique hostnames.
|
|
||||||
session = pinned_session(pool_maxsize=max_connections)
|
|
||||||
if proxies:
|
|
||||||
session.proxies = proxies
|
|
||||||
return session
|
|
||||||
|
|
||||||
class _DropboxTransport(object):
|
|
||||||
"""
|
|
||||||
Responsible for implementing the wire protocol for making requests to the
|
|
||||||
Dropbox API.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_API_VERSION = '2'
|
|
||||||
|
|
||||||
# Download style means that the route argument goes in a Dropbox-API-Arg
|
|
||||||
# header, and the result comes back in a Dropbox-API-Result header. The
|
|
||||||
# HTTP response body contains a binary payload.
|
|
||||||
_ROUTE_STYLE_DOWNLOAD = 'download'
|
|
||||||
|
|
||||||
# Upload style means that the route argument goes in a Dropbox-API-Arg
|
|
||||||
# header. The HTTP request body contains a binary payload. The result
|
|
||||||
# comes back in a Dropbox-API-Result header.
|
|
||||||
_ROUTE_STYLE_UPLOAD = 'upload'
|
|
||||||
|
|
||||||
# RPC style means that the argument and result of a route are contained in
|
|
||||||
# the HTTP body.
|
|
||||||
_ROUTE_STYLE_RPC = 'rpc'
|
|
||||||
|
|
||||||
# This is the default longest time we'll block on receiving data from the server
|
|
||||||
_DEFAULT_TIMEOUT = 100
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
oauth2_access_token,
|
|
||||||
max_retries_on_error=4,
|
|
||||||
max_retries_on_rate_limit=None,
|
|
||||||
user_agent=None,
|
|
||||||
session=None,
|
|
||||||
headers=None,
|
|
||||||
timeout=_DEFAULT_TIMEOUT):
|
|
||||||
"""
|
|
||||||
:param str oauth2_access_token: OAuth2 access token for making client
|
|
||||||
requests.
|
|
||||||
|
|
||||||
:param int max_retries_on_error: On 5xx errors, the number of times to
|
|
||||||
retry.
|
|
||||||
:param Optional[int] max_retries_on_rate_limit: On 429 errors, the
|
|
||||||
number of times to retry. If `None`, always retries.
|
|
||||||
:param str user_agent: The user agent to use when making requests. This
|
|
||||||
helps us identify requests coming from your application. We
|
|
||||||
recommend you use the format "AppName/Version". If set, we append
|
|
||||||
"/OfficialDropboxPythonSDKv2/__version__" to the user_agent,
|
|
||||||
:param session: If not provided, a new session (connection pool) is
|
|
||||||
created. To share a session across multiple clients, use
|
|
||||||
:func:`create_session`.
|
|
||||||
:type session: :class:`requests.sessions.Session`
|
|
||||||
:param dict headers: Additional headers to add to requests.
|
|
||||||
:param Optional[float] timeout: Maximum duration in seconds that
|
|
||||||
client will wait for any single packet from the
|
|
||||||
server. After the timeout the client will give up on
|
|
||||||
connection. If `None`, client will wait forever. Defaults
|
|
||||||
to 30 seconds.
|
|
||||||
"""
|
|
||||||
assert len(oauth2_access_token) > 0, \
|
|
||||||
'OAuth2 access token cannot be empty.'
|
|
||||||
assert headers is None or isinstance(headers, dict), \
|
|
||||||
'Expected dict, got %r' % headers
|
|
||||||
self._oauth2_access_token = oauth2_access_token
|
|
||||||
|
|
||||||
self._max_retries_on_error = max_retries_on_error
|
|
||||||
self._max_retries_on_rate_limit = max_retries_on_rate_limit
|
|
||||||
if session:
|
|
||||||
assert isinstance(session, requests.sessions.Session), \
|
|
||||||
'Expected requests.sessions.Session, got %r' % session
|
|
||||||
self._session = session
|
|
||||||
else:
|
|
||||||
self._session = create_session()
|
|
||||||
self._headers = headers
|
|
||||||
|
|
||||||
base_user_agent = 'OfficialDropboxPythonSDKv2/' + __version__
|
|
||||||
if user_agent:
|
|
||||||
self._raw_user_agent = user_agent
|
|
||||||
self._user_agent = '{}/{}'.format(user_agent, base_user_agent)
|
|
||||||
else:
|
|
||||||
self._raw_user_agent = None
|
|
||||||
self._user_agent = base_user_agent
|
|
||||||
|
|
||||||
self._logger = logging.getLogger('dropbox')
|
|
||||||
|
|
||||||
self._host_map = {HOST_API: API_HOST,
|
|
||||||
HOST_CONTENT: API_CONTENT_HOST,
|
|
||||||
HOST_NOTIFY: API_NOTIFICATION_HOST}
|
|
||||||
|
|
||||||
self._timeout = timeout
|
|
||||||
|
|
||||||
def clone(
|
|
||||||
self,
|
|
||||||
oauth2_access_token=None,
|
|
||||||
max_retries_on_error=None,
|
|
||||||
max_retries_on_rate_limit=None,
|
|
||||||
user_agent=None,
|
|
||||||
session=None,
|
|
||||||
headers=None,
|
|
||||||
timeout=None):
|
|
||||||
"""
|
|
||||||
Creates a new copy of the Dropbox client with the same defaults unless modified by
|
|
||||||
arguments to clone()
|
|
||||||
|
|
||||||
See constructor for original parameter descriptions.
|
|
||||||
|
|
||||||
:return: New instance of Dropbox clent
|
|
||||||
:rtype: Dropbox
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.__class__(
|
|
||||||
oauth2_access_token or self._oauth2_access_token,
|
|
||||||
max_retries_on_error or self._max_retries_on_error,
|
|
||||||
max_retries_on_rate_limit or self._max_retries_on_rate_limit,
|
|
||||||
user_agent or self._user_agent,
|
|
||||||
session or self._session,
|
|
||||||
headers or self._headers,
|
|
||||||
timeout or self._timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
def request(self,
|
|
||||||
route,
|
|
||||||
namespace,
|
|
||||||
request_arg,
|
|
||||||
request_binary,
|
|
||||||
timeout=None):
|
|
||||||
"""
|
|
||||||
Makes a request to the Dropbox API and in the process validates that
|
|
||||||
the route argument and result are the expected data types. The
|
|
||||||
request_arg is converted to JSON based on the arg_data_type. Likewise,
|
|
||||||
the response is deserialized from JSON and converted to an object based
|
|
||||||
on the {result,error}_data_type.
|
|
||||||
|
|
||||||
:param host: The Dropbox API host to connect to.
|
|
||||||
:param route: The route to make the request to.
|
|
||||||
:type route: :class:`.datatypes.stone_base.Route`
|
|
||||||
:param request_arg: Argument for the route that conforms to the
|
|
||||||
validator specified by route.arg_type.
|
|
||||||
:param request_binary: String or file pointer representing the binary
|
|
||||||
payload. Use None if there is no binary payload.
|
|
||||||
:param Optional[float] timeout: Maximum duration in seconds
|
|
||||||
that client will wait for any single packet from the
|
|
||||||
server. After the timeout the client will give up on
|
|
||||||
connection. If `None`, will use default timeout set on
|
|
||||||
Dropbox object. Defaults to `None`.
|
|
||||||
:return: The route's result.
|
|
||||||
"""
|
|
||||||
host = route.attrs['host'] or 'api'
|
|
||||||
route_name = namespace + '/' + route.name
|
|
||||||
if route.version > 1:
|
|
||||||
route_name += '_v{}'.format(route.version)
|
|
||||||
route_style = route.attrs['style'] or 'rpc'
|
|
||||||
serialized_arg = stone_serializers.json_encode(route.arg_type,
|
|
||||||
request_arg)
|
|
||||||
|
|
||||||
if (timeout is None and
|
|
||||||
route == files.list_folder_longpoll):
|
|
||||||
# The client normally sends a timeout value to the
|
|
||||||
# longpoll route. The server will respond after
|
|
||||||
# <timeout> + random(0, 90) seconds. We increase the
|
|
||||||
# socket timeout to the longpoll timeout value plus 90
|
|
||||||
# seconds so that we don't cut the server response short
|
|
||||||
# due to a shorter socket timeout.
|
|
||||||
# NB: This is done here because base.py is auto-generated
|
|
||||||
timeout = request_arg.timeout + 90
|
|
||||||
|
|
||||||
res = self.request_json_string_with_retry(host,
|
|
||||||
route_name,
|
|
||||||
route_style,
|
|
||||||
serialized_arg,
|
|
||||||
request_binary,
|
|
||||||
timeout=timeout)
|
|
||||||
decoded_obj_result = json.loads(res.obj_result)
|
|
||||||
if isinstance(res, RouteResult):
|
|
||||||
returned_data_type = route.result_type
|
|
||||||
obj = decoded_obj_result
|
|
||||||
elif isinstance(res, RouteErrorResult):
|
|
||||||
returned_data_type = route.error_type
|
|
||||||
obj = decoded_obj_result['error']
|
|
||||||
user_message = decoded_obj_result.get('user_message')
|
|
||||||
user_message_text = user_message and user_message.get('text')
|
|
||||||
user_message_locale = user_message and user_message.get('locale')
|
|
||||||
else:
|
|
||||||
raise AssertionError('Expected RouteResult or RouteErrorResult, '
|
|
||||||
'but res is %s' % type(res))
|
|
||||||
|
|
||||||
deserialized_result = stone_serializers.json_compat_obj_decode(
|
|
||||||
returned_data_type, obj, strict=False)
|
|
||||||
|
|
||||||
if isinstance(res, RouteErrorResult):
|
|
||||||
raise ApiError(res.request_id,
|
|
||||||
deserialized_result,
|
|
||||||
user_message_text,
|
|
||||||
user_message_locale)
|
|
||||||
elif route_style == self._ROUTE_STYLE_DOWNLOAD:
|
|
||||||
return (deserialized_result, res.http_resp)
|
|
||||||
else:
|
|
||||||
return deserialized_result
|
|
||||||
|
|
||||||
def request_json_object(self,
|
|
||||||
host,
|
|
||||||
route_name,
|
|
||||||
route_style,
|
|
||||||
request_arg,
|
|
||||||
request_binary,
|
|
||||||
timeout=None):
|
|
||||||
"""
|
|
||||||
Makes a request to the Dropbox API, taking a JSON-serializable Python
|
|
||||||
object as an argument, and returning one as a response.
|
|
||||||
|
|
||||||
:param host: The Dropbox API host to connect to.
|
|
||||||
:param route_name: The name of the route to invoke.
|
|
||||||
:param route_style: The style of the route.
|
|
||||||
:param str request_arg: A JSON-serializable Python object representing
|
|
||||||
the argument for the route.
|
|
||||||
:param Optional[bytes] request_binary: Bytes representing the binary
|
|
||||||
payload. Use None if there is no binary payload.
|
|
||||||
:param Optional[float] timeout: Maximum duration in seconds
|
|
||||||
that client will wait for any single packet from the
|
|
||||||
server. After the timeout the client will give up on
|
|
||||||
connection. If `None`, will use default timeout set on
|
|
||||||
Dropbox object. Defaults to `None`.
|
|
||||||
:return: The route's result as a JSON-serializable Python object.
|
|
||||||
"""
|
|
||||||
serialized_arg = json.dumps(request_arg)
|
|
||||||
res = self.request_json_string_with_retry(host,
|
|
||||||
route_name,
|
|
||||||
route_style,
|
|
||||||
serialized_arg,
|
|
||||||
request_binary,
|
|
||||||
timeout=timeout)
|
|
||||||
# This can throw a ValueError if the result is not deserializable,
|
|
||||||
# but that would be completely unexpected.
|
|
||||||
deserialized_result = json.loads(res.obj_result)
|
|
||||||
if isinstance(res, RouteResult) and res.http_resp is not None:
|
|
||||||
return (deserialized_result, res.http_resp)
|
|
||||||
else:
|
|
||||||
return deserialized_result
|
|
||||||
|
|
||||||
def request_json_string_with_retry(self,
|
|
||||||
host,
|
|
||||||
route_name,
|
|
||||||
route_style,
|
|
||||||
request_json_arg,
|
|
||||||
request_binary,
|
|
||||||
timeout=None):
|
|
||||||
"""
|
|
||||||
See :meth:`request_json_object` for description of parameters.
|
|
||||||
|
|
||||||
:param request_json_arg: A string representing the serialized JSON
|
|
||||||
argument to the route.
|
|
||||||
"""
|
|
||||||
attempt = 0
|
|
||||||
rate_limit_errors = 0
|
|
||||||
while True:
|
|
||||||
self._logger.info('Request to %s', route_name)
|
|
||||||
try:
|
|
||||||
return self.request_json_string(host,
|
|
||||||
route_name,
|
|
||||||
route_style,
|
|
||||||
request_json_arg,
|
|
||||||
request_binary,
|
|
||||||
timeout=timeout)
|
|
||||||
except InternalServerError as e:
|
|
||||||
attempt += 1
|
|
||||||
if attempt <= self._max_retries_on_error:
|
|
||||||
# Use exponential backoff
|
|
||||||
backoff = 2**attempt * random.random()
|
|
||||||
self._logger.info(
|
|
||||||
'HttpError status_code=%s: Retrying in %.1f seconds',
|
|
||||||
e.status_code, backoff)
|
|
||||||
time.sleep(backoff)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
except RateLimitError as e:
|
|
||||||
rate_limit_errors += 1
|
|
||||||
if (self._max_retries_on_rate_limit is None or
|
|
||||||
self._max_retries_on_rate_limit >= rate_limit_errors):
|
|
||||||
# Set default backoff to 5 seconds.
|
|
||||||
backoff = e.backoff if e.backoff is not None else 5.0
|
|
||||||
self._logger.info(
|
|
||||||
'Ratelimit: Retrying in %.1f seconds.', backoff)
|
|
||||||
time.sleep(backoff)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
def request_json_string(self,
|
|
||||||
host,
|
|
||||||
func_name,
|
|
||||||
route_style,
|
|
||||||
request_json_arg,
|
|
||||||
request_binary,
|
|
||||||
timeout=None):
|
|
||||||
"""
|
|
||||||
See :meth:`request_json_string_with_retry` for description of
|
|
||||||
parameters.
|
|
||||||
"""
|
|
||||||
if host not in self._host_map:
|
|
||||||
raise ValueError('Unknown value for host: %r' % host)
|
|
||||||
|
|
||||||
if not isinstance(request_binary, (six.binary_type, type(None))):
|
|
||||||
# Disallow streams and file-like objects even though the underlying
|
|
||||||
# requests library supports them. This is to prevent incorrect
|
|
||||||
# behavior when a non-rewindable stream is read from, but the
|
|
||||||
# request fails and needs to be re-tried at a later time.
|
|
||||||
raise TypeError('expected request_binary as binary type, got %s' %
|
|
||||||
type(request_binary))
|
|
||||||
|
|
||||||
# Fully qualified hostname
|
|
||||||
fq_hostname = self._host_map[host]
|
|
||||||
url = self._get_route_url(fq_hostname, func_name)
|
|
||||||
|
|
||||||
headers = {'User-Agent': self._user_agent}
|
|
||||||
if host != HOST_NOTIFY:
|
|
||||||
headers['Authorization'] = 'Bearer %s' % self._oauth2_access_token
|
|
||||||
if self._headers:
|
|
||||||
headers.update(self._headers)
|
|
||||||
|
|
||||||
# The contents of the body of the HTTP request
|
|
||||||
body = None
|
|
||||||
# Whether the response should be streamed incrementally, or buffered
|
|
||||||
# entirely. If stream is True, the caller is responsible for closing
|
|
||||||
# the HTTP response.
|
|
||||||
stream = False
|
|
||||||
|
|
||||||
if route_style == self._ROUTE_STYLE_RPC:
|
|
||||||
headers['Content-Type'] = 'application/json'
|
|
||||||
body = request_json_arg
|
|
||||||
elif route_style == self._ROUTE_STYLE_DOWNLOAD:
|
|
||||||
headers['Dropbox-API-Arg'] = request_json_arg
|
|
||||||
stream = True
|
|
||||||
elif route_style == self._ROUTE_STYLE_UPLOAD:
|
|
||||||
headers['Content-Type'] = 'application/octet-stream'
|
|
||||||
headers['Dropbox-API-Arg'] = request_json_arg
|
|
||||||
body = request_binary
|
|
||||||
else:
|
|
||||||
raise ValueError('Unknown operation style: %r' % route_style)
|
|
||||||
|
|
||||||
if timeout is None:
|
|
||||||
timeout = self._timeout
|
|
||||||
|
|
||||||
r = self._session.post(url,
|
|
||||||
headers=headers,
|
|
||||||
data=body,
|
|
||||||
stream=stream,
|
|
||||||
verify=True,
|
|
||||||
timeout=timeout,
|
|
||||||
)
|
|
||||||
|
|
||||||
request_id = r.headers.get('x-dropbox-request-id')
|
|
||||||
if r.status_code >= 500:
|
|
||||||
raise InternalServerError(request_id, r.status_code, r.text)
|
|
||||||
elif r.status_code == 400:
|
|
||||||
raise BadInputError(request_id, r.text)
|
|
||||||
elif r.status_code == 401:
|
|
||||||
assert r.headers.get('content-type') == 'application/json', (
|
|
||||||
'Expected content-type to be application/json, got %r' %
|
|
||||||
r.headers.get('content-type'))
|
|
||||||
err = stone_serializers.json_compat_obj_decode(
|
|
||||||
AuthError_validator, r.json()['error'])
|
|
||||||
raise AuthError(request_id, err)
|
|
||||||
elif r.status_code == HTTP_STATUS_INVALID_PATH_ROOT:
|
|
||||||
err = stone_serializers.json_compat_obj_decode(
|
|
||||||
PathRootError_validator, r.json()['error'])
|
|
||||||
raise PathRootError(request_id, err)
|
|
||||||
elif r.status_code == 429:
|
|
||||||
err = None
|
|
||||||
if r.headers.get('content-type') == 'application/json':
|
|
||||||
err = stone_serializers.json_compat_obj_decode(
|
|
||||||
RateLimitError_validator, r.json()['error'])
|
|
||||||
retry_after = err.retry_after
|
|
||||||
else:
|
|
||||||
retry_after_str = r.headers.get('retry-after')
|
|
||||||
if retry_after_str is not None:
|
|
||||||
retry_after = int(retry_after_str)
|
|
||||||
else:
|
|
||||||
retry_after = None
|
|
||||||
raise RateLimitError(request_id, err, retry_after)
|
|
||||||
elif 200 <= r.status_code <= 299:
|
|
||||||
if route_style == self._ROUTE_STYLE_DOWNLOAD:
|
|
||||||
raw_resp = r.headers['dropbox-api-result']
|
|
||||||
else:
|
|
||||||
assert r.headers.get('content-type') == 'application/json', (
|
|
||||||
'Expected content-type to be application/json, got %r' %
|
|
||||||
r.headers.get('content-type'))
|
|
||||||
raw_resp = r.content.decode('utf-8')
|
|
||||||
if route_style == self._ROUTE_STYLE_DOWNLOAD:
|
|
||||||
return RouteResult(raw_resp, r)
|
|
||||||
else:
|
|
||||||
return RouteResult(raw_resp)
|
|
||||||
elif r.status_code in (403, 404, 409):
|
|
||||||
raw_resp = r.content.decode('utf-8')
|
|
||||||
return RouteErrorResult(request_id, raw_resp)
|
|
||||||
else:
|
|
||||||
raise HttpError(request_id, r.status_code, r.text)
|
|
||||||
|
|
||||||
def _get_route_url(self, hostname, route_name):
|
|
||||||
"""Returns the URL of the route.
|
|
||||||
|
|
||||||
:param str hostname: Hostname to make the request to.
|
|
||||||
:param str route_name: Name of the route.
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
return 'https://{hostname}/{version}/{route_name}'.format(
|
|
||||||
hostname=hostname,
|
|
||||||
version=Dropbox._API_VERSION,
|
|
||||||
route_name=route_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _save_body_to_file(self, download_path, http_resp, chunksize=2**16):
|
|
||||||
"""
|
|
||||||
Saves the body of an HTTP response to a file.
|
|
||||||
|
|
||||||
:param str download_path: Local path to save data to.
|
|
||||||
:param http_resp: The HTTP response whose body will be saved.
|
|
||||||
:type http_resp: :class:`requests.models.Response`
|
|
||||||
:rtype: None
|
|
||||||
"""
|
|
||||||
with open(download_path, 'wb') as f:
|
|
||||||
with contextlib.closing(http_resp):
|
|
||||||
for c in http_resp.iter_content(chunksize):
|
|
||||||
f.write(c)
|
|
||||||
|
|
||||||
def with_path_root(self, path_root):
|
|
||||||
"""
|
|
||||||
Creates a clone of the Dropbox instance with the Dropbox-API-Path-Root header
|
|
||||||
as the appropriate serialized instance of PathRoot.
|
|
||||||
|
|
||||||
For more information, see
|
|
||||||
https://www.dropbox.com/developers/reference/namespace-guide#pathrootmodes
|
|
||||||
|
|
||||||
:param PathRoot path_root: instance of PathRoot to serialize into the headers field
|
|
||||||
:return: A :class: `Dropbox`
|
|
||||||
:rtype: Dropbox
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not isinstance(path_root, PathRoot):
|
|
||||||
raise ValueError("path_root must be an instance of PathRoot")
|
|
||||||
|
|
||||||
return self.clone(
|
|
||||||
headers={
|
|
||||||
PATH_ROOT_HEADER: stone_serializers.json_encode(PathRoot_validator, path_root)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
class Dropbox(_DropboxTransport, DropboxBase):
|
|
||||||
"""
|
|
||||||
Use this class to make requests to the Dropbox API using a user's access
|
|
||||||
token. Methods of this class are meant to act on the corresponding user's
|
|
||||||
Dropbox.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class DropboxTeam(_DropboxTransport, DropboxTeamBase):
|
|
||||||
"""
|
|
||||||
Use this class to make requests to the Dropbox API using a team's access
|
|
||||||
token. Methods of this class are meant to act on the team, but there is
|
|
||||||
also an :meth:`as_user` method for assuming a team member's identity.
|
|
||||||
"""
|
|
||||||
def as_admin(self, team_member_id):
|
|
||||||
"""
|
|
||||||
Allows a team credential to assume the identity of an administrator on the team
|
|
||||||
and perform operations on any team-owned content.
|
|
||||||
|
|
||||||
:param str team_member_id: team member id of administrator to perform actions with
|
|
||||||
:return: A :class:`Dropbox` object that can be used to query on behalf
|
|
||||||
of this admin of the team.
|
|
||||||
:rtype: Dropbox
|
|
||||||
"""
|
|
||||||
return self._get_dropbox_client_with_select_header('Dropbox-API-Select-Admin',
|
|
||||||
team_member_id)
|
|
||||||
|
|
||||||
def as_user(self, team_member_id):
|
|
||||||
"""
|
|
||||||
Allows a team credential to assume the identity of a member of the
|
|
||||||
team.
|
|
||||||
|
|
||||||
:param str team_member_id: team member id of team member to perform actions with
|
|
||||||
:return: A :class:`Dropbox` object that can be used to query on behalf
|
|
||||||
of this member of the team.
|
|
||||||
:rtype: Dropbox
|
|
||||||
"""
|
|
||||||
return self._get_dropbox_client_with_select_header('Dropbox-API-Select-User',
|
|
||||||
team_member_id)
|
|
||||||
|
|
||||||
def _get_dropbox_client_with_select_header(self, select_header_name, team_member_id):
|
|
||||||
"""
|
|
||||||
Get Dropbox client with modified headers
|
|
||||||
|
|
||||||
:param str select_header_name: Header name used to select users
|
|
||||||
:param str team_member_id: team member id of team member to perform actions with
|
|
||||||
:return: A :class:`Dropbox` object that can be used to query on behalf
|
|
||||||
of a member or admin of the team
|
|
||||||
:rtype: Dropbox
|
|
||||||
"""
|
|
||||||
|
|
||||||
new_headers = self._headers.copy() if self._headers else {}
|
|
||||||
new_headers[select_header_name] = team_member_id
|
|
||||||
return Dropbox(
|
|
||||||
self._oauth2_access_token,
|
|
||||||
max_retries_on_error=self._max_retries_on_error,
|
|
||||||
max_retries_on_rate_limit=self._max_retries_on_rate_limit,
|
|
||||||
timeout=self._timeout,
|
|
||||||
user_agent=self._raw_user_agent,
|
|
||||||
session=self._session,
|
|
||||||
headers=new_headers,
|
|
||||||
)
|
|
@ -1,100 +0,0 @@
|
|||||||
class DropboxException(Exception):
|
|
||||||
"""All errors related to making an API request extend this."""
|
|
||||||
|
|
||||||
def __init__(self, request_id, *args, **kwargs):
|
|
||||||
# A request_id can be shared with Dropbox Support to pinpoint the exact
|
|
||||||
# request that returns an error.
|
|
||||||
super(DropboxException, self).__init__(request_id, *args, **kwargs)
|
|
||||||
self.request_id = request_id
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return repr(self)
|
|
||||||
|
|
||||||
|
|
||||||
class ApiError(DropboxException):
|
|
||||||
"""Errors produced by the Dropbox API."""
|
|
||||||
|
|
||||||
def __init__(self, request_id, error, user_message_text, user_message_locale):
|
|
||||||
"""
|
|
||||||
:param (str) request_id: A request_id can be shared with Dropbox
|
|
||||||
Support to pinpoint the exact request that returns an error.
|
|
||||||
:param error: An instance of the error data type for the route.
|
|
||||||
:param (str) user_message_text: A human-readable message that can be
|
|
||||||
displayed to the end user. Is None, if unavailable.
|
|
||||||
:param (str) user_message_locale: The locale of ``user_message_text``,
|
|
||||||
if present.
|
|
||||||
"""
|
|
||||||
super(ApiError, self).__init__(request_id, error)
|
|
||||||
self.error = error
|
|
||||||
self.user_message_text = user_message_text
|
|
||||||
self.user_message_locale = user_message_locale
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'ApiError({!r}, {})'.format(self.request_id, self.error)
|
|
||||||
|
|
||||||
|
|
||||||
class HttpError(DropboxException):
|
|
||||||
"""Errors produced at the HTTP layer."""
|
|
||||||
|
|
||||||
def __init__(self, request_id, status_code, body):
|
|
||||||
super(HttpError, self).__init__(request_id, status_code, body)
|
|
||||||
self.status_code = status_code
|
|
||||||
self.body = body
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'HttpError({!r}, {}, {!r})'.format(self.request_id,
|
|
||||||
self.status_code, self.body)
|
|
||||||
|
|
||||||
|
|
||||||
class PathRootError(HttpError):
|
|
||||||
"""Error caused by an invalid path root."""
|
|
||||||
|
|
||||||
def __init__(self, request_id, error=None):
|
|
||||||
super(PathRootError, self).__init__(request_id, 422, None)
|
|
||||||
self.error = error
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'PathRootError({!r}, {!r})'.format(self.request_id, self.error)
|
|
||||||
|
|
||||||
|
|
||||||
class BadInputError(HttpError):
|
|
||||||
"""Errors due to bad input parameters to an API Operation."""
|
|
||||||
|
|
||||||
def __init__(self, request_id, message):
|
|
||||||
super(BadInputError, self).__init__(request_id, 400, message)
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'BadInputError({!r}, {!r})'.format(self.request_id, self.message)
|
|
||||||
|
|
||||||
|
|
||||||
class AuthError(HttpError):
|
|
||||||
"""Errors due to invalid authentication credentials."""
|
|
||||||
|
|
||||||
def __init__(self, request_id, error):
|
|
||||||
super(AuthError, self).__init__(request_id, 401, None)
|
|
||||||
self.error = error
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'AuthError({!r}, {!r})'.format(self.request_id, self.error)
|
|
||||||
|
|
||||||
|
|
||||||
class RateLimitError(HttpError):
|
|
||||||
"""Error caused by rate limiting."""
|
|
||||||
|
|
||||||
def __init__(self, request_id, error=None, backoff=None):
|
|
||||||
super(RateLimitError, self).__init__(request_id, 429, None)
|
|
||||||
self.error = error
|
|
||||||
self.backoff = backoff
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'RateLimitError({!r}, {!r}, {!r})'.format(
|
|
||||||
self.request_id, self.error, self.backoff)
|
|
||||||
|
|
||||||
|
|
||||||
class InternalServerError(HttpError):
|
|
||||||
"""Errors due to a problem on Dropbox."""
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'InternalServerError({!r}, {}, {!r})'.format(
|
|
||||||
self.request_id, self.status_code, self.body)
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,515 +0,0 @@
|
|||||||
__all__ = [
|
|
||||||
'BadRequestException',
|
|
||||||
'BadStateException',
|
|
||||||
'CsrfException',
|
|
||||||
'DropboxOAuth2Flow',
|
|
||||||
'DropboxOAuth2FlowNoRedirect',
|
|
||||||
'NotApprovedException',
|
|
||||||
'OAuth2FlowNoRedirectResult',
|
|
||||||
'OAuth2FlowResult',
|
|
||||||
'ProviderException',
|
|
||||||
]
|
|
||||||
|
|
||||||
import base64
|
|
||||||
import os
|
|
||||||
import six
|
|
||||||
import urllib
|
|
||||||
|
|
||||||
from .session import (
|
|
||||||
API_HOST,
|
|
||||||
WEB_HOST,
|
|
||||||
pinned_session,
|
|
||||||
)
|
|
||||||
|
|
||||||
if six.PY3:
|
|
||||||
url_path_quote = urllib.parse.quote # pylint: disable=no-member,useless-suppression
|
|
||||||
url_encode = urllib.parse.urlencode # pylint: disable=no-member,useless-suppression
|
|
||||||
else:
|
|
||||||
url_path_quote = urllib.quote # pylint: disable=no-member,useless-suppression
|
|
||||||
url_encode = urllib.urlencode # pylint: disable=no-member,useless-suppression
|
|
||||||
|
|
||||||
|
|
||||||
class OAuth2FlowNoRedirectResult(object):
|
|
||||||
"""
|
|
||||||
Authorization information for an OAuth2Flow performed with no redirect.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, access_token, account_id, user_id):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
access_token (str): Token to be used to authenticate later
|
|
||||||
requests.
|
|
||||||
account_id (str): The Dropbox user's account ID.
|
|
||||||
user_id (str): Deprecated (use account_id instead).
|
|
||||||
"""
|
|
||||||
self.access_token = access_token
|
|
||||||
self.account_id = account_id
|
|
||||||
self.user_id = user_id
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'OAuth2FlowNoRedirectResult(%r, %r, %r)' % (
|
|
||||||
self.access_token,
|
|
||||||
self.account_id,
|
|
||||||
self.user_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OAuth2FlowResult(OAuth2FlowNoRedirectResult):
|
|
||||||
"""
|
|
||||||
Authorization information for an OAuth2Flow with redirect.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, access_token, account_id, user_id, url_state):
|
|
||||||
"""
|
|
||||||
Same as OAuth2FlowNoRedirectResult but with url_state.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
url_state (str): The url state that was set by
|
|
||||||
:meth:`DropboxOAuth2Flow.start`.
|
|
||||||
"""
|
|
||||||
super(OAuth2FlowResult, self).__init__(
|
|
||||||
access_token, account_id, user_id)
|
|
||||||
self.url_state = url_state
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_no_redirect_result(cls, result, url_state):
|
|
||||||
assert isinstance(result, OAuth2FlowNoRedirectResult)
|
|
||||||
return cls(
|
|
||||||
result.access_token, result.account_id, result.user_id, url_state)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'OAuth2FlowResult(%r, %r, %r, %r)' % (
|
|
||||||
self.access_token,
|
|
||||||
self.account_id,
|
|
||||||
self.user_id,
|
|
||||||
self.url_state,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DropboxOAuth2FlowBase(object):
|
|
||||||
|
|
||||||
def __init__(self, consumer_key, consumer_secret, locale=None):
|
|
||||||
self.consumer_key = consumer_key
|
|
||||||
self.consumer_secret = consumer_secret
|
|
||||||
self.locale = locale
|
|
||||||
self.requests_session = pinned_session()
|
|
||||||
|
|
||||||
def _get_authorize_url(self, redirect_uri, state):
|
|
||||||
params = dict(response_type='code',
|
|
||||||
client_id=self.consumer_key)
|
|
||||||
if redirect_uri is not None:
|
|
||||||
params['redirect_uri'] = redirect_uri
|
|
||||||
if state is not None:
|
|
||||||
params['state'] = state
|
|
||||||
|
|
||||||
return self.build_url('/oauth2/authorize', params, WEB_HOST)
|
|
||||||
|
|
||||||
def _finish(self, code, redirect_uri):
|
|
||||||
url = self.build_url('/oauth2/token')
|
|
||||||
params = {'grant_type': 'authorization_code',
|
|
||||||
'code': code,
|
|
||||||
'client_id': self.consumer_key,
|
|
||||||
'client_secret': self.consumer_secret,
|
|
||||||
}
|
|
||||||
if self.locale is not None:
|
|
||||||
params['locale'] = self.locale
|
|
||||||
if redirect_uri is not None:
|
|
||||||
params['redirect_uri'] = redirect_uri
|
|
||||||
|
|
||||||
resp = self.requests_session.post(url, data=params)
|
|
||||||
resp.raise_for_status()
|
|
||||||
|
|
||||||
d = resp.json()
|
|
||||||
|
|
||||||
if 'team_id' in d:
|
|
||||||
account_id = d['team_id']
|
|
||||||
else:
|
|
||||||
account_id = d['account_id']
|
|
||||||
|
|
||||||
access_token = d['access_token']
|
|
||||||
uid = d['uid']
|
|
||||||
|
|
||||||
return OAuth2FlowNoRedirectResult(
|
|
||||||
access_token,
|
|
||||||
account_id,
|
|
||||||
uid)
|
|
||||||
|
|
||||||
def build_path(self, target, params=None):
|
|
||||||
"""Build the path component for an API URL.
|
|
||||||
|
|
||||||
This method urlencodes the parameters, adds them
|
|
||||||
to the end of the target url, and puts a marker for the API
|
|
||||||
version in front.
|
|
||||||
|
|
||||||
:param str target: A target url (e.g. '/files') to build upon.
|
|
||||||
:param dict params: Optional dictionary of parameters (name to value).
|
|
||||||
:return: The path and parameters components of an API URL.
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if six.PY2 and isinstance(target, six.text_type):
|
|
||||||
target = target.encode('utf8')
|
|
||||||
|
|
||||||
target_path = url_path_quote(target)
|
|
||||||
|
|
||||||
params = params or {}
|
|
||||||
params = params.copy()
|
|
||||||
|
|
||||||
if self.locale:
|
|
||||||
params['locale'] = self.locale
|
|
||||||
|
|
||||||
if params:
|
|
||||||
query_string = _params_to_urlencoded(params)
|
|
||||||
return "%s?%s" % (target_path, query_string)
|
|
||||||
else:
|
|
||||||
return target_path
|
|
||||||
|
|
||||||
def build_url(self, target, params=None, host=API_HOST):
|
|
||||||
"""Build an API URL.
|
|
||||||
|
|
||||||
This method adds scheme and hostname to the path
|
|
||||||
returned from build_path.
|
|
||||||
|
|
||||||
:param str target: A target url (e.g. '/files') to build upon.
|
|
||||||
:param dict params: Optional dictionary of parameters (name to value).
|
|
||||||
:return: The full API URL.
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
return "https://%s%s" % (host, self.build_path(target, params))
|
|
||||||
|
|
||||||
|
|
||||||
class DropboxOAuth2FlowNoRedirect(DropboxOAuth2FlowBase):
|
|
||||||
"""
|
|
||||||
OAuth 2 authorization helper for apps that can't provide a redirect URI
|
|
||||||
(such as the command-line example apps).
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
from dropbox import DropboxOAuth2FlowNoRedirect
|
|
||||||
|
|
||||||
auth_flow = DropboxOAuth2FlowNoRedirect(APP_KEY, APP_SECRET)
|
|
||||||
|
|
||||||
authorize_url = auth_flow.start()
|
|
||||||
print "1. Go to: " + authorize_url
|
|
||||||
print "2. Click \\"Allow\\" (you might have to log in first)."
|
|
||||||
print "3. Copy the authorization code."
|
|
||||||
auth_code = raw_input("Enter the authorization code here: ").strip()
|
|
||||||
|
|
||||||
try:
|
|
||||||
oauth_result = auth_flow.finish(auth_code)
|
|
||||||
except Exception, e:
|
|
||||||
print('Error: %s' % (e,))
|
|
||||||
return
|
|
||||||
|
|
||||||
dbx = Dropbox(oauth_result.access_token)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, consumer_key, consumer_secret, locale=None): # noqa: E501; pylint: disable=useless-super-delegation
|
|
||||||
"""
|
|
||||||
Construct an instance.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
:param str consumer_key: Your API app's "app key".
|
|
||||||
:param str consumer_secret: Your API app's "app secret".
|
|
||||||
:param str locale: The locale of the user of your application. For
|
|
||||||
example "en" or "en_US". Some API calls return localized data and
|
|
||||||
error messages; this setting tells the server which locale to use.
|
|
||||||
By default, the server uses "en_US".
|
|
||||||
"""
|
|
||||||
# pylint: disable=useless-super-delegation
|
|
||||||
super(DropboxOAuth2FlowNoRedirect, self).__init__(
|
|
||||||
consumer_key,
|
|
||||||
consumer_secret,
|
|
||||||
locale,
|
|
||||||
)
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"""
|
|
||||||
Starts the OAuth 2 authorization process.
|
|
||||||
|
|
||||||
:return: The URL for a page on Dropbox's website. This page will let
|
|
||||||
the user "approve" your app, which gives your app permission to
|
|
||||||
access the user's Dropbox account. Tell the user to visit this URL
|
|
||||||
and approve your app.
|
|
||||||
"""
|
|
||||||
return self._get_authorize_url(None, None)
|
|
||||||
|
|
||||||
def finish(self, code):
|
|
||||||
"""
|
|
||||||
If the user approves your app, they will be presented with an
|
|
||||||
"authorization code". Have the user copy/paste that authorization code
|
|
||||||
into your app and then call this method to get an access token.
|
|
||||||
|
|
||||||
:param str code: The authorization code shown to the user when they
|
|
||||||
approved your app.
|
|
||||||
:rtype: OAuth2FlowNoRedirectResult
|
|
||||||
:raises: The same exceptions as :meth:`DropboxOAuth2Flow.finish()`.
|
|
||||||
"""
|
|
||||||
return self._finish(code, None)
|
|
||||||
|
|
||||||
|
|
||||||
class DropboxOAuth2Flow(DropboxOAuth2FlowBase):
|
|
||||||
"""
|
|
||||||
OAuth 2 authorization helper. Use this for web apps.
|
|
||||||
|
|
||||||
OAuth 2 has a two-step authorization process. The first step is having the
|
|
||||||
user authorize your app. The second involves getting an OAuth 2 access
|
|
||||||
token from Dropbox.
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
from dropbox import DropboxOAuth2Flow
|
|
||||||
|
|
||||||
def get_dropbox_auth_flow(web_app_session):
|
|
||||||
redirect_uri = "https://my-web-server.org/dropbox-auth-finish"
|
|
||||||
return DropboxOAuth2Flow(
|
|
||||||
APP_KEY, APP_SECRET, redirect_uri, web_app_session,
|
|
||||||
"dropbox-auth-csrf-token")
|
|
||||||
|
|
||||||
# URL handler for /dropbox-auth-start
|
|
||||||
def dropbox_auth_start(web_app_session, request):
|
|
||||||
authorize_url = get_dropbox_auth_flow(web_app_session).start()
|
|
||||||
redirect_to(authorize_url)
|
|
||||||
|
|
||||||
# URL handler for /dropbox-auth-finish
|
|
||||||
def dropbox_auth_finish(web_app_session, request):
|
|
||||||
try:
|
|
||||||
oauth_result = \\
|
|
||||||
get_dropbox_auth_flow(web_app_session).finish(
|
|
||||||
request.query_params)
|
|
||||||
except BadRequestException, e:
|
|
||||||
http_status(400)
|
|
||||||
except BadStateException, e:
|
|
||||||
# Start the auth flow again.
|
|
||||||
redirect_to("/dropbox-auth-start")
|
|
||||||
except CsrfException, e:
|
|
||||||
http_status(403)
|
|
||||||
except NotApprovedException, e:
|
|
||||||
flash('Not approved? Why not?')
|
|
||||||
return redirect_to("/home")
|
|
||||||
except ProviderException, e:
|
|
||||||
logger.log("Auth error: %s" % (e,))
|
|
||||||
http_status(403)
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, consumer_key, consumer_secret, redirect_uri, session,
|
|
||||||
csrf_token_session_key, locale=None):
|
|
||||||
"""
|
|
||||||
Construct an instance.
|
|
||||||
|
|
||||||
:param str consumer_key: Your API app's "app key".
|
|
||||||
:param str consumer_secret: Your API app's "app secret".
|
|
||||||
:param str redirect_uri: The URI that the Dropbox server will redirect
|
|
||||||
the user to after the user finishes authorizing your app. This URI
|
|
||||||
must be HTTPS-based and pre-registered with the Dropbox servers,
|
|
||||||
though localhost URIs are allowed without pre-registration and can
|
|
||||||
be either HTTP or HTTPS.
|
|
||||||
:param dict session: A dict-like object that represents the current
|
|
||||||
user's web session (will be used to save the CSRF token).
|
|
||||||
:param str csrf_token_session_key: The key to use when storing the CSRF
|
|
||||||
token in the session (for example: "dropbox-auth-csrf-token").
|
|
||||||
:param str locale: The locale of the user of your application. For
|
|
||||||
example "en" or "en_US". Some API calls return localized data and
|
|
||||||
error messages; this setting tells the server which locale to use.
|
|
||||||
By default, the server uses "en_US".
|
|
||||||
"""
|
|
||||||
super(DropboxOAuth2Flow, self).__init__(consumer_key, consumer_secret, locale)
|
|
||||||
self.redirect_uri = redirect_uri
|
|
||||||
self.session = session
|
|
||||||
self.csrf_token_session_key = csrf_token_session_key
|
|
||||||
|
|
||||||
def start(self, url_state=None):
|
|
||||||
"""
|
|
||||||
Starts the OAuth 2 authorization process.
|
|
||||||
|
|
||||||
This function builds an "authorization URL". You should redirect your
|
|
||||||
user's browser to this URL, which will give them an opportunity to
|
|
||||||
grant your app access to their Dropbox account. When the user
|
|
||||||
completes this process, they will be automatically redirected to the
|
|
||||||
``redirect_uri`` you passed in to the constructor.
|
|
||||||
|
|
||||||
This function will also save a CSRF token to
|
|
||||||
``session[csrf_token_session_key]`` (as provided to the constructor).
|
|
||||||
This CSRF token will be checked on :meth:`finish()` to prevent request
|
|
||||||
forgery.
|
|
||||||
|
|
||||||
:param str url_state: Any data that you would like to keep in the URL
|
|
||||||
through the authorization process. This exact value will be
|
|
||||||
returned to you by :meth:`finish()`.
|
|
||||||
:return: The URL for a page on Dropbox's website. This page will let
|
|
||||||
the user "approve" your app, which gives your app permission to
|
|
||||||
access the user's Dropbox account. Tell the user to visit this URL
|
|
||||||
and approve your app.
|
|
||||||
"""
|
|
||||||
csrf_token = base64.urlsafe_b64encode(os.urandom(16)).decode('ascii')
|
|
||||||
state = csrf_token
|
|
||||||
if url_state is not None:
|
|
||||||
state += "|" + url_state
|
|
||||||
self.session[self.csrf_token_session_key] = csrf_token
|
|
||||||
|
|
||||||
return self._get_authorize_url(self.redirect_uri, state)
|
|
||||||
|
|
||||||
def finish(self, query_params):
|
|
||||||
"""
|
|
||||||
Call this after the user has visited the authorize URL (see
|
|
||||||
:meth:`start()`), approved your app and was redirected to your redirect
|
|
||||||
URI.
|
|
||||||
|
|
||||||
:param dict query_params: The query parameters on the GET request to
|
|
||||||
your redirect URI.
|
|
||||||
:rtype: OAuth2FlowResult
|
|
||||||
:raises: :class:`BadRequestException` If the redirect URL was missing
|
|
||||||
parameters or if the given parameters were not valid.
|
|
||||||
:raises: :class:`BadStateException` If there's no CSRF token in the
|
|
||||||
session.
|
|
||||||
:raises: :class:`CsrfException` If the ``state`` query parameter
|
|
||||||
doesn't contain the CSRF token from the user's session.
|
|
||||||
:raises: :class:`NotApprovedException` If the user chose not to
|
|
||||||
approve your app.
|
|
||||||
:raises: :class:`ProviderException` If Dropbox redirected to your
|
|
||||||
redirect URI with some unexpected error identifier and error message.
|
|
||||||
"""
|
|
||||||
# Check well-formedness of request.
|
|
||||||
|
|
||||||
state = query_params.get('state')
|
|
||||||
if state is None:
|
|
||||||
raise BadRequestException("Missing query parameter 'state'.")
|
|
||||||
|
|
||||||
error = query_params.get('error')
|
|
||||||
error_description = query_params.get('error_description')
|
|
||||||
code = query_params.get('code')
|
|
||||||
|
|
||||||
if error is not None and code is not None:
|
|
||||||
raise BadRequestException(
|
|
||||||
"Query parameters 'code' and 'error' are both set; "
|
|
||||||
"only one must be set.")
|
|
||||||
if error is None and code is None:
|
|
||||||
raise BadRequestException(
|
|
||||||
"Neither query parameter 'code' or 'error' is set.")
|
|
||||||
|
|
||||||
# Check CSRF token
|
|
||||||
|
|
||||||
if self.csrf_token_session_key not in self.session:
|
|
||||||
raise BadStateException('Missing CSRF token in session.')
|
|
||||||
csrf_token_from_session = self.session[self.csrf_token_session_key]
|
|
||||||
if len(csrf_token_from_session) <= 20:
|
|
||||||
raise AssertionError('CSRF token unexpectedly short: %r' %
|
|
||||||
csrf_token_from_session)
|
|
||||||
|
|
||||||
split_pos = state.find('|')
|
|
||||||
if split_pos < 0:
|
|
||||||
given_csrf_token = state
|
|
||||||
url_state = None
|
|
||||||
else:
|
|
||||||
given_csrf_token = state[0:split_pos]
|
|
||||||
url_state = state[split_pos + 1:]
|
|
||||||
|
|
||||||
if not _safe_equals(csrf_token_from_session, given_csrf_token):
|
|
||||||
raise CsrfException('expected %r, got %r' %
|
|
||||||
(csrf_token_from_session, given_csrf_token))
|
|
||||||
|
|
||||||
del self.session[self.csrf_token_session_key]
|
|
||||||
|
|
||||||
# Check for error identifier
|
|
||||||
|
|
||||||
if error is not None:
|
|
||||||
if error == 'access_denied':
|
|
||||||
# The user clicked "Deny"
|
|
||||||
if error_description is None:
|
|
||||||
raise NotApprovedException(
|
|
||||||
'No additional description from Dropbox')
|
|
||||||
else:
|
|
||||||
raise NotApprovedException(
|
|
||||||
'Additional description from Dropbox: %s' %
|
|
||||||
error_description)
|
|
||||||
else:
|
|
||||||
# All other errors
|
|
||||||
full_message = error
|
|
||||||
if error_description is not None:
|
|
||||||
full_message += ": " + error_description
|
|
||||||
raise ProviderException(full_message)
|
|
||||||
|
|
||||||
# If everything went ok, make the network call to get an access token.
|
|
||||||
|
|
||||||
no_redirect_result = self._finish(code, self.redirect_uri)
|
|
||||||
return OAuth2FlowResult.from_no_redirect_result(
|
|
||||||
no_redirect_result, url_state)
|
|
||||||
|
|
||||||
|
|
||||||
class BadRequestException(Exception):
|
|
||||||
"""
|
|
||||||
Thrown if the redirect URL was missing parameters or if the
|
|
||||||
given parameters were not valid.
|
|
||||||
|
|
||||||
The recommended action is to show an HTTP 400 error page.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BadStateException(Exception):
|
|
||||||
"""
|
|
||||||
Thrown if all the parameters are correct, but there's no CSRF token in the
|
|
||||||
session. This probably means that the session expired.
|
|
||||||
|
|
||||||
The recommended action is to redirect the user's browser to try the
|
|
||||||
approval process again.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CsrfException(Exception):
|
|
||||||
"""
|
|
||||||
Thrown if the given 'state' parameter doesn't contain the CSRF token from
|
|
||||||
the user's session. This is blocked to prevent CSRF attacks.
|
|
||||||
|
|
||||||
The recommended action is to respond with an HTTP 403 error page.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NotApprovedException(Exception):
|
|
||||||
"""
|
|
||||||
The user chose not to approve your app.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ProviderException(Exception):
|
|
||||||
"""
|
|
||||||
Dropbox redirected to your redirect URI with some unexpected error
|
|
||||||
identifier and error message.
|
|
||||||
|
|
||||||
The recommended action is to log the error, tell the user something went
|
|
||||||
wrong, and let them try again.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _safe_equals(a, b):
|
|
||||||
if len(a) != len(b):
|
|
||||||
return False
|
|
||||||
res = 0
|
|
||||||
for ca, cb in zip(a, b):
|
|
||||||
res |= ord(ca) ^ ord(cb)
|
|
||||||
return res == 0
|
|
||||||
|
|
||||||
|
|
||||||
def _params_to_urlencoded(params):
|
|
||||||
"""
|
|
||||||
Returns a application/x-www-form-urlencoded ``str`` representing the
|
|
||||||
key/value pairs in ``params``.
|
|
||||||
|
|
||||||
Keys are values are ``str()``'d before calling ``urllib.urlencode``, with
|
|
||||||
the exception of unicode objects which are utf8-encoded.
|
|
||||||
"""
|
|
||||||
def encode(o):
|
|
||||||
if isinstance(o, six.binary_type):
|
|
||||||
return o
|
|
||||||
else:
|
|
||||||
if isinstance(o, six.text_type):
|
|
||||||
return o.encode('utf-8')
|
|
||||||
else:
|
|
||||||
return str(o).encode('utf-8')
|
|
||||||
|
|
||||||
utf8_params = {encode(k): encode(v) for k, v in six.iteritems(params)}
|
|
||||||
return url_encode(utf8_params)
|
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +0,0 @@
|
|||||||
import resources.lib.utils as utils
|
|
||||||
|
|
||||||
def resource_filename(*args):
|
|
||||||
return utils.addon_dir() + "/resources/lib/dropbox/trusted-certs.crt"
|
|
@ -1,835 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Auto-generated by Stone, do not modify.
|
|
||||||
# flake8: noqa
|
|
||||||
# pylint: skip-file
|
|
||||||
"""
|
|
||||||
This namespace contains helper entities for property and property/template endpoints.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
from . import stone_validators as bv
|
|
||||||
from . import stone_base as bb
|
|
||||||
except (SystemError, ValueError):
|
|
||||||
# Catch errors raised when importing a relative module when not in a package.
|
|
||||||
# This makes testing this file directly (outside of a package) easier.
|
|
||||||
import stone_validators as bv
|
|
||||||
import stone_base as bb
|
|
||||||
|
|
||||||
class GetPropertyTemplateArg(object):
|
|
||||||
"""
|
|
||||||
:ivar template_id: An identifier for property template added by route
|
|
||||||
properties/template/add.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
'_template_id_value',
|
|
||||||
'_template_id_present',
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
template_id=None):
|
|
||||||
self._template_id_value = None
|
|
||||||
self._template_id_present = False
|
|
||||||
if template_id is not None:
|
|
||||||
self.template_id = template_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def template_id(self):
|
|
||||||
"""
|
|
||||||
An identifier for property template added by route
|
|
||||||
properties/template/add.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._template_id_present:
|
|
||||||
return self._template_id_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'template_id'")
|
|
||||||
|
|
||||||
@template_id.setter
|
|
||||||
def template_id(self, val):
|
|
||||||
val = self._template_id_validator.validate(val)
|
|
||||||
self._template_id_value = val
|
|
||||||
self._template_id_present = True
|
|
||||||
|
|
||||||
@template_id.deleter
|
|
||||||
def template_id(self):
|
|
||||||
self._template_id_value = None
|
|
||||||
self._template_id_present = False
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'GetPropertyTemplateArg(template_id={!r})'.format(
|
|
||||||
self._template_id_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
GetPropertyTemplateArg_validator = bv.Struct(GetPropertyTemplateArg)
|
|
||||||
|
|
||||||
class PropertyGroupTemplate(object):
|
|
||||||
"""
|
|
||||||
Describes property templates that can be filled and associated with a file.
|
|
||||||
|
|
||||||
:ivar name: A display name for the property template. Property template
|
|
||||||
names can be up to 256 bytes.
|
|
||||||
:ivar description: Description for new property template. Property template
|
|
||||||
descriptions can be up to 1024 bytes.
|
|
||||||
:ivar fields: This is a list of custom properties associated with a property
|
|
||||||
template. There can be up to 64 properties in a single property
|
|
||||||
template.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
'_name_value',
|
|
||||||
'_name_present',
|
|
||||||
'_description_value',
|
|
||||||
'_description_present',
|
|
||||||
'_fields_value',
|
|
||||||
'_fields_present',
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
name=None,
|
|
||||||
description=None,
|
|
||||||
fields=None):
|
|
||||||
self._name_value = None
|
|
||||||
self._name_present = False
|
|
||||||
self._description_value = None
|
|
||||||
self._description_present = False
|
|
||||||
self._fields_value = None
|
|
||||||
self._fields_present = False
|
|
||||||
if name is not None:
|
|
||||||
self.name = name
|
|
||||||
if description is not None:
|
|
||||||
self.description = description
|
|
||||||
if fields is not None:
|
|
||||||
self.fields = fields
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""
|
|
||||||
A display name for the property template. Property template names can be
|
|
||||||
up to 256 bytes.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._name_present:
|
|
||||||
return self._name_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'name'")
|
|
||||||
|
|
||||||
@name.setter
|
|
||||||
def name(self, val):
|
|
||||||
val = self._name_validator.validate(val)
|
|
||||||
self._name_value = val
|
|
||||||
self._name_present = True
|
|
||||||
|
|
||||||
@name.deleter
|
|
||||||
def name(self):
|
|
||||||
self._name_value = None
|
|
||||||
self._name_present = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def description(self):
|
|
||||||
"""
|
|
||||||
Description for new property template. Property template descriptions
|
|
||||||
can be up to 1024 bytes.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._description_present:
|
|
||||||
return self._description_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'description'")
|
|
||||||
|
|
||||||
@description.setter
|
|
||||||
def description(self, val):
|
|
||||||
val = self._description_validator.validate(val)
|
|
||||||
self._description_value = val
|
|
||||||
self._description_present = True
|
|
||||||
|
|
||||||
@description.deleter
|
|
||||||
def description(self):
|
|
||||||
self._description_value = None
|
|
||||||
self._description_present = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fields(self):
|
|
||||||
"""
|
|
||||||
This is a list of custom properties associated with a property template.
|
|
||||||
There can be up to 64 properties in a single property template.
|
|
||||||
|
|
||||||
:rtype: list of [PropertyFieldTemplate]
|
|
||||||
"""
|
|
||||||
if self._fields_present:
|
|
||||||
return self._fields_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'fields'")
|
|
||||||
|
|
||||||
@fields.setter
|
|
||||||
def fields(self, val):
|
|
||||||
val = self._fields_validator.validate(val)
|
|
||||||
self._fields_value = val
|
|
||||||
self._fields_present = True
|
|
||||||
|
|
||||||
@fields.deleter
|
|
||||||
def fields(self):
|
|
||||||
self._fields_value = None
|
|
||||||
self._fields_present = False
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'PropertyGroupTemplate(name={!r}, description={!r}, fields={!r})'.format(
|
|
||||||
self._name_value,
|
|
||||||
self._description_value,
|
|
||||||
self._fields_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
PropertyGroupTemplate_validator = bv.Struct(PropertyGroupTemplate)
|
|
||||||
|
|
||||||
class GetPropertyTemplateResult(PropertyGroupTemplate):
|
|
||||||
"""
|
|
||||||
The Property template for the specified template.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
name=None,
|
|
||||||
description=None,
|
|
||||||
fields=None):
|
|
||||||
super(GetPropertyTemplateResult, self).__init__(name,
|
|
||||||
description,
|
|
||||||
fields)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'GetPropertyTemplateResult(name={!r}, description={!r}, fields={!r})'.format(
|
|
||||||
self._name_value,
|
|
||||||
self._description_value,
|
|
||||||
self._fields_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
GetPropertyTemplateResult_validator = bv.Struct(GetPropertyTemplateResult)
|
|
||||||
|
|
||||||
class ListPropertyTemplateIds(object):
|
|
||||||
"""
|
|
||||||
:ivar template_ids: List of identifiers for templates added by route
|
|
||||||
properties/template/add.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
'_template_ids_value',
|
|
||||||
'_template_ids_present',
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
template_ids=None):
|
|
||||||
self._template_ids_value = None
|
|
||||||
self._template_ids_present = False
|
|
||||||
if template_ids is not None:
|
|
||||||
self.template_ids = template_ids
|
|
||||||
|
|
||||||
@property
|
|
||||||
def template_ids(self):
|
|
||||||
"""
|
|
||||||
List of identifiers for templates added by route
|
|
||||||
properties/template/add.
|
|
||||||
|
|
||||||
:rtype: list of [str]
|
|
||||||
"""
|
|
||||||
if self._template_ids_present:
|
|
||||||
return self._template_ids_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'template_ids'")
|
|
||||||
|
|
||||||
@template_ids.setter
|
|
||||||
def template_ids(self, val):
|
|
||||||
val = self._template_ids_validator.validate(val)
|
|
||||||
self._template_ids_value = val
|
|
||||||
self._template_ids_present = True
|
|
||||||
|
|
||||||
@template_ids.deleter
|
|
||||||
def template_ids(self):
|
|
||||||
self._template_ids_value = None
|
|
||||||
self._template_ids_present = False
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'ListPropertyTemplateIds(template_ids={!r})'.format(
|
|
||||||
self._template_ids_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
ListPropertyTemplateIds_validator = bv.Struct(ListPropertyTemplateIds)
|
|
||||||
|
|
||||||
class PropertyTemplateError(bb.Union):
|
|
||||||
"""
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar str template_not_found: Property template does not exist for given
|
|
||||||
identifier.
|
|
||||||
:ivar restricted_content: You do not have the permissions to modify this
|
|
||||||
property template.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_catch_all = 'other'
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
restricted_content = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
other = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def template_not_found(cls, val):
|
|
||||||
"""
|
|
||||||
Create an instance of this class set to the ``template_not_found`` tag
|
|
||||||
with value ``val``.
|
|
||||||
|
|
||||||
:param str val:
|
|
||||||
:rtype: PropertyTemplateError
|
|
||||||
"""
|
|
||||||
return cls('template_not_found', val)
|
|
||||||
|
|
||||||
def is_template_not_found(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``template_not_found``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'template_not_found'
|
|
||||||
|
|
||||||
def is_restricted_content(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``restricted_content``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'restricted_content'
|
|
||||||
|
|
||||||
def is_other(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``other``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'other'
|
|
||||||
|
|
||||||
def get_template_not_found(self):
|
|
||||||
"""
|
|
||||||
Property template does not exist for given identifier.
|
|
||||||
|
|
||||||
Only call this if :meth:`is_template_not_found` is true.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if not self.is_template_not_found():
|
|
||||||
raise AttributeError("tag 'template_not_found' not set")
|
|
||||||
return self._value
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'PropertyTemplateError(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
PropertyTemplateError_validator = bv.Union(PropertyTemplateError)
|
|
||||||
|
|
||||||
class ModifyPropertyTemplateError(PropertyTemplateError):
|
|
||||||
"""
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar conflicting_property_names: A property field name already exists in
|
|
||||||
the template.
|
|
||||||
:ivar too_many_properties: There are too many properties in the changed
|
|
||||||
template. The maximum number of properties per template is 32.
|
|
||||||
:ivar too_many_templates: There are too many templates for the team.
|
|
||||||
:ivar template_attribute_too_large: The template name, description or field
|
|
||||||
names is too large.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
conflicting_property_names = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
too_many_properties = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
too_many_templates = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
template_attribute_too_large = None
|
|
||||||
|
|
||||||
def is_conflicting_property_names(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``conflicting_property_names``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'conflicting_property_names'
|
|
||||||
|
|
||||||
def is_too_many_properties(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``too_many_properties``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'too_many_properties'
|
|
||||||
|
|
||||||
def is_too_many_templates(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``too_many_templates``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'too_many_templates'
|
|
||||||
|
|
||||||
def is_template_attribute_too_large(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``template_attribute_too_large``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'template_attribute_too_large'
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'ModifyPropertyTemplateError(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
ModifyPropertyTemplateError_validator = bv.Union(ModifyPropertyTemplateError)
|
|
||||||
|
|
||||||
class PropertyField(object):
|
|
||||||
"""
|
|
||||||
:ivar name: This is the name or key of a custom property in a property
|
|
||||||
template. File property names can be up to 256 bytes.
|
|
||||||
:ivar value: Value of a custom property attached to a file. Values can be up
|
|
||||||
to 1024 bytes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
'_name_value',
|
|
||||||
'_name_present',
|
|
||||||
'_value_value',
|
|
||||||
'_value_present',
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
name=None,
|
|
||||||
value=None):
|
|
||||||
self._name_value = None
|
|
||||||
self._name_present = False
|
|
||||||
self._value_value = None
|
|
||||||
self._value_present = False
|
|
||||||
if name is not None:
|
|
||||||
self.name = name
|
|
||||||
if value is not None:
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""
|
|
||||||
This is the name or key of a custom property in a property template.
|
|
||||||
File property names can be up to 256 bytes.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._name_present:
|
|
||||||
return self._name_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'name'")
|
|
||||||
|
|
||||||
@name.setter
|
|
||||||
def name(self, val):
|
|
||||||
val = self._name_validator.validate(val)
|
|
||||||
self._name_value = val
|
|
||||||
self._name_present = True
|
|
||||||
|
|
||||||
@name.deleter
|
|
||||||
def name(self):
|
|
||||||
self._name_value = None
|
|
||||||
self._name_present = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def value(self):
|
|
||||||
"""
|
|
||||||
Value of a custom property attached to a file. Values can be up to 1024
|
|
||||||
bytes.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._value_present:
|
|
||||||
return self._value_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'value'")
|
|
||||||
|
|
||||||
@value.setter
|
|
||||||
def value(self, val):
|
|
||||||
val = self._value_validator.validate(val)
|
|
||||||
self._value_value = val
|
|
||||||
self._value_present = True
|
|
||||||
|
|
||||||
@value.deleter
|
|
||||||
def value(self):
|
|
||||||
self._value_value = None
|
|
||||||
self._value_present = False
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'PropertyField(name={!r}, value={!r})'.format(
|
|
||||||
self._name_value,
|
|
||||||
self._value_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
PropertyField_validator = bv.Struct(PropertyField)
|
|
||||||
|
|
||||||
class PropertyFieldTemplate(object):
|
|
||||||
"""
|
|
||||||
Describe a single property field type which that can be part of a property
|
|
||||||
template.
|
|
||||||
|
|
||||||
:ivar name: This is the name or key of a custom property in a property
|
|
||||||
template. File property names can be up to 256 bytes.
|
|
||||||
:ivar description: This is the description for a custom property in a
|
|
||||||
property template. File property description can be up to 1024 bytes.
|
|
||||||
:ivar type: This is the data type of the value of this property. This type
|
|
||||||
will be enforced upon property creation and modifications.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
'_name_value',
|
|
||||||
'_name_present',
|
|
||||||
'_description_value',
|
|
||||||
'_description_present',
|
|
||||||
'_type_value',
|
|
||||||
'_type_present',
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
name=None,
|
|
||||||
description=None,
|
|
||||||
type=None):
|
|
||||||
self._name_value = None
|
|
||||||
self._name_present = False
|
|
||||||
self._description_value = None
|
|
||||||
self._description_present = False
|
|
||||||
self._type_value = None
|
|
||||||
self._type_present = False
|
|
||||||
if name is not None:
|
|
||||||
self.name = name
|
|
||||||
if description is not None:
|
|
||||||
self.description = description
|
|
||||||
if type is not None:
|
|
||||||
self.type = type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""
|
|
||||||
This is the name or key of a custom property in a property template.
|
|
||||||
File property names can be up to 256 bytes.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._name_present:
|
|
||||||
return self._name_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'name'")
|
|
||||||
|
|
||||||
@name.setter
|
|
||||||
def name(self, val):
|
|
||||||
val = self._name_validator.validate(val)
|
|
||||||
self._name_value = val
|
|
||||||
self._name_present = True
|
|
||||||
|
|
||||||
@name.deleter
|
|
||||||
def name(self):
|
|
||||||
self._name_value = None
|
|
||||||
self._name_present = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def description(self):
|
|
||||||
"""
|
|
||||||
This is the description for a custom property in a property template.
|
|
||||||
File property description can be up to 1024 bytes.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._description_present:
|
|
||||||
return self._description_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'description'")
|
|
||||||
|
|
||||||
@description.setter
|
|
||||||
def description(self, val):
|
|
||||||
val = self._description_validator.validate(val)
|
|
||||||
self._description_value = val
|
|
||||||
self._description_present = True
|
|
||||||
|
|
||||||
@description.deleter
|
|
||||||
def description(self):
|
|
||||||
self._description_value = None
|
|
||||||
self._description_present = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def type(self):
|
|
||||||
"""
|
|
||||||
This is the data type of the value of this property. This type will be
|
|
||||||
enforced upon property creation and modifications.
|
|
||||||
|
|
||||||
:rtype: PropertyType
|
|
||||||
"""
|
|
||||||
if self._type_present:
|
|
||||||
return self._type_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'type'")
|
|
||||||
|
|
||||||
@type.setter
|
|
||||||
def type(self, val):
|
|
||||||
self._type_validator.validate_type_only(val)
|
|
||||||
self._type_value = val
|
|
||||||
self._type_present = True
|
|
||||||
|
|
||||||
@type.deleter
|
|
||||||
def type(self):
|
|
||||||
self._type_value = None
|
|
||||||
self._type_present = False
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'PropertyFieldTemplate(name={!r}, description={!r}, type={!r})'.format(
|
|
||||||
self._name_value,
|
|
||||||
self._description_value,
|
|
||||||
self._type_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
PropertyFieldTemplate_validator = bv.Struct(PropertyFieldTemplate)
|
|
||||||
|
|
||||||
class PropertyGroup(object):
|
|
||||||
"""
|
|
||||||
Collection of custom properties in filled property templates.
|
|
||||||
|
|
||||||
:ivar template_id: A unique identifier for a property template type.
|
|
||||||
:ivar fields: This is a list of custom properties associated with a file.
|
|
||||||
There can be up to 32 properties for a template.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
'_template_id_value',
|
|
||||||
'_template_id_present',
|
|
||||||
'_fields_value',
|
|
||||||
'_fields_present',
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
template_id=None,
|
|
||||||
fields=None):
|
|
||||||
self._template_id_value = None
|
|
||||||
self._template_id_present = False
|
|
||||||
self._fields_value = None
|
|
||||||
self._fields_present = False
|
|
||||||
if template_id is not None:
|
|
||||||
self.template_id = template_id
|
|
||||||
if fields is not None:
|
|
||||||
self.fields = fields
|
|
||||||
|
|
||||||
@property
|
|
||||||
def template_id(self):
|
|
||||||
"""
|
|
||||||
A unique identifier for a property template type.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._template_id_present:
|
|
||||||
return self._template_id_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'template_id'")
|
|
||||||
|
|
||||||
@template_id.setter
|
|
||||||
def template_id(self, val):
|
|
||||||
val = self._template_id_validator.validate(val)
|
|
||||||
self._template_id_value = val
|
|
||||||
self._template_id_present = True
|
|
||||||
|
|
||||||
@template_id.deleter
|
|
||||||
def template_id(self):
|
|
||||||
self._template_id_value = None
|
|
||||||
self._template_id_present = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fields(self):
|
|
||||||
"""
|
|
||||||
This is a list of custom properties associated with a file. There can be
|
|
||||||
up to 32 properties for a template.
|
|
||||||
|
|
||||||
:rtype: list of [PropertyField]
|
|
||||||
"""
|
|
||||||
if self._fields_present:
|
|
||||||
return self._fields_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'fields'")
|
|
||||||
|
|
||||||
@fields.setter
|
|
||||||
def fields(self, val):
|
|
||||||
val = self._fields_validator.validate(val)
|
|
||||||
self._fields_value = val
|
|
||||||
self._fields_present = True
|
|
||||||
|
|
||||||
@fields.deleter
|
|
||||||
def fields(self):
|
|
||||||
self._fields_value = None
|
|
||||||
self._fields_present = False
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'PropertyGroup(template_id={!r}, fields={!r})'.format(
|
|
||||||
self._template_id_value,
|
|
||||||
self._fields_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
PropertyGroup_validator = bv.Struct(PropertyGroup)
|
|
||||||
|
|
||||||
class PropertyType(bb.Union):
|
|
||||||
"""
|
|
||||||
Data type of the given property added. This endpoint is in beta and only
|
|
||||||
properties of type strings is supported.
|
|
||||||
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar string: The associated property will be of type string. Unicode is
|
|
||||||
supported.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_catch_all = 'other'
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
string = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
other = None
|
|
||||||
|
|
||||||
def is_string(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``string``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'string'
|
|
||||||
|
|
||||||
def is_other(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``other``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'other'
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'PropertyType(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
PropertyType_validator = bv.Union(PropertyType)
|
|
||||||
|
|
||||||
TemplateId_validator = bv.String(min_length=1, pattern=u'(/|ptid:).*')
|
|
||||||
GetPropertyTemplateArg._template_id_validator = TemplateId_validator
|
|
||||||
GetPropertyTemplateArg._all_field_names_ = set(['template_id'])
|
|
||||||
GetPropertyTemplateArg._all_fields_ = [('template_id', GetPropertyTemplateArg._template_id_validator)]
|
|
||||||
|
|
||||||
PropertyGroupTemplate._name_validator = bv.String()
|
|
||||||
PropertyGroupTemplate._description_validator = bv.String()
|
|
||||||
PropertyGroupTemplate._fields_validator = bv.List(PropertyFieldTemplate_validator)
|
|
||||||
PropertyGroupTemplate._all_field_names_ = set([
|
|
||||||
'name',
|
|
||||||
'description',
|
|
||||||
'fields',
|
|
||||||
])
|
|
||||||
PropertyGroupTemplate._all_fields_ = [
|
|
||||||
('name', PropertyGroupTemplate._name_validator),
|
|
||||||
('description', PropertyGroupTemplate._description_validator),
|
|
||||||
('fields', PropertyGroupTemplate._fields_validator),
|
|
||||||
]
|
|
||||||
|
|
||||||
GetPropertyTemplateResult._all_field_names_ = PropertyGroupTemplate._all_field_names_.union(set([]))
|
|
||||||
GetPropertyTemplateResult._all_fields_ = PropertyGroupTemplate._all_fields_ + []
|
|
||||||
|
|
||||||
ListPropertyTemplateIds._template_ids_validator = bv.List(TemplateId_validator)
|
|
||||||
ListPropertyTemplateIds._all_field_names_ = set(['template_ids'])
|
|
||||||
ListPropertyTemplateIds._all_fields_ = [('template_ids', ListPropertyTemplateIds._template_ids_validator)]
|
|
||||||
|
|
||||||
PropertyTemplateError._template_not_found_validator = TemplateId_validator
|
|
||||||
PropertyTemplateError._restricted_content_validator = bv.Void()
|
|
||||||
PropertyTemplateError._other_validator = bv.Void()
|
|
||||||
PropertyTemplateError._tagmap = {
|
|
||||||
'template_not_found': PropertyTemplateError._template_not_found_validator,
|
|
||||||
'restricted_content': PropertyTemplateError._restricted_content_validator,
|
|
||||||
'other': PropertyTemplateError._other_validator,
|
|
||||||
}
|
|
||||||
|
|
||||||
PropertyTemplateError.restricted_content = PropertyTemplateError('restricted_content')
|
|
||||||
PropertyTemplateError.other = PropertyTemplateError('other')
|
|
||||||
|
|
||||||
ModifyPropertyTemplateError._conflicting_property_names_validator = bv.Void()
|
|
||||||
ModifyPropertyTemplateError._too_many_properties_validator = bv.Void()
|
|
||||||
ModifyPropertyTemplateError._too_many_templates_validator = bv.Void()
|
|
||||||
ModifyPropertyTemplateError._template_attribute_too_large_validator = bv.Void()
|
|
||||||
ModifyPropertyTemplateError._tagmap = {
|
|
||||||
'conflicting_property_names': ModifyPropertyTemplateError._conflicting_property_names_validator,
|
|
||||||
'too_many_properties': ModifyPropertyTemplateError._too_many_properties_validator,
|
|
||||||
'too_many_templates': ModifyPropertyTemplateError._too_many_templates_validator,
|
|
||||||
'template_attribute_too_large': ModifyPropertyTemplateError._template_attribute_too_large_validator,
|
|
||||||
}
|
|
||||||
ModifyPropertyTemplateError._tagmap.update(PropertyTemplateError._tagmap)
|
|
||||||
|
|
||||||
ModifyPropertyTemplateError.conflicting_property_names = ModifyPropertyTemplateError('conflicting_property_names')
|
|
||||||
ModifyPropertyTemplateError.too_many_properties = ModifyPropertyTemplateError('too_many_properties')
|
|
||||||
ModifyPropertyTemplateError.too_many_templates = ModifyPropertyTemplateError('too_many_templates')
|
|
||||||
ModifyPropertyTemplateError.template_attribute_too_large = ModifyPropertyTemplateError('template_attribute_too_large')
|
|
||||||
|
|
||||||
PropertyField._name_validator = bv.String()
|
|
||||||
PropertyField._value_validator = bv.String()
|
|
||||||
PropertyField._all_field_names_ = set([
|
|
||||||
'name',
|
|
||||||
'value',
|
|
||||||
])
|
|
||||||
PropertyField._all_fields_ = [
|
|
||||||
('name', PropertyField._name_validator),
|
|
||||||
('value', PropertyField._value_validator),
|
|
||||||
]
|
|
||||||
|
|
||||||
PropertyFieldTemplate._name_validator = bv.String()
|
|
||||||
PropertyFieldTemplate._description_validator = bv.String()
|
|
||||||
PropertyFieldTemplate._type_validator = PropertyType_validator
|
|
||||||
PropertyFieldTemplate._all_field_names_ = set([
|
|
||||||
'name',
|
|
||||||
'description',
|
|
||||||
'type',
|
|
||||||
])
|
|
||||||
PropertyFieldTemplate._all_fields_ = [
|
|
||||||
('name', PropertyFieldTemplate._name_validator),
|
|
||||||
('description', PropertyFieldTemplate._description_validator),
|
|
||||||
('type', PropertyFieldTemplate._type_validator),
|
|
||||||
]
|
|
||||||
|
|
||||||
PropertyGroup._template_id_validator = TemplateId_validator
|
|
||||||
PropertyGroup._fields_validator = bv.List(PropertyField_validator)
|
|
||||||
PropertyGroup._all_field_names_ = set([
|
|
||||||
'template_id',
|
|
||||||
'fields',
|
|
||||||
])
|
|
||||||
PropertyGroup._all_fields_ = [
|
|
||||||
('template_id', PropertyGroup._template_id_validator),
|
|
||||||
('fields', PropertyGroup._fields_validator),
|
|
||||||
]
|
|
||||||
|
|
||||||
PropertyType._string_validator = bv.Void()
|
|
||||||
PropertyType._other_validator = bv.Void()
|
|
||||||
PropertyType._tagmap = {
|
|
||||||
'string': PropertyType._string_validator,
|
|
||||||
'other': PropertyType._other_validator,
|
|
||||||
}
|
|
||||||
|
|
||||||
PropertyType.string = PropertyType('string')
|
|
||||||
PropertyType.other = PropertyType('other')
|
|
||||||
|
|
||||||
ROUTES = {
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
|||||||
from . import pkg_resources
|
|
||||||
import os
|
|
||||||
import ssl
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from requests.adapters import HTTPAdapter
|
|
||||||
from urllib3.poolmanager import PoolManager
|
|
||||||
|
|
||||||
API_DOMAIN = os.environ.get('DROPBOX_API_DOMAIN',
|
|
||||||
os.environ.get('DROPBOX_DOMAIN', '.dropboxapi.com'))
|
|
||||||
|
|
||||||
WEB_DOMAIN = os.environ.get('DROPBOX_WEB_DOMAIN',
|
|
||||||
os.environ.get('DROPBOX_DOMAIN', '.dropbox.com'))
|
|
||||||
|
|
||||||
# Default short hostname for RPC-style routes.
|
|
||||||
HOST_API = 'api'
|
|
||||||
|
|
||||||
# Default short hostname for upload and download-style routes.
|
|
||||||
HOST_CONTENT = 'content'
|
|
||||||
|
|
||||||
# Default short hostname for longpoll routes.
|
|
||||||
HOST_NOTIFY = 'notify'
|
|
||||||
|
|
||||||
# Default short hostname for the Drobox website.
|
|
||||||
HOST_WWW = 'www'
|
|
||||||
|
|
||||||
API_HOST = os.environ.get('DROPBOX_API_HOST', HOST_API + API_DOMAIN)
|
|
||||||
API_CONTENT_HOST = os.environ.get('DROPBOX_API_CONTENT_HOST', HOST_CONTENT + API_DOMAIN)
|
|
||||||
API_NOTIFICATION_HOST = os.environ.get('DROPBOX_API_NOTIFY_HOST', HOST_NOTIFY + API_DOMAIN)
|
|
||||||
WEB_HOST = os.environ.get('DROPBOX_WEB_HOST', HOST_WWW + WEB_DOMAIN)
|
|
||||||
|
|
||||||
_TRUSTED_CERT_FILE = pkg_resources.resource_filename(__name__, 'trusted-certs.crt')
|
|
||||||
|
|
||||||
# TODO(kelkabany): We probably only want to instantiate this once so that even
|
|
||||||
# if multiple Dropbox objects are instantiated, they all share the same pool.
|
|
||||||
class _SSLAdapter(HTTPAdapter):
|
|
||||||
def init_poolmanager(self, connections, maxsize, block=False, **_):
|
|
||||||
self.poolmanager = PoolManager(
|
|
||||||
num_pools=connections,
|
|
||||||
maxsize=maxsize,
|
|
||||||
block=block,
|
|
||||||
cert_reqs=ssl.CERT_REQUIRED,
|
|
||||||
ca_certs=_TRUSTED_CERT_FILE,
|
|
||||||
)
|
|
||||||
|
|
||||||
def pinned_session(pool_maxsize=8):
|
|
||||||
http_adapter = _SSLAdapter(pool_connections=4, pool_maxsize=pool_maxsize)
|
|
||||||
_session = requests.session()
|
|
||||||
_session.mount('https://', http_adapter)
|
|
||||||
|
|
||||||
return _session
|
|
File diff suppressed because it is too large
Load Diff
@ -1,152 +0,0 @@
|
|||||||
"""
|
|
||||||
Helpers for representing Stone data types in Python.
|
|
||||||
|
|
||||||
This module should be dropped into a project that requires the use of Stone. In
|
|
||||||
the future, this could be imported from a pre-installed Python package, rather
|
|
||||||
than being added to a project.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import functools
|
|
||||||
|
|
||||||
try:
|
|
||||||
from . import stone_validators as bv
|
|
||||||
except (ImportError, SystemError, ValueError):
|
|
||||||
# Catch errors raised when importing a relative module when not in a package.
|
|
||||||
# This makes testing this file directly (outside of a package) easier.
|
|
||||||
import stone_validators as bv # type: ignore
|
|
||||||
|
|
||||||
_MYPY = False
|
|
||||||
if _MYPY:
|
|
||||||
import typing # noqa: F401 # pylint: disable=import-error,unused-import,useless-suppression
|
|
||||||
|
|
||||||
class AnnotationType(object):
|
|
||||||
# This is a base class for all annotation types.
|
|
||||||
pass
|
|
||||||
|
|
||||||
if _MYPY:
|
|
||||||
T = typing.TypeVar('T', bound=AnnotationType)
|
|
||||||
U = typing.TypeVar('U')
|
|
||||||
|
|
||||||
class Struct(object):
|
|
||||||
# This is a base class for all classes representing Stone structs.
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
# type: (typing.Type[T], typing.Text, typing.Callable[[T, U], U]) -> None
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Union(object):
|
|
||||||
# TODO(kelkabany): Possible optimization is to remove _value if a
|
|
||||||
# union is composed of only symbols.
|
|
||||||
__slots__ = ['_tag', '_value']
|
|
||||||
_tagmap = {} # type: typing.Dict[typing.Text, bv.Validator]
|
|
||||||
_permissioned_tagmaps = set() # type: typing.Set[typing.Text]
|
|
||||||
|
|
||||||
def __init__(self, tag, value=None):
|
|
||||||
validator = None
|
|
||||||
tagmap_names = ['_{}_tagmap'.format(map_name) for map_name in self._permissioned_tagmaps]
|
|
||||||
for tagmap_name in ['_tagmap'] + tagmap_names:
|
|
||||||
if tag in getattr(self, tagmap_name):
|
|
||||||
validator = getattr(self, tagmap_name)[tag]
|
|
||||||
assert validator is not None, 'Invalid tag %r.' % tag
|
|
||||||
if isinstance(validator, bv.Void):
|
|
||||||
assert value is None, 'Void type union member must have None value.'
|
|
||||||
elif isinstance(validator, (bv.Struct, bv.Union)):
|
|
||||||
validator.validate_type_only(value)
|
|
||||||
else:
|
|
||||||
validator.validate(value)
|
|
||||||
self._tag = tag
|
|
||||||
self._value = value
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
# Also need to check if one class is a subclass of another. If one union extends another,
|
|
||||||
# the common fields should be able to be compared to each other.
|
|
||||||
return (
|
|
||||||
isinstance(other, Union) and
|
|
||||||
(isinstance(self, other.__class__) or isinstance(other, self.__class__)) and
|
|
||||||
self._tag == other._tag and self._value == other._value
|
|
||||||
)
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self == other
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash((self._tag, self._value))
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
# type: (typing.Type[T], typing.Text, typing.Callable[[T, U], U]) -> None
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _is_tag_present(cls, tag, caller_permissions):
|
|
||||||
assert tag, 'tag value should not be None'
|
|
||||||
|
|
||||||
if tag in cls._tagmap:
|
|
||||||
return True
|
|
||||||
|
|
||||||
for extra_permission in caller_permissions.permissions:
|
|
||||||
tagmap_name = '_{}_tagmap'.format(extra_permission)
|
|
||||||
if hasattr(cls, tagmap_name) and tag in getattr(cls, tagmap_name):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _get_val_data_type(cls, tag, caller_permissions):
|
|
||||||
assert tag, 'tag value should not be None'
|
|
||||||
|
|
||||||
for extra_permission in caller_permissions.permissions:
|
|
||||||
tagmap_name = '_{}_tagmap'.format(extra_permission)
|
|
||||||
if hasattr(cls, tagmap_name) and tag in getattr(cls, tagmap_name):
|
|
||||||
return getattr(cls, tagmap_name)[tag]
|
|
||||||
|
|
||||||
return cls._tagmap[tag]
|
|
||||||
|
|
||||||
class Route(object):
|
|
||||||
|
|
||||||
def __init__(self, name, version, deprecated, arg_type, result_type, error_type, attrs):
|
|
||||||
self.name = name
|
|
||||||
self.version = version
|
|
||||||
self.deprecated = deprecated
|
|
||||||
self.arg_type = arg_type
|
|
||||||
self.result_type = result_type
|
|
||||||
self.error_type = error_type
|
|
||||||
assert isinstance(attrs, dict), 'Expected dict, got %r' % attrs
|
|
||||||
self.attrs = attrs
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'Route({!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r})'.format(
|
|
||||||
self.name,
|
|
||||||
self.version,
|
|
||||||
self.deprecated,
|
|
||||||
self.arg_type,
|
|
||||||
self.result_type,
|
|
||||||
self.error_type,
|
|
||||||
self.attrs)
|
|
||||||
|
|
||||||
# helper functions used when constructing custom annotation processors
|
|
||||||
|
|
||||||
# put this here so that every other file doesn't need to import functools
|
|
||||||
partially_apply = functools.partial
|
|
||||||
|
|
||||||
def make_struct_annotation_processor(annotation_type, processor):
|
|
||||||
def g(field_path, struct):
|
|
||||||
if struct is None:
|
|
||||||
return struct
|
|
||||||
struct._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
return struct
|
|
||||||
return g
|
|
||||||
|
|
||||||
def make_list_annotation_processor(processor):
|
|
||||||
def g(field_path, list_):
|
|
||||||
if list_ is None:
|
|
||||||
return list_
|
|
||||||
return [processor('{}[{}]'.format(field_path, idx), x) for idx, x in enumerate(list_)]
|
|
||||||
return g
|
|
||||||
|
|
||||||
def make_map_value_annotation_processor(processor):
|
|
||||||
def g(field_path, map_):
|
|
||||||
if map_ is None:
|
|
||||||
return map_
|
|
||||||
return {k: processor('{}[{}]'.format(field_path, repr(k)), v) for k, v in map_.items()}
|
|
||||||
return g
|
|
File diff suppressed because it is too large
Load Diff
@ -1,673 +0,0 @@
|
|||||||
"""
|
|
||||||
Defines classes to represent each Stone type in Python. These classes should
|
|
||||||
be used to validate Python objects and normalize them for a given type.
|
|
||||||
|
|
||||||
The data types defined here should not be specific to an RPC or serialization
|
|
||||||
format.
|
|
||||||
|
|
||||||
This module should be dropped into a project that requires the use of Stone. In
|
|
||||||
the future, this could be imported from a pre-installed Python package, rather
|
|
||||||
than being added to a project.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
from abc import ABCMeta, abstractmethod
|
|
||||||
import datetime
|
|
||||||
import hashlib
|
|
||||||
import math
|
|
||||||
import numbers
|
|
||||||
import re
|
|
||||||
import six
|
|
||||||
|
|
||||||
_MYPY = False
|
|
||||||
if _MYPY:
|
|
||||||
import typing # noqa: F401 # pylint: disable=import-error,unused-import,useless-suppression
|
|
||||||
|
|
||||||
# See <http://python3porting.com/differences.html#buffer>
|
|
||||||
if six.PY3:
|
|
||||||
_binary_types = (bytes, memoryview) # noqa: E501,F821 # pylint: disable=undefined-variable,useless-suppression
|
|
||||||
else:
|
|
||||||
_binary_types = (bytes, buffer) # noqa: E501,F821 # pylint: disable=undefined-variable,useless-suppression
|
|
||||||
|
|
||||||
|
|
||||||
class ValidationError(Exception):
|
|
||||||
"""Raised when a value doesn't pass validation by its validator."""
|
|
||||||
|
|
||||||
def __init__(self, message, parent=None):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
message (str): Error message detailing validation failure.
|
|
||||||
parent (str): Adds the parent as the closest reference point for
|
|
||||||
the error. Use :meth:`add_parent` to add more.
|
|
||||||
"""
|
|
||||||
super(ValidationError, self).__init__(message)
|
|
||||||
self.message = message
|
|
||||||
self._parents = []
|
|
||||||
if parent:
|
|
||||||
self._parents.append(parent)
|
|
||||||
|
|
||||||
def add_parent(self, parent):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
parent (str): Adds the parent to the top of the tree of references
|
|
||||||
that lead to the validator that failed.
|
|
||||||
"""
|
|
||||||
self._parents.append(parent)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""
|
|
||||||
Returns:
|
|
||||||
str: A descriptive message of the validation error that may also
|
|
||||||
include the path to the validator that failed.
|
|
||||||
"""
|
|
||||||
if self._parents:
|
|
||||||
return '{}: {}'.format('.'.join(self._parents[::-1]), self.message)
|
|
||||||
else:
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
# Not a perfect repr, but includes the error location information.
|
|
||||||
return 'ValidationError(%r)' % six.text_type(self)
|
|
||||||
|
|
||||||
|
|
||||||
def generic_type_name(v):
|
|
||||||
"""Return a descriptive type name that isn't Python specific. For example,
|
|
||||||
an int value will return 'integer' rather than 'int'."""
|
|
||||||
if isinstance(v, bool):
|
|
||||||
# Must come before any numbers checks since booleans are integers too
|
|
||||||
return 'boolean'
|
|
||||||
elif isinstance(v, numbers.Integral):
|
|
||||||
# Must come before real numbers check since integrals are reals too
|
|
||||||
return 'integer'
|
|
||||||
elif isinstance(v, numbers.Real):
|
|
||||||
return 'float'
|
|
||||||
elif isinstance(v, (tuple, list)):
|
|
||||||
return 'list'
|
|
||||||
elif isinstance(v, six.string_types):
|
|
||||||
return 'string'
|
|
||||||
elif v is None:
|
|
||||||
return 'null'
|
|
||||||
else:
|
|
||||||
return type(v).__name__
|
|
||||||
|
|
||||||
|
|
||||||
class Validator(object):
|
|
||||||
"""All primitive and composite data types should be a subclass of this."""
|
|
||||||
__metaclass__ = ABCMeta
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def validate(self, val):
|
|
||||||
"""Validates that val is of this data type.
|
|
||||||
|
|
||||||
Returns: A normalized value if validation succeeds.
|
|
||||||
Raises: ValidationError
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def has_default(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_default(self):
|
|
||||||
raise AssertionError('No default available.')
|
|
||||||
|
|
||||||
|
|
||||||
class Primitive(Validator):
|
|
||||||
"""A basic type that is defined by Stone."""
|
|
||||||
# pylint: disable=abstract-method
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Boolean(Primitive):
|
|
||||||
|
|
||||||
def validate(self, val):
|
|
||||||
if not isinstance(val, bool):
|
|
||||||
raise ValidationError('%r is not a valid boolean' % val)
|
|
||||||
return val
|
|
||||||
|
|
||||||
|
|
||||||
class Integer(Primitive):
|
|
||||||
"""
|
|
||||||
Do not use this class directly. Extend it and specify a 'minimum' and
|
|
||||||
'maximum' value as class variables for a more restrictive integer range.
|
|
||||||
"""
|
|
||||||
minimum = None # type: typing.Optional[int]
|
|
||||||
maximum = None # type: typing.Optional[int]
|
|
||||||
|
|
||||||
def __init__(self, min_value=None, max_value=None):
|
|
||||||
"""
|
|
||||||
A more restrictive minimum or maximum value can be specified than the
|
|
||||||
range inherent to the defined type.
|
|
||||||
"""
|
|
||||||
if min_value is not None:
|
|
||||||
assert isinstance(min_value, numbers.Integral), \
|
|
||||||
'min_value must be an integral number'
|
|
||||||
assert min_value >= self.minimum, \
|
|
||||||
'min_value cannot be less than the minimum value for this ' \
|
|
||||||
'type (%d < %d)' % (min_value, self.minimum)
|
|
||||||
self.minimum = min_value
|
|
||||||
if max_value is not None:
|
|
||||||
assert isinstance(max_value, numbers.Integral), \
|
|
||||||
'max_value must be an integral number'
|
|
||||||
assert max_value <= self.maximum, \
|
|
||||||
'max_value cannot be greater than the maximum value for ' \
|
|
||||||
'this type (%d < %d)' % (max_value, self.maximum)
|
|
||||||
self.maximum = max_value
|
|
||||||
|
|
||||||
def validate(self, val):
|
|
||||||
if not isinstance(val, numbers.Integral):
|
|
||||||
raise ValidationError('expected integer, got %s'
|
|
||||||
% generic_type_name(val))
|
|
||||||
elif not (self.minimum <= val <= self.maximum):
|
|
||||||
raise ValidationError('%d is not within range [%d, %d]'
|
|
||||||
% (val, self.minimum, self.maximum))
|
|
||||||
return val
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '%s()' % self.__class__.__name__
|
|
||||||
|
|
||||||
|
|
||||||
class Int32(Integer):
|
|
||||||
minimum = -2**31
|
|
||||||
maximum = 2**31 - 1
|
|
||||||
|
|
||||||
|
|
||||||
class UInt32(Integer):
|
|
||||||
minimum = 0
|
|
||||||
maximum = 2**32 - 1
|
|
||||||
|
|
||||||
|
|
||||||
class Int64(Integer):
|
|
||||||
minimum = -2**63
|
|
||||||
maximum = 2**63 - 1
|
|
||||||
|
|
||||||
|
|
||||||
class UInt64(Integer):
|
|
||||||
minimum = 0
|
|
||||||
maximum = 2**64 - 1
|
|
||||||
|
|
||||||
|
|
||||||
class Real(Primitive):
|
|
||||||
"""
|
|
||||||
Do not use this class directly. Extend it and optionally set a 'minimum'
|
|
||||||
and 'maximum' value to enforce a range that's a subset of the Python float
|
|
||||||
implementation. Python floats are doubles.
|
|
||||||
"""
|
|
||||||
minimum = None # type: typing.Optional[float]
|
|
||||||
maximum = None # type: typing.Optional[float]
|
|
||||||
|
|
||||||
def __init__(self, min_value=None, max_value=None):
|
|
||||||
"""
|
|
||||||
A more restrictive minimum or maximum value can be specified than the
|
|
||||||
range inherent to the defined type.
|
|
||||||
"""
|
|
||||||
if min_value is not None:
|
|
||||||
assert isinstance(min_value, numbers.Real), \
|
|
||||||
'min_value must be a real number'
|
|
||||||
if not isinstance(min_value, float):
|
|
||||||
try:
|
|
||||||
min_value = float(min_value)
|
|
||||||
except OverflowError:
|
|
||||||
raise AssertionError('min_value is too small for a float')
|
|
||||||
if self.minimum is not None and min_value < self.minimum:
|
|
||||||
raise AssertionError('min_value cannot be less than the '
|
|
||||||
'minimum value for this type (%f < %f)' %
|
|
||||||
(min_value, self.minimum))
|
|
||||||
self.minimum = min_value
|
|
||||||
if max_value is not None:
|
|
||||||
assert isinstance(max_value, numbers.Real), \
|
|
||||||
'max_value must be a real number'
|
|
||||||
if not isinstance(max_value, float):
|
|
||||||
try:
|
|
||||||
max_value = float(max_value)
|
|
||||||
except OverflowError:
|
|
||||||
raise AssertionError('max_value is too large for a float')
|
|
||||||
if self.maximum is not None and max_value > self.maximum:
|
|
||||||
raise AssertionError('max_value cannot be greater than the '
|
|
||||||
'maximum value for this type (%f < %f)' %
|
|
||||||
(max_value, self.maximum))
|
|
||||||
self.maximum = max_value
|
|
||||||
|
|
||||||
def validate(self, val):
|
|
||||||
if not isinstance(val, numbers.Real):
|
|
||||||
raise ValidationError('expected real number, got %s' %
|
|
||||||
generic_type_name(val))
|
|
||||||
if not isinstance(val, float):
|
|
||||||
# This checks for the case where a number is passed in with a
|
|
||||||
# magnitude larger than supported by float64.
|
|
||||||
try:
|
|
||||||
val = float(val)
|
|
||||||
except OverflowError:
|
|
||||||
raise ValidationError('too large for float')
|
|
||||||
if math.isnan(val) or math.isinf(val):
|
|
||||||
raise ValidationError('%f values are not supported' % val)
|
|
||||||
if self.minimum is not None and val < self.minimum:
|
|
||||||
raise ValidationError('%f is not greater than %f' %
|
|
||||||
(val, self.minimum))
|
|
||||||
if self.maximum is not None and val > self.maximum:
|
|
||||||
raise ValidationError('%f is not less than %f' %
|
|
||||||
(val, self.maximum))
|
|
||||||
return val
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '%s()' % self.__class__.__name__
|
|
||||||
|
|
||||||
|
|
||||||
class Float32(Real):
|
|
||||||
# Maximum and minimums from the IEEE 754-1985 standard
|
|
||||||
minimum = -3.40282 * 10**38
|
|
||||||
maximum = 3.40282 * 10**38
|
|
||||||
|
|
||||||
|
|
||||||
class Float64(Real):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class String(Primitive):
|
|
||||||
"""Represents a unicode string."""
|
|
||||||
|
|
||||||
def __init__(self, min_length=None, max_length=None, pattern=None):
|
|
||||||
if min_length is not None:
|
|
||||||
assert isinstance(min_length, numbers.Integral), \
|
|
||||||
'min_length must be an integral number'
|
|
||||||
assert min_length >= 0, 'min_length must be >= 0'
|
|
||||||
if max_length is not None:
|
|
||||||
assert isinstance(max_length, numbers.Integral), \
|
|
||||||
'max_length must be an integral number'
|
|
||||||
assert max_length > 0, 'max_length must be > 0'
|
|
||||||
if min_length and max_length:
|
|
||||||
assert max_length >= min_length, 'max_length must be >= min_length'
|
|
||||||
if pattern is not None:
|
|
||||||
assert isinstance(pattern, six.string_types), \
|
|
||||||
'pattern must be a string'
|
|
||||||
|
|
||||||
self.min_length = min_length
|
|
||||||
self.max_length = max_length
|
|
||||||
self.pattern = pattern
|
|
||||||
self.pattern_re = None
|
|
||||||
|
|
||||||
if pattern:
|
|
||||||
try:
|
|
||||||
self.pattern_re = re.compile(r"\A(?:" + pattern + r")\Z")
|
|
||||||
except re.error as e:
|
|
||||||
raise AssertionError('Regex {!r} failed: {}'.format(
|
|
||||||
pattern, e.args[0]))
|
|
||||||
|
|
||||||
def validate(self, val):
|
|
||||||
"""
|
|
||||||
A unicode string of the correct length and pattern will pass validation.
|
|
||||||
In PY2, we enforce that a str type must be valid utf-8, and a unicode
|
|
||||||
string will be returned.
|
|
||||||
"""
|
|
||||||
if not isinstance(val, six.string_types):
|
|
||||||
raise ValidationError("'%s' expected to be a string, got %s"
|
|
||||||
% (val, generic_type_name(val)))
|
|
||||||
if not six.PY3 and isinstance(val, str):
|
|
||||||
try:
|
|
||||||
val = val.decode('utf-8')
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
raise ValidationError("'%s' was not valid utf-8")
|
|
||||||
|
|
||||||
if self.max_length is not None and len(val) > self.max_length:
|
|
||||||
raise ValidationError("'%s' must be at most %d characters, got %d"
|
|
||||||
% (val, self.max_length, len(val)))
|
|
||||||
if self.min_length is not None and len(val) < self.min_length:
|
|
||||||
raise ValidationError("'%s' must be at least %d characters, got %d"
|
|
||||||
% (val, self.min_length, len(val)))
|
|
||||||
|
|
||||||
if self.pattern and not self.pattern_re.match(val):
|
|
||||||
raise ValidationError("'%s' did not match pattern '%s'"
|
|
||||||
% (val, self.pattern))
|
|
||||||
return val
|
|
||||||
|
|
||||||
|
|
||||||
class Bytes(Primitive):
|
|
||||||
|
|
||||||
def __init__(self, min_length=None, max_length=None):
|
|
||||||
if min_length is not None:
|
|
||||||
assert isinstance(min_length, numbers.Integral), \
|
|
||||||
'min_length must be an integral number'
|
|
||||||
assert min_length >= 0, 'min_length must be >= 0'
|
|
||||||
if max_length is not None:
|
|
||||||
assert isinstance(max_length, numbers.Integral), \
|
|
||||||
'max_length must be an integral number'
|
|
||||||
assert max_length > 0, 'max_length must be > 0'
|
|
||||||
if min_length is not None and max_length is not None:
|
|
||||||
assert max_length >= min_length, 'max_length must be >= min_length'
|
|
||||||
|
|
||||||
self.min_length = min_length
|
|
||||||
self.max_length = max_length
|
|
||||||
|
|
||||||
def validate(self, val):
|
|
||||||
if not isinstance(val, _binary_types):
|
|
||||||
raise ValidationError("expected bytes type, got %s"
|
|
||||||
% generic_type_name(val))
|
|
||||||
elif self.max_length is not None and len(val) > self.max_length:
|
|
||||||
raise ValidationError("'%s' must have at most %d bytes, got %d"
|
|
||||||
% (val, self.max_length, len(val)))
|
|
||||||
elif self.min_length is not None and len(val) < self.min_length:
|
|
||||||
raise ValidationError("'%s' has fewer than %d bytes, got %d"
|
|
||||||
% (val, self.min_length, len(val)))
|
|
||||||
return val
|
|
||||||
|
|
||||||
|
|
||||||
class Timestamp(Primitive):
|
|
||||||
"""Note that while a format is specified, it isn't used in validation
|
|
||||||
since a native Python datetime object is preferred. The format, however,
|
|
||||||
can and should be used by serializers."""
|
|
||||||
|
|
||||||
def __init__(self, fmt):
|
|
||||||
"""fmt must be composed of format codes that the C standard (1989)
|
|
||||||
supports, most notably in its strftime() function."""
|
|
||||||
assert isinstance(fmt, six.text_type), 'format must be a string'
|
|
||||||
self.format = fmt
|
|
||||||
|
|
||||||
def validate(self, val):
|
|
||||||
if not isinstance(val, datetime.datetime):
|
|
||||||
raise ValidationError('expected timestamp, got %s'
|
|
||||||
% generic_type_name(val))
|
|
||||||
elif val.tzinfo is not None and \
|
|
||||||
val.tzinfo.utcoffset(val).total_seconds() != 0:
|
|
||||||
raise ValidationError('timestamp should have either a UTC '
|
|
||||||
'timezone or none set at all')
|
|
||||||
return val
|
|
||||||
|
|
||||||
|
|
||||||
class Composite(Validator):
|
|
||||||
"""Validator for a type that builds on other primitive and composite
|
|
||||||
types."""
|
|
||||||
# pylint: disable=abstract-method
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class List(Composite):
|
|
||||||
"""Assumes list contents are homogeneous with respect to types."""
|
|
||||||
|
|
||||||
def __init__(self, item_validator, min_items=None, max_items=None):
|
|
||||||
"""Every list item will be validated with item_validator."""
|
|
||||||
self.item_validator = item_validator
|
|
||||||
if min_items is not None:
|
|
||||||
assert isinstance(min_items, numbers.Integral), \
|
|
||||||
'min_items must be an integral number'
|
|
||||||
assert min_items >= 0, 'min_items must be >= 0'
|
|
||||||
if max_items is not None:
|
|
||||||
assert isinstance(max_items, numbers.Integral), \
|
|
||||||
'max_items must be an integral number'
|
|
||||||
assert max_items > 0, 'max_items must be > 0'
|
|
||||||
if min_items is not None and max_items is not None:
|
|
||||||
assert max_items >= min_items, 'max_items must be >= min_items'
|
|
||||||
|
|
||||||
self.min_items = min_items
|
|
||||||
self.max_items = max_items
|
|
||||||
|
|
||||||
def validate(self, val):
|
|
||||||
if not isinstance(val, (tuple, list)):
|
|
||||||
raise ValidationError('%r is not a valid list' % val)
|
|
||||||
elif self.max_items is not None and len(val) > self.max_items:
|
|
||||||
raise ValidationError('%r has more than %s items'
|
|
||||||
% (val, self.max_items))
|
|
||||||
elif self.min_items is not None and len(val) < self.min_items:
|
|
||||||
raise ValidationError('%r has fewer than %s items'
|
|
||||||
% (val, self.min_items))
|
|
||||||
return [self.item_validator.validate(item) for item in val]
|
|
||||||
|
|
||||||
|
|
||||||
class Map(Composite):
|
|
||||||
"""Assumes map keys and values are homogeneous with respect to types."""
|
|
||||||
|
|
||||||
def __init__(self, key_validator, value_validator):
|
|
||||||
"""
|
|
||||||
Every Map key/value pair will be validated with item_validator.
|
|
||||||
key validators must be a subclass of a String validator
|
|
||||||
"""
|
|
||||||
self.key_validator = key_validator
|
|
||||||
self.value_validator = value_validator
|
|
||||||
|
|
||||||
def validate(self, val):
|
|
||||||
if not isinstance(val, dict):
|
|
||||||
raise ValidationError('%r is not a valid dict' % val)
|
|
||||||
return {
|
|
||||||
self.key_validator.validate(key):
|
|
||||||
self.value_validator.validate(value) for key, value in val.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Struct(Composite):
|
|
||||||
|
|
||||||
def __init__(self, definition):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
definition (class): A generated class representing a Stone struct
|
|
||||||
from a spec. Must have a _fields_ attribute with the following
|
|
||||||
structure:
|
|
||||||
|
|
||||||
_fields_ = [(field_name, validator), ...]
|
|
||||||
|
|
||||||
where
|
|
||||||
field_name: Name of the field (str).
|
|
||||||
validator: Validator object.
|
|
||||||
"""
|
|
||||||
super(Struct, self).__init__()
|
|
||||||
self.definition = definition
|
|
||||||
|
|
||||||
def validate(self, val):
|
|
||||||
"""
|
|
||||||
For a val to pass validation, val must be of the correct type and have
|
|
||||||
all required fields present.
|
|
||||||
"""
|
|
||||||
self.validate_type_only(val)
|
|
||||||
self.validate_fields_only(val)
|
|
||||||
return val
|
|
||||||
|
|
||||||
def validate_with_permissions(self, val, caller_permissions):
|
|
||||||
"""
|
|
||||||
For a val to pass validation, val must be of the correct type and have
|
|
||||||
all required permissioned fields present. Should only be called
|
|
||||||
for callers with extra permissions.
|
|
||||||
"""
|
|
||||||
self.validate(val)
|
|
||||||
self.validate_fields_only_with_permissions(val, caller_permissions)
|
|
||||||
return val
|
|
||||||
|
|
||||||
def validate_fields_only(self, val):
|
|
||||||
"""
|
|
||||||
To pass field validation, no required field should be missing.
|
|
||||||
|
|
||||||
This method assumes that the contents of each field have already been
|
|
||||||
validated on assignment, so it's merely a presence check.
|
|
||||||
|
|
||||||
FIXME(kelkabany): Since the definition object does not maintain a list
|
|
||||||
of which fields are required, all fields are scanned.
|
|
||||||
"""
|
|
||||||
for field_name in self.definition._all_field_names_:
|
|
||||||
if not hasattr(val, field_name):
|
|
||||||
raise ValidationError("missing required field '%s'" %
|
|
||||||
field_name)
|
|
||||||
|
|
||||||
def validate_fields_only_with_permissions(self, val, caller_permissions):
|
|
||||||
"""
|
|
||||||
To pass field validation, no required field should be missing.
|
|
||||||
This method assumes that the contents of each field have already been
|
|
||||||
validated on assignment, so it's merely a presence check.
|
|
||||||
Should only be called for callers with extra permissions.
|
|
||||||
"""
|
|
||||||
self.validate_fields_only(val)
|
|
||||||
|
|
||||||
# check if type has been patched
|
|
||||||
for extra_permission in caller_permissions.permissions:
|
|
||||||
all_field_names = '_all_{}_field_names_'.format(extra_permission)
|
|
||||||
for field_name in getattr(self.definition, all_field_names, set()):
|
|
||||||
if not hasattr(val, field_name):
|
|
||||||
raise ValidationError("missing required field '%s'" % field_name)
|
|
||||||
|
|
||||||
def validate_type_only(self, val):
|
|
||||||
"""
|
|
||||||
Use this when you only want to validate that the type of an object
|
|
||||||
is correct, but not yet validate each field.
|
|
||||||
"""
|
|
||||||
# Since the definition maintains the list of fields for serialization,
|
|
||||||
# we're okay with a subclass that might have extra information. This
|
|
||||||
# makes it easier to return one subclass for two routes, one of which
|
|
||||||
# relies on the parent class.
|
|
||||||
if not isinstance(val, self.definition):
|
|
||||||
raise ValidationError('expected type %s, got %s' %
|
|
||||||
(self.definition.__name__, generic_type_name(val)))
|
|
||||||
|
|
||||||
def has_default(self):
|
|
||||||
return not self.definition._has_required_fields
|
|
||||||
|
|
||||||
def get_default(self):
|
|
||||||
assert not self.definition._has_required_fields, 'No default available.'
|
|
||||||
return self.definition()
|
|
||||||
|
|
||||||
|
|
||||||
class StructTree(Struct):
|
|
||||||
"""Validator for structs with enumerated subtypes.
|
|
||||||
|
|
||||||
NOTE: validate_fields_only() validates the fields known to this base
|
|
||||||
struct, but does not do any validation specific to the subtype.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# See PyCQA/pylint#1043 for why this is disabled; this should show up
|
|
||||||
# as a usless-suppression (and can be removed) once a fix is released
|
|
||||||
def __init__(self, definition): # pylint: disable=useless-super-delegation
|
|
||||||
super(StructTree, self).__init__(definition)
|
|
||||||
|
|
||||||
|
|
||||||
class Union(Composite):
|
|
||||||
|
|
||||||
def __init__(self, definition):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
definition (class): A generated class representing a Stone union
|
|
||||||
from a spec. Must have a _tagmap attribute with the following
|
|
||||||
structure:
|
|
||||||
|
|
||||||
_tagmap = {field_name: validator, ...}
|
|
||||||
|
|
||||||
where
|
|
||||||
field_name (str): Tag name.
|
|
||||||
validator (Validator): Tag value validator.
|
|
||||||
"""
|
|
||||||
self.definition = definition
|
|
||||||
|
|
||||||
def validate(self, val):
|
|
||||||
"""
|
|
||||||
For a val to pass validation, it must have a _tag set. This assumes
|
|
||||||
that the object validated that _tag is a valid tag, and that any
|
|
||||||
associated value has also been validated.
|
|
||||||
"""
|
|
||||||
self.validate_type_only(val)
|
|
||||||
if not hasattr(val, '_tag') or val._tag is None:
|
|
||||||
raise ValidationError('no tag set')
|
|
||||||
return val
|
|
||||||
|
|
||||||
def validate_type_only(self, val):
|
|
||||||
"""
|
|
||||||
Use this when you only want to validate that the type of an object
|
|
||||||
is correct, but not yet validate each field.
|
|
||||||
|
|
||||||
We check whether val is a Python parent class of the definition. This
|
|
||||||
is because Union subtyping works in the opposite direction of Python
|
|
||||||
inheritance. For example, if a union U2 extends U1 in Python, this
|
|
||||||
validator will accept U1 in places where U2 is expected.
|
|
||||||
"""
|
|
||||||
if not issubclass(self.definition, type(val)):
|
|
||||||
raise ValidationError('expected type %s or subtype, got %s' %
|
|
||||||
(self.definition.__name__, generic_type_name(val)))
|
|
||||||
|
|
||||||
|
|
||||||
class Void(Primitive):
|
|
||||||
|
|
||||||
def validate(self, val):
|
|
||||||
if val is not None:
|
|
||||||
raise ValidationError('expected NoneType, got %s' %
|
|
||||||
generic_type_name(val))
|
|
||||||
|
|
||||||
def has_default(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_default(self):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class Nullable(Validator):
|
|
||||||
|
|
||||||
def __init__(self, validator):
|
|
||||||
assert isinstance(validator, (Primitive, Composite)), \
|
|
||||||
'validator must be for a primitive or composite type'
|
|
||||||
assert not isinstance(validator, Nullable), \
|
|
||||||
'nullables cannot be stacked'
|
|
||||||
assert not isinstance(validator, Void), \
|
|
||||||
'void cannot be made nullable'
|
|
||||||
self.validator = validator
|
|
||||||
|
|
||||||
def validate(self, val):
|
|
||||||
if val is None:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
return self.validator.validate(val)
|
|
||||||
|
|
||||||
def validate_type_only(self, val):
|
|
||||||
"""Use this only if Nullable is wrapping a Composite."""
|
|
||||||
if val is None:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
return self.validator.validate_type_only(val)
|
|
||||||
|
|
||||||
def has_default(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_default(self):
|
|
||||||
return None
|
|
||||||
|
|
||||||
class Redactor(object):
|
|
||||||
def __init__(self, regex):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
regex: What parts of the field to redact.
|
|
||||||
"""
|
|
||||||
self.regex = regex
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def apply(self, val):
|
|
||||||
"""Redacts information from annotated field.
|
|
||||||
Returns: A redacted version of the string provided.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _get_matches(self, val):
|
|
||||||
if not self.regex:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
return re.search(self.regex, val)
|
|
||||||
except TypeError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class HashRedactor(Redactor):
|
|
||||||
def apply(self, val):
|
|
||||||
matches = self._get_matches(val)
|
|
||||||
|
|
||||||
val_to_hash = str(val) if isinstance(val, int) or isinstance(val, float) else val
|
|
||||||
|
|
||||||
try:
|
|
||||||
# add string literal to ensure unicode
|
|
||||||
hashed = hashlib.md5(val_to_hash.encode('utf-8')).hexdigest() + ''
|
|
||||||
except [AttributeError, ValueError]:
|
|
||||||
hashed = None
|
|
||||||
|
|
||||||
if matches:
|
|
||||||
blotted = '***'.join(matches.groups())
|
|
||||||
if hashed:
|
|
||||||
return '{} ({})'.format(hashed, blotted)
|
|
||||||
return blotted
|
|
||||||
return hashed
|
|
||||||
|
|
||||||
|
|
||||||
class BlotRedactor(Redactor):
|
|
||||||
def apply(self, val):
|
|
||||||
matches = self._get_matches(val)
|
|
||||||
if matches:
|
|
||||||
return '***'.join(matches.groups())
|
|
||||||
return '********'
|
|
File diff suppressed because it is too large
Load Diff
@ -1,572 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Auto-generated by Stone, do not modify.
|
|
||||||
# @generated
|
|
||||||
# flake8: noqa
|
|
||||||
# pylint: skip-file
|
|
||||||
try:
|
|
||||||
from . import stone_validators as bv
|
|
||||||
from . import stone_base as bb
|
|
||||||
except (ImportError, SystemError, ValueError):
|
|
||||||
# Catch errors raised when importing a relative module when not in a package.
|
|
||||||
# This makes testing this file directly (outside of a package) easier.
|
|
||||||
import stone_validators as bv
|
|
||||||
import stone_base as bb
|
|
||||||
|
|
||||||
try:
|
|
||||||
from . import (
|
|
||||||
common,
|
|
||||||
)
|
|
||||||
except (ImportError, SystemError, ValueError):
|
|
||||||
import common
|
|
||||||
|
|
||||||
class GroupManagementType(bb.Union):
|
|
||||||
"""
|
|
||||||
The group type determines how a group is managed.
|
|
||||||
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar team_common.GroupManagementType.user_managed: A group which is managed
|
|
||||||
by selected users.
|
|
||||||
:ivar team_common.GroupManagementType.company_managed: A group which is
|
|
||||||
managed by team admins only.
|
|
||||||
:ivar team_common.GroupManagementType.system_managed: A group which is
|
|
||||||
managed automatically by Dropbox.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_catch_all = 'other'
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
user_managed = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
company_managed = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
system_managed = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
other = None
|
|
||||||
|
|
||||||
def is_user_managed(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``user_managed``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'user_managed'
|
|
||||||
|
|
||||||
def is_company_managed(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``company_managed``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'company_managed'
|
|
||||||
|
|
||||||
def is_system_managed(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``system_managed``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'system_managed'
|
|
||||||
|
|
||||||
def is_other(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``other``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'other'
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(GroupManagementType, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'GroupManagementType(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
GroupManagementType_validator = bv.Union(GroupManagementType)
|
|
||||||
|
|
||||||
class GroupSummary(bb.Struct):
|
|
||||||
"""
|
|
||||||
Information about a group.
|
|
||||||
|
|
||||||
:ivar team_common.GroupSummary.group_external_id: External ID of group. This
|
|
||||||
is an arbitrary ID that an admin can attach to a group.
|
|
||||||
:ivar team_common.GroupSummary.member_count: The number of members in the
|
|
||||||
group.
|
|
||||||
:ivar team_common.GroupSummary.group_management_type: Who is allowed to
|
|
||||||
manage the group.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
'_group_name_value',
|
|
||||||
'_group_name_present',
|
|
||||||
'_group_id_value',
|
|
||||||
'_group_id_present',
|
|
||||||
'_group_external_id_value',
|
|
||||||
'_group_external_id_present',
|
|
||||||
'_member_count_value',
|
|
||||||
'_member_count_present',
|
|
||||||
'_group_management_type_value',
|
|
||||||
'_group_management_type_present',
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = True
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
group_name=None,
|
|
||||||
group_id=None,
|
|
||||||
group_management_type=None,
|
|
||||||
group_external_id=None,
|
|
||||||
member_count=None):
|
|
||||||
self._group_name_value = None
|
|
||||||
self._group_name_present = False
|
|
||||||
self._group_id_value = None
|
|
||||||
self._group_id_present = False
|
|
||||||
self._group_external_id_value = None
|
|
||||||
self._group_external_id_present = False
|
|
||||||
self._member_count_value = None
|
|
||||||
self._member_count_present = False
|
|
||||||
self._group_management_type_value = None
|
|
||||||
self._group_management_type_present = False
|
|
||||||
if group_name is not None:
|
|
||||||
self.group_name = group_name
|
|
||||||
if group_id is not None:
|
|
||||||
self.group_id = group_id
|
|
||||||
if group_external_id is not None:
|
|
||||||
self.group_external_id = group_external_id
|
|
||||||
if member_count is not None:
|
|
||||||
self.member_count = member_count
|
|
||||||
if group_management_type is not None:
|
|
||||||
self.group_management_type = group_management_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def group_name(self):
|
|
||||||
"""
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._group_name_present:
|
|
||||||
return self._group_name_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'group_name'")
|
|
||||||
|
|
||||||
@group_name.setter
|
|
||||||
def group_name(self, val):
|
|
||||||
val = self._group_name_validator.validate(val)
|
|
||||||
self._group_name_value = val
|
|
||||||
self._group_name_present = True
|
|
||||||
|
|
||||||
@group_name.deleter
|
|
||||||
def group_name(self):
|
|
||||||
self._group_name_value = None
|
|
||||||
self._group_name_present = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def group_id(self):
|
|
||||||
"""
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._group_id_present:
|
|
||||||
return self._group_id_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'group_id'")
|
|
||||||
|
|
||||||
@group_id.setter
|
|
||||||
def group_id(self, val):
|
|
||||||
val = self._group_id_validator.validate(val)
|
|
||||||
self._group_id_value = val
|
|
||||||
self._group_id_present = True
|
|
||||||
|
|
||||||
@group_id.deleter
|
|
||||||
def group_id(self):
|
|
||||||
self._group_id_value = None
|
|
||||||
self._group_id_present = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def group_external_id(self):
|
|
||||||
"""
|
|
||||||
External ID of group. This is an arbitrary ID that an admin can attach
|
|
||||||
to a group.
|
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
"""
|
|
||||||
if self._group_external_id_present:
|
|
||||||
return self._group_external_id_value
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@group_external_id.setter
|
|
||||||
def group_external_id(self, val):
|
|
||||||
if val is None:
|
|
||||||
del self.group_external_id
|
|
||||||
return
|
|
||||||
val = self._group_external_id_validator.validate(val)
|
|
||||||
self._group_external_id_value = val
|
|
||||||
self._group_external_id_present = True
|
|
||||||
|
|
||||||
@group_external_id.deleter
|
|
||||||
def group_external_id(self):
|
|
||||||
self._group_external_id_value = None
|
|
||||||
self._group_external_id_present = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def member_count(self):
|
|
||||||
"""
|
|
||||||
The number of members in the group.
|
|
||||||
|
|
||||||
:rtype: int
|
|
||||||
"""
|
|
||||||
if self._member_count_present:
|
|
||||||
return self._member_count_value
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@member_count.setter
|
|
||||||
def member_count(self, val):
|
|
||||||
if val is None:
|
|
||||||
del self.member_count
|
|
||||||
return
|
|
||||||
val = self._member_count_validator.validate(val)
|
|
||||||
self._member_count_value = val
|
|
||||||
self._member_count_present = True
|
|
||||||
|
|
||||||
@member_count.deleter
|
|
||||||
def member_count(self):
|
|
||||||
self._member_count_value = None
|
|
||||||
self._member_count_present = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def group_management_type(self):
|
|
||||||
"""
|
|
||||||
Who is allowed to manage the group.
|
|
||||||
|
|
||||||
:rtype: GroupManagementType
|
|
||||||
"""
|
|
||||||
if self._group_management_type_present:
|
|
||||||
return self._group_management_type_value
|
|
||||||
else:
|
|
||||||
raise AttributeError("missing required field 'group_management_type'")
|
|
||||||
|
|
||||||
@group_management_type.setter
|
|
||||||
def group_management_type(self, val):
|
|
||||||
self._group_management_type_validator.validate_type_only(val)
|
|
||||||
self._group_management_type_value = val
|
|
||||||
self._group_management_type_present = True
|
|
||||||
|
|
||||||
@group_management_type.deleter
|
|
||||||
def group_management_type(self):
|
|
||||||
self._group_management_type_value = None
|
|
||||||
self._group_management_type_present = False
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(GroupSummary, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'GroupSummary(group_name={!r}, group_id={!r}, group_management_type={!r}, group_external_id={!r}, member_count={!r})'.format(
|
|
||||||
self._group_name_value,
|
|
||||||
self._group_id_value,
|
|
||||||
self._group_management_type_value,
|
|
||||||
self._group_external_id_value,
|
|
||||||
self._member_count_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
GroupSummary_validator = bv.Struct(GroupSummary)
|
|
||||||
|
|
||||||
class GroupType(bb.Union):
|
|
||||||
"""
|
|
||||||
The group type determines how a group is created and managed.
|
|
||||||
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar team_common.GroupType.team: A group to which team members are
|
|
||||||
automatically added. Applicable to `team folders
|
|
||||||
<https://www.dropbox.com/help/986>`_ only.
|
|
||||||
:ivar team_common.GroupType.user_managed: A group is created and managed by
|
|
||||||
a user.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_catch_all = 'other'
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
team = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
user_managed = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
other = None
|
|
||||||
|
|
||||||
def is_team(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``team``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'team'
|
|
||||||
|
|
||||||
def is_user_managed(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``user_managed``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'user_managed'
|
|
||||||
|
|
||||||
def is_other(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``other``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'other'
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(GroupType, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'GroupType(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
GroupType_validator = bv.Union(GroupType)
|
|
||||||
|
|
||||||
class MemberSpaceLimitType(bb.Union):
|
|
||||||
"""
|
|
||||||
The type of the space limit imposed on a team member.
|
|
||||||
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar team_common.MemberSpaceLimitType.off: The team member does not have
|
|
||||||
imposed space limit.
|
|
||||||
:ivar team_common.MemberSpaceLimitType.alert_only: The team member has soft
|
|
||||||
imposed space limit - the limit is used for display and for
|
|
||||||
notifications.
|
|
||||||
:ivar team_common.MemberSpaceLimitType.stop_sync: The team member has hard
|
|
||||||
imposed space limit - Dropbox file sync will stop after the limit is
|
|
||||||
reached.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_catch_all = 'other'
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
off = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
alert_only = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
stop_sync = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
other = None
|
|
||||||
|
|
||||||
def is_off(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``off``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'off'
|
|
||||||
|
|
||||||
def is_alert_only(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``alert_only``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'alert_only'
|
|
||||||
|
|
||||||
def is_stop_sync(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``stop_sync``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'stop_sync'
|
|
||||||
|
|
||||||
def is_other(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``other``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'other'
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(MemberSpaceLimitType, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'MemberSpaceLimitType(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
MemberSpaceLimitType_validator = bv.Union(MemberSpaceLimitType)
|
|
||||||
|
|
||||||
class TimeRange(bb.Struct):
|
|
||||||
"""
|
|
||||||
Time range.
|
|
||||||
|
|
||||||
:ivar team_common.TimeRange.start_time: Optional starting time (inclusive).
|
|
||||||
:ivar team_common.TimeRange.end_time: Optional ending time (exclusive).
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = [
|
|
||||||
'_start_time_value',
|
|
||||||
'_start_time_present',
|
|
||||||
'_end_time_value',
|
|
||||||
'_end_time_present',
|
|
||||||
]
|
|
||||||
|
|
||||||
_has_required_fields = False
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
start_time=None,
|
|
||||||
end_time=None):
|
|
||||||
self._start_time_value = None
|
|
||||||
self._start_time_present = False
|
|
||||||
self._end_time_value = None
|
|
||||||
self._end_time_present = False
|
|
||||||
if start_time is not None:
|
|
||||||
self.start_time = start_time
|
|
||||||
if end_time is not None:
|
|
||||||
self.end_time = end_time
|
|
||||||
|
|
||||||
@property
|
|
||||||
def start_time(self):
|
|
||||||
"""
|
|
||||||
Optional starting time (inclusive).
|
|
||||||
|
|
||||||
:rtype: datetime.datetime
|
|
||||||
"""
|
|
||||||
if self._start_time_present:
|
|
||||||
return self._start_time_value
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@start_time.setter
|
|
||||||
def start_time(self, val):
|
|
||||||
if val is None:
|
|
||||||
del self.start_time
|
|
||||||
return
|
|
||||||
val = self._start_time_validator.validate(val)
|
|
||||||
self._start_time_value = val
|
|
||||||
self._start_time_present = True
|
|
||||||
|
|
||||||
@start_time.deleter
|
|
||||||
def start_time(self):
|
|
||||||
self._start_time_value = None
|
|
||||||
self._start_time_present = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def end_time(self):
|
|
||||||
"""
|
|
||||||
Optional ending time (exclusive).
|
|
||||||
|
|
||||||
:rtype: datetime.datetime
|
|
||||||
"""
|
|
||||||
if self._end_time_present:
|
|
||||||
return self._end_time_value
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@end_time.setter
|
|
||||||
def end_time(self, val):
|
|
||||||
if val is None:
|
|
||||||
del self.end_time
|
|
||||||
return
|
|
||||||
val = self._end_time_validator.validate(val)
|
|
||||||
self._end_time_value = val
|
|
||||||
self._end_time_present = True
|
|
||||||
|
|
||||||
@end_time.deleter
|
|
||||||
def end_time(self):
|
|
||||||
self._end_time_value = None
|
|
||||||
self._end_time_present = False
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(TimeRange, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'TimeRange(start_time={!r}, end_time={!r})'.format(
|
|
||||||
self._start_time_value,
|
|
||||||
self._end_time_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
TimeRange_validator = bv.Struct(TimeRange)
|
|
||||||
|
|
||||||
GroupExternalId_validator = bv.String()
|
|
||||||
GroupId_validator = bv.String()
|
|
||||||
MemberExternalId_validator = bv.String(max_length=64)
|
|
||||||
ResellerId_validator = bv.String()
|
|
||||||
TeamMemberId_validator = bv.String()
|
|
||||||
GroupManagementType._user_managed_validator = bv.Void()
|
|
||||||
GroupManagementType._company_managed_validator = bv.Void()
|
|
||||||
GroupManagementType._system_managed_validator = bv.Void()
|
|
||||||
GroupManagementType._other_validator = bv.Void()
|
|
||||||
GroupManagementType._tagmap = {
|
|
||||||
'user_managed': GroupManagementType._user_managed_validator,
|
|
||||||
'company_managed': GroupManagementType._company_managed_validator,
|
|
||||||
'system_managed': GroupManagementType._system_managed_validator,
|
|
||||||
'other': GroupManagementType._other_validator,
|
|
||||||
}
|
|
||||||
|
|
||||||
GroupManagementType.user_managed = GroupManagementType('user_managed')
|
|
||||||
GroupManagementType.company_managed = GroupManagementType('company_managed')
|
|
||||||
GroupManagementType.system_managed = GroupManagementType('system_managed')
|
|
||||||
GroupManagementType.other = GroupManagementType('other')
|
|
||||||
|
|
||||||
GroupSummary._group_name_validator = bv.String()
|
|
||||||
GroupSummary._group_id_validator = GroupId_validator
|
|
||||||
GroupSummary._group_external_id_validator = bv.Nullable(GroupExternalId_validator)
|
|
||||||
GroupSummary._member_count_validator = bv.Nullable(bv.UInt32())
|
|
||||||
GroupSummary._group_management_type_validator = GroupManagementType_validator
|
|
||||||
GroupSummary._all_field_names_ = set([
|
|
||||||
'group_name',
|
|
||||||
'group_id',
|
|
||||||
'group_external_id',
|
|
||||||
'member_count',
|
|
||||||
'group_management_type',
|
|
||||||
])
|
|
||||||
GroupSummary._all_fields_ = [
|
|
||||||
('group_name', GroupSummary._group_name_validator),
|
|
||||||
('group_id', GroupSummary._group_id_validator),
|
|
||||||
('group_external_id', GroupSummary._group_external_id_validator),
|
|
||||||
('member_count', GroupSummary._member_count_validator),
|
|
||||||
('group_management_type', GroupSummary._group_management_type_validator),
|
|
||||||
]
|
|
||||||
|
|
||||||
GroupType._team_validator = bv.Void()
|
|
||||||
GroupType._user_managed_validator = bv.Void()
|
|
||||||
GroupType._other_validator = bv.Void()
|
|
||||||
GroupType._tagmap = {
|
|
||||||
'team': GroupType._team_validator,
|
|
||||||
'user_managed': GroupType._user_managed_validator,
|
|
||||||
'other': GroupType._other_validator,
|
|
||||||
}
|
|
||||||
|
|
||||||
GroupType.team = GroupType('team')
|
|
||||||
GroupType.user_managed = GroupType('user_managed')
|
|
||||||
GroupType.other = GroupType('other')
|
|
||||||
|
|
||||||
MemberSpaceLimitType._off_validator = bv.Void()
|
|
||||||
MemberSpaceLimitType._alert_only_validator = bv.Void()
|
|
||||||
MemberSpaceLimitType._stop_sync_validator = bv.Void()
|
|
||||||
MemberSpaceLimitType._other_validator = bv.Void()
|
|
||||||
MemberSpaceLimitType._tagmap = {
|
|
||||||
'off': MemberSpaceLimitType._off_validator,
|
|
||||||
'alert_only': MemberSpaceLimitType._alert_only_validator,
|
|
||||||
'stop_sync': MemberSpaceLimitType._stop_sync_validator,
|
|
||||||
'other': MemberSpaceLimitType._other_validator,
|
|
||||||
}
|
|
||||||
|
|
||||||
MemberSpaceLimitType.off = MemberSpaceLimitType('off')
|
|
||||||
MemberSpaceLimitType.alert_only = MemberSpaceLimitType('alert_only')
|
|
||||||
MemberSpaceLimitType.stop_sync = MemberSpaceLimitType('stop_sync')
|
|
||||||
MemberSpaceLimitType.other = MemberSpaceLimitType('other')
|
|
||||||
|
|
||||||
TimeRange._start_time_validator = bv.Nullable(common.DropboxTimestamp_validator)
|
|
||||||
TimeRange._end_time_validator = bv.Nullable(common.DropboxTimestamp_validator)
|
|
||||||
TimeRange._all_field_names_ = set([
|
|
||||||
'start_time',
|
|
||||||
'end_time',
|
|
||||||
])
|
|
||||||
TimeRange._all_fields_ = [
|
|
||||||
('start_time', TimeRange._start_time_validator),
|
|
||||||
('end_time', TimeRange._end_time_validator),
|
|
||||||
]
|
|
||||||
|
|
||||||
ROUTES = {
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,88 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Auto-generated by Stone, do not modify.
|
|
||||||
# @generated
|
|
||||||
# flake8: noqa
|
|
||||||
# pylint: skip-file
|
|
||||||
"""
|
|
||||||
This namespace contains common data types used within the users namespace.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
from . import stone_validators as bv
|
|
||||||
from . import stone_base as bb
|
|
||||||
except (ImportError, SystemError, ValueError):
|
|
||||||
# Catch errors raised when importing a relative module when not in a package.
|
|
||||||
# This makes testing this file directly (outside of a package) easier.
|
|
||||||
import stone_validators as bv
|
|
||||||
import stone_base as bb
|
|
||||||
|
|
||||||
class AccountType(bb.Union):
|
|
||||||
"""
|
|
||||||
What type of account this user has.
|
|
||||||
|
|
||||||
This class acts as a tagged union. Only one of the ``is_*`` methods will
|
|
||||||
return true. To get the associated value of a tag (if one exists), use the
|
|
||||||
corresponding ``get_*`` method.
|
|
||||||
|
|
||||||
:ivar users_common.AccountType.basic: The basic account type.
|
|
||||||
:ivar users_common.AccountType.pro: The Dropbox Pro account type.
|
|
||||||
:ivar users_common.AccountType.business: The Dropbox Business account type.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_catch_all = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
basic = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
pro = None
|
|
||||||
# Attribute is overwritten below the class definition
|
|
||||||
business = None
|
|
||||||
|
|
||||||
def is_basic(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``basic``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'basic'
|
|
||||||
|
|
||||||
def is_pro(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``pro``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'pro'
|
|
||||||
|
|
||||||
def is_business(self):
|
|
||||||
"""
|
|
||||||
Check if the union tag is ``business``.
|
|
||||||
|
|
||||||
:rtype: bool
|
|
||||||
"""
|
|
||||||
return self._tag == 'business'
|
|
||||||
|
|
||||||
def _process_custom_annotations(self, annotation_type, field_path, processor):
|
|
||||||
super(AccountType, self)._process_custom_annotations(annotation_type, field_path, processor)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'AccountType(%r, %r)' % (self._tag, self._value)
|
|
||||||
|
|
||||||
AccountType_validator = bv.Union(AccountType)
|
|
||||||
|
|
||||||
AccountId_validator = bv.String(min_length=40, max_length=40)
|
|
||||||
AccountType._basic_validator = bv.Void()
|
|
||||||
AccountType._pro_validator = bv.Void()
|
|
||||||
AccountType._business_validator = bv.Void()
|
|
||||||
AccountType._tagmap = {
|
|
||||||
'basic': AccountType._basic_validator,
|
|
||||||
'pro': AccountType._pro_validator,
|
|
||||||
'business': AccountType._business_validator,
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountType.basic = AccountType('basic')
|
|
||||||
AccountType.pro = AccountType('pro')
|
|
||||||
AccountType.business = AccountType('business')
|
|
||||||
|
|
||||||
ROUTES = {
|
|
||||||
}
|
|
||||||
|
|
@ -30,11 +30,11 @@ class GuiSettingsManager:
|
|||||||
restoreSettings = self.__parseNodes(self.doc.getElementsByTagName('setting'))
|
restoreSettings = self.__parseNodes(self.doc.getElementsByTagName('setting'))
|
||||||
|
|
||||||
#get a list where the restore setting value != the current value
|
#get a list where the restore setting value != the current value
|
||||||
updateSettings = {k: v for k, v in restoreSettings.items() if (k in currentSettings and currentSettings[k] != v)}
|
updateSettings = {k: v for k, v in list(restoreSettings.items()) if (k in currentSettings and currentSettings[k] != v)}
|
||||||
|
|
||||||
#go through all the found settings and update them
|
#go through all the found settings and update them
|
||||||
jsonObj = {"jsonrpc":"2.0","id":1,"method":"Settings.SetSettingValue","params":{"setting":"","value":""}}
|
jsonObj = {"jsonrpc":"2.0","id":1,"method":"Settings.SetSettingValue","params":{"setting":"","value":""}}
|
||||||
for anId, aValue in updateSettings.items():
|
for anId, aValue in list(updateSettings.items()):
|
||||||
utils.log("updating: " + anId + ", value: " + str(aValue))
|
utils.log("updating: " + anId + ", value: " + str(aValue))
|
||||||
|
|
||||||
jsonObj['params']['setting'] = anId
|
jsonObj['params']['setting'] = anId
|
||||||
|
@ -3,9 +3,9 @@ import zipfile
|
|||||||
import os.path
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
from kodi_six import xbmc, xbmcvfs, xbmcgui
|
from kodi_six import xbmc, xbmcvfs, xbmcgui
|
||||||
from . import dropbox
|
import dropbox
|
||||||
from . import utils as utils
|
from . import utils as utils
|
||||||
from . dropbox.files import WriteMode,CommitInfo,UploadSessionCursor
|
from dropbox.files import WriteMode,CommitInfo,UploadSessionCursor
|
||||||
from . authorizers import DropboxAuthorizer
|
from . authorizers import DropboxAuthorizer
|
||||||
|
|
||||||
class Vfs:
|
class Vfs:
|
||||||
|
Loading…
Reference in New Issue
Block a user