Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Theia Update USGS API Callouts to reflect USGS API changes and Landsat Collection 1 Data Shutdown #189

Merged
merged 22 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3361d1c
commenting out espa wrapper orders. orders now done through eros wrap…
yuenmichelle1 Jan 23, 2024
e7576b2
remove unneeded apiKey parameter
yuenmichelle1 Jan 23, 2024
c5d2312
attempt to replace espa api calls with corresponding eros calls
yuenmichelle1 Feb 6, 2024
26f2955
update metadata parsing
yuenmichelle1 Feb 8, 2024
4a553c6
update typo missing arg on cloud_cover
yuenmichelle1 Feb 8, 2024
9040369
update Bands to match new file names
yuenmichelle1 Feb 9, 2024
b30968f
remove comments from floating_forest.py xml operations
yuenmichelle1 Feb 12, 2024
14c4289
Update ErosWraper to remove unused download_urls method and update co…
yuenmichelle1 Feb 12, 2024
fea5e84
remove unused threading on eros wrapper
yuenmichelle1 Feb 12, 2024
16ce486
remove unused code from tasks.py wait_for_scene
yuenmichelle1 Feb 12, 2024
858725c
undo change in settings.py on ALLOWED_HOSTS
yuenmichelle1 Feb 12, 2024
93e6dd8
update adapter.py to remove unused code
yuenmichelle1 Feb 12, 2024
383dabd
remove logs from espa wrapper. will keep in case we need to make any …
yuenmichelle1 Feb 12, 2024
355c9f0
fix tests in Adapter and set default dataset to Landsat c2 l2
yuenmichelle1 Feb 12, 2024
2e221fa
update eros scene-search test to not take in the expected response as…
yuenmichelle1 Feb 13, 2024
6628079
remove unused vars on tests and update wait_for_scene tests to expect…
yuenmichelle1 Feb 13, 2024
1fdd888
adding tests to add_scene_to_order_list new method
yuenmichelle1 Feb 14, 2024
29dd53e
add login and token expiration check tests
yuenmichelle1 Feb 15, 2024
346247a
add tests for requesting available products
yuenmichelle1 Feb 15, 2024
2598570
Merge branch 'master' into theia-ff-image-script-fix-attempt
yuenmichelle1 Feb 15, 2024
353d2b7
add tests for requesting downloads
yuenmichelle1 Feb 15, 2024
dd14e8c
update test_adapter.py to fake user login
yuenmichelle1 Feb 15, 2024
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
37 changes: 21 additions & 16 deletions tests/adapters/usgs/test_adapter.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import pytest
from unittest import mock
from unittest.mock import patch, PropertyMock
import numpy as np

from theia.adapters.usgs import Adapter, ErosWrapper, EspaWrapper, ImagerySearch
import datetime
from theia.adapters.usgs import Adapter
from theia.api.models import ImageryRequest, JobBundle, RequestedScene

import tarfile

EROS_AVAILABLE_PRODUCTS_EXAMPLE = [{'entityId': 'LANDSATIMAGE123', 'productId': 123, 'displayId': 'LANDSAT_IMAGE_123'}]
DEFAULT_SEARCH = {'datasetName': 'LANDSAT_OT_C2_L2'}

class TestUsgsAdapter:
def test_resolve_relative_image(self):
Expand All @@ -18,20 +18,23 @@ def test_construct_filename(self):
bundle = JobBundle(scene_entity_id='LC08', local_path='tmp/')
assert(Adapter().construct_filename(bundle, 'aerosol')=='LC08_sr_aerosol.tif')

@patch('theia.adapters.usgs.ImagerySearch.build_search', return_value={})
@patch('theia.adapters.usgs.ErosWrapper.send_request', return_value=200)
@patch('theia.adapters.usgs.ErosWrapper.available_products', return_value=EROS_AVAILABLE_PRODUCTS_EXAMPLE)
@patch('theia.adapters.usgs.ImagerySearch.build_search', return_value=DEFAULT_SEARCH)
@patch('theia.adapters.usgs.ErosWrapper.search', return_value=['some scene id'])
@patch('theia.adapters.usgs.EspaWrapper.order_all', return_value=[{}])
@patch('theia.adapters.usgs.ErosWrapper.add_scenes_to_order_list', return_value=[{}])
@patch('theia.api.models.RequestedScene.objects.create', return_value=RequestedScene(id=3))
@patch('theia.adapters.usgs.tasks.wait_for_scene.delay')
def test_process_request(self, mock_wait, mock_rso, mock_order_all, mock_search, mock_build):
def test_process_request(self, mock_wait, mock_requested_scene_creation, mock_add_scene_to_order, mock_search, mock_build, mock_avail_prods, _):
request = ImageryRequest()
Adapter().process_request(request)

