mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #1979 from tokejepsen/feature/syncserver_dropbox
SyncServer: Dropbox Provider
This commit is contained in:
commit
7c98442d7b
10 changed files with 538 additions and 10 deletions
|
|
@ -103,7 +103,8 @@ class AbstractProvider:
|
|||
representation (dict): complete repre containing 'file'
|
||||
site (str): site name
|
||||
Returns:
|
||||
None
|
||||
(string) file_id of created/modified file ,
|
||||
throws FileExistsError, FileNotFoundError exceptions
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,423 @@
|
|||
import os
|
||||
|
||||
import dropbox
|
||||
|
||||
from openpype.api import Logger
|
||||
from .abstract_provider import AbstractProvider
|
||||
from ..utils import EditableScopes
|
||||
|
||||
log = Logger().get_logger("SyncServer")
|
||||
|
||||
|
||||
class DropboxHandler(AbstractProvider):
|
||||
CODE = 'dropbox'
|
||||
LABEL = 'Dropbox'
|
||||
|
||||
def __init__(self, project_name, site_name, tree=None, presets=None):
|
||||
self.active = False
|
||||
self.site_name = site_name
|
||||
self.presets = presets
|
||||
|
||||
if not self.presets:
|
||||
log.info(
|
||||
"Sync Server: There are no presets for {}.".format(site_name)
|
||||
)
|
||||
return
|
||||
|
||||
provider_presets = self.presets.get(self.CODE)
|
||||
if not provider_presets:
|
||||
msg = "Sync Server: No provider presets for {}".format(self.CODE)
|
||||
log.info(msg)
|
||||
return
|
||||
|
||||
token = self.presets[self.CODE].get("token", "")
|
||||
if not token:
|
||||
msg = "Sync Server: No access token for dropbox provider"
|
||||
log.info(msg)
|
||||
return
|
||||
|
||||
team_folder_name = self.presets[self.CODE].get("team_folder_name", "")
|
||||
if not team_folder_name:
|
||||
msg = "Sync Server: No team folder name for dropbox provider"
|
||||
log.info(msg)
|
||||
return
|
||||
|
||||
acting_as_member = self.presets[self.CODE].get("acting_as_member", "")
|
||||
if not acting_as_member:
|
||||
msg = (
|
||||
"Sync Server: No acting member for dropbox provider"
|
||||
)
|
||||
log.info(msg)
|
||||
return
|
||||
|
||||
self.dbx = None
|
||||
try:
|
||||
self.dbx = self._get_service(
|
||||
token, acting_as_member, team_folder_name
|
||||
)
|
||||
except Exception as e:
|
||||
log.info("Could not establish dropbox object: {}".format(e))
|
||||
return
|
||||
|
||||
super(AbstractProvider, self).__init__()
|
||||
|
||||
@classmethod
|
||||
def get_system_settings_schema(cls):
|
||||
"""
|
||||
Returns dict for editable properties on system settings level
|
||||
|
||||
|
||||
Returns:
|
||||
(list) of dict
|
||||
"""
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def get_project_settings_schema(cls):
|
||||
"""
|
||||
Returns dict for editable properties on project settings level
|
||||
|
||||
|
||||
Returns:
|
||||
(list) of dict
|
||||
"""
|
||||
# {platform} tells that value is multiplatform and only specific OS
|
||||
# should be returned
|
||||
return [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "token",
|
||||
"label": "Access Token"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "team_folder_name",
|
||||
"label": "Team Folder Name"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "acting_as_member",
|
||||
"label": "Acting As Member"
|
||||
},
|
||||
# roots could be overriden only on Project level, User cannot
|
||||
{
|
||||
'key': "roots",
|
||||
'label': "Roots",
|
||||
'type': 'dict'
|
||||
}
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_local_settings_schema(cls):
|
||||
"""
|
||||
Returns dict for editable properties on local settings level
|
||||
|
||||
|
||||
Returns:
|
||||
(dict)
|
||||
"""
|
||||
return []
|
||||
|
||||
def _get_service(self, token, acting_as_member, team_folder_name):
|
||||
dbx = dropbox.DropboxTeam(token)
|
||||
|
||||
# Getting member id.
|
||||
member_id = None
|
||||
member_names = []
|
||||
for member in dbx.team_members_list().members:
|
||||
member_names.append(member.profile.name.display_name)
|
||||
if member.profile.name.display_name == acting_as_member:
|
||||
member_id = member.profile.team_member_id
|
||||
|
||||
if member_id is None:
|
||||
raise ValueError(
|
||||
"Could not find member \"{}\". Available members: {}".format(
|
||||
acting_as_member, member_names
|
||||
)
|
||||
)
|
||||
|
||||
# Getting team folder id.
|
||||
team_folder_id = None
|
||||
team_folder_names = []
|
||||
for entry in dbx.team_team_folder_list().team_folders:
|
||||
team_folder_names.append(entry.name)
|
||||
if entry.name == team_folder_name:
|
||||
team_folder_id = entry.team_folder_id
|
||||
|
||||
if team_folder_id is None:
|
||||
raise ValueError(
|
||||
"Could not find team folder \"{}\". Available folders: "
|
||||
"{}".format(
|
||||
team_folder_name, team_folder_names
|
||||
)
|
||||
)
|
||||
|
||||
# Establish dropbox object.
|
||||
path_root = dropbox.common.PathRoot.namespace_id(team_folder_id)
|
||||
return dropbox.DropboxTeam(
|
||||
token
|
||||
).with_path_root(path_root).as_user(member_id)
|
||||
|
||||
def is_active(self):
|
||||
"""
|
||||
Returns True if provider is activated, eg. has working credentials.
|
||||
Returns:
|
||||
(boolean)
|
||||
"""
|
||||
return self.dbx is not None
|
||||
|
||||
@classmethod
|
||||
def get_configurable_items(cls):
|
||||
"""
|
||||
Returns filtered dict of editable properties
|
||||
|
||||
|
||||
Returns:
|
||||
(dict)
|
||||
"""
|
||||
editable = {
|
||||
'token': {
|
||||
'scope': [EditableScopes.PROJECT],
|
||||
'label': "Access Token",
|
||||
'type': 'text',
|
||||
'namespace': (
|
||||
'{project_settings}/global/sync_server/sites/{site}/token'
|
||||
)
|
||||
},
|
||||
'team_folder_name': {
|
||||
'scope': [EditableScopes.PROJECT],
|
||||
'label': "Team Folder Name",
|
||||
'type': 'text',
|
||||
'namespace': (
|
||||
'{project_settings}/global/sync_server/sites/{site}'
|
||||
'/team_folder_name'
|
||||
)
|
||||
},
|
||||
'acting_as_member': {
|
||||
'scope': [EditableScopes.PROJECT, EditableScopes.LOCAL],
|
||||
'label': "Acting As Member",
|
||||
'type': 'text',
|
||||
'namespace': (
|
||||
'{project_settings}/global/sync_server/sites/{site}'
|
||||
'/acting_as_member'
|
||||
)
|
||||
}
|
||||
}
|
||||
return editable
|
||||
|
||||
def _path_exists(self, path):
|
||||
try:
|
||||
entries = self.dbx.files_list_folder(
|
||||
path=os.path.dirname(path)
|
||||
).entries
|
||||
except dropbox.exceptions.ApiError:
|
||||
return False
|
||||
|
||||
for entry in entries:
|
||||
if entry.name == os.path.basename(path):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def upload_file(self, source_path, path,
|
||||
server, collection, file, representation, site,
|
||||
overwrite=False):
|
||||
"""
|
||||
Copy file from 'source_path' to 'target_path' on provider.
|
||||
Use 'overwrite' boolean to rewrite existing file on provider
|
||||
|
||||
Args:
|
||||
source_path (string):
|
||||
path (string): absolute path with or without name of the file
|
||||
overwrite (boolean): replace existing file
|
||||
|
||||
arguments for saving progress:
|
||||
server (SyncServer): server instance to call update_db on
|
||||
collection (str): name of collection
|
||||
file (dict): info about uploaded file (matches structure from db)
|
||||
representation (dict): complete repre containing 'file'
|
||||
site (str): site name
|
||||
Returns:
|
||||
(string) file_id of created file, raises exception
|
||||
"""
|
||||
# Check source path.
|
||||
if not os.path.exists(source_path):
|
||||
raise FileNotFoundError(
|
||||
"Source file {} doesn't exist.".format(source_path)
|
||||
)
|
||||
|
||||
if self._path_exists(path) and not overwrite:
|
||||
raise FileExistsError(
|
||||
"File already exists, use 'overwrite' argument"
|
||||
)
|
||||
|
||||
mode = dropbox.files.WriteMode("add", None)
|
||||
if overwrite:
|
||||
mode = dropbox.files.WriteMode.overwrite
|
||||
|
||||
with open(source_path, "rb") as f:
|
||||
self.dbx.files_upload(f.read(), path, mode=mode)
|
||||
|
||||
server.update_db(
|
||||
collection=collection,
|
||||
new_file_id=None,
|
||||
file=file,
|
||||
representation=representation,
|
||||
site=site,
|
||||
progress=100
|
||||
)
|
||||
|
||||
return path
|
||||
|
||||
def download_file(self, source_path, local_path,
|
||||
server, collection, file, representation, site,
|
||||
overwrite=False):
|
||||
"""
|
||||
Download file from provider into local system
|
||||
|
||||
Args:
|
||||
source_path (string): absolute path on provider
|
||||
local_path (string): absolute path with or without name of the file
|
||||
overwrite (boolean): replace existing file
|
||||
|
||||
arguments for saving progress:
|
||||
server (SyncServer): server instance to call update_db on
|
||||
collection (str): name of collection
|
||||
file (dict): info about uploaded file (matches structure from db)
|
||||
representation (dict): complete repre containing 'file'
|
||||
site (str): site name
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
# Check source path.
|
||||
if not self._path_exists(source_path):
|
||||
raise FileNotFoundError(
|
||||
"Source file {} doesn't exist.".format(source_path)
|
||||
)
|
||||
|
||||
if os.path.exists(local_path) and not overwrite:
|
||||
raise FileExistsError(
|
||||
"File already exists, use 'overwrite' argument"
|
||||
)
|
||||
|
||||
if os.path.exists(local_path) and overwrite:
|
||||
os.unlink(local_path)
|
||||
|
||||
self.dbx.files_download_to_file(local_path, source_path)
|
||||
|
||||
server.update_db(
|
||||
collection=collection,
|
||||
new_file_id=None,
|
||||
file=file,
|
||||
representation=representation,
|
||||
site=site,
|
||||
progress=100
|
||||
)
|
||||
|
||||
return os.path.basename(source_path)
|
||||
|
||||
def delete_file(self, path):
|
||||
"""
|
||||
Deletes file from 'path'. Expects path to specific file.
|
||||
|
||||
Args:
|
||||
path (string): absolute path to particular file
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if not self._path_exists(path):
|
||||
raise FileExistsError("File {} doesn't exist".format(path))
|
||||
|
||||
self.dbx.files_delete(path)
|
||||
|
||||
def list_folder(self, folder_path):
|
||||
"""
|
||||
List all files and subfolders of particular path non-recursively.
|
||||
Args:
|
||||
folder_path (string): absolut path on provider
|
||||
|
||||
Returns:
|
||||
(list)
|
||||
"""
|
||||
if not self._path_exists(folder_path):
|
||||
raise FileExistsError(
|
||||
"Folder \"{}\" does not exist".format(folder_path)
|
||||
)
|
||||
|
||||
entry_names = []
|
||||
for entry in self.dbx.files_list_folder(path=folder_path).entries:
|
||||
entry_names.append(entry.name)
|
||||
return entry_names
|
||||
|
||||
def create_folder(self, folder_path):
|
||||
"""
|
||||
Create all nonexistent folders and subfolders in 'path'.
|
||||
|
||||
Args:
|
||||
path (string): absolute path
|
||||
|
||||
Returns:
|
||||
(string) folder id of lowest subfolder from 'path'
|
||||
"""
|
||||
if self._path_exists(folder_path):
|
||||
return folder_path
|
||||
|
||||
self.dbx.files_create_folder_v2(folder_path)
|
||||
|
||||
return folder_path
|
||||
|
||||
def get_tree(self):
|
||||
"""
|
||||
Creates folder structure for providers which do not provide
|
||||
tree folder structure (GDrive has no accessible tree structure,
|
||||
only parents and their parents)
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_roots_config(self, anatomy=None):
|
||||
"""
|
||||
Returns root values for path resolving
|
||||
|
||||
Takes value from Anatomy which takes values from Settings
|
||||
overridden by Local Settings
|
||||
|
||||
Returns:
|
||||
(dict) - {"root": {"root": "/My Drive"}}
|
||||
OR
|
||||
{"root": {"root_ONE": "value", "root_TWO":"value}}
|
||||
Format is importing for usage of python's format ** approach
|
||||
"""
|
||||
return self.presets['root']
|
||||
|
||||
def resolve_path(self, path, root_config=None, anatomy=None):
|
||||
"""
|
||||
Replaces all root placeholders with proper values
|
||||
|
||||
Args:
|
||||
path(string): root[work]/folder...
|
||||
root_config (dict): {'work': "c:/..."...}
|
||||
anatomy (Anatomy): object of Anatomy
|
||||
Returns:
|
||||
(string): proper url
|
||||
"""
|
||||
if not root_config:
|
||||
root_config = self.get_roots_config(anatomy)
|
||||
|
||||
if root_config and not root_config.get("root"):
|
||||
root_config = {"root": root_config}
|
||||
|
||||
try:
|
||||
if not root_config:
|
||||
raise KeyError
|
||||
|
||||
path = path.format(**root_config)
|
||||
except KeyError:
|
||||
try:
|
||||
path = anatomy.fill_root(path)
|
||||
except KeyError:
|
||||
msg = "Error in resolving local root from anatomy"
|
||||
log.error(msg)
|
||||
raise ValueError(msg)
|
||||
|
||||
return path
|
||||
|
|
@ -61,7 +61,6 @@ class GDriveHandler(AbstractProvider):
|
|||
CHUNK_SIZE = 2097152 # must be divisible by 256! used for upload chunks
|
||||
|
||||
def __init__(self, project_name, site_name, tree=None, presets=None):
|
||||
self.presets = None
|
||||
self.active = False
|
||||
self.project_name = project_name
|
||||
self.site_name = site_name
|
||||
|
|
@ -74,7 +73,13 @@ class GDriveHandler(AbstractProvider):
|
|||
format(site_name))
|
||||
return
|
||||
|
||||
cred_path = self.presets.get("credentials_url", {}).\
|
||||
provider_presets = self.presets.get(self.CODE)
|
||||
if not provider_presets:
|
||||
msg = "Sync Server: No provider presets for {}".format(self.CODE)
|
||||
log.info(msg)
|
||||
return
|
||||
|
||||
cred_path = self.presets[self.CODE].get("credentials_url", {}).\
|
||||
get(platform.system().lower()) or ''
|
||||
if not os.path.exists(cred_path):
|
||||
msg = "Sync Server: No credentials for gdrive provider " + \
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from .gdrive import GDriveHandler
|
||||
from .dropbox import DropboxHandler
|
||||
from .local_drive import LocalDriveHandler
|
||||
|
||||
|
||||
|
|
@ -111,4 +112,5 @@ factory = ProviderFactory()
|
|||
# 7 denotes number of files that could be synced in single loop - learned by
|
||||
# trial and error
|
||||
factory.register_provider(GDriveHandler.CODE, GDriveHandler, 7)
|
||||
factory.register_provider(DropboxHandler.CODE, DropboxHandler, 10)
|
||||
factory.register_provider(LocalDriveHandler.CODE, LocalDriveHandler, 50)
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 2 KiB |
|
|
@ -221,6 +221,7 @@ def _get_configured_sites_from_setting(module, project_name, project_setting):
|
|||
|
||||
return configured_sites
|
||||
|
||||
|
||||
class SyncServerThread(threading.Thread):
|
||||
"""
|
||||
Separate thread running synchronization server with asyncio loop.
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ def time_function(method):
|
|||
kw['log_time'][name] = int((te - ts) * 1000)
|
||||
else:
|
||||
log.debug('%r %2.2f ms' % (method.__name__, (te - ts) * 1000))
|
||||
print('%r %2.2f ms' % (method.__name__, (te - ts) * 1000))
|
||||
return result
|
||||
|
||||
return timed
|
||||
|
|
|
|||
|
|
@ -48,10 +48,41 @@
|
|||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"type": "path",
|
||||
"key": "credentials_url",
|
||||
"label": "Credentials url",
|
||||
"multiplatform": true
|
||||
"type": "dict",
|
||||
"key": "gdrive",
|
||||
"label": "Google Drive",
|
||||
"collapsible": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "path",
|
||||
"key": "credentials_url",
|
||||
"label": "Credentials url",
|
||||
"multiplatform": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"key": "dropbox",
|
||||
"label": "Dropbox",
|
||||
"collapsible": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "token",
|
||||
"label": "Access Token"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "team_folder_name",
|
||||
"label": "Team Folder Name"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "acting_as_member",
|
||||
"label": "Acting As Member"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict-modifiable",
|
||||
|
|
@ -61,7 +92,7 @@
|
|||
"collapsable_key": false,
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
67
poetry.lock
generated
67
poetry.lock
generated
|
|
@ -313,6 +313,19 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "dropbox"
|
||||
version = "11.20.0"
|
||||
description = "Official Dropbox API Client"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
requests = ">=2.16.2"
|
||||
six = ">=1.12.0"
|
||||
stone = ">=2"
|
||||
|
||||
[[package]]
|
||||
name = "enlighten"
|
||||
version = "1.10.1"
|
||||
|
|
@ -749,6 +762,14 @@ importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
|||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "ply"
|
||||
version = "3.11"
|
||||
description = "Python Lex & Yacc"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "prefixed"
|
||||
version = "0.3.2"
|
||||
|
|
@ -1341,6 +1362,18 @@ sphinxcontrib-serializinghtml = "*"
|
|||
lint = ["flake8"]
|
||||
test = ["pytest", "sqlalchemy", "whoosh", "sphinx"]
|
||||
|
||||
[[package]]
|
||||
name = "stone"
|
||||
version = "3.2.1"
|
||||
description = "Stone is an interface description language (IDL) for APIs."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
ply = ">=3.4"
|
||||
six = ">=1.3.0"
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.0"
|
||||
|
|
@ -1466,7 +1499,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "3.7.*"
|
||||
content-hash = "8875d530ae66f9763b5b0cb84d9d35edc184ef5c141b63d38bf1ff5a1226e556"
|
||||
content-hash = "63ab0f15fa9d40931622f71ad6e8d5810e1f9b7ef5d27e1d9a8d00caad767c1d"
|
||||
|
||||
[metadata.files]
|
||||
acre = []
|
||||
|
|
@ -1582,24 +1615,36 @@ cffi = [
|
|||
{file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"},
|
||||
{file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"},
|
||||
{file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"},
|
||||
{file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f"},
|
||||
{file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed"},
|
||||
{file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55"},
|
||||
{file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"},
|
||||
{file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"},
|
||||
{file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"},
|
||||
{file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"},
|
||||
{file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"},
|
||||
{file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"},
|
||||
|
|
@ -1692,8 +1737,10 @@ cryptography = [
|
|||
{file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"},
|
||||
{file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"},
|
||||
{file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"},
|
||||
{file = "cryptography-3.4.7-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b01fd6f2737816cb1e08ed4807ae194404790eac7ad030b34f2ce72b332f5586"},
|
||||
{file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"},
|
||||
{file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"},
|
||||
{file = "cryptography-3.4.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3"},
|
||||
{file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"},
|
||||
]
|
||||
cx-freeze = [
|
||||
|
|
@ -1730,6 +1777,11 @@ docutils = [
|
|||
{file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"},
|
||||
{file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"},
|
||||
]
|
||||
dropbox = [
|
||||
{file = "dropbox-11.20.0-py2-none-any.whl", hash = "sha256:0926aab25445fe78b0284e0b86f4126ec4e5e2bf6cd2ac8562002008a21073b8"},
|
||||
{file = "dropbox-11.20.0-py3-none-any.whl", hash = "sha256:f2106aa566f9e3c175879c226c60b7089a39099b228061acbb7258670f6b859c"},
|
||||
{file = "dropbox-11.20.0.tar.gz", hash = "sha256:1aa351ec8bbb11cf3560e731b81d25f39c7edcb5fa92c06c5d68866cb9f90d54"},
|
||||
]
|
||||
enlighten = [
|
||||
{file = "enlighten-1.10.1-py2.py3-none-any.whl", hash = "sha256:3d6c3eec8cf3eb626ee7b65eddc1b3e904d01f4547a2b9fe7f1da8892a0297e8"},
|
||||
{file = "enlighten-1.10.1.tar.gz", hash = "sha256:3391916586364aedced5d6926482b48745e4948f822de096d32258ba238ea984"},
|
||||
|
|
@ -1983,6 +2035,10 @@ pluggy = [
|
|||
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
|
||||
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
|
||||
]
|
||||
ply = [
|
||||
{file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"},
|
||||
{file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"},
|
||||
]
|
||||
prefixed = [
|
||||
{file = "prefixed-0.3.2-py2.py3-none-any.whl", hash = "sha256:5e107306462d63f2f03c529dbf11b0026fdfec621a9a008ca639d71de22995c3"},
|
||||
{file = "prefixed-0.3.2.tar.gz", hash = "sha256:ca48277ba5fa8346dd4b760847da930c7b84416387c39e93affef086add2c029"},
|
||||
|
|
@ -2006,9 +2062,13 @@ protobuf = [
|
|||
{file = "protobuf-3.17.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2ae692bb6d1992afb6b74348e7bb648a75bb0d3565a3f5eea5bec8f62bd06d87"},
|
||||
{file = "protobuf-3.17.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:99938f2a2d7ca6563c0ade0c5ca8982264c484fdecf418bd68e880a7ab5730b1"},
|
||||
{file = "protobuf-3.17.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6902a1e4b7a319ec611a7345ff81b6b004b36b0d2196ce7a748b3493da3d226d"},
|
||||
{file = "protobuf-3.17.3-cp38-cp38-win32.whl", hash = "sha256:59e5cf6b737c3a376932fbfb869043415f7c16a0cf176ab30a5bbc419cd709c1"},
|
||||
{file = "protobuf-3.17.3-cp38-cp38-win_amd64.whl", hash = "sha256:ebcb546f10069b56dc2e3da35e003a02076aaa377caf8530fe9789570984a8d2"},
|
||||
{file = "protobuf-3.17.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ffbd23640bb7403574f7aff8368e2aeb2ec9a5c6306580be48ac59a6bac8bde"},
|
||||
{file = "protobuf-3.17.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:26010f693b675ff5a1d0e1bdb17689b8b716a18709113288fead438703d45539"},
|
||||
{file = "protobuf-3.17.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e76d9686e088fece2450dbc7ee905f9be904e427341d289acbe9ad00b78ebd47"},
|
||||
{file = "protobuf-3.17.3-cp39-cp39-win32.whl", hash = "sha256:a38bac25f51c93e4be4092c88b2568b9f407c27217d3dd23c7a57fa522a17554"},
|
||||
{file = "protobuf-3.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:85d6303e4adade2827e43c2b54114d9a6ea547b671cb63fafd5011dc47d0e13d"},
|
||||
{file = "protobuf-3.17.3-py2.py3-none-any.whl", hash = "sha256:2bfb815216a9cd9faec52b16fd2bfa68437a44b67c56bee59bc3926522ecb04e"},
|
||||
{file = "protobuf-3.17.3.tar.gz", hash = "sha256:72804ea5eaa9c22a090d2803813e280fb273b62d5ae497aaf3553d141c4fdd7b"},
|
||||
]
|
||||
|
|
@ -2339,6 +2399,11 @@ sphinxcontrib-websupport = [
|
|||
{file = "sphinxcontrib-websupport-1.2.4.tar.gz", hash = "sha256:4edf0223a0685a7c485ae5a156b6f529ba1ee481a1417817935b20bde1956232"},
|
||||
{file = "sphinxcontrib_websupport-1.2.4-py2.py3-none-any.whl", hash = "sha256:6fc9287dfc823fe9aa432463edd6cea47fa9ebbf488d7f289b322ffcfca075c7"},
|
||||
]
|
||||
stone = [
|
||||
{file = "stone-3.2.1-py2-none-any.whl", hash = "sha256:2a50866528f60cc7cedd010def733e8ae9d581d17f967278a08059bffaea3c57"},
|
||||
{file = "stone-3.2.1-py3-none-any.whl", hash = "sha256:76235137c09ee88aa53e8c1e666819f6c20ac8064c4ac6c4ee4194eac0e3b7af"},
|
||||
{file = "stone-3.2.1.tar.gz", hash = "sha256:9bc78b40143b4ef33bf569e515408c2996ffebefbb1a897616ebe8aa6f2d7e75"},
|
||||
]
|
||||
termcolor = [
|
||||
{file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ jinxed = [
|
|||
python3-xlib = { version="*", markers = "sys_platform == 'linux'"}
|
||||
enlighten = "^1.9.0"
|
||||
slack-sdk = "^3.6.0"
|
||||
dropbox = "^11.20.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
flake8 = "^3.7"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue