airbyte-source-shopify 3.1.2__tar.gz → 3.2.0__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.
Files changed (71) hide show
  1. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/PKG-INFO +1 -1
  2. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/pyproject.toml +1 -1
  3. airbyte_source_shopify-3.2.0/source_shopify/schemas/collection_products.json +35 -0
  4. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/scopes.py +1 -0
  5. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/shopify_graphql/bulk/query.py +108 -0
  6. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/source.py +2 -0
  7. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/streams/streams.py +16 -0
  8. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/README.md +0 -0
  9. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/__init__.py +0 -0
  10. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/auth.py +0 -0
  11. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/config_migrations.py +0 -0
  12. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/http_request.py +0 -0
  13. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/run.py +0 -0
  14. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/abandoned_checkouts.json +0 -0
  15. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/articles.json +0 -0
  16. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/balance_transactions.json +0 -0
  17. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/blogs.json +0 -0
  18. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/collections.json +0 -0
  19. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/collects.json +0 -0
  20. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/countries.json +0 -0
  21. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/custom_collections.json +0 -0
  22. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/customer_address.json +0 -0
  23. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/customer_journey_summary.json +0 -0
  24. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/customers.json +0 -0
  25. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/deleted_products.json +0 -0
  26. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/discount_codes.json +0 -0
  27. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/disputes.json +0 -0
  28. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/draft_orders.json +0 -0
  29. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/fulfillment_orders.json +0 -0
  30. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/fulfillments.json +0 -0
  31. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/inventory_items.json +0 -0
  32. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/inventory_levels.json +0 -0
  33. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/locations.json +0 -0
  34. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/metafield_articles.json +0 -0
  35. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/metafield_blogs.json +0 -0
  36. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/metafield_collections.json +0 -0
  37. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/metafield_customers.json +0 -0
  38. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/metafield_draft_orders.json +0 -0
  39. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/metafield_locations.json +0 -0
  40. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/metafield_orders.json +0 -0
  41. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/metafield_pages.json +0 -0
  42. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/metafield_product_images.json +0 -0
  43. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/metafield_product_variants.json +0 -0
  44. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/metafield_products.json +0 -0
  45. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/metafield_shops.json +0 -0
  46. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/metafield_smart_collections.json +0 -0
  47. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/order_agreements.json +0 -0
  48. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/order_refunds.json +0 -0
  49. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/order_risks.json +0 -0
  50. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/orders.json +0 -0
  51. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/pages.json +0 -0
  52. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/price_rules.json +0 -0
  53. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/product_images.json +0 -0
  54. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/product_variants.json +0 -0
  55. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/products.json +0 -0
  56. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/profile_location_groups.json +0 -0
  57. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/shop.json +0 -0
  58. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/smart_collections.json +0 -0
  59. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/tender_transactions.json +0 -0
  60. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/schemas/transactions.json +0 -0
  61. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/shopify_graphql/bulk/__init__.py +0 -0
  62. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/shopify_graphql/bulk/exceptions.py +0 -0
  63. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/shopify_graphql/bulk/job.py +0 -0
  64. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/shopify_graphql/bulk/record.py +0 -0
  65. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/shopify_graphql/bulk/retry.py +0 -0
  66. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/shopify_graphql/bulk/status.py +0 -0
  67. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/shopify_graphql/bulk/tools.py +0 -0
  68. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/spec.json +0 -0
  69. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/streams/base_streams.py +0 -0
  70. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/transform.py +0 -0
  71. {airbyte_source_shopify-3.1.2 → airbyte_source_shopify-3.2.0}/source_shopify/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-source-shopify
3
- Version: 3.1.2
3
+ Version: 3.2.0
4
4
  Summary: Source CDK implementation for Shopify.
5
5
  Home-page: https://airbyte.com
6
6
  License: ELv2
@@ -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.1.2"
6
+ version = "3.2.0"
7
7
  name = "airbyte-source-shopify"
8
8
  description = "Source CDK implementation for Shopify."
9
9
  authors = [ "Airbyte <contact@airbyte.io>",]
@@ -0,0 +1,35 @@
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
+ }
@@ -44,6 +44,7 @@ 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",),
47
48
  "ProductVariants": ("read_products", "read_inventory"),
48
49
  "MetafieldCollections": ("read_products",),
49
50
  "SmartCollections": ("read_products",),
@@ -952,6 +952,114 @@ 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
+
955
1063
  class CustomerAddresses(ShopifyBulkQuery):
956
1064
  """
957
1065
  {
@@ -21,6 +21,7 @@ from .streams.streams import (
21
21
  Articles,
22
22
  BalanceTransactions,
23
23
  Blogs,
24
+ CollectionProducts,
24
25
  Collections,
25
26
  Collects,
26
27
  Countries,
@@ -181,6 +182,7 @@ class SourceShopify(AbstractSource):
181
182
  Articles(config),
182
183
  BalanceTransactions(config),
183
184
  Blogs(config),
185
+ CollectionProducts(config),
184
186
  Collections(config),
185
187
  Collects(config),
186
188
  CustomCollections(config),
@@ -10,6 +10,7 @@ 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,
13
14
  CustomerAddresses,
14
15
  CustomerJourney,
15
16
  DeliveryProfile,
@@ -323,6 +324,21 @@ class MetafieldCollections(IncrementalShopifyGraphQlBulkStream):
323
324
  bulk_query: MetafieldCollection = MetafieldCollection
324
325
 
325
326
 
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
+
326
342
  class BalanceTransactions(IncrementalShopifyStream):
327
343
  """
328
344
  PaymentsTransactions stream does not support Incremental Refresh based on datetime fields, only `since_id` is supported: