Skip to content

Commit afe3749

Browse files
authored
Merge pull request #48 from observatorycontrolsystem/feature/thumbnails
Feature/thumbnails
2 parents 0631b2d + 698a80a commit afe3749

9 files changed

Lines changed: 113 additions & 17 deletions

File tree

.github/workflows/build.yaml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,26 @@ jobs:
2727
run: |
2828
python -m pip install --upgrade pip
2929
pip install -e .[tests]
30-
pip install coverage "coveralls<3.3"
30+
pip install coverage
3131
- name: Run tests
32-
run: coverage run -m pytest
32+
run: |
33+
coverage run -m pytest
34+
coverage xml
3335
env:
3436
OPENTSDB_PYTHON_METRICS_TEST_MODE: True
35-
- name: Coveralls report
36-
run: coveralls --service=github
37-
env:
38-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37+
- name: Generate and send coveralls report
38+
uses: coverallsapp/github-action@v2
39+
with:
40+
parallel: true
41+
format: cobertura
42+
finish:
43+
runs-on: ubuntu-latest
44+
needs: run_tests
45+
steps:
46+
- name: Close parallel build
47+
uses: coverallsapp/github-action@v2
48+
with:
49+
parallel-finished: true
3950

4051
publish_to_pypi:
4152
# Only run this job if the run_tests job has succeeded, and if

CHANGELOG

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
3.1.0
2+
2024-06-03
3+
Upgrade ocs_archive library, add support for uploading thumbnails to an OCS archive.
4+
15
3.0.6
26
2024-01-31
37
Upgrade ocs_archive library version to support selecting S3 addressing_style

ocs_ingester/archive.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,29 @@ def post_frame(self, archive_record):
9393
**ingester_settings.EXTRA_METRICS_TAGS
9494
)
9595
return archive_record
96+
97+
@metrics.method_timer('ingester.post_thumbnail')
98+
def post_thumbnail(self, archive_record):
99+
response = requests.post(
100+
'{0}thumbnails/'.format(self.api_root), json=archive_record, headers=self.headers
101+
)
102+
result = self.handle_response(response)
103+
logger.info('Ingester posted thumbnail to archive', extra={
104+
'tags': {
105+
'filename': result.get('filename'),
106+
'request_id': archive_record.get('request_id'),
107+
'proposal_id': result.get('proposal_id'),
108+
'id': result.get('id')
109+
}
110+
})
111+
# Add some useful information from the result
112+
archive_record['thumbnail_id'] = result.get('id')
113+
# Record metric for the ingest lag (time between date of image vs date ingested)
114+
ingest_lag = datetime.utcnow() - obs_end_time_from_dict(archive_record)
115+
self.send_metric(
116+
metric_name='ingester.ingest_lag',
117+
value=ingest_lag.total_seconds(),
118+
asynchronous=ingester_settings.SUBMIT_METRICS_ASYNCHRONOUSLY,
119+
**ingester_settings.EXTRA_METRICS_TAGS
120+
)
121+
return archive_record

