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

πŸŽ‰ Source Shopify: Add a graphql products stream - Invalid PR #19492

Closed
wants to merge 0 commits into from
Closed

Conversation

PhilipCorr
Copy link
Contributor

@PhilipCorr PhilipCorr commented Nov 16, 2022

This PR has been closed as invalid and is for context only.
The current valid PR is here: #19789

What

Shopify have a graphQL admin API in addition to the rest admin API. Some data is only available via the graphQL api. This PR adds support for this graphQL API. It also adds the first graphQL stream for fetching products.

There is a products stream that uses the rest API already. However, some product data can only be fetched via the use of the graphQL API. In this PR I've added the ProductsGraphQl stream. I've named it like this to separate it from the pre-existing rest API Products stream.

How

I followed the github source as an example. This already has streams that use the github graphql api.
The main thing I copied from the github source is the use of the python sgqlc package.

To make full use of this package I ran the following command to generate the json representation of shopify's graphql api:

 python3 -m sgqlc.introspection --exclude-deprecated --exclude-description -H "X-Shopify-Access-Token: shpat_123abc" https://<my_shop>.myshopify.com/admin/api/2022-10/graphql.json shopify_schema.json

I then ran the folloiwng command to convert that json representation to python:

sgqlc-codegen schema shopify_schema.json shopify_schema.py

The resulting shopify_schema.py file is being added in this PR.
This follows the examples in the sgqlc documentation closely.

Recommended reading order

  1. As per the above comments, the shopify_schema.py file was auto-generated. Hence why it is so long.
  2. setup.py. This was updated to add the sgqlc depencency.
  3. source.py. This shows the added graphql stream.
  4. graphql.py. This builds up the graphql queries that get sent to the graphql endpoint.
  5. utils.py. Here, ShopifyRateLimiter was updated as the graphql api uses slightly different rate limiting compared to the rest api.
  6. test_control_rate_limit.py. This adds tests for the new graphql rate limiting.

🚨 User Impact 🚨

There should not be any breaking changes. The changes here are intended to be backwards compatible.

In order to simplify the changes here and to get quicker feedback, the fields requested via this stream are hard-coded as seen in graphql.py. Hard coding them like this follows how the GitHub source is currently implemented.
I'm open to suggestions as to how this should be handled.
Maybe we should use the catalog here to specify which fields are included in the graphql query.

Pre-merge Checklist

Updating a connector

Community member or Airbyter

  • Grant edit access to maintainers (instructions)
  • Secrets in the connector's spec are annotated with airbyte_secret
  • Unit & integration tests added and passing. Community members, please provide proof of success locally e.g: screenshot or copy-paste unit, integration, and acceptance test output. To run acceptance tests for a Python connector, follow instructions in the README. For java connectors run ./gradlew :airbyte-integrations:connectors:<name>:integrationTest.
  • Code reviews completed
  • Documentation updated
    • Connector's README.md
    • Connector's bootstrap.md. See description and examples
    • Changelog updated in docs/integrations/<source or destination>/<name>.md including changelog. See changelog example
  • PR name follows PR naming conventions

Airbyter

If this is a community PR, the Airbyte engineer reviewing this PR is responsible for the below items.

  • Create a non-forked branch based on this PR and test the below items on it
  • Build is successful
  • If new credentials are required for use in CI, add them to GSM. Instructions.
  • /test connector=connectors/<name> command is passing
  • New Connector version released on Dockerhub and connector version bumped by running the /publish command described here

Tests

Unit I had to add pytest-mock here. I'm not sure how these tests were working in the past without that dependency.
TEST_REQUIREMENTS = [
    "pytest~=6.1",
    "pytest-mock~=3.10.0",
    "requests-mock",
    "source-acceptance-test",
]

Output:

(virtualenv) ➜  source-shopify git:(master) βœ— python -m pytest unit_tests               
===================================================================================================================================================================================================== test session starts ======================================================================================================================================================================================================
platform darwin -- Python 3.9.11, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/bin/python
cachedir: .pytest_cache
rootdir: /Users/phil/Repos/airbyte, configfile: pytest.ini
plugins: mock-3.10.0, requests-mock-1.10.0
collected 72 items                                                                                                                                                                                                                                                                                                                                                                                                             

