atomicshop 2.13.1__py3-none-any.whl → 2.13.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.13.1'
4
+ __version__ = '2.13.2'
atomicshop/etws/trace.py CHANGED
@@ -1,11 +1,20 @@
1
1
  import queue
2
2
  import sys
3
+ import time
3
4
 
4
5
  # Import FireEye Event Tracing library.
5
6
  import etw
6
7
 
7
8
  from ..print_api import print_api
8
9
  from . import sessions
10
+ from .. import process_poller
11
+ from ..wrappers.psutilw import psutilw
12
+
13
+
14
+ PROCESS_POLLER_ETW_DEFAULT_SESSION_NAME: str = 'AtomicShopProcessTrace'
15
+
16
+ WAIT_FOR_PROCESS_POLLER_PID_SECONDS: int = 3
17
+ WAIT_FOR_PROCESS_POLLER_PID_COUNTS: int = WAIT_FOR_PROCESS_POLLER_PID_SECONDS * 10
9
18
 
10
19
 
11
20
  class EventTrace(etw.ETW):
@@ -15,7 +24,9 @@ class EventTrace(etw.ETW):
15
24
  event_callback=None,
16
25
  event_id_filters: list = None,
17
26
  session_name: str = None,
18
- close_existing_session_name: bool = True
27
+ close_existing_session_name: bool = True,
28
+ enable_process_poller: bool = False,
29
+ process_poller_etw_session_name: str = None
19
30
  ):
20
31
  """
21
32
  :param providers: List of tuples with provider name and provider GUID.
@@ -26,7 +37,13 @@ class EventTrace(etw.ETW):
26
37
  The default in the 'etw.ETW' method is 'None'.
27
38
  :param session_name: The name of the session to create. If not provided, a UUID will be generated.
28
39
  :param close_existing_session_name: Boolean to close existing session names.
40
+ :param enable_process_poller: Boolean to enable process poller. Gets the process PID, Name and CommandLine.
41
+ Since the DNS events doesn't contain the process name and command line, only PID.
42
+ Then DNS events will be enriched with the process name and command line from the process poller.
43
+ :param process_poller_etw_session_name: The name of the ETW session for tracing process creation.
44
+
29
45
  ------------------------------------------
46
+
30
47
  You should stop the ETW tracing when you are done with it.
31
48
  'pywintrace' module starts a new session for ETW tracing, and it will not stop the session when the script
32
49
  exits or exception is raised.
@@ -46,6 +63,8 @@ class EventTrace(etw.ETW):
46
63
  """
47
64
  self.event_queue = queue.Queue()
48
65
  self.close_existing_session_name: bool = close_existing_session_name
66
+ self.enable_process_poller: bool = enable_process_poller
67
+ self.process_poller_etw_session_name: str = process_poller_etw_session_name
49
68
 
50
69
  # If no callback function is provided, we will use the default one, which will put the event in the queue.
51
70
  if not event_callback:
@@ -59,12 +78,23 @@ class EventTrace(etw.ETW):
59
78
  for provider in providers:
60
79
  etw_format_providers.append(etw.ProviderInfo(provider[0], etw.GUID(provider[1])))
61
80
 
81
+ if not process_poller_etw_session_name:
82
+ process_poller_etw_session_name = PROCESS_POLLER_ETW_DEFAULT_SESSION_NAME
83
+
84
+ if self.enable_process_poller:
85
+ self.process_poller = process_poller.ProcessPollerPool(
86
+ operation='process', poller_method='sysmon_etw',
87
+ sysmon_etw_session_name=process_poller_etw_session_name)
88
+
62
89
  super().__init__(
63
90
  providers=etw_format_providers, event_callback=function_callable, event_id_filters=event_id_filters,
64
91
  session_name=session_name
65
92
  )
66
93
 
67
94
  def start(self):
95
+ if self.enable_process_poller:
96
+ self.process_poller.start()
97
+
68
98
  # Check if the session name already exists.
