Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 40 additions & 21 deletions source/ftrack_api/accessor/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import os
import hashlib
import base64
import json

import requests

Expand All @@ -22,6 +21,7 @@ def __init__(self, resource_identifier, session, mode="rb"):
self.mode = mode
self.resource_identifier = resource_identifier
self._session = session
self._timeout = session.request_timeout
self._has_read = False

super(ServerFile, self).__init__()
Expand All @@ -46,15 +46,21 @@ def _read(self):
position = self.tell()
self.seek(0)

response = requests.get(
"{0}/component/get".format(self._session.server_url),
params={
"id": self.resource_identifier,
"username": self._session.api_user,
"apiKey": self._session.api_key,
},
stream=True,
)
try:
response = requests.get(
"{0}/component/get".format(self._session.server_url),
params={
"id": self.resource_identifier,
"username": self._session.api_user,
"apiKey": self._session.api_key,
},
stream=True,
timeout=self._timeout,
)
except Exception as error:
raise ftrack_api.exception.AccessorOperationFailedError(
"Failed to read data: {0}.".format(error)
)

try:
response.raise_for_status()
Expand Down Expand Up @@ -105,7 +111,10 @@ def _write(self):

# Put the file based on the metadata.
response = requests.put(
metadata["url"], data=self.wrapped_file, headers=metadata["headers"]
metadata["url"],
data=self.wrapped_file,
headers=metadata["headers"],
timeout=self._timeout,
)

try:
Expand Down Expand Up @@ -153,24 +162,34 @@ def __init__(self, session, **kw):
super(_ServerAccessor, self).__init__(**kw)

self._session = session
self._timeout = session.request_timeout

def open(self, resource_identifier, mode="rb"):
"""Return :py:class:`~ftrack_api.Data` for *resource_identifier*."""
return ServerFile(resource_identifier, session=self._session, mode=mode)

def remove(self, resourceIdentifier):
"""Remove *resourceIdentifier*."""
response = requests.get(
"{0}/component/remove".format(self._session.server_url),
params={
"id": resourceIdentifier,
"username": self._session.api_user,
"apiKey": self._session.api_key,
},
)
if response.status_code != 200:
try:
response = requests.get(
"{0}/component/remove".format(self._session.server_url),
params={
"id": resourceIdentifier,
"username": self._session.api_user,
"apiKey": self._session.api_key,
},
timeout=self._timeout,
)
except Exception as error:
raise ftrack_api.exception.AccessorOperationFailedError(
"Failed to remove file: {0}.".format(error)
)

try:
response.raise_for_status()
except requests.exceptions.HTTPError as error:
raise ftrack_api.exception.AccessorOperationFailedError(
"Failed to remove file."
"Failed to remove file: {0}.".format(error)
)

def get_container(self, resource_identifier):
Expand Down
72 changes: 63 additions & 9 deletions test/unit/accessor/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,85 @@
import ftrack_api.data


def test_read_and_write(new_component, session):
"""Read and write data from server accessor."""
random_data = uuid.uuid1().hex.encode()
@pytest.fixture(scope="module")
def random_binary_data():
return uuid.uuid1().hex.encode()


def test_read_and_write(new_component, random_binary_data, session):
"""Read and write data from server accessor."""
accessor = ftrack_api.accessor.server._ServerAccessor(session)
http_file = accessor.open(new_component["id"], mode="wb")
http_file.write(random_data)
http_file.write(random_binary_data)
http_file.close()

data = accessor.open(new_component["id"], "r")
assert data.read() == random_data, "Read data is the same as written."
assert data.read() == random_binary_data, "Read data is the same as written."
data.close()


def test_remove_data(new_component, session):
def test_remove_data(new_component, random_binary_data, session):
"""Remove data using server accessor."""
random_data = uuid.uuid1().hex

accessor = ftrack_api.accessor.server._ServerAccessor(session)
http_file = accessor.open(new_component["id"], mode="wb")
http_file.write(random_data)
http_file.write(random_binary_data)
http_file.close()

accessor.remove(new_component["id"])

data = accessor.open(new_component["id"], "r")
with pytest.raises(ftrack_api.exception.AccessorOperationFailedError):
data.read()


def test_read_timeout(new_component, random_binary_data, session, monkeypatch):
"""Test that read operations respect timeout settings."""
# First, write some data so there's something to read
accessor = ftrack_api.accessor.server._ServerAccessor(session)
http_file = accessor.open(new_component["id"], mode="wb")
http_file.write(random_binary_data)
http_file.close()

# Set an impossibly short timeout - no server can respond this fast
monkeypatch.setattr(session, "request_timeout", 0.0001)

# Open a new file handle (this captures the patched timeout)
data = accessor.open(new_component["id"], "r")
with pytest.raises(
ftrack_api.exception.AccessorOperationFailedError, match="timed out"
):
data.read()


def test_write_timeout(new_component, random_binary_data, session, monkeypatch):
"""Test that write operations respect timeout settings."""
# Set an impossibly short timeout
monkeypatch.setattr(session, "request_timeout", 0.0001)

accessor = ftrack_api.accessor.server._ServerAccessor(session)
http_file = accessor.open(new_component["id"], mode="wb")
http_file.write(random_binary_data)

# Timeout is caught and wrapped in AccessorOperationFailedError
with pytest.raises(
ftrack_api.exception.AccessorOperationFailedError, match="timed out"
):
http_file.close() # close() triggers flush() which triggers _write()


def test_remove_timeout(new_component, random_binary_data, session, monkeypatch):
"""Test that remove operations respect timeout settings."""
# Write something first
accessor = ftrack_api.accessor.server._ServerAccessor(session)
http_file = accessor.open(new_component["id"], mode="wb")
http_file.write(random_binary_data)
http_file.close()

# Set timeout and create new accessor to pick up the timeout for remove()
monkeypatch.setattr(session, "request_timeout", 0.0001)
accessor = ftrack_api.accessor.server._ServerAccessor(session)

with pytest.raises(
ftrack_api.exception.AccessorOperationFailedError, match="timed out"
):
accessor.remove(new_component["id"])