diff --git a/.gitignore b/.gitignore
index cef5098..7b57817 100644
--- a/.gitignore
+++ b/.gitignore
@@ -153,9 +153,4 @@ dmypy.json
# Cython debug symbols
cython_debug/
-# PyCharm
-# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
-# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
-# and can be added to the global gitignore or merged into this file. For a more nuclear
-# option (not recommended) you can uncomment the following to ignore the entire idea folder.
-#.idea/
+tests/test_root
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..dfcb70e
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,11 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+/copilot.*
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..1b70b27
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..be59e71
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/python-common-utility.iml b/.idea/python-common-utility.iml
new file mode 100644
index 0000000..79c1f95
--- /dev/null
+++ b/.idea/python-common-utility.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..5831342
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common_utility/__init__.py b/common_utility/__init__.py
index 4def908..bf395b4 100644
--- a/common_utility/__init__.py
+++ b/common_utility/__init__.py
@@ -4,3 +4,4 @@
from .fileDownloader import *
from .configLoader import *
from .rateLimiter import *
+from .interfaceResolver import *
diff --git a/common_utility/interfaceResolver.py b/common_utility/interfaceResolver.py
new file mode 100644
index 0000000..8ae17b2
--- /dev/null
+++ b/common_utility/interfaceResolver.py
@@ -0,0 +1,37 @@
+from enum import Enum
+
+import netifaces
+from context_logger import get_logger
+
+log = get_logger('InterfaceResolver')
+
+
+class AddressFamily(Enum):
+ IPv4 = netifaces.AF_INET
+ IPv6 = netifaces.AF_INET6
+ MAC = netifaces.AF_LINK
+
+
+class IInterfaceResolver:
+
+ def resolve(self, interface: str, family: AddressFamily = AddressFamily.IPv4) -> str | None:
+ raise NotImplementedError()
+
+
+class InterfaceResolver(IInterfaceResolver):
+
+ def resolve(self, interface: str, family: AddressFamily = AddressFamily.IPv4) -> str | None:
+ interfaces = netifaces.interfaces()
+ if interface in interfaces:
+ inet_address = netifaces.ifaddresses(interface).get(family.value)
+ if inet_address and len(inet_address) > 0:
+ if address := inet_address[0].get('addr'):
+ return address
+ else:
+ log.error('Address not found for interface', interface=interface, family=family.name)
+ else:
+ log.error('Address family not found for interface', interface=interface, family=family.name)
+ else:
+ log.error('Selected interface not found', interface=interface, interfaces=interfaces)
+
+ return None
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..56c43d4
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,37 @@
+[project]
+name = "python-common-utility"
+description = "Common utility packages for Python projects"
+authors = [
+ { name = "Ferenc Nandor Janky & Attila Gombos", email = "info@effective-range.com" }
+]
+maintainers = [
+ { name = "Ferenc Nandor Janky & Attila Gombos", email = "info@effective-range.com" }
+]
+dependencies = [
+ "requests",
+ "pydantic",
+ "jinja2",
+ "netifaces",
+ "python-context-logger @ git+https://github.com/EffectiveRange/python-context-logger.git@latest",
+]
+dynamic = ["version"]
+
+[tool.setuptools]
+package-dir = {"" = "."}
+packages = ["common_utility", "test_utility"]
+
+[tool.setuptools.package-data]
+hello = ["py.typed"]
+
+[build-system]
+requires = ["setuptools>=61", "setuptools_scm"]
+build-backend = "setuptools.build_meta"
+
+[tool.setuptools_scm]
+version_scheme = "guess-next-dev"
+local_scheme = "node-and-date"
+
+[tool.pytest]
+addopts = ["--verbose", "--capture=no"]
+python_files = ["*Test.py"]
+python_classes = ["*Test"]
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 8f63338..0000000
--- a/setup.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from setuptools import setup, find_packages
-
-setup(
- name='python-common-utility',
- description='Common utility packages for Python projects',
- author='Ferenc Nandor Janky & Attila Gombos',
- author_email='info@effective-range.com',
- packages=find_packages(exclude=['tests']),
- package_data={'common_utility': ['py.typed'], 'test_utility': ['py.typed']},
- use_scm_version=True,
- setup_requires=["setuptools_scm"],
- install_requires=[
- 'requests',
- 'pydantic',
- 'jinja2',
- 'python-context-logger@git+https://github.com/EffectiveRange/python-context-logger.git@latest',
- ],
-)
diff --git a/tests/interfaceResolverTest.py b/tests/interfaceResolverTest.py
new file mode 100644
index 0000000..c543b54
--- /dev/null
+++ b/tests/interfaceResolverTest.py
@@ -0,0 +1,135 @@
+import unittest
+from unittest import TestCase
+from unittest.mock import patch
+
+from context_logger import setup_logging
+
+from common_utility import InterfaceResolver, AddressFamily
+
+
+class InterfaceResolverTest(TestCase):
+ resolver = InterfaceResolver()
+
+ @classmethod
+ def setUpClass(cls):
+ setup_logging('python-common-utility', 'DEBUG', warn_on_overwrite=False)
+
+ def setUp(self):
+ print()
+
+ @patch('common_utility.interfaceResolver.netifaces')
+ def test_returns_ipv4_address(self, mock_netifaces):
+ # Given
+ self._setup_mocks(mock_netifaces)
+
+ # When
+ address = self.resolver.resolve('lo')
+
+ # Then
+ self.assertEqual('127.0.0.1', address)
+
+ @patch('common_utility.interfaceResolver.netifaces')
+ def test_returns_ipv6_address(self, mock_netifaces):
+ # Given
+ self._setup_mocks(mock_netifaces)
+
+ # When
+ address = self.resolver.resolve('lo', AddressFamily.IPv6)
+
+ # Then
+ self.assertEqual('::1', address)
+
+ @patch('common_utility.interfaceResolver.netifaces')
+ def test_returns_mac_address(self, mock_netifaces):
+ # Given
+ self._setup_mocks(mock_netifaces)
+
+ # When
+ address = self.resolver.resolve('lo', AddressFamily.MAC)
+
+ # Then
+ self.assertEqual('00:00:00:00:00:00', address)
+
+ @patch('common_utility.interfaceResolver.netifaces')
+ def test_returns_none_when_interface_not_exists(self, mock_netifaces):
+ # Given
+ self._setup_mocks(mock_netifaces)
+ mock_netifaces.interfaces.return_value = ['lo', 'wlan0']
+
+ # When
+ address = self.resolver.resolve('eth0')
+
+ # Then
+ self.assertIsNone(address)
+
+ @patch('common_utility.interfaceResolver.netifaces')
+ def test_returns_none_when_address_family_not_exists_for_interface(self, mock_netifaces):
+ # Given
+ self._setup_mocks(mock_netifaces)
+ mock_netifaces.ifaddresses.return_value = {
+ 10: [{'addr': '::1'}],
+ 17: [{'addr': '00:00:00:00:00:00'}],
+ }
+
+ # When
+ address = self.resolver.resolve('lo')
+
+ # Then
+ self.assertIsNone(address)
+
+ @patch('common_utility.interfaceResolver.netifaces')
+ def test_returns_none_when_address_not_exists_for_address_family(self, mock_netifaces):
+ # Given
+ self._setup_mocks(mock_netifaces)
+ mock_netifaces.ifaddresses.return_value = {
+ 2: [],
+ }
+
+ # When
+ address = self.resolver.resolve('wlan0')
+
+ # Then
+ self.assertIsNone(address)
+
+ @patch('common_utility.interfaceResolver.netifaces')
+ def test_returns_none_when_addr_key_not_exists_for_address(self, mock_netifaces):
+ # Given
+ self._setup_mocks(mock_netifaces)
+ mock_netifaces.ifaddresses.return_value = {
+ 2: [{}],
+ }
+
+ # When
+ address = self.resolver.resolve('eth0')
+
+ # Then
+ self.assertIsNone(address)
+
+ @patch('common_utility.interfaceResolver.netifaces')
+ def test_returns_none_when_addr_value_not_exists_for_address(self, mock_netifaces):
+ # Given
+ self._setup_mocks(mock_netifaces)
+ mock_netifaces.ifaddresses.return_value = {
+ 2: [{'addr': ''}],
+ }
+
+ # When
+ address = self.resolver.resolve('eth0')
+
+ # Then
+ self.assertIsNone(address)
+
+ def _setup_mocks(self, mock_netifaces):
+ mock_netifaces.interfaces.return_value = ['lo', 'eth0', 'wlan0']
+ mock_netifaces.AF_INET = 2
+ mock_netifaces.AF_INET6 = 10
+ mock_netifaces.AF_INET6 = 17
+ mock_netifaces.ifaddresses.return_value = {
+ 2: [{'addr': '127.0.0.1'}],
+ 10: [{'addr': '::1'}],
+ 17: [{'addr': '00:00:00:00:00:00'}],
+ }
+
+
+if __name__ == '__main__':
+ unittest.main()