atomicshop 3.1.8__py3-none-any.whl → 3.1.10__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.8'
4
+ __version__ = '3.1.10'
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
 
@@ -389,12 +389,12 @@ class GitHubWrapper:
389
389
  """
390
390
  return self.get_the_latest_release_json()['tag_name']
391
391
 
392
- def get_latest_commit_comment(self):
392
+ def get_latest_commit(self) -> dict:
393
393
  """
394
- This function retrieves the commit message (comment) of the latest commit on the specified branch.
394
+ This function retrieves the latest commit on the specified branch.
395
395
  It uses the GitHub API endpoint for commits.
396
396
 
397
- :return: str, the commit message of the latest commit.
397
+ :return: dict, the latest commit data.
398
398
  """
399
399
 
400
400
  headers: dict = self._get_headers()
@@ -413,9 +413,22 @@ class GitHubWrapper:
413
413
 
414
414
  commits = response.json()
415
415
  if not commits:
416
- return None
416
+ return {}
417
+
418
+ latest_commit = commits[0]
419
+ return latest_commit
420
+
421
+ def get_latest_commit_message(self):
422
+ """
423
+ This function retrieves the commit message (comment) of the latest commit on the specified branch.
424
+ It uses the GitHub API endpoint for commits.
417
425
 
418
- commit_message = commits[0].get("commit", {}).get("message", "")
426
+ :return: str, the commit message of the latest commit.
427
+ """
428
+
429
+ latest_commit: dict = self.get_latest_commit()
430
+
431
+ commit_message = latest_commit.get("commit", {}).get("message", "")
419
432
  return commit_message
420
433
 
421
434
 
@@ -432,7 +445,7 @@ def parse_github_args():
432
445
  parser.add_argument(
433
446
  '-p', '--path', type=str, default=None,
434
447
  help="The path to the file/folder inside the repo that we'll do certain actions on.\n"
435
- "Available actions: get_latest_commit_comment, download_path_from_branch.")
448
+ "Available actions: get_latest_commit_message, download_path_from_branch.")
436
449
  parser.add_argument(
437
450
  '-t', '--target_directory', type=str, default=None,
438
451
  help='The target directory to download the file/folder.'
@@ -441,8 +454,11 @@ def parse_github_args():
441
454
  '--pat', type=str, default=None,
442
455
  help='The personal access token to the repo.')
443
456
  parser.add_argument(
444
- '-glcc', '--get_latest_commit_comment', action='store_true', default=False,
445
- help='Sets if the latest commit comment will be printed.')
457
+ '-glcm', '--get_latest_commit_message', action='store_true', default=False,
458
+ help='Sets if the latest commit message comment will be printed.')
459
+ parser.add_argument(
460
+ '-glcj', '--get_latest_commit_json', action='store_true', default=False,
461
+ help='Sets if the latest commit json will be printed.')
446
462
  parser.add_argument(
447
463
  '-db', '--download_branch', action='store_true', default=False,
448
464
  help='Sets if the branch will be downloaded. In conjunction with path, only the path will be downloaded.')
@@ -456,7 +472,8 @@ def github_wrapper_main(
456
472
  path: str = None,
457
473
  target_directory: str = None,
458
474
  pat: str = None,
459
- get_latest_commit_comment: bool = False,
475
+ get_latest_commit_json: bool = False,
476
+ get_latest_commit_message: bool = False,
460
477
  download_branch: bool = False
461
478
  ):
462
479
  """
