[Python-ideas] IPLike objects
Andrew Svetlov
andrew.svetlov at gmail.com
Sun Oct 21 08:16:12 EDT 2018
Should we support bytes? Why? For path name, the bytes are sometimes the
only way to encode the file name, Posix operates with bytes internally.
But IP addresses are always ASCII strings. I doubt if we shall support
bytes for it, complicating the implementation.
Support for `int` in `get_addr()` is event more questionable and
error-prone.
The main consumer of IPLIke API is socket module.
Moving IPLike from ipaddress.py into socket.py makes sense for me.
Calling `ip_address` from `get_ipaddr()` is another question.
I doubt if `get_ipaddr()` is required at all.
In my mind `socket.ipaddr(arg)` should be very similar to `os,fspath` and
implemented in the same way:
1. Return argument as is if it is a string,
2. Otherwise try to return `type(arg).__ipaddr__(arg)`
`socket.ipaddr` should be as fast as possible, don't process extra
conversions and have a C Accelerated version used by default.
Basically it is a mirror of `os.fspath()`, `socket.IPLike` is a mirror of
`os.PathLike`.
Your thoughts?
On Sun, Oct 21, 2018 at 1:59 PM Damla Altun <initalize.damla at gmail.com>
wrote:
> 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
>
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
--
Thanks,
Andrew Svetlov
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20181021/119a414b/attachment.html>
More information about the Python-ideas
mailing list