-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation of HTTP connector
- Loading branch information
1 parent
b45d405
commit c76de68
Showing
17 changed files
with
899 additions
and
330 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
singer_sdk.connectors.BaseConnector | ||
=================================== | ||
|
||
.. currentmodule:: singer_sdk.connectors | ||
|
||
.. autoclass:: BaseConnector | ||
:members: | ||
:special-members: __init__, __call__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Using a custom connector class | ||
|
||
The Singer SDK has a few built-in connector classes that are designed to work with a variety of sources: | ||
|
||
* [`SQLConnector`](../../classes/singer_sdk.SQLConnector) for SQL databases | ||
|
||
If you need to connect to a source that is not supported by one of these built-in connectors, you can create your own connector class. This guide will walk you through the process of creating a custom connector class. | ||
|
||
## Subclass `BaseConnector` | ||
|
||
The first step is to create a subclass of [`BaseConnector`](../../classes/singer_sdk.connectors.BaseConnector). This class is responsible for creating streams and handling the connection to the source. | ||
|
||
```python | ||
from singer_sdk.connectors import BaseConnector | ||
|
||
|
||
class MyConnector(BaseConnector): | ||
pass | ||
``` | ||
|
||
## Implement `get_connection` | ||
|
||
The [`get_connection`](http://127.0.0.1:5500/build/classes/singer_sdk.connectors.BaseConnector.html#singer_sdk.connectors.BaseConnector.get_connection) method is responsible for creating a connection to the source. It should return an object that implements the [context manager protocol](https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers), e.g. it has `__enter__` and `__exit__` methods. | ||
|
||
```python | ||
from singer_sdk.connectors import BaseConnector | ||
|
||
|
||
class MyConnector(BaseConnector): | ||
def get_connection(self): | ||
return MyConnection() | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
"""HTTP-based tap class for Singer SDK.""" | ||
|
||
from __future__ import annotations | ||
|
||
import typing as t | ||
|
||
import requests | ||
|
||
from singer_sdk.authenticators import NoopAuth | ||
from singer_sdk.connectors.base import BaseConnector | ||
|
||
if t.TYPE_CHECKING: | ||
import sys | ||
|
||
from requests.adapters import BaseAdapter | ||
|
||
if sys.version_info >= (3, 10): | ||
from typing import TypeAlias # noqa: ICN003 | ||
else: | ||
from typing_extensions import TypeAlias | ||
|
||
_Auth: TypeAlias = t.Callable[[requests.PreparedRequest], requests.PreparedRequest] | ||
|
||
|
||
class HTTPConnector(BaseConnector[requests.Session]): | ||
"""Base class for all HTTP-based connectors.""" | ||
|
||
def __init__(self, config: t.Mapping[str, t.Any] | None) -> None: | ||
"""Initialize the HTTP connector. | ||
Args: | ||
config: Connector configuration parameters. | ||
""" | ||
super().__init__(config) | ||
self._session = self.get_session() | ||
self.refresh_auth() | ||
|
||
def get_connection(self, *, authenticate: bool = True) -> requests.Session: | ||
"""Return a new HTTP session object. | ||
Adds adapters and optionally authenticates the session. | ||
Args: | ||
authenticate: Whether to authenticate the request. | ||
Returns: | ||
A new HTTP session object. | ||
""" | ||
for prefix, adapter in self.adapters.items(): | ||
self._session.mount(prefix, adapter) | ||
|
||
self._session.auth = self._auth if authenticate else None | ||
|
||
return self._session | ||
|
||
def get_session(self) -> requests.Session: | ||
"""Return a new HTTP session object. | ||
Returns: | ||
A new HTTP session object. | ||
""" | ||
return requests.Session() | ||
|
||
def get_authenticator(self) -> _Auth: | ||
"""Authenticate the HTTP session. | ||
Returns: | ||
An auth callable. | ||
""" | ||
return NoopAuth() | ||
|
||
def refresh_auth(self) -> None: | ||
"""Refresh the HTTP session authentication.""" | ||
self._auth = self.get_authenticator() | ||
|
||
@property | ||
def adapters(self) -> dict[str, BaseAdapter]: | ||
"""Return a mapping of URL prefixes to adapter objects. | ||
Returns: | ||
A mapping of URL prefixes to adapter objects. | ||
""" | ||
return {} | ||
|
||
@property | ||
def default_request_kwargs(self) -> dict[str, t.Any]: | ||
"""Return default kwargs for HTTP requests. | ||
Returns: | ||
A mapping of default kwargs for HTTP requests. | ||
""" | ||
return {} | ||
|
||
def request( | ||
self, | ||
*args: t.Any, | ||
authenticate: bool = True, | ||
**kwargs: t.Any, | ||
) -> requests.Response: | ||
"""Make an HTTP request. | ||
Args: | ||
*args: Positional arguments to pass to the request method. | ||
authenticate: Whether to authenticate the request. | ||
**kwargs: Keyword arguments to pass to the request method. | ||
Returns: | ||
The HTTP response object. | ||
""" | ||
with self._connect(authenticate=authenticate) as session: | ||
kwargs = {**self.default_request_kwargs, **kwargs} | ||
return session.request(*args, **kwargs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
"""Base class for all connectors.""" | ||
|
||
from __future__ import annotations | ||
|
||
import abc | ||
import typing as t | ||
from contextlib import contextmanager | ||
|
||
from singer_sdk.helpers._compat import Protocol | ||
|
||
_T = t.TypeVar("_T", covariant=True) | ||
|
||
|
||
class ContextManagerProtocol(Protocol[_T]): | ||
"""Protocol for context manager enter/exit.""" | ||
|
||
def __enter__(self) -> _T: # noqa: D105 | ||
... # pragma: no cover | ||
|
||
def __exit__(self, *args: t.Any) -> None: # noqa: D105 | ||
... # pragma: no cover | ||
|
||
|
||
_C = t.TypeVar("_C", bound=ContextManagerProtocol) | ||
|
||
|
||
class BaseConnector(abc.ABC, t.Generic[_C]): | ||
"""Base class for all connectors.""" | ||
|
||
def __init__(self, config: t.Mapping[str, t.Any] | None) -> None: | ||
"""Initialize the connector. | ||
Args: | ||
config: Plugin configuration parameters. | ||
""" | ||
self._config = config or {} | ||
|
||
@property | ||
def config(self) -> t.Mapping: | ||
"""Return the connector configuration. | ||
Returns: | ||
A mapping of configuration parameters. | ||
""" | ||
return self._config | ||
|
||
@contextmanager | ||
def _connect(self, *args: t.Any, **kwargs: t.Any) -> t.Generator[_C, None, None]: | ||
"""Connect to the destination. | ||
Args: | ||
args: Positional arguments to pass to the connection method. | ||
kwargs: Keyword arguments to pass to the connection method. | ||
Yields: | ||
A connection object. | ||
""" | ||
with self.get_connection(*args, **kwargs) as connection: | ||
yield connection | ||
|
||
@abc.abstractmethod | ||
def get_connection(self, *args: t.Any, **kwargs: t.Any) -> _C: | ||
"""Connect to the destination. | ||
Args: | ||
args: Positional arguments to pass to the connection method. | ||
kwargs: Keyword arguments to pass to the connection method. | ||
""" | ||
... |
Oops, something went wrong.