@@ -467,7 +484,8 @@ def github_wrapper_main(
467
484
  :param path: str, the path to the file/folder for which the commit message should be retrieved.
468
485
  :param target_directory: str, the target directory to download the file/folder.
469
486
  :param pat: str, the personal access token to the repo.
470
- :param get_latest_commit_comment: bool, sets if the latest commit comment will be printed.
487
+ :param get_latest_commit_json: bool, sets if the latest commit json will be printed.
488
+ :param get_latest_commit_message: bool, sets if the latest commit message comment will be printed.
471
489
  :param download_branch: bool, sets if the branch will be downloaded. In conjunction with path, only the path will be
472
490
  downloaded.
473
491
  :return:
@@ -475,11 +493,16 @@ def github_wrapper_main(
475
493
 
476
494
  git_wrapper = GitHubWrapper(repo_url=repo_url, branch=branch, path=path, pat=pat)
477
495
 
478
- if get_latest_commit_comment:
479
- commit_comment = git_wrapper.get_latest_commit_comment()
496
+ if get_latest_commit_message:
497
+ commit_comment = git_wrapper.get_latest_commit_message()
480
498
  print_api(commit_comment)
481
499
  return 0
482
500
 
501
+ if get_latest_commit_json:
502
+ latest_commit_json = git_wrapper.get_latest_commit()
503
+ print_api(latest_commit_json)
504
+ return 0
505
+
483
506
  if download_branch:
484
507
  git_wrapper.download_and_extract_branch(
485
508
  target_directory=target_directory, download_each_file=False, archive_remove_first_directory=True)
@@ -496,6 +519,7 @@ def github_wrapper_main_with_args():
496
519
  path=args.path,
497
520
  target_directory=args.target_directory,
498
521
  pat=args.pat,
499
- get_latest_commit_comment=args.get_latest_commit_comment,
522
+ get_latest_commit_json=args.get_latest_commit_json,
523
+ get_latest_commit_message=args.get_latest_commit_message,
500
524
  download_branch=args.download_branch
501
525
  )
@@ -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
@@ -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.8
3
+ Version: 3.1.10
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=EsaT4vSXGAXzBfTEAtjwOzP6zvlGXekljCTQOrMqSTA,122
1
+ atomicshop/__init__.py,sha256=4UwVcmMtjfVbDhhZfkGYNJuONJup1-ZKYf0TXOv0NFU,123
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
@@ -197,8 +197,9 @@ atomicshop/wrappers/astw.py,sha256=VkYfkfyc_PJLIOxByT6L7B8uUmKY6-I8XGZl4t_z828,4
197
197
  atomicshop/wrappers/configparserw.py,sha256=JwDTPjZoSrv44YKwIRcjyUnpN-FjgXVfMqMK_tJuSgU,22800
198
198
  atomicshop/wrappers/cryptographyw.py,sha256=LfzTnwvJE03G6WZryOOf43VKhhnyMakzHpn8DPPCoy4,13252
199
199
  atomicshop/wrappers/ffmpegw.py,sha256=wcq0ZnAe0yajBOuTKZCCaKI7CDBjkq7FAgdW5IsKcVE,6031
200
- atomicshop/wrappers/githubw.py,sha256=vaVQg0pkHY63_TfIbCoWmHDPtI2rDPAqMYwsOl3GN78,22559
200
+ atomicshop/wrappers/githubw.py,sha256=DrFF_oN-rulPQV1iKgVzZadCjuYuCC5eKAjZp_3YD0g,23476
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
@@ -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.8.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
340
- atomicshop-3.1.8.dist-info/METADATA,sha256=evAROFKYJtONn4b-_ij9PzDB6jKZW3hMlXGRHP_j4Gk,10662
341
- atomicshop-3.1.8.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
342
- atomicshop-3.1.8.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
343
- atomicshop-3.1.8.dist-info/RECORD,,
339
+ atomicshop/wrappers/winregw/winreg_network.py,sha256=ih0BVNwByLvf9F_Lac4EdmDYYJA3PzMvmG0PieDZrsE,9905
340
+ atomicshop-3.1.10.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
341
+ atomicshop-3.1.10.dist-info/METADATA,sha256=leFjMfEKSkTJWSkmGTX8QxNL2t-j2IXQc4yvijOi6YY,10663
342
+ atomicshop-3.1.10.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
343
+ atomicshop-3.1.10.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
344
+ atomicshop-3.1.10.dist-info/RECORD,,