airbyte-source-convex 0.4.1__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- airbyte_source_convex-0.4.1/PKG-INFO +110 -0
- airbyte_source_convex-0.4.1/README.md +91 -0
- airbyte_source_convex-0.4.1/pyproject.toml +34 -0
- airbyte_source_convex-0.4.1/source_convex/__init__.py +8 -0
- airbyte_source_convex-0.4.1/source_convex/run.py +14 -0
- airbyte_source_convex-0.4.1/source_convex/source.py +243 -0
- airbyte_source_convex-0.4.1/source_convex/spec.yaml +21 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: airbyte-source-convex
|
3
|
+
Version: 0.4.1
|
4
|
+
Summary: Source implementation for Convex.
|
5
|
+
Home-page: https://airbyte.com
|
6
|
+
License: MIT
|
7
|
+
Author: Airbyte
|
8
|
+
Author-email: contact@airbyte.io
|
9
|
+
Requires-Python: >=3.9,<3.12
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
15
|
+
Requires-Dist: airbyte-cdk (==0.80.0)
|
16
|
+
Project-URL: Documentation, https://docs.airbyte.com/integrations/sources/convex
|
17
|
+
Project-URL: Repository, https://github.com/airbytehq/airbyte
|
18
|
+
Description-Content-Type: text/markdown
|
19
|
+
|
20
|
+
# Convex source connector
|
21
|
+
|
22
|
+
|
23
|
+
This is the repository for the Convex source connector, written in Python.
|
24
|
+
For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.com/integrations/sources/convex).
|
25
|
+
|
26
|
+
## Local development
|
27
|
+
|
28
|
+
### Prerequisites
|
29
|
+
* Python (~=3.9)
|
30
|
+
* Poetry (~=1.7) - installation instructions [here](https://python-poetry.org/docs/#installation)
|
31
|
+
|
32
|
+
|
33
|
+
### Installing the connector
|
34
|
+
From this connector directory, run:
|
35
|
+
```bash
|
36
|
+
poetry install --with dev
|
37
|
+
```
|
38
|
+
|
39
|
+
|
40
|
+
### Create credentials
|
41
|
+
**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.com/integrations/sources/convex)
|
42
|
+
to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_convex/spec.yaml` file.
|
43
|
+
Note that any directory named `secrets` is gitignored across the entire Airbyte repo, so there is no danger of accidentally checking in sensitive information.
|
44
|
+
See `sample_files/sample_config.json` for a sample config file.
|
45
|
+
|
46
|
+
|
47
|
+
### Locally running the connector
|
48
|
+
```
|
49
|
+
poetry run source-convex spec
|
50
|
+
poetry run source-convex check --config secrets/config.json
|
51
|
+
poetry run source-convex discover --config secrets/config.json
|
52
|
+
poetry run source-convex read --config secrets/config.json --catalog sample_files/configured_catalog.json
|
53
|
+
```
|
54
|
+
|
55
|
+
### Running unit tests
|
56
|
+
To run unit tests locally, from the connector directory run:
|
57
|
+
```
|
58
|
+
poetry run pytest unit_tests
|
59
|
+
```
|
60
|
+
|
61
|
+
### Building the docker image
|
62
|
+
1. Install [`airbyte-ci`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/README.md)
|
63
|
+
2. Run the following command to build the docker image:
|
64
|
+
```bash
|
65
|
+
airbyte-ci connectors --name=source-convex build
|
66
|
+
```
|
67
|
+
|
68
|
+
An image will be available on your host with the tag `airbyte/source-convex:dev`.
|
69
|
+
|
70
|
+
|
71
|
+
### Running as a docker container
|
72
|
+
Then run any of the connector commands as follows:
|
73
|
+
```
|
74
|
+
docker run --rm airbyte/source-convex:dev spec
|
75
|
+
docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-convex:dev check --config /secrets/config.json
|
76
|
+
docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-convex:dev discover --config /secrets/config.json
|
77
|
+
docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-convex:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json
|
78
|
+
```
|
79
|
+
|
80
|
+
### Running our CI test suite
|
81
|
+
You can run our full test suite locally using [`airbyte-ci`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/README.md):
|
82
|
+
```bash
|
83
|
+
airbyte-ci connectors --name=source-convex test
|
84
|
+
```
|
85
|
+
|
86
|
+
### Customizing acceptance Tests
|
87
|
+
Customize `acceptance-test-config.yml` file to configure acceptance tests. See [Connector Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/connector-acceptance-tests-reference) for more information.
|
88
|
+
If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py.
|
89
|
+
|
90
|
+
### Dependency Management
|
91
|
+
All of your dependencies should be managed via Poetry.
|
92
|
+
To add a new dependency, run:
|
93
|
+
```bash
|
94
|
+
poetry add <package-name>
|
95
|
+
```
|
96
|
+
|
97
|
+
Please commit the changes to `pyproject.toml` and `poetry.lock` files.
|
98
|
+
|
99
|
+
## Publishing a new version of the connector
|
100
|
+
You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what?
|
101
|
+
1. Make sure your changes are passing our test suite: `airbyte-ci connectors --name=source-convex test`
|
102
|
+
2. Bump the connector version (please follow [semantic versioning for connectors](https://docs.airbyte.com/contributing-to-airbyte/resources/pull-requests-handbook/#semantic-versioning-for-connectors)):
|
103
|
+
- bump the `dockerImageTag` value in in `metadata.yaml`
|
104
|
+
- bump the `version` value in `pyproject.toml`
|
105
|
+
3. Make sure the `metadata.yaml` content is up to date.
|
106
|
+
4. Make sure the connector documentation and its changelog is up to date (`docs/integrations/sources/convex.md`).
|
107
|
+
5. Create a Pull Request: use [our PR naming conventions](https://docs.airbyte.com/contributing-to-airbyte/resources/pull-requests-handbook/#pull-request-title-convention).
|
108
|
+
6. Pat yourself on the back for being an awesome contributor.
|
109
|
+
7. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master.
|
110
|
+
8. Once your PR is merged, the new version of the connector will be automatically published to Docker Hub and our connector registry.
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Convex source connector
|
2
|
+
|
3
|
+
|
4
|
+
This is the repository for the Convex source connector, written in Python.
|
5
|
+
For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.com/integrations/sources/convex).
|
6
|
+
|
7
|
+
## Local development
|
8
|
+
|
9
|
+
### Prerequisites
|
10
|
+
* Python (~=3.9)
|
11
|
+
* Poetry (~=1.7) - installation instructions [here](https://python-poetry.org/docs/#installation)
|
12
|
+
|
13
|
+
|
14
|
+
### Installing the connector
|
15
|
+
From this connector directory, run:
|
16
|
+
```bash
|
17
|
+
poetry install --with dev
|
18
|
+
```
|
19
|
+
|
20
|
+
|
21
|
+
### Create credentials
|
22
|
+
**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.com/integrations/sources/convex)
|
23
|
+
to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_convex/spec.yaml` file.
|
24
|
+
Note that any directory named `secrets` is gitignored across the entire Airbyte repo, so there is no danger of accidentally checking in sensitive information.
|
25
|
+
See `sample_files/sample_config.json` for a sample config file.
|
26
|
+
|
27
|
+
|
28
|
+
### Locally running the connector
|
29
|
+
```
|
30
|
+
poetry run source-convex spec
|
31
|
+
poetry run source-convex check --config secrets/config.json
|
32
|
+
poetry run source-convex discover --config secrets/config.json
|
33
|
+
poetry run source-convex read --config secrets/config.json --catalog sample_files/configured_catalog.json
|
34
|
+
```
|
35
|
+
|
36
|
+
### Running unit tests
|
37
|
+
To run unit tests locally, from the connector directory run:
|
38
|
+
```
|
39
|
+
poetry run pytest unit_tests
|
40
|
+
```
|
41
|
+
|
42
|
+
### Building the docker image
|
43
|
+
1. Install [`airbyte-ci`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/README.md)
|
44
|
+
2. Run the following command to build the docker image:
|
45
|
+
```bash
|
46
|
+
airbyte-ci connectors --name=source-convex build
|
47
|
+
```
|
48
|
+
|
49
|
+
An image will be available on your host with the tag `airbyte/source-convex:dev`.
|
50
|
+
|
51
|
+
|
52
|
+
### Running as a docker container
|
53
|
+
Then run any of the connector commands as follows:
|
54
|
+
```
|
55
|
+
docker run --rm airbyte/source-convex:dev spec
|
56
|
+
docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-convex:dev check --config /secrets/config.json
|
57
|
+
docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-convex:dev discover --config /secrets/config.json
|
58
|
+
docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-convex:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json
|
59
|
+
```
|
60
|
+
|
61
|
+
### Running our CI test suite
|
62
|
+
You can run our full test suite locally using [`airbyte-ci`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/README.md):
|
63
|
+
```bash
|
64
|
+
airbyte-ci connectors --name=source-convex test
|
65
|
+
```
|
66
|
+
|
67
|
+
### Customizing acceptance Tests
|
68
|
+
Customize `acceptance-test-config.yml` file to configure acceptance tests. See [Connector Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/connector-acceptance-tests-reference) for more information.
|
69
|
+
If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py.
|
70
|
+
|
71
|
+
### Dependency Management
|
72
|
+
All of your dependencies should be managed via Poetry.
|
73
|
+
To add a new dependency, run:
|
74
|
+
```bash
|
75
|
+
poetry add <package-name>
|
76
|
+
```
|
77
|
+
|
78
|
+
Please commit the changes to `pyproject.toml` and `poetry.lock` files.
|
79
|
+
|
80
|
+
## Publishing a new version of the connector
|
81
|
+
You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what?
|
82
|
+
1. Make sure your changes are passing our test suite: `airbyte-ci connectors --name=source-convex test`
|
83
|
+
2. Bump the connector version (please follow [semantic versioning for connectors](https://docs.airbyte.com/contributing-to-airbyte/resources/pull-requests-handbook/#semantic-versioning-for-connectors)):
|
84
|
+
- bump the `dockerImageTag` value in in `metadata.yaml`
|
85
|
+
- bump the `version` value in `pyproject.toml`
|
86
|
+
3. Make sure the `metadata.yaml` content is up to date.
|
87
|
+
4. Make sure the connector documentation and its changelog is up to date (`docs/integrations/sources/convex.md`).
|
88
|
+
5. Create a Pull Request: use [our PR naming conventions](https://docs.airbyte.com/contributing-to-airbyte/resources/pull-requests-handbook/#pull-request-title-convention).
|
89
|
+
6. Pat yourself on the back for being an awesome contributor.
|
90
|
+
7. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master.
|
91
|
+
8. Once your PR is merged, the new version of the connector will be automatically published to Docker Hub and our connector registry.
|
@@ -0,0 +1,34 @@
|
|
1
|
+
[build-system]
|
2
|
+
requires = [
|
3
|
+
"poetry-core>=1.0.0",
|
4
|
+
]
|
5
|
+
build-backend = "poetry.core.masonry.api"
|
6
|
+
|
7
|
+
[tool.poetry]
|
8
|
+
version = "0.4.1"
|
9
|
+
name = "airbyte-source-convex"
|
10
|
+
description = "Source implementation for Convex."
|
11
|
+
authors = [
|
12
|
+
"Airbyte <contact@airbyte.io>",
|
13
|
+
]
|
14
|
+
license = "MIT"
|
15
|
+
readme = "README.md"
|
16
|
+
documentation = "https://docs.airbyte.com/integrations/sources/convex"
|
17
|
+
homepage = "https://airbyte.com"
|
18
|
+
repository = "https://github.com/airbytehq/airbyte"
|
19
|
+
packages = [
|
20
|
+
{ include = "source_convex" },
|
21
|
+
]
|
22
|
+
|
23
|
+
[tool.poetry.dependencies]
|
24
|
+
python = "^3.9,<3.12"
|
25
|
+
airbyte-cdk = "0.80.0"
|
26
|
+
|
27
|
+
[tool.poetry.scripts]
|
28
|
+
source-convex = "source_convex.run:run"
|
29
|
+
|
30
|
+
[tool.poetry.group.dev.dependencies]
|
31
|
+
responses = "^0.13.3"
|
32
|
+
requests-mock = "^1.9.3"
|
33
|
+
pytest-mock = "^3.6.1"
|
34
|
+
pytest = "^6.1"
|
@@ -0,0 +1,243 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
from datetime import datetime
|
7
|
+
from json import JSONDecodeError
|
8
|
+
from typing import Any, Dict, Iterable, Iterator, List, Mapping, MutableMapping, Optional, Tuple, TypedDict, cast
|
9
|
+
|
10
|
+
import requests
|
11
|
+
from airbyte_cdk.models import SyncMode
|
12
|
+
from airbyte_cdk.sources import AbstractSource
|
13
|
+
from airbyte_cdk.sources.streams import IncrementalMixin, Stream
|
14
|
+
from airbyte_cdk.sources.streams.http import HttpStream
|
15
|
+
from airbyte_cdk.sources.streams.http.requests_native_auth.token import TokenAuthenticator
|
16
|
+
|
17
|
+
ConvexConfig = TypedDict(
|
18
|
+
"ConvexConfig",
|
19
|
+
{
|
20
|
+
"deployment_url": str,
|
21
|
+
"access_key": str,
|
22
|
+
},
|
23
|
+
)
|
24
|
+
|
25
|
+
ConvexState = TypedDict(
|
26
|
+
"ConvexState",
|
27
|
+
{
|
28
|
+
"snapshot_cursor": Optional[str],
|
29
|
+
"snapshot_has_more": bool,
|
30
|
+
"delta_cursor": Optional[int],
|
31
|
+
},
|
32
|
+
)
|
33
|
+
|
34
|
+
CONVEX_CLIENT_VERSION = "0.4.0"
|
35
|
+
|
36
|
+
|
37
|
+
# Source
|
38
|
+
class SourceConvex(AbstractSource):
|
39
|
+
def _json_schemas(self, config: ConvexConfig) -> requests.Response:
|
40
|
+
deployment_url = config["deployment_url"]
|
41
|
+
access_key = config["access_key"]
|
42
|
+
url = f"{deployment_url}/api/json_schemas?deltaSchema=true&format=json"
|
43
|
+
headers = {
|
44
|
+
"Authorization": f"Convex {access_key}",
|
45
|
+
"Convex-Client": f"airbyte-export-{CONVEX_CLIENT_VERSION}",
|
46
|
+
}
|
47
|
+
return requests.get(url, headers=headers)
|
48
|
+
|
49
|
+
def check_connection(self, logger: Any, config: Mapping[str, Any]) -> Tuple[bool, Any]:
|
50
|
+
"""
|
51
|
+
Connection check to validate that the user-provided config can be used to connect to the underlying API
|
52
|
+
|
53
|
+
:param config: the user-input config object conforming to the connector's spec.yaml
|
54
|
+
:param logger: logger object
|
55
|
+
:return Tuple[bool, any]: (True, None) if the input config can be used to connect to the API successfully, (False, error) otherwise.
|
56
|
+
"""
|
57
|
+
config = cast(ConvexConfig, config)
|
58
|
+
resp = self._json_schemas(config)
|
59
|
+
if resp.status_code == 200:
|
60
|
+
return True, None
|
61
|
+
else:
|
62
|
+
return False, format_http_error("Connection to Convex via json_schemas endpoint failed", resp)
|
63
|
+
|
64
|
+
def streams(self, config: Mapping[str, Any]) -> List[Stream]:
|
65
|
+
"""
|
66
|
+
:param config: A Mapping of the user input configuration as defined in the connector spec.
|
67
|
+
"""
|
68
|
+
config = cast(ConvexConfig, config)
|
69
|
+
resp = self._json_schemas(config)
|
70
|
+
if resp.status_code != 200:
|
71
|
+
raise Exception(format_http_error("Failed request to json_schemas", resp))
|
72
|
+
json_schemas = resp.json()
|
73
|
+
table_names = list(json_schemas.keys())
|
74
|
+
return [
|
75
|
+
ConvexStream(
|
76
|
+
config["deployment_url"],
|
77
|
+
config["access_key"],
|
78
|
+
"json", # Use `json` export format
|
79
|
+
table_name,
|
80
|
+
json_schemas[table_name],
|
81
|
+
)
|
82
|
+
for table_name in table_names
|
83
|
+
]
|
84
|
+
|
85
|
+
|
86
|
+
class ConvexStream(HttpStream, IncrementalMixin):
|
87
|
+
def __init__(
|
88
|
+
self,
|
89
|
+
deployment_url: str,
|
90
|
+
access_key: str,
|
91
|
+
fmt: str,
|
92
|
+
table_name: str,
|
93
|
+
json_schema: Dict[str, Any],
|
94
|
+
):
|
95
|
+
self.deployment_url = deployment_url
|
96
|
+
self.fmt = fmt
|
97
|
+
self.table_name = table_name
|
98
|
+
if json_schema:
|
99
|
+
json_schema["additionalProperties"] = True
|
100
|
+
json_schema["properties"]["_ab_cdc_lsn"] = {"type": "number"}
|
101
|
+
json_schema["properties"]["_ab_cdc_updated_at"] = {"type": "string"}
|
102
|
+
json_schema["properties"]["_ab_cdc_deleted_at"] = {"anyOf": [{"type": "string"}, {"type": "null"}]}
|
103
|
+
else:
|
104
|
+
json_schema = {}
|
105
|
+
self.json_schema = json_schema
|
106
|
+
self._snapshot_cursor_value: Optional[str] = None
|
107
|
+
self._snapshot_has_more = True
|
108
|
+
self._delta_cursor_value: Optional[int] = None
|
109
|
+
self._delta_has_more = True
|
110
|
+
super().__init__(TokenAuthenticator(access_key, "Convex"))
|
111
|
+
|
112
|
+
@property
|
113
|
+
def name(self) -> str:
|
114
|
+
return self.table_name
|
115
|
+
|
116
|
+
@property
|
117
|
+
def url_base(self) -> str:
|
118
|
+
return self.deployment_url
|
119
|
+
|
120
|
+
def get_json_schema(self) -> Mapping[str, Any]: # type: ignore[override]
|
121
|
+
return self.json_schema
|
122
|
+
|
123
|
+
primary_key = "_id"
|
124
|
+
cursor_field = "_ts"
|
125
|
+
|
126
|
+
# Checkpoint stream reads after this many records. This prevents re-reading of data if the stream fails for any reason.
|
127
|
+
state_checkpoint_interval = 128
|
128
|
+
|
129
|
+
@property
|
130
|
+
def state(self) -> MutableMapping[str, Any]:
|
131
|
+
value: ConvexState = {
|
132
|
+
"snapshot_cursor": self._snapshot_cursor_value,
|
133
|
+
"snapshot_has_more": self._snapshot_has_more,
|
134
|
+
"delta_cursor": self._delta_cursor_value,
|
135
|
+
}
|
136
|
+
return cast(MutableMapping[str, Any], value)
|
137
|
+
|
138
|
+
@state.setter
|
139
|
+
def state(self, value: MutableMapping[str, Any]) -> None:
|
140
|
+
state = cast(ConvexState, value)
|
141
|
+
self._snapshot_cursor_value = state["snapshot_cursor"]
|
142
|
+
self._snapshot_has_more = state["snapshot_has_more"]
|
143
|
+
self._delta_cursor_value = state["delta_cursor"]
|
144
|
+
|
145
|
+
def next_page_token(self, response: requests.Response) -> Optional[ConvexState]:
|
146
|
+
if response.status_code != 200:
|
147
|
+
raise Exception(format_http_error("Failed request", response))
|
148
|
+
resp_json = response.json()
|
149
|
+
if self._snapshot_has_more:
|
150
|
+
self._snapshot_cursor_value = resp_json["cursor"]
|
151
|
+
self._snapshot_has_more = resp_json["hasMore"]
|
152
|
+
self._delta_cursor_value = resp_json["snapshot"]
|
153
|
+
else:
|
154
|
+
self._delta_cursor_value = resp_json["cursor"]
|
155
|
+
self._delta_has_more = resp_json["hasMore"]
|
156
|
+
has_more = self._snapshot_has_more or self._delta_has_more
|
157
|
+
return cast(ConvexState, self.state) if has_more else None
|
158
|
+
|
159
|
+
def path(
|
160
|
+
self,
|
161
|
+
stream_state: Optional[Mapping[str, Any]] = None,
|
162
|
+
stream_slice: Optional[Mapping[str, Any]] = None,
|
163
|
+
next_page_token: Optional[Mapping[str, Any]] = None,
|
164
|
+
) -> str:
|
165
|
+
# https://docs.convex.dev/http-api/#sync
|
166
|
+
if self._snapshot_has_more:
|
167
|
+
return "/api/list_snapshot"
|
168
|
+
else:
|
169
|
+
return "/api/document_deltas"
|
170
|
+
|
171
|
+
def parse_response(
|
172
|
+
self,
|
173
|
+
response: requests.Response,
|
174
|
+
stream_state: Mapping[str, Any],
|
175
|
+
stream_slice: Optional[Mapping[str, Any]] = None,
|
176
|
+
next_page_token: Optional[Mapping[str, Any]] = None,
|
177
|
+
) -> Iterable[Mapping[str, Any]]:
|
178
|
+
if response.status_code != 200:
|
179
|
+
raise Exception(format_http_error("Failed request", response))
|
180
|
+
resp_json = response.json()
|
181
|
+
return list(resp_json["values"])
|
182
|
+
|
183
|
+
def request_params(
|
184
|
+
self,
|
185
|
+
stream_state: Optional[Mapping[str, Any]],
|
186
|
+
stream_slice: Optional[Mapping[str, Any]] = None,
|
187
|
+
next_page_token: Optional[Mapping[str, Any]] = None,
|
188
|
+
) -> MutableMapping[str, Any]:
|
189
|
+
params: Dict[str, Any] = {"tableName": self.table_name, "format": self.fmt}
|
190
|
+
if self._snapshot_has_more:
|
191
|
+
if self._snapshot_cursor_value:
|
192
|
+
params["cursor"] = self._snapshot_cursor_value
|
193
|
+
if self._delta_cursor_value:
|
194
|
+
params["snapshot"] = self._delta_cursor_value
|
195
|
+
else:
|
196
|
+
if self._delta_cursor_value:
|
197
|
+
params["cursor"] = self._delta_cursor_value
|
198
|
+
return params
|
199
|
+
|
200
|
+
def request_headers(
|
201
|
+
self,
|
202
|
+
stream_state: Optional[Mapping[str, Any]],
|
203
|
+
stream_slice: Optional[Mapping[str, Any]] = None,
|
204
|
+
next_page_token: Optional[Mapping[str, Any]] = None,
|
205
|
+
) -> Dict[str, str]:
|
206
|
+
"""
|
207
|
+
Custom headers for each HTTP request, not including Authorization.
|
208
|
+
"""
|
209
|
+
return {
|
210
|
+
"Convex-Client": f"airbyte-export-{CONVEX_CLIENT_VERSION}",
|
211
|
+
}
|
212
|
+
|
213
|
+
def get_updated_state(self, current_stream_state: ConvexState, latest_record: Mapping[str, Any]) -> ConvexState:
|
214
|
+
"""
|
215
|
+
This (deprecated) method is still used by AbstractSource to update state between calls to `read_records`.
|
216
|
+
"""
|
217
|
+
return cast(ConvexState, self.state)
|
218
|
+
|
219
|
+
def read_records(self, sync_mode: SyncMode, *args: Any, **kwargs: Any) -> Iterator[Any]:
|
220
|
+
self._delta_has_more = sync_mode == SyncMode.incremental
|
221
|
+
for read_record in super().read_records(sync_mode, *args, **kwargs):
|
222
|
+
record = dict(read_record)
|
223
|
+
ts_ns = record["_ts"]
|
224
|
+
ts_seconds = ts_ns / 1e9 # convert from nanoseconds.
|
225
|
+
# equivalent of java's `new Timestamp(transactionMillis).toInstant().toString()`
|
226
|
+
ts_datetime = datetime.utcfromtimestamp(ts_seconds)
|
227
|
+
ts = ts_datetime.isoformat()
|
228
|
+
# DebeziumEventUtils.CDC_LSN
|
229
|
+
record["_ab_cdc_lsn"] = ts_ns
|
230
|
+
# DebeziumEventUtils.CDC_DELETED_AT
|
231
|
+
record["_ab_cdc_updated_at"] = ts
|
232
|
+
record["_deleted"] = "_deleted" in record and record["_deleted"]
|
233
|
+
# DebeziumEventUtils.CDC_DELETED_AT
|
234
|
+
record["_ab_cdc_deleted_at"] = ts if record["_deleted"] else None
|
235
|
+
yield record
|
236
|
+
|
237
|
+
|
238
|
+
def format_http_error(context: str, resp: requests.Response) -> str:
|
239
|
+
try:
|
240
|
+
err = resp.json()
|
241
|
+
return f"{context}: {resp.status_code}: {err['code']}: {err['message']}"
|
242
|
+
except (JSONDecodeError, KeyError):
|
243
|
+
return f"{context}: {resp.text}"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
documentationUrl: https://docs.airbyte.com/integrations/sources/convex
|
2
|
+
connectionSpecification:
|
3
|
+
additionalProperties: true
|
4
|
+
$schema: http://json-schema.org/draft-07/schema#
|
5
|
+
title: Convex Source Spec
|
6
|
+
type: object
|
7
|
+
required:
|
8
|
+
- deployment_url
|
9
|
+
- access_key
|
10
|
+
properties:
|
11
|
+
deployment_url:
|
12
|
+
type: string
|
13
|
+
title: Deployment Url
|
14
|
+
examples:
|
15
|
+
- https://murky-swan-635.convex.cloud
|
16
|
+
- https://cluttered-owl-337.convex.cloud
|
17
|
+
access_key:
|
18
|
+
type: string
|
19
|
+
title: Access Key
|
20
|
+
description: API access key used to retrieve data from Convex.
|
21
|
+
airbyte_secret: true
|