datamule 1.6.0__tar.gz → 1.6.2__tar.gz
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.
- {datamule-1.6.0 → datamule-1.6.2}/PKG-INFO +1 -1
- {datamule-1.6.0 → datamule-1.6.2}/datamule/portfolio.py +92 -12
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/submissions/monitor.py +115 -75
- {datamule-1.6.0 → datamule-1.6.2}/datamule/seclibrary/downloader.py +163 -161
- {datamule-1.6.0 → datamule-1.6.2}/datamule/submission.py +102 -66
- datamule-1.6.2/datamule/utils/__init__.py +0 -0
- datamule-1.6.2/datamule/utils/construct_submissions_data.py +150 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule.egg-info/PKG-INFO +1 -1
- {datamule-1.6.0 → datamule-1.6.2}/datamule.egg-info/SOURCES.txt +3 -1
- {datamule-1.6.0 → datamule-1.6.2}/setup.py +1 -1
- {datamule-1.6.0 → datamule-1.6.2}/datamule/__init__.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/config.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/data/listed_filer_metadata.csv +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/datamule/__init__.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/datamule/sec_connector.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/__init__.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/document.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/__init__.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/atsn.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/cfportal.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/d.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/ex102_abs.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/ex99a_sdr.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/ex99c_sdr.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/ex99g_sdr.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/ex99i_sdr.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/information_table.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/nmfp.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/npx.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/onefourtyfour.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/ownership.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/proxy_voting_record.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/sbs.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/sbsef.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/schedule13.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/sdr.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/submission_metadata.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/ta.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/thirteenfhr.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/twentyfivense.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings/twentyfourf2nt.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings_new/__init__.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings_new/mappings.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/mappings_new/ownership.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/processing.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/document/table.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/helper.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/index.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/mapping_dicts/__init__.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/mapping_dicts/html_mapping_dicts.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/mapping_dicts/txt_mapping_dicts.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/mapping_dicts/xml_mapping_dicts.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/package_updater.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/__init__.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/infrastructure/__init__.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/infrastructure/submissions_metadata.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/submissions/__init__.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/submissions/downloader.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/submissions/eftsquery.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/submissions/streamer.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/submissions/textsearch.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/utils.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/xbrl/__init__.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/xbrl/downloadcompanyfacts.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/xbrl/filter_xbrl.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/xbrl/streamcompanyfacts.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sec/xbrl/xbrlmonitor.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/seclibrary/__init__.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/seclibrary/bq.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/seclibrary/query.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule/sheet.py +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule.egg-info/dependency_links.txt +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule.egg-info/requires.txt +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/datamule.egg-info/top_level.txt +0 -0
- {datamule-1.6.0 → datamule-1.6.2}/setup.cfg +0 -0
@@ -1,11 +1,13 @@
|
|
1
1
|
from pathlib import Path
|
2
2
|
from tqdm import tqdm
|
3
|
-
from concurrent.futures import ThreadPoolExecutor
|
3
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
4
4
|
from .submission import Submission
|
5
5
|
from .sec.submissions.downloader import download as sec_download
|
6
6
|
from .sec.submissions.textsearch import filter_text
|
7
7
|
from .config import Config
|
8
8
|
import os
|
9
|
+
import tarfile
|
10
|
+
from threading import Lock
|
9
11
|
from .helper import _process_cik_and_metadata_filters
|
10
12
|
from .seclibrary.downloader import download as seclibrary_download
|
11
13
|
from .sec.xbrl.filter_xbrl import filter_xbrl
|
@@ -21,6 +23,10 @@ class Portfolio:
|
|
21
23
|
self.submissions = []
|
22
24
|
self.submissions_loaded = False
|
23
25
|
self.MAX_WORKERS = os.cpu_count() - 1
|
26
|
+
|
27
|
+
# Batch tar support
|
28
|
+
self.batch_tar_handles = {} # {batch_tar_path: tarfile_handle}
|
29
|
+
self.batch_tar_locks = {} # {batch_tar_path: threading.Lock}
|
24
30
|
|
25
31
|
self.monitor = Monitor()
|
26
32
|
|
@@ -34,9 +40,13 @@ class Portfolio:
|
|
34
40
|
self.api_key = api_key
|
35
41
|
|
36
42
|
def _load_submissions(self):
|
37
|
-
|
38
|
-
|
43
|
+
print(f"Loading submissions")
|
44
|
+
|
45
|
+
# Separate regular and batch items
|
46
|
+
regular_items = [f for f in self.path.iterdir() if (f.is_dir() or f.suffix=='.tar') and 'batch' not in f.name]
|
47
|
+
batch_tars = [f for f in self.path.iterdir() if f.is_file() and 'batch' in f.name and f.suffix == '.tar']
|
39
48
|
|
49
|
+
# Load regular submissions (existing logic)
|
40
50
|
def load_submission(folder):
|
41
51
|
try:
|
42
52
|
return Submission(folder)
|
@@ -44,17 +54,86 @@ class Portfolio:
|
|
44
54
|
print(f"Error loading submission from {folder}: {str(e)}")
|
45
55
|
return None
|
46
56
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
57
|
+
regular_submissions = []
|
58
|
+
if regular_items:
|
59
|
+
with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor:
|
60
|
+
regular_submissions = list(tqdm(
|
61
|
+
executor.map(load_submission, regular_items),
|
62
|
+
total=len(regular_items),
|
63
|
+
desc="Loading regular submissions"
|
64
|
+
))
|
65
|
+
|
66
|
+
# Load batch submissions with parallel processing + progress
|
67
|
+
batch_submissions = []
|
68
|
+
if batch_tars:
|
69
|
+
with tqdm(desc="Loading batch submissions", unit="submissions") as pbar:
|
70
|
+
with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor:
|
71
|
+
# Submit all batch tar jobs
|
72
|
+
futures = [
|
73
|
+
executor.submit(self._load_batch_submissions_worker, batch_tar, pbar)
|
74
|
+
for batch_tar in batch_tars
|
75
|
+
]
|
76
|
+
|
77
|
+
# Collect results as they complete
|
78
|
+
for future in as_completed(futures):
|
79
|
+
try:
|
80
|
+
batch_submissions.extend(future.result())
|
81
|
+
except Exception as e:
|
82
|
+
print(f"Error in batch processing: {str(e)}")
|
83
|
+
|
84
|
+
# Combine and filter None values
|
85
|
+
self.submissions = [s for s in (regular_submissions + batch_submissions) if s is not None]
|
56
86
|
print(f"Successfully loaded {len(self.submissions)} submissions")
|
57
87
|
|
88
|
+
def _load_batch_submissions_worker(self, batch_tar_path, pbar):
|
89
|
+
"""Worker function to load submissions from one batch tar with progress updates"""
|
90
|
+
try:
|
91
|
+
# Open tar handle and store it
|
92
|
+
tar_handle = tarfile.open(batch_tar_path, 'r')
|
93
|
+
self.batch_tar_handles[batch_tar_path] = tar_handle
|
94
|
+
self.batch_tar_locks[batch_tar_path] = Lock()
|
95
|
+
|
96
|
+
# Find all accession directories
|
97
|
+
accession_prefixes = set()
|
98
|
+
for member in tar_handle.getmembers():
|
99
|
+
if '/' in member.name and member.name.endswith('metadata.json'):
|
100
|
+
accession_prefix = member.name.split('/')[0]
|
101
|
+
accession_prefixes.add(accession_prefix)
|
102
|
+
|
103
|
+
# Create submissions for each accession
|
104
|
+
submissions = []
|
105
|
+
for accession_prefix in accession_prefixes:
|
106
|
+
try:
|
107
|
+
submission = Submission(
|
108
|
+
batch_tar_path=batch_tar_path,
|
109
|
+
accession_prefix=accession_prefix,
|
110
|
+
portfolio_ref=self
|
111
|
+
)
|
112
|
+
submissions.append(submission)
|
113
|
+
pbar.update(1) # Update progress for each successful submission
|
114
|
+
except Exception as e:
|
115
|
+
print(f"Error loading batch submission {accession_prefix} from {batch_tar_path.name}: {str(e)}")
|
116
|
+
|
117
|
+
return submissions
|
118
|
+
|
119
|
+
except Exception as e:
|
120
|
+
print(f"Error loading batch tar {batch_tar_path}: {str(e)}")
|
121
|
+
return []
|
122
|
+
|
123
|
+
def _close_batch_handles(self):
|
124
|
+
"""Close all open batch tar handles to free resources"""
|
125
|
+
for handle in self.batch_tar_handles.values():
|
126
|
+
try:
|
127
|
+
handle.close()
|
128
|
+
except Exception as e:
|
129
|
+
print(f"Error closing batch tar handle: {str(e)}")
|
130
|
+
self.batch_tar_handles.clear()
|
131
|
+
self.batch_tar_locks.clear()
|
132
|
+
|
133
|
+
def __del__(self):
|
134
|
+
"""Cleanup batch tar handles on destruction"""
|
135
|
+
self._close_batch_handles()
|
136
|
+
|
58
137
|
def process_submissions(self, callback):
|
59
138
|
"""Process all submissions using a thread pool."""
|
60
139
|
if not self.submissions_loaded:
|
@@ -169,6 +248,7 @@ class Portfolio:
|
|
169
248
|
)
|
170
249
|
|
171
250
|
self.submissions_loaded = False
|
251
|
+
|
172
252
|
def monitor_submissions(self, data_callback=None, interval_callback=None,
|
173
253
|
polling_interval=1000, quiet=True, start_date=None,
|
174
254
|
validation_interval=600000):
|
@@ -9,16 +9,14 @@ from .eftsquery import EFTSQuery
|
|
9
9
|
import aiohttp
|
10
10
|
from zoneinfo import ZoneInfo
|
11
11
|
|
12
|
-
async def poll_rss(limiter):
|
12
|
+
async def poll_rss(limiter, session):
|
13
13
|
base_url = 'https://www.sec.gov/cgi-bin/browse-edgar?count=100&action=getcurrent&output=rss'
|
14
14
|
|
15
|
-
#
|
16
|
-
async with
|
17
|
-
# Use the
|
18
|
-
async with
|
19
|
-
|
20
|
-
async with session.get(base_url) as response:
|
21
|
-
content = await response.read()
|
15
|
+
# Use the rate limiter before making the request
|
16
|
+
async with limiter:
|
17
|
+
# Use the provided session instead of creating a new one
|
18
|
+
async with session.get(base_url) as response:
|
19
|
+
content = await response.read()
|
22
20
|
|
23
21
|
# Process the content
|
24
22
|
content_str = content.decode('utf-8')
|
@@ -70,12 +68,31 @@ class Monitor():
|
|
70
68
|
self.ratelimiters = {'sec.gov': PreciseRateLimiter(rate=5)}
|
71
69
|
self.efts_query = EFTSQuery(quiet=True)
|
72
70
|
self.efts_query.limiter = self.ratelimiters['sec.gov']
|
71
|
+
self.session = None
|
72
|
+
self.session_created_at = 0
|
73
|
+
self.session_lifetime = 300 # 5 minutes in seconds
|
73
74
|
|
74
75
|
def set_domain_rate_limit(self, domain, rate):
|
75
76
|
self.ratelimiters[domain] = PreciseRateLimiter(rate=rate)
|
76
77
|
if domain == 'sec.gov':
|
77
78
|
self.efts_query.limiter = self.ratelimiters[domain]
|
78
79
|
|
80
|
+
async def _ensure_fresh_session(self):
|
81
|
+
"""Ensure we have a fresh session, recreating if expired or missing"""
|
82
|
+
current_time = time.time()
|
83
|
+
|
84
|
+
# Check if we need a new session
|
85
|
+
if (self.session is None or
|
86
|
+
current_time - self.session_created_at > self.session_lifetime):
|
87
|
+
|
88
|
+
# Close old session if it exists
|
89
|
+
if self.session:
|
90
|
+
await self.session.close()
|
91
|
+
|
92
|
+
# Create new session
|
93
|
+
self.session = aiohttp.ClientSession(headers=headers)
|
94
|
+
self.session_created_at = current_time
|
95
|
+
|
79
96
|
async def _async_run_efts_query(self, **kwargs):
|
80
97
|
"""Async helper method to run EFTS query without creating a new event loop"""
|
81
98
|
# Make sure to set quiet parameter if provided in kwargs
|
@@ -103,83 +120,106 @@ class Monitor():
|
|
103
120
|
if polling_interval is None and validation_interval is None:
|
104
121
|
raise ValueError("At least one of polling_interval or validation_interval must be specified")
|
105
122
|
|
106
|
-
#
|
107
|
-
|
108
|
-
today_date = datetime.now(ZoneInfo("America/New_York")).strftime('%Y-%m-%d')
|
109
|
-
if not quiet:
|
110
|
-
print(f"Backfilling from {start_date} to {today_date}")
|
111
|
-
|
112
|
-
hits = clean_efts_hits(await self._async_run_efts_query(
|
113
|
-
filing_date=(start_date, today_date),
|
114
|
-
quiet=quiet
|
115
|
-
))
|
116
|
-
|
117
|
-
new_hits = self._filter_new_accessions(hits)
|
118
|
-
if not quiet:
|
119
|
-
print(f"New submissions found: {len(new_hits)}")
|
120
|
-
if new_hits and data_callback:
|
121
|
-
data_callback(new_hits)
|
122
|
-
|
123
|
-
# Initialize timing variables
|
124
|
-
current_time = time.time()
|
125
|
-
last_polling_time = current_time
|
126
|
-
last_validation_time = current_time
|
127
|
-
|
128
|
-
# Determine which operations to perform
|
129
|
-
do_polling = polling_interval is not None
|
130
|
-
do_validation = validation_interval is not None
|
123
|
+
# Ensure we have a fresh session
|
124
|
+
await self._ensure_fresh_session()
|
131
125
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
# RSS polling (if enabled)
|
136
|
-
if do_polling and (current_time - last_polling_time) >= polling_interval/1000:
|
137
|
-
if not quiet:
|
138
|
-
print(f"Polling RSS feed")
|
139
|
-
results = await poll_rss(self.ratelimiters['sec.gov'])
|
140
|
-
new_results = self._filter_new_accessions(results)
|
141
|
-
if new_results:
|
142
|
-
if not quiet:
|
143
|
-
print(f"Found {len(new_results)} new submissions via RSS")
|
144
|
-
if data_callback:
|
145
|
-
data_callback(new_results)
|
146
|
-
last_polling_time = current_time
|
147
|
-
|
148
|
-
# EFTS validation (if enabled)
|
149
|
-
if do_validation and (current_time - last_validation_time) >= validation_interval/1000:
|
150
|
-
# Get submissions from the last 24 hours for validation
|
126
|
+
try:
|
127
|
+
# Backfill if start_date is provided
|
128
|
+
if start_date is not None:
|
151
129
|
today_date = datetime.now(ZoneInfo("America/New_York")).strftime('%Y-%m-%d')
|
152
130
|
if not quiet:
|
153
|
-
print(f"
|
131
|
+
print(f"Backfilling from {start_date} to {today_date}")
|
154
132
|
|
155
133
|
hits = clean_efts_hits(await self._async_run_efts_query(
|
156
|
-
filing_date=(
|
134
|
+
filing_date=(start_date, today_date),
|
157
135
|
quiet=quiet
|
158
136
|
))
|
159
|
-
|
137
|
+
|
160
138
|
new_hits = self._filter_new_accessions(hits)
|
161
|
-
if
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
139
|
+
if not quiet:
|
140
|
+
print(f"New submissions found: {len(new_hits)}")
|
141
|
+
if new_hits and data_callback:
|
142
|
+
data_callback(new_hits)
|
143
|
+
|
144
|
+
# Initialize timing variables
|
145
|
+
current_time = time.time()
|
146
|
+
last_polling_time = current_time
|
147
|
+
last_validation_time = current_time
|
167
148
|
|
168
|
-
#
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
# Calculate next wake-up time
|
173
|
-
next_times = []
|
174
|
-
if do_polling:
|
175
|
-
next_times.append(last_polling_time + (polling_interval / 1000))
|
176
|
-
if do_validation:
|
177
|
-
next_times.append(last_validation_time + (validation_interval / 1000))
|
149
|
+
# Determine which operations to perform
|
150
|
+
do_polling = polling_interval is not None
|
151
|
+
do_validation = validation_interval is not None
|
178
152
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
153
|
+
while True:
|
154
|
+
current_time = time.time()
|
155
|
+
|
156
|
+
# RSS polling (if enabled)
|
157
|
+
if do_polling and (current_time - last_polling_time) >= polling_interval/1000:
|
158
|
+
if not quiet:
|
159
|
+
print(f"Polling RSS feed")
|
160
|
+
|
161
|
+
# Ensure session is fresh before polling
|
162
|
+
await self._ensure_fresh_session()
|
163
|
+
|
164
|
+
try:
|
165
|
+
results = await poll_rss(self.ratelimiters['sec.gov'], self.session)
|
166
|
+
new_results = self._filter_new_accessions(results)
|
167
|
+
if new_results:
|
168
|
+
if not quiet:
|
169
|
+
print(f"Found {len(new_results)} new submissions via RSS")
|
170
|
+
if data_callback:
|
171
|
+
data_callback(new_results)
|
172
|
+
except Exception as e:
|
173
|
+
if not quiet:
|
174
|
+
print(f"RSS polling error: {e}, will recreate session on next poll")
|
175
|
+
# Force session recreation on next poll
|
176
|
+
if self.session:
|
177
|
+
await self.session.close()
|
178
|
+
self.session = None
|
179
|
+
|
180
|
+
last_polling_time = current_time
|
181
|
+
|
182
|
+
# EFTS validation (if enabled)
|
183
|
+
if do_validation and (current_time - last_validation_time) >= validation_interval/1000:
|
184
|
+
# Get submissions from the last 24 hours for validation
|
185
|
+
today_date = datetime.now(ZoneInfo("America/New_York")).strftime('%Y-%m-%d')
|
186
|
+
if not quiet:
|
187
|
+
print(f"Validating submissions from {today_date}")
|
188
|
+
|
189
|
+
hits = clean_efts_hits(await self._async_run_efts_query(
|
190
|
+
filing_date=(today_date, today_date),
|
191
|
+
quiet=quiet
|
192
|
+
))
|
193
|
+
|
194
|
+
new_hits = self._filter_new_accessions(hits)
|
195
|
+
if new_hits:
|
196
|
+
if not quiet:
|
197
|
+
print(f"Found {len(new_hits)} new submissions via EFTS validation")
|
198
|
+
if data_callback:
|
199
|
+
data_callback(new_hits)
|
200
|
+
last_validation_time = current_time
|
201
|
+
|
202
|
+
# Interval callback
|
203
|
+
if interval_callback:
|
204
|
+
interval_callback()
|
205
|
+
|
206
|
+
# Calculate next wake-up time
|
207
|
+
next_times = []
|
208
|
+
if do_polling:
|
209
|
+
next_times.append(last_polling_time + (polling_interval / 1000))
|
210
|
+
if do_validation:
|
211
|
+
next_times.append(last_validation_time + (validation_interval / 1000))
|
212
|
+
|
213
|
+
next_wake_time = min(next_times)
|
214
|
+
current_time = time.time()
|
215
|
+
time_to_sleep = max(0, next_wake_time - current_time)
|
216
|
+
await asyncio.sleep(time_to_sleep)
|
217
|
+
|
218
|
+
finally:
|
219
|
+
# Clean up the session when done
|
220
|
+
if self.session:
|
221
|
+
await self.session.close()
|
222
|
+
self.session = None
|
183
223
|
|
184
224
|
def monitor_submissions(self, data_callback=None, interval_callback=None,
|
185
225
|
polling_interval=1000, quiet=True, start_date=None,
|