How to escape array notation when passing a dictionary as a parameter in Python/Ansible?

I couldn’t find the answer to this anywhere online.

Does anyone know how to escape array notation when a dictionary key is passed as a parameter? My case is in Ansible, but I assume this is really a Python question.

For instance, this works:

selectattr('dict.key', 'defined')

But how would you use dict.key in array notation? Every combination of quotes and back slashes I’ve tried has failed. For instance, selectattr('dict["key"]', 'defined') does not work.


TIL though that you can escape a space in dot notation:

selectattr['dict.the\ key', 'defined')

That does work but looks terrible imo.

1 Like

Set it as a var?

1 Like

Good thought, but still doesn’t work.

1 Like

quote it inside the quote, without the double quotes?

selectattr('\'dict[key]\'', 'defined')

or

selectattr('dict[\'key\']', 'defined')

If the value returned from the hashmap is a string, you can’t just pass it as an argument? (Sorry not at my workstation to test it myself).

selectattr(some_dict[“awesomeness”], ‘defined’)

1 Like

I don’t want the value, I want the keys in array notation as a string.

do the var thing again…
but try the quote filter?

1 Like

No dice. I’m starting to think it just isn’t supported…

1 Like

Can we get an example of what you’re trying to do with code and doesn’t work?
Ansible/Yaml/python is fine, as long as it gives context ,

1 Like

On macOS, I want to lookup an interface based on it’s MAC address. I don’t want to use Ansible facts for this (let’s not get into why).

On macOS, system_profiler -json SPNetworkDataType spits out a list of network interfaces. An item in the list looks like this:

{
      "_name" : "Belkin USB-C LAN",
      "Ethernet" : {
        "MAC Address" : "xx:xx:xx:xx:xx:xx",
        "MediaOptions" : [

        ],
        "MediaSubType" : "none"
      },
      "hardware" : "Ethernet",
      "interface" : "en8",
      "IPv4" : {
        "ConfigMethod" : "DHCP"
      },
      "IPv6" : {
        "ConfigMethod" : "Automatic"
      },
      "Proxies" : {
        "ExceptionsList" : [
          "*.local",
          "169.254/16"
        ],
        "FTPPassive" : "yes"
      },
      "spnetwork_service_order" : 3,
      "type" : "Ethernet"
    }

I want to lookup an interface by its mac address. Test playbook (which works) looks like this:

- hosts: localhost
  gather_facts: no

  tasks:

  - shell: system_profiler -json SPNetworkDataType
    register: sysp

  - debug:
      msg: "{{  ( sysp['stdout'] | from_json )['SPNetworkDataType']
                | selectattr('Ethernet.MAC\ Address', 'defined')
                | selectattr('Ethernet.MAC\ Address', '==', 'xx:xx:xx:xx:xx:xx')
                | map(attribute='interface') }}"

But best practice is to use array notation instead of dot notation, so I’d like to use 'Ethernet["MAC Address"]' instead of 'Ethernet.MAC\ Address'.

2 Likes

https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#ansible-allows-dot-notation-and-array-notation-for-variables-which-notation-should-i-use

maybe you need curly braces around it if you use array notation?

No, the whole thing is already in jinja brackets (I did test it though for kicks).

I have come up with a guess as to why this doesn’t work though. selectattr with array notation might result in SPNetworkDataType['Ethernet['MAC Address']'] which inherently fails, while the equivalent dot notation: SPNetworkDataType.Ethernet.MAC\ Address works simply because the syntax doesn’t wrap the keys.

I think the “correct” way to do this is probably with json_query but I was having trouble getting even a simple query to work on this for some reason…

1 Like

You may be right, the docs for jinja2 only mention using dot notation for nested access …

https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.groupby

1 Like

This, using jq instead of jinja2 …

- hosts: localhost
  gather_facts: no

  tasks:


  - shell: system_profiler -json SPNetworkDataType | jq '.SPNetworkDataType| map( select (.Ethernet["MAC Address"]== "aa:7e:0e:dd:75:ee"))|map(.interface)'
    register: syspjq

  - debug:
      msg: " {{syspjq['stdout']| from_json}}"
[email protected] CODE % ansible-playbook a.yaml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] **************************************************************************************************************************************************

TASK [shell] ******************************************************************************************************************************************************
changed: [localhost]

TASK [debug] ******************************************************************************************************************************************************
ok: [localhost] => {
    "msg": " ['en0']"
}

PLAY RECAP ********************************************************************************************************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

2 Likes

Nice. I am more familiar with jq than jmespath (json_query uses jmespath).

1 Like

Shouldn’t:

SPNetworkDataType['Ethernet['MAC Address']'] 

Be:

SPNetworkDataType['Ethernet']['MAC Address']

It should. I am guessing as to why it fails.

I had luck setting my query as a var and using it that way.

1 Like

I was copying the most simple example I could find in the documentation and it erroring out, so I’m sure I was doing something wrong on a really basic level. I’ll revisit it later.

There you go, jsonquery version:

- hosts: localhost
  vars:
      query: "SPNetworkDataType[?Ethernet.\"MAC Address\" && Ethernet.\"MAC Address\"=='dd:7e:ee:11:aa:84'].interface"
  gather_facts: no

  tasks:


  - shell: system_profiler -json SPNetworkDataType | jq '.SPNetworkDataType| map( select (.Ethernet["MAC Address"]== "52:7e:0e:49:75:84"))|map(.interface)'
    register: syspjq

  - debug:
      msg: " {{syspjq['stdout']| from_json}}"


  - shell: system_profiler -json SPNetworkDataType
    register: syspjsonq

  - debug:
      msg: " {{query}}"


  - debug:
      msg: " {{syspjsonq['stdout']| from_json| json_query(query) }}"

[email protected] CODE % ansible-playbook a.yaml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
[WARNING]: Found variable using reserved name: query

PLAY [localhost] **************************************************************************************************************************************************

TASK [shell] ******************************************************************************************************************************************************
changed: [localhost]

TASK [debug] ******************************************************************************************************************************************************
ok: [localhost] => {
    "msg": " ['en0']"
}

TASK [shell] ******************************************************************************************************************************************************
changed: [localhost]

TASK [debug] ******************************************************************************************************************************************************
ok: [localhost] => {
    "msg": " SPNetworkDataType[?Ethernet.\"MAC Address\" && Ethernet.\"MAC Address\"=='dd:7e:ee:11:aa:84'].interface"
}

TASK [debug] ******************************************************************************************************************************************************
ok: [localhost] => {
    "msg": " ['en0']"
}

PLAY RECAP ********************************************************************************************************************************************************
localhost                  : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

3 Likes