69
99
  if sessions.is_session_running(self.session_name):
70
100
  print_api(f'ETW Session already running: {self.session_name}', color='yellow')
@@ -87,6 +117,9 @@ class EventTrace(etw.ETW):
87
117
  def stop(self):
88
118
  super().stop()
89
119
 
120
+ if self.enable_process_poller:
121
+ self.process_poller.stop()
122
+
90
123
  def emit(self):
91
124
  """
92
125
  The Function will return the next event from the queue.
@@ -104,34 +137,44 @@ class EventTrace(etw.ETW):
104
137
  :return: etw event object.
105
138
  """
106
139
 
107
- return self.event_queue.get()
108
-
109
-
110
- def find_sessions_by_provider(provider_name: str):
111
- """
112
- Find ETW session by provider name.
113
-
114
- :param provider_name: The name of the provider to search for.
115
- """
140
+ # Get the processes first, since we need the process name and command line.
141
+ # If they're not ready, we will get just pids from DNS tracing.
142
+ if self.enable_process_poller:
143
+ self._get_processes_from_poller()
116
144
 
117
- return
145
+ event: tuple = self.event_queue.get()
118
146
 
147
+ event_dict: dict = {
148
+ 'event_id': event[0],
149
+ 'event': event[1],
150
+ 'pid': event[1]['EventHeader']['ProcessId']
151
+ }
119
152
 
120
- def get_all_providers_from_session(session_name: str):
121
- """
122
- Get all providers that ETW session uses.
153
+ if self.enable_process_poller:
154
+ processes = self.process_poller.get_processes()
155
+ if event_dict['pid'] not in processes:
156
+ counter = 0
157
+ while counter < WAIT_FOR_PROCESS_POLLER_PID_COUNTS:
158
+ processes = self.process_poller.get_processes()
159
+ if event_dict['pid'] not in processes:
160
+ time.sleep(0.1)
161
+ counter += 1
162
+ else:
163
+ break
123
164
 
124
- :param session_name: The name of the session to get providers from.
125
- """
165
+ if counter == WAIT_FOR_PROCESS_POLLER_PID_COUNTS:
166
+ print_api(f"Error: Couldn't get the process name for PID: {event_dict['pid']}.", color='red')
126
167
 
127
- return
168
+ event_dict = psutilw.cross_single_connection_with_processes(event_dict, processes)
128
169
 
170
+ return event_dict
129
171
 
130
- def stop_session_by_name(session_name: str):
131
- """
132
- Stop ETW session by name.
172
+ def _get_processes_from_poller(self):
173
+ processes: dict = {}
174
+ while not processes:
175
+ processes = self.process_poller.get_processes()
133
176
 
134
- :param session_name: The name of the session to stop.
135
- """
177
+ if isinstance(processes, BaseException):
178
+ raise processes
136
179
 
137
- return
180
+ return processes
@@ -19,20 +19,15 @@ WAIT_FOR_PROCESS_POLLER_PID_COUNTS: int = WAIT_FOR_PROCESS_POLLER_PID_SECONDS *
19
19
 
20
20
 
21
21
  class DnsRequestResponseTrace:
22
+ """DnsTrace class use to trace DNS events from Windows Event Tracing for EventId 3008."""
22
23
  def __init__(
23
24
  self,
24
25
  attrs: list = None,
25
26
  session_name: str = None,
26
27
  close_existing_session_name: bool = True,
27
- enable_process_poller: bool = False,
28
28
  process_poller_etw_session_name: str = None
29
29
  ):