unit_tests/test_auth.py::test_shopify_authenticator_access_token PASSED
unit_tests/test_auth.py::test_shopify_authenticator_api_password PASSED
unit_tests/test_auth.py::test_raises_notimplemented_auth <ExceptionInfo for raises contextmanager>
{"type": "LOG", "log": {"level": "ERROR", "message": "Not implemented Auth method = not_implemented_auth_method"}}
PASSED
unit_tests/test_cached_stream_state.py::test_full_refresh[Sync Started. Parent.] PASSED
unit_tests/test_cached_stream_state.py::test_full_refresh[Sync Started. Child.] PASSED
unit_tests/test_cached_stream_state.py::test_incremental_sync[Sync Started. Parent] PASSED
unit_tests/test_cached_stream_state.py::test_incremental_sync[Sync Started. Child] PASSED
unit_tests/test_cached_stream_state.py::test_incremental_sync[Sync in progress. Parent] PASSED
unit_tests/test_cached_stream_state.py::test_incremental_sync[Sync in progress. Child] PASSED
unit_tests/test_control_rate_limit.py::test_rest_api_with_unknown_load PASSED
unit_tests/test_control_rate_limit.py::test_rest_api_with_low_load PASSED
unit_tests/test_control_rate_limit.py::test_rest_api_with_mid_load PASSED
unit_tests/test_control_rate_limit.py::test_rest_api_with_high_load PASSED
unit_tests/test_control_rate_limit.py::test_graphql_api_with_unknown_load PASSED
unit_tests/test_control_rate_limit.py::test_graphql_api_with_low_load PASSED
unit_tests/test_control_rate_limit.py::test_graphql_api_with_mid_load PASSED
unit_tests/test_control_rate_limit.py::test_graphql_api_with_high_load PASSED
unit_tests/test_source.py::test_customers_path[Articles-None-articles.json] PASSED
unit_tests/test_source.py::test_customers_path[Blogs-None-blogs.json] PASSED
unit_tests/test_source.py::test_customers_path[MetafieldBlogs-stream_slice2-blogs/123/metafields.json] PASSED
unit_tests/test_source.py::test_customers_path[MetafieldArticles-stream_slice3-articles/123/metafields.json] PASSED
unit_tests/test_source.py::test_customers_path[MetafieldCustomers-stream_slice4-customers/123/metafields.json] PASSED
unit_tests/test_source.py::test_customers_path[MetafieldOrders-stream_slice5-orders/123/metafields.json] PASSED
unit_tests/test_source.py::test_customers_path[MetafieldDraftOrders-stream_slice6-draft_orders/123/metafields.json] PASSED
unit_tests/test_source.py::test_customers_path[MetafieldProducts-stream_slice7-products/123/metafields.json] PASSED
unit_tests/test_source.py::test_customers_path[MetafieldProductVariants-stream_slice8-variants/123/metafields.json] PASSED
unit_tests/test_source.py::test_customers_path[MetafieldSmartCollections-stream_slice9-smart_collections/123/metafields.json] PASSED
unit_tests/test_source.py::test_customers_path[MetafieldCollections-stream_slice10-collections/123/metafields.json] PASSED
unit_tests/test_source.py::test_customers_path[MetafieldPages-stream_slice11-pages/123/metafields.json] PASSED
unit_tests/test_source.py::test_customers_path[MetafieldLocations-stream_slice12-locations/123/metafields.json] PASSED
unit_tests/test_source.py::test_customers_path[MetafieldShops-None-metafields.json] PASSED
unit_tests/test_source.py::test_customers_path[ProductImages-stream_slice14-products/123/images.json] PASSED
unit_tests/test_source.py::test_customers_path[ProductVariants-stream_slice15-products/123/variants.json] PASSED
unit_tests/test_source.py::test_customers_path[Customers-None-customers.json] PASSED
unit_tests/test_source.py::test_customers_path[Orders-None-orders.json] PASSED
unit_tests/test_source.py::test_customers_path[DraftOrders-None-draft_orders.json] PASSED
unit_tests/test_source.py::test_customers_path[Products-None-products.json] PASSED
unit_tests/test_source.py::test_customers_path[AbandonedCheckouts-None-checkouts.json] PASSED
unit_tests/test_source.py::test_customers_path[Collects-None-collects.json] PASSED
unit_tests/test_source.py::test_customers_path[TenderTransactions-None-tender_transactions.json] PASSED
unit_tests/test_source.py::test_customers_path[Pages-None-pages.json] PASSED
unit_tests/test_source.py::test_customers_path[PriceRules-None-price_rules.json] PASSED
unit_tests/test_source.py::test_customers_path[Locations-None-locations.json] PASSED
unit_tests/test_source.py::test_customers_path[Shop-None-shop.json] PASSED
unit_tests/test_source.py::test_customers_path[CustomCollections-None-custom_collections.json] PASSED
unit_tests/test_source.py::test_customers_path_with_stream_slice_param[OrderRefunds-stream_slice0-orders/12345/refunds.json] PASSED
unit_tests/test_source.py::test_customers_path_with_stream_slice_param[OrderRisks-stream_slice1-orders/12345/risks.json] PASSED
unit_tests/test_source.py::test_customers_path_with_stream_slice_param[Transactions-stream_slice2-orders/12345/transactions.json] PASSED
unit_tests/test_source.py::test_customers_path_with_stream_slice_param[DiscountCodes-stream_slice3-price_rules/12345/discount_codes.json] PASSED
unit_tests/test_source.py::test_customers_path_with_stream_slice_param[InventoryLevels-stream_slice4-locations/12345/inventory_levels.json] PASSED
unit_tests/test_source.py::test_customers_path_with_stream_slice_param[FulfillmentOrders-stream_slice5-orders/12345/fulfillment_orders.json] PASSED
unit_tests/test_source.py::test_customers_path_with_stream_slice_param[Fulfillments-stream_slice6-orders/12345/fulfillments.json] PASSED
unit_tests/test_source.py::test_check_connection PASSED
unit_tests/test_source.py::test_read_records {"type": "LOG", "log": {"level": "INFO", "message": "Reading order_refunds for order_id: None"}}
PASSED
unit_tests/test_source.py::test_request_params[OrderRefunds-expected0] PASSED
unit_tests/test_source.py::test_request_params[Orders-expected1] PASSED
unit_tests/test_source.py::test_request_params[AbandonedCheckouts-expected2] PASSED
unit_tests/test_source.py::test_get_updated_state PASSED
unit_tests/test_transform.py::test_enforcer_correct_type[transform_object0-schema0-checks0] PASSED
unit_tests/test_transform.py::test_enforcer_correct_type[transform_object1-schema1-checks1] PASSED
unit_tests/test_transform.py::test_enforcer_correct_type[transform_object2-schema2-checks2] PASSED
unit_tests/test_transform.py::test_enforcer_correct_type[transform_object3-schema3-checks3] PASSED
unit_tests/test_transform.py::test_enforcer_string_to_number[transform_object0-schema0-checks0] PASSED
unit_tests/test_transform.py::test_enforcer_string_to_number[transform_object1-schema1-checks1] PASSED
unit_tests/test_transform.py::test_enforcer_nested_object[transform_object0-schema0-checks0] PASSED
unit_tests/test_transform.py::test_enforcer_nested_array[transform_object0-schema0-checks0] PASSED
unit_tests/test_transform.py::test_enforcer_nested_array[transform_object1-schema1-checks1] PASSED
unit_tests/test_transform.py::test_enforcer_string_to_number_in_array[transform_object0-schema0-checks0] PASSED
unit_tests/unit_test.py::test_get_next_page_token PASSED
unit_tests/unit_test.py::test_privileges_validation PASSED
unit_tests/unit_test.py::test_unavailable_stream {"type": "LOG", "log": {"level": "WARN", "message": "Stream `balance_transactions` is not available, skipping..."}}
PASSED
unit_tests/unit_test.py::test_filter_records_newer_than_state {"type": "LOG", "log": {"level": "WARN", "message": "Stream `discount_codes`, Record ID: `2` missing cursor field: updated_at, record is emitted without state comparison"}}
{"type": "LOG", "log": {"level": "WARN", "message": "Stream `discount_codes`, Record ID: `4` cursor value is: None, record is emitted without state comparison"}}
PASSED

