Search code examples
pythonjsonansiblejmespath

JMESPath date filtering


I am trying to convert an Ansible script to a Python AWS lambda function. In my Python script I am using the jmespath library to filter by date, which is given as a string in ISO 8601 format.

My filter works in my Ansible script and also using the JMESPath website tool. I cannot figure out why my Python version does not work; Ansible is written Python so I expected it to work the same way.

This works in Ansible:

 - name: parse groups
      debug:
        msg: "{{ results_gitlabGroupsProjects | to_json | from_json  | json_query(projects_query) }}"
      vars:
        projects_query: "json[?last_activity_at > `{{gitlab_date}}`].{name: name, id: id, last_activity_at: last_activity_at }"
      register: gitlabGroupsProjects2

When I try to do the same thing in Python, I get an empty list, []:

compareTime="2020-01-15T17:55:3S.390Z"
plist2 = jmespath.search('[?last_activity_at > `str(compareTime)`]', project_data )
with open('plist2.json', 'w') as json_file:
    json.dump(plist2, json_file)

Sample JSON data:

[
  {
    "name": "test",
    "id": 16340975,
    "last_activity_at": "2020-01-15T20:12:49.775Z"
  },
  {
    "name": "test1",
    "id": 11111111,
    "last_activity_at": "2020-01-15T15:57:29.670Z"
  },
  {
    "name": "test2",
    "id": 222222,
    "last_activity_at": "2020-01-15T23:08:22.313Z"
  },
  {
    "name": "test3",
    "id": 133333,
    "last_activity_at": "2020-01-15T22:28:42.628Z"
  },
  {
    "name": "test4",
    "id": 444444,
    "last_activity_at": "2020-01-14T02:20:47.496Z"
  },
  {
    "name": "test5",
    "id": 555555,
    "last_activity_at": "2020-01-13T04:54:18.353Z"
  },
  {
    "name": "test6",
    "id": 66666666,
    "last_activity_at": "2020-01-12T07:12:05.858Z"
  },
  {
    "name": "test7",
    "id": 7777777,
    "last_activity_at": "2020-01-10T20:52:32.269Z"
  }
]

Using Ansible, and on the JMESPath website I get this output:

[
  {
    "name": "test",
    "id": 16340975,
    "last_activity_at": "2020-01-15T20:12:49.775Z"
  },
  {
    "name": "test2",
    "id": 222222,
    "last_activity_at": "2020-01-15T23:08:22.313Z"
  },
  {
    "name": "test3",
    "id": 133333,
    "last_activity_at": "2020-01-15T22:28:42.628Z"
  }
]

Solution

  • You can't just put a str(compareTime) expression in as string literal and have Python understand that that's what you wanted to replace.

    In the string

    '[?last_activity_at > `str(compareTime)`]'
    

    nothing is dynamic; the characters str(compareTime) do not carry special meaning to Python.

    In Ansible, you used special syntax, {{gitlab_date}}, to tell Ansible to treat the string differently, and the value of the gitlab_date variable is placed into the string at that point, before using it as a JMESPath query.

    In Python, you need to something similar. E.g. you could use a formatted string literal, or f-string:

    plist2 = jmespath.search(f"[?last_activity_at > `{compareTime}`]", project_data)
    

    The f before the string literal tells Python to look for any expressions between {...} curly braces, so compareTime is slotted into place at that point of the string. This is a lot like the Ansible syntax.

    The above produces your expected output:

    >>> jmespath.search(f"[?last_activity_at > `{compareTime}`]", project_data)
    [{'name': 'test', 'id': 16340975, 'last_activity_at': '2020-01-15T20:12:49.775Z'}, {'name': 'test2', 'id': 222222, 'last_activity_at': '2020-01-15T23:08:22.313Z'}, {'name': 'test3', 'id': 133333, 'last_activity_at': '2020-01-15T22:28:42.628Z'}]
    >>> from pprint import pprint
    >>> pprint(_)
    [{'id': 16340975,
      'last_activity_at': '2020-01-15T20:12:49.775Z',
      'name': 'test'},
     {'id': 222222,
      'last_activity_at': '2020-01-15T23:08:22.313Z',
      'name': 'test2'},
     {'id': 133333,
      'last_activity_at': '2020-01-15T22:28:42.628Z',
      'name': 'test3'}]