atomicshop 3.1.7__py3-none-any.whl → 3.1.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of atomicshop might be problematic. Click here for more details.

atomicshop/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """Atomic Basic functions and classes to make developer life easier"""
2
2
 
3
3
  __author__ = "Den Kras"
4
- __version__ = '3.1.7'
4
+ __version__ = '3.1.9'
atomicshop/dns.py CHANGED
@@ -1,3 +1,4 @@
1
+ import socket
1
2
  import argparse
2
3
 
3
4
  # noinspection PyPackageRequirements
@@ -6,7 +7,7 @@ import dns.resolver
6
7
  from . import print_api
7
8
  from .permissions import permissions
8
9
  from .wrappers.pywin32w.wmis import win32networkadapter
9
- from .wrappers.winregw import winreg_network
10
+ from .wrappers import netshw
10
11
 
11
12
 
12
13
  # Defining Dictionary of Numeric to String DNS Query Types.
@@ -73,7 +74,16 @@ def get_default_dns_gateway() -> tuple[bool, list[str]]:
73
74
  :return: tuple(is dynamic boolean, list of DNS server IPv4s).
74
75
  """
75
76
 
76
- is_dynamic, dns_servers = winreg_network.get_default_dns_gateway()
77
+ interfaces_with_dns_settings: list[dict] = netshw.get_netsh_ipv4()
78
+
79
+ default_interface_ipv4 = socket.gethostbyname(socket.gethostname())
80
+ is_dynamic, dns_servers = None, None
81
+ for interface in interfaces_with_dns_settings:
82
+ if default_interface_ipv4 in interface['ip_addresses']:
83
+ is_dynamic = interface['dns_mode']
84
+ dns_servers = interface['dns_servers']
85
+ break
86
+
77
87
  return is_dynamic, dns_servers
78
88
 
79
89
 
atomicshop/networks.py CHANGED
@@ -273,6 +273,9 @@ def generate_unused_ipv4_addresses_from_ip(
273
273
  if not skip_ips:
274
274
  skip_ips = []
275
275
 
276
+ # Remove duplicate IPs from the skip_ips list, loses order.
277
+ skip_ips = list(set(skip_ips))
278
+
276
279
  # Add the IP address to the list of IPs to skip.
277
280
  if ip_address not in skip_ips:
278
281
  skip_ips = [ip_address] + skip_ips
@@ -356,6 +359,9 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
356
359
  will be generated from it. Unused addresses decided by pinging them.
357
360
  Same for the subnet mask.
358
361
 
362
+ While generating the IPs, the function will skip the already existing IPs in the adapter, like default gateway
363
+ and DNS servers.
364
+
359
365
  :param number_of_ips: int, number of IPs to generate in addition to the IPv4s that already exist in the adapter.
360
366
  Or you add the IPs and masks to the adapter with the parameters virtual_ipv4s_to_add and virtual_ipv4_masks_to_add.
361
367
 
@@ -409,7 +415,7 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
409
415
  ip_address=current_ipv4s[0],
410
416
  mask=current_ipv4_masks[0],
411
417
  number_of_ips=number_of_ips,
412
- skip_ips=current_ipv4s
418
+ skip_ips=current_ipv4s + default_adapter_info['default_gateways'] + default_adapter_info['dns_gateways']
413
419
  )
414
420
  elif virtual_ipv4s_to_add and virtual_ipv4_masks_to_add:
415
421
  ips_to_assign = virtual_ipv4s_to_add
@@ -0,0 +1,150 @@
1
+ import subprocess
2
+ import re
3
+ from typing import List, Dict, Any
4
+
5
+ # ── regex helpers ─────────────────────────────────────────────────────────
6
+ IP_PATTERN = r'(?:\d{1,3}\.){3}\d{1,3}'
7
+ RE_ADAPTER_HEADER = re.compile(r'Configuration for interface +"([^"]+)"', re.I)
8
+ RE_NUMERIC = re.compile(r'\d+')
9
+ RE_SUBNET = re.compile(rf'(?P<prefix>{IP_PATTERN}/\d+)\s+\(mask\s+(?P<mask>{IP_PATTERN})', re.I)
10
+ RE_IP = re.compile(IP_PATTERN)
11
+
12
+
13
+ def _get_netsh_show_config() -> str:
14
+ """Run `netsh interface ipv4 show config` and return the raw text."""
15
+ return subprocess.check_output(
16
+ ["netsh", "interface", "ipv4", "show", "config"],
17
+ text=True, encoding="utf-8", errors="ignore"
18
+ )
19
+
20
+
21
+ def get_netsh_ipv4() -> List[Dict[str, Any]]:
22
+ """
23
+ Parse *all* data from `netsh interface ipv4 show config`.
24
+
25
+ Returns a list of dicts – one per adapter – with keys:
26
+ interface, dhcp_enabled, ip_addresses, subnet_prefixes, subnet_masks,
27
+ default_gateways, gateway_metric, interface_metric,
28
+ dns_mode, dns_servers, wins_mode, wins_servers
29
+ """
30
+ config_text = _get_netsh_show_config()
31
+
32
+ adapters: List[Dict[str, Any]] = []
33
+ adapter: Dict[str, Any] | None = None
34
+
35
+ # Track whether we’re in continuation lines of DNS / WINS lists
36
+ dns_list_type: str | None = None # 'static' | 'dynamic' | None
37
+ wins_list_type: str | None = None
38
+
39
+ for raw_line in config_text.splitlines():
40
+ line = raw_line.strip()
41
+
42
+ # 1) New adapter block ------------------------------------------------
43
+ header_match = RE_ADAPTER_HEADER.search(line)
44
+ if header_match:
45
+ # Flush the previous adapter, if any
46
+ if adapter:
47
+ adapters.append(adapter)
48
+
49
+ iface_name = header_match.group(1)
50
+ adapter = {
51
+ 'interface_name' : iface_name,
52
+ 'dhcp_enabled' : None,
53
+ 'gateway_metric' : None,
54
+ 'interface_metric' : None,
55
+ 'dns_mode' : 'unknown',
56
+ 'wins_mode' : 'unknown',
57
+ 'dns_servers' : [],
58
+ 'wins_servers' : [],
59
+ 'ip_addresses' : [],
60
+ 'subnet_prefixes' : [],
61
+ 'subnet_masks' : [],
62
+ 'default_gateways' : [],
63
+ }
64
+ dns_list_type = wins_list_type = None
65
+ continue
66
+
67
+ if adapter is None: # skip prologue lines
68
+ continue
69
+
70
+ # 2) DHCP flag -------------------------------------------------------
71
+ if line.startswith("DHCP enabled"):
72
+ adapter['dhcp_enabled'] = "yes" in line.lower()
73
+ continue
74
+
75
+ # 3) IP addresses ----------------------------------------------------
76
+ if line.startswith("IP Address"):
77
+ adapter['ip_addresses'].extend(RE_IP.findall(line))
78
+ continue
79
+
80
+ # 4) Subnet prefix & mask -------------------------------------------
81
+ if line.startswith("Subnet Prefix"):
82
+ subnet_match = RE_SUBNET.search(line)
83
+ if subnet_match:
84
+ adapter['subnet_prefixes'].append(subnet_match.group('prefix'))
85
+ adapter['subnet_masks'].append(subnet_match.group('mask'))
86
+ continue
87
+
88
+ # 5) Gateway & metrics ----------------------------------------------
89
+ if line.startswith("Default Gateway"):
90
+ adapter['default_gateways'].extend(RE_IP.findall(line))
91
+ continue
92
+ if line.startswith("Gateway Metric"):
93
+ metric = RE_NUMERIC.search(line)
94
+ if metric:
95
+ adapter['gateway_metric'] = int(metric.group())
96
+ continue
97
+ if line.startswith("InterfaceMetric"):
98
+ metric = RE_NUMERIC.search(line)
99
+ if metric:
100
+ adapter['interface_metric'] = int(metric.group())
101
+ continue
102
+
103
+ # 6) DNS header lines -----------------------------------------------
104
+ if "DNS servers configured through DHCP" in line:
105
+ adapter['dns_mode'] = 'dynamic'
106
+ adapter['dns_servers'].extend(RE_IP.findall(line))
107
+ dns_list_type = 'dynamic'
108
+ continue
109
+ if "Statically Configured DNS Servers" in line:
110
+ adapter['dns_mode'] = 'static'
111
+ adapter['dns_servers'].extend(RE_IP.findall(line))
112
+ dns_list_type = 'static'
113
+ continue
114
+
115
+ # 7) WINS header lines ----------------------------------------------
116
+ if "WINS servers configured through DHCP" in line:
117
+ adapter['wins_mode'] = 'dynamic'
118
+ adapter['wins_servers'].extend(RE_IP.findall(line))
119
+ wins_list_type = 'dynamic'
120
+ continue
121
+ if line.startswith(("Primary WINS Server", "Secondary WINS Server")):
122
+ adapter['wins_mode'] = 'static'
123
+ adapter['wins_servers'].extend(RE_IP.findall(line))
124
+ wins_list_type = 'static'
125
+ continue
126
+
127
+ # 8) Continuation lines for DNS / WINS -------------------------------
128
+ if dns_list_type and RE_IP.search(line):
129
+ adapter['dns_servers'].extend(RE_IP.findall(line))
130
+ continue
131
+ if wins_list_type and RE_IP.search(line):
132
+ adapter['wins_servers'].extend(RE_IP.findall(line))
133
+ continue
134
+
135
+ # Flush the final adapter block
136
+ if adapter:
137
+ adapters.append(adapter)
138
+
139
+ # # ── post-process: detect “mixed” modes ----------------------------------
140
+ # NOT SURE THIS PART WORKS AS INTENDED!!!
141
+ # for ad in adapters:
142
+ # if ad['dns_mode'] == 'dynamic' and ad['dns_servers']:
143
+ # # If both headers appeared the last one wins; treat that as mixed
144
+ # if any(k in ad['dns_servers'] for k in ad['default_gateways']):
145
+ # ad['dns_mode'] = 'mixed'
146
+ # if ad['wins_mode'] == 'dynamic' and ad['wins_servers']:
147
+ # if any(ip not in ad['wins_servers'] for ip in ad['wins_servers']):
148
+ # ad['wins_mode'] = 'mixed'
149
+
150
+ return adapters
@@ -3,6 +3,7 @@ import socket
3
3
  import time
4
4
 
5
5
  from win32com.client import CDispatch
6
+ import pywintypes
6
7
 
7
8
  from . import wmi_helpers, win32networkadapter
8
9
  from ...psutilw import psutil_networks
@@ -137,7 +138,18 @@ def set_static_ips(
137
138
  if not masks or len(ips) != len(masks):
138
139
  raise ValueError("ipv4 and masks must be lists of equal length")
139
140
 
140
- in_params = network_config.Methods_("EnableStatic").InParameters.SpawnInstance_()
141
+ # # refresh the instance – state may have changed after the previous call
142
+ # network_config = network_config.Path_.GetObject_()
143
+
144
+ try:
145
+ in_params = network_config.Methods_("EnableStatic").InParameters.SpawnInstance_()
146
+ except pywintypes.com_error as e:
147
+ if e.excepinfo[1] == 'SWbemMethodSet' and 'Not found' in e.excepinfo[2]:
148
+ raise RuntimeError(f"Probably the adapter is already set to static non-DHCP.\n"
149
+ f"Failed to get [EnableStatic] method parameters: {e}\n")
150
+ else:
151
+ raise
152
+
141
153
  in_params.IPAddress = ips
142
154
  in_params.SubnetMask = masks
143
155
 
@@ -142,7 +142,8 @@ def call_method(
142
142
  :return: WMI method object.
143
143
  """
