From fdc155e64bcc86504834b1a01d0871232b7c5e08 Mon Sep 17 00:00:00 2001 From: Elizabeth Kenyon Date: Tue, 24 Sep 2024 13:36:25 -0500 Subject: [PATCH 01/15] Make API version more flexible Allow the use of API versions that are not previsouly defined --- CHANGELOG | 1 + shopify/api_version.py | 4 ++++ test/api_version_test.py | 14 ++++++++++++++ test/session_test.py | 13 +++++++++++++ 4 files changed, 32 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index cd636df3..5a5ed5db 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,5 @@ == Unreleased +- Remove requirement to use a predefined API version. Now you can use any valid API version string. ([#737](https://github.com/Shopify/shopify_python_api/pull/737)) == Version 12.6.0 diff --git a/shopify/api_version.py b/shopify/api_version.py index 22df6052..32276668 100644 --- a/shopify/api_version.py +++ b/shopify/api_version.py @@ -17,6 +17,9 @@ def coerce_to_version(cls, version): try: return cls.versions[version] except KeyError: + # Dynamically create a new Release object if version string is not found + if Release.FORMAT.match(version): + return Release(version) raise VersionNotFoundError @classmethod @@ -39,6 +42,7 @@ def define_known_versions(cls): cls.define_version(Release("2024-01")) cls.define_version(Release("2024-04")) cls.define_version(Release("2024-07")) + cls.define_version(Release("2024-10")) @classmethod def clear_defined_versions(cls): diff --git a/test/api_version_test.py b/test/api_version_test.py index 3089daee..9dce8cb2 100644 --- a/test/api_version_test.py +++ b/test/api_version_test.py @@ -29,6 +29,20 @@ def test_coerce_to_version_raises_with_string_that_does_not_match_known_version( with self.assertRaises(shopify.VersionNotFoundError): shopify.ApiVersion.coerce_to_version("crazy-name") + def test_coerce_to_version_creates_new_release_on_the_fly(self): + new_version = "2025-01" + coerced_version = shopify.ApiVersion.coerce_to_version(new_version) + + self.assertIsInstance(coerced_version, shopify.Release) + self.assertEqual(coerced_version.name, new_version) + self.assertEqual( + coerced_version.api_path("https://test.myshopify.com"), + f"https://test.myshopify.com/admin/api/{new_version}", + ) + + # Verify that the new version is not added to the known versions + self.assertNotIn(new_version, shopify.ApiVersion.versions) + class ReleaseTest(TestCase): def test_raises_if_format_invalid(self): diff --git a/test/session_test.py b/test/session_test.py index 806d551b..d7cd5c3d 100644 --- a/test/session_test.py +++ b/test/session_test.py @@ -288,3 +288,16 @@ def normalize_url(self, url): scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url) query = "&".join(sorted(query.split("&"))) return urllib.parse.urlunsplit((scheme, netloc, path, query, fragment)) + + def test_session_with_coerced_version(self): + future_version = "2030-01" + session = shopify.Session("test.myshopify.com", future_version, "token") + self.assertEqual(session.api_version.name, future_version) + self.assertEqual( + session.api_version.api_path("https://test.myshopify.com"), + f"https://test.myshopify.com/admin/api/{future_version}", + ) + + def test_session_with_invalid_version(self): + with self.assertRaises(shopify.VersionNotFoundError): + shopify.Session("test.myshopify.com", "invalid-version", "token") From 7229004441645b1bc0a4c848cc1a6593fc9d6281 Mon Sep 17 00:00:00 2001 From: Matteo Depalo Date: Wed, 30 Oct 2024 10:46:27 +0000 Subject: [PATCH 02/15] Release v12.7.0 --- CHANGELOG | 3 +++ shopify/version.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5a5ed5db..50cae06e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,7 @@ == Unreleased + +== Version 12.7.0 + - Remove requirement to use a predefined API version. Now you can use any valid API version string. ([#737](https://github.com/Shopify/shopify_python_api/pull/737)) == Version 12.6.0 diff --git a/shopify/version.py b/shopify/version.py index 7293b298..126c3ab4 100644 --- a/shopify/version.py +++ b/shopify/version.py @@ -1 +1 @@ -VERSION = "12.6.0" +VERSION = "12.7.0" From 9451fe5f45c6d809787384e9ceaa294fec1b66c6 Mon Sep 17 00:00:00 2001 From: "yuichi.nasukawa" Date: Wed, 8 Jan 2025 22:01:30 +0900 Subject: [PATCH 03/15] Add REST API deprecation notice to README --- README.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a8fa6d74..6226bcb1 100644 --- a/README.md +++ b/README.md @@ -87,10 +87,11 @@ pip install --upgrade ShopifyAPI session = shopify.Session(shop_url, api_version, access_token) shopify.ShopifyResource.activate_session(session) - shop = shopify.Shop.current() # Get the current shop - product = shopify.Product.find(179761209) # Get a specific product + # Note: REST API examples will be deprecated in 2025 + shop = shopify.Shop.current() # Get the current shop + product = shopify.Product.find(179761209) # Get a specific product - # execute a graphQL call + # GraphQL API example shopify.GraphQL().execute("{ shop { name id } }") ``` @@ -150,6 +151,13 @@ _Note: Your application must be public to test the billing process. To test on a ``` ### Advanced Usage + +> **⚠️ Note**: As of October 1, 2024, the REST Admin API is legacy: +> - Public apps must migrate to GraphQL by February 2025 +> - Custom apps must migrate to GraphQL by April 2025 +> +> For migration guidance, see [Shopify's migration guide](https://shopify.dev/docs/apps/build/graphql/migrate/new-product-model) + It is recommended to have at least a basic grasp on the principles of the [pyactiveresource](https://github.com/Shopify/pyactiveresource) library, which is a port of rails/ActiveResource to Python and upon which this package relies heavily. Instances of `pyactiveresource` resources map to RESTful resources in the Shopify API. @@ -157,6 +165,7 @@ Instances of `pyactiveresource` resources map to RESTful resources in the Shopif `pyactiveresource` exposes life cycle methods for creating, finding, updating, and deleting resources which are equivalent to the `POST`, `GET`, `PUT`, and `DELETE` HTTP verbs. ```python +# Note: REST API examples will be deprecated in 2025 product = shopify.Product() product.title = "Shopify Logo T-Shirt" product.id # => 292082188312 @@ -182,6 +191,7 @@ new_orders = shopify.Order.find(status="open", limit="50") Some resources such as `Fulfillment` are prefixed by a parent resource in the Shopify API (e.g. `orders/450789469/fulfillments/255858046`). In order to interact with these resources, you must specify the identifier of the parent resource in your request. ```python +# Note: This REST API example will be deprecated in the future shopify.Fulfillment.find(255858046, order_id=450789469) ``` @@ -196,6 +206,9 @@ This package also includes the `shopify_api.py` script to make it easy to open a This library also supports Shopify's new [GraphQL API](https://help.shopify.com/en/api/graphql-admin-api). The authentication process is identical. Once your session is activated, simply construct a new graphql client and use `execute` to execute the query. +> **Note**: Shopify recommends using GraphQL API for new development as REST API will be deprecated. +> See [Migration Guide](https://shopify.dev/docs/apps/build/graphql/migrate/new-product-model) for more details. + ```python result = shopify.GraphQL().execute('{ shop { name id } }') ``` From 024d94691ed889d047704d2f1615f40a03cc02ac Mon Sep 17 00:00:00 2001 From: Tyler J Date: Sat, 11 Jan 2025 13:31:05 -0500 Subject: [PATCH 04/15] Refactor `create_permission_url` to handle optional `scope`. Modified `create_permission_url` to make `scope` optional, allowing it to be omitted when specified in the app's configuration (TOML). Updated the README to reflect this change and clarify usage. This improves flexibility and simplifies configuration management. --- README.md | 4 +++- shopify/session.py | 9 +++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a8fa6d74..0b680d07 100644 --- a/README.md +++ b/README.md @@ -66,10 +66,12 @@ pip install --upgrade ShopifyAPI api_version = '2024-07' state = binascii.b2a_hex(os.urandom(15)).decode("utf-8") redirect_uri = "http://myapp.com/auth/shopify/callback" + # `scope` should be omitted if provided by app's TOML scopes = ['read_products', 'read_orders'] newSession = shopify.Session(shop_url, api_version) - auth_url = newSession.create_permission_url(scopes, redirect_uri, state) + # `scope` should be omitted if provided by app's TOML + auth_url = newSession.create_permission_url(redirect_uri, scopes, state) # redirect to auth_url ``` diff --git a/shopify/session.py b/shopify/session.py index 39ce5f7b..52dc83f4 100644 --- a/shopify/session.py +++ b/shopify/session.py @@ -53,10 +53,11 @@ def __init__(self, shop_url, version=None, token=None, access_scopes=None): self.access_scopes = access_scopes return - def create_permission_url(self, scope, redirect_uri, state=None): - query_params = dict(client_id=self.api_key, scope=",".join(scope), redirect_uri=redirect_uri) - if state: - query_params["state"] = state + def create_permission_url(self, redirect_uri, scope=None, state=None): + query_params = dict(client_id=self.api_key, redirect_uri=redirect_uri) + # `scope` should be omitted if provided by app's TOML + if scope: query_params["scope"] = ",".join(scope) + if state: query_params["state"] = state return "https://%s/admin/oauth/authorize?%s" % (self.url, urllib.parse.urlencode(query_params)) def request_token(self, params): From cdaa10d2d6a82d44652468d0d78aba537df08baa Mon Sep 17 00:00:00 2001 From: "yuichi.nasukawa" Date: Sun, 12 Jan 2025 08:40:56 +0900 Subject: [PATCH 05/15] chore: upgrade pre-commit dependencies --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 43267923..f3500257 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,15 +2,15 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v5.0.0 hooks: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 24.10.0 hooks: - id: black - repo: https://github.com/PyCQA/pylint - rev: v2.15.8 + rev: v3.3.3 hooks: - id: pylint From 7b044441d59fe9087abc02554cbf267fb9a6d923 Mon Sep 17 00:00:00 2001 From: "yuichi.nasukawa" Date: Sun, 12 Jan 2025 08:44:30 +0900 Subject: [PATCH 06/15] fix: resolve new pylint warnings - Fix warnings introduced by pylint v3.3.3 upgrade --- scripts/shopify_api.py | 2 +- shopify/api_access.py | 1 - shopify/mixins.py | 2 +- shopify/session.py | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/shopify_api.py b/scripts/shopify_api.py index 5dfab93a..bab35f15 100755 --- a/scripts/shopify_api.py +++ b/scripts/shopify_api.py @@ -128,7 +128,7 @@ def add(cls, connection): if os.path.exists(filename): raise ConfigFileError("There is already a config file at " + filename) else: - config = dict(protocol="https") + config = {"protocol": "https"} domain = input("Domain? (leave blank for %s.myshopify.com) " % (connection)) if not domain.strip(): domain = "%s.myshopify.com" % (connection) diff --git a/shopify/api_access.py b/shopify/api_access.py index d5ffbe35..19b80671 100644 --- a/shopify/api_access.py +++ b/shopify/api_access.py @@ -14,7 +14,6 @@ class ApiAccessError(Exception): class ApiAccess: - SCOPE_DELIMITER = "," SCOPE_RE = re.compile(r"\A(?Punauthenticated_)?(write|read)_(?P.*)\Z") IMPLIED_SCOPE_RE = re.compile(r"\A(?Punauthenticated_)?write_(?P.*)\Z") diff --git a/shopify/mixins.py b/shopify/mixins.py index 54496dbf..5a13ca3a 100644 --- a/shopify/mixins.py +++ b/shopify/mixins.py @@ -24,7 +24,7 @@ def add_metafield(self, metafield): if self.is_new(): raise ValueError("You can only add metafields to a resource that has been saved") - metafield._prefix_options = dict(resource=self.__class__.plural, resource_id=self.id) + metafield._prefix_options = {"resource": self.__class__.plural, "resource_id": self.id} metafield.save() return metafield diff --git a/shopify/session.py b/shopify/session.py index 39ce5f7b..c3ec6d4b 100644 --- a/shopify/session.py +++ b/shopify/session.py @@ -54,7 +54,7 @@ def __init__(self, shop_url, version=None, token=None, access_scopes=None): return def create_permission_url(self, scope, redirect_uri, state=None): - query_params = dict(client_id=self.api_key, scope=",".join(scope), redirect_uri=redirect_uri) + query_params = {"client_id": self.api_key, "scope": ",".join(scope), "redirect_uri": redirect_uri} if state: query_params["state"] = state return "https://%s/admin/oauth/authorize?%s" % (self.url, urllib.parse.urlencode(query_params)) @@ -69,7 +69,7 @@ def request_token(self, params): code = params["code"] url = "https://%s/admin/oauth/access_token?" % self.url - query_params = dict(client_id=self.api_key, client_secret=self.secret, code=code) + query_params = {"client_id": self.api_key, "client_secret": self.secret, "code": code} request = urllib.request.Request(url, urllib.parse.urlencode(query_params).encode("utf-8")) response = urllib.request.urlopen(request) From ceada2433b004365b7d9f74669b9c1067e299ccb Mon Sep 17 00:00:00 2001 From: Tyler J Date: Sun, 12 Jan 2025 11:33:31 -0500 Subject: [PATCH 07/15] Update to version 12.7.1 and update the CHANGELOG. --- CHANGELOG | 2 ++ shopify/version.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 50cae06e..e9910c2e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ == Unreleased +- Remove requirement to provide scopes to Permission URL, as it should be omitted if defined with the TOML file. + == Version 12.7.0 - Remove requirement to use a predefined API version. Now you can use any valid API version string. ([#737](https://github.com/Shopify/shopify_python_api/pull/737)) diff --git a/shopify/version.py b/shopify/version.py index 126c3ab4..dfb0b4e4 100644 --- a/shopify/version.py +++ b/shopify/version.py @@ -1 +1 @@ -VERSION = "12.7.0" +VERSION = "12.7.1" From 07d6c47146e00c35bb39a83d86033f9b250623af Mon Sep 17 00:00:00 2001 From: Tyler J Date: Sun, 12 Jan 2025 11:48:02 -0500 Subject: [PATCH 08/15] Fix typo in method signature of create_permission_url Removed an unnecessary extra space in the method signature of `create_permission_url`. --- shopify/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shopify/session.py b/shopify/session.py index 52dc83f4..eec40517 100644 --- a/shopify/session.py +++ b/shopify/session.py @@ -53,7 +53,7 @@ def __init__(self, shop_url, version=None, token=None, access_scopes=None): self.access_scopes = access_scopes return - def create_permission_url(self, redirect_uri, scope=None, state=None): + def create_permission_url(self, redirect_uri, scope=None, state=None): query_params = dict(client_id=self.api_key, redirect_uri=redirect_uri) # `scope` should be omitted if provided by app's TOML if scope: query_params["scope"] = ",".join(scope) From adaf770a0b5a99ae791048967f39d89e13ea500f Mon Sep 17 00:00:00 2001 From: Tyler J Date: Fri, 17 Jan 2025 14:28:20 -0500 Subject: [PATCH 09/15] Update tests for `create_permission_url` method. Updated tests to improve clarity and consistency in naming and arguments. Modified `create_permission_url` calls to match new positional order for `redirect_uri` and `scope`. Enhanced assertion coverage for edge cases like empty scopes and added tests for state parameter handling. --- test/session_test.py | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/test/session_test.py b/test/session_test.py index d7cd5c3d..04d30748 100644 --- a/test/session_test.py +++ b/test/session_test.py @@ -86,51 +86,69 @@ def test_temp_works_without_currently_active_session(self): self.assertEqual("https://testshop.myshopify.com/admin/api/unstable", assigned_site) self.assertEqual("https://none/admin/api/unstable", shopify.ShopifyResource.site) - def test_create_permission_url_returns_correct_url_with_single_scope_and_redirect_uri(self): + def test_create_permission_url_returns_correct_url_with_redirect_uri(self): + shopify.Session.setup(api_key="My_test_key", secret="My test secret") + session = shopify.Session("http://localhost.myshopify.com", "unstable") + permission_url = session.create_permission_url("my_redirect_uri.com") + self.assertEqual( + "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&redirect_uri=my_redirect_uri.com", + self.normalize_url(permission_url), + ) + + def test_create_permission_url_returns_correct_url_with_redirect_uri_and_single_scope(self): shopify.Session.setup(api_key="My_test_key", secret="My test secret") session = shopify.Session("http://localhost.myshopify.com", "unstable") scope = ["write_products"] - permission_url = session.create_permission_url(scope, "my_redirect_uri.com") + permission_url = session.create_permission_url("my_redirect_uri.com", scope=scope) self.assertEqual( "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&redirect_uri=my_redirect_uri.com&scope=write_products", self.normalize_url(permission_url), ) - def test_create_permission_url_returns_correct_url_with_dual_scope_and_redirect_uri(self): + def test_create_permission_url_returns_correct_url_with_redirect_uri_and_dual_scope(self): shopify.Session.setup(api_key="My_test_key", secret="My test secret") session = shopify.Session("http://localhost.myshopify.com", "unstable") scope = ["write_products", "write_customers"] - permission_url = session.create_permission_url(scope, "my_redirect_uri.com") + permission_url = session.create_permission_url("my_redirect_uri.com", scope=scope) self.assertEqual( "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&redirect_uri=my_redirect_uri.com&scope=write_products%2Cwrite_customers", self.normalize_url(permission_url), ) - def test_create_permission_url_returns_correct_url_with_no_scope_and_redirect_uri(self): + def test_create_permission_url_returns_correct_url_with_redirect_uri_and_empty_scope(self): shopify.Session.setup(api_key="My_test_key", secret="My test secret") session = shopify.Session("http://localhost.myshopify.com", "unstable") scope = [] - permission_url = session.create_permission_url(scope, "my_redirect_uri.com") + permission_url = session.create_permission_url("my_redirect_uri.com", scope=scope) + self.assertEqual( + "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&redirect_uri=my_redirect_uri.com", + self.normalize_url(permission_url), + ) + + def test_create_permission_url_returns_correct_url_with_redirect_uri_and_state(self): + shopify.Session.setup(api_key="My_test_key", secret="My test secret") + session = shopify.Session("http://localhost.myshopify.com", "unstable") + permission_url = session.create_permission_url("my_redirect_uri.com", state="mystate") self.assertEqual( - "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&redirect_uri=my_redirect_uri.com&scope=", + "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&redirect_uri=my_redirect_uri.com&state=mystate", self.normalize_url(permission_url), ) - def test_create_permission_url_returns_correct_url_with_no_scope_and_redirect_uri_and_state(self): + def test_create_permission_url_returns_correct_url_with_redirect_uri_empty_scope_and_state(self): shopify.Session.setup(api_key="My_test_key", secret="My test secret") session = shopify.Session("http://localhost.myshopify.com", "unstable") scope = [] - permission_url = session.create_permission_url(scope, "my_redirect_uri.com", state="mystate") + permission_url = session.create_permission_url("my_redirect_uri.com", scope=scope, state="mystate") self.assertEqual( - "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&redirect_uri=my_redirect_uri.com&scope=&state=mystate", + "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&redirect_uri=my_redirect_uri.com&state=mystate", self.normalize_url(permission_url), ) - def test_create_permission_url_returns_correct_url_with_single_scope_and_redirect_uri_and_state(self): + def test_create_permission_url_returns_correct_url_with_redirect_uri_and_single_scope_and_state(self): shopify.Session.setup(api_key="My_test_key", secret="My test secret") session = shopify.Session("http://localhost.myshopify.com", "unstable") scope = ["write_customers"] - permission_url = session.create_permission_url(scope, "my_redirect_uri.com", state="mystate") + permission_url = session.create_permission_url( "my_redirect_uri.com", scope=scope, state="mystate") self.assertEqual( "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&redirect_uri=my_redirect_uri.com&scope=write_customers&state=mystate", self.normalize_url(permission_url), From 12e933ba9cadd2ba5b834fe5143cb70438e9e34b Mon Sep 17 00:00:00 2001 From: Tyler J Date: Mon, 20 Jan 2025 08:32:20 -0500 Subject: [PATCH 10/15] Fix linting errors Removes extra white space in parameters in session_test.py and changes conditional formatting in shopify/session.py. --- shopify/session.py | 8 +++++--- test/session_test.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/shopify/session.py b/shopify/session.py index eec40517..dcb41d41 100644 --- a/shopify/session.py +++ b/shopify/session.py @@ -54,10 +54,12 @@ def __init__(self, shop_url, version=None, token=None, access_scopes=None): return def create_permission_url(self, redirect_uri, scope=None, state=None): - query_params = dict(client_id=self.api_key, redirect_uri=redirect_uri) + query_params = {"client_id": self.api_key, "redirect_uri": redirect_uri} # `scope` should be omitted if provided by app's TOML - if scope: query_params["scope"] = ",".join(scope) - if state: query_params["state"] = state + if scope: + query_params["scope"] = ",".join(scope) + if state: + query_params["state"] = state return "https://%s/admin/oauth/authorize?%s" % (self.url, urllib.parse.urlencode(query_params)) def request_token(self, params): diff --git a/test/session_test.py b/test/session_test.py index 04d30748..8d73e293 100644 --- a/test/session_test.py +++ b/test/session_test.py @@ -148,7 +148,7 @@ def test_create_permission_url_returns_correct_url_with_redirect_uri_and_single_ shopify.Session.setup(api_key="My_test_key", secret="My test secret") session = shopify.Session("http://localhost.myshopify.com", "unstable") scope = ["write_customers"] - permission_url = session.create_permission_url( "my_redirect_uri.com", scope=scope, state="mystate") + permission_url = session.create_permission_url("my_redirect_uri.com", scope=scope, state="mystate") self.assertEqual( "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&redirect_uri=my_redirect_uri.com&scope=write_customers&state=mystate", self.normalize_url(permission_url), From 2f998c691bd15608603ef25e31238f6c22abc544 Mon Sep 17 00:00:00 2001 From: Tyler J Date: Mon, 20 Jan 2025 08:40:04 -0500 Subject: [PATCH 11/15] Fix linting errors Removes extra white space in parameters in session_test.py and changes conditional formatting in shopify/session.py. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8378880..cadda24e 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ _Note: Your application must be public to test the billing process. To test on a > **⚠️ Note**: As of October 1, 2024, the REST Admin API is legacy: > - Public apps must migrate to GraphQL by February 2025 > - Custom apps must migrate to GraphQL by April 2025 -> +> > For migration guidance, see [Shopify's migration guide](https://shopify.dev/docs/apps/build/graphql/migrate/new-product-model) It is recommended to have at least a basic grasp on the principles of the [pyactiveresource](https://github.com/Shopify/pyactiveresource) library, which is a port of rails/ActiveResource to Python and upon which this package relies heavily. From cd0463d5df5de9dc62574ba20b8b4fc1a397ce29 Mon Sep 17 00:00:00 2001 From: Richard Powell Date: Mon, 9 Feb 2026 14:27:40 -0500 Subject: [PATCH 12/15] Add a CTA to the new package --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cadda24e..1b91703c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +**Note:** We've released a new experimental package for Python. Please read [rethinking our support for PHP & Python packages](https://community.shopify.dev/t/rethinking-support-for-php-python-packages/28325). The new Python package supports the latest Shopify platform features and we'd love your feedback. Please see [shopifyapp](https://pypi.org/project/shopifyapp/) to get started. + # Shopify API [![Build Status](https://github.com/Shopify/shopify_python_api/workflows/CI/badge.svg)](https://github.com/Shopify/shopify_python_api/actions) From b491b5271c5379e0b5611f5829ede2b5ba7e080f Mon Sep 17 00:00:00 2001 From: Richard Powell Date: Thu, 12 Mar 2026 14:56:06 -0400 Subject: [PATCH 13/15] Update CODEOWNERS to @shopify/app-inner-loop --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..bf616977 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @shopify/app-inner-loop From e554dbf2afab1f499933077b0b767f303b33dcbe Mon Sep 17 00:00:00 2001 From: Richard Powell Date: Fri, 13 Mar 2026 09:00:44 -0400 Subject: [PATCH 14/15] Update CODEOWNERS to @shop/dev_experience --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bf616977..5cccacf2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @shopify/app-inner-loop +* @shop/dev_experience From e47515ad8881b6893e5d615941a9cdc94156076a Mon Sep 17 00:00:00 2001 From: Kaiyi Li Date: Mon, 27 Apr 2026 11:27:29 -0700 Subject: [PATCH 15/15] pin github actions to SHAs --- .github/workflows/api_update_reminder.yml | 2 +- .github/workflows/api_update_reminder_on_release.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/close-waiting-for-response-issues.yml | 2 +- .github/workflows/pre-commit.yml | 2 +- .github/workflows/remove-labels-on-activity.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/api_update_reminder.yml b/.github/workflows/api_update_reminder.yml index b4cfb456..f7f21218 100644 --- a/.github/workflows/api_update_reminder.yml +++ b/.github/workflows/api_update_reminder.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: JasonEtco/create-an-issue@v2.4.0 + - uses: JasonEtco/create-an-issue@e6b4b190af80961b6462c725454e7828d0247a68 # v2.4.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/api_update_reminder_on_release.yml b/.github/workflows/api_update_reminder_on_release.yml index 5e3053f9..f75dbcdc 100644 --- a/.github/workflows/api_update_reminder_on_release.yml +++ b/.github/workflows/api_update_reminder_on_release.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: JasonEtco/create-an-issue@v2.4.0 + - uses: JasonEtco/create-an-issue@e6b4b190af80961b6462c725454e7828d0247a68 # v2.4.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0cd5ab9..d706f9e6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: - name: Run Tests run: python -m pytest -v - name: upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@29386c70ef20e286228c72b668a06fd0e8399192 # v1 with: name: codecov-umbrella fail_ci_if_error: false diff --git a/.github/workflows/close-waiting-for-response-issues.yml b/.github/workflows/close-waiting-for-response-issues.yml index ffd7a382..4f9bf665 100644 --- a/.github/workflows/close-waiting-for-response-issues.yml +++ b/.github/workflows/close-waiting-for-response-issues.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - name: close-issues - uses: actions-cool/issues-helper@v3 + uses: actions-cool/issues-helper@200c78641dbf33838311e5a1e0c31bbdb92d7cf0 # v3 with: actions: 'close-issues' token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index f92848d7..d596200f 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -14,4 +14,4 @@ jobs: - name: Setup uses: actions/setup-python@v4 - name: Pre-commit - uses: pre-commit/action@v2.0.0 + uses: pre-commit/action@0764670bf370aab253130d534e1eda7ff497dc60 # v2.0.0 diff --git a/.github/workflows/remove-labels-on-activity.yml b/.github/workflows/remove-labels-on-activity.yml index 948801e7..8c08db6f 100644 --- a/.github/workflows/remove-labels-on-activity.yml +++ b/.github/workflows/remove-labels-on-activity.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-ecosystem/action-remove-labels@v1 + - uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1 if: contains(github.event.issue.labels.*.name, 'Waiting for Response') with: labels: |