From 0cf1f1051e2161c8287560444d1d673cdb0b883b Mon Sep 17 00:00:00 2001 From: Ken Kroenlein Date: Thu, 29 Aug 2024 12:27:34 -0600 Subject: [PATCH 1/2] Update dependencies, eliminating warnings --- requirements.txt | 5 +++-- setup.py | 7 +++---- src/citrine/__version__.py | 2 +- src/citrine/_session.py | 19 +++++++++++------ src/citrine/resources/data_concepts.py | 4 ++-- test_requirements.txt | 1 - tests/test_citrine.py | 7 +++++-- tests/test_session.py | 28 +++++++++++++------------- 8 files changed, 41 insertions(+), 32 deletions(-) diff --git a/requirements.txt b/requirements.txt index 479cf9fde..d14f4fe12 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,10 @@ arrow==1.3.0 boto3==1.34.35 deprecation==2.1.0 -gemd==2.1.8 +gemd==2.1.9 pyjwt==2.8.0 -requests==2.32.0 +pytz==2024.1; python_version < "3.9" +requests==2.32.2 tqdm==4.66.3 # boto3 (through botocore) depends on urllib3. Version 1.34.35 requires diff --git a/setup.py b/setup.py index 91ab80a33..9104a74e4 100644 --- a/setup.py +++ b/setup.py @@ -22,15 +22,15 @@ package_dir={'': 'src'}, packages=find_packages(where='src'), install_requires=[ - "requests>=2.31.0,<3", + "requests>=2.32.2,<3", "pyjwt>=2,<3", "arrow>=1.0.0,<2", - "gemd>=2.1.8,<3", + "gemd>=2.1.9,<3", "boto3>=1.34.35,<2", "deprecation>=2.1.0,<3", "urllib3>=1.26.18,<3", "tqdm>=4.27.0,<5", - "pint>=0.21,<0.24" + "pytz>=2024.1; python_version<'3.9'", ], extras_require={ "tests": [ @@ -38,7 +38,6 @@ "mock>=5.1.0,<6", "pandas>=2.0.3,<3", "pytest>=8.0.0,<9", - "pytz>=2024.1", "requests-mock>=1.11.0,<2", ] }, diff --git a/src/citrine/__version__.py b/src/citrine/__version__.py index dcbfb52f6..0c11babd0 100644 --- a/src/citrine/__version__.py +++ b/src/citrine/__version__.py @@ -1 +1 @@ -__version__ = "3.5.0" +__version__ = "3.5.1" diff --git a/src/citrine/_session.py b/src/citrine/_session.py index d0807fb42..28b8133e9 100644 --- a/src/citrine/_session.py +++ b/src/citrine/_session.py @@ -1,5 +1,9 @@ import platform from datetime import datetime, timedelta +try: # Only available starting Python 3.9 + from datetime import UTC +except ImportError: # pragma: no cover + from pytz import utc as UTC from json.decoder import JSONDecodeError from logging import getLogger from os import environ @@ -53,7 +57,7 @@ def __init__(self, self.authority = ':'.join(([host] if host else []) + ([port] if port else [])) self.refresh_token: str = refresh_token self.access_token: Optional[str] = None - self.access_token_expiration: datetime = datetime.utcnow() + self.access_token_expiration: datetime = datetime.now(UTC) agent = "{}/{} python-requests/{} citrine-python/{}".format( platform.python_implementation(), @@ -106,7 +110,7 @@ def _versioned_base_url(self, version: str = 'v1'): )) def _is_access_token_expired(self): - return self.access_token_expiration - EXPIRATION_BUFFER_MILLIS <= datetime.utcnow() + return self.access_token_expiration - EXPIRATION_BUFFER_MILLIS <= datetime.now(UTC) def _refresh_access_token(self) -> None: """Optionally refresh our access token (if the previous one is about to expire).""" @@ -118,10 +122,13 @@ def _refresh_access_token(self) -> None: if response.status_code != 200: raise UnauthorizedRefreshToken() self.access_token = response.json()['access_token'] - self.access_token_expiration = datetime.utcfromtimestamp( - jwt.decode(self.access_token, - options={"verify_signature": False}, - algorithms=["HS256"])['exp'] + self.access_token_expiration = datetime.fromtimestamp( + jwt.decode( + self.access_token, + options={"verify_signature": False}, + algorithms=["HS256"] + )['exp'], + UTC ) # Explicitly set an updated 'auth', so as to not rely on implicit cookie handling. diff --git a/src/citrine/resources/data_concepts.py b/src/citrine/resources/data_concepts.py index c6cbaea80..344db7ddb 100644 --- a/src/citrine/resources/data_concepts.py +++ b/src/citrine/resources/data_concepts.py @@ -248,9 +248,9 @@ def _path_template(self): @property def _dataset_agnostic_path_template(self): if self.project_id is None: - return f'teams/{self.team_id}/{self._collection_key.replace("_","-")}' + return f'teams/{self.team_id}/{self._collection_key.replace("_", "-")}' else: - return f'projects/{self.project_id}/{self._collection_key.replace("_","-")}' + return f'projects/{self.project_id}/{self._collection_key.replace("_", "-")}' def build(self, data: dict) -> ResourceType: """ diff --git a/test_requirements.txt b/test_requirements.txt index 31c4ee7dd..17599bc63 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -5,7 +5,6 @@ flake8-docstrings==1.7.0 mock==5.1.0 pytest==8.0.0 pytest-cov==4.1.0 -pytz==2024.1 requests-mock==1.11.0 # faker is a dependency of factory-boy, but factory-boy sets a very low floor diff --git a/tests/test_citrine.py b/tests/test_citrine.py index 0e1ced6e5..bae23ccd6 100644 --- a/tests/test_citrine.py +++ b/tests/test_citrine.py @@ -1,9 +1,12 @@ import platform from datetime import datetime +try: + from datetime import UTC +except ImportError: # Only available starting Python 3.9 + from pytz import utc as UTC import jwt import pytest -import pytz import requests_mock from citrine import Citrine @@ -17,7 +20,7 @@ def refresh_token(expiration: datetime = None) -> dict: return {'access_token': token} -token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=pytz.utc)) +token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=UTC)) def test_citrine_creation(): diff --git a/tests/test_session.py b/tests/test_session.py index d039bad8e..b702049c9 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -1,19 +1,19 @@ -import json - import jwt import pytest -import unittest from citrine.exceptions import ( BadRequest, - CitrineException, Conflict, NonRetryableException, WorkflowNotReadyException, RetryableException) from datetime import datetime, timedelta -import pytz +try: + from datetime import UTC +except ImportError: # Only available starting Python 3.9 + from pytz import utc as UTC + import mock import requests import requests_mock @@ -32,7 +32,7 @@ def refresh_token(expiration: datetime = None) -> dict: @pytest.fixture def session(): - token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=pytz.utc)) + token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=UTC)) with requests_mock.Mocker() as m: m.post('http://citrine-testing.fake/api/v1/tokens/refresh', json=token_refresh_response) session = Session( @@ -43,13 +43,13 @@ def session(): # Default behavior is to *not* require a refresh - those tests can clear this out # As rule of thumb, we should be using freezegun or similar to never rely on the system clock # for these scenarios, but I thought this is light enough to postpone that for the time being - session.access_token_expiration = datetime.utcnow() + timedelta(minutes=3) + session.access_token_expiration = datetime.now(UTC) + timedelta(minutes=3) return session def test_session_signature(monkeypatch): - token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=pytz.utc)) + token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=UTC)) with requests_mock.Mocker() as m: m.post('ftp://citrine-testing.fake:8080/api/v1/tokens/refresh', json=token_refresh_response) @@ -77,8 +77,8 @@ def test_session_signature(monkeypatch): def test_get_refreshes_token(session: Session): - session.access_token_expiration = datetime.utcnow() - timedelta(minutes=1) - token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=pytz.utc)) + session.access_token_expiration = datetime.now(UTC) - timedelta(minutes=1) + token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=UTC)) with requests_mock.Mocker() as m: m.post('http://citrine-testing.fake/api/v1/tokens/refresh', json=token_refresh_response) @@ -89,11 +89,11 @@ def test_get_refreshes_token(session: Session): resp = session.get_resource('/foo') assert {'foo': 'bar'} == resp - assert datetime(2019, 3, 14) == session.access_token_expiration + assert datetime(2019, 3, 14, tzinfo=UTC) == session.access_token_expiration def test_get_refresh_token_failure(session: Session): - session.access_token_expiration = datetime.utcnow() - timedelta(minutes=1) + session.access_token_expiration = datetime.now(UTC) - timedelta(minutes=1) with requests_mock.Mocker() as m: m.post('http://citrine-testing.fake/api/v1/tokens/refresh', status_code=401) @@ -197,7 +197,7 @@ def test_connection_error(session: Session): def test_post_refreshes_token_when_denied(session: Session): - token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=pytz.utc)) + token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=UTC)) with requests_mock.Mocker() as m: m.post('http://citrine-testing.fake/api/v1/tokens/refresh', json=token_refresh_response) @@ -209,7 +209,7 @@ def test_post_refreshes_token_when_denied(session: Session): resp = session.post_resource('/foo', json={'data': 'hi'}) assert {'foo': 'bar'} == resp - assert datetime(2019, 3, 14) == session.access_token_expiration + assert datetime(2019, 3, 14, tzinfo=UTC) == session.access_token_expiration # this test exists to provide 100% coverage for the legacy 401 status on Unauthorized responses From 34438e05fc8508e5c12c3d3b003f1ec0c9199185 Mon Sep 17 00:00:00 2001 From: Ken Kroenlein Date: Thu, 29 Aug 2024 13:44:25 -0600 Subject: [PATCH 2/2] Remove dependence on pytz --- requirements.txt | 1 - setup.py | 1 - src/citrine/_session.py | 15 ++++++--------- tests/test_citrine.py | 8 ++------ tests/test_session.py | 24 ++++++++++-------------- 5 files changed, 18 insertions(+), 31 deletions(-) diff --git a/requirements.txt b/requirements.txt index d14f4fe12..c7f58a04e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,6 @@ boto3==1.34.35 deprecation==2.1.0 gemd==2.1.9 pyjwt==2.8.0 -pytz==2024.1; python_version < "3.9" requests==2.32.2 tqdm==4.66.3 diff --git a/setup.py b/setup.py index 9104a74e4..848e1f4e0 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,6 @@ "deprecation>=2.1.0,<3", "urllib3>=1.26.18,<3", "tqdm>=4.27.0,<5", - "pytz>=2024.1; python_version<'3.9'", ], extras_require={ "tests": [ diff --git a/src/citrine/_session.py b/src/citrine/_session.py index 28b8133e9..e7033cf35 100644 --- a/src/citrine/_session.py +++ b/src/citrine/_session.py @@ -1,9 +1,5 @@ import platform -from datetime import datetime, timedelta -try: # Only available starting Python 3.9 - from datetime import UTC -except ImportError: # pragma: no cover - from pytz import utc as UTC +from datetime import datetime, timedelta, timezone from json.decoder import JSONDecodeError from logging import getLogger from os import environ @@ -29,7 +25,7 @@ # Choose a 5-second buffer so that there's no chance of the access token # expiring during the check for expiration -EXPIRATION_BUFFER_MILLIS: timedelta = timedelta(milliseconds=5000) +EXPIRATION_BUFFER: timedelta = timedelta(seconds=5) logger = getLogger(__name__) @@ -57,7 +53,7 @@ def __init__(self, self.authority = ':'.join(([host] if host else []) + ([port] if port else [])) self.refresh_token: str = refresh_token self.access_token: Optional[str] = None - self.access_token_expiration: datetime = datetime.now(UTC) + self.access_token_expiration: datetime = datetime.now(timezone.utc) agent = "{}/{} python-requests/{} citrine-python/{}".format( platform.python_implementation(), @@ -110,7 +106,8 @@ def _versioned_base_url(self, version: str = 'v1'): )) def _is_access_token_expired(self): - return self.access_token_expiration - EXPIRATION_BUFFER_MILLIS <= datetime.now(UTC) + buffered_expire = self.access_token_expiration - EXPIRATION_BUFFER + return datetime.now(timezone.utc) > buffered_expire def _refresh_access_token(self) -> None: """Optionally refresh our access token (if the previous one is about to expire).""" @@ -128,7 +125,7 @@ def _refresh_access_token(self) -> None: options={"verify_signature": False}, algorithms=["HS256"] )['exp'], - UTC + timezone.utc ) # Explicitly set an updated 'auth', so as to not rely on implicit cookie handling. diff --git a/tests/test_citrine.py b/tests/test_citrine.py index bae23ccd6..539ce2a26 100644 --- a/tests/test_citrine.py +++ b/tests/test_citrine.py @@ -1,9 +1,5 @@ import platform -from datetime import datetime -try: - from datetime import UTC -except ImportError: # Only available starting Python 3.9 - from pytz import utc as UTC +from datetime import datetime, timezone import jwt import pytest @@ -20,7 +16,7 @@ def refresh_token(expiration: datetime = None) -> dict: return {'access_token': token} -token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=UTC)) +token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=timezone.utc)) def test_citrine_creation(): diff --git a/tests/test_session.py b/tests/test_session.py index b702049c9..f1e105bb3 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -8,11 +8,7 @@ WorkflowNotReadyException, RetryableException) -from datetime import datetime, timedelta -try: - from datetime import UTC -except ImportError: # Only available starting Python 3.9 - from pytz import utc as UTC +from datetime import datetime, timedelta, timezone import mock import requests @@ -32,7 +28,7 @@ def refresh_token(expiration: datetime = None) -> dict: @pytest.fixture def session(): - token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=UTC)) + token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=timezone.utc)) with requests_mock.Mocker() as m: m.post('http://citrine-testing.fake/api/v1/tokens/refresh', json=token_refresh_response) session = Session( @@ -43,13 +39,13 @@ def session(): # Default behavior is to *not* require a refresh - those tests can clear this out # As rule of thumb, we should be using freezegun or similar to never rely on the system clock # for these scenarios, but I thought this is light enough to postpone that for the time being - session.access_token_expiration = datetime.now(UTC) + timedelta(minutes=3) + session.access_token_expiration = datetime.now(timezone.utc) + timedelta(minutes=3) return session def test_session_signature(monkeypatch): - token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=UTC)) + token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=timezone.utc)) with requests_mock.Mocker() as m: m.post('ftp://citrine-testing.fake:8080/api/v1/tokens/refresh', json=token_refresh_response) @@ -77,8 +73,8 @@ def test_session_signature(monkeypatch): def test_get_refreshes_token(session: Session): - session.access_token_expiration = datetime.now(UTC) - timedelta(minutes=1) - token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=UTC)) + session.access_token_expiration = datetime.now(timezone.utc) - timedelta(minutes=1) + token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=timezone.utc)) with requests_mock.Mocker() as m: m.post('http://citrine-testing.fake/api/v1/tokens/refresh', json=token_refresh_response) @@ -89,11 +85,11 @@ def test_get_refreshes_token(session: Session): resp = session.get_resource('/foo') assert {'foo': 'bar'} == resp - assert datetime(2019, 3, 14, tzinfo=UTC) == session.access_token_expiration + assert datetime(2019, 3, 14, tzinfo=timezone.utc) == session.access_token_expiration def test_get_refresh_token_failure(session: Session): - session.access_token_expiration = datetime.now(UTC) - timedelta(minutes=1) + session.access_token_expiration = datetime.now(timezone.utc) - timedelta(minutes=1) with requests_mock.Mocker() as m: m.post('http://citrine-testing.fake/api/v1/tokens/refresh', status_code=401) @@ -197,7 +193,7 @@ def test_connection_error(session: Session): def test_post_refreshes_token_when_denied(session: Session): - token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=UTC)) + token_refresh_response = refresh_token(datetime(2019, 3, 14, tzinfo=timezone.utc)) with requests_mock.Mocker() as m: m.post('http://citrine-testing.fake/api/v1/tokens/refresh', json=token_refresh_response) @@ -209,7 +205,7 @@ def test_post_refreshes_token_when_denied(session: Session): resp = session.post_resource('/foo', json={'data': 'hi'}) assert {'foo': 'bar'} == resp - assert datetime(2019, 3, 14, tzinfo=UTC) == session.access_token_expiration + assert datetime(2019, 3, 14, tzinfo=timezone.utc) == session.access_token_expiration # this test exists to provide 100% coverage for the legacy 401 status on Unauthorized responses