144
144
 
145
- if not (isinstance(value, EmptyValue) and isinstance(value, tuple) and isinstance(value, dict)):
145
+ # Assign the single value to a tuple if it is not already a tuple or dict and not an EmptyValue.
146
+ if not isinstance(value, EmptyValue) and not (isinstance(value, tuple) and isinstance(value, dict)):
146
147
  value = (value,)
147
148
 
148
149
  # Get the method instance out of the WMI object.
@@ -137,8 +137,13 @@ def get_network_connections_details(get_enum_info: bool = True) -> dict:
137
137
  return adapter_details
138
138
 
139
139
 
140
- def get_default_dns_gateway() -> tuple[bool, list[str]]:
140
+ def _get_default_dns_gateway() -> tuple[bool, list[str]]:
141
141
  """
142
+ NOTICE: This stopped working from the last Windows update on 11.06.2025.
143
+ They moved it to 'ProfileNameServer', anyway Since Windows 8 the recommended API has been the WMI
144
+ NetTCPIP CIM provider (MSFT_DNSClientServerAddress) - didn't test it though.
145
+ Just moved to netsh wrapping for now.
146
+
142
147
  Get the default DNS gateway from the Windows registry.
143
148
 
144
149
  :return: tuple(is dynamic boolean, list of DNS server IPv4s).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: atomicshop
3
- Version: 3.1.7
3
+ Version: 3.1.9
4
4
  Summary: Atomic functions and classes to make developer life easier
5
5
  Author: Denis Kras
6
6
  License: MIT License
@@ -1,4 +1,4 @@
1
- atomicshop/__init__.py,sha256=kQEi2B2TC6t8SrtWizdi3xNq0Bk0eCdVFgVx0yYYTAA,122
1
+ atomicshop/__init__.py,sha256=EQJIgq1QRIHAmPvbH_Qo_f07M3JEn4xL2sUgoWRi8Q4,122
2
2
  atomicshop/_basics_temp.py,sha256=6cu2dd6r2dLrd1BRNcVDKTHlsHs_26Gpw8QS6v32lQ0,3699
3
3
  atomicshop/_create_pdf_demo.py,sha256=Yi-PGZuMg0RKvQmLqVeLIZYadqEZwUm-4A9JxBl_vYA,3713
4
4
  atomicshop/_patch_import.py,sha256=ENp55sKVJ0e6-4lBvZnpz9PQCt3Otbur7F6aXDlyje4,6334
@@ -10,7 +10,7 @@ atomicshop/console_output.py,sha256=AOSJjrRryE97PAGtgDL03IBtWSi02aNol8noDnW3k6M,
10
10
  atomicshop/console_user_response.py,sha256=OHcjuzWAys6WmfRnMIU_nkJA634kKmJh6T8w1VtUTJM,2714
11
11
  atomicshop/datetimes.py,sha256=IQZ66lmta-ZqxYbyHzm_9eugbJFSilXK1e0kfMgoXGg,18371
12
12
  atomicshop/diff_check.py,sha256=vxTDccVbGZHEge6Ja9_ArLWwslOUgIoJAdYPylh4cZg,27176
13
- atomicshop/dns.py,sha256=FVrXYk-r3zH6HbUyB3wvhhWM-J0RKmd_CCO50TXVq1A,6831
13
+ atomicshop/dns.py,sha256=XB0tijVi1bxWLWKV9hPzpt75jK4SrGbZCV5VJbiiQ74,7185
14
14
  atomicshop/domains.py,sha256=Rxu6JhhMqFZRcoFs69IoEd1PtYca0lMCG6F1AomP7z4,3197
15
15
  atomicshop/emails.py,sha256=I0KyODQpIMEsNRi9YWSOL8EUPBiWyon3HRdIuSj3AEU,1410
16
16
  atomicshop/file_types.py,sha256=-0jzQMRlmU1AP9DARjk-HJm1tVE22E6ngP2mRblyEjY,763
@@ -23,7 +23,7 @@ atomicshop/http_parse.py,sha256=1Tna9YbOM0rE3t6i_M-klBlwd1KNSA9skA_BqKGXDFc,1186
23
23
  atomicshop/inspect_wrapper.py,sha256=sGRVQhrJovNygHTydqJj0hxES-aB2Eg9KbIk3G31apw,11429
24
24
  atomicshop/ip_addresses.py,sha256=penRFeJ1-LDVTko4Q0EwK4JiN5cU-KzCBR2VXg9qbUY,1238
25
25
  atomicshop/keyboard_press.py,sha256=1W5kRtOB75fulVx-uF2yarBhW0_IzdI1k73AnvXstk0,452
26
- atomicshop/networks.py,sha256=OSFaEUu8otxCkNq9oMbYQpj5SLzHie2r5uJDiyLR19U,18685
26
+ atomicshop/networks.py,sha256=xOU_lDf6Rct178W7EB80-AFMgu9Nnh6l7GgjA9z9Jtg,19010
27
27
  atomicshop/on_exit.py,sha256=9XlOnzoAG8zlI8wBF4AB8hyrC6Q1b84gkhqpAhhdN9g,6977
28
28
  atomicshop/pbtkmultifile_argparse.py,sha256=aEk8nhvoQVu-xyfZosK3ma17CwIgOjzO1erXXdjwtS4,4574
29
29
  atomicshop/print_api.py,sha256=SJNQIMqSLlYaPtjHnALySAI-jQYuYHOCGgfP7oe96fU,10957
@@ -199,6 +199,7 @@ atomicshop/wrappers/cryptographyw.py,sha256=LfzTnwvJE03G6WZryOOf43VKhhnyMakzHpn8
199
199
  atomicshop/wrappers/ffmpegw.py,sha256=wcq0ZnAe0yajBOuTKZCCaKI7CDBjkq7FAgdW5IsKcVE,6031
200
200
  atomicshop/wrappers/githubw.py,sha256=vaVQg0pkHY63_TfIbCoWmHDPtI2rDPAqMYwsOl3GN78,22559
201
201
  atomicshop/wrappers/msiw.py,sha256=GQLqud72nfex3kvO1bJSruNriCYTYX1_G1gSf1MPkIA,6118
202
+ atomicshop/wrappers/netshw.py,sha256=8WE_576XiiHykwFuE-VkCx5CydMpFlztX4frlEteCtI,6350
202
203
  atomicshop/wrappers/numpyw.py,sha256=sBV4gSKyr23kXTalqAb1oqttzE_2XxBooCui66jbAqc,1025
203
204
  atomicshop/wrappers/olefilew.py,sha256=biD5m58rogifCYmYhJBrAFb9O_Bn_spLek_9HofLeYE,2051
204
205
  atomicshop/wrappers/pipw.py,sha256=mu4jnHkSaYNfpBiLZKMZxEX_E2LqW5BVthMZkblPB_c,1317
@@ -313,10 +314,10 @@ atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py,sha25
313
314
  atomicshop/wrappers/pywin32w/win_event_log/subscribes/schannel_logging.py,sha256=8nxIcNcbeEuvoBwhujgh7-oIpL9A6J-gg1NM8hOGAVA,3442
314
315
  atomicshop/wrappers/pywin32w/wmis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
315
316
  atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py,sha256=Kn2gQsyEquM5QQkdF2Vtr1MqK5D7uo6EFi-cxt52Q0g,4800
316
- atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py,sha256=Iz-p--cE2iP6wyD9ceSF3scMMvdXC5fF-VKP1v-iIDo,10813
317
+ atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py,sha256=CnHFac1FT6cnH1VwHUyD6vZr6FKPxY8fpbj-1s2_Mzg,11327
317
318
  atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py,sha256=iao4OK6u097uRacw6vQuya1kOtJzLA1IM4cgo6UCWLM,3941
318
319
  atomicshop/wrappers/pywin32w/wmis/win32process.py,sha256=qMzXtJ5hBZ5ydAyqpDbSx0nO2RJQL95HdmV5SzNKMhk,6826
319
- atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py,sha256=H8QIiHlm9ei8GSudKECFpEc1qny_1PNnVxZuIDK9xfU,8604
320
+ atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py,sha256=3SA6hNgw-iBWYl3l1t3RBxRYB4BH-FbTs9gzmcg_Q8w,8710
320
321
  atomicshop/wrappers/socketw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
321
322
  atomicshop/wrappers/socketw/accepter.py,sha256=4I9ORugRDvwaqSzm_gWSjZnRwQGY8hDTlCdsYHwH_ZE,2377
322
323
  atomicshop/wrappers/socketw/base.py,sha256=EcosGkD8VzgBY3GeIHDSG29ThQfXwg3-GQPmBTAqTdw,3048
@@ -335,9 +336,9 @@ atomicshop/wrappers/socketw/ssl_base.py,sha256=kmiif84kMhBr5yjQW17p935sfjR5JKG0L
335
336
  atomicshop/wrappers/socketw/statistics_csv.py,sha256=WcNyaqEZ82S5-f3kzqi1nllNT2Nd2P_zg8HqCc7vW4s,4120
336
337
  atomicshop/wrappers/winregw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
337
338
  atomicshop/wrappers/winregw/winreg_installed_software.py,sha256=Qzmyktvob1qp6Tjk2DjLfAqr_yXV0sgWzdMW_9kwNjY,2345
338
- atomicshop/wrappers/winregw/winreg_network.py,sha256=3Ts1sVqSUiCDsHRHwJCbiZ9EYvv2ELGxF0Y_pibGU4k,9596
339
- atomicshop-3.1.7.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
340
- atomicshop-3.1.7.dist-info/METADATA,sha256=lpnnJWxz69LaEjSCOjW-sp7tPzkbK3Ee2xefjakUyDI,10662
341
- atomicshop-3.1.7.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
342
- atomicshop-3.1.7.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
343
- atomicshop-3.1.7.dist-info/RECORD,,
339
+ atomicshop/wrappers/winregw/winreg_network.py,sha256=ih0BVNwByLvf9F_Lac4EdmDYYJA3PzMvmG0PieDZrsE,9905
340
+ atomicshop-3.1.9.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
341
+ atomicshop-3.1.9.dist-info/METADATA,sha256=-CO4-bPxF4SKM5Gi0aC0Sbqkl0s974zx0o5bR5gnBWE,10662
342
+ atomicshop-3.1.9.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
343
+ atomicshop-3.1.9.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
344
+ atomicshop-3.1.9.dist-info/RECORD,,