Skip to content

Commit 0d65bfa

Browse files
committed
Making file cleanup + added test
Signed-off-by: Cédric Foellmi <cedric@onekiloparsec.dev>
1 parent b04b492 commit 0d65bfa

6 files changed

Lines changed: 100 additions & 36 deletions

File tree

arcsecond/cloud/uploader/allskycameraimages/uploader.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from arcsecond.cloud.uploader.errors import UploadRemoteFileMetadataError
44
from arcsecond.cloud.uploader.uploader import BaseFileUploader
5-
from arcsecond.cloud.uploader.utils import AutoCleanupFile
65
from .context import AllSkyCameraImageUploadContext
76

87

@@ -16,9 +15,10 @@ def _prepare_upload(self):
1615

1716
def _get_upload_data_fields(self):
1817
filename = os.path.basename(self._file_path)
19-
auto_cleanup_file = AutoCleanupFile(self._file_path)
18+
self._file = open(self._file_path, "rb")
19+
self._cleanup_resources.append(self._file)
2020
return {
21-
"file": (filename, auto_cleanup_file, "application/octet-stream"),
21+
"file": (filename, self._file, "application/octet-stream"),
2222
"camera": self._context.camera_uuid,
2323
}
2424

arcsecond/cloud/uploader/datafiles/uploader.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from arcsecond.cloud.uploader.errors import UploadRemoteFileMetadataError
44
from arcsecond.cloud.uploader.uploader import BaseFileUploader
5-
from arcsecond.cloud.uploader.utils import get_upload_progress_printer
65
from .context import DatasetUploadContext
76
from .errors import UploadRemoteDatasetPreparationError
87

@@ -38,9 +37,10 @@ def _prepare_upload(self):
3837

3938
def _get_upload_data_fields(self):
4039
filename = os.path.basename(self._file_path)
41-
# auto_cleanup_file = AutoCleanupFile(self._file_path)
40+
self._file = open(self._file_path, "rb")
41+
self._cleanup_resources.append(self._file)
4242
return {
43-
"file": (filename, open(self._file_path, "rb"), "application/octet-stream"),
43+
"file": (filename, self._file, "application/octet-stream"),
4444
"dataset": self._context.dataset_uuid,
4545
}
4646

