Decoupling authenticators from streams¶
In v0.58, the stream constructor parameter and all related shim API on authenticator
classes will be removed. Authenticators are now constructed directly with explicit
parameters, making them easier to test in isolation without a stream instance.
This guide walks through each affected class and shows the new construction pattern.
Background¶
The old pattern passed a stream instance directly to the authenticator constructor so
it could read stream.config and stream.tap_name internally:
# Old (deprecated)
class MyStream(RESTStream):
@cached_property
def authenticator(self):
return APIKeyAuthenticator(
stream=self,
key="x-api-key",
value=self.config["api_key"],
)
Authenticators no longer hold a reference to the stream. Config values are passed explicitly at construction time, which makes authenticators easier to test in isolation and removes a hidden dependency on the stream lifecycle.
What’s removed¶
Deprecated |
Replacement |
|---|---|
|
Construct directly with explicit kwargs |
|
Construct |
|
Construct |
|
Construct |
|
Access via your tap/stream directly |
|
Access via your tap/stream directly |
Simple authenticators¶
APIKeyAuthenticator, BearerTokenAuthenticator, and BasicAuthenticator are the
easiest to migrate — drop stream= and the create_for_stream factory:
# Old (deprecated)
APIKeyAuthenticator(stream=self, key="x-api-key", value="secret")
APIKeyAuthenticator.create_for_stream(stream=self, key="x-api-key", value="secret")
BearerTokenAuthenticator(stream=self, token="my-token")
BearerTokenAuthenticator.create_for_stream(stream=self, token="my-token")
BasicAuthenticator(stream=self, username="user", password="pass")
BasicAuthenticator.create_for_stream(stream=self, username="user", password="pass")
# New
APIKeyAuthenticator(key="x-api-key", value="secret")
BearerTokenAuthenticator(token="my-token")
BasicAuthenticator(username="user", password="pass")
In your stream’s authenticator property, read config values directly from self.config:
@cached_property
def authenticator(self) -> APIKeyAuthenticator:
return APIKeyAuthenticator(key="x-api-key", value=self.config["api_key"])
OAuth authenticators¶
OAuthAuthenticator and OAuthJWTAuthenticator require a few more steps because the
old stream= path read client_id, client_secret (and private_key for JWT) from
stream.config automatically. These must now be passed explicitly.
OAuthAuthenticator¶
# Old (deprecated)
class MyAuthenticator(OAuthAuthenticator):
@classmethod
def create_for_stream(cls, stream):
return cls(
stream=stream,
auth_endpoint="https://api.example.com/oauth/token",
)
@property
def oauth_request_body(self) -> dict:
return {
"client_id": self.client_id, # populated from stream.config
"client_secret": self.client_secret, # populated from stream.config
"grant_type": "client_credentials",
}
# In your stream:
@cached_property
def authenticator(self):
return MyAuthenticator.create_for_stream(self)
# New — remove create_for_stream; pass config values from the stream
class MyAuthenticator(OAuthAuthenticator):
@property
def oauth_request_body(self) -> dict:
return {
"client_id": self.client_id,
"client_secret": self.client_secret,
"grant_type": "client_credentials",
}
# In your stream:
@cached_property
def authenticator(self):
return MyAuthenticator(
auth_endpoint="https://api.example.com/oauth/token",
client_id=self.config["client_id"],
client_secret=self.config["client_secret"],
)
OAuthJWTAuthenticator¶
The same pattern applies; pass private_key and private_key_passphrase explicitly:
# Old (deprecated)
class MyJWTAuthenticator(OAuthJWTAuthenticator):
@classmethod
def create_for_stream(cls, stream):
return cls(stream=stream, auth_endpoint="https://api.example.com/oauth/token")
# In your stream:
@cached_property
def authenticator(self):
return MyJWTAuthenticator.create_for_stream(self)
# New
# In your stream:
@cached_property
def authenticator(self):
return MyJWTAuthenticator(
auth_endpoint="https://api.example.com/oauth/token",
client_id=self.config["client_id"],
client_secret=self.config["client_secret"],
private_key=self.config["private_key"],
private_key_passphrase=self.config.get("private_key_passphrase"),
)
Subclasses that access self.config or self.tap_name¶
If your authenticator subclass reads self.config or self.tap_name in methods like
oauth_request_body, store those values in __init__ instead:
# Old (deprecated) — reads self.config inside the authenticator
class MyAuthenticator(OAuthAuthenticator):
@property
def oauth_request_body(self) -> dict:
return {
"audience": self.config["audience"], # will no longer work
"grant_type": "client_credentials",
}
# New — receive the value at construction time
class MyAuthenticator(OAuthAuthenticator):
def __init__(self, *, audience: str, **kwargs: t.Any) -> None:
super().__init__(**kwargs)
self._audience = audience
@property
def oauth_request_body(self) -> dict:
return {
"audience": self._audience,
"grant_type": "client_credentials",
}
# In your stream:
@cached_property
def authenticator(self):
return MyAuthenticator(
auth_endpoint="https://api.example.com/oauth/token",
client_id=self.config["client_id"],
client_secret=self.config["client_secret"],
audience=self.config["audience"],
)