[Python-ideas] IPLike objects
Damla Altun
initalize.damla at gmail.com
Sun Oct 21 06:58:19 EDT 2018
Hi,
Python's ipaddress module is really good for working with network / IP
related thing.
I would like to use ipaddress.IPv4/v6Address in creating network
interfaces. Like a socket but i can't,
the internal things and some standard libraries doesn't support them (even
socket).
>>> server_address = (ipaddress.IPv4Address('127.0.0.1'), 10000)
>>> sock.bind(server_address)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: str, bytes or bytearray expected, not IPv4Address
Proposal
========
The best solution we've got[0] is implementing something like PathLike[1]
for IP Address objects. For PoC[2]
we called it IPLike. It is a protocol (some kind of interface) created with
Abstract Base Classes and
it forces class to implement __ipaddr__ method (like __fspath__ method).
The __ipaddr__ method should return
string or bytes formed notation of IP address. It is dotted notation in
IPv4, long formed notation in IPv6.
The __ipaddr__ method is also an identifier for IPLike classes. IPLike
interface's subclasshook
checks the __ipaddr__ method of given class when issubclass called.
>>> class SomethingRepresentIP:
... def __ipaddr__(self) -> str:
... return '127.0.0.1'
...
>>> issubclass(SomethingRepresentIP, IPLike)
True
>>> isinstance(SomethingRepresentIP(), IPLike)
True
For PoC[2] we implented __ipaddr__ method to ipaddress._BaseAddress class.
It is parent class of IPv4/v6Adress.
>>> issubclass(IPv4Address, IPLike)
True
>>> IPv4Address('127.0.0.1').__ipaddr__()
'127.0.0.1'
Like os.fspath we need a general-purpose ip-getter. So we implemented
ipaddress.get_ipaddr(address) function.
When you give it an valid IP like object it returns the value of __ipaddr__
method.
>>> get_ipaddr(IPv4Address('127.0.0.1'))
'127.0.0.1'
When you give it string or bytes or integer object it calls
ipaddr.ip_address(address) and re-calls itself with
the value it got from ip_address. It helps get_ipaddr to convert
integer-formed / byte-formed IPs to string formed IPs
with a valid notation.
>>> get_ipaddr('127.0.0.1')
'127.0.0.1'
>>> get_ipaddr(3232235521)
'192.168.0.1'
>>> get_ipaddr(b'\xC0\xA8\x00\x01')
'192.168.0.1'
If you give any type else it raises TypeError. Also if you give it a IP
like object (an object that has __ipaddr__ method),
but the object's __ipaddr__ method returns something that is not
string/bytes it raises TypeError.
For PoC[2] i added socket to support IP like objects.
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> server_address = (SomethingRepresentIP(), 10000)
>>> sock.bind(server_address)
>>> sock.listen(1)
>>> conn, client = sock.accept()
>>> conn
<socket.socket fd=4, family=AddressFamily.AF_INET,
type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 10000),
raddr=('127.0.0.1', 49452)>
I didn't implement a C API for ipaddress.get_ipaddr. The function is too
small for creating a new module, new header and the ipaddress library
currently has no
C-side module. If needed i can write a function inside the socketmodule.c .
Proof-of-Concept (Implementation)
=================================
The implementation can be found on github (DamlaAltun/cpython[2]) We have a
Abstract Base Class for IP Address protocol called IPLike.
(ipaddress.py[3])
class IPLike(abc.ABC):
"""
Abstract base class for IP (address) like objects.
"""
@abc.abstractmethod
def __ipaddr__(self):
"""
If it is a IPv4 return in the dotted form,
If it is a IPv6 return in the long form.
"""
raise NotImplementedError
@classmethod
def __subclasshook__(cls, subclass):
return hasattr(subclass, '__ipaddr__')
It has a abstract method __ipaddr__ and a subclasshook. The __ipaddr__
method returns ip representation of object.
The subclasshook checks for __ipaddr__ method.
Then there is a ip-getter called get_ipaddr
(ipaddress.py[4])
def get_ipaddr(address):
"""Return the representation of a ip address like object.
If bytes or string or int passed in, get_ipaddr converts it to
a IPv4/v6 Address and calls it self again for getting,
IP address of IPv4/v6 object. If a IPLike passed it uses IPLike
interface to get the value.
"""
if isinstance(address, (str, bytes, int)):
return get_ipaddr(ip_address(address))
addr_type = type(address)
try:
ip_addr = address.__ipaddr__()
except AttributeError:
if hasattr(address, '__ipaddr__'):
raise
else:
raise TypeError("expected str, bytes or ipaddress.IPLike
object, "
"not {}".format(addr_type.__name__))
if isinstance(ip_addr, (str, bytes)):
return ip_addr
else:
raise TypeError("expected {}.__ipaddr__() to return str or
bytes, "
"not {}".format(addr_type.__name__,
type(addr_type).__name__))
It takes one parameter, address. The address might be string, integer,
bytes or ipaddress.IPLike
If it is string or bytes or integer the get_ipaddr calls
ipaddress.ip_address for getting IPv4/v6Address
object. The purpose of that is converting bytes/int IP addresses to valid
formed string IP addresses. And calls
it self again with result of ipaddress.ip_address
It tries to get address' __ipaddr__ method, if it can successfully perform
it checks the return value of __ipaddr__ method.
If return value is string or bytes it returns the ip address. If it is not
it raises a TypeError.
In the C-Side we can check via _PyObject_LookupSpecial.
(socketmodule.c[5])
idna_converter(PyObject *obj, struct maybe_idna *data)
{
...
_Py_IDENTIFIER(__ipaddr__);
...
func = _PyObject_LookupSpecial(obj, &PyId___ipaddr__);
if (func != NULL) {
obj = PyObject_CallFunctionObjArgs(func, NULL);
Py_DECREF(func);
}
}
Thanks for your attention,
Damla
[0] https://github.com/python/cpython/pull/9956#pullrequestreview-166756568
[1] https://www.python.org/dev/peps/pep-0519
[2] https://github.com/DamlaAltun/cpython/tree/iplike-object
[3]
https://github.com/DamlaAltun/cpython/blob/iplike-object/Lib/ipaddress.py#L413
[4]
https://github.com/DamlaAltun/cpython/blob/iplike-object/Lib/ipaddress.py#L384
[5]
https://github.com/DamlaAltun/cpython/blob/iplike-object/Modules/socketmodule.c#L1537
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20181021/53149f0b/attachment-0001.html>
More information about the Python-ideas
mailing list