arcsecond/cloud/uploader/uploader.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def __init__(
4040
self._file_size = os.path.getsize(file_path)
4141

4242
self._uploaded_file = None
43+
self._cleanup_resources = []
4344

4445
@property
4546
def log_prefix(self):
@@ -71,6 +72,8 @@ def _get_upload_data(self):
7172

7273
fields = self._get_upload_data_fields()
7374
assert len(fields) > 0
75+
assert len(self._cleanup_resources) > 0
76+
7477
e = MultipartEncoder(fields=fields)
7578

7679
# Create progress monitor if display_progress is True
@@ -104,7 +107,7 @@ def _perform_upload(self):
104107
return
105108

106109
if "already exists in dataset" in str(
107-
error
110+
error
108111
): # VERY WEAK!!! But solution with HTTP 409 isn't nice either.
109112
self._status = [Status.SKIPPED, Substatus.ALREADY_SYNCED, None]
110113
else:
@@ -119,6 +122,15 @@ def _update_metadata(self, **kwargs):
119122
"""Update metadata after upload - to be implemented by subclasses"""
120123
raise NotImplementedError()
121124

125+
def _cleanup(self):
126+
for resource in self._cleanup_resources:
127+
try:
128+
if hasattr(resource, 'close') and not getattr(resource, 'closed', False):
129+
resource.close()
130+
except Exception as e:
131+
self._logger.error(f"{self.log_prefix} {str(e)}.")
132+
self._cleanup_resources = []
133+
122134
def upload_file(self, **kwargs):
123135
"""Generic upload method that orchestrates the upload process"""
124136
if self._context.is_validated is False:
@@ -141,6 +153,8 @@ def upload_file(self, **kwargs):
141153
# Just try again
142154
time.sleep(1)
143155
self._perform_upload()
156+
finally:
157+
self._cleanup()
144158

145159
# Update metadata if upload successful
146160
if self._status[0] == Status.SKIPPED:

arcsecond/cloud/uploader/utils.py

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import math
2-
from pathlib import Path
32

43

54
def is_file_hidden(path):
@@ -43,28 +42,3 @@ def percent_printer(monitor):
4342
print(f"[{hashes}{spaces}] {(fraction * 100):.1f}%", end="\r")
4443

4544
return percent_printer
46-
47-
48-
class AutoCleanupFile:
49-
def __init__(self, file_path):
50-
self.file_path = file_path
51-
self.file_obj = open(file_path, "rb")
52-
self._size = Path(self.file_path).stat().st_size
53-
54-
def read(self, size=-1):
55-
return self.file_obj.read(size)
56-
57-
def seek(self, offset, whence=0):
58-
return self.file_obj.seek(offset, whence)
59-
60-
def tell(self):
61-
return self.file_obj.tell()
62-
63-
def close(self):
64-
self.file_obj.close()
65-
66-
def __len__(self):
67-
return self._size
68-
69-
def __del__(self):
70-
self.close()

tests/cloud/uploader/datafiles/test_uploader_full_process.py

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
import responses
88

99
from api.constants import ARCSECOND_API_URL_DEV
10-
from arcsecond import ArcsecondConfig, DatasetUploadContext, DatasetFileUploader
10+
from arcsecond import ArcsecondConfig, DatasetUploadContext, DatasetFileUploader, AllSkyCameraImageUploadContext, \
11+
AllSkyCameraImageFileUploader
1112
from arcsecond.cloud.uploader.constants import Substatus, Status
1213
from arcsecond.options import State
13-
from tests.utils import prepare_successful_login, prepare_upload_files
14+
from tests.utils import prepare_successful_login, prepare_upload_files, prepare_upload_allskyimage
1415

1516

1617
@responses.activate
17-
def test_full_upload_process():
18+
def test_full_upload_process_datafiles():
1819
dataset_uuid = str(uuid.uuid4())
1920
telescope_uuid = str(uuid.uuid4())
2021
org_subdomain = 'test-portal'
@@ -76,3 +77,65 @@ def test_full_upload_process():
7677
assert status.value == Status.OK.value
7778
assert substatus.value == Substatus.DONE.value
7879
assert error is None
80+
81+
82+
@responses.activate
83+
def test_full_upload_process_allskyimages():
84+
camera_uuid = str(uuid.uuid4())
85+
org_subdomain = 'test-portal'
86+
87+
prepare_successful_login(org_subdomain)
88+
prepare_upload_allskyimage(camera_uuid, org_subdomain)
89+
90+
# file upload
91+
image_id = random.randint(1, 1000)
92+
responses.post(
93+
"/".join([ARCSECOND_API_URL_DEV, org_subdomain, 'allskycameraimages']) + "/",
94+
status=201,
95+
json={"status": "success", "id": image_id}
96+
)
97+
# update metadata
98+
responses.patch(
99+
"/".join([ARCSECOND_API_URL_DEV, org_subdomain, 'allskycameraimages', str(image_id)]) + "/",
100+
status=200,
101+
json={"id": image_id}
102+
)
103+
104+
state = State(is_using_cli=False, verbose=False, api_name='cloud')
105+
config = {
106+
'cloud': {
107+
'username': 'dummy',
108+
'upload_key': '1234',
109+
'api_server': ARCSECOND_API_URL_DEV
110+
}
111+
}
112+
config = ArcsecondConfig(state, config) # it will read your config file.
113+
114+
context = AllSkyCameraImageUploadContext(
115+
config,
116+
input_camera_uuid=camera_uuid,
117+
org_subdomain=org_subdomain
118+
)
119+
120+
context.validate() # important step to perform before uploading.
121+
fixtures_dir = Path(__file__).parent.parent.parent.parent / "fixtures"
122+
fixture_files = list(fixtures_dir.glob('*.fits'))
123+
124+
for fixture_file in fixture_files:
125+
# Create a temporary directory and copy the fixture file there
126+
with tempfile.TemporaryDirectory() as temp_dir:
127+
temp_path = Path(temp_dir) / fixture_file.name
128+
shutil.copy(fixture_file, temp_path)
129+
130+
# Use the actual file for uploading
131+
uploader = AllSkyCameraImageFileUploader(
132+
context,
133+
str(temp_dir),
134+
str(temp_path),
135+
display_progress=False
136+
)
137+
138+
status, substatus, error = uploader.upload_file()
139+
assert status.value == Status.OK.value
140+
assert substatus.value == Substatus.DONE.value
141+
assert error is None

tests/utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,19 @@ def prepare_upload_files(dataset_uuid, telescope_uuid, org_subdomain=''):
6666
json={"subdomain": org_subdomain, "name": "dummy org"},
6767
)
6868

69+
def prepare_upload_allskyimage(camera_uuid, org_subdomain=''):
70+
responses.get(
71+
"/".join([part for part in [ARCSECOND_API_URL_DEV, org_subdomain, 'allskycameras', camera_uuid] if part]) + "/",
72+
status=201,
73+
json={"status": "success", "uuid": camera_uuid}
74+
)
75+
if org_subdomain:
76+
responses.get(
77+
"/".join([ARCSECOND_API_URL_DEV, 'organisations', org_subdomain]) + "/",
78+
status=200,
79+
json={"subdomain": org_subdomain, "name": "dummy org"},
80+
)
81+
6982

7083
def save_test_credentials(username, memberships=None):
7184
config = ArcsecondConfig(State(api_name="test"))

0 commit comments

Comments
 (0)