8000 Fix MWS MalformedInput error when passing datetime strings by samamorgan · Pull Request #260 · python-amazon-mws/python-amazon-mws · GitHub
[go: up one dir, main page]

Skip to content
This repository was archived by the owner on Jan 4, 2025. It is now read-only.

Conversation

@samamorgan
Copy link
Contributor

Fixes #259

The datetime `fromisoformat` method was introduced in Python 3.7, so can't be used here while retaining Python 3.6 compatibility.

- Added python-dateutil requirement.
- Use `dateutil.parse.isoparse` to detect datetime strings.
- Update `mws.future_utils.params.clean_date` docstring.
- Sort imports
@adamantike
Copy link

Experienced this same issue while upgrading from 0.8.10 to 0.8.13. Bump, so this is merged quickly!

@GriceTurrble
Copy link
Member

Apologies for delay, haven't found the time for review outside work and home life. Not forgotten, will come to this when I can.

My initial take would have been to avoid the new dependency on python-dateutil, but I can't think of a better way to identify datetime strings at this time. It's also something I hadn't considered, as we had focused on supporting datetime objects directly.

I don't see too much issue here, as long as we provide a good range of supported python-dateutil versions to prevent locking up pip dependencies. Will review all this fully when I can, though.

@adamantike
Copy link
adamantike commented Apr 29, 2021

@samamorgan, to avoid the new dependency on python-dateutil, can you check if datetime.fromisoformat also fixes the issue, and is able to parse the same inputs?

It was added in Python 3.7, so we would still need a fallback on python-dateutil for older Python versions. We can achieve that by adding the new dependency conditionally to setup's install_requires:

requires = [
    "python-dateutil; python_version < '3.7.0'",
    "requests",
]

And having the fallback in the parsing code:

try:
    from datetime import datetime
    fromisoformat = datetime.fromisoformat
except AttributeError:
    from dateutil.parser import isoparse as fromisoformat

@samamorgan
Copy link
Contributor Author
samamorgan commented Apr 29, 2021

@GriceTurrble While I typically like to avoid external dependencies as well, python-dateutil is widely used enough that it's mentioned in datetime docs. I can do a little research and pin to a version range if you'd like.

@adamantike I'd prefer simply accepting the dependency rather than adding this complexity. The biggest drawback to your proposed approach is we're now importing on every function call in Python >= 3.7, which adds overhead to the function, making each call take longer.

Edit: Further supporting this change, there's even a recommendation from the datetime docs.

Caution: This does not support parsing arbitrary ISO 8601 strings - it is only intended as the inverse operation of datetime.isoformat(). A more full-featured ISO 8601 parser, dateutil.parser.isoparse is available in the third-party package dateutil.

@samamorgan
Copy link
Contributor Author
samamorgan commented Apr 29, 2021

@adamantike Sorry, I didn't answer your initial question. It does fix the issue, but doesn't work with 3.6. See the initial commit: 3b47e27

Failing check from that commit: https://github.com/python-amazon-mws/python-amazon-mws/actions/runs/665822944

@adamantike
Copy link

It does fix the issue, but doesn't work with 3.6.

Yes, it won't work for 3.6 or older, but for Python 3.7 installations and onwards, the new dependency could be avoided if we go with this approach based on Python version.

The biggest drawback to your proposed approach is we're now importing on every function call in Python >= 3.7, which adds overhead to the function, making each call take longer.

There's no performance overhead. The code snippet I added for the imports is meant to be at the beginning of the file, not inside the function.

@samamorgan
Copy link
Contributor Author

I'd argue that if we're worried about adding a well-known dependency then it shouldn't be added for any version. I see no measurable advantage to the additional setup and import complexity.

@adamantike
Copy link

I would really like for a fix to this issue to be merged, as it's a pretty common scenario, and an unexpected breaking change when trying to upgrade from 0.8.10 to a newer patch version.

@GriceTurrble mentioned that adding a new external dependency could potentially delay the decision on merging this PR, so my proposed alternative is there for the project maintainers to decide on which approach has a better alignment with their opinions.

I can do a little research and pin to a version range if you'd like.

isoparse is available since version 2.7.0

By the way, is this fix also needed in the develop branch, for compatibility when a new major version gets released and current installations try to upgrade?

@GriceTurrble
Copy link
Member
GriceTurrble commented Jun 4, 2021

Finally got some time in to check on current PRs here. :)

Recently merged in #263, which changes a bit of how the encoding works in 0.8 version:

  • clean_string was added to wrap around value.isoformat() in clean_date method:

    def clean_date(val):
    """Converts a datetime.datetime or datetime.date to ISO 8601 string.
    Further passes that string through `urllib.parse.quote`.
    """
    return clean_string(val.isoformat())

  • The use of quote was removed from calc_request_description in mws.py:

    def calc_request_description(params):
    request_description = ""
    for key in sorted(params):
    encoded_value = params[key]
    request_description += "&{}={}".format(key, encoded_value)
    return request_description[1:] # don't include leading ampersand

  • New test added checks that a string value works when cleaning these datetimes:

    def test_calc_request_description_for_cleaned_params(access_key, account_id):
    params = clean_params_dict(
    {
    "AWSAccessKeyId": access_key,
    "Markets": account_id,
    "Subscription.Destination.AttributeList.member.1.Key": "sqsQueueUrl",
    "Subscription.Destination.AttributeList.member.1.Value": "https://sqs.us-east-1.amazonaws.com/123456789/mws-notifications",
    "SignatureVersion": "2",
    "Timestamp": "2017-08-12T19:40:35Z",
    "Version": "2017-01-01",
    "SignatureMethod": "HmacSHA256",
    }
    )
    request_description = calc_request_description(params)
    assert not request_description.startswith("&")
    assert request_description == (
    "AWSAccessKeyId="
    + access_key
    + "&Markets="
    + account_id
    + "&SignatureMethod=HmacSHA256"
    "&SignatureVersion=2"
    "&Subscription.Destination.AttributeList.member.1.Key=sqsQueueUrl"
    "&Subscription.Destination.AttributeList.member.1.Value="
    "https%3A%2F%2Fsqs.us-east-1.amazonaws.com%2F123456789%2Fmws-notifications"
    "&Timestamp=2017-08-12T19%3A40%3A35Z"
    "&Version=2017-01-01"
    )

I did a quick test locally to ensure that the same output is found when cleaning either a string or datetime object.

From my perspective, that seems like the simpler fix. And as this is all targeting older 0.8 version (while new development contains on 1.0dev), I think this change is no longer necessary.

Please let me know if I've missed anything.

@GriceTurrble
Copy link
Member

@samamorgan I apologize for potentially breaking your code: I handled a merge conflict from this side to bring your code up to date with the changes from #263 , and that seems to have busted the tests.

I confirmed locally that simply removing the new check that uses python-dateutil allows tests to pass again. This indicates to me that the changes from 263 are sufficient to cover this issue; but you may find your own branch now has a breaking change, as it includes both sets of code.

I'm going to close this out. If you find those new changes are not working as you expect, please ping me again, and we can work through it to get a final resolution. Thank you. 👋

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Successfully merging this pull request may close these issues.

3 participants

0