diff --git a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/pyproject.toml b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/pyproject.toml index 0152ce146..4b7cfdcaf 100644 --- a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/pyproject.toml +++ b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/pyproject.toml @@ -31,7 +31,10 @@ packages = [ [tool.poetry.dependencies] python = ">=3.8" importlib-resources = { version = "==6.1.*", python = "<3.9" } -singer-sdk = { version="~=0.36.1"{{ ', extras = ["faker"]' if cookiecutter.faker_extra }} } +singer-sdk = { version="~=0.36.1", extras = [ + {%- if cookiecutter.auth_method == "JWT" -%}"jwt", {% endif -%} + {%- if cookiecutter.faker_extra -%}"faker",{%- endif -%} +] } fs-s3fs = { version = "~=1.1.1", optional = true } {%- if cookiecutter.stream_type in ["REST", "GraphQL"] %} requests = "~=2.31.0" @@ -39,7 +42,11 @@ requests = "~=2.31.0" [tool.poetry.group.dev.dependencies] pytest = ">=7.4.0" +{%- if cookiecutter.auth_method == "JWT" %} +singer-sdk = { version="~=0.36.1", extras = ["jwt", "testing"] } +{%- else %} singer-sdk = { version="~=0.36.1", extras = ["testing"] } +{%- endif %} [tool.poetry.extras] s3 = ["fs-s3fs"] diff --git a/docs/deprecation.md b/docs/deprecation.md index b27e61b2b..e1d964129 100644 --- a/docs/deprecation.md +++ b/docs/deprecation.md @@ -13,3 +13,5 @@ incompatible way, following their deprecation, as indicated in the See the [migration guide](./guides/pagination-classes.md) for more information. - The `singer_sdk.testing.get_standard_tap_tests` and `singer_sdk.testing.get_standard_target_tests` functions will be removed. Replace them with `singer_sdk.testing.get_tap_test_class` and `singer_sdk.testing.get_target_test_class` functions respective to generate a richer test suite. + +- The `PyJWT` and `cryptography` libraries will be no longer be installed by default. If you are using the `OAuthJWTAuthenticator` you will need to install [`singer-sdk[jwt]`](./dev_guide.md#extra-features). diff --git a/docs/dev_guide.md b/docs/dev_guide.md index af198e9b8..5d520916e 100644 --- a/docs/dev_guide.md +++ b/docs/dev_guide.md @@ -17,7 +17,7 @@ Create taps with the SDK requires overriding just two or three classes: `http_headers` property in the stream class. - `OAuthAuthenticator` - This class performs an OAuth 2.0 authentication flow. - `OAuthJWTAuthenticator` - This class performs an JWT (JSON Web Token) authentication - flow. + flow. Requires installing the `singer-sdk[jwt]` extra. ## Target Development Overview @@ -182,6 +182,7 @@ Some APIs instead return the records as values inside an object where each key i The following [extra features](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#extras) are available for the Singer SDK: - `faker` - Enables the use of [Faker](https://faker.readthedocs.io/en/master/) in [stream maps](stream_maps.md). +- `jwt` - Enables the `OAuthJWTAuthenticator` class for JWT (JSON Web Token) authentication. - `s3` - Enables AWS S3 as a [BATCH storage](batch.md#the-batch-message). - `parquet` - Enables as [BATCH encoding](batch.md#encoding). - `testing` - Pytest dependencies required to use the [Tap & Target Testing Framework](testing.md). diff --git a/noxfile.py b/noxfile.py index 6cd39b416..5334cb051 100644 --- a/noxfile.py +++ b/noxfile.py @@ -59,7 +59,7 @@ def mypy(session: Session) -> None: """Check types with mypy.""" args = session.posargs or ["singer_sdk"] - session.install(".[faker,parquet,s3,testing]") + session.install(".[faker,jwt,parquet,s3,testing]") session.install( "exceptiongroup", "mypy", @@ -80,7 +80,7 @@ def mypy(session: Session) -> None: @session(python=python_versions) def tests(session: Session) -> None: """Execute pytest tests and compute coverage.""" - session.install(".[faker,parquet,s3]") + session.install(".[faker,jwt,parquet,s3]") session.install(*test_dependencies) sqlalchemy_version = os.environ.get("SQLALCHEMY_VERSION") @@ -113,7 +113,7 @@ def tests(session: Session) -> None: @session(python=main_python_version) def benches(session: Session) -> None: """Run benchmarks.""" - session.install(".[s3]") + session.install(".[jwt,s3]") session.install(*test_dependencies) sqlalchemy_version = os.environ.get("SQLALCHEMY_VERSION") if sqlalchemy_version: @@ -135,7 +135,7 @@ def update_snapshots(session: Session) -> None: """Update pytest snapshots.""" args = session.posargs or ["-m", "snapshot"] - session.install(".[faker]") + session.install(".[faker,jwt]") session.install(*test_dependencies) session.run("pytest", "--snapshot-update", *args) diff --git a/poetry.lock b/poetry.lock index 54c3c3fe7..6b4f44808 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2636,6 +2636,7 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [extras] docs = ["furo", "myst-parser", "pytest", "sphinx", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-notfound-page", "sphinx-reredirects"] faker = ["faker"] +jwt = ["PyJWT", "cryptography"] parquet = ["numpy", "numpy", "pyarrow"] s3 = ["fs-s3fs"] testing = ["pytest", "pytest-durations"] @@ -2643,4 +2644,4 @@ testing = ["pytest", "pytest-durations"] [metadata] lock-version = "2.0" python-versions = ">=3.8" -content-hash = "a1649e1ccefeed0391552089bf6be64ca6e8e54b6db177ac86e6ca730f677d01" +content-hash = "fa28db8eb855ac9bb217e40ad0c1720726effa79e8a07cf08d17aa0d7ceb8bef" diff --git a/pyproject.toml b/pyproject.toml index 20c3cdc29..6d8fa7d19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,6 +94,10 @@ pytest-durations = {version = ">=1.2.0", optional = true} faker = {version = ">=22.5,<25.0", optional = true} [tool.poetry.extras] +jwt = [ + "cryptography", + "PyJWT", +] docs = [ "furo", "myst-parser", diff --git a/singer_sdk/authenticators.py b/singer_sdk/authenticators.py index faf34e85a..4b7c2463f 100644 --- a/singer_sdk/authenticators.py +++ b/singer_sdk/authenticators.py @@ -10,10 +10,7 @@ from types import MappingProxyType from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit -import jwt import requests -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization from singer_sdk.helpers._util import utc_now @@ -575,6 +572,10 @@ def oauth_request_payload(self) -> dict: Raises: ValueError: If the private key is not set. """ + import jwt + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import serialization + if not self.private_key: msg = "Missing 'private_key' property for OAuth payload." raise ValueError(msg)