Python: ipaddress AttributeError: 'str' object has no attribute

8k views Asked by At

Following the advice given here, I'm using the check the ipaddress module to perform checks of type:

In [25]: IPv4Address(u'100.64.1.1') in IPv4Network(u'100.64.0.0/10')
Out[25]: True

Works fine in IPython. Yet when I turn it into a function:

import ipaddress
def isPrivateIp(ip):
    ip4addressBlocks = [u'0.0.0.0/8', u'10.0.0.0/8', u'100.64.0.0/10', u'127.0.0.0/8', u'169.254.0.0/16', u'172.16.0.0/12', u'192.0.0.0/24', u'192.0.2.0/24', u'192.88.99.0/24', 
    u'192.168.0.0/16', u'198.18.0.0/15', u'198.51.100.0/24', u'203.0.113.0/24', u'224.0.0.0/4', u'240.0.0.0/4', u'255.255.255.255/32']
    unicoded = unicode(ip)
    if any(unicoded in ipaddress.IPv4Network(address) for address in ip4addressBlocks):
        return True
    else:
        return False

print isPrivateIp(r'169.254.255.1')

I get:

  File "isPrivateIP.py", line 14, in <module>
    print isPrivateIp(r'169.254.255.1')
  File "isPrivateIP.py", line 9, in isPrivateIp
    if any(unicoded in ipaddress.IPv4Network(address) for address in ip4addressBlocks):
  File "isPrivateIP.py", line 9, in <genexpr>
    if any(unicoded in ipaddress.IPv4Network(address) for address in ip4addressBlocks):
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ipaddress.py", line 705, in __contains__
    if self._version != other._version:
AttributeError: 'str' object has no attribute '_version'

Why so?

2

There are 2 answers

0
Phillip On BEST ANSWER

You check if the unicode string ip is in the network, whereas before you've used an IPv4Address instance.

Your test must instead be

if any(IPv4Address(unicoded) in ipaddress.IPv4Network(address) for address \
       in ip4addressBlocks):
1
TessellatingHeckler On

@Phillip's answer is correct, I just want to expand on it a bit. I think from your other questions you're crashing through a lot of programming and Python-specific ideas all at once, and some explanation might help - or at least demonstrate that it's dense code, but not magic, and give you some other terms to Google if you want to.

Objects

It's a big topic, and there is no easy tutorial introduction, so I'm not going to try and link one. But when you do IPv4Address(u'100.64.1.1'), it's not just testing whether the IP address is a valid address yes or no, it's taking a lot of code that deals with IP addresses, wrapping it around the address you gave, and returning the whole thing.

Like Tony Stark putting on his Iron Man suit, this takes basic text and gives you a suited-up IPv4Address object with loads of enhancements tuned for working with IP addresses.

IPv4Network() is similar, it takes your network address text and wraps it in a load of enhancements for working with IP networks.

Containment

When you do IPv4Address(u'100.64.1.1') in IPv4Network(u'100.64.0.0/10'), it's the extra enhancement code that understands what it means for one network to be "in" another.

In your question, your code is doing: unicoded in ipaddress.IPv4Network(address) and unicoded at that point is still just text.

"word" in "sentence with some words" is a way to test whether one string of text is in another, so your code is doing one-half of a text membership test and the other half of an IPv4Network membership test. And that mismatch causes the failure and the error you see. The IPv4Network object can't deal.

(The keyword in calls the Python special method __contains__ behind the scenes to handle this kind of test).

Comprehension

In your other question asking about a nicer way to do things in Python (great thing to ask), the code goes from this shape of a classic for loop shape (example fake code):

for network in networks:
    test if address in network:

to this one-line shape:

[if address in network for network in networks]

That reshaped one-line version is a list comprehension and it's a special case made for the common pattern of "doing something for every item in a list, and gathering up the results into another list". e.g.

>>> nums = [2, 4, 6, 8, 10, 12]
>>> results = [num+1 for num in nums]

>>> results
[3, 5, 7, 9, 11, 13]

>>> results = = [num > 5 for num in nums]
[False, False, True, True, True, True]

(More specifically, without the [ ] on the outsides, it's the same thing but now called a generator comprehension, it's a newer version which is more memory efficient).

any

Then the any() function summarises a list down to one result. It considers a list in terms of truth values True/False, and returns True if anything in the list is True. From above:

>>> results
[False, False, True, True, True, True]
>>> any(results)
True

(It goes with the all() function which tests if everything in the list is True).

So the line of code you get from combining all of these is:

if any(ipaddress.IPv4Address(unicoded) in ipaddress.IPv4Network(address) for address in ip4addressBlocks):
    print "it's reserved"

But (imho) that is still ugly. Partly it writes "ipaddress" a lot, partly it's building the enhanced objects over and over again, partly the variable name "ip4addressBlocks" doesn't explain anything about their significance, and partly you redundantly test true/false and then return true/false.

So, here, I:

  • Import only the two names from the module, so I can use them directly without writing ipaddress. everywhere
  • Build the reserved list IPv4Network objects once at the start
  • Build the IPv4Address object once
  • run the test and return the value directly, cutting out the "if true return true else return false"
  • I'm not sure why you've put unicode and raw strings all over, but it seems unnecessary, so I've removed it.

e.g.:

from ipaddress import IPv4Address, IPv4Network

reserved_networks = [IPv4Network(x) for x in [
    '0.0.0.0/8',      '10.0.0.0/8',     '100.64.0.0/10', '127.0.0.0/8', 
    '169.254.0.0/16', '172.16.0.0/12',  '192.0.0.0/24',  '192.0.2.0/24',      
    '192.88.99.0/24', '192.168.0.0/16', '198.18.0.0/15', '198.51.100.0/24',
    '203.0.113.0/24', '224.0.0.0/4',    '240.0.0.0/4',   '255.255.255.255/32'
    ]]

def isPrivateIp(ip):
    # True if ip is in a reserved range, otherwise False
    ip = IPv4Address(ip)
    return any((ip in net) for net in reserved_networks)

print isPrivateIp('179.254.255.1')