mock_build.assert_called_once_with(request)
mock_search.assert_called_once_with({})
mock_order_all.assert_called_once_with('some scene id', 'sr')
mock_rso.assert_called_once()
mock_wait.assert_called_once_with(3)
mock_search.assert_called_once_with(DEFAULT_SEARCH)
mock_add_scene_to_order.assert_called_once_with('some scene id', DEFAULT_SEARCH)
mock_avail_prods.assert_called_once_with('some scene id', DEFAULT_SEARCH)
mock_requested_scene_creation.assert_called_once()
mock_wait.assert_called_once_with(3, EROS_AVAILABLE_PRODUCTS_EXAMPLE)

@patch('os.path.isfile', return_value=False)
@patch('platform.uname_result.node', new_callable=PropertyMock, return_value='testhostname')
Expand All @@ -57,16 +60,18 @@ def test_remap_pixel(self):
assert(remap.tolist()==[0, 0, 125, 250, 255])
assert(remap.dtype==np.uint8)

@patch('theia.adapters.usgs.ImagerySearch.build_search', return_value={})
@patch('theia.adapters.usgs.ErosWrapper.send_request', return_value=200)
@patch('theia.adapters.usgs.ErosWrapper.available_products', return_value=EROS_AVAILABLE_PRODUCTS_EXAMPLE)
@patch('theia.adapters.usgs.ImagerySearch.build_search', return_value=DEFAULT_SEARCH)
@patch('theia.adapters.usgs.ErosWrapper.search', return_value=[1, 2, 3, 4, 5])
@patch('theia.adapters.usgs.EspaWrapper.order_all', return_value=[{}])
@patch('theia.api.models.RequestedScene.objects.create', return_value=RequestedScene(id=3))
@patch('theia.adapters.usgs.tasks.wait_for_scene.delay')
def test_limit_scenes(self, mock_wait, mock_rso, mock_order_all, mock_search, mock_build):
@patch('theia.adapters.usgs.ErosWrapper.add_scenes_to_order_list', return_value=[{}])
def test_limit_scenes(self, mock_add_scenes_to_order, *_):
request = ImageryRequest(max_results=3)
Adapter().process_request(request)

assert(mock_order_all.call_count==3)
assert(mock_add_scenes_to_order.call_count==3)

@patch('theia.adapters.usgs.XmlHelper.get_tree', autospec=True)
@patch('theia.adapters.usgs.XmlHelper.resolve')
Expand Down
156 changes: 149 additions & 7 deletions tests/adapters/usgs/test_eros_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,70 @@
from theia.adapters.usgs import ErosWrapper

from unittest.mock import patch, PropertyMock, call
from unittest.mock import patch, call
import datetime
THREE_HOURS_AGO = datetime.datetime.utcnow() - datetime.timedelta(hours=3)
FAKE_SEARCH = {'datasetName' : 'LANDSAT_BAND'}

class TestErosWrapper:
def test_is_token_expired_no_login_time(self):
is_expired = ErosWrapper().is_token_expired()
assert is_expired == True

def test_is_token_expired_login_time_past_expiry(self):
eros_wrapper = ErosWrapper()
eros_wrapper.login_time = THREE_HOURS_AGO
assert eros_wrapper.is_token_expired() == True

def test_is_token_expired_login_time_not_expired(self):
eros_wrapper = ErosWrapper()
eros_wrapper.login_time = datetime.datetime.utcnow()
assert eros_wrapper.is_token_expired() == False

@patch('theia.adapters.usgs.Utils.get_username', return_value='Richard Feynman')
@patch('theia.adapters.usgs.Utils.get_password', return_value='feynmansSuperSecurePassowrd')
@patch('theia.adapters.usgs.ErosWrapper.send_request', return_value=200)
def test_login_no_login_time(self, mock_send_request, *_):
eros_wrapper = ErosWrapper()
eros_wrapper.login()
mock_send_request.assert_has_calls([
call(
'https://m2m.cr.usgs.gov/api/api/json/stable/login',
{'username': 'Richard Feynman', 'password': 'feynmansSuperSecurePassowrd'}
)
])