======================================================================================================================================================================================================= warnings summary =======================================================================================================================================================================================================
source_shopify/auth.py:14
  /Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/source_shopify/auth.py:14: DeprecationWarning: Call to deprecated class AirbyteLogger. (Use logging.getLogger('airbyte') instead) -- Deprecated since version 0.1.47.
    logger = AirbyteLogger()

virtualenv/lib/python3.9/site-packages/airbyte_cdk/sources/streams/http/http.py:43: 2 warnings
airbyte-integrations/connectors/source-shopify/unit_tests/test_source.py: 43 warnings
airbyte-integrations/connectors/source-shopify/unit_tests/unit_test.py: 43 warnings
  /Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/airbyte_cdk/sources/streams/http/http.py:43: DeprecationWarning: Call to deprecated class NoAuth. (Set `authenticator=None` instead) -- Deprecated since version 0.1.20.
    self._authenticator: HttpAuthenticator = NoAuth()

virtualenv/lib/python3.9/site-packages/deprecated/classic.py:173: 2 warnings
airbyte-integrations/connectors/source-shopify/unit_tests/test_source.py: 43 warnings
airbyte-integrations/connectors/source-shopify/unit_tests/unit_test.py: 43 warnings
  /Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/deprecated/classic.py:173: DeprecationWarning: Call to deprecated class HttpAuthenticator. (Use requests.auth.AuthBase instead) -- Deprecated since version 0.1.20.
    return old_new1(cls, *args, **kwargs)