30
30
  """
31
- DnsTrace class use to trace DNS events from Windows Event Tracing for EventId 3008.
32
-
33
- :param enable_process_poller: Boolean to enable process poller. Gets the process PID, Name and CommandLine,
34
- every 100 ms. Since the DNS events doesn't contain the process name and command line, only PID.
35
- Then DNS events will be enriched with the process name and command line from the process poller.
36
31
  :param attrs: List of attributes to return. If None, all attributes will be returned.
37
32
  :param session_name: The name of the session to create. If not provided, a UUID will be generated.
38
33
  :param close_existing_session_name: Boolean to close existing session names.
@@ -63,7 +58,6 @@ class DnsRequestResponseTrace:
63
58
  dns_trace_w.stop()
64
59
  """
65
60
 
66
- self.enable_process_poller = enable_process_poller
67
61
  self.attrs = attrs
68
62
 
69
63
  if not session_name:
@@ -74,26 +68,17 @@ class DnsRequestResponseTrace:
74
68
  # lambda x: self.event_queue.put(x),
75
69
  event_id_filters=[REQUEST_RESP_EVENT_ID],
76
70
  session_name=session_name,
77
- close_existing_session_name=close_existing_session_name
71
+ close_existing_session_name=close_existing_session_name,
72
+ enable_process_poller=True,
73
+ process_poller_etw_session_name=process_poller_etw_session_name
78
74
  )
79
75
 
80
- if self.enable_process_poller:
81
- self.process_poller = ProcessPollerPool(
82
- operation='process', poller_method='sysmon_etw',
83
- sysmon_etw_session_name=process_poller_etw_session_name)
84
-
85
76
  def start(self):
86
- if self.enable_process_poller:
87
- self.process_poller.start()
88
-
89
77
  self.event_trace.start()
90
78
 
91
79
  def stop(self):
92
80
  self.event_trace.stop()
93
81
 
94
- if self.enable_process_poller:
95
- self.process_poller.stop()
96
-
97
82
  def emit(self):
98
83
  """
99
84
  Function that will return the next event from the queue.
@@ -107,29 +92,26 @@ class DnsRequestResponseTrace:
107
92
  :return: Dictionary with the event data.
108
93
  """
109
94
 
110
- # Get the processes first, since we need the process name and command line.
111
- # If they're not ready, we will get just pids from DNS tracing.
112
- if self.enable_process_poller:
113
- self._get_processes_from_poller()
114
-
115
95
  event = self.event_trace.emit()
116
96
 
117
97
  event_dict: dict = {
118
- 'pid': event[1]['EventHeader']['ProcessId'],
119
- 'etw_id': event[0],
120
- 'domain': event[1]['QueryName'],
121
- 'query_type_id': str(event[1]['QueryType']),
122
- 'query_type': dns.TYPES_DICT[str(event[1]['QueryType'])]
98
+ 'event_id': event['event_id'],
99
+ 'domain': event['event']['QueryName'],
100
+ 'query_type_id': str(event['event']['QueryType']),
101
+ 'query_type': dns.TYPES_DICT[str(event['event']['QueryType'])],
102
+ 'pid': event['pid'],
103
+ 'name': event['name'],
104
+ 'cmdline': event['cmdline']
123
105
  }
124
106
 
125
107
  # Defining list if ips and other answers, which aren't IPs.
126
108
  list_of_ips = list()
127
109
  list_of_other_domains = list()
128
110
  # Parse DNS results, only if 'QueryResults' key isn't empty, since many of the events are, mostly due errors.
129
- if event[1]['QueryResults']:
111
+ if event['event']['QueryResults']:
130
112
  # 'QueryResults' key contains a string with all the 'Answers' divided by type and ';' character.
131
113
  # Basically, we can parse each type out of string, but we need only IPs and other answers.
132
- list_of_parameters = event[1]['QueryResults'].split(';')
114
+ list_of_parameters = event['event']['QueryResults'].split(';')
133
115
 
134
116
  # Iterating through all the parameters that we got from 'QueryResults' key.
135
117
  for parameter in list_of_parameters:
@@ -149,47 +131,17 @@ class DnsRequestResponseTrace:
149
131
  event_dict['other_domains'] = list_of_other_domains
150
132
 
