-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
🎉 Add Braintree source connector #5362
Conversation
/test connector=source-braintree
|
2a43333
to
fe956f1
Compare
/test connector=source-braintree
|
fe956f1
to
11508d7
Compare
ed6132d
to
f277359
Compare
/test connector=source-braintree
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
few comments
airbyte-integrations/connectors/source-braintree/integration_tests/__init__.py
Show resolved
Hide resolved
airbyte-integrations/connectors/source-braintree/unit_tests/test_source.py
Outdated
Show resolved
Hide resolved
airbyte-integrations/connectors/source-braintree/acceptance-test-config.yml
Outdated
Show resolved
Hide resolved
airbyte-integrations/connectors/source-braintree/source_braintree/streams.py
Outdated
Show resolved
Hide resolved
airbyte-integrations/connectors/source-braintree/source_braintree/streams.py
Outdated
Show resolved
Hide resolved
airbyte-integrations/connectors/source-braintree/source_braintree/streams.py
Outdated
Show resolved
Hide resolved
/test connector=source-braintree
|
airbyte-integrations/connectors/source-braintree/source_braintree/streams.py
Outdated
Show resolved
Hide resolved
b9dd023
to
3e8f0b7
Compare
@@ -0,0 +1,7 @@ | |||
{ | |||
"sourceDefinitionId": "63cea06f-1c75-458d-88fe-ad48c7cb27fd", | |||
"name": "Braintree", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add "Deprecated" to the old one (for json
and yml
files)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
"stream": { | ||
"name": "customer_stream", | ||
"json_schema": {}, | ||
"supported_sync_modes": ["full_refresh"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I look at the code and don't understand why your customer_stream
stream can be Incremental, but in the catalog you indicate it simply as full_refresh
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it ignored by acceptance test anyway? What point of those changes to integration_tests/configured_catalog.json ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
"json_schema": {}, | ||
"supported_sync_modes": ["full_refresh"], | ||
"source_defined_cursor": false, | ||
"default_cursor_field": ["column_name"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And I don't see the source_defined_primary_key field
"source_defined_cursor": false, | ||
"default_cursor_field": ["column_name"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is unnecessary
item = self.get_json_from_resource(item) | ||
item = self.model(**item) | ||
yield item.dict(exclude_unset=True) | ||
yield from [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need this line - the code should complete without it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed
@backoff.on_exception( | ||
backoff.expo, | ||
( | ||
braintree.exceptions.GatewayTimeoutError, | ||
braintree.exceptions.RequestTimeoutError, | ||
braintree.exceptions.ServerError, | ||
braintree.exceptions.ServiceUnavailableError, | ||
braintree.exceptions.TooManyRequestsError, | ||
), | ||
max_tries=5, | ||
) | ||
def read_records( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this is correct.
I think that we should not wrap the whole generator in backoff
, but a separate function that requests data once.
In this case, we exclude the likelihood that if an error pops up at the 10th iteration, then we will not run the read_records
function with the initial values, thereby getting duplicate records.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, fixed
docs/SUMMARY.md
Outdated
* [Braintree](integrations/sources/braintree.md) | ||
* [Bing Ads](integrations/sources/bing-ads.md) | ||
* [BigQuery](integrations/sources/bigquery.md) | ||
* [Braintree (standalone)](integrations/sources/braintree-standalone.md) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* [Braintree](integrations/sources/braintree.md) | |
* [Bing Ads](integrations/sources/bing-ads.md) | |
* [BigQuery](integrations/sources/bigquery.md) | |
* [Braintree (standalone)](integrations/sources/braintree-standalone.md) | |
* [Braintree (Deprecated)](integrations/sources/braintree.md) | |
* [Bing Ads](integrations/sources/bing-ads.md) | |
* [BigQuery](integrations/sources/bigquery.md) | |
* [Braintree](integrations/sources/braintree-standalone.md) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments
/test connector=source-braintree
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nicely done with using pydantic for the models. It's super easy to read. Was it easier to use than JSON/would you prefer that we do things this way going forward?
We should remove the previous singer connector from the YAMLs, jsons, etc... (not just rename it). We should make things as simple as possible for the user, who probably doesn't care if something is based on singer or airbyte, they just want data from braintree
@@ -168,7 +168,7 @@ | |||
documentationUrl: https://docs.airbyte.io/integrations/sources/freshdesk | |||
icon: freshdesk.svg | |||
- sourceDefinitionId: 396e4ca3-8a97-4b85-aa4e-c9d8c2d5f992 | |||
name: Braintree | |||
name: Braintree (Deprecated) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should delete the singer one
configured_catalog_path: "integration_tests/configured_catalog.json" | ||
empty_streams: ["subscription_stream"] | ||
expect_records: | ||
path: "integration_tests/expected_records.txt" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great job adding expected records!
|
||
|
||
class AllOptional(pydantic.main.ModelMetaclass): | ||
def __new__(self, name, bases, namespaces, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add a docstring to explain why we need this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
VenmoAccount, | ||
VisaCheckoutCard, | ||
) | ||
from .common import CatalogModel as BaseModel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think renaming it as basemodel is a little misleading because we're not using basemodel, we're using a subclass of it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
name="Merchant ID", | ||
description='<a href="https://docs.airbyte.io/integrations/sources/braintree">Merchant ID</a> is the unique identifier for entire gateway account.', | ||
) | ||
public_key: str = Field(name="Public key", description="This is your user-specific public identifier for Braintree.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add the title
field to each of these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would automatically be added by pydantic model generator:
{
"type": "SPEC",
"spec": {
"documentationUrl": "https://docs.airbyte.io/integrations/sources/braintree",
"connectionSpecification": {
"title": "Braintree Spec",
"type": "object",
"properties": {
"merchant_id": {
"title": "Merchant Id",
"description": "<a href=\"https://docs.airbyte.io/integrations/sources/braintree\">Merchant ID</a> is the unique identifier for entire gateway account.",
"name": "Merchant ID",
"type": "string"
},
"public_key": {
"title": "Public Key",
"description": "This is your user-specific public identifier for Braintree.",
"name": "Public key",
"type": "string"
},
"private_key": {
"title": "Private Key",
"description": "This is your user-specific private identifier.",
"name": "Private Key",
"airbyte_secret": true,
"type": "string"
},
"start_date": {
"title": "Start Date",
"description": "The date from which you'd like to replicate data for Braintree API for UTC timezone, All data generated after this date will be replicated.",
"name": "Start date",
"examples": [
"2020",
"2020-12-30",
"2020-11-22 20:20:05"
],
"type": "string",
"format": "date-time"
},
"environment": {
"$ref": "#/definitions/Environment"
}
},
"required": [
"merchant_id",
"public_key",
"private_key",
"environment"
],
"definitions": {
"Environment": {
"title": "Environment",
"description": "An enumeration.",
"enum": [
"Development",
"Sandbox",
"Qa",
"Production"
],
"type": "string"
}
}
}
}
}
|
||
|
||
@responses.activate | ||
def test_customers_stream(test_config): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome use of this library. Nicely done!
from source_braintree.spec import BraintreeConfig | ||
|
||
|
||
class BraintreeStream(Stream): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we should declare as ABC since it declares abstract methods
class BraintreeStream(Stream): | |
class BraintreeStream(Stream, ABC): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
/test connector=source-braintree
|
Yes, it was a lot more easy than JSON, I used pydantic models for Amazon Ads and Braintree and was happy about it, would like to use it instead of jsonschema models. |
/publish connector=source-braintree
|
c55ce76
to
2fc1619
Compare
2fc1619
to
5e4f747
Compare
/publish connector=source-braintree
|
/publish connector=connectors/source-braintree
|
What
Resolves #5236 issues
How
Describe the solution
Recommended reading order
x.java
y.python
Pre-merge Checklist
Expand the relevant checklist and delete the others.
New Connector
Community member or Airbyter
airbyte_secret
./gradlew :airbyte-integrations:connectors:<name>:integrationTest
.README.md
docs/SUMMARY.md
docs/integrations/<source or destination>/<name>.md
including changelog. See changelog exampledocs/integrations/README.md
airbyte-integrations/builds.md
Airbyter
If this is a community PR, the Airbyte engineer reviewing this PR is responsible for the below items.
/test connector=connectors/<name>
command is passing./publish
command described hereUpdating a connector
Community member or Airbyter
airbyte_secret
./gradlew :airbyte-integrations:connectors:<name>:integrationTest
.README.md
docs/integrations/<source or destination>/<name>.md
including changelog. See changelog exampleAirbyter
If this is a community PR, the Airbyte engineer reviewing this PR is responsible for the below items.
/test connector=connectors/<name>
command is passing./publish
command described hereConnector Generator
-scaffold
in their name) have been updated with the latest scaffold by running./gradlew :airbyte-integrations:connector-templates:generator:testScaffoldTemplates
then checking in your changes