@patch('theia.adapters.usgs.Utils.get_username', return_value='Richard Feynman')
@patch('theia.adapters.usgs.Utils.get_password', return_value='feynmansSuperSecurePassowrd')
@patch('theia.adapters.usgs.ErosWrapper.send_request', return_value=200)
def test_login_token_expired(self, mock_send_request, *_):
eros_wrapper = ErosWrapper()
eros_wrapper.login()
mock_send_request.assert_has_calls([
call(
'https://m2m.cr.usgs.gov/api/api/json/stable/login',
{'username': 'Richard Feynman', 'password': 'feynmansSuperSecurePassowrd'}
)
])

@patch('theia.adapters.usgs.ErosWrapper.send_request')
def test_login_not_called_when_token_not_expired(self, mock_send_request):
eros_wrapper = ErosWrapper()
eros_wrapper.login_time = datetime.datetime.utcnow()
eros_wrapper.login()
mock_send_request.assert_not_called()

@patch('theia.adapters.usgs.Utils.get_username', return_value='Richard Feynman')
@patch('theia.adapters.usgs.Utils.get_password', return_value='feynmansSuperSecurePassowrd')
@patch('theia.adapters.usgs.ErosWrapper.send_request', return_value={'results': ['LC01_FAKESCENE_007']})
def test_send_search_request(self, mockRequestSend, mockPassword, mockUsername):
fakeSearch = {'datasetName' : 'LANDSAT_BAND'}
ErosWrapper().search(search=fakeSearch)
def test_send_search_request(self, mockRequestSend, *_):
ErosWrapper().search(search=FAKE_SEARCH)
mockRequestSend.assert_has_calls([
call(
'https://m2m.cr.usgs.gov/api/api/json/stable/login',
{'username': 'Richard Feynman', 'password': 'feynmansSuperSecurePassowrd'}
),
call(
'https://m2m.cr.usgs.gov/api/api/json/stable/scene-search',
{'datasetName': 'LANDSAT_BAND'},
{'results': ['LC01_FAKESCENE_007']}
{'datasetName': 'LANDSAT_BAND'}
)
],
any_order=False)
Expand Down Expand Up @@ -54,3 +100,99 @@ def test_parse_result_set(self):
)
assert result == ['a']

@patch('theia.adapters.usgs.ErosWrapper.send_request', return_value=200)
def test_add_scene_to_order_list(self, mock_send_request):
eros_wrapper = ErosWrapper()
eros_wrapper.login_time = datetime.datetime.utcnow()
eros_wrapper.add_scenes_to_order_list(scene_id=1, search=FAKE_SEARCH)
mock_send_request.assert_has_calls([
call(
'https://m2m.cr.usgs.gov/api/api/json/stable/scene-list-add',
{'listId': 1, 'idField': 'displayId', 'entityId': 1, 'datasetName': 'LANDSAT_BAND'}
)
],
any_order=False)

@patch('theia.adapters.usgs.ErosWrapper.login')
@patch('theia.adapters.usgs.ErosWrapper.send_request', return_value=200)
def test_add_scene_to_order_list_logs_in(self, _, mock_login):
eros_wrapper = ErosWrapper()
eros_wrapper.add_scenes_to_order_list(scene_id=1, search=FAKE_SEARCH)
mock_login.assert_called_once()

@patch('theia.adapters.usgs.ErosWrapper.login')
@patch('theia.adapters.usgs.ErosWrapper.send_request')
def test_available_products_logs_in(self, _, mock_login):
eros_wrapper = ErosWrapper()
eros_wrapper.available_products(list_id=1, search=FAKE_SEARCH)
mock_login.assert_called_once()

@patch('theia.adapters.usgs.ErosWrapper.send_request', return_value=[{'bulkAvailable': True, 'downloadSystem': 'dds', 'entityId': 'LC01_FAKESCENE_007', 'displayId': 'LC01FAKESCENE007', 'id': 1}])
def test_available_products(self, mock_send_request):
eros_wrapper = ErosWrapper()
eros_wrapper.login_time = datetime.datetime.utcnow()
avail_products = eros_wrapper.available_products(list_id=1, search=FAKE_SEARCH)
mock_send_request.assert_has_calls([
call(
'https://m2m.cr.usgs.gov/api/api/json/stable/download-options',
{'listId': 1, 'datasetName': 'LANDSAT_BAND'}
)
],
any_order=False)
assert len(avail_products) == 1
assert avail_products[0] == {
'entityId': 'LC01_FAKESCENE_007',
'productId': 1,
'displayId': 'LC01FAKESCENE007'
}

