diff --git a/pyproject.toml b/pyproject.toml index e3c73d5cd..95895ba5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -238,6 +238,7 @@ types-requests = "requests" [tool.deptry.per_rule_ignores] DEP002 = [ # Deprecated dependencies + "pendulum", "python-dateutil", # Transitive constraints "numpy", @@ -407,6 +408,9 @@ typing = "t" fixture-parentheses = false parametrize-names-type = "csv" +[tool.ruff.lint.flake8-tidy-imports.banned-api] +"pendulum".msg = "BAN002: pendulum is banned" + [tool.ruff.lint.isort] known-first-party = ["singer_sdk", "samples", "tests"] required-imports = ["from __future__ import annotations"] diff --git a/singer_sdk/authenticators.py b/singer_sdk/authenticators.py index 8c550fe31..09f42efee 100644 --- a/singer_sdk/authenticators.py +++ b/singer_sdk/authenticators.py @@ -7,7 +7,6 @@ import math import typing as t import warnings -from datetime import timedelta from types import MappingProxyType from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit @@ -557,7 +556,7 @@ def oauth_request_body(self) -> dict: "iss": self.client_id, "scope": self.oauth_scopes, "aud": self.auth_endpoint, - "exp": math.floor((request_time + timedelta(hours=1)).timestamp()), + "exp": math.floor((request_time + datetime.timedelta(hours=1)).timestamp()), "iat": math.floor(request_time.timestamp()), } diff --git a/singer_sdk/helpers/_util.py b/singer_sdk/helpers/_util.py index 5d8f53f26..0e8250c2a 100644 --- a/singer_sdk/helpers/_util.py +++ b/singer_sdk/helpers/_util.py @@ -2,15 +2,11 @@ from __future__ import annotations +import datetime import json import typing as t from pathlib import Path, PurePath -import pendulum - -if t.TYPE_CHECKING: - import datetime - def read_json_file(path: PurePath | str) -> dict[str, t.Any]: """Read json file, throwing an error if missing.""" @@ -30,5 +26,4 @@ def read_json_file(path: PurePath | str) -> dict[str, t.Any]: def utc_now() -> datetime.datetime: """Return current time in UTC.""" - # TODO: replace with datetime.datetime.now(tz=datetime.timezone.utc) - return pendulum.now(tz="UTC") + return datetime.datetime.now(datetime.timezone.utc) diff --git a/singer_sdk/streams/core.py b/singer_sdk/streams/core.py index 886466c5d..0b4d6eef6 100644 --- a/singer_sdk/streams/core.py +++ b/singer_sdk/streams/core.py @@ -12,8 +12,6 @@ from pathlib import Path from types import MappingProxyType -import pendulum - import singer_sdk._singerlib as singer from singer_sdk import metrics from singer_sdk.batch import Batcher @@ -30,6 +28,7 @@ SDKBatchMessage, ) from singer_sdk.helpers._catalog import pop_deselected_record_properties +from singer_sdk.helpers._compat import datetime_fromisoformat from singer_sdk.helpers._flattening import get_flattening_options from singer_sdk.helpers._state import ( finalize_state_progress_markers, @@ -291,7 +290,8 @@ def get_starting_timestamp( msg = f"The replication key {self.replication_key} is not of timestamp type" raise ValueError(msg) - return t.cast(datetime.datetime, pendulum.parse(value)) + result = datetime_fromisoformat(value) + return result if result.tzinfo else result.replace(tzinfo=datetime.timezone.utc) @property def selected(self) -> bool: @@ -377,7 +377,7 @@ def compare_start_date(self, value: str, start_date_value: str) -> str: The most recent value between the bookmark and start date. """ if self.is_timestamp_replication_key: - return max(value, start_date_value, key=pendulum.parse) + return max(value, start_date_value, key=datetime_fromisoformat) return value diff --git a/tests/core/conftest.py b/tests/core/conftest.py index 97eb76e7f..30798b01c 100644 --- a/tests/core/conftest.py +++ b/tests/core/conftest.py @@ -5,11 +5,11 @@ import typing as t from contextlib import contextmanager -import pendulum import pytest from typing_extensions import override from singer_sdk import Stream, Tap +from singer_sdk.helpers._compat import datetime_fromisoformat from singer_sdk.typing import ( DateTimeType, IntegerType, @@ -70,7 +70,7 @@ class UnixTimestampIncrementalStream2(UnixTimestampIncrementalStream): def compare_start_date(self, value: str, start_date_value: str) -> str: """Compare a value to a start date value.""" - start_timestamp = pendulum.parse(start_date_value).format("X") + start_timestamp = datetime_fromisoformat(start_date_value).timestamp() return max(value, start_timestamp, key=float) diff --git a/tests/core/test_streams.py b/tests/core/test_streams.py index 2dd0dd4cb..592f921e6 100644 --- a/tests/core/test_streams.py +++ b/tests/core/test_streams.py @@ -6,7 +6,6 @@ import logging import typing as t -import pendulum import pytest import requests @@ -24,12 +23,12 @@ from singer_sdk.typing import IntegerType, PropertiesList, Property, StringType from tests.core.conftest import SimpleTestStream -CONFIG_START_DATE = "2021-01-01" - if t.TYPE_CHECKING: from singer_sdk import Stream, Tap from tests.core.conftest import SimpleTestTap +CONFIG_START_DATE = "2021-01-01" + class RestTestStream(RESTStream): """Test RESTful stream class.""" @@ -181,7 +180,7 @@ def test_stream_apply_catalog(stream: Stream): "unix_ts_override", None, "1577858400", - pendulum.parse(CONFIG_START_DATE).format("X"), + parse(CONFIG_START_DATE).timestamp(), id="unix-ts-repl-key-old-bookmark", ), ],