Skip to content
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 tap tester #14

Merged
merged 34 commits into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
16fea46
changes to tap entrypoint to work with rest of tap
Sep 17, 2021
f080302
changes to discover.py to work with rest of tap
Sep 17, 2021
e6bfe76
changes to schema.py to work with rest of the tap
Sep 17, 2021
0857584
changes to sync.py to work with rest of the tap
Sep 17, 2021
bd8bac8
updated README links to documentation
Sep 17, 2021
cb60599
changes to client.py:
Sep 17, 2021
8c8a893
initial working version of streams.py
Sep 18, 2021
7aa3c20
adding pagination to streams.py
Sep 21, 2021
806997c
adding links object as part of client return for pagination changes
Sep 21, 2021
3f8a7b5
adjusting indentation to match PEP8:
Sep 23, 2021
694a4a5
removing extraneous keys and related functions from discover.py
Sep 23, 2021
19418cd
changes to streams.py:
Sep 23, 2021
e138b89
adding tap-tester base.py
Sep 28, 2021
b8d6985
adding tap-tester sync canary
Sep 28, 2021
da59cd0
adding tap-tester discovery
Sep 28, 2021
bc3e350
adding tap-tester start date
Sep 28, 2021
4586cdd
adding tap-tester automated fields
Sep 28, 2021
531cada
changes to setup.py:
Sep 28, 2021
d537130
adding circleci config
Sep 28, 2021
c3cdcb0
adding tap-tester pagination
Sep 30, 2021
723ccda
fix for onetimes stream not supporting cursor based pagination
Sep 30, 2021
c0c5227
updating cirlce config
Oct 1, 2021
d5f9756
adding additional assertions per PR feedback
Oct 1, 2021
9e7f8e8
add additional streams to pagination test
Oct 1, 2021
43c0e52
switch customers stream to page based
Oct 1, 2021
39fdb3c
Make pylint happy
luandy64 Oct 1, 2021
267616a
Update tap-tester invocation
luandy64 Oct 1, 2021
ea83b51
pylint fixes
Oct 1, 2021
dcbba56
Merge branch 'pr-14' of https://github.com/singer-io/tap-recharge int…
Oct 1, 2021
3e3db8b
Merge branch 'singer-io-pr-14' into add-tap-tester
Oct 1, 2021
715807e
remove extraneous string interpolation
Oct 1, 2021
132eee7
remove extraneous class attributes for shop stream
Oct 1, 2021
6f35642
fixes to start_date test:
Oct 5, 2021
e70536d
adding assertion for valid-replication-keys to discover test
Oct 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
version: 2
jobs:
build:
docker:
- image: 218546966473.dkr.ecr.us-east-1.amazonaws.com/circle-ci:stitch-tap-tester
steps:
- checkout
- run:
name: 'Setup virtual env'
command: |
python3 -mvenv /usr/local/share/virtualenvs/tap-recharge
source /usr/local/share/virtualenvs/tap-recharge/bin/activate
pip install .[dev]
- run:
name: 'pylint'
command: |
source /usr/local/share/virtualenvs/tap-recharge/bin/activate
pylint tap_recharge --disable 'missing-module-docstring,missing-function-docstring,missing-class-docstring,no-else-raise,raise-missing-from,inconsistent-return-statements'
- run:
when: always
name: 'Integration Tests'
command: |
aws s3 cp s3://com-stitchdata-dev-deployment-assets/environments/tap-tester/tap_tester_sandbox dev_env.sh
source dev_env.sh
source /usr/local/share/virtualenvs/tap-tester/bin/activate
run-test --tap=tap-recharge tests
workflows:
version: 2
commit:
jobs:
- build:
context: circleci-user
build_daily:
triggers:
- schedule:
cron: "0 14 * * *"
filters:
branches:
only:
- master
jobs:
- build:
context: circleci-user
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ This tap:
- [Customers](https://developer.rechargepayments.com/#list-customers)
- [Discounts](https://developer.rechargepayments.com/#list-discounts)
- [Metafields for Store, Customers, Subscriptions](https://developer.rechargepayments.com/#list-metafields)
- [One-time Products](https://developer.rechargepayments.com/#list-onetimes-alpha)
- [One-time Products](https://developer.rechargepayments.com/#list-onetimes)
- [Orders](https://developer.rechargepayments.com/#list-orders)
- [Products](https://developer.rechargepayments.com/#list-products-beta)
- [Shop](https://developer.rechargepayments.com/#retrieve-shop)
- [Products](https://developer.rechargepayments.com/#list-products)
- [Shop](https://developer.rechargepayments.com/#retrieve-a-shop)
- [Subscriptions](https://developer.rechargepayments.com/#list-subscriptions)
- Outputs the schema for each resource
- Incrementally pulls data based on the input state
Expand Down Expand Up @@ -94,7 +94,7 @@ This tap:
- Bookmark: updated_at (date-time)
- Transformations: None

[**onetimes**](https://developer.rechargepayments.com/#list-onetimes-alpha)
[**onetimes**](https://developer.rechargepayments.com/#list-onetimes)
- Endpoint: https://api.rechargeapps.com/onetimes
- Primary keys: id
- Foreign keys: address_id (addresses), customer_id (customers), recharge_product_id (products), shopify_product_id, shopify_variant_id
Expand All @@ -112,15 +112,15 @@ This tap:
- Bookmark: updated_at (date-time)
- Transformations: None

[**products**](https://developer.rechargepayments.com/#list-products-beta)
[**products**](https://developer.rechargepayments.com/#list-products)
- Endpoint: https://api.rechargeapps.com/products
- Primary keys: id
- Foreign keys: collection_id (collections), shopify_product_id
- Replication strategy: Incremental (query all, filter results)
- Bookmark: updated_at (date-time)
- Transformations: None

[**shop**](https://developer.rechargepayments.com/#retrieve-shop)
[**shop**](https://developer.rechargepayments.com/#retrieve-a-shop)
- Endpoint: https://api.rechargeapps.com/shop
- Primary keys: id
- Foreign keys: None
Expand Down
7 changes: 6 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
install_requires=[
'backoff==1.8.0',
'requests==2.23.0',
'singer-python==5.9.0'
'singer-python==5.10.0'
],
entry_points='''
[console_scripts]
Expand All @@ -22,4 +22,9 @@
'tap_recharge': [
'schemas/*.json'
]
},
extras_require={
'dev': [
'pylint'
]
})
30 changes: 15 additions & 15 deletions tap_recharge/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
#!/usr/bin/env python3

import sys
import json
import argparse
import singer
from singer import metadata, utils
from singer import get_logger, utils

from tap_recharge.client import RechargeClient
from tap_recharge.discover import discover
from tap_recharge.sync import sync

LOGGER = singer.get_logger()
LOGGER = get_logger()

REQUIRED_CONFIG_KEYS = [
'access_token',
Expand All @@ -21,17 +18,19 @@ def do_discover():

LOGGER.info('Starting discover')
catalog = discover()
json.dump(catalog.to_dict(), sys.stdout, indent=2)
catalog.dump()
LOGGER.info('Finished discover')


@singer.utils.handle_top_exception(LOGGER)
@utils.handle_top_exception(LOGGER)
def main():
"""Entrypoint function for tap."""

parsed_args = singer.utils.parse_args(REQUIRED_CONFIG_KEYS)
parsed_args = utils.parse_args(REQUIRED_CONFIG_KEYS)

with RechargeClient(parsed_args.config['access_token'],
parsed_args.config['user_agent']) as client:
with RechargeClient(
parsed_args.config['access_token'],
parsed_args.config['user_agent']) as client:

state = {}
if parsed_args.state:
Expand All @@ -40,10 +39,11 @@ def main():
if parsed_args.discover:
do_discover()
elif parsed_args.catalog:
sync(client=client,
catalog=parsed_args.catalog,
state=state,
start_date=parsed_args.config['start_date'])
sync(
client=client,
catalog=parsed_args.catalog,
state=state,
config=parsed_args.config)

if __name__ == '__main__':
main()
44 changes: 25 additions & 19 deletions tap_recharge/client.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import backoff
import requests
from requests.exceptions import ConnectionError
from singer import metrics, utils

import singer
from singer import metrics, utils

LOGGER = singer.get_logger()

Expand Down Expand Up @@ -82,8 +82,8 @@ def raise_for_error(response):
return
response = response.json()
if ('error' in response) or ('errorCode' in response):
message = '%s: %s' % (response.get('error', str(error)),
response.get('message', 'Unknown Error'))
message = f"{response.get('error', str(error))}: \
{response.get('message', 'Unknown Error')}"
error_code = response.get('status')
ex = get_exception_for_error_code(error_code)
if response.status_code == 401 and 'Expired access token' in message:
Expand All @@ -97,10 +97,11 @@ def raise_for_error(response):
raise RechargeError(error)


class RechargeClient(object):
def __init__(self,
access_token,
user_agent=None):
class RechargeClient:
def __init__(
self,
access_token,
user_agent=None):
self.__access_token = access_token
self.__user_agent = user_agent
self.__session = requests.Session()
Expand All @@ -114,10 +115,11 @@ def __enter__(self):
def __exit__(self, exception_type, exception_value, traceback):
self.__session.close()

@backoff.on_exception(backoff.expo,
Server5xxError,
max_tries=5,
factor=2)
@backoff.on_exception(
backoff.expo,
Server5xxError,
max_tries=5,
factor=2)
def check_access_token(self):
if self.__access_token is None:
raise Exception('Error: Missing access_token.')
Expand All @@ -131,16 +133,17 @@ def check_access_token(self):
url='https://api.rechargeapps.com',
headers=headers)
if response.status_code != 200:
LOGGER.error('Error status_code = {}'.format(response.status_code))
LOGGER.error('Error status_code = %s', response.status_code)
raise_for_error(response)
else:
return True


@backoff.on_exception(backoff.expo,
(Server5xxError, ConnectionError, Server429Error),
max_tries=5,
factor=2)
@backoff.on_exception(
backoff.expo,
(Server5xxError, requests.ConnectionError, Server429Error),
max_tries=5,
factor=2)
# Call/rate limit: https://developer.rechargepayments.com/#rate-limits
@utils.ratelimit(120, 60)
def request(self, method, path=None, url=None, **kwargs):
Expand Down Expand Up @@ -177,17 +180,20 @@ def request(self, method, path=None, url=None, **kwargs):
if response.status_code >= 500:
raise Server5xxError()

if response.status_code == 429:
raise Server429Error()

if response.status_code != 200:
raise_for_error(response)

# Log invalid JSON (e.g. unterminated string errors)
try:
response_json = response.json()
except Exception as err:
LOGGER.error('{}'.format(err))
LOGGER.error(err)
raise Exception(err)

return response_json
return response_json, response.links

def get(self, path, **kwargs):
return self.request('GET', path=path, **kwargs)
Expand Down
32 changes: 18 additions & 14 deletions tap_recharge/discover.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
from singer.catalog import Catalog, CatalogEntry, Schema
from tap_recharge.schema import get_schemas, STREAMS
from singer.catalog import Catalog
from tap_recharge.schema import get_schemas


def discover():
"""
Constructs a singer Catalog object based on the schemas and metadata.
"""
schemas, field_metadata = get_schemas()
catalog = Catalog([])
streams = []

for schema_name, schema in schemas.items():
schema_meta = field_metadata[schema_name]

for stream_name, schema_dict in schemas.items():
schema = Schema.from_dict(schema_dict)
mdata = field_metadata[stream_name]
catalog_entry = {
'stream': schema_name,
'tap_stream_id': schema_name,
'schema': schema,
'metadata': schema_meta
}

catalog.streams.append(CatalogEntry(
stream=stream_name,
tap_stream_id=stream_name,
key_properties=STREAMS[stream_name]['key_properties'],
schema=schema,
metadata=mdata
))
streams.append(catalog_entry)

return catalog
return Catalog.from_dict({'streams': streams})
Loading