151
133
  # Getting the 'QueryStatus' key.
152
- event_dict['status_id'] = event[1]['QueryStatus']
134
+ event_dict['status_id'] = event['event']['QueryStatus']
153
135
 
154
136
  # Getting the 'QueryStatus' key. If DNS Query Status is '0' then it was executed successfully.
155
137
  # And if not, it means there was an error. The 'QueryStatus' indicate what number of an error it is.
156
- if event[1]['QueryStatus'] == '0':
138
+ if event['event']['QueryStatus'] == '0':
157
139
  event_dict['status'] = 'Success'
158
140
  else:
159
141
  event_dict['status'] = 'Error'
160
142
 
161
- if self.enable_process_poller:
162
- processes = self.process_poller.get_processes()
163
- if event_dict['pid'] not in processes:
164
- counter = 0
165
- while counter < WAIT_FOR_PROCESS_POLLER_PID_COUNTS:
166
- processes = self.process_poller.get_processes()
167
- if event_dict['pid'] not in processes:
168
- time.sleep(0.1)
169
- counter += 1
170
- else:
171
- break
172
-
173
- if counter == WAIT_FOR_PROCESS_POLLER_PID_COUNTS:
174
- print_api(f"Error: Couldn't get the process name for PID: {event_dict['pid']}.", color='red')
175
-
176
- event_dict = psutilw.cross_single_connection_with_processes(event_dict, processes)
177
- # If it was impossible to get the process name from the process poller, get it from psutil.
178
- # if event_dict['name'].isnumeric():
179
- # event_dict['name'] = process_name
180
-
181
143
  if self.attrs:
182
144
  event_dict = dicts.reorder_keys(
183
145
  event_dict, self.attrs, skip_keys_not_in_list=True)
184
146
 
185
147
  return event_dict
186
-
187
- def _get_processes_from_poller(self):
188
- processes: dict = {}
189
- while not processes:
190
- processes = self.process_poller.get_processes()
191
-
192
- if isinstance(processes, BaseException):
193
- raise processes
194
-
195
- return processes
@@ -1,8 +1,6 @@
1
1
  from .. import trace, const
2
- from ...wrappers.psutilw import psutilw
3
2
  from ...wrappers import sysmonw
4
3
  from ...basics import dicts
5
- from ...print_api import print_api
6
4
 
7
5
 
8
6
  PROVIDER_NAME: str = const.ETW_SYSMON['provider_name']
@@ -87,26 +85,26 @@ class SysmonProcessCreationTrace:
87
85
  event = self.event_trace.emit()
88
86
 
89
87
  event_dict: dict = {
90
- 'etw_id': event[0],
91
- 'pid': event[1]['ProcessId'],
92
- 'process_guid': event[1]['ProcessGuid'],
93
- 'image': event[1]['Image'],
94
- 'file_version': event[1]['FileVersion'],
95
- 'product': event[1]['Product'],
96
- 'company': event[1]['Company'],
97
- 'original_file_name': event[1]['OriginalFileName'],
98
- 'command_line': event[1]['CommandLine'],
99
- 'current_directory': event[1]['CurrentDirectory'],
100
- 'user': event[1]['User'],
101
- 'logon_id': event[1]['LogonId'],
102
- 'logon_guid': event[1]['LogonGuid'],
103
- 'terminal_session_id': event[1]['TerminalSessionId'],
104
- 'integrity_level': event[1]['IntegrityLevel'],
105
- 'hashes': event[1]['Hashes'],
106
- 'parent_process_guid': event[1]['ParentProcessGuid'],
107
- 'parent_process_id': event[1]['ParentProcessId'],
108
- 'parent_image': event[1]['ParentImage'],
109
- 'parent_command_line': event[1]['ParentCommandLine']
88
+ 'event_id': event['event_id'],
89
+ 'pid': event['event']['ProcessId'],
90
+ 'process_guid': event['event']['ProcessGuid'],
91
+ 'image': event['event']['Image'],
92
+ 'file_version': event['event']['FileVersion'],
93
+ 'product': event['event']['Product'],
94
+ 'company': event['event']['Company'],
95
+ 'original_file_name': event['event']['OriginalFileName'],
96
+ 'command_line': event['event']['CommandLine'],
97
+ 'current_directory': event['event']['CurrentDirectory'],
98
+ 'user': event['event']['User'],
99
+ 'logon_id': event['event']['LogonId'],
100
+ 'logon_guid': event['event']['LogonGuid'],
101
+ 'terminal_session_id': event['event']['TerminalSessionId'],
102
+ 'integrity_level': event['event']['IntegrityLevel'],
103
+ 'hashes': event['event']['Hashes'],
104
+ 'parent_process_guid': event['event']['ParentProcessGuid'],
105
+ 'parent_process_id': event['event']['ParentProcessId'],
106
+ 'parent_image': event['event']['ParentImage'],
107
+ 'parent_command_line': event['event']['ParentCommandLine']
110
108
  }
