diff -Nru python-ifaddr-0.1.7/debian/changelog python-ifaddr-0.2.0/debian/changelog --- python-ifaddr-0.1.7/debian/changelog 2022-05-26 16:16:02.000000000 +0000 +++ python-ifaddr-0.2.0/debian/changelog 2023-06-27 17:17:54.000000000 +0000 @@ -1,3 +1,25 @@ +python-ifaddr (0.2.0-1) unstable; urgency=medium + + * Team upload. + + [ upstream ] + * new release + + [ Jonas Smedegaard ] + * update watch file: track tags, not releases + * build-depend on dh-sequence-python3 + pybuild-plugin-pyproject python3-pytest + * simplify rules: use dh snippet for cleaning egg + * add patch 2001 to avoid using python module ptest-cov + + -- Jonas Smedegaard Tue, 27 Jun 2023 19:17:54 +0200 + +python-ifaddr (0.1.7-3) UNRELEASED; urgency=medium + + * Update standards version to 4.6.2, no changes needed. + + -- Debian Janitor Wed, 11 Jan 2023 17:58:12 -0000 + python-ifaddr (0.1.7-2) unstable; urgency=medium [ Debian Janitor ] diff -Nru python-ifaddr-0.1.7/debian/clean python-ifaddr-0.2.0/debian/clean --- python-ifaddr-0.1.7/debian/clean 1970-01-01 00:00:00.000000000 +0000 +++ python-ifaddr-0.2.0/debian/clean 2023-06-27 17:17:54.000000000 +0000 @@ -0,0 +1 @@ +ifaddr.egg-info/ diff -Nru python-ifaddr-0.1.7/debian/control python-ifaddr-0.2.0/debian/control --- python-ifaddr-0.1.7/debian/control 2022-05-26 16:16:02.000000000 +0000 +++ python-ifaddr-0.2.0/debian/control 2023-06-27 17:17:54.000000000 +0000 @@ -3,22 +3,27 @@ Testsuite: autopkgtest-pkg-python Priority: optional Maintainer: Debian Python Team -Uploaders: Ruben Undheim -Build-Depends: debhelper-compat (= 13), - dh-python, - python3-all, - python3-setuptools -Standards-Version: 4.5.1 +Uploaders: + Ruben Undheim , +Build-Depends: + debhelper-compat (= 13), + dh-python, + dh-sequence-python3, + pybuild-plugin-pyproject, + python3-all, + python3-pytest , + python3-setuptools, +Standards-Version: 4.6.2 Homepage: https://github.com/pydron/ifaddr Rules-Requires-Root: no Vcs-Browser: https://salsa.debian.org/python-team/packages/python-ifaddr Vcs-Git: https://salsa.debian.org/python-team/packages/python-ifaddr.git - Package: python3-ifaddr Architecture: all -Depends: ${python3:Depends}, - ${misc:Depends} +Depends: + ${misc:Depends}, + ${python3:Depends}, Description: Pure Python implementation for detecting IP addresses This is a small Python library which allows you to find all the IP addresses of the computer. Both ipv4 and ipv6 addresses will be found. diff -Nru python-ifaddr-0.1.7/debian/patches/2001_no-pytest-cov.patch python-ifaddr-0.2.0/debian/patches/2001_no-pytest-cov.patch --- python-ifaddr-0.1.7/debian/patches/2001_no-pytest-cov.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-ifaddr-0.2.0/debian/patches/2001_no-pytest-cov.patch 2023-06-27 17:17:54.000000000 +0000 @@ -0,0 +1,18 @@ +Description: avoid using python module ptest-cov +Forwarded: not-needed +Author: Jonas Smedegaard +Last-Update: 2023-06-27 +--- +This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ +--- a/pytest.ini ++++ /dev/null +@@ -1,2 +0,0 @@ +-[pytest] +-addopts = -v --cov-report term --cov-report html --cov-report xml --cov-report term-missing --cov=ifaddr --cov-branch +--- a/requirements-dev.txt ++++ b/requirements-dev.txt +@@ -3,4 +3,3 @@ + # netifaces only provides 64-bit Windows wheels for Python 3.6 and 3.7 and we use 64-bit CI builds + netifaces;python_version=='3.7' and platform_system=='Windows' or platform_system!='Windows' + pytest +-pytest-cov diff -Nru python-ifaddr-0.1.7/debian/patches/README python-ifaddr-0.2.0/debian/patches/README --- python-ifaddr-0.1.7/debian/patches/README 1970-01-01 00:00:00.000000000 +0000 +++ python-ifaddr-0.2.0/debian/patches/README 2020-12-25 16:24:13.000000000 +0000 @@ -0,0 +1,3 @@ +0xx: Backports from newer upstream CVS. +1xx: Possibly relevant for upstream adoption. +2xx: Only relevant for official Debian release. diff -Nru python-ifaddr-0.1.7/debian/patches/series python-ifaddr-0.2.0/debian/patches/series --- python-ifaddr-0.1.7/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ python-ifaddr-0.2.0/debian/patches/series 2023-06-27 17:17:54.000000000 +0000 @@ -0,0 +1 @@ +2001_no-pytest-cov.patch diff -Nru python-ifaddr-0.1.7/debian/rules python-ifaddr-0.2.0/debian/rules --- python-ifaddr-0.1.7/debian/rules 2022-05-26 16:16:02.000000000 +0000 +++ python-ifaddr-0.2.0/debian/rules 2023-06-27 17:17:54.000000000 +0000 @@ -4,9 +4,4 @@ #LC_ALL=C.UTF-8 Makes sure that it builds correctly with pbuilder %: - LC_ALL=C.UTF-8 dh $@ --with python3 --buildsystem=pybuild - - -override_dh_auto_clean: - dh_auto_clean - $(RM) -r ifaddr.egg-info + LC_ALL=C.UTF-8 dh $@ --buildsystem=pybuild diff -Nru python-ifaddr-0.1.7/debian/watch python-ifaddr-0.2.0/debian/watch --- python-ifaddr-0.1.7/debian/watch 2022-05-26 16:16:02.000000000 +0000 +++ python-ifaddr-0.2.0/debian/watch 2023-06-27 17:15:59.000000000 +0000 @@ -1,2 +1,2 @@ version=4 -https://github.com/pydron/ifaddr/releases /pydron/ifaddr/archive/refs/tags/(\d\S+)\.tar\.(?:bz2|gz|xz) +https://github.com/pydron/ifaddr/tags /pydron/ifaddr/archive/refs/tags/(\d\S+)\.tar\.(?:bz2|gz|xz) diff -Nru python-ifaddr-0.1.7/doc/conf.py python-ifaddr-0.2.0/doc/conf.py --- python-ifaddr-0.1.7/doc/conf.py 2020-06-06 18:56:39.000000000 +0000 +++ python-ifaddr-0.2.0/doc/conf.py 2022-06-15 21:40:32.000000000 +0000 @@ -21,19 +21,21 @@ # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.pngmath', - 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode', - 'sphinx.ext.graphviz'] +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.pngmath', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', + 'sphinx.ext.graphviz', +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -42,7 +44,7 @@ source_suffix = '.rst' # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' @@ -62,40 +64,40 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # -- Options for HTML output --------------------------------------------------- @@ -107,26 +109,26 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -135,44 +137,44 @@ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'pythondoc' @@ -181,55 +183,49 @@ # -- Options for LaTeX output -------------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'python.tex', u'ifaddr Documentation', - u'Stefan C. Mueller', 'manual'), + ('index', 'python.tex', u'ifaddr Documentation', u'Stefan C. Mueller', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'python', u'ifaddr Documentation', - [u'Stefan C. Mueller'], 1) -] +man_pages = [('index', 'python', u'ifaddr Documentation', [u'Stefan C. Mueller'], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ @@ -238,22 +234,28 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'python', u'ifaddr Documentation', - u'Stefan C. Mueller', 'python', 'Enumerates all IP addresses on all network adapters of the system.', - 'Miscellaneous'), + ( + 'index', + 'python', + u'ifaddr Documentation', + u'Stefan C. Mueller', + 'python', + 'Enumerates all IP addresses on all network adapters of the system.', + 'Miscellaneous', + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. @@ -265,5 +267,6 @@ return False return skip + def setup(app): app.connect("autodoc-skip-member", skip) diff -Nru python-ifaddr-0.1.7/.github/workflows/ci.yml python-ifaddr-0.2.0/.github/workflows/ci.yml --- python-ifaddr-0.1.7/.github/workflows/ci.yml 2020-06-06 18:56:39.000000000 +0000 +++ python-ifaddr-0.2.0/.github/workflows/ci.yml 2022-06-15 21:40:32.000000000 +0000 @@ -1,6 +1,9 @@ name: CI -on: [push, pull_request] +on: + push: + branches: [master] + pull_request: jobs: build: @@ -8,11 +11,12 @@ strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [2.7, 3.5, 3.6, 3.7, 3.8, pypy2, pypy3] + # At the time of writing this pypy3.10 is not available through setup-python + python-version: [3.7, 3.8, 3.9, "3.10", pypy3.7, pypy3.8, pypy3.9] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -20,8 +24,11 @@ pip install --upgrade -r requirements-dev.txt pip install . - name: Run tests + shell: bash run: | pytest + if which mypy; then mypy ifaddr; fi + if which black; then black --check . ; fi # Coveralls won't work here trivially because it can't ingest coverage.xml, so Codecov it is. # https://github.com/coverallsapp/github-action/issues/30 - name: Report coverage to Codecov diff -Nru python-ifaddr-0.1.7/ifaddr/netifaces.py python-ifaddr-0.2.0/ifaddr/netifaces.py --- python-ifaddr-0.1.7/ifaddr/netifaces.py 1970-01-01 00:00:00.000000000 +0000 +++ python-ifaddr-0.2.0/ifaddr/netifaces.py 2022-06-15 21:40:32.000000000 +0000 @@ -0,0 +1,10 @@ +# netifaces compatibility layer + +import ifaddr + +from typing import List + + +def interfaces() -> List[str]: + adapters = ifaddr.get_adapters(include_unconfigured=True) + return [a.name for a in adapters] diff -Nru python-ifaddr-0.1.7/ifaddr/_posix.py python-ifaddr-0.2.0/ifaddr/_posix.py --- python-ifaddr-0.1.7/ifaddr/_posix.py 2020-06-06 18:56:39.000000000 +0000 +++ python-ifaddr-0.2.0/ifaddr/_posix.py 2022-06-15 21:40:32.000000000 +0000 @@ -19,27 +19,35 @@ # IN THE SOFTWARE. -import sys import os import ctypes.util import ipaddress import collections import socket +from typing import Iterable, Optional + import ifaddr._shared as shared -#from ifaddr._shared import sockaddr, Interface, sockaddr_to_ip, ipv6_prefixlength + +# from ifaddr._shared import sockaddr, Interface, sockaddr_to_ip, ipv6_prefixlength + class ifaddrs(ctypes.Structure): pass -ifaddrs._fields_ = [('ifa_next', ctypes.POINTER(ifaddrs)), - ('ifa_name', ctypes.c_char_p), - ('ifa_flags', ctypes.c_uint), - ('ifa_addr', ctypes.POINTER(shared.sockaddr)), - ('ifa_netmask', ctypes.POINTER(shared.sockaddr))] -libc = ctypes.CDLL(ctypes.util.find_library("socket" if os.uname()[0] == "SunOS" else "c"), use_errno=True) -def get_adapters(): +ifaddrs._fields_ = [ + ('ifa_next', ctypes.POINTER(ifaddrs)), + ('ifa_name', ctypes.c_char_p), + ('ifa_flags', ctypes.c_uint), + ('ifa_addr', ctypes.POINTER(shared.sockaddr)), + ('ifa_netmask', ctypes.POINTER(shared.sockaddr)), +] + +libc = ctypes.CDLL(ctypes.util.find_library("socket" if os.uname()[0] == "SunOS" else "c"), use_errno=True) # type: ignore + + +def get_adapters(include_unconfigured: bool = False) -> Iterable[shared.Adapter]: addr0 = addr = ctypes.POINTER(ifaddrs)() retval = libc.getifaddrs(ctypes.byref(addr)) @@ -49,41 +57,38 @@ ips = collections.OrderedDict() - def add_ip(adapter_name, ip): - if not adapter_name in ips: + def add_ip(adapter_name: str, ip: Optional[shared.IP]) -> None: + if adapter_name not in ips: + index = None # type: Optional[int] try: - index = socket.if_nametoindex(adapter_name) + # Mypy errors on this when the Windows CI runs: + # error: Module has no attribute "if_nametoindex" + index = socket.if_nametoindex(adapter_name) # type: ignore except (OSError, AttributeError): - index = None - ips[adapter_name] = shared.Adapter(adapter_name, adapter_name, [], - index=index) - ips[adapter_name].ips.append(ip) - + pass + ips[adapter_name] = shared.Adapter(adapter_name, adapter_name, [], index=index) + if ip is not None: + ips[adapter_name].ips.append(ip) while addr: - name = addr[0].ifa_name - if sys.version_info[0] > 2: - name = name.decode(encoding='UTF-8') - ip = shared.sockaddr_to_ip(addr[0].ifa_addr) - if ip: + name = addr[0].ifa_name.decode(encoding='UTF-8') + ip_addr = shared.sockaddr_to_ip(addr[0].ifa_addr) + if ip_addr: if addr[0].ifa_netmask and not addr[0].ifa_netmask[0].sa_familiy: addr[0].ifa_netmask[0].sa_familiy = addr[0].ifa_addr[0].sa_familiy netmask = shared.sockaddr_to_ip(addr[0].ifa_netmask) if isinstance(netmask, tuple): - netmask = netmask[0] - if sys.version_info[0] > 2: - netmaskStr = str(netmask) - else: - netmaskStr = unicode(netmask) + netmaskStr = str(netmask[0]) prefixlen = shared.ipv6_prefixlength(ipaddress.IPv6Address(netmaskStr)) else: - if sys.version_info[0] > 2: - netmaskStr = str('0.0.0.0/' + netmask) - else: - netmaskStr = unicode('0.0.0.0/' + netmask) + assert netmask is not None, f'sockaddr_to_ip({addr[0].ifa_netmask}) returned None' + netmaskStr = str('0.0.0.0/' + netmask) prefixlen = ipaddress.IPv4Network(netmaskStr).prefixlen - ip = shared.IP(ip, prefixlen, name) + ip = shared.IP(ip_addr, prefixlen, name) add_ip(name, ip) + else: + if include_unconfigured: + add_ip(name, None) addr = addr[0].ifa_next libc.freeifaddrs(addr0) diff -Nru python-ifaddr-0.1.7/ifaddr/_shared.py python-ifaddr-0.2.0/ifaddr/_shared.py --- python-ifaddr-0.1.7/ifaddr/_shared.py 2020-06-06 18:56:39.000000000 +0000 +++ python-ifaddr-0.2.0/ifaddr/_shared.py 2022-06-15 21:40:32.000000000 +0000 @@ -24,6 +24,9 @@ import ipaddress import platform +from typing import List, Optional, Tuple, Union + + class Adapter(object): """ Represents a network interface device controller (NIC), such as a @@ -35,7 +38,7 @@ a IPv4 and an IPv6 IP address. """ - def __init__(self, name, nice_name, ips, index=None): + def __init__(self, name: str, nice_name: str, ips: List['IP'], index: Optional[int] = None) -> None: #: Unique name that identifies the adapter in the system. #: On Linux this is of the form of `eth0` or `eth0:1`, on @@ -55,21 +58,25 @@ #: Adapter index as used by some API (e.g. IPv6 multicast group join). self.index = index - def __repr__(self): + def __repr__(self) -> str: return "Adapter(name={name}, nice_name={nice_name}, ips={ips}, index={index})".format( - name = repr(self.name), - nice_name = repr(self.nice_name), - ips = repr(self.ips), - index=repr(self.index) + name=repr(self.name), nice_name=repr(self.nice_name), ips=repr(self.ips), index=repr(self.index) ) +# Type of an IPv4 address (a string in "xxx.xxx.xxx.xxx" format) +_IPv4Address = str + +# Type of an IPv6 address (a three-tuple `(ip, flowinfo, scope_id)`) +_IPv6Address = Tuple[str, int, int] + + class IP(object): """ Represents an IP address of an adapter. """ - def __init__(self, ip, network_prefix, nice_name): + def __init__(self, ip: Union[_IPv4Address, _IPv6Address], network_prefix: int, nice_name: str) -> None: #: IP address. For IPv4 addresses this is a string in #: "xxx.xxx.xxx.xxx" format. For IPv6 addresses this @@ -90,7 +97,7 @@ self.nice_name = nice_name @property - def is_IPv4(self): + def is_IPv4(self) -> bool: """ Returns `True` if this IP is an IPv4 address and `False` if it is an IPv6 address. @@ -98,19 +105,16 @@ return not isinstance(self.ip, tuple) @property - def is_IPv6(self): + def is_IPv6(self) -> bool: """ Returns `True` if this IP is an IPv6 address and `False` if it is an IPv4 address. """ return isinstance(self.ip, tuple) - - def __repr__(self): + def __repr__(self) -> str: return "IP(ip={ip}, network_prefix={network_prefix}, nice_name={nice_name})".format( - ip = repr(self.ip), - network_prefix = repr(self.network_prefix), - nice_name = repr(self.nice_name) + ip=repr(self.ip), network_prefix=repr(self.network_prefix), nice_name=repr(self.nice_name) ) @@ -122,46 +126,55 @@ # both structures equally. class sockaddr(ctypes.Structure): - _fields_= [('sa_len', ctypes.c_uint8), - ('sa_familiy', ctypes.c_uint8), - ('sa_data', ctypes.c_uint8 * 14)] + _fields_ = [ + ('sa_len', ctypes.c_uint8), + ('sa_familiy', ctypes.c_uint8), + ('sa_data', ctypes.c_uint8 * 14), + ] class sockaddr_in(ctypes.Structure): - _fields_= [('sa_len', ctypes.c_uint8), - ('sa_familiy', ctypes.c_uint8), - ('sin_port', ctypes.c_uint16), - ('sin_addr', ctypes.c_uint8 * 4), - ('sin_zero', ctypes.c_uint8 * 8)] + _fields_ = [ + ('sa_len', ctypes.c_uint8), + ('sa_familiy', ctypes.c_uint8), + ('sin_port', ctypes.c_uint16), + ('sin_addr', ctypes.c_uint8 * 4), + ('sin_zero', ctypes.c_uint8 * 8), + ] class sockaddr_in6(ctypes.Structure): - _fields_= [('sa_len', ctypes.c_uint8), - ('sa_familiy', ctypes.c_uint8), - ('sin6_port', ctypes.c_uint16), - ('sin6_flowinfo', ctypes.c_uint32), - ('sin6_addr', ctypes.c_uint8 * 16), - ('sin6_scope_id', ctypes.c_uint32)] + _fields_ = [ + ('sa_len', ctypes.c_uint8), + ('sa_familiy', ctypes.c_uint8), + ('sin6_port', ctypes.c_uint16), + ('sin6_flowinfo', ctypes.c_uint32), + ('sin6_addr', ctypes.c_uint8 * 16), + ('sin6_scope_id', ctypes.c_uint32), + ] else: - class sockaddr(ctypes.Structure): - _fields_= [('sa_familiy', ctypes.c_uint16), - ('sa_data', ctypes.c_uint8 * 14)] + class sockaddr(ctypes.Structure): # type: ignore + _fields_ = [('sa_familiy', ctypes.c_uint16), ('sa_data', ctypes.c_uint8 * 14)] - class sockaddr_in(ctypes.Structure): - _fields_= [('sin_familiy', ctypes.c_uint16), - ('sin_port', ctypes.c_uint16), - ('sin_addr', ctypes.c_uint8 * 4), - ('sin_zero', ctypes.c_uint8 * 8)] - - class sockaddr_in6(ctypes.Structure): - _fields_= [('sin6_familiy', ctypes.c_uint16), - ('sin6_port', ctypes.c_uint16), - ('sin6_flowinfo', ctypes.c_uint32), - ('sin6_addr', ctypes.c_uint8 * 16), - ('sin6_scope_id', ctypes.c_uint32)] + class sockaddr_in(ctypes.Structure): # type: ignore + _fields_ = [ + ('sin_familiy', ctypes.c_uint16), + ('sin_port', ctypes.c_uint16), + ('sin_addr', ctypes.c_uint8 * 4), + ('sin_zero', ctypes.c_uint8 * 8), + ] + + class sockaddr_in6(ctypes.Structure): # type: ignore + _fields_ = [ + ('sin6_familiy', ctypes.c_uint16), + ('sin6_port', ctypes.c_uint16), + ('sin6_flowinfo', ctypes.c_uint32), + ('sin6_addr', ctypes.c_uint8 * 16), + ('sin6_scope_id', ctypes.c_uint32), + ] -def sockaddr_to_ip(sockaddr_ptr): +def sockaddr_to_ip(sockaddr_ptr: 'ctypes.pointer[sockaddr]') -> Optional[Union[_IPv4Address, _IPv6Address]]: if sockaddr_ptr: if sockaddr_ptr[0].sa_familiy == socket.AF_INET: ipv4 = ctypes.cast(sockaddr_ptr, ctypes.POINTER(sockaddr_in)) @@ -174,11 +187,11 @@ ippacked = bytes(bytearray(ipv6[0].sin6_addr)) ip = str(ipaddress.ip_address(ippacked)) scope_id = ipv6[0].sin6_scope_id - return(ip, flowinfo, scope_id) + return (ip, flowinfo, scope_id) return None -def ipv6_prefixlength(address): +def ipv6_prefixlength(address: ipaddress.IPv6Address) -> int: prefix_length = 0 for i in range(address.max_prefixlen): if int(address) >> i & 1: diff -Nru python-ifaddr-0.1.7/ifaddr/test_ifaddr.py python-ifaddr-0.2.0/ifaddr/test_ifaddr.py --- python-ifaddr-0.1.7/ifaddr/test_ifaddr.py 2020-06-06 18:56:39.000000000 +0000 +++ python-ifaddr-0.2.0/ifaddr/test_ifaddr.py 2022-06-15 21:40:32.000000000 +0000 @@ -1,7 +1,20 @@ # Copyright (C) 2015 Stefan C. Mueller import unittest + +import pytest + import ifaddr +import ifaddr.netifaces + + +try: + import netifaces +except ImportError: + skip_netifaces = True +else: + skip_netifaces = False + class TestIfaddr(unittest.TestCase): """ @@ -12,7 +25,7 @@ a sanity check for the moment. """ - def test_get_adapters_contains_localhost(self): + def test_get_adapters_contains_localhost(self) -> None: found = False adapters = ifaddr.get_adapters() @@ -22,3 +35,14 @@ found = True self.assertTrue(found, "No adapter has IP 127.0.0.1: %s" % str(adapters)) + + +@pytest.mark.skipif(skip_netifaces, reason='netifaces not installed') +def test_netifaces_compatibility() -> None: + interfaces = ifaddr.netifaces.interfaces() + assert interfaces == netifaces.interfaces() + # TODO: implement those as well + # for interface in interfaces: + # print(interface) + # assert ifaddr.netifaces.ifaddresses(interface) == netifaces.ifaddresses(interface) + # assert ifaddr.netifaces.gateways() == netifaces.gateways() diff -Nru python-ifaddr-0.1.7/ifaddr/_win32.py python-ifaddr-0.2.0/ifaddr/_win32.py --- python-ifaddr-0.1.7/ifaddr/_win32.py 2020-06-06 18:56:39.000000000 +0000 +++ python-ifaddr-0.2.0/ifaddr/_win32.py 2022-06-15 21:40:32.000000000 +0000 @@ -21,10 +21,11 @@ import ctypes from ctypes import wintypes +from typing import Iterable, List import ifaddr._shared as shared -NO_ERROR=0 +NO_ERROR = 0 ERROR_BUFFER_OVERFLOW = 111 MAX_ADAPTER_NAME_LENGTH = 256 MAX_ADAPTER_DESCRIPTION_LENGTH = 128 @@ -32,50 +33,57 @@ AF_UNSPEC = 0 - class SOCKET_ADDRESS(ctypes.Structure): - _fields_ = [('lpSockaddr', ctypes.POINTER(shared.sockaddr)), - ('iSockaddrLength', wintypes.INT)] + _fields_ = [('lpSockaddr', ctypes.POINTER(shared.sockaddr)), ('iSockaddrLength', wintypes.INT)] + class IP_ADAPTER_UNICAST_ADDRESS(ctypes.Structure): pass -IP_ADAPTER_UNICAST_ADDRESS._fields_ = \ - [('Length', wintypes.ULONG), - ('Flags', wintypes.DWORD), - ('Next', ctypes.POINTER(IP_ADAPTER_UNICAST_ADDRESS)), - ('Address', SOCKET_ADDRESS), - ('PrefixOrigin', ctypes.c_uint), - ('SuffixOrigin', ctypes.c_uint), - ('DadState', ctypes.c_uint), - ('ValidLifetime', wintypes.ULONG), - ('PreferredLifetime', wintypes.ULONG), - ('LeaseLifetime', wintypes.ULONG), - ('OnLinkPrefixLength', ctypes.c_uint8), - ] -class IP_ADAPTER_ADDRESSES(ctypes.Structure): - pass -IP_ADAPTER_ADDRESSES._fields_ = [('Length', wintypes.ULONG), - ('IfIndex', wintypes.DWORD), - ('Next', ctypes.POINTER(IP_ADAPTER_ADDRESSES)), - ('AdapterName', ctypes.c_char_p), - ('FirstUnicastAddress', ctypes.POINTER(IP_ADAPTER_UNICAST_ADDRESS)), - ('FirstAnycastAddress', ctypes.POINTER(None)), - ('FirstMulticastAddress', ctypes.POINTER(None)), - ('FirstDnsServerAddress', ctypes.POINTER(None)), - ('DnsSuffix', ctypes.c_wchar_p), - ('Description', ctypes.c_wchar_p), - ('FriendlyName', ctypes.c_wchar_p) - ] +IP_ADAPTER_UNICAST_ADDRESS._fields_ = [ + ('Length', wintypes.ULONG), + ('Flags', wintypes.DWORD), + ('Next', ctypes.POINTER(IP_ADAPTER_UNICAST_ADDRESS)), + ('Address', SOCKET_ADDRESS), + ('PrefixOrigin', ctypes.c_uint), + ('SuffixOrigin', ctypes.c_uint), + ('DadState', ctypes.c_uint), + ('ValidLifetime', wintypes.ULONG), + ('PreferredLifetime', wintypes.ULONG), + ('LeaseLifetime', wintypes.ULONG), + ('OnLinkPrefixLength', ctypes.c_uint8), +] -iphlpapi = ctypes.windll.LoadLibrary("Iphlpapi") +class IP_ADAPTER_ADDRESSES(ctypes.Structure): + pass -def enumerate_interfaces_of_adapter(nice_name, address): + +IP_ADAPTER_ADDRESSES._fields_ = [ + ('Length', wintypes.ULONG), + ('IfIndex', wintypes.DWORD), + ('Next', ctypes.POINTER(IP_ADAPTER_ADDRESSES)), + ('AdapterName', ctypes.c_char_p), + ('FirstUnicastAddress', ctypes.POINTER(IP_ADAPTER_UNICAST_ADDRESS)), + ('FirstAnycastAddress', ctypes.c_void_p), + ('FirstMulticastAddress', ctypes.c_void_p), + ('FirstDnsServerAddress', ctypes.c_void_p), + ('DnsSuffix', ctypes.c_wchar_p), + ('Description', ctypes.c_wchar_p), + ('FriendlyName', ctypes.c_wchar_p), +] + + +iphlpapi = ctypes.windll.LoadLibrary("Iphlpapi") # type: ignore + + +def enumerate_interfaces_of_adapter( + nice_name: str, address: IP_ADAPTER_UNICAST_ADDRESS +) -> Iterable[shared.IP]: # Iterate through linked list and fill list - addresses = [] + addresses = [] # type: List[IP_ADAPTER_UNICAST_ADDRESS] while True: addresses.append(address) if not address.Next: @@ -84,28 +92,31 @@ for address in addresses: ip = shared.sockaddr_to_ip(address.Address.lpSockaddr) + assert ip is not None, f'sockaddr_to_ip({address.Address.lpSockaddr}) returned None' network_prefix = address.OnLinkPrefixLength yield shared.IP(ip, network_prefix, nice_name) -def get_adapters(): +def get_adapters(include_unconfigured: bool = False) -> Iterable[shared.Adapter]: # Call GetAdaptersAddresses() with error and buffer size handling - addressbuffersize = wintypes.ULONG(15*1024) + addressbuffersize = wintypes.ULONG(15 * 1024) retval = ERROR_BUFFER_OVERFLOW while retval == ERROR_BUFFER_OVERFLOW: addressbuffer = ctypes.create_string_buffer(addressbuffersize.value) - retval = iphlpapi.GetAdaptersAddresses(wintypes.ULONG(AF_UNSPEC), - wintypes.ULONG(0), - None, - ctypes.byref(addressbuffer), - ctypes.byref(addressbuffersize)) + retval = iphlpapi.GetAdaptersAddresses( + wintypes.ULONG(AF_UNSPEC), + wintypes.ULONG(0), + None, + ctypes.byref(addressbuffer), + ctypes.byref(addressbuffersize), + ) if retval != NO_ERROR: - raise ctypes.WinError() + raise ctypes.WinError() # type: ignore # Iterate through adapters fill array - address_infos = [] + address_infos = [] # type: List[IP_ADAPTER_ADDRESSES] address_info = IP_ADAPTER_ADDRESSES.from_buffer(addressbuffer) while True: address_infos.append(address_info) @@ -113,19 +124,22 @@ break address_info = address_info.Next[0] - # Iterate through unicast addresses - result = [] + result = [] # type: List[shared.Adapter] for adapter_info in address_infos: - name = adapter_info.AdapterName + # We don't expect non-ascii characters here, so encoding shouldn't matter + name = adapter_info.AdapterName.decode() nice_name = adapter_info.Description index = adapter_info.IfIndex if adapter_info.FirstUnicastAddress: - ips = enumerate_interfaces_of_adapter(adapter_info.FriendlyName, adapter_info.FirstUnicastAddress[0]) + ips = enumerate_interfaces_of_adapter( + adapter_info.FriendlyName, adapter_info.FirstUnicastAddress[0] + ) ips = list(ips) - result.append(shared.Adapter(name, nice_name, ips, - index=index)) + result.append(shared.Adapter(name, nice_name, ips, index=index)) + elif include_unconfigured: + result.append(shared.Adapter(name, nice_name, [], index=index)) return result diff -Nru python-ifaddr-0.1.7/mypy.ini python-ifaddr-0.2.0/mypy.ini --- python-ifaddr-0.1.7/mypy.ini 1970-01-01 00:00:00.000000000 +0000 +++ python-ifaddr-0.2.0/mypy.ini 2022-06-15 21:40:32.000000000 +0000 @@ -0,0 +1,15 @@ +[mypy] +# The warn_unused_configs flag may be useful to debug misspelled section names. +warn_unused_configs = True + +# Shows a warning when returning a value with type 'Any' from +# a function declared with a non-'Any' return type. +warn_return_any = True + +# Disallows defining functions without type annotations +# or with incomplete type annotations. +disallow_untyped_defs = True + +[mypy-netifaces.*] +# Suppresses error messages about imports that cannot be resolved. +ignore_missing_imports = True diff -Nru python-ifaddr-0.1.7/pyproject.toml python-ifaddr-0.2.0/pyproject.toml --- python-ifaddr-0.1.7/pyproject.toml 1970-01-01 00:00:00.000000000 +0000 +++ python-ifaddr-0.2.0/pyproject.toml 2022-06-15 21:40:32.000000000 +0000 @@ -0,0 +1,4 @@ +[tool.black] +line-length = 110 +target_version = ['py37', 'py38', 'py39', 'py310'] +skip_string_normalization = true diff -Nru python-ifaddr-0.1.7/README.rst python-ifaddr-0.2.0/README.rst --- python-ifaddr-0.1.7/README.rst 2020-06-06 18:56:39.000000000 +0000 +++ python-ifaddr-0.2.0/README.rst 2022-06-15 21:40:32.000000000 +0000 @@ -10,14 +10,14 @@ .. image:: https://codecov.io/gh/pydron/ifaddr/branch/master/graph/badge.svg :target: https://codecov.io/gh/pydron/ifaddr -`ifaddr` is a small Python library that allows you to find all the +`ifaddr` is a small Python library that allows you to find all the Ethernet and IP addresses of the computer. It is tested on **Linux**, **OS X**, and **Windows**. Other BSD derivatives like **OpenBSD**, **FreeBSD**, and **NetBSD** should work too, but I haven't personally tested those. **Solaris/Illumos** should also work. This library is open source and released under the MIT License. It works -with Python 2.7 and 3.5+. +with Python 3.7+. You can install it with `pip install ifaddr`. It doesn't need to compile anything, so there shouldn't be any surprises. Even on Windows. @@ -62,10 +62,43 @@ You get both IPv4 and IPv6 addresses. The later complete with flowinfo and scope_id. +If you wish to include network interfaces that do not have a configured IP +addresss, pass the `include_unconfigured` parameter to `get_adapters()`. +Adapters with no configured IP addresses will have an zero-length `ips` +property. For example: + +.. code-block:: python + + import ifaddr + + adapters = ifaddr.get_adapters(include_unconfigured=True) + + for adapter in adapters: + print("IPs of network adapter " + adapter.nice_name) + if adapter.ips: + for ip in adapter.ips: + print(" %s/%s" % (ip.ip, ip.network_prefix)) + else: + print(" No IPs configured") + + --------- Changelog --------- +0.2.0 +----- + +* Added an option to include IP-less adapters, thanks to memory +* Fixed a bug where an interface's name was `bytes`, not `str`, on Windows +* Added an implementation of `netifaces.interfaces()` (available through + `ifaddr.netifaces.interfaces()`) +* Added type hints + +Backwards incompatible/breaking changes: + +* Dropped Python 3.6 support + 0.1.7 ----- @@ -82,3 +115,6 @@ Alastair Houghton develops `netifaces `_ which can do everything this library can, and more. The only drawback is that it needs to be compiled, which can make the installation difficult. + +As of ifaddr 0.2.0 we implement the equivalent of `netifaces.interfaces()`. It's available through +`ifaddr.netifaces.interfaces()`. diff -Nru python-ifaddr-0.1.7/requirements-dev.txt python-ifaddr-0.2.0/requirements-dev.txt --- python-ifaddr-0.1.7/requirements-dev.txt 2020-06-06 18:56:39.000000000 +0000 +++ python-ifaddr-0.2.0/requirements-dev.txt 2022-06-15 21:40:32.000000000 +0000 @@ -1,2 +1,6 @@ +black;implementation_name=="cpython" +mypy;implementation_name=="cpython" +# netifaces only provides 64-bit Windows wheels for Python 3.6 and 3.7 and we use 64-bit CI builds +netifaces;python_version=='3.7' and platform_system=='Windows' or platform_system!='Windows' pytest pytest-cov diff -Nru python-ifaddr-0.1.7/setup.cfg python-ifaddr-0.2.0/setup.cfg --- python-ifaddr-0.1.7/setup.cfg 2020-06-06 18:56:39.000000000 +0000 +++ python-ifaddr-0.2.0/setup.cfg 2022-06-15 21:40:32.000000000 +0000 @@ -2,6 +2,3 @@ source-dir = doc build-dir = build/doc all_files = 1 - -[wheel] -universal = True diff -Nru python-ifaddr-0.1.7/setup.py python-ifaddr-0.2.0/setup.py --- python-ifaddr-0.1.7/setup.py 2020-06-06 18:56:39.000000000 +0000 +++ python-ifaddr-0.2.0/setup.py 2022-06-15 21:40:32.000000000 +0000 @@ -28,28 +28,26 @@ long_description = "" setup( - name = 'ifaddr', - version = '0.1.7', + name='ifaddr', + version='0.2.0', description='Cross-platform network interface and IP address enumeration library', long_description=long_description, author='Stefan C. Mueller', author_email='scm@smurn.org', url='https://github.com/pydron/ifaddr', - packages = find_packages(), + packages=find_packages(), + package_data={'ifaddr': ['py.typed']}, license='MIT', - install_requires = ['ipaddress;python_version<"3.3"'], classifiers=[ 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Topic :: System :: Networking', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', ], keywords=['network interfaces', 'network adapters', 'network addresses', 'IP addresses'], )