d2spy 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- d2spy-0.0.1/LICENSE +21 -0
- d2spy-0.0.1/PKG-INFO +32 -0
- d2spy-0.0.1/README.md +9 -0
- d2spy-0.0.1/d2spy/__init__.py +0 -0
- d2spy-0.0.1/d2spy/api_client.py +64 -0
- d2spy-0.0.1/d2spy/auth.py +108 -0
- d2spy-0.0.1/d2spy/extras/__init__.py +0 -0
- d2spy-0.0.1/d2spy/extras/third_party/tusclient/__init__.py +1 -0
- d2spy-0.0.1/d2spy/extras/third_party/tusclient/client.py +77 -0
- d2spy-0.0.1/d2spy/extras/third_party/tusclient/exceptions.py +35 -0
- d2spy-0.0.1/d2spy/extras/third_party/tusclient/py.typed +0 -0
- d2spy-0.0.1/d2spy/extras/third_party/tusclient/request.py +93 -0
- d2spy-0.0.1/d2spy/extras/third_party/tusclient/uploader/__init__.py +1 -0
- d2spy-0.0.1/d2spy/extras/third_party/tusclient/uploader/baseuploader.py +246 -0
- d2spy-0.0.1/d2spy/extras/third_party/tusclient/uploader/uploader.py +102 -0
- d2spy-0.0.1/d2spy/extras/utils.py +11 -0
- d2spy-0.0.1/d2spy/models/__init__.py +3 -0
- d2spy-0.0.1/d2spy/models/data_product.py +17 -0
- d2spy-0.0.1/d2spy/models/flight.py +165 -0
- d2spy-0.0.1/d2spy/models/job.py +31 -0
- d2spy-0.0.1/d2spy/models/location.py +8 -0
- d2spy-0.0.1/d2spy/models/project.py +125 -0
- d2spy-0.0.1/d2spy/models/user.py +26 -0
- d2spy-0.0.1/d2spy/schemas/__init__.py +3 -0
- d2spy-0.0.1/d2spy/schemas/data_product.py +37 -0
- d2spy-0.0.1/d2spy/schemas/flight.py +39 -0
- d2spy-0.0.1/d2spy/schemas/geojson.py +19 -0
- d2spy-0.0.1/d2spy/schemas/project.py +41 -0
- d2spy-0.0.1/d2spy/schemas/stac_properties.py +25 -0
- d2spy-0.0.1/d2spy/workspace.py +101 -0
- d2spy-0.0.1/pyproject.toml +43 -0
d2spy-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Geospatial Data Science Lab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
d2spy-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: d2spy
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Python package for interacting with a Data to Science instance.
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: python,data to science,uas
|
|
7
|
+
Author: Ben Hancock
|
|
8
|
+
Author-email: hancocb@purdue.edu
|
|
9
|
+
Requires-Python: >=3.11,<4.0
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Building with poetry
|
|
24
|
+
|
|
25
|
+
Run the following from the root directory to build the source and wheels archives: `poetry build`
|
|
26
|
+
|
|
27
|
+
This will create a `dist` directory containing a `.tar.gz` archive of the source code and a `.whl` file.
|
|
28
|
+
|
|
29
|
+
# Installing d2spy package with pip
|
|
30
|
+
|
|
31
|
+
The d2spy package can be installed with pip using the `.whl` in the build's `dist` directory. Inside a Python virtual environment, run `python -m pip install d2spy_pkg-VERSION-py3-none-any.whl` to install d2spy.
|
|
32
|
+
|
d2spy-0.0.1/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Building with poetry
|
|
2
|
+
|
|
3
|
+
Run the following from the root directory to build the source and wheels archives: `poetry build`
|
|
4
|
+
|
|
5
|
+
This will create a `dist` directory containing a `.tar.gz` archive of the source code and a `.whl` file.
|
|
6
|
+
|
|
7
|
+
# Installing d2spy package with pip
|
|
8
|
+
|
|
9
|
+
The d2spy package can be installed with pip using the `.whl` in the build's `dist` directory. Inside a Python virtual environment, run `python -m pip install d2spy_pkg-VERSION-py3-none-any.whl` to install d2spy.
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class APIClient:
|
|
5
|
+
"""Makes API requests to D2S API."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, base_url: str, session: requests.Session):
|
|
8
|
+
"""Constructor for APIClient class.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
base_url (str): Base URL for D2S instance.
|
|
12
|
+
session (requests.Session): Session set by Auth.
|
|
13
|
+
|
|
14
|
+
Raises:
|
|
15
|
+
ValueError: Raised if access token missing from session.
|
|
16
|
+
"""
|
|
17
|
+
self.base_url = base_url
|
|
18
|
+
self.session = session
|
|
19
|
+
|
|
20
|
+
# Check if access token in session cookies
|
|
21
|
+
if not self.session.cookies.get("access_token"):
|
|
22
|
+
raise ValueError("Session missing access token. Must sign in first.")
|
|
23
|
+
|
|
24
|
+
def make_get_request(self, endpoint: str) -> requests.Response:
|
|
25
|
+
"""Makes GET request to D2S API.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
endpoint (str): D2S endpoint for request.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
requests.Response: Response from D2S API to request.
|
|
32
|
+
"""
|
|
33
|
+
url = self.base_url + endpoint
|
|
34
|
+
response = self.session.get(url)
|
|
35
|
+
|
|
36
|
+
return response
|
|
37
|
+
|
|
38
|
+
def make_post_request(self, endpoint: str, **kwargs) -> requests.Response:
|
|
39
|
+
"""Make POST request to D2S API.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
endpoint (str): D2S endpoint for request.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
requests.Response: Response from D2S API to request.
|
|
46
|
+
"""
|
|
47
|
+
url = self.base_url + endpoint
|
|
48
|
+
response = self.session.post(url, **kwargs)
|
|
49
|
+
|
|
50
|
+
return response
|
|
51
|
+
|
|
52
|
+
def make_put_request(self, endpoint: str, **kwargs) -> requests.Response:
|
|
53
|
+
"""Make PUT request to D2S API.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
endpoint (str): _description_
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
requests.Response: _description_
|
|
60
|
+
"""
|
|
61
|
+
url = self.base_url + endpoint
|
|
62
|
+
response = self.session.put(url, **kwargs)
|
|
63
|
+
|
|
64
|
+
return response
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import getpass
|
|
2
|
+
import json
|
|
3
|
+
import requests
|
|
4
|
+
from typing import Dict, Union
|
|
5
|
+
from urllib.parse import urlparse
|
|
6
|
+
|
|
7
|
+
from .extras.utils import pretty_print_response
|
|
8
|
+
from .models.user import User
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Auth:
|
|
12
|
+
"""Authenticates with D2S."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, base_url: str) -> None:
|
|
15
|
+
"""Constructor for Auth class.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
base_url (str): Base URL for D2S instance.
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
ValueError: Raised if unable to communicate with host.
|
|
22
|
+
"""
|
|
23
|
+
self.base_url: str = base_url
|
|
24
|
+
|
|
25
|
+
if test_base_url(self.base_url) is False:
|
|
26
|
+
raise ValueError("unable to connect to provided host")
|
|
27
|
+
|
|
28
|
+
self.session: requests.Session = requests.session()
|
|
29
|
+
|
|
30
|
+
def login(self, email: str) -> Union[User, None]:
|
|
31
|
+
"""Login to D2S platform with email and password.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
email (str): Email address used to sign in to D2S.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Union[User, None]: User object or None.
|
|
38
|
+
"""
|
|
39
|
+
# Request password from user
|
|
40
|
+
password = getpass.getpass(prompt="Enter your D2S password:")
|
|
41
|
+
# Credentials that will be sent to D2S auth API
|
|
42
|
+
credentials = {"username": email, "password": password}
|
|
43
|
+
# URL for D2S access-token endpoint
|
|
44
|
+
url = f"{self.base_url}/api/v1/auth/access-token"
|
|
45
|
+
# Post credentials to access-token endpoint
|
|
46
|
+
response = requests.post(url, data=credentials)
|
|
47
|
+
# JWT access token returned for successful request
|
|
48
|
+
if response.status_code == 200 and "access_token" in response.cookies:
|
|
49
|
+
# Add JWT access token to session cookies
|
|
50
|
+
self.session.cookies.set(
|
|
51
|
+
"access_token", response.cookies.get("access_token")
|
|
52
|
+
)
|
|
53
|
+
# Fetch user object associated with access token
|
|
54
|
+
user = self.get_current_user()
|
|
55
|
+
# Return dictionary of user attributes and values
|
|
56
|
+
if user:
|
|
57
|
+
return User.from_dict(user)
|
|
58
|
+
else:
|
|
59
|
+
return None
|
|
60
|
+
else:
|
|
61
|
+
# Print response if request fails
|
|
62
|
+
pretty_print_response(response)
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
def logout(self) -> None:
|
|
66
|
+
"""Logout of D2S platform."""
|
|
67
|
+
# Delete access-token cookie from session and end session
|
|
68
|
+
self.session.cookies.clear(domain="", path="/", name="access_token")
|
|
69
|
+
self.session.close()
|
|
70
|
+
print("session ended")
|
|
71
|
+
|
|
72
|
+
def get_current_user(self) -> Union[User, None]:
|
|
73
|
+
"""Get user object for logged in user.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Union[User, None]: User object or None.
|
|
77
|
+
"""
|
|
78
|
+
# D2S endpoint for fetching user object for signed in user
|
|
79
|
+
url = f"{self.base_url}/api/v1/users/current"
|
|
80
|
+
# Request user object from D2S instance
|
|
81
|
+
response = self.session.get(url)
|
|
82
|
+
# Return user object if request successful
|
|
83
|
+
if response.status_code == 200:
|
|
84
|
+
return response.json()
|
|
85
|
+
else:
|
|
86
|
+
# Print response if request fails
|
|
87
|
+
pretty_print_response(response)
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_base_url(base_url: str) -> bool:
|
|
92
|
+
"""Return true if base_url returns HTTP 200 else false.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
base_url (str): Base URL for D2S instance.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
bool: Returns True if D2S instance returns status OK, otherwise False
|
|
99
|
+
"""
|
|
100
|
+
response: Union[requests.Response, None] = None
|
|
101
|
+
try:
|
|
102
|
+
response = requests.get(base_url)
|
|
103
|
+
except requests.exceptions.ConnectionError:
|
|
104
|
+
response = None
|
|
105
|
+
finally:
|
|
106
|
+
if response and response.status_code == 200:
|
|
107
|
+
return True
|
|
108
|
+
return False
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.3"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from typing import Dict, Optional
|
|
2
|
+
|
|
3
|
+
from d2spy.extras.third_party.tusclient.uploader import Uploader
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TusClient:
|
|
7
|
+
"""
|
|
8
|
+
Object representation of Tus client.
|
|
9
|
+
|
|
10
|
+
:Attributes:
|
|
11
|
+
- url (str):
|
|
12
|
+
represents the tus server's create extension url. On instantiation this argument
|
|
13
|
+
must be passed to the constructor.
|
|
14
|
+
- headers (dict):
|
|
15
|
+
This can be used to set the server specific headers. These headers would be sent
|
|
16
|
+
along with every request made by the client to the server. This may be used to set
|
|
17
|
+
authentication headers. These headers should not include headers required by tus
|
|
18
|
+
protocol. If not set this defaults to an empty dictionary.
|
|
19
|
+
- cookies (dict):
|
|
20
|
+
This can be used to set the server specific cookies. These cookies would be sent
|
|
21
|
+
along with every request made by the client to the server. This may be used to set
|
|
22
|
+
authorization cookies.
|
|
23
|
+
|
|
24
|
+
:Constructor Args:
|
|
25
|
+
- url (str)
|
|
26
|
+
- headers (Optional[dict])
|
|
27
|
+
- cookies (Optional[dict])
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
url: str,
|
|
33
|
+
headers: Optional[Dict[str, str]] = None,
|
|
34
|
+
cookies: Optional[Dict[str, str]] = None,
|
|
35
|
+
):
|
|
36
|
+
self.url = url
|
|
37
|
+
self.headers = headers or {}
|
|
38
|
+
self.cookies = cookies or {}
|
|
39
|
+
|
|
40
|
+
def set_headers(self, headers: Dict[str, str]):
|
|
41
|
+
"""
|
|
42
|
+
Set tus client headers.
|
|
43
|
+
|
|
44
|
+
Update and/or set new headers that would be sent along with every request made
|
|
45
|
+
to the server.
|
|
46
|
+
|
|
47
|
+
:Args:
|
|
48
|
+
- headers (dict):
|
|
49
|
+
key, value pairs of the headers to be set. This argument is required.
|
|
50
|
+
"""
|
|
51
|
+
self.headers.update(headers)
|
|
52
|
+
|
|
53
|
+
def set_cookies(self, cookies: Dict[str, str]):
|
|
54
|
+
"""
|
|
55
|
+
Set tus client cookies.
|
|
56
|
+
|
|
57
|
+
Update and/or set new cookies that would be sent along with every request made
|
|
58
|
+
to the server.
|
|
59
|
+
|
|
60
|
+
:Args:
|
|
61
|
+
- cookies (dict):
|
|
62
|
+
key, value pairs of the cookies to be set. This argument is required.
|
|
63
|
+
"""
|
|
64
|
+
self.cookies.update(cookies)
|
|
65
|
+
|
|
66
|
+
def uploader(self, *args, **kwargs) -> Uploader:
|
|
67
|
+
"""
|
|
68
|
+
Return uploader instance pointing at current client instance.
|
|
69
|
+
|
|
70
|
+
Return uploader instance with which you can control the upload of a specific
|
|
71
|
+
file. The current instance of the tus client is passed to the uploader on creation.
|
|
72
|
+
|
|
73
|
+
:Args:
|
|
74
|
+
see tusclient.uploader.Uploader for required and optional arguments.
|
|
75
|
+
"""
|
|
76
|
+
kwargs["client"] = self
|
|
77
|
+
return Uploader(*args, **kwargs)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Global Tusclient exception and warning classes.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TusCommunicationError(Exception):
|
|
7
|
+
"""
|
|
8
|
+
Should be raised when communications with tus-server behaves
|
|
9
|
+
unexpectedly.
|
|
10
|
+
|
|
11
|
+
:Attributes:
|
|
12
|
+
- message (str):
|
|
13
|
+
Main message of the exception
|
|
14
|
+
- status_code (int):
|
|
15
|
+
Status code of response indicating an error
|
|
16
|
+
- response_content (str):
|
|
17
|
+
Content of response indicating an error
|
|
18
|
+
:Constructor Args:
|
|
19
|
+
- message (Optional[str])
|
|
20
|
+
- status_code (Optional[int])
|
|
21
|
+
- response_content (Optional[str])
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, message, status_code=None, response_content=None):
|
|
25
|
+
default_message = "Communication with tus server failed with status {}".format(
|
|
26
|
+
status_code
|
|
27
|
+
)
|
|
28
|
+
message = message or default_message
|
|
29
|
+
super(TusCommunicationError, self).__init__(message)
|
|
30
|
+
self.status_code = status_code
|
|
31
|
+
self.response_content = response_content
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TusUploadFailed(TusCommunicationError):
|
|
35
|
+
"""Should be raised when an attempted upload fails"""
|
|
File without changes
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
import base64
|
|
3
|
+
from functools import wraps
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from d2spy.extras.third_party.tusclient.exceptions import TusUploadFailed, TusCommunicationError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Catches requests exceptions and throws custom tuspy errors.
|
|
11
|
+
def catch_requests_error(func):
|
|
12
|
+
"""Deocrator to catch requests exceptions"""
|
|
13
|
+
|
|
14
|
+
@wraps(func)
|
|
15
|
+
def _wrapper(*args, **kwargs):
|
|
16
|
+
try:
|
|
17
|
+
return func(*args, **kwargs)
|
|
18
|
+
except requests.exceptions.RequestException as error:
|
|
19
|
+
raise TusCommunicationError(error)
|
|
20
|
+
|
|
21
|
+
return _wrapper
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BaseTusRequest:
|
|
25
|
+
"""
|
|
26
|
+
Http Request Abstraction.
|
|
27
|
+
|
|
28
|
+
Sets up tus custom http request on instantiation.
|
|
29
|
+
|
|
30
|
+
requires argument 'uploader' an instance of tusclient.uploader.Uploader
|
|
31
|
+
on instantiation.
|
|
32
|
+
|
|
33
|
+
:Attributes:
|
|
34
|
+
- response_headers (dict)
|
|
35
|
+
- file (file):
|
|
36
|
+
The file that is being uploaded.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, uploader):
|
|
40
|
+
self._url = uploader.url
|
|
41
|
+
self.response_headers = {}
|
|
42
|
+
self.status_code = None
|
|
43
|
+
self.response_content = None
|
|
44
|
+
self.verify_tls_cert = bool(uploader.verify_tls_cert)
|
|
45
|
+
self.file = uploader.get_file_stream()
|
|
46
|
+
self.file.seek(uploader.offset)
|
|
47
|
+
|
|
48
|
+
self._request_headers = {
|
|
49
|
+
"upload-offset": str(uploader.offset),
|
|
50
|
+
"Content-Type": "application/offset+octet-stream",
|
|
51
|
+
}
|
|
52
|
+
self._request_cookies = {}
|
|
53
|
+
self._request_headers.update(uploader.get_headers())
|
|
54
|
+
self._request_cookies.update(uploader.get_cookies())
|
|
55
|
+
self._content_length = uploader.get_request_length()
|
|
56
|
+
self._upload_checksum = uploader.upload_checksum
|
|
57
|
+
self._checksum_algorithm = uploader.checksum_algorithm
|
|
58
|
+
self._checksum_algorithm_name = uploader.checksum_algorithm_name
|
|
59
|
+
|
|
60
|
+
def add_checksum(self, chunk: bytes):
|
|
61
|
+
if self._upload_checksum:
|
|
62
|
+
self._request_headers["upload-checksum"] = " ".join(
|
|
63
|
+
(
|
|
64
|
+
self._checksum_algorithm_name,
|
|
65
|
+
base64.b64encode(self._checksum_algorithm(chunk).digest()).decode(
|
|
66
|
+
"ascii"
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class TusRequest(BaseTusRequest):
|
|
73
|
+
"""Class to handle async Tus upload requests"""
|
|
74
|
+
|
|
75
|
+
def perform(self):
|
|
76
|
+
"""
|
|
77
|
+
Perform actual request.
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
chunk = self.file.read(self._content_length)
|
|
81
|
+
self.add_checksum(chunk)
|
|
82
|
+
resp = requests.patch(
|
|
83
|
+
self._url,
|
|
84
|
+
data=chunk,
|
|
85
|
+
headers=self._request_headers,
|
|
86
|
+
cookies=self._request_cookies,
|
|
87
|
+
verify=self.verify_tls_cert,
|
|
88
|
+
)
|
|
89
|
+
self.status_code = resp.status_code
|
|
90
|
+
self.response_content = resp.content
|
|
91
|
+
self.response_headers = {k.lower(): v for k, v in resp.headers.items()}
|
|
92
|
+
except requests.exceptions.RequestException as error:
|
|
93
|
+
raise TusUploadFailed(error)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from d2spy.extras.third_party.tusclient.uploader.uploader import Uploader
|