atomicshop 2.19.12__py3-none-any.whl → 2.19.14__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 +1 -1
- atomicshop/basics/classes.py +70 -0
- atomicshop/process_poller/simple_process_pool.py +81 -14
- atomicshop/wrappers/playwrightw/scenarios.py +11 -10
- {atomicshop-2.19.12.dist-info → atomicshop-2.19.14.dist-info}/METADATA +1 -1
- {atomicshop-2.19.12.dist-info → atomicshop-2.19.14.dist-info}/RECORD +9 -9
- {atomicshop-2.19.12.dist-info → atomicshop-2.19.14.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.19.12.dist-info → atomicshop-2.19.14.dist-info}/WHEEL +0 -0
- {atomicshop-2.19.12.dist-info → atomicshop-2.19.14.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py
CHANGED
atomicshop/basics/classes.py
CHANGED
|
@@ -10,6 +10,8 @@ from ..file_io.file_io import read_file
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
"""
|
|
13
|
+
Using logger in the class only once during the import of the module.
|
|
14
|
+
|
|
13
15
|
class ParserParent:
|
|
14
16
|
# Initializing the logger in the "class variable" section will leave the instance of the logger initiated
|
|
15
17
|
# and the rest of the instances of the class will use the same logger.
|
|
@@ -28,6 +30,58 @@ class ParserParent:
|
|
|
28
30
|
"""
|
|
29
31
|
|
|
30
32
|
|
|
33
|
+
"""
|
|
34
|
+
Using Base class for easier interfacing on subclasses.
|
|
35
|
+
|
|
36
|
+
recognition/recognition_base.py:
|
|
37
|
+
from abc import abstractmethod
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Recognizer:
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def recognize_vendor(self, file_path: str) -> str:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def recognize_family(self, bytes_list: list[str]]) -> str:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
recognition/super_vendor.py:
|
|
51
|
+
from .recognition_base import Recognizer
|
|
52
|
+
|
|
53
|
+
class SupervendorRecognizer(Recognizer):
|
|
54
|
+
def recognize_vendor(self, file_path: str) -> str:
|
|
55
|
+
classification_string: str = <Some logic to classify the SuperVendor>
|
|
56
|
+
return classification_string
|
|
57
|
+
|
|
58
|
+
def recognize_family(self, bytes_list: list[str]]) -> str:
|
|
59
|
+
family_classification_string: str = <Some logic to classify the family of the SuperVendor>
|
|
60
|
+
return family_classification_string
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
main_script.py:
|
|
64
|
+
from . import recognition
|
|
65
|
+
from .recognition.recognition_base import Recognizer
|
|
66
|
+
|
|
67
|
+
# Get the list of all the recognizers in the recognition package.
|
|
68
|
+
recognizers_list: list = classes.get_list_of_classes_in_module(
|
|
69
|
+
imported_package=recognition, imported_base_class=Recognizer)
|
|
70
|
+
|
|
71
|
+
# Get the list of all the vendors from the file.
|
|
72
|
+
vendors_list: list = list()
|
|
73
|
+
for recognizer in recognizers_list:
|
|
74
|
+
recognizer_instance = recognizer()
|
|
75
|
+
vendor_name: str = recognizer_instance.recognize_vendor(file_object=file_path)
|
|
76
|
+
if vendor_name:
|
|
77
|
+
vendors_list.append((vendor_name, recognizer_instance))
|
|
78
|
+
|
|
79
|
+
# Get the families of the vendors.
|
|
80
|
+
for vendor_name, recognizer_instance in vendors_list:
|
|
81
|
+
family_name: str = recognizer_instance.recognize_family(bytes_list=file_bytes_list)
|
|
82
|
+
print(f"Vendor: {vendor_name}, Family: {family_name}")
|
|
83
|
+
"""
|
|
84
|
+
|
|
31
85
|
def get_list_of_classes_in_module(
|
|
32
86
|
imported_package,
|
|
33
87
|
imported_base_class
|
|
@@ -66,6 +120,22 @@ def get_list_of_classes_in_module(
|
|
|
66
120
|
# Get the list of classes
|
|
67
121
|
unpacker_classes = get_list_of_classes_in_module(imported_package=unpackers, imported_base_class=Unpacker)
|
|
68
122
|
|
|
123
|
+
# Initialize the classes
|
|
124
|
+
for unpacker_class in unpacker_classes:
|
|
125
|
+
unpacker_instance = unpacker_class()
|
|
126
|
+
unpacker_instance.unpack("file_path")
|
|
127
|
+
----------------------------
|
|
128
|
+
# You can also initialize the list of classes dynamically and after that execute methods.
|
|
129
|
+
# Example:
|
|
130
|
+
unpacker_classes = get_list_of_classes_in_module(imported_package=unpackers, imported_base_class=Unpacker)
|
|
131
|
+
|
|
132
|
+
instance_list: list = []
|
|
133
|
+
for unpacker_class in unpacker_classes:
|
|
134
|
+
instance_list.append(unpacker_class())
|
|
135
|
+
|
|
136
|
+
for instance in instance_list:
|
|
137
|
+
instance.unpack("file_path")
|
|
138
|
+
|
|
69
139
|
:param imported_package:
|
|
70
140
|
:param imported_base_class:
|
|
71
141
|
:return:
|
|
@@ -2,9 +2,11 @@ import threading
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
import time
|
|
4
4
|
import multiprocessing.managers
|
|
5
|
+
import queue
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
from ..wrappers.pywin32w.win_event_log.subscribes import process_create, process_terminate
|
|
7
|
-
from .. import get_process_list
|
|
9
|
+
from .. import get_process_list
|
|
8
10
|
from ..print_api import print_api
|
|
9
11
|
|
|
10
12
|
|
|
@@ -15,6 +17,10 @@ WAIT_FOR_PROCESS_POLLER_PID_SECONDS: int = 3
|
|
|
15
17
|
WAIT_FOR_PROCESS_POLLER_PID_COUNTS: int = WAIT_FOR_PROCESS_POLLER_PID_SECONDS * 10
|
|
16
18
|
|
|
17
19
|
|
|
20
|
+
class PidProcessConverterPIDNotFoundError(Exception):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
18
24
|
class SimpleProcessPool:
|
|
19
25
|
"""
|
|
20
26
|
THE POOL OF PROCESSES IS NOT REAL TIME!!!
|
|
@@ -106,14 +112,16 @@ class SimpleProcessPool:
|
|
|
106
112
|
|
|
107
113
|
self._processes: dict = dict()
|
|
108
114
|
self._running: bool = False
|
|
115
|
+
self.shared_dict_update_queue: queue.Queue = queue.Queue()
|
|
116
|
+
self.empty_cmdline_queue: queue.Queue = queue.Queue()
|
|
109
117
|
|
|
110
118
|
def start(self):
|
|
111
119
|
self._running = True
|
|
112
120
|
|
|
113
121
|
self._processes = get_process_list.GetProcessList(
|
|
114
|
-
get_method='
|
|
122
|
+
get_method='psutil', connect_on_init=True).get_processes(as_dict=True)
|
|
115
123
|
|
|
116
|
-
thread_get_queue = threading.Thread(target=self._start_main_thread, args=(self.
|
|
124
|
+
thread_get_queue = threading.Thread(target=self._start_main_thread, args=(self.shared_dict_update_queue,))
|
|
117
125
|
thread_get_queue.daemon = True
|
|
118
126
|
thread_get_queue.start()
|
|
119
127
|
|
|
@@ -121,13 +129,21 @@ class SimpleProcessPool:
|
|
|
121
129
|
thread_process_termination.daemon = True
|
|
122
130
|
thread_process_termination.start()
|
|
123
131
|
|
|
132
|
+
thread_get_psutil_commandline = threading.Thread(target=self._thread_get_psutil_commandline)
|
|
133
|
+
thread_get_psutil_commandline.daemon = True
|
|
134
|
+
thread_get_psutil_commandline.start()
|
|
135
|
+
|
|
136
|
+
thread_update_shared_dict = threading.Thread(target=self._update_shared_dict, args=(self.shared_dict_update_queue,))
|
|
137
|
+
thread_update_shared_dict.daemon = True
|
|
138
|
+
thread_update_shared_dict.start()
|
|
139
|
+
|
|
124
140
|
def stop(self):
|
|
125
141
|
self._running = False
|
|
126
142
|
|
|
127
143
|
def get_processes(self):
|
|
128
144
|
return self._processes
|
|
129
145
|
|
|
130
|
-
def _start_main_thread(self,
|
|
146
|
+
def _start_main_thread(self, shared_dict_update_queue):
|
|
131
147
|
get_instance = process_create.ProcessCreateSubscriber()
|
|
132
148
|
get_instance.start()
|
|
133
149
|
|
|
@@ -137,17 +153,35 @@ class SimpleProcessPool:
|
|
|
137
153
|
process_name = Path(event['NewProcessName']).name
|
|
138
154
|
command_line = event['CommandLine']
|
|
139
155
|
|
|
156
|
+
# The event log tracing method doesn't always give the command line, unlike the psutil method.
|
|
157
|
+
# So, we'll get the command line from the current psutil snapshot separately.
|
|
158
|
+
if command_line == '':
|
|
159
|
+
self.empty_cmdline_queue.put(process_id)
|
|
160
|
+
|
|
140
161
|
self._processes[process_id] = {
|
|
141
162
|
'name': process_name,
|
|
142
163
|
'cmdline': command_line
|
|
143
164
|
}
|
|
144
165
|
|
|
145
166
|
# Update the multiprocessing shared dict proxy.
|
|
146
|
-
|
|
147
|
-
process_pool_shared_dict_proxy.clear()
|
|
148
|
-
process_pool_shared_dict_proxy.update(self._processes)
|
|
167
|
+
shared_dict_update_queue.put(dict(self._processes))
|
|
149
168
|
|
|
150
|
-
|
|
169
|
+
def _thread_get_psutil_commandline(self):
|
|
170
|
+
"""
|
|
171
|
+
This function will get an entry from the queue where command line is missing and get the command line
|
|
172
|
+
from the psutil snapshot.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
while self._running:
|
|
176
|
+
empty_cmd_pid = self.empty_cmdline_queue.get()
|
|
177
|
+
current_psutil_process_snapshot: dict = get_process_list.GetProcessList(
|
|
178
|
+
get_method='psutil', connect_on_init=True).get_processes(as_dict=True)
|
|
179
|
+
command_line = current_psutil_process_snapshot[empty_cmd_pid]['cmdline']
|
|
180
|
+
|
|
181
|
+
self._processes[empty_cmd_pid]['cmdline'] = command_line
|
|
182
|
+
|
|
183
|
+
# Update the multiprocessing shared dict proxy.
|
|
184
|
+
self.shared_dict_update_queue.put(dict(self._processes))
|
|
151
185
|
|
|
152
186
|
def _thread_process_termination(self):
|
|
153
187
|
process_terminate_instance = process_terminate.ProcessTerminateSubscriber()
|
|
@@ -188,6 +222,15 @@ class SimpleProcessPool:
|
|
|
188
222
|
_ = self._processes.pop(process_id, None)
|
|
189
223
|
# print_api(f'Process [{process_id}] removed from the pool.', color='yellow')
|
|
190
224
|
|
|
225
|
+
self.shared_dict_update_queue.put(dict(self._processes))
|
|
226
|
+
|
|
227
|
+
def _update_shared_dict(self, shared_dict_update_queue):
|
|
228
|
+
while self._running:
|
|
229
|
+
current_process_pool = shared_dict_update_queue.get()
|
|
230
|
+
if self.process_pool_shared_dict_proxy is not None:
|
|
231
|
+
self.process_pool_shared_dict_proxy.clear()
|
|
232
|
+
self.process_pool_shared_dict_proxy.update(current_process_pool)
|
|
233
|
+
|
|
191
234
|
|
|
192
235
|
class PidProcessConverter:
|
|
193
236
|
"""
|
|
@@ -204,7 +247,7 @@ class PidProcessConverter:
|
|
|
204
247
|
|
|
205
248
|
self.process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy = process_pool_shared_dict_proxy
|
|
206
249
|
|
|
207
|
-
self.
|
|
250
|
+
self.get_process_with_psutil = get_process_list.GetProcessList(get_method='psutil', connect_on_init=True)
|
|
208
251
|
|
|
209
252
|
def get_process_by_pid(self, pid: int):
|
|
210
253
|
"""
|
|
@@ -219,18 +262,20 @@ class PidProcessConverter:
|
|
|
219
262
|
counter = 0
|
|
220
263
|
process_dict: dict = dict()
|
|
221
264
|
while counter < WAIT_FOR_PROCESS_POLLER_PID_COUNTS:
|
|
222
|
-
|
|
265
|
+
# We need it so that the pool will not change in the middle of the process.
|
|
266
|
+
current_pid_pool = convert_proxy_dict_to_dict(self.process_pool_shared_dict_proxy)
|
|
267
|
+
if pid not in current_pid_pool:
|
|
223
268
|
# print(dict(self.process_pool_shared_dict_proxy))
|
|
224
269
|
time.sleep(0.1)
|
|
225
270
|
counter += 1
|
|
226
271
|
else:
|
|
227
|
-
process_dict =
|
|
272
|
+
process_dict = current_pid_pool[pid]
|
|
228
273
|
break
|
|
229
274
|
|
|
230
|
-
if
|
|
231
|
-
print_api(f"Error: The PID [{pid}] is not in the pool, trying
|
|
275
|
+
if not process_dict:
|
|
276
|
+
print_api(f"Error: The PID [{pid}] is not in the pool, trying psutil snapshot.", color='yellow')
|
|
232
277
|
# Last resort, try to get the process name by current process snapshot.
|
|
233
|
-
processes = self.
|
|
278
|
+
processes = self.get_process_with_psutil.get_processes(as_dict=True)
|
|
234
279
|
if pid not in processes:
|
|
235
280
|
print_api(f"Error: Couldn't get the process name for PID: {pid}.", color='red')
|
|
236
281
|
process_dict = {
|
|
@@ -242,3 +287,25 @@ class PidProcessConverter:
|
|
|
242
287
|
|
|
243
288
|
return process_dict
|
|
244
289
|
|
|
290
|
+
|
|
291
|
+
def convert_proxy_dict_to_dict(proxy_dict: multiprocessing.managers.DictProxy) -> dict:
|
|
292
|
+
"""
|
|
293
|
+
Convert the multiprocessing shared dict proxy to a normal dict.
|
|
294
|
+
|
|
295
|
+
:param proxy_dict: multiprocessing.managers.DictProxy, the shared dict proxy.
|
|
296
|
+
:return: dict, the normal dict.
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
# Create a snapshot of the keys
|
|
300
|
+
keys = list(proxy_dict.keys())
|
|
301
|
+
current_pid_pool = {}
|
|
302
|
+
|
|
303
|
+
for key in keys:
|
|
304
|
+
try:
|
|
305
|
+
# Attempt to retrieve the value for each key
|
|
306
|
+
current_pid_pool[key] = proxy_dict[key]
|
|
307
|
+
except KeyError:
|
|
308
|
+
# The key was removed concurrently; skip it
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
return current_pid_pool
|
|
@@ -207,7 +207,9 @@ def _fetch_content(
|
|
|
207
207
|
'playwright_text',
|
|
208
208
|
'js_text',
|
|
209
209
|
'playwright_html',
|
|
210
|
+
'playwright_html_to_text',
|
|
210
211
|
'js_html',
|
|
212
|
+
'js_html_to_text',
|
|
211
213
|
'playwright_copypaste'
|
|
212
214
|
],
|
|
213
215
|
headless: bool = True):
|
|
@@ -260,18 +262,12 @@ def _fetch_content(
|
|
|
260
262
|
elif text_fetch_method == "js_text":
|
|
261
263
|
# Use JavaScript to extract only the visible text from the page
|
|
262
264
|
text_content: str = page.evaluate("document.body.innerText")
|
|
263
|
-
elif
|
|
265
|
+
elif "playwright_html" in text_fetch_method:
|
|
264
266
|
# Get the full HTML content of the page
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
soup = BeautifulSoup(html, 'html.parser')
|
|
268
|
-
text_content = soup.get_text()
|
|
269
|
-
elif text_fetch_method == "js_html":
|
|
267
|
+
text_content = page.content()
|
|
268
|
+
elif "js_html" in text_fetch_method:
|
|
270
269
|
# Use JavaScript to extract the full text from the page
|
|
271
|
-
|
|
272
|
-
# Parse the HTML using BeautifulSoup and extract the text
|
|
273
|
-
soup = BeautifulSoup(html, 'html.parser')
|
|
274
|
-
text_content = soup.get_text()
|
|
270
|
+
text_content = page.evaluate('document.documentElement.outerHTML')
|
|
275
271
|
elif text_fetch_method == "playwright_copypaste":
|
|
276
272
|
# Focus the page and simulate Ctrl+A and Ctrl+C
|
|
277
273
|
page.keyboard.press("Control+a") # Select all text
|
|
@@ -281,6 +277,11 @@ def _fetch_content(
|
|
|
281
277
|
else:
|
|
282
278
|
raise ValueError(f"Invalid text_fetch_method: {text_fetch_method}")
|
|
283
279
|
|
|
280
|
+
if "to_text" in text_fetch_method:
|
|
281
|
+
# Convert HTML to plain text using BeautifulSoup
|
|
282
|
+
soup = BeautifulSoup(text_content, "html.parser")
|
|
283
|
+
text_content = soup.get_text()
|
|
284
|
+
|
|
284
285
|
# text = page.evaluate('document.body.textContent')
|
|
285
286
|
# text = page.eval_on_selector('body', 'element => element.innerText')
|
|
286
287
|
# text = page.eval_on_selector('body', 'element => element.textContent')
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
atomicshop/__init__.py,sha256=
|
|
1
|
+
atomicshop/__init__.py,sha256=jnzkSiVKr7rNaRPe51te8qpwDBNj2V37gzYMQVqPkoQ,124
|
|
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
|
|
@@ -92,7 +92,7 @@ atomicshop/basics/ansi_escape_codes.py,sha256=uGVRW01v2O052701sOfAdCaM_GLF_cu_jr
|
|
|
92
92
|
atomicshop/basics/argparse_template.py,sha256=horwgSf3MX1ZgRnYxtmmQuz9OU_vKrKggF65gmjlmfg,5836
|
|
93
93
|
atomicshop/basics/booleans.py,sha256=V36NaMf8AffhCom_ovQeOZlYcdtGyIcQwWKki6h7O0M,1745
|
|
94
94
|
atomicshop/basics/bytes_arrays.py,sha256=xfFW9CBQyzf0iXNWpx-EfrxtN3-tiKzuczPzOb6cOdU,7190
|
|
95
|
-
atomicshop/basics/classes.py,sha256=
|
|
95
|
+
atomicshop/basics/classes.py,sha256=dBSQktjc1GmphpD_qOGYNDl-mQcmwg8OkhSqtXPfHEU,15301
|
|
96
96
|
atomicshop/basics/dicts.py,sha256=DeYHIh940pMMBrFhpXt4dsigFVYzTrlqWymNo4Pq_Js,14049
|
|
97
97
|
atomicshop/basics/dicts_nested.py,sha256=StYxYnYPa0SEJr1lmEwAv5zfERWWqoULeyG8e0zRAwE,4107
|
|
98
98
|
atomicshop/basics/enumerations.py,sha256=41VVQYh_vnVapggxKg2IRU5e_EiMpZzX1n1mtxvoSzM,1364
|
|
@@ -170,7 +170,7 @@ atomicshop/permissions/ubuntu_permissions.py,sha256=n8z1vcIXDts4zLVue33dtJiTopjg
|
|
|
170
170
|
atomicshop/permissions/win_permissions.py,sha256=eDQm1jfK9x_hkbLqIJjFTwfqinAWQ0iSr0kW3XrF1BE,1272
|
|
171
171
|
atomicshop/process_poller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
172
172
|
atomicshop/process_poller/process_pool.py,sha256=4Qs427qd7OcBxu5PMFU5PTmyuxRy0vgj2GLsRt0IoEw,9565
|
|
173
|
-
atomicshop/process_poller/simple_process_pool.py,sha256=
|
|
173
|
+
atomicshop/process_poller/simple_process_pool.py,sha256=ap9nCywHZ-eta7EHe0mTYS45-zYvMjgvfESXAuG0Wi4,12578
|
|
174
174
|
atomicshop/process_poller/tracer_base.py,sha256=IOiHcnmF-MccOSCErixN5mve9RifZ9cPnGVHCIRchrs,1091
|
|
175
175
|
atomicshop/process_poller/pollers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
176
176
|
atomicshop/process_poller/pollers/psutil_pywin32wmi_dll.py,sha256=XRRfOIy62iOYU8IKRcyECWiL0rqQ35DeYbPsv_SHDVM,4510
|
|
@@ -284,7 +284,7 @@ atomicshop/wrappers/playwrightw/javascript.py,sha256=_bW7CAtm0Y8IHYrAalg5HpPFnk6
|
|
|
284
284
|
atomicshop/wrappers/playwrightw/keyboard.py,sha256=zN3YddGO-qUkn6C0CRVFejP4cTuaUwXLDNFhFREjERY,422
|
|
285
285
|
atomicshop/wrappers/playwrightw/locators.py,sha256=6wsLywZxDuii7mwv-zQsRbqQC8r7j96Bma5b5_7ZoVo,2411
|
|
286
286
|
atomicshop/wrappers/playwrightw/mouse.py,sha256=-2FZbQtjgH7tdXWld6ZPGqlKFUdf5in0ujN0hewxa50,656
|
|
287
|
-
atomicshop/wrappers/playwrightw/scenarios.py,sha256=
|
|
287
|
+
atomicshop/wrappers/playwrightw/scenarios.py,sha256=oLrZiWnIc09I0Zvh3IxPVDPsQ-Kbr4QM0TeyXGvlsaY,11494
|
|
288
288
|
atomicshop/wrappers/playwrightw/waits.py,sha256=PBFdz_PoM7Fo7O8hLqMrxNPzBEYgPoXwZceFFCGGeu8,7182
|
|
289
289
|
atomicshop/wrappers/psutilw/cpus.py,sha256=w6LPBMINqS-T_X8vzdYkLS2Wzuve28Ydp_GafTCngrc,236
|
|
290
290
|
atomicshop/wrappers/psutilw/disks.py,sha256=3ZSVoommKH1TWo37j_83frB-NqXF4Nf5q5mBCX8G4jE,9221
|
|
@@ -329,8 +329,8 @@ atomicshop/wrappers/socketw/statistics_csv.py,sha256=fgMzDXI0cybwUEqAxprRmY3lqbh
|
|
|
329
329
|
atomicshop/wrappers/winregw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
330
330
|
atomicshop/wrappers/winregw/winreg_installed_software.py,sha256=Qzmyktvob1qp6Tjk2DjLfAqr_yXV0sgWzdMW_9kwNjY,2345
|
|
331
331
|
atomicshop/wrappers/winregw/winreg_network.py,sha256=AENV88H1qDidrcpyM9OwEZxX5svfi-Jb4N6FkS1xtqA,8851
|
|
332
|
-
atomicshop-2.19.
|
|
333
|
-
atomicshop-2.19.
|
|
334
|
-
atomicshop-2.19.
|
|
335
|
-
atomicshop-2.19.
|
|
336
|
-
atomicshop-2.19.
|
|
332
|
+
atomicshop-2.19.14.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
|
|
333
|
+
atomicshop-2.19.14.dist-info/METADATA,sha256=1ZQp-Wt5osHkFGnEavFkPEC41-jmfqJsSsktj0T7aBk,10631
|
|
334
|
+
atomicshop-2.19.14.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
335
|
+
atomicshop-2.19.14.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
|
|
336
|
+
atomicshop-2.19.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|