atomicshop 2.14.1__py3-none-any.whl → 2.14.2__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__ = '2.14.1'
4
+ __version__ = '2.14.2'
@@ -5,7 +5,7 @@ from typing import Literal, Union
5
5
  from pathlib import Path
6
6
 
7
7
  from .wrappers.pywin32w import wmi_win32process
8
- from .wrappers.pywin32w.win_event_log.subscribes import subscribe_to_process_create
8
+ from .wrappers.pywin32w.win_event_log.subscribes import process_create
9
9
  from .wrappers.psutilw import psutilw
10
10
  from .etws.traces import trace_sysmon_process_creation
11
11
  from .basics import dicts
@@ -279,7 +279,7 @@ def _worker(
279
279
  processes = GetProcessList(get_method='pywin32', connect_on_init=True).get_processes(as_dict=True)
280
280
  process_queue.put(processes)
281
281
  elif poller_method == 'event_log':
282
- poller_instance = subscribe_to_process_create.ProcessCreateSubscriber()
282
+ poller_instance = process_create.ProcessCreateSubscriber()
283
283
  poller_instance.start()
284
284
 
285
285
  processes = GetProcessList(get_method='pywin32', connect_on_init=True).get_processes(as_dict=True)
@@ -290,7 +290,6 @@ def _worker(
290
290
  processes = {}
291
291
 
292
292
  exception = None
293
- list_of_processes: list = list()
294
293
  while running_state:
295
294
  try:
296
295
  if poller_method == 'sysmon_etw':
@@ -330,8 +329,8 @@ def _worker(
330
329
 
331
330
  process_queue.put(processes)
332
331
 
333
- # Since ETW is a blocking operation, we don't need to sleep.
334
- if poller_method != 'sysmon_etw':
332
+ # Since ETW is a blocking operation, we don't need to sleep in tracing pollers [sysmon_etw, event_log].
333
+ if poller_method not in ['sysmon_etw', 'event_log']:
335
334
  time.sleep(interval_seconds)
336
335
  except KeyboardInterrupt as e:
337
336
  running_state = False
@@ -4,6 +4,7 @@ import time
4
4
  import threading
5
5
  import queue
6
6
  from typing import Union
7
+ import binascii
7
8
 
8
9
 
9
10
  class EventLogSubscriber:
@@ -20,15 +21,29 @@ class EventLogSubscriber:
20
21
  event = event_log_subscriber.emit()
21
22
  print(event)
22
23
  """
23
- def __init__(self, log_channel: str, event_id: int):
24
+ def __init__(self, log_channel: str, event_id: int = None, provider: str = None):
24
25
  """
25
26
  :param log_channel: The name of the event log channel to subscribe to. Examples:
26
27
  Security, System, Application, etc.
27
28
  :param event_id: The ID of the event to subscribe to.
28
29
  Example: 4688 for process creation events in "Security" channel.
30
+ You can only subscribe by event ID or provider, not both.
31
+ :param provider: The name of the provider to subscribe to.
32
+ You can only subscribe by event ID or provider, not both.
29
33
  """
34
+
35
+ if event_id is None and provider is None:
36
+ raise ValueError("You must specify either an event ID or provider name to subscribe to.")
37
+ elif event_id and provider:
38
+ raise ValueError("You can only subscribe by event ID or provider, not both.")
39
+
30
40
  self.log_channel: str = log_channel
31
- self.event_id: str = str(event_id)
41
+ self.provider: str = provider
42
+
43
+ if event_id:
44
+ self.event_id: str = str(event_id)
45
+ else:
46
+ self.event_id = event_id
32
47
 
33
48
  self._event_queue = queue.Queue()
34
49
  self._subscription_thread = None
@@ -36,7 +51,7 @@ class EventLogSubscriber:
36
51
  def start(self):
37
52
  """Start the subscription process."""
38
53
  self._subscription_thread = threading.Thread(
39
- target=start_subscription, args=(self.log_channel, self.event_id, self._event_queue)
54
+ target=start_subscription, args=(self.log_channel, self._event_queue, self.event_id, self.provider)
40
55
  )
41
56
  self._subscription_thread.daemon = True
42
57
  self._subscription_thread.start()
@@ -64,51 +79,83 @@ class EventLogSubscriber:
64
79
  def _parse_event_xml(event_xml):
65
80
  root = Et.fromstring(event_xml)
66
81
  data = {}
82
+
83
+ # Helper function to strip namespace
84
+ def strip_namespace(tag):
85
+ return tag.split('}')[-1] # Remove namespace
86
+
87
+ # Iterate over all elements
67
88
  for elem in root.iter():
68
- if 'Name' in elem.attrib:
69
- data[elem.attrib['Name']] = elem.text
89
+ # Extract elements with text content
90
+ if elem.text and elem.text.strip():
91
+ tag = elem.tag.split('}')[-1] # Remove namespace
92
+ data[tag] = elem.text.strip()
93
+
94
+ # Extract elements with attributes
95
+ for attr_name, attr_value in elem.attrib.items():
96
+ tag = elem.tag.split('}')[-1] # Remove namespace
97
+ data[f"{tag}_{attr_name}"] = attr_value
98
+
99
+ # Handle Binary data
100
+ if elem.tag.split('}')[-1] == 'Binary':
101
+ try:
102
+ data['BinaryReadable'] = binascii.unhexlify(elem.text.strip())
103
+ except (TypeError, binascii.Error) as e:
104
+ print(f"Error decoding binary data: {e}")
105
+ data['BinaryReadable'] = elem.text.strip()
106
+
107
+ # Extract system-specific data
108
+ system_data = root.find(".//{http://schemas.microsoft.com/win/2004/08/events/event}System")
109
+ if system_data is not None:
110
+ for system_elem in system_data:
111
+ tag = strip_namespace(system_elem.tag)
112
+ if system_elem.attrib:
113
+ for attr_name, attr_value in system_elem.attrib.items():
114
+ data[f"{tag}_{attr_name}"] = attr_value
115
+ if system_elem.text and system_elem.text.strip():
116
+ data[tag] = system_elem.text.strip()
117
+
118
+ # Extract event-specific data
119
+ event_data = root.find(".//{http://schemas.microsoft.com/win/2004/08/events/event}EventData")
120
+ if event_data is not None:
121
+ for data_elem in event_data:
122
+ if strip_namespace(data_elem.tag) == 'Data' and 'Name' in data_elem.attrib:
123
+ data[data_elem.attrib['Name']] = data_elem.text.strip()
124
+
125
+ # Extract user data if available
126
+ user_data = root.find(".//{http://schemas.microsoft.com/win/2004/08/events/event}UserData")
127
+ if user_data is not None:
128
+ for user_elem in user_data:
129
+ tag = strip_namespace(user_elem.tag)
130
+ if user_elem.attrib:
131
+ for attr_name, attr_value in user_elem.attrib.items():
132
+ data[f"{tag}_{attr_name}"] = attr_value
133
+ if user_elem.text and user_elem.text.strip():
134
+ data[tag] = user_elem.text.strip()
135
+
136
+ # Extract rendering info (additional details like the message)
137
+ rendering_info = root.find(".//{http://schemas.microsoft.com/win/2004/08/events/event}RenderingInfo")
138
+ if rendering_info is not None:
139
+ for info_elem in rendering_info:
140
+ tag = strip_namespace(info_elem.tag)
141
+ if info_elem.text and info_elem.text.strip():
142
+ data[f"RenderingInfo_{tag}"] = info_elem.text.strip()
143
+
70
144
  return data
71
145
 
72
146
 
73
147
  def _handle_event(event, event_queue):
148
+ # Render event as XML
74
149
  event_xml = win32evtlog.EvtRender(event, win32evtlog.EvtRenderEventXml)
150
+ data = None
75
151
  try:
76
152
  data = _parse_event_xml(event_xml)
77
153
  except Et.ParseError as e:
78
154
  print(f"Error parsing event XML: {e}")
79
- return
80
-
81
- event_dict: dict = {
82
- 'user_sid': data.get("SubjectUserSid", "Unknown"),
83
- 'user_name': data.get("SubjectUserName", "Unknown"),
84
- 'domain': data.get("SubjectDomainName", "Unknown"),
85
- 'pid_hex': data.get("NewProcessId", "0"),
86
- 'process_name': data.get("NewProcessName", "Unknown"),
87
- 'command_line': data.get("CommandLine", None),
88
- 'parent_pid_hex': data.get("ProcessId", "0"),
89
- 'parent_process_name': data.get("ParentProcessName", "Unknown")
90
- }
91
-
92
- try:
93
- process_id = int(event_dict['pid_hex'], 16)
94
- except ValueError:
95
- process_id = "Unknown"
155
+ except Exception as e:
156
+ print(f"Error getting rendered message: {e}")
96
157
 
97
- try:
98
- parent_pid = int(event_dict['parent_pid_hex'], 16)
99
- except ValueError:
100
- parent_pid = "Unknown"
101
-
102
- event_dict['pid'] = process_id
103
- event_dict['parent_pid'] = parent_pid
104
-
105
- # if user_sid != "Unknown":
106
- # try:
107
- # user_name, domain, type = win32security.LookupAccountSid(None, user_sid)
108
- # except Exception as e:
109
- # print(f"Error looking up account SID: {e}")
110
-
111
- event_queue.put(event_dict)
158
+ event_queue.put(data)
112
159
 
113
160
 
114
161
  def _event_callback(action, context, event):
@@ -117,25 +164,43 @@ def _event_callback(action, context, event):
117
164
  _handle_event(event, event_queue)
118
165
 
119
166
 
120
- def start_subscription(log_channel: str, event_id: int, event_queue):
167
+ def start_subscription(
168
+ log_channel: str,
169
+ event_queue,
170
+ event_id: str = None,
171
+ provider: str = None
172
+ ):
121
173
  """
122
174
  Start listening for events in the specified log channel with the given event ID.
123
175
 
124
176
  :param log_channel: The name of the event log channel to subscribe to. Examples:
125
177
  Security, System, Application, etc.
178
+ :param event_queue: A queue to store the received events
126
179
  :param event_id: The ID of the event to subscribe to.
127
180
  Example: 4688 for process creation events in "Security" channel.
128
- :param event_queue: A queue to store the received events
181
+ You can only subscribe by event ID or provider, not both.
182
+ :param provider: The name of the provider to subscribe to.
183
+ You can only subscribe by event ID or provider, not both.
129
184
  """
185
+
186
+ if event_id is None and provider is None:
187
+ raise ValueError("You must specify either an event ID or provider name to subscribe to.")
188
+ elif event_id and provider:
189
+ raise ValueError("You can only subscribe by event ID or provider, not both.")
190
+
130
191
  # This selects the System node within each event.
131
192
  # The System node contains metadata about the event, such as the event ID, provider name, timestamp, and more.
132
- query = f"*[System/EventID={str(event_id)}]"
193
+ xpath_query = None
194
+ if provider:
195
+ xpath_query = f"*[System/Provider[@Name='{provider}']]"
196
+ elif event_id:
197
+ xpath_query = f"*[System/EventID={event_id}]"
133
198
 
134
199
  subscription = win32evtlog.EvtSubscribe(
135
200
  log_channel,
136
201
  win32evtlog.EvtSubscribeToFutureEvents,
137
202
  SignalEvent=None,
138
- Query=query,
203
+ Query=xpath_query,
139
204
  Callback=_event_callback,
140
205
  Context={'event_queue': event_queue}
141
206
  )
@@ -7,6 +7,8 @@ from .....print_api import print_api
7
7
 
8
8
  AUDITING_REG_PATH: str = r"Software\Microsoft\Windows\CurrentVersion\Policies\System\Audit"
9
9
  PROCESS_CREATION_INCLUDE_CMDLINE_VALUE: str = "ProcessCreationIncludeCmdLine_Enabled"
10
+ LOG_CHANNEL: str = 'Security'
11
+ EVENT_ID: int = 4688
10
12
 
11
13
 
12
14
  class ProcessCreateSubscriber(subscribe.EventLogSubscriber):
@@ -14,9 +16,9 @@ class ProcessCreateSubscriber(subscribe.EventLogSubscriber):
14
16
  Class for subscribing to Windows Event Log events related to process creation.
15
17
 
16
18
  Usage:
17
- from atomicshop.wrappers.pywin32w.win_event_log.subscribes import subscribe_to_process_create
19
+ from atomicshop.wrappers.pywin32w.win_event_log.subscribes import process_create
18
20
 
19
- process_create_subscriber = subscribe_to_process_create.ProcessCreateSubscriber()
21
+ process_create_subscriber = process_create.ProcessCreateSubscriber()
20
22
  process_create_subscriber.start()
21
23
 
22
24
  while True:
@@ -24,7 +26,7 @@ class ProcessCreateSubscriber(subscribe.EventLogSubscriber):
24
26
  print(event)
25
27
  """
26
28
  def __init__(self):
27
- super().__init__('Security', 4688)
29
+ super().__init__(log_channel=LOG_CHANNEL, event_id=EVENT_ID)
28
30
 
29
31
  def start(self):
30
32
  """Start the subscription process."""
@@ -45,7 +47,40 @@ class ProcessCreateSubscriber(subscribe.EventLogSubscriber):
45
47
  If None, the function will block until an event is available.
46
48
  :return: A dictionary containing the event data.
47
49
  """
48
- return super().emit(timeout=timeout)
50
+
51
+ data = super().emit(timeout=timeout)
52
+
53
+ event_dict: dict = {
54
+ 'user_sid': data.get("SubjectUserSid", "Unknown"),
55
+ 'user_name': data.get("SubjectUserName", "Unknown"),
56
+ 'domain': data.get("SubjectDomainName", "Unknown"),
57
+ 'pid_hex': data.get("NewProcessId", "0"),
58
+ 'process_name': data.get("NewProcessName", "Unknown"),
59
+ 'command_line': data.get("CommandLine", None),
60
+ 'parent_pid_hex': data.get("ProcessId", "0"),
61
+ 'parent_process_name': data.get("ParentProcessName", "Unknown")
62
+ }
63
+
64
+ try:
65
+ process_id = int(event_dict['pid_hex'], 16)
66
+ except ValueError:
67
+ process_id = "Unknown"
68
+
69
+ try:
70
+ parent_pid = int(event_dict['parent_pid_hex'], 16)
71
+ except ValueError:
72
+ parent_pid = "Unknown"
73
+
74
+ event_dict['pid'] = process_id
75
+ event_dict['parent_pid'] = parent_pid
76
+
77
+ # if user_sid != "Unknown":
78
+ # try:
79
+ # user_name, domain, type = win32security.LookupAccountSid(None, user_sid)
80
+ # except Exception as e:
81
+ # print(f"Error looking up account SID: {e}")
82
+
83
+ return event_dict
49
84
 
50
85
 
51
86
  def enable_audit_process_creation(print_kwargs: dict = None):
@@ -0,0 +1,97 @@
1
+ import winreg
2
+ import sys
3
+
4
+ from .. import subscribe
5
+ from .....print_api import print_api
6
+
7
+
8
+ SCHANNEL_LOGGING_REG_PATH: str = r'SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL'
9
+ SCHANNEL_EVENT_LOGGING_KEY: str = 'EventLogging'
10
+ LOG_CHANNEL: str = 'System'
11
+ PROVIDER: str = 'Schannel'
12
+
13
+
14
+ class SchannelLoggingSubscriber(subscribe.EventLogSubscriber):
15
+ """
16
+ Class for subscribing to Windows Event Log events related to process creation.
17
+
18
+ Usage:
19
+ from atomicshop.wrappers.pywin32w.win_event_log.subscribes import schannel_logging
20
+
21
+ process_create_subscriber = schannel_logging.SchannelLoggingSubscriber()
22
+ process_create_subscriber.start()
23
+
24
+ while True:
25
+ event = process_create_subscriber.emit()
26
+ print(event)
27
+ """
28
+ def __init__(self):
29
+ super().__init__(log_channel=LOG_CHANNEL, provider=PROVIDER)
30
+
31
+ def start(self):
32
+ """Start the subscription process."""
33
+ enable_schannel_logging()
34
+
35
+ super().start()
36
+
37
+ def stop(self):
38
+ """Stop the subscription process."""
39
+ super().stop()
40
+
41
+ def emit(self, timeout: float = None) -> dict:
42
+ """
43
+ Get the next event from the event queue.
44
+
45
+ :param timeout: The maximum time (in seconds) to wait for an event.
46
+ If None, the function will block until an event is available.
47
+ :return: A dictionary containing the event data.
48
+ """
49
+ return super().emit(timeout=timeout)
50
+
51
+
52
+ def enable_schannel_logging(logging_value: int = 1, print_kwargs: dict = None):
53
+ """
54
+ Value Description
55
+ 0x0000 Do not log
56
+ 0x0001 Log error messages
57
+ 0x0002 Log warnings
58
+ 0x0003 Log warnings and error messages
59
+ 0x0004 Log informational and success events
60
+ 0x0005 Log informational, success events and error messages
61
+ 0x0006 Log informational, success events and warnings
62
+ 0x0007 Log informational, success events, warnings, and error messages (all log levels)
63
+ """
64
+
65
+ if is_schannel_logging_enabled(logging_value):
66
+ print_api(
67
+ "Schannel event logging is already enabled.", color='yellow',
68
+ **(print_kwargs or {}))
69
+ return
70
+
71
+ try:
72
+ with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, SCHANNEL_LOGGING_REG_PATH, 0, winreg.KEY_ALL_ACCESS) as key:
73
+ winreg.SetValueEx(key, SCHANNEL_EVENT_LOGGING_KEY, 0, winreg.REG_DWORD, logging_value)
74
+
75
+ print_api(
76
+ "Successfully enabled Schannel logging.",
77
+ color='green', **(print_kwargs or {}))
78
+ print_api(
79
+ "Please restart the computer for the changes to take effect.",
80
+ color='yellow', **(print_kwargs or {}))
81
+ sys.exit()
82
+ except WindowsError as e:
83
+ print_api(
84
+ f"Failed to enable Schannel event logging: {e}", error_type=True,
85
+ color='red', **(print_kwargs or {}))
86
+
87
+
88
+ def is_schannel_logging_enabled(logging_value: int) -> bool:
89
+ try:
90
+ with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, SCHANNEL_LOGGING_REG_PATH, 0, winreg.KEY_READ) as key:
91
+ value, regtype = winreg.QueryValueEx(key, SCHANNEL_EVENT_LOGGING_KEY)
92
+ return value == logging_value
93
+ except FileNotFoundError:
94
+ return False
95
+ except WindowsError as e:
96
+ print(f"Failed to read the Schannel event logging setting: {e}")
97
+ return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: atomicshop
3
- Version: 2.14.1
3
+ Version: 2.14.2
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=w-I1vM1vpc9PJDvoRTt804j2SwPM8mjp6n5FOtCdutg,123
1
+ atomicshop/__init__.py,sha256=z4rThLLSu1i3Wlh2xQO9kTZiAnom3boVcidQ2m4F8V0,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
@@ -27,7 +27,7 @@ atomicshop/permissions.py,sha256=P6tiUKV-Gw-c3ePEVsst9bqWaHJbB4ZlJB4xbDYVpEs,443
27
27
  atomicshop/print_api.py,sha256=DhbCQd0MWZZ5GYEk4oTu1opRFC-b31g1VWZgTGewG2Y,11568
28
28
  atomicshop/process.py,sha256=R1BtXWjG2g2Q3WlsyhbIlXZz0UkQeagY7fQyBOIX_DM,15951
29
29
  atomicshop/process_name_cmd.py,sha256=CtaSp3mgxxJKCCVW8BLx6BJNx4giCklU_T7USiCEwfc,5162
30
- atomicshop/process_poller.py,sha256=B91ugFIo84IMFXbWJeW8P7TsIkzYxMXBVWuvYqRXS-c,16107
30
+ atomicshop/process_poller.py,sha256=17sc332AcTfSVPorjYsgRwNm75rLCqvpOQsieMwLMKU,16105
31
31
  atomicshop/python_file_patcher.py,sha256=kd3rBWvTcosLEk-7TycNdfKW9fZbe161iVwmH4niUo0,5515
32
32
  atomicshop/python_functions.py,sha256=zJg4ogUwECxrDD7xdDN5JikIUctITM5lsyabr_ZNsRw,4435
33
33
  atomicshop/question_answer_engine.py,sha256=DuOn7QEgKKfqZu2cR8mVeFIfFgayfBHiW-jY2VPq_Fo,841
@@ -251,9 +251,10 @@ atomicshop/wrappers/pywin32w/console.py,sha256=LstHajPLgXp9qQxFNR44QfH10nOnNp3bC
251
251
  atomicshop/wrappers/pywin32w/winshell.py,sha256=i2bKiMldPU7_azsD5xGQDdMwjaM7suKJd3k0Szmcs6c,723
252
252
  atomicshop/wrappers/pywin32w/wmi_win32process.py,sha256=qMzXtJ5hBZ5ydAyqpDbSx0nO2RJQL95HdmV5SzNKMhk,6826
253
253
  atomicshop/wrappers/pywin32w/win_event_log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
254
- atomicshop/wrappers/pywin32w/win_event_log/subscribe.py,sha256=UztWltQPK_fQ3EWyY6tGfhAqwxDjK7RVIAZppu97rKI,5104
254
+ atomicshop/wrappers/pywin32w/win_event_log/subscribe.py,sha256=a0SqjtUfo-fVR5jlsoygFlDrjHelOKgCBvia3suyzW8,8217
255
255
  atomicshop/wrappers/pywin32w/win_event_log/subscribes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
256
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/subscribe_to_process_create.py,sha256=Cviy1n3NSCGswQ4TxAyeDh7d21u8Vt3UOjnLmYYU2fQ,5602
256
+ atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py,sha256=_no1MnXjhLgOi9Y7xc7HHgTe6sjZkHIIhcVCd5BKZgM,6865
257
+ atomicshop/wrappers/pywin32w/win_event_log/subscribes/schannel_logging.py,sha256=8nxIcNcbeEuvoBwhujgh7-oIpL9A6J-gg1NM8hOGAVA,3442
257
258
  atomicshop/wrappers/socketw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
258
259
  atomicshop/wrappers/socketw/accepter.py,sha256=HQC1EyZmyUtVEfFbaBkHCE-VZp6RWyd9mEqAkgsE1fk,1749
259
260
  atomicshop/wrappers/socketw/base.py,sha256=1vvg8EhRGvnxdrRAm1VJSLCXkm2SZDHRjdpTuhkH3Mg,1844
@@ -270,8 +271,8 @@ atomicshop/wrappers/socketw/socket_server_tester.py,sha256=AhpurHJmP2kgzHaUbq5ey
270
271
  atomicshop/wrappers/socketw/socket_wrapper.py,sha256=aXBwlEIJhFT0-c4i8iNlFx2It9VpCEpsv--5Oqcpxao,11624
271
272
  atomicshop/wrappers/socketw/ssl_base.py,sha256=k4V3gwkbq10MvOH4btU4onLX2GNOsSfUAdcHmL1rpVE,2274
272
273
  atomicshop/wrappers/socketw/statistics_csv.py,sha256=t3dtDEfN47CfYVi0CW6Kc2QHTEeZVyYhc57IYYh5nmA,826
273
- atomicshop-2.14.1.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
274
- atomicshop-2.14.1.dist-info/METADATA,sha256=rZNM503plkk4O9jvmeQTuAHA-7JS24mxM3k7bHNo0SA,10478
275
- atomicshop-2.14.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
276
- atomicshop-2.14.1.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
277
- atomicshop-2.14.1.dist-info/RECORD,,
274
+ atomicshop-2.14.2.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
275
+ atomicshop-2.14.2.dist-info/METADATA,sha256=VIgp-Go_u3KNgCb_tRAE6XcD9IyNa2lHdn0C2WFmscY,10478
276
+ atomicshop-2.14.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
277
+ atomicshop-2.14.2.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
278
+ atomicshop-2.14.2.dist-info/RECORD,,