ocs_ingester/ingester.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
>>> ingested_record = ingester.upload_file_and_ingest_to_archive(fileobj)
2727
2828
"""
29-
3029
from ocs_ingester.exceptions import BackoffRetryError, NonFatalDoNotRetryError, DoNotRetryError
3130
from ocs_ingester.archive import ArchiveService
3231
from ocs_ingester.utils.metrics import upload_and_collect_metrics, get_md5_and_collect_metrics
@@ -160,7 +159,7 @@ def upload_file_to_file_store(fileobj, path=None, file_metadata=None):
160159

161160
def ingest_archive_record(version, record, api_root=ingester_settings.API_ROOT,
162161
auth_token=ingester_settings.AUTH_TOKEN):
163-
"""Adds a record to the science archive database.
162+
"""Adds a frame record to the science archive database.
164163
165164
Args:
166165
version (dict): Version information returned from the upload to S3
@@ -202,7 +201,8 @@ def ingest_archive_record(version, record, api_root=ingester_settings.API_ROOT,
202201
def upload_file_and_ingest_to_archive(fileobj, path=None, file_metadata=None,
203202
required_headers=archive_settings.REQUIRED_HEADERS,
204203
blacklist_headers=archive_settings.HEADER_BLACKLIST,
205-
api_root=ingester_settings.API_ROOT, auth_token=ingester_settings.AUTH_TOKEN):
204+
api_root=ingester_settings.API_ROOT, auth_token=ingester_settings.AUTH_TOKEN,
205+
is_thumbnail=False, thumbnail_size=None):
206206
"""Uploads a file to S3 and adds the associated record to the science archive database.
207207
208208
This is a standalone function that runs all of the necessary steps to add data to the
@@ -218,6 +218,8 @@ def upload_file_and_ingest_to_archive(fileobj, path=None, file_metadata=None,
218218
auth_token (str): Science archive API authentication token
219219
required_headers (tuple): FITS headers that must be present
220220
blacklist_headers (tuple): FITS headers that should not be ingested
221+
is_thumbnail (bool): Whether the file is a thumbnail
222+
thumbnail_size (str): The size of the thumbnail
221223
222224
Returns:
223225
dict: Information about the uploaded file and record. For example:
@@ -250,6 +252,11 @@ def upload_file_and_ingest_to_archive(fileobj, path=None, file_metadata=None,
250252
try:
251253
if file_metadata is None:
252254
file_metadata = {}
255+
if is_thumbnail:
256+
if thumbnail_size is None:
257+
raise FileSpecificationException('thumbnail_size must be provided for thumbnail files')
258+
file_metadata['size'] = thumbnail_size
259+
required_headers = archive_settings.REQUIRED_THUMBNAIL_METADATA
253260
open_file = File(fileobj, path)
254261
datafile = FileFactory.get_datafile_class_for_extension(open_file.extension)(
255262
open_file, file_metadata, required_headers=required_headers, blacklist_headers=blacklist_headers
@@ -259,7 +266,7 @@ def upload_file_and_ingest_to_archive(fileobj, path=None, file_metadata=None,
259266
raise DoNotRetryError(str(fe))
260267

261268
archive = ArchiveService(api_root=api_root, auth_token=auth_token)
262-
ingester = Ingester(datafile, filestore, archive)
269+
ingester = Ingester(datafile, filestore, archive, is_thumbnail=is_thumbnail)
263270
return ingester.ingest()
264271

265272

@@ -269,10 +276,11 @@ class Ingester(object):
269276
A single instance of this class is responsible for parsing a fits file,
270277
uploading the data to s3, and making a call to the archive api.
271278
"""
272-
def __init__(self, datafile, filestore, archive):
279+
def __init__(self, datafile, filestore, archive, is_thumbnail=False):
273280
self.datafile = datafile
274281
self.filestore = filestore
275282
self.archive = archive
283+
self.is_thumbnail = is_thumbnail
276284

277285
def ingest(self):
278286
# Get the Md5 checksum of this file and check if it already exists in the archive
@@ -293,4 +301,5 @@ def ingest(self):
293301
record['area'] = self.datafile.get_wcs_corners()
294302
record['version_set'] = [version]
295303
record['basename'] = self.datafile.open_file.basename
296-
return self.archive.post_frame(record)
304+
305+
return self.archive.post_thumbnail(record) if self.is_thumbnail else self.archive.post_frame(record)

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
setup(
1010
name='ocs-ingester',
11-
version='3.0.6',
11+
version='3.1.0',
1212
description='Ingest frames into the science archive of an observatory control system',
1313
long_description=long_description,
1414
long_description_content_type='text/markdown',
@@ -22,7 +22,7 @@
2222
'astropy',
2323
'requests',
2424
'boto3',
25-
'ocs_archive==0.3.0',
25+
'ocs_archive==0.4.0',
2626
'python-dateutil',
2727
'lcogt-logging',
2828
'opentsdb-python-metrics>=0.2.0'

tests/__init__.py

Whitespace-only changes.

tests/test_archive.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ def test_archive_post(self, post_mock, get_mock):
5757
self.assertTrue(post_mock.called)
5858
self.assertEqual(post_mock.call_args[0][0], 'http://fake/frames/')
5959

60+
def test_archive_post_thumbnail(self, post_mock, get_mock):
61+
archive_service = ArchiveService(api_root='http://fake/', auth_token='')
62+
archive_service.post_thumbnail({'observation_date': datetime.utcnow().isoformat()})
63+
self.assertTrue(post_mock.called)
64+
self.assertEqual(post_mock.call_args[0][0], 'http://fake/thumbnails/')
65+
6066
def test_existing_md5(self, post_mock, get_mock):
6167
archive_service = ArchiveService(api_root='http://return1/', auth_token='')
6268
self.assertTrue(archive_service.version_exists(''))
58.3 KB
Loading

tests/test_ingester.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from ocs_ingester.ingester import (Ingester, upload_file_and_ingest_to_archive, ingest_archive_record,
1616
upload_file_to_file_store, validate_fits_and_create_archive_record)
1717
from ocs_ingester.exceptions import DoNotRetryError, NonFatalDoNotRetryError
18-
from ocs_ingester.settings import settings
1918

2019
opentsdb_python_metrics.metric_wrappers.test_mode = True
2120

@@ -53,6 +52,11 @@
5352
'cptnrs03-fa13-20150219-0001-e92-summary.pdf'
5453
)
5554

55+
JPG_FILE = os.path.join(
56+
OTHER_PATH,
57+
'tfn0m419-sq32-20240426-0097-e91-small.jpg'
58+
)
59+
5660

5761
def mock_hashlib_md5(*args, **kwargs):
5862
class MockHash(object):
@@ -70,10 +74,10 @@ def hexdigest(self):
7074
filestore_mock = MagicMock()
7175
filestore_mock.store_file = MagicMock(return_value={'md5': 'fakemd5'})
7276

73-
def mocked_ingester(datafile_real, fake_filestore, fake_archive):
77+
def mocked_ingester(datafile_real, fake_filestore, fake_archive, is_thumbnail):
7478
class MockIngester(Ingester):
7579
def __init__(self):
76-
super().__init__(datafile_real, filestore_mock, archive_mock)
80+
super().__init__(datafile_real, filestore_mock, archive_mock, is_thumbnail)
7781

7882
return MockIngester()
7983

@@ -108,6 +112,7 @@ def setUp(self):
108112
self.open_files = [File(open(os.path.join(FITS_PATH, f), 'rb')) for f in os.listdir(FITS_PATH)]
109113
self.data_files = [FileFactory.get_datafile_class_for_extension(open_file.extension)(open_file) for open_file in self.open_files]
110114
self.mock_metadata = {'PROPID': 'INGEST-TEST-2021',
115+
'DAY-OBS': '20150219',
111116
'DATE-OBS': '2015-02-19T13:56:05.261',
112117
'INSTRUME': 'nres03',
113118
'SITEID': 'cpt',
@@ -282,3 +287,38 @@ def test_ingest_pdf_with_meta(self, ingester_mock):
282287
upload_file_and_ingest_to_archive(fileobj, file_metadata=self.mock_metadata)
283288
self.assertTrue(filestore_mock.store_file.called)
284289
self.assertTrue(archive_mock.post_frame.called)
290+
291+
@patch('ocs_ingester.ingester.Ingester', side_effect=mocked_ingester)
292+
def test_ingest_jpg_no_meta(self, ingester_mock):
293+
with open(JPG_FILE, 'rb') as fileobj:
294+
with self.assertRaises(DoNotRetryError):
295+
upload_file_and_ingest_to_archive(fileobj)
296+
self.assertFalse(filestore_mock.store_file.called)
297+
self.assertFalse(archive_mock.post_frame.called)
298+
299+
@patch('ocs_ingester.ingester.Ingester', side_effect=mocked_ingester)
300+
def test_ingest_jpg_missing_keyword(self, ingester_mock):
301+
bad_metadata = copy(self.mock_metadata)
302+
del bad_metadata['BLKUID']
303+
with open(JPG_FILE, 'rb') as fileobj:
304+
with self.assertRaises(DoNotRetryError):
305+
upload_file_and_ingest_to_archive(fileobj, file_metadata=bad_metadata)
306+
self.assertFalse(filestore_mock.store_file.called)
307+
self.assertFalse(archive_mock.post_frame.called)
308+
309+
@patch('ocs_ingester.ingester.Ingester', side_effect=mocked_ingester)
310+
def test_ingest_jpg_with_incomplete_meta(self, ingester_mock):
311+
with open(JPG_FILE, 'rb') as fileobj:
312+
with self.assertRaises(DoNotRetryError):
313+
upload_file_and_ingest_to_archive(fileobj, file_metadata=self.mock_metadata, is_thumbnail=True, thumbnail_size='small')
314+
self.assertFalse(filestore_mock.store_file.called)
315+
self.assertFalse(archive_mock.post_thumbnail.called)
316+
317+
@patch('ocs_ingester.ingester.Ingester', side_effect=mocked_ingester)
318+
def test_ingest_jpg_with_complete_meta(self, ingester_mock):
319+
test_metadata = copy(self.mock_metadata)
320+
test_metadata['frame_basename'] = 'tfn0m419-sq32-20240426-0097-e91'
321+
with open(JPG_FILE, 'rb') as fileobj:
322+
upload_file_and_ingest_to_archive(fileobj, file_metadata=test_metadata, is_thumbnail=True, thumbnail_size='small')
323+
self.assertTrue(filestore_mock.store_file.called)
324+
self.assertTrue(archive_mock.post_thumbnail.called)

0 commit comments

Comments
 (0)