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@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/