airbyte-integrations/connectors/source-shopify/unit_tests/unit_test.py::test_unavailable_stream
  /Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/source_shopify/source.py:91: DeprecationWarning: The 'warn' method is deprecated, use 'warning' instead
    self.logger.warn(f"Stream `{self.name}` is not available, skipping...")

airbyte-integrations/connectors/source-shopify/unit_tests/unit_test.py::test_filter_records_newer_than_state
  /Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/source_shopify/source.py:157: DeprecationWarning: The 'warn' method is deprecated, use 'warning' instead
    self.logger.warn(

airbyte-integrations/connectors/source-shopify/unit_tests/unit_test.py::test_filter_records_newer_than_state
  /Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/source_shopify/source.py:151: DeprecationWarning: The 'warn' method is deprecated, use 'warning' instead
    self.logger.warn(

-- Docs: https://docs.pytest.org/en/stable/warnings.html
=============================================================================================================================================================================================== 72 passed, 180 warnings in 1.22s ===============================================================================================================================================================================================
(virtualenv) ➜  source-shopify git:(master) βœ— 

Integration

There doesn't seem to be any integration tests here

(virtualenv) ➜  source-shopify git:(master) βœ— python3 -m pytest integration_tests
===================================================================================================================================================================================================== test session starts ======================================================================================================================================================================================================
platform darwin -- Python 3.9.11, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/bin/python3
cachedir: .pytest_cache
rootdir: /Users/phil/Repos/airbyte, configfile: pytest.ini
plugins: mock-3.10.0, requests-mock-1.10.0
collected 0 items                                                                                                                                                                                                                                                                                                                                                                                                              

==================================================================================================================================================================================================== no tests ran in 0.01s =====================================================================================================================================================================================================
(virtualenv) ➜  source-shopify git:(master) βœ— 

Acceptance

Currently running into an error when I run the command as per the readme:

(virtualenv) ➜  source-shopify git:(master) βœ— python -m pytest -p source_acceptance_test.plugin
Traceback (most recent call last):
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 703, in import_plugin
    __import__(importspec)
ModuleNotFoundError: No module named 'source_acceptance_test'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/phil/.pyenv/versions/3.9.11/lib/python3.9/runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/Users/phil/.pyenv/versions/3.9.11/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/pytest/__main__.py", line 5, in <module>
    raise SystemExit(pytest.console_main())
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 185, in console_main
    code = main()
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 143, in main
    config = _prepareconfig(args, plugins)
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 318, in _prepareconfig
    config = pluginmanager.hook.pytest_cmdline_parse(
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/pluggy/_hooks.py", line 265, in __call__
    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/pluggy/_manager.py", line 80, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/pluggy/_callers.py", line 55, in _multicall
    gen.send(outcome)
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/_pytest/helpconfig.py", line 100, in pytest_cmdline_parse
    config: Config = outcome.get_result()
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/pluggy/_result.py", line 60, in get_result
    raise ex[1].with_traceback(ex[2])
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/pluggy/_callers.py", line 39, in _multicall
    res = hook_impl.function(*args)
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 1003, in pytest_cmdline_parse
    self.parse(args)
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 1283, in parse
    self._preparse(args, addopts=addopts)
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 1168, in _preparse
    self.pluginmanager.consider_preparse(args, exclude_only=False)
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 635, in consider_preparse
    self.consider_pluginarg(parg)
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 660, in consider_pluginarg
    self.import_plugin(arg, consider_entry_points=True)
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 705, in import_plugin
    raise ImportError(
  File "/Users/phil/Repos/airbyte/airbyte-integrations/connectors/source-shopify/virtualenv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 703, in import_plugin
    __import__(importspec)
ImportError: Error importing plugin "source_acceptance_test.plugin": No module named 'source_acceptance_test'

@PhilipCorr PhilipCorr marked this pull request as ready for review November 16, 2022 17:30
@octavia-squidington-iv octavia-squidington-iv added bounty area/documentation Improvements or additions to documentation labels Nov 16, 2022
@sajarin
Copy link
Contributor

sajarin commented Nov 17, 2022

Hey @PhilipCorr

This PR is part of Airbyte's Community Maintainer program and will be reviewed by a member of our community. Please remain patient as a maintainer is assigned to review the PR. Thank you!

@andresbravog
Copy link
Contributor

Thanks @PhilipCorr , here Andres, part of the Community Maintainer program. I'll be assisting with the review of this PR.

@sajarin sajarin added the bounty-L Maintainer program: claimable large bounty PR label Nov 17, 2022
@PhilipCorr
Copy link
Contributor Author

Great thanks @sajarin and @andresbravog! Looking forward to hearing what you think.

@andresbravog
Copy link
Contributor

  1. Unit tests βœ…
  2. Acceptance Tests πŸƒ (still going over the three types of authentication)
  3. Usage of connector βœ…

image

Copy link
Contributor

@andresbravog andresbravog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ‘ Gret work @PhilipCorr , Just a few style comments and minor type check issues

@@ -26,6 +26,9 @@
"products": {
"updated_at": "2025-07-08T05:40:38-07:00"
},
"products_graph_ql": {
"updated_at": "2022-11-16T16:00:00"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

won't be this date in the same format as the others including the timezone?

Copy link
Contributor Author

@PhilipCorr PhilipCorr Nov 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran a number of tests to make sure that the Graphql API returned the same data as the REST API.
This is me thinking out loud as much as anything but posting here for context too. Depending on the state timestamp format, the number of rows returned by the connector sometimes differs. Here are the results i'm getting:

Date Used REST API GraphQL API
2022-11-18T14:10:00 99 99
2022-11-18T14:30:00 89 89
2022-11-18T14:50:00 78 78
2022-11-18T14:50:00-07:00 0 79
2022-11-18T14:50:00:07:00 79 79
2022-11-18T14:50:00+07:00 238 80
2022-11-18T07:50:00 238 238
2022-11-18T14:50:00Z 80 80

The rest api accounts for the 7 hour offset for the timezone whereas the GraphQl API doesn't.

The state being returned by the connector looks like this:

{"type": "STATE", "state": {"type": "STREAM", "stream": {"stream_descriptor": {"name": "products"}, "stream_state": {"updated_at": "2022-11-18T16:51:10+00:00"}}, "data": {"products_graph_ql": {"updated_at": "2022-11-18T14:50:00Z"}, "products": {"updated_at": "2022-11-18T16:51:10+00:00"}}}}

I.e. in my tests, the updated_at timestamp (which is what updates the state) is such that it always includes +00:00. This might just be because of the timezone of the shop that I'm using. Because of this particular timezone (+00:00), both apis return the same results for both apis. I'll update the hard-coded json to include the timezone. I'm curious what your thoughts are here though?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks that makes sense, could you review the other style issues and commit updates?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, yes, will do that this morning!

@@ -11,6 +11,9 @@
"products": {
"updated_at": "2022-10-10T06:21:56-07:00"
},
"products_graph_ql": {
"updated_at": "2022-11-16T16:00:00"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

won't be this date equal in format to the others including the timezone?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be yes. See my comment above for some tests I ran. A date formatted with the suffix -07:00 gives different results between the REST API and the GraphQL API.

_schema_root = _schema.shopify_schema


def get_query_products(first: int, filter_field: str, filter_value: str, next_page_token: Optional[str]):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe break this on a second line to avoid pflake8 offenses

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ‘

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't changed when I ran black -l 120 . --extend-exclude virtualenv so also considering it formatted correctly if you agree?

@@ -145,7 +147,8 @@ This is expected when the connector hits the 429 - Rate Limit Exceeded HTTP Erro

| Version | Date | Pull Request | Subject |
|:--------|:-----------|:----------------------------------------------------------|:----------------------------------------------------------------------------------------------------------|
| 0.2.0 | 2022-10-21 | [18298](https://github.com/airbytehq/airbyte/pull/18298) | Updated API version to the `2022-10`, make stream schemas backward cpmpatible |
| 0.2.0 | 2022-11-16 | [19492](https://github.com/airbytehq/airbyte/pull/19492) | Add support for graphql and add a graphql products stream |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be 0.3.0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ‘


This Source Connector is based on a [Airbyte CDK](https://docs.airbyte.io/connector-development/cdk-python).

## Troubleshooting

Check out common troubleshooting issues for the BigQuery destination connector on our Discourse [here](https://discuss.airbyte.io/tags/c/connector/11/source-shopify).
Check out common troubleshooting issues for the Shopify destination connector on our Discourse [here](https://discuss.airbyte.io/tags/c/connector/11/source-shopify).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/destination/source/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the word destination to source here.

return wait_time

@staticmethod
def get_rest_api_wait_time(*args, threshold: float = 0.9, rate_limit_header: str = "X-Shopify-Shop-Api-Call-Limit"):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line long

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also was unaffected by black -l 120 . --extend-exclude virtualenv.

wait_time = ShopifyRateLimiter.on_mid_load
elif load < mid_load:
wait_time = ShopifyRateLimiter.on_low_load
wait_time = ShopifyRateLimiter._convert_load_to_time(load, threshold)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like load here is type float but an Optional[int] is expected in the function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot! Updated the expected type to be Optional[float]

else:
load = None

wait_time = ShopifyRateLimiter._convert_load_to_time(load, threshold)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto, load here is either None or float

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, I updated the expected type for this function to be Optional[float]

)
if api_type == ApiTypeEnum.rest.value:
ShopifyRateLimiter.wait_time(
ShopifyRateLimiter.get_rest_api_wait_time(*args, threshold=threshold, rate_limit_header=rate_limit_header)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long line

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated via black -l 120 . --extend-exclude virtualenv

)
elif api_type == ApiTypeEnum.graphql.value:
ShopifyRateLimiter.wait_time(
ShopifyRateLimiter.get_graphql_api_wait_time(*args, threshold=threshold)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long line

Copy link
Contributor Author

@PhilipCorr PhilipCorr Nov 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not impacted by black -l 120 . --extend-exclude virtualenv so considering it short enough if you agree?

@andresbravog
Copy link
Contributor

andresbravog commented Nov 18, 2022

Run flakeCheck these are the offenses:

> Task :airbyte-integrations:connectors:source-shopify:flakeCheck
[python] .venv/bin/python -m pflake8 --config /airbyte/pyproject.toml ./
         ./unit_tests/test_control_rate_limit.py:13:1: E302 expected 2 blank lines, found 1
         ./unit_tests/test_control_rate_limit.py:31:1: E302 expected 2 blank lines, found 1

@PhilipCorr
Copy link
Contributor Author

Running pflake8 now gives no output as the issues have been cleared up:

(virtualenv) ➜  source-shopify git:(master) βœ— python -m pflake8 --config ../../../pyproject.toml ./
(virtualenv) ➜  source-shopify git:(master) βœ— 

@PhilipCorr
Copy link
Contributor Author

Thanks for reviewing @andresbravog, I've made the requested changes now.

Copy link
Contributor

@andresbravog andresbravog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸŽ‰ Well done @PhilipCorr

@andresbravog
Copy link
Contributor

@sajarin This PR is ready to run tests and merge.

@PhilipCorr PhilipCorr closed this by deleting the head repository Nov 21, 2022
@PhilipCorr
Copy link
Contributor Author

Hi @andresbravog and @sajarin, I have unfortunately become aware that the access token I was using to test my new stream was accidentally committed to this PR. I removed the file and the access token has since been rotated, but the access token is still in the commit history here. Would you mind removing it from the commit history? I deleted my forked repo too in an effort to remove the token. I can open a new PR with the same changes as soon as this issue is resolved. Thanks and sorry for the inconvenience.

@andresbravog
Copy link
Contributor

@PhilipCorr I'd recommend modifying your repo history and reopening this PR. That should modify the history here.

@PhilipCorr PhilipCorr reopened this Nov 22, 2022
@PhilipCorr
Copy link
Contributor Author

PhilipCorr commented Nov 22, 2022

Hi @andresbravog and @sajarin,

The above reopening of the PR is based on a new fork that I created. This doesn't seem to have been enough to clear the commit history though.

I opened a support ticket with github to get their opinion on what should be done here.
Below you will see their recommendation.

I'm in favour of just deleting the internal references and preserving the comment history for context if you think this is appropriate.
I have since created a new fork of the repo and I can open a new PR (and close this one) with my changes based on this new fork.
I could then reference this PR from the new PR for context.
I'm also in favour of purging the deleted fork from the github servers entirely.
I'm happy to follow whichever actions you think are best here.
Thanks and applogies again for the inconvenience.

Hi Philip,

Thank you for writing in to GitHub Support.

Good work on rotating the token, that's definitely worth doing.

As the PR had a lot of discussion on it we'll need authorization from an admin of airbytehq/airbyte before we can deal with the references it contains.

We can either delete the pull request entirely, or just delete its internal references, which will make the diffs and the sensitive data inaccessible, but preserve the comment history.

Please let me know which you'd prefer?

Could you please ask a repository admin to authorize this request by forwarding them the email version of this message and asking them to reply to it with an email address associated with their GitHub account? Alternatively, they could leave a comment on the PR itself confirming authorization and whether it should be deleted or de-reffed.

We also need to deal with the reference in your deleted fork. Please confirm that's ok for me to purge the fork from our servers entirely?

Cheers,

Guy

@andresbravog
Copy link
Contributor

@sajarin @marcosmarxm we have a weird situation here, can we get help from a repository admin to delete the current PR so the credentials are not exposed any more?

@marcosmarxm
Copy link
Member

Hello πŸ‘‹, first thank you for this amazing contribution.

We really appreciate the effort you've made to improve the project.
We ask you patience for the code review. Last month our team was focused on Hacktoberfest event and that probably left some PR without the proper feedback. And this week, due to the Thanksgiving US Holiday, most our team is out of office with their families. Another important piece of information why code won't be merge this week is: as a safety measure the core team has decided to freeze merging code to main branch to keep the release stable. Next week we'll return to you with the proper code review and update the status of your contribution.

If you have any questions feel free to send me a message in Slack!
Thanks!

@marcosmarxm
Copy link
Member

I can't delete a PR

@marcosmarxm marcosmarxm reopened this Nov 23, 2022
@marcosmarxm
Copy link
Member

@PhilipCorr I can delete the PR, only you can do it. Did you try to remove using cherrypicking the commit?

@PhilipCorr
Copy link
Contributor Author

Hi @marcosmarxm, I did cherry pick the commit via the new fork I created. However, I couldn't find a way to get the history of this PR to reference the new PR. It seems like the history of this PR is tied to the old fork.

Either way, as per github support message above, are you happy to authorize deleting the internal references and preserving the comment history for this PR?

I'll open a new PR which points to this for context and we can merge via that new PR.
I need a message authorising this approach so that github support can proceed.

@sajarin
Copy link
Contributor

sajarin commented Nov 23, 2022

Hey @PhilipCorr,

Deleting the internal references should be fine, we want to preserve the comment history here. Let us know if you need anything else!

@PhilipCorr
Copy link
Contributor Author

Sounds great, thanks @sajarin. I'll let github support know that was are happy to:

  1. Preserve the comment history here.
  2. Delete the internal references.
  3. Purge my deleted fork of the repo.

@PhilipCorr
Copy link
Contributor Author

The internal references have been deleted. Closing this and will reference in a new PR.

@PhilipCorr PhilipCorr closed this Nov 24, 2022
@andresbravog
Copy link
Contributor

Thanks @PhilipCorr let me know when you create the new one so I can accelerate the Review process

@PhilipCorr PhilipCorr changed the title πŸŽ‰ Source Shopify: Add a graphql products stream πŸŽ‰ Source Shopify: Add a graphql products stream - Invalid PR Nov 24, 2022
@PhilipCorr
Copy link
Contributor Author

PhilipCorr commented Nov 24, 2022

Thanks @andresbravog, I have the follow-up PR here and it's ready for review: #19789

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/connectors Connector related issues area/documentation Improvements or additions to documentation bounty bounty-L Maintainer program: claimable large bounty PR community connectors/source/shopify
Projects
Development

Successfully merging this pull request may close these issues.

None yet

5 participants