airbyte-source-shopify 3.2.0.dev202602040050__tar.gz → 3.2.1.dev202601082335__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/PKG-INFO +1 -1
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/pyproject.toml +1 -1
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/product_variants.json +2 -10
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/scopes.py +0 -1
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/shopify_graphql/bulk/query.py +1 -146
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/source.py +0 -5
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/streams/streams.py +39 -19
- airbyte_source_shopify-3.2.0.dev202602040050/source_shopify/schemas/collection_products.json +0 -35
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/README.md +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/__init__.py +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/auth.py +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/config_migrations.py +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/http_request.py +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/run.py +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/abandoned_checkouts.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/articles.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/balance_transactions.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/blogs.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/collections.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/collects.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/countries.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/custom_collections.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/customer_address.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/customer_journey_summary.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/customers.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/deleted_products.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/discount_codes.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/disputes.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/draft_orders.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/fulfillment_orders.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/fulfillments.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/inventory_items.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/inventory_levels.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/locations.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/metafield_articles.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/metafield_blogs.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/metafield_collections.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/metafield_customers.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/metafield_draft_orders.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/metafield_locations.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/metafield_orders.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/metafield_pages.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/metafield_product_images.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/metafield_product_variants.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/metafield_products.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/metafield_shops.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/metafield_smart_collections.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/order_agreements.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/order_refunds.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/order_risks.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/orders.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/pages.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/price_rules.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/product_images.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/products.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/profile_location_groups.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/shop.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/smart_collections.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/tender_transactions.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/schemas/transactions.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/shopify_graphql/bulk/__init__.py +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/shopify_graphql/bulk/exceptions.py +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/shopify_graphql/bulk/job.py +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/shopify_graphql/bulk/record.py +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/shopify_graphql/bulk/retry.py +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/shopify_graphql/bulk/status.py +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/shopify_graphql/bulk/tools.py +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/spec.json +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/streams/base_streams.py +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/transform.py +0 -0
- {airbyte_source_shopify-3.2.0.dev202602040050 → airbyte_source_shopify-3.2.1.dev202601082335}/source_shopify/utils.py +0 -0
|
@@ -3,7 +3,7 @@ requires = [ "poetry-core>=1.0.0",]
|
|
|
3
3
|
build-backend = "poetry.core.masonry.api"
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
|
-
version = "3.2.
|
|
6
|
+
version = "3.2.1.dev.202601082335"
|
|
7
7
|
name = "airbyte-source-shopify"
|
|
8
8
|
description = "Source CDK implementation for Shopify."
|
|
9
9
|
authors = [ "Airbyte <contact@airbyte.io>",]
|
|
@@ -52,20 +52,12 @@
|
|
|
52
52
|
"items": {
|
|
53
53
|
"type": ["null", "object"],
|
|
54
54
|
"properties": {
|
|
55
|
-
"id": {
|
|
56
|
-
"description": "The unique identifier for the product option.",
|
|
57
|
-
"type": ["null", "integer"]
|
|
58
|
-
},
|
|
59
55
|
"name": {
|
|
60
|
-
"description": "The product option
|
|
56
|
+
"description": "The product option’s name.",
|
|
61
57
|
"type": ["null", "string"]
|
|
62
58
|
},
|
|
63
|
-
"position": {
|
|
64
|
-
"description": "The display order of the product option.",
|
|
65
|
-
"type": ["null", "integer"]
|
|
66
|
-
},
|
|
67
59
|
"value": {
|
|
68
|
-
"description": "The product option
|
|
60
|
+
"description": "The product option’s value.",
|
|
69
61
|
"type": ["null", "string"]
|
|
70
62
|
},
|
|
71
63
|
"option_value": {
|
|
@@ -44,7 +44,6 @@ SCOPES_MAPPING: Mapping[str, set[str]] = {
|
|
|
44
44
|
"MetafieldProductVariants": ("read_products",),
|
|
45
45
|
"CustomCollections": ("read_products",),
|
|
46
46
|
"Collects": ("read_products",),
|
|
47
|
-
"CollectionProducts": ("read_products",),
|
|
48
47
|
"ProductVariants": ("read_products", "read_inventory"),
|
|
49
48
|
"MetafieldCollections": ("read_products",),
|
|
50
49
|
"SmartCollections": ("read_products",),
|
|
@@ -952,114 +952,6 @@ class Collection(ShopifyBulkQuery):
|
|
|
952
952
|
yield record
|
|
953
953
|
|
|
954
954
|
|
|
955
|
-
class CollectionProduct(ShopifyBulkQuery):
|
|
956
|
-
"""
|
|
957
|
-
Returns the products associated with each collection, including both custom collections
|
|
958
|
-
and smart collections. This provides all product<>collection associations, not just
|
|
959
|
-
manually associated products (which is what the Collects REST API provides).
|
|
960
|
-
|
|
961
|
-
{
|
|
962
|
-
collections(query: "updated_at:>='2023-02-07T00:00:00+00:00' AND updated_at:<='2023-12-04T00:00:00+00:00'", sortKey: UPDATED_AT) {
|
|
963
|
-
edges {
|
|
964
|
-
node {
|
|
965
|
-
__typename
|
|
966
|
-
id
|
|
967
|
-
handle
|
|
968
|
-
updatedAt
|
|
969
|
-
products {
|
|
970
|
-
edges {
|
|
971
|
-
node {
|
|
972
|
-
__typename
|
|
973
|
-
id
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
"""
|
|
982
|
-
|
|
983
|
-
query_name = "collections"
|
|
984
|
-
sort_key = "UPDATED_AT"
|
|
985
|
-
|
|
986
|
-
products_fields: List[Field] = [
|
|
987
|
-
Field(
|
|
988
|
-
name="edges",
|
|
989
|
-
fields=[
|
|
990
|
-
Field(
|
|
991
|
-
name="node",
|
|
992
|
-
fields=[
|
|
993
|
-
"__typename",
|
|
994
|
-
"id",
|
|
995
|
-
],
|
|
996
|
-
)
|
|
997
|
-
],
|
|
998
|
-
)
|
|
999
|
-
]
|
|
1000
|
-
|
|
1001
|
-
query_nodes: List[Field] = [
|
|
1002
|
-
"__typename",
|
|
1003
|
-
"id",
|
|
1004
|
-
Field(name="handle"),
|
|
1005
|
-
Field(name="updatedAt"),
|
|
1006
|
-
Field(name="products", fields=products_fields),
|
|
1007
|
-
]
|
|
1008
|
-
|
|
1009
|
-
record_composition = {
|
|
1010
|
-
"new_record": "Collection",
|
|
1011
|
-
"record_components": ["Product"],
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
def _process_product_components(self, products: List[dict]) -> List[dict]:
|
|
1015
|
-
"""
|
|
1016
|
-
Process product components to resolve IDs from string to int and preserve the original ID.
|
|
1017
|
-
|
|
1018
|
-
Args:
|
|
1019
|
-
products: List of product dictionaries with string IDs
|
|
1020
|
-
|
|
1021
|
-
Returns:
|
|
1022
|
-
List of processed product dictionaries with both id (int) and admin_graphql_api_id (str)
|
|
1023
|
-
"""
|
|
1024
|
-
for product in products:
|
|
1025
|
-
# Save the original string ID before resolving
|
|
1026
|
-
product["admin_graphql_api_id"] = product.get("id")
|
|
1027
|
-
# Resolve the ID from string to int
|
|
1028
|
-
product["id"] = self.tools.resolve_str_id(product.get("id"))
|
|
1029
|
-
return products
|
|
1030
|
-
|
|
1031
|
-
def record_process_components(self, record: MutableMapping[str, Any]) -> Iterable[MutableMapping[str, Any]]:
|
|
1032
|
-
"""
|
|
1033
|
-
Process collection records and yield one record per collection-product association.
|
|
1034
|
-
"""
|
|
1035
|
-
record_components = record.get("record_components", {})
|
|
1036
|
-
products = record_components.get("Product", [])
|
|
1037
|
-
|
|
1038
|
-
# Get collection info - id is already resolved to int, admin_graphql_api_id has the string version
|
|
1039
|
-
collection_id = record.get("id")
|
|
1040
|
-
collection_admin_graphql_api_id = record.get("admin_graphql_api_id")
|
|
1041
|
-
collection_handle = record.get("handle")
|
|
1042
|
-
collection_updated_at = self.tools.from_iso8601_to_rfc3339(record, "updatedAt")
|
|
1043
|
-
|
|
1044
|
-
if products:
|
|
1045
|
-
# Process products to resolve their IDs
|
|
1046
|
-
products = self._process_product_components(products)
|
|
1047
|
-
|
|
1048
|
-
for product in products:
|
|
1049
|
-
product_id = product.get("id")
|
|
1050
|
-
product_admin_graphql_api_id = product.get("admin_graphql_api_id")
|
|
1051
|
-
|
|
1052
|
-
yield {
|
|
1053
|
-
"collection_id": collection_id,
|
|
1054
|
-
"collection_admin_graphql_api_id": collection_admin_graphql_api_id,
|
|
1055
|
-
"collection_handle": collection_handle,
|
|
1056
|
-
"collection_updated_at": collection_updated_at,
|
|
1057
|
-
"product_id": product_id,
|
|
1058
|
-
"product_admin_graphql_api_id": product_admin_graphql_api_id,
|
|
1059
|
-
"shop_url": self.config.get("shop"),
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
955
|
class CustomerAddresses(ShopifyBulkQuery):
|
|
1064
956
|
"""
|
|
1065
957
|
{
|
|
@@ -2791,13 +2683,7 @@ class ProductVariant(ShopifyBulkQuery):
|
|
|
2791
2683
|
Field(name="selectedOptions", alias="options", fields=option_fields),
|
|
2792
2684
|
Field(name="image", fields=image_fields),
|
|
2793
2685
|
Field(name="inventoryQuantity", alias="old_inventory_quantity"),
|
|
2794
|
-
Field(
|
|
2795
|
-
name="product",
|
|
2796
|
-
fields=[
|
|
2797
|
-
Field(name="id", alias="product_id"),
|
|
2798
|
-
Field(name="options", alias="product_options", fields=["id", "name", "position"]),
|
|
2799
|
-
],
|
|
2800
|
-
),
|
|
2686
|
+
Field(name="product", fields=[Field(name="id", alias="product_id")]),
|
|
2801
2687
|
Field(name="inventoryItem", fields=inventory_item_fields),
|
|
2802
2688
|
] + presentment_prices
|
|
2803
2689
|
|
|
@@ -2844,34 +2730,6 @@ class ProductVariant(ShopifyBulkQuery):
|
|
|
2844
2730
|
entity = record.get(from_property, {})
|
|
2845
2731
|
return self.tools.resolve_str_id(entity.get(id_field)) if entity else None
|
|
2846
2732
|
|
|
2847
|
-
def _enrich_options_with_product_options(self, record: MutableMapping[str, Any]) -> None:
|
|
2848
|
-
"""
|
|
2849
|
-
Enriches the variant's options with id and position from the product's options.
|
|
2850
|
-
Matches options by name and adds the corresponding ProductOption id and position.
|
|
2851
|
-
"""
|
|
2852
|
-
options = record.get("options") or []
|
|
2853
|
-
product = record.get("product") or {}
|
|
2854
|
-
product_options = product.get("product_options") or []
|
|
2855
|
-
|
|
2856
|
-
# Build a lookup map from option name to ProductOption data
|
|
2857
|
-
product_options_map = {}
|
|
2858
|
-
for product_option in product_options:
|
|
2859
|
-
if product_option:
|
|
2860
|
-
name = product_option.get("name")
|
|
2861
|
-
if name:
|
|
2862
|
-
product_options_map[name] = {
|
|
2863
|
-
"id": self.tools.resolve_str_id(product_option.get("id")),
|
|
2864
|
-
"position": product_option.get("position"),
|
|
2865
|
-
}
|
|
2866
|
-
|
|
2867
|
-
# Enrich each option with id and position from the matching ProductOption
|
|
2868
|
-
for option in options:
|
|
2869
|
-
if option:
|
|
2870
|
-
option_name = option.get("name")
|
|
2871
|
-
if option_name and option_name in product_options_map:
|
|
2872
|
-
option["id"] = product_options_map[option_name]["id"]
|
|
2873
|
-
option["position"] = product_options_map[option_name]["position"]
|
|
2874
|
-
|
|
2875
2733
|
def record_process_components(self, record: MutableMapping[str, Any]) -> Iterable[MutableMapping[str, Any]]:
|
|
2876
2734
|
"""
|
|
2877
2735
|
Defines how to process collected components.
|
|
@@ -2884,9 +2742,6 @@ class ProductVariant(ShopifyBulkQuery):
|
|
|
2884
2742
|
record["presentment_prices"] = self._process_presentment_prices(record_components.get("ProductVariantPricePair", []))
|
|
2885
2743
|
record.pop("record_components")
|
|
2886
2744
|
|
|
2887
|
-
# enrich options with id and position from product options (must be done before product is removed)
|
|
2888
|
-
self._enrich_options_with_product_options(record)
|
|
2889
|
-
|
|
2890
2745
|
# unnest mandatory fields from their placeholders
|
|
2891
2746
|
record["product_id"] = self._unnest_and_resolve_id(record, "product", "product_id")
|
|
2892
2747
|
record["inventory_item_id"] = self._unnest_and_resolve_id(record, "inventoryItem", "inventory_item_id")
|
|
@@ -11,7 +11,6 @@ from requests.exceptions import ConnectionError, RequestException, SSLError
|
|
|
11
11
|
from airbyte_cdk.models import FailureType, SyncMode
|
|
12
12
|
from airbyte_cdk.sources import AbstractSource
|
|
13
13
|
from airbyte_cdk.sources.streams import Stream
|
|
14
|
-
from airbyte_cdk.sources.streams.http.exceptions import BaseBackoffException
|
|
15
14
|
from airbyte_cdk.utils import AirbyteTracedException
|
|
16
15
|
|
|
17
16
|
from .auth import MissingAccessTokenError, ShopifyAuthenticator
|
|
@@ -21,7 +20,6 @@ from .streams.streams import (
|
|
|
21
20
|
Articles,
|
|
22
21
|
BalanceTransactions,
|
|
23
22
|
Blogs,
|
|
24
|
-
CollectionProducts,
|
|
25
23
|
Collections,
|
|
26
24
|
Collects,
|
|
27
25
|
Countries,
|
|
@@ -110,8 +108,6 @@ class ConnectionCheckTest:
|
|
|
110
108
|
return False, self.describe_error("index_error", shop_name, response)
|
|
111
109
|
except MissingAccessTokenError:
|
|
112
110
|
return False, self.describe_error("missing_token_error")
|
|
113
|
-
except (BaseBackoffException, AirbyteTracedException) as error:
|
|
114
|
-
return False, self.describe_error("connection_error", shop_name) or str(error)
|
|
115
111
|
|
|
116
112
|
def get_shop_id(self) -> str:
|
|
117
113
|
"""
|
|
@@ -182,7 +178,6 @@ class SourceShopify(AbstractSource):
|
|
|
182
178
|
Articles(config),
|
|
183
179
|
BalanceTransactions(config),
|
|
184
180
|
Blogs(config),
|
|
185
|
-
CollectionProducts(config),
|
|
186
181
|
Collections(config),
|
|
187
182
|
Collects(config),
|
|
188
183
|
CustomCollections(config),
|
|
@@ -10,7 +10,6 @@ from typing import Any, Iterable, Mapping, MutableMapping, Optional
|
|
|
10
10
|
import requests
|
|
11
11
|
from source_shopify.shopify_graphql.bulk.query import (
|
|
12
12
|
Collection,
|
|
13
|
-
CollectionProduct,
|
|
14
13
|
CustomerAddresses,
|
|
15
14
|
CustomerJourney,
|
|
16
15
|
DeliveryProfile,
|
|
@@ -111,14 +110,50 @@ class Orders(IncrementalShopifyStreamWithDeletedEvents):
|
|
|
111
110
|
|
|
112
111
|
|
|
113
112
|
class Disputes(IncrementalShopifyStream):
|
|
113
|
+
"""
|
|
114
|
+
Disputes stream for Shopify Payments API.
|
|
115
|
+
|
|
116
|
+
Note: Uses 'initiated_at' as cursor field to ensure dispute status updates are captured
|
|
117
|
+
during incremental syncs. Previously used 'id' with 'since_id' filtering, which only
|
|
118
|
+
captured new disputes but missed updates to existing disputes (e.g., status changes
|
|
119
|
+
from 'needs_response' to 'won' or 'lost').
|
|
120
|
+
|
|
121
|
+
The Shopify API supports 'initiated_at' as a filter parameter, but only for exact date
|
|
122
|
+
matching (e.g., initiated_at=2013-05-03), not for range queries needed for incremental
|
|
123
|
+
sync. An alternative approach would be to iterate through dates and make multiple API
|
|
124
|
+
calls for each date, but this would be complex and inefficient. Instead, we leverage
|
|
125
|
+
the existing datetime-based client-side filtering that the connector already provides,
|
|
126
|
+
fetching all disputes ordered by 'initiated_at' and filtering them client-side.
|
|
127
|
+
|
|
128
|
+
API Reference: https://shopify.dev/docs/api/admin-rest/latest/resources/dispute
|
|
129
|
+
"""
|
|
130
|
+
|
|
114
131
|
data_field = "disputes"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
order_field = "id"
|
|
132
|
+
cursor_field = "initiated_at"
|
|
133
|
+
order_field = "initiated_at"
|
|
118
134
|
|
|
119
135
|
def path(self, **kwargs) -> str:
|
|
120
136
|
return f"shopify_payments/{self.data_field}.json"
|
|
121
137
|
|
|
138
|
+
def request_params(
|
|
139
|
+
self, stream_state: Optional[Mapping[str, Any]] = None, next_page_token: Optional[Mapping[str, Any]] = None, **kwargs
|
|
140
|
+
) -> MutableMapping[str, Any]:
|
|
141
|
+
"""
|
|
142
|
+
Override to exclude server-side filtering since the API only supports exact date matching,
|
|
143
|
+
not the range queries needed for incremental sync.
|
|
144
|
+
"""
|
|
145
|
+
params = ShopifyStream.request_params(self, stream_state=stream_state, next_page_token=next_page_token, **kwargs)
|
|
146
|
+
if not next_page_token:
|
|
147
|
+
params["order"] = f"{self.order_field} asc"
|
|
148
|
+
return params
|
|
149
|
+
|
|
150
|
+
def read_records(self, stream_state: Optional[Mapping[str, Any]] = None, **kwargs) -> Iterable[Mapping[str, Any]]:
|
|
151
|
+
"""
|
|
152
|
+
Override to apply client-side filtering based on initiated_at cursor field.
|
|
153
|
+
"""
|
|
154
|
+
records = super().read_records(stream_state=stream_state, **kwargs)
|
|
155
|
+
yield from self.filter_records_newer_than_state(stream_state=stream_state, records_slice=records)
|
|
156
|
+
|
|
122
157
|
|
|
123
158
|
class MetafieldOrders(IncrementalShopifyGraphQlBulkStream):
|
|
124
159
|
bulk_query: MetafieldOrder = MetafieldOrder
|
|
@@ -324,21 +359,6 @@ class MetafieldCollections(IncrementalShopifyGraphQlBulkStream):
|
|
|
324
359
|
bulk_query: MetafieldCollection = MetafieldCollection
|
|
325
360
|
|
|
326
361
|
|
|
327
|
-
class CollectionProducts(IncrementalShopifyGraphQlBulkStream):
|
|
328
|
-
"""
|
|
329
|
-
Stream that returns all products associated with each collection, including both
|
|
330
|
-
custom collections and smart collections. Unlike the Collects stream which only
|
|
331
|
-
returns manually associated products, this stream returns all products that belong
|
|
332
|
-
to a collection (including those matched by smart collection rules).
|
|
333
|
-
|
|
334
|
-
https://shopify.dev/docs/api/admin-graphql/latest/objects/Collection#field-Collection.fields.products
|
|
335
|
-
"""
|
|
336
|
-
|
|
337
|
-
bulk_query: CollectionProduct = CollectionProduct
|
|
338
|
-
cursor_field = "collection_updated_at"
|
|
339
|
-
primary_key = ["collection_id", "product_id"]
|
|
340
|
-
|
|
341
|
-
|
|
342
362
|
class BalanceTransactions(IncrementalShopifyStream):
|
|
343
363
|
"""
|
|
344
364
|
PaymentsTransactions stream does not support Incremental Refresh based on datetime fields, only `since_id` is supported:
|
airbyte_source_shopify-3.2.0.dev202602040050/source_shopify/schemas/collection_products.json
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"type": "object",
|
|
3
|
-
"additionalProperties": true,
|
|
4
|
-
"properties": {
|
|
5
|
-
"collection_id": {
|
|
6
|
-
"description": "The unique identifier for the collection.",
|
|
7
|
-
"type": ["null", "integer"]
|
|
8
|
-
},
|
|
9
|
-
"collection_admin_graphql_api_id": {
|
|
10
|
-
"description": "The Admin GraphQL API ID for the collection.",
|
|
11
|
-
"type": ["null", "string"]
|
|
12
|
-
},
|
|
13
|
-
"collection_handle": {
|
|
14
|
-
"description": "The handle (URL-friendly name) for the collection.",
|
|
15
|
-
"type": ["null", "string"]
|
|
16
|
-
},
|
|
17
|
-
"collection_updated_at": {
|
|
18
|
-
"description": "The date and time when the collection was last updated.",
|
|
19
|
-
"type": ["null", "string"],
|
|
20
|
-
"format": "date-time"
|
|
21
|
-
},
|
|
22
|
-
"product_id": {
|
|
23
|
-
"description": "The unique identifier for the product.",
|
|
24
|
-
"type": ["null", "integer"]
|
|
25
|
-
},
|
|
26
|
-
"product_admin_graphql_api_id": {
|
|
27
|
-
"description": "The Admin GraphQL API ID for the product.",
|
|
28
|
-
"type": ["null", "string"]
|
|
29
|
-
},
|
|
30
|
-
"shop_url": {
|
|
31
|
-
"description": "The URL of the shop associated with this collection-product association.",
|
|
32
|
-
"type": ["null", "string"]
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|