111
109
 
112
110
  if self.attrs:
@@ -28,7 +28,6 @@ class DnsCheck:
28
28
  attrs=['name', 'cmdline', 'domain', 'query_type'],
29
29
  session_name=self.etw_session_name,
30
30
  close_existing_session_name=True,
31
- enable_process_poller=True,
32
31
  process_poller_etw_session_name=change_monitor_instance.etw_process_session_name
33
32
  )
34
33
  )
@@ -6,7 +6,7 @@ from typing import Literal, Union
6
6
  from .wrappers.pywin32w import wmi_win32process
7
7
  from .wrappers.psutilw import psutilw
8
8
  from .etws.traces import trace_sysmon_process_creation
9
- from .basics import list_of_dicts, dicts
9
+ from .basics import dicts
10
10
  from .process_name_cmd import ProcessNameCmdline
11
11
  from .print_api import print_api
12
12
 
@@ -322,6 +322,7 @@ def _worker(
322
322
  running_state = False
323
323
  exception = e
324
324
  print_api(f'Exception in ProcessPollerPool: {e}', color='red')
325
+ raise
325
326
 
326
327
  if not running_state:
327
328
  process_queue.put(exception)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: atomicshop
3
- Version: 2.13.1
3
+ Version: 2.13.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=aqwPLStJUJ8YnGq3I420Yccvy8q9JIXVsivcavrcOtM,123
1
+ atomicshop/__init__.py,sha256=e_cepUB_kyEwwtls5dudclY5653g8Bn7epJj7a_khw8,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=sEuuFu3gmUrp4tY1LVxE27L0eK5LIwNKjTbV7KtzeRM,15029
30
+ atomicshop/process_poller.py,sha256=sGIiLNdYdyxdER7hKP8QcbYg1esKe5ymCfh5qUATpJM,15033
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
@@ -106,10 +106,10 @@ atomicshop/etws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
106
  atomicshop/etws/const.py,sha256=v3x_IdCYeSKbCGywiZFOZln80ldpwKW5nuMDuUe51Jg,1257
107
107
  atomicshop/etws/providers.py,sha256=fVmWi-uGdtnsQTDpu_ty6dzx0GMhGokiST73LNBEJ38,129
108
108
  atomicshop/etws/sessions.py,sha256=k3miewU278xn829cqDbsuH_bmZHPQE9-Zn-hINbxUSE,1330
109
- atomicshop/etws/trace.py,sha256=tjBvV4RxCSn8Aoxj8NVxmqHekdECne_y4Zv2lbh11ak,5341
109
+ atomicshop/etws/trace.py,sha256=ptdDyLuk8frKgmgOEug8KrH5niC6SfgWnOqw06tGPAw,7831
110
110
  atomicshop/etws/traces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
111
- atomicshop/etws/traces/trace_dns.py,sha256=wRFwfFgVgMbYJu1Sf0Yy9LPoMrf9kXejl2Xjk3PNELQ,8430
112
- atomicshop/etws/traces/trace_sysmon_process_creation.py,sha256=pjb0qzNZttucEWOKv2j_BGVqaSeKjBL4O8oCsAteiGU,4785
111
+ atomicshop/etws/traces/trace_dns.py,sha256=8Fl5UW4An3SE8tGXGTufTgOcWa574YZ6zQbU7x5DYrs,6270
112
+ atomicshop/etws/traces/trace_sysmon_process_creation.py,sha256=WdlQiOfRZC-_PpuE6BkOw5zB0DLc3fhVrANyLrgfCac,4833
113
113
  atomicshop/file_io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
114
114
  atomicshop/file_io/csvs.py,sha256=y8cJtnlN-NNxNupzJgSeGq9aQ4wNxYLFPX9vNNlUiIc,5830
115
115
  atomicshop/file_io/docxs.py,sha256=6tcYFGp0vRsHR47VwcRqwhdt2DQOwrAUYhrwN996n9U,5117
@@ -141,7 +141,7 @@ atomicshop/mitm/engines/__reference_general/responder___reference_general.py,sha
141
141
  atomicshop/monitor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
142
142
  atomicshop/monitor/change_monitor.py,sha256=eJRP7NBnA-Y_n6Ofm_jKrR5XguJV6gEmxFdtdGMBF3k,7866
143
143
  atomicshop/monitor/checks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
144
- atomicshop/monitor/checks/dns.py,sha256=_DxInUSiiMa8Bx1sYJ6fRzRl7n_eAsoaGHhUk9XgF7w,7182
144
+ atomicshop/monitor/checks/dns.py,sha256=OJWvMKl0zNtvBTYVYHSK_tfH0cNxCJ1kc_O-2Ob_5fc,7137
145
145
  atomicshop/monitor/checks/file.py,sha256=2tIDSlX2KZNc_9i9ji1tcOqupbFTIOj7cKXLyBEDWMk,3263
146
146
  atomicshop/monitor/checks/network.py,sha256=CGZWl4WlQrxayZeVF9JspJXwYA-zWx8ECWTVGSlXc98,3825
147
147
  atomicshop/monitor/checks/process_running.py,sha256=x66wd6-l466r8sbRQaIli0yswyGt1dH2DVXkGDL6O0Q,1891
@@ -265,8 +265,8 @@ atomicshop/wrappers/socketw/socket_server_tester.py,sha256=AhpurHJmP2kgzHaUbq5ey
265
265
  atomicshop/wrappers/socketw/socket_wrapper.py,sha256=aXBwlEIJhFT0-c4i8iNlFx2It9VpCEpsv--5Oqcpxao,11624
266
266
  atomicshop/wrappers/socketw/ssl_base.py,sha256=k4V3gwkbq10MvOH4btU4onLX2GNOsSfUAdcHmL1rpVE,2274
267
267
  atomicshop/wrappers/socketw/statistics_csv.py,sha256=t3dtDEfN47CfYVi0CW6Kc2QHTEeZVyYhc57IYYh5nmA,826
268
- atomicshop-2.13.1.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
269
- atomicshop-2.13.1.dist-info/METADATA,sha256=yOVvRROyXl4BM9Qr9EzI5BTZgvEo0OG3-uCo9Ryni2Y,10478
270
- atomicshop-2.13.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
271
- atomicshop-2.13.1.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
272
- atomicshop-2.13.1.dist-info/RECORD,,
268
+ atomicshop-2.13.2.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
269
+ atomicshop-2.13.2.dist-info/METADATA,sha256=FwrMOVELwpnXQKf-LbaJhMr9B2mbEos5YA94BQbSuAk,10478
270
+ atomicshop-2.13.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
271
+ atomicshop-2.13.2.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
272
+ atomicshop-2.13.2.dist-info/RECORD,,