diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f2494b1..82990ed 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,13 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + version: 2 updates: - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: github-actions + directory: / schedule: - interval: "daily" + interval: weekly labels: - - "bumpless" + - bumpless diff --git a/.github/workflows/distribute.yml b/.github/workflows/distribute.yml index 80beefc..00323de 100644 --- a/.github/workflows/distribute.yml +++ b/.github/workflows/distribute.yml @@ -29,26 +29,7 @@ jobs: python -m build - name: upload to PyPI.org - uses: pypa/gh-action-pypi-publish@v1.8.11 + uses: pypa/gh-action-pypi-publish@v1.8.14 with: user: __token__ password: ${{ secrets.TOOLS_PYPI_PAK }} - - verify-distribution: - runs-on: ubuntu-latest - needs: - - call-version-info-workflow - - distribute - defaults: - run: - shell: bash -l {0} - steps: - - uses: actions/checkout@v4 - - - uses: mamba-org/setup-micromamba@v1 - with: - environment-file: environment.yml - - - name: Ensure hyp3_sdk v${{ needs.call-version-info-workflow.outputs.version }}} is pip installable - run: | - python -m pip install hyp3_sdk==${{ needs.call-version-info-workflow.outputs.version_tag }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bfd7a6..a8f34af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [PEP 440](https://www.python.org/dev/peps/pep-0440/) and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [6.2.0] + +### Added +* `Job.priority` attribute +* Unapproved `hyp3-sdk` users receive an error message when connecting to `HyP3` + ## [6.1.0] ### Added diff --git a/src/hyp3_sdk/hyp3.py b/src/hyp3_sdk/hyp3.py index 4f653cc..ab9d97a 100644 --- a/src/hyp3_sdk/hyp3.py +++ b/src/hyp3_sdk/hyp3.py @@ -46,6 +46,20 @@ def __init__(self, api_url: str = PROD_API, username: Optional[str] = None, pass self.session = hyp3_sdk.util.get_authenticated_session(username, password) self.session.headers.update({'User-Agent': f'{hyp3_sdk.__name__}/{hyp3_sdk.__version__}'}) + self._check_application_status() + + def _check_application_status(self) -> None: + help_url = 'https://hyp3-docs.asf.alaska.edu/using/requesting_access' + info = self.my_info() + if info['application_status'] == 'NOT_STARTED': + warnings.warn(f'User {info["user_id"]} has not yet applied for a monthly credit allotment.' + f' Please visit {help_url} to submit your application.') + if info['application_status'] == 'PENDING': + warnings.warn(f'User {info["user_id"]}\'s request for access is pending review. For more information, ' + f'visit {help_url}') + if info['application_status'] == 'REJECTED': + warnings.warn(f'{info["user_id"]}\'s request for access has been rejected. For more information, ' + f'visit {help_url}') def find_jobs(self, start: Optional[datetime] = None, diff --git a/src/hyp3_sdk/jobs.py b/src/hyp3_sdk/jobs.py index 5a6a09d..3e11548 100644 --- a/src/hyp3_sdk/jobs.py +++ b/src/hyp3_sdk/jobs.py @@ -32,6 +32,7 @@ def __init__( expiration_time: Optional[datetime] = None, processing_times: Optional[List[float]] = None, credit_cost: Optional[float] = None, + priority: Optional[int] = None, ): self.job_id = job_id self.job_type = job_type @@ -47,6 +48,7 @@ def __init__( self.expiration_time = expiration_time self.processing_times = processing_times self.credit_cost = credit_cost + self.priority = priority def __repr__(self): return f'Job.from_dict({self.to_dict()})' @@ -75,6 +77,7 @@ def from_dict(input_dict: dict): expiration_time=expiration_time, processing_times=input_dict.get('processing_times'), credit_cost=input_dict.get('credit_cost'), + priority=input_dict.get('priority'), ) def to_dict(self, for_resubmit: bool = False): diff --git a/tests/conftest.py b/tests/conftest.py index fdb2895..9c7d68c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,29 @@ import shutil from datetime import datetime from pathlib import Path +from unittest.mock import patch from uuid import uuid4 import pytest +import requests from hyp3_sdk import Job +from hyp3_sdk.hyp3 import HyP3 + + +@pytest.fixture(autouse=True) +def get_mock_hyp3(): + def mock_my_info(self): + return {'application_status': 'APPROVED'} + + def mock_get_authenticated_session(username, password): + return requests.Session() + + def default_hyp3(): + with patch('hyp3_sdk.hyp3.HyP3.my_info', mock_my_info): + with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): + return HyP3() + return default_hyp3 @pytest.fixture(autouse=True) @@ -22,6 +40,7 @@ def default_job( thumbnail_images=None, expiration_time=None, credit_cost=None, + priority=None, ): if job_parameters is None: job_parameters = {'param1': 'value1'} diff --git a/tests/test_hyp3.py b/tests/test_hyp3.py index e1cb2cb..aa60407 100644 --- a/tests/test_hyp3.py +++ b/tests/test_hyp3.py @@ -11,14 +11,9 @@ from hyp3_sdk import HyP3, Job -def mock_get_authenticated_session(username, password): - return requests.Session() - - @responses.activate -def test_session_headers(): - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() +def test_session_headers(get_mock_hyp3): + api = get_mock_hyp3() responses.add(responses.GET, urljoin(api.url, '/user'), json={'foo': 'bar'}) @@ -30,7 +25,7 @@ def test_session_headers(): @responses.activate -def test_find_jobs(get_mock_job): +def test_find_jobs(get_mock_hyp3, get_mock_job): api_response_mock = { 'jobs': [ get_mock_job(name='job1').to_dict(), @@ -42,8 +37,7 @@ def test_find_jobs(get_mock_job): ] } - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() + api = get_mock_hyp3() responses.add(responses.GET, urljoin(api.url, '/jobs'), json=api_response_mock) responses.add(responses.GET, urljoin(api.url, '/jobs'), json={'jobs': []}) @@ -56,9 +50,8 @@ def test_find_jobs(get_mock_job): @responses.activate -def test_find_jobs_paging(get_mock_job): - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() +def test_find_jobs_paging(get_mock_hyp3, get_mock_job): + api = get_mock_hyp3() api_response_mock_1 = { 'jobs': [ @@ -83,9 +76,8 @@ def test_find_jobs_paging(get_mock_job): @responses.activate -def test_find_jobs_user_id(get_mock_job): - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() +def test_find_jobs_user_id(get_mock_hyp3, get_mock_job): + api = get_mock_hyp3() responses.add(responses.GET, urljoin(api.url, '/jobs?user_id=foo'), json={'jobs': []}, match_querystring=True) @@ -101,9 +93,8 @@ def test_find_jobs_user_id(get_mock_job): @responses.activate -def test_find_jobs_start(): - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() +def test_find_jobs_start(get_mock_hyp3): + api = get_mock_hyp3() responses.add(responses.GET, urljoin(api.url, '/jobs?start=2021-01-01T00%3A00%3A00%2B00%3A00'), json={'jobs': []}, match_querystring=True) @@ -116,9 +107,8 @@ def test_find_jobs_start(): @responses.activate -def test_find_jobs_end(): - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() +def test_find_jobs_end(get_mock_hyp3): + api = get_mock_hyp3() responses.add(responses.GET, urljoin(api.url, '/jobs?end=2021-01-02T00%3A00%3A00%2B00%3A00'), json={'jobs': []}, match_querystring=True) @@ -131,9 +121,8 @@ def test_find_jobs_end(): @responses.activate -def test_find_jobs_status_code(): - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() +def test_find_jobs_status_code(get_mock_hyp3): + api = get_mock_hyp3() responses.add(responses.GET, urljoin(api.url, '/jobs?status_code=RUNNING'), json={'jobs': []}, match_querystring=True) @@ -147,22 +136,20 @@ def test_find_jobs_status_code(): @responses.activate -def test_get_job_by_id(get_mock_job): +def test_get_job_by_id(get_mock_hyp3, get_mock_job): job = get_mock_job() - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() + api = get_mock_hyp3() responses.add(responses.GET, urljoin(api.url, f'/jobs/{job.job_id}'), json=job.to_dict()) response = api.get_job_by_id(job.job_id) assert response == job @responses.activate -def test_watch(get_mock_job): +def test_watch(get_mock_hyp3, get_mock_job): incomplete_job = get_mock_job() complete_job = Job.from_dict(incomplete_job.to_dict()) complete_job.status_code = 'SUCCEEDED' - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() + api = get_mock_hyp3() for ii in range(3): responses.add(responses.GET, urljoin(api.url, f'/jobs/{incomplete_job.job_id}'), json=incomplete_job.to_dict()) @@ -174,13 +161,12 @@ def test_watch(get_mock_job): @responses.activate -def test_refresh(get_mock_job): +def test_refresh(get_mock_hyp3, get_mock_job): job = get_mock_job() new_job = Job.from_dict(job.to_dict()) new_job.status_code = 'SUCCEEDED' - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() + api = get_mock_hyp3() responses.add(responses.GET, urljoin(api.url, f'/jobs/{job.job_id}'), json=new_job.to_dict()) response = api.refresh(job) @@ -188,7 +174,7 @@ def test_refresh(get_mock_job): @responses.activate -def test_submit_prepared_jobs(get_mock_job): +def test_submit_prepared_jobs(get_mock_hyp3, get_mock_job): rtc_job = get_mock_job('RTC_GAMMA', job_parameters={'granules': ['g1']}) insar_job = get_mock_job('INSAR_GAMMA', job_parameters={'granules': ['g1', 'g2']}) api_response = { @@ -198,8 +184,7 @@ def test_submit_prepared_jobs(get_mock_job): ] } - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() + api = get_mock_hyp3() responses.add(responses.POST, urljoin(api.url, '/jobs'), json=api_response) @@ -331,82 +316,77 @@ def test_deprecated_warning(): @responses.activate -def test_submit_autorift_job(get_mock_job): +def test_submit_autorift_job(get_mock_hyp3, get_mock_job): job = get_mock_job('AUTORIFT', job_parameters={'granules': ['g1', 'g2']}) api_response = { 'jobs': [ job.to_dict() ] } - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() + api = get_mock_hyp3() responses.add(responses.POST, urljoin(api.url, '/jobs'), json=api_response) batch = api.submit_autorift_job('g1', 'g2') assert batch.jobs[0] == job @responses.activate -def test_submit_rtc_job(get_mock_job): +def test_submit_rtc_job(get_mock_hyp3, get_mock_job): job = get_mock_job('RTC_GAMMA', job_parameters={'granules': ['g1']}) api_response = { 'jobs': [ job.to_dict() ] } - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() + api = get_mock_hyp3() responses.add(responses.POST, urljoin(api.url, '/jobs'), json=api_response) batch = api.submit_rtc_job('g1') assert batch.jobs[0] == job @responses.activate -def test_submit_insar_job(get_mock_job): +def test_submit_insar_job(get_mock_hyp3, get_mock_job): job = get_mock_job('INSAR_GAMMA', job_parameters={'granules': ['g1', 'g2']}) api_response = { 'jobs': [ job.to_dict() ] } - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() + api = get_mock_hyp3() responses.add(responses.POST, urljoin(api.url, '/jobs'), json=api_response) batch = api.submit_insar_job('g1', 'g2') assert batch.jobs[0] == job @responses.activate -def test_submit_insar_isce_burst_job(get_mock_job): +def test_submit_insar_isce_burst_job(get_mock_hyp3, get_mock_job): job = get_mock_job('INSAR_ISCE_BURST', job_parameters={'granules': ['g1', 'g2']}) api_response = { 'jobs': [ job.to_dict() ] } - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() + api = get_mock_hyp3() responses.add(responses.POST, urljoin(api.url, '/jobs'), json=api_response) batch = api.submit_insar_isce_burst_job('g1', 'g2') assert batch.jobs[0] == job @responses.activate -def test_resubmit_previous_job(get_mock_job): +def test_resubmit_previous_job(get_mock_hyp3, get_mock_job): job = get_mock_job() api_response = { 'jobs': [ job.to_dict() ] } - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() + api = get_mock_hyp3() responses.add(responses.POST, urljoin(api.url, '/jobs'), json=api_response) batch = api.submit_prepared_jobs(job.to_dict(for_resubmit=True)) assert batch.jobs[0] == job @responses.activate -def test_my_info(): +def test_my_info(get_mock_hyp3): api_response = { 'job_names': [ 'name1', @@ -415,15 +395,14 @@ def test_my_info(): 'remaining_credits': 25., 'user_id': 'someUser' } - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() + api = get_mock_hyp3() responses.add(responses.GET, urljoin(api.url, '/user'), json=api_response) response = api.my_info() assert response == api_response @responses.activate -def test_check_credits(): +def test_check_credits(get_mock_hyp3): api_response = { 'job_names': [ 'name1', @@ -432,18 +411,53 @@ def test_check_credits(): 'remaining_credits': 25., 'user_id': 'someUser' } - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() + api = get_mock_hyp3() responses.add(responses.GET, urljoin(api.url, '/user'), json=api_response) assert math.isclose(api.check_credits(), 25.) @responses.activate -def test_costs(): +def test_check_application_status_approved(get_mock_hyp3): + with warnings.catch_warnings(record=True) as w: + _ = get_mock_hyp3() + assert len(w) == 0 + + +@responses.activate +def test_check_application_status_errors(get_mock_hyp3): + application_status = 'NOT_STARTED' + with warnings.catch_warnings(record=True) as w: + with patch('hyp3_sdk.hyp3.HyP3.my_info', lambda x: {'user_id': 'someUser', + 'application_status': application_status}): + with patch('hyp3_sdk.util.get_authenticated_session', lambda username, password: requests.Session()): + _ = HyP3() + assert len(w) == 1 + assert 'not yet applied for a monthly credit allotment' in str(w[0].message) + + application_status = 'PENDING' + with warnings.catch_warnings(record=True) as w: + with patch('hyp3_sdk.hyp3.HyP3.my_info', lambda x: {'user_id': 'someUser', + 'application_status': application_status}): + with patch('hyp3_sdk.util.get_authenticated_session', lambda username, password: requests.Session()): + _ = HyP3() + assert len(w) == 1 + assert 'request for access is pending review' in str(w[0].message) + + application_status = 'REJECTED' + with warnings.catch_warnings(record=True) as w: + with patch('hyp3_sdk.hyp3.HyP3.my_info', lambda x: {'user_id': 'someUser', + 'application_status': application_status}): + with patch('hyp3_sdk.util.get_authenticated_session', lambda username, password: requests.Session()): + _ = HyP3() + assert len(w) == 1 + assert 'request for access has been rejected' in str(w[0].message) + + +@responses.activate +def test_costs(get_mock_hyp3): api_response = {'foo': 5} - with patch('hyp3_sdk.util.get_authenticated_session', mock_get_authenticated_session): - api = HyP3() + api = get_mock_hyp3() responses.add(responses.GET, urljoin(api.url, '/costs'), json=api_response) assert api.costs() == {'foo': 5} diff --git a/tests/test_jobs.py b/tests/test_jobs.py index 02c2640..e679e79 100644 --- a/tests/test_jobs.py +++ b/tests/test_jobs.py @@ -25,6 +25,7 @@ "thumbnail_images": ["https://PAIR_PROCESS_thumb.png"], "user_id": "asf_hyp3", "credit_cost": 1, + "priority": 9999, } FAILED_JOB = { @@ -42,6 +43,7 @@ "status_code": "FAILED", "user_id": "asf_hyp3", "credit_cost": 1, + "priority": 9999, }