In the first 2 commits, this PR attempts to correct the schema and urls used in commercetools.testing.product_projections's ProductProjectionsBackend.search() to squash this error.
In the last 2 commits, this PR proposes minor changes to the coverage GitHub Action job to squash bugs from the breaking changes in tooling dependencies, such as one in actions/upload-artifact@3 to actions/upload-artifact@4 of not being able to upload artifacts with the same name.
Additional Information
See commit messages for detailed explanation of changes.
Full error logs that prompted change are below. (But see commit messages first.)
Output of error resolved by a5b5f273d259cfde32e1620c5f32c2b04d2264c8
Note in the full logs below how this mocker matches to to `get_by_id()` instead of `search()`:
```
<...>
../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/product_projections.py:85: in get_by_id
params = utils.parse_request_params(_ProductProjectionQuerySchema, request)
<...>
```
Full logs, starting with where `search()` is called:
```
% pytest
<...truncated...>
commerce_coordinator/apps/commercetools/clients.py:240: in get_product_variant_by_course_run
results = self.base_client.product_projections.search(False, filter=f"variants.sku:\"{cr_id}\"").results
../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/services/product_projections.py:294: in search
return self._client._get(
../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/client.py:36: in _get
response = self._http_client.get(self._base_url + endpoint, params=params)
../../.virtualenvs/workarea/lib/python3.8/site-packages/requests/sessions.py:602: in get
return self.request("GET", url, **kwargs)
../../.virtualenvs/workarea/lib/python3.8/site-packages/requests_oauthlib/oauth2_session.py:521: in request
return super(OAuth2Session, self).request(
../../.virtualenvs/workarea/lib/python3.8/site-packages/requests/sessions.py:589: in request
resp = self.send(prep, **send_kwargs)
../../.virtualenvs/workarea/lib/python3.8/site-packages/requests_mock/mocker.py:185: in _fake_send
return _original_send(session, request, **kwargs)
../../.virtualenvs/workarea/lib/python3.8/site-packages/requests/sessions.py:703: in send
r = adapter.send(request, **kwargs)
../../.virtualenvs/workarea/lib/python3.8/site-packages/requests_mock/adapter.py:248: in send
resp = matcher(request)
../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/abstract.py:174: in _matcher
response = callback(request, **path_match.groupdict())
../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/product_projections.py:85: in get_by_id
params = utils.parse_request_params(_ProductProjectionQuerySchema, request)
../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/utils.py:32: in parse_request_params
obj = schema().load(params)
../../.virtualenvs/workarea/lib/python3.8/site-packages/marshmallow/schema.py:722: in load
return self._do_load(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <_ProductProjectionQuerySchema(many=False)>
data = {'filter': 'variants.sku:"course-v1:michiganx+injurypreventionx+1t2021"', 'markmatchingvariants': 'false', 'predicate_var': {}}
def _do_load(
self,
data: (
typing.Mapping[str, typing.Any]
| typing.Iterable[typing.Mapping[str, typing.Any]]
),
*,
many: bool | None = None,
partial: bool | types.StrSequenceOrSet | None = None,
unknown: str | None = None,
postprocess: bool = True,
):
"""Deserialize `data`, returning the deserialized result.
This method is private API.
:param data: The data to deserialize.
:param many: Whether to deserialize `data` as a collection. If `None`, the
value for `self.many` is used.
:param partial: Whether to validate required fields. If its
value is an iterable, only fields listed in that iterable will be
ignored will be allowed missing. If `True`, all fields will be allowed missing.
If `None`, the value for `self.partial` is used.
:param unknown: Whether to exclude, include, or raise an error for unknown
fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.
If `None`, the value for `self.unknown` is used.
:param postprocess: Whether to run post_load methods..
:return: Deserialized data
"""
error_store = ErrorStore()
errors = {} # type: dict[str, list[str]]
many = self.many if many is None else bool(many)
unknown = (
self.unknown
if unknown is None
else validate_unknown_parameter_value(unknown)
)
if partial is None:
partial = self.partial
# Run preprocessors
if self._has_processors(PRE_LOAD):
try:
processed_data = self._invoke_load_processors(
PRE_LOAD, data, many=many, original_data=data, partial=partial
)
except ValidationError as err:
errors = err.normalized_messages()
result = None # type: list | dict | None
else:
processed_data = data
if not errors:
# Deserialize data
result = self._deserialize(
processed_data,
error_store=error_store,
many=many,
partial=partial,
unknown=unknown,
)
# Run field-level validation
self._invoke_field_validators(
error_store=error_store, data=result, many=many
)
# Run schema-level validation
if self._has_processors(VALIDATES_SCHEMA):
field_errors = bool(error_store.errors)
self._invoke_schema_validators(
error_store=error_store,
pass_many=True,
data=result,
original_data=data,
many=many,
partial=partial,
field_errors=field_errors,
)
self._invoke_schema_validators(
error_store=error_store,
pass_many=False,
data=result,
original_data=data,
many=many,
partial=partial,
field_errors=field_errors,
)
errors = error_store.errors
# Run post processors
if not errors and postprocess and self._has_processors(POST_LOAD):
try:
result = self._invoke_load_processors(
POST_LOAD,
result,
many=many,
original_data=data,
partial=partial,
)
except ValidationError as err:
errors = err.normalized_messages()
if errors:
exc = ValidationError(errors, data=data, valid_data=result)
self.handle_error(exc, data, many=many, partial=partial)
> raise exc
E marshmallow.exceptions.ValidationError: {'filter': ['Unknown field.'], 'markmatchingvariants': ['Unknown field.']}
../../.virtualenvs/workarea/lib/python3.8/site-packages/marshmallow/schema.py:909: ValidationError
```
Output of error resolved by 59c762312d71732d5b8de5e67767c6d056ab0e49
Note in full logs below that now call is to `search()`:
```
<...>
../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/product_projections.py:57: in search
params = utils.parse_request_params(_ProductProjectionQuerySchema, request)
<...>
```
but `_ProductProjectionQuerySchema` is used instead of `_ProductProjectionSearchSchema`:
```
<...>
../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/product_projections.py:57: in search
params = utils.parse_request_params(_ProductProjectionQuerySchema, request)
<...>
```
Full logs:
```
% pytest
<...truncated...>
commerce_coordinator/apps/commercetools/clients.py:240: in get_product_variant_by_course_run
results = self.base_client.product_projections.search(False, filter=f"variants.sku:\"{cr_id}\"").results
../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/services/product_projections.py:294: in search
return self._client._get(
../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/client.py:36: in _get
response = self._http_client.get(self._base_url + endpoint, params=params)
../../.virtualenvs/workarea/lib/python3.8/site-packages/requests/sessions.py:602: in get
return self.request("GET", url, **kwargs)
../../.virtualenvs/workarea/lib/python3.8/site-packages/requests_oauthlib/oauth2_session.py:521: in request
return super(OAuth2Session, self).request(
../../.virtualenvs/workarea/lib/python3.8/site-packages/requests/sessions.py:589: in request
resp = self.send(prep, **send_kwargs)
../../.virtualenvs/workarea/lib/python3.8/site-packages/requests_mock/mocker.py:185: in _fake_send
return _original_send(session, request, **kwargs)
../../.virtualenvs/workarea/lib/python3.8/site-packages/requests/sessions.py:703: in send
r = adapter.send(request, **kwargs)
../../.virtualenvs/workarea/lib/python3.8/site-packages/requests_mock/adapter.py:248: in send
resp = matcher(request)
../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/abstract.py:174: in _matcher
response = callback(request, **path_match.groupdict())
../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/product_projections.py:57: in search
params = utils.parse_request_params(_ProductProjectionQuerySchema, request)
../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/utils.py:32: in parse_request_params
obj = schema().load(params)
../../.virtualenvs/workarea/lib/python3.8/site-packages/marshmallow/schema.py:722: in load
return self._do_load(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <_ProductProjectionQuerySchema(many=False)>
data = {'filter': 'variants.sku:"course-v1:michiganx+injurypreventionx+1t2021"', 'markmatchingvariants': 'false', 'predicate_var': {}}
def _do_load(
self,
data: (
typing.Mapping[str, typing.Any]
| typing.Iterable[typing.Mapping[str, typing.Any]]
),
*,
many: bool | None = None,
partial: bool | types.StrSequenceOrSet | None = None,
unknown: str | None = None,
postprocess: bool = True,
):
"""Deserialize `data`, returning the deserialized result.
This method is private API.
:param data: The data to deserialize.
:param many: Whether to deserialize `data` as a collection. If `None`, the
value for `self.many` is used.
:param partial: Whether to validate required fields. If its
value is an iterable, only fields listed in that iterable will be
ignored will be allowed missing. If `True`, all fields will be allowed missing.
If `None`, the value for `self.partial` is used.
:param unknown: Whether to exclude, include, or raise an error for unknown
fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.
If `None`, the value for `self.unknown` is used.
:param postprocess: Whether to run post_load methods..
:return: Deserialized data
"""
error_store = ErrorStore()
errors = {} # type: dict[str, list[str]]
many = self.many if many is None else bool(many)
unknown = (
self.unknown
if unknown is None
else validate_unknown_parameter_value(unknown)
)
if partial is None:
partial = self.partial
# Run preprocessors
if self._has_processors(PRE_LOAD):
try:
processed_data = self._invoke_load_processors(
PRE_LOAD, data, many=many, original_data=data, partial=partial
)
except ValidationError as err:
errors = err.normalized_messages()
result = None # type: list | dict | None
else:
processed_data = data
if not errors:
# Deserialize data
result = self._deserialize(
processed_data,
error_store=error_store,
many=many,
partial=partial,
unknown=unknown,
)
# Run field-level validation
self._invoke_field_validators(
error_store=error_store, data=result, many=many
)
# Run schema-level validation
if self._has_processors(VALIDATES_SCHEMA):
field_errors = bool(error_store.errors)
self._invoke_schema_validators(
error_store=error_store,
pass_many=True,
data=result,
original_data=data,
many=many,
partial=partial,
field_errors=field_errors,
)
self._invoke_schema_validators(
error_store=error_store,
pass_many=False,
data=result,
original_data=data,
many=many,
partial=partial,
field_errors=field_errors,
)
errors = error_store.errors
# Run post processors
if not errors and postprocess and self._has_processors(POST_LOAD):
try:
result = self._invoke_load_processors(
POST_LOAD,
result,
many=many,
original_data=data,
partial=partial,
)
except ValidationError as err:
errors = err.normalized_messages()
if errors:
exc = ValidationError(errors, data=data, valid_data=result)
self.handle_error(exc, data, many=many, partial=partial)
> raise exc
E marshmallow.exceptions.ValidationError: {'filter': ['Unknown field.'], 'markmatchingvariants': ['Unknown field.']}
../../.virtualenvs/workarea/lib/python3.8/site-packages/marshmallow/schema.py:909: ValidationError
```
Hi! Don't know if this project accepts PRs, but in case it does:
Description
On tests of commercetools-python-sdk SDK calls to Product Projection Search, we see error:
In the first 2 commits, this PR attempts to correct the schema and urls used in
commercetools.testing.product_projections
'sProductProjectionsBackend.search()
to squash this error.In the last 2 commits, this PR proposes minor changes to the coverage GitHub Action job to squash bugs from the breaking changes in tooling dependencies, such as one in actions/upload-artifact@3 to actions/upload-artifact@4 of not being able to upload artifacts with the same name.
Additional Information
See commit messages for detailed explanation of changes.
Full error logs that prompted change are below. (But see commit messages first.)
Output of error resolved by a5b5f273d259cfde32e1620c5f32c2b04d2264c8
Note in the full logs below how this mocker matches to to `get_by_id()` instead of `search()`: ``` <...> ../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/product_projections.py:85: in get_by_id params = utils.parse_request_params(_ProductProjectionQuerySchema, request) <...> ``` Full logs, starting with where `search()` is called: ``` % pytest <...truncated...> commerce_coordinator/apps/commercetools/clients.py:240: in get_product_variant_by_course_run results = self.base_client.product_projections.search(False, filter=f"variants.sku:\"{cr_id}\"").results ../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/services/product_projections.py:294: in search return self._client._get( ../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/client.py:36: in _get response = self._http_client.get(self._base_url + endpoint, params=params) ../../.virtualenvs/workarea/lib/python3.8/site-packages/requests/sessions.py:602: in get return self.request("GET", url, **kwargs) ../../.virtualenvs/workarea/lib/python3.8/site-packages/requests_oauthlib/oauth2_session.py:521: in request return super(OAuth2Session, self).request( ../../.virtualenvs/workarea/lib/python3.8/site-packages/requests/sessions.py:589: in request resp = self.send(prep, **send_kwargs) ../../.virtualenvs/workarea/lib/python3.8/site-packages/requests_mock/mocker.py:185: in _fake_send return _original_send(session, request, **kwargs) ../../.virtualenvs/workarea/lib/python3.8/site-packages/requests/sessions.py:703: in send r = adapter.send(request, **kwargs) ../../.virtualenvs/workarea/lib/python3.8/site-packages/requests_mock/adapter.py:248: in send resp = matcher(request) ../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/abstract.py:174: in _matcher response = callback(request, **path_match.groupdict()) ../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/product_projections.py:85: in get_by_id params = utils.parse_request_params(_ProductProjectionQuerySchema, request) ../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/utils.py:32: in parse_request_params obj = schema().load(params) ../../.virtualenvs/workarea/lib/python3.8/site-packages/marshmallow/schema.py:722: in load return self._do_load( _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = <_ProductProjectionQuerySchema(many=False)> data = {'filter': 'variants.sku:"course-v1:michiganx+injurypreventionx+1t2021"', 'markmatchingvariants': 'false', 'predicate_var': {}} def _do_load( self, data: ( typing.Mapping[str, typing.Any] | typing.Iterable[typing.Mapping[str, typing.Any]] ), *, many: bool | None = None, partial: bool | types.StrSequenceOrSet | None = None, unknown: str | None = None, postprocess: bool = True, ): """Deserialize `data`, returning the deserialized result. This method is private API. :param data: The data to deserialize. :param many: Whether to deserialize `data` as a collection. If `None`, the value for `self.many` is used. :param partial: Whether to validate required fields. If its value is an iterable, only fields listed in that iterable will be ignored will be allowed missing. If `True`, all fields will be allowed missing. If `None`, the value for `self.partial` is used. :param unknown: Whether to exclude, include, or raise an error for unknown fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`. If `None`, the value for `self.unknown` is used. :param postprocess: Whether to run post_load methods.. :return: Deserialized data """ error_store = ErrorStore() errors = {} # type: dict[str, list[str]] many = self.many if many is None else bool(many) unknown = ( self.unknown if unknown is None else validate_unknown_parameter_value(unknown) ) if partial is None: partial = self.partial # Run preprocessors if self._has_processors(PRE_LOAD): try: processed_data = self._invoke_load_processors( PRE_LOAD, data, many=many, original_data=data, partial=partial ) except ValidationError as err: errors = err.normalized_messages() result = None # type: list | dict | None else: processed_data = data if not errors: # Deserialize data result = self._deserialize( processed_data, error_store=error_store, many=many, partial=partial, unknown=unknown, ) # Run field-level validation self._invoke_field_validators( error_store=error_store, data=result, many=many ) # Run schema-level validation if self._has_processors(VALIDATES_SCHEMA): field_errors = bool(error_store.errors) self._invoke_schema_validators( error_store=error_store, pass_many=True, data=result, original_data=data, many=many, partial=partial, field_errors=field_errors, ) self._invoke_schema_validators( error_store=error_store, pass_many=False, data=result, original_data=data, many=many, partial=partial, field_errors=field_errors, ) errors = error_store.errors # Run post processors if not errors and postprocess and self._has_processors(POST_LOAD): try: result = self._invoke_load_processors( POST_LOAD, result, many=many, original_data=data, partial=partial, ) except ValidationError as err: errors = err.normalized_messages() if errors: exc = ValidationError(errors, data=data, valid_data=result) self.handle_error(exc, data, many=many, partial=partial) > raise exc E marshmallow.exceptions.ValidationError: {'filter': ['Unknown field.'], 'markmatchingvariants': ['Unknown field.']} ../../.virtualenvs/workarea/lib/python3.8/site-packages/marshmallow/schema.py:909: ValidationError ```Output of error resolved by 59c762312d71732d5b8de5e67767c6d056ab0e49
Note in full logs below that now call is to `search()`: ``` <...> ../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/product_projections.py:57: in search params = utils.parse_request_params(_ProductProjectionQuerySchema, request) <...> ``` but `_ProductProjectionQuerySchema` is used instead of `_ProductProjectionSearchSchema`: ``` <...> ../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/product_projections.py:57: in search params = utils.parse_request_params(_ProductProjectionQuerySchema, request) <...> ``` Full logs: ``` % pytest <...truncated...> commerce_coordinator/apps/commercetools/clients.py:240: in get_product_variant_by_course_run results = self.base_client.product_projections.search(False, filter=f"variants.sku:\"{cr_id}\"").results ../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/services/product_projections.py:294: in search return self._client._get( ../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/client.py:36: in _get response = self._http_client.get(self._base_url + endpoint, params=params) ../../.virtualenvs/workarea/lib/python3.8/site-packages/requests/sessions.py:602: in get return self.request("GET", url, **kwargs) ../../.virtualenvs/workarea/lib/python3.8/site-packages/requests_oauthlib/oauth2_session.py:521: in request return super(OAuth2Session, self).request( ../../.virtualenvs/workarea/lib/python3.8/site-packages/requests/sessions.py:589: in request resp = self.send(prep, **send_kwargs) ../../.virtualenvs/workarea/lib/python3.8/site-packages/requests_mock/mocker.py:185: in _fake_send return _original_send(session, request, **kwargs) ../../.virtualenvs/workarea/lib/python3.8/site-packages/requests/sessions.py:703: in send r = adapter.send(request, **kwargs) ../../.virtualenvs/workarea/lib/python3.8/site-packages/requests_mock/adapter.py:248: in send resp = matcher(request) ../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/abstract.py:174: in _matcher response = callback(request, **path_match.groupdict()) ../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/product_projections.py:57: in search params = utils.parse_request_params(_ProductProjectionQuerySchema, request) ../../.virtualenvs/workarea/lib/python3.8/site-packages/commercetools/testing/utils.py:32: in parse_request_params obj = schema().load(params) ../../.virtualenvs/workarea/lib/python3.8/site-packages/marshmallow/schema.py:722: in load return self._do_load( _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = <_ProductProjectionQuerySchema(many=False)> data = {'filter': 'variants.sku:"course-v1:michiganx+injurypreventionx+1t2021"', 'markmatchingvariants': 'false', 'predicate_var': {}} def _do_load( self, data: ( typing.Mapping[str, typing.Any] | typing.Iterable[typing.Mapping[str, typing.Any]] ), *, many: bool | None = None, partial: bool | types.StrSequenceOrSet | None = None, unknown: str | None = None, postprocess: bool = True, ): """Deserialize `data`, returning the deserialized result. This method is private API. :param data: The data to deserialize. :param many: Whether to deserialize `data` as a collection. If `None`, the value for `self.many` is used. :param partial: Whether to validate required fields. If its value is an iterable, only fields listed in that iterable will be ignored will be allowed missing. If `True`, all fields will be allowed missing. If `None`, the value for `self.partial` is used. :param unknown: Whether to exclude, include, or raise an error for unknown fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`. If `None`, the value for `self.unknown` is used. :param postprocess: Whether to run post_load methods.. :return: Deserialized data """ error_store = ErrorStore() errors = {} # type: dict[str, list[str]] many = self.many if many is None else bool(many) unknown = ( self.unknown if unknown is None else validate_unknown_parameter_value(unknown) ) if partial is None: partial = self.partial # Run preprocessors if self._has_processors(PRE_LOAD): try: processed_data = self._invoke_load_processors( PRE_LOAD, data, many=many, original_data=data, partial=partial ) except ValidationError as err: errors = err.normalized_messages() result = None # type: list | dict | None else: processed_data = data if not errors: # Deserialize data result = self._deserialize( processed_data, error_store=error_store, many=many, partial=partial, unknown=unknown, ) # Run field-level validation self._invoke_field_validators( error_store=error_store, data=result, many=many ) # Run schema-level validation if self._has_processors(VALIDATES_SCHEMA): field_errors = bool(error_store.errors) self._invoke_schema_validators( error_store=error_store, pass_many=True, data=result, original_data=data, many=many, partial=partial, field_errors=field_errors, ) self._invoke_schema_validators( error_store=error_store, pass_many=False, data=result, original_data=data, many=many, partial=partial, field_errors=field_errors, ) errors = error_store.errors # Run post processors if not errors and postprocess and self._has_processors(POST_LOAD): try: result = self._invoke_load_processors( POST_LOAD, result, many=many, original_data=data, partial=partial, ) except ValidationError as err: errors = err.normalized_messages() if errors: exc = ValidationError(errors, data=data, valid_data=result) self.handle_error(exc, data, many=many, partial=partial) > raise exc E marshmallow.exceptions.ValidationError: {'filter': ['Unknown field.'], 'markmatchingvariants': ['Unknown field.']} ../../.virtualenvs/workarea/lib/python3.8/site-packages/marshmallow/schema.py:909: ValidationError ```Testing Information
We tested these changes by verifying that the test errors in https://github.com/edx/commerce-coordinator/pull/135 (see this comment) turned green on our locals when manually loading this branch of the SDK in our code.
We verified format/tests/coverage jobs ran successfully in https://github.com/edx/commercetools-python-sdk/pull/1.