@patch('theia.adapters.usgs.ErosWrapper.send_request', return_value=[])
def test_available_products_has_no_results(self, _):
eros_wrapper = ErosWrapper()
eros_wrapper.login_time = datetime.datetime.utcnow()
avail_products = eros_wrapper.available_products(list_id=1, search=FAKE_SEARCH)
assert avail_products == []

@patch('theia.adapters.usgs.ErosWrapper.send_request', return_value=[{'bulkAvailable': False, 'downloadSystem': 'dds', 'entityId': 'LC01_FAKESCENE_007', 'displayId': 'LC01FAKESCENE007', 'id': 1}])
def test_avail_products_result_not_available(self, _):
eros_wrapper = ErosWrapper()
eros_wrapper.login_time = datetime.datetime.utcnow()
avail_products = eros_wrapper.available_products(list_id=1, search=FAKE_SEARCH)
assert avail_products == []

@patch('theia.adapters.usgs.ErosWrapper.send_request', return_value=[{'bulkAvailable': True, 'downloadSystem': 'folder', 'entityId': 'LC01_FAKESCENE_007', 'displayId': 'LC01FAKESCENE007', 'id': 1}])
def test_avail_products_result_download_system_incorrect(self, _):
eros_wrapper = ErosWrapper()
eros_wrapper.login_time = datetime.datetime.utcnow()
avail_products = eros_wrapper.available_products(list_id=1, search=FAKE_SEARCH)
assert avail_products == []

@patch('theia.adapters.usgs.ErosWrapper.login')
@patch('theia.adapters.usgs.ErosWrapper.send_request')
def test_request_download_logs_in(self, _, mock_login):
eros_wrapper = ErosWrapper()
eros_wrapper.request_download([{
'entityId': 'LC01_FAKESCENE_007',
'productId': 1,
'displayId': 'LC01FAKESCENE007'
}])
mock_login.assert_called_once()

@patch('theia.adapters.usgs.ErosWrapper.send_request')
def test_request_download(self, mock_send_request):
eros_wrapper = ErosWrapper()
eros_wrapper.login_time = datetime.datetime.utcnow()
avail_products = [{
'entityId': 'LC01_FAKESCENE_007',
'productId': 1,
'displayId': 'LC01FAKESCENE007'
}]
eros_wrapper.request_download(avail_products)

mock_send_request.assert_has_calls([
call(
'https://m2m.cr.usgs.gov/api/api/json/stable/download-request',
{'downloads': avail_products, 'label': eros_wrapper.download_request_label}
)
]
)
48 changes: 34 additions & 14 deletions tests/adapters/usgs/test_usgs_tasks.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,59 @@
import pytest
from unittest.mock import patch
import theia.adapters.usgs.tasks

EROS_AVAILABLE_PRODUCTS_EXAMPLE = [{'entityId': 'LANDSATIMAGE123', 'productId': 123, 'displayId': 'LANDSAT_IMAGE_123'}]
EROS_REQUEST_DOWNLOAD_PENDING_DOWNLOADS_RESULT = {
"availableDownloads":[],
"duplicateProducts":[],
"preparingDownloads":[
{
"downloadId":550754832,
"eulaCode":"None",
"url":"https://dds.cr.usgs.gov/download-staging/eyJpZCI6NTUwNzU0ODMyLCJjb250YWN0SWQiOjI2MzY4NDQ1fQ=="
}
]
}

EROS_REQUEST_DOWNLOAD_DOWNLOADS_AVAILABLE_RESULT = {
"availableDownloads":[{
"downloadId":550752861,
"eulaCode":"None",
"url":"https://dds.cr.usgs.gov/download-staging/eyJpZCI6NTUwNzUyODYxLCJjb250YWN0SWQiOjI2MzY4NDQ1fQ=="
}],
"duplicateProducts":[],
"preparingDownloads":[]
}

@patch('theia.api.models.RequestedScene.objects.get', autospec=True)
@patch('theia.adapters.usgs.EspaWrapper.order_status', autospec=True)
@patch('theia.adapters.usgs.ErosWrapper.request_download', autospec=True)
def test_wait_for_scene_already_done(mockStatus, mockGetScene):
mockGetScene.return_value.status=1
theia.adapters.usgs.tasks.wait_for_scene(1)
theia.adapters.usgs.tasks.wait_for_scene(1, EROS_AVAILABLE_PRODUCTS_EXAMPLE)
mockStatus.assert_not_called()

@patch('theia.api.models.RequestedScene.objects.get', autospec=True)
@patch('theia.adapters.usgs.EspaWrapper.order_status', return_value='whatever')
@patch('theia.adapters.usgs.EspaWrapper.download_urls', autospec=True)
@patch('theia.adapters.usgs.ErosWrapper.request_download', return_value=EROS_REQUEST_DOWNLOAD_PENDING_DOWNLOADS_RESULT)
@patch('theia.adapters.usgs.tasks.wait_for_scene.apply_async', autospec=True)
def test_wait_for_scene_pending(mockWait, mockUrls, mockStatus, mockGetScene):
def test_wait_for_scene_pending(mockWait, mockStatus, mockGetScene):
mockGetScene.return_value.status=0
mockGetScene.return_value.scene_order_id='order id'

theia.adapters.usgs.tasks.wait_for_scene(3)
theia.adapters.usgs.tasks.wait_for_scene(3, EROS_AVAILABLE_PRODUCTS_EXAMPLE)

mockGetScene.assert_called_once_with(pk=3)
mockStatus.assert_called_once_with('order id')
mockStatus.assert_called_once_with(EROS_AVAILABLE_PRODUCTS_EXAMPLE)
mockGetScene.return_value.save.assert_called_once()
mockWait.assert_called_once()

@patch('theia.api.models.RequestedScene.objects.get', autospec=True)
@patch('theia.adapters.usgs.EspaWrapper.order_status', return_value='complete')
@patch('theia.adapters.usgs.EspaWrapper.download_urls')
@patch('theia.adapters.usgs.ErosWrapper.request_download', return_value=EROS_REQUEST_DOWNLOAD_DOWNLOADS_AVAILABLE_RESULT)
@patch('theia.api.models.JobBundleManager.from_requested_scene', autospec=True)
def test_wait_for_scene_ready(mockConstruct, mockUrls, mockStatus, mockGetScene):
def test_wait_for_scene_ready(mockConstruct, mockStatus, mockGetScene):
mockGetScene.return_value.status=0
mockGetScene.return_value.scene_order_id='order id'
theia.adapters.usgs.tasks.wait_for_scene(3)
theia.adapters.usgs.tasks.wait_for_scene(3, EROS_AVAILABLE_PRODUCTS_EXAMPLE)

mockGetScene.assert_called_once_with(pk=3)
mockStatus.assert_called_once_with('order id')
mockStatus.assert_called_once_with(EROS_AVAILABLE_PRODUCTS_EXAMPLE)
mockGetScene.return_value.save.assert_called()
mockConstruct.assert_called_once()
mockConstruct.assert_called_once()
23 changes: 15 additions & 8 deletions theia/adapters/usgs/adapter.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import os.path
import platform
import re
import urllib.request
import numpy as np

from theia.api import models
from theia.utils import FileUtils

from .eros_wrapper import ErosWrapper
from .espa_wrapper import EspaWrapper
from .imagery_search import ImagerySearch
from .tasks import wait_for_scene
from .xml_helper import XmlHelper
Expand Down Expand Up @@ -69,15 +67,25 @@ def __init__(self):

def process_request(self, imagery_request):
search = ImagerySearch.build_search(imagery_request)
scenes = ErosWrapper().search(search)
eros_wrapper = ErosWrapper()
scenes = eros_wrapper.search(search)

if imagery_request.max_results:
scenes = scenes[0:imagery_request.max_results]

for scene in scenes:
result = EspaWrapper.order_all(scene, 'sr')
for item in result:
req = models.RequestedScene.objects.create(**{**item, **{'imagery_request': imagery_request}})
wait_for_scene.delay(req.id)
eros_wrapper.add_scenes_to_order_list(scene, search)
available_products = eros_wrapper.available_products(scene, search)

eros_wrapper.request_download(available_products)

for item in available_products:
req = models.RequestedScene.objects.create(
scene_entity_id = item['displayId'],
scene_order_id = item['productId'],
**{'imagery_request': imagery_request}
)
wait_for_scene.delay(req.id, available_products)

def construct_filename(self, bundle, suffix):
product = "sr"
Expand All @@ -104,7 +112,6 @@ def retrieve(self, job_bundle):
# get the compressed scene data if we don't have it
if not os.path.isfile(zip_path):
urllib.request.urlretrieve(job_bundle.requested_scene.scene_url, zip_path)

FileUtils.untar(zip_path, job_bundle.local_path)

def default_extension(self):
Expand Down
Loading
Loading