datamule 0.429__cp311-cp311-win_amd64.whl → 0.430__cp311-cp311-win_amd64.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.
datamule/document.py CHANGED
@@ -17,6 +17,9 @@ class Document:
17
17
  self.data = None
18
18
  self.content = None
19
19
 
20
+ def _load_content(self):
21
+ self.content = load_file_content(self.path)
22
+
20
23
  def contains_string(self, pattern):
21
24
  """Currently only works for .htm, .html, and .txt files"""
22
25
  if self.path.suffix in ['.htm', '.html', '.txt']:
@@ -259,7 +259,8 @@ class PremiumDownloader:
259
259
  keepalive_timeout=60
260
260
  )
261
261
 
262
- async with aiohttp.ClientSession(connector=connector, timeout=aiohttp.ClientTimeout(total=3600)) as session:
262
+ # timeout should be max 2 hours.
263
+ async with aiohttp.ClientSession(connector=connector, timeout=aiohttp.ClientTimeout(total=7200)) as session:
263
264
  tasks = [self.download_and_process(session, url, semaphore, decompression_pool, output_dir, processor) for url in urls]
264
265
  await asyncio.gather(*tasks, return_exceptions=True)
265
266
 
@@ -62,7 +62,7 @@ def load_file_content(filename):
62
62
  elif filename.suffix in ['.html','.htm']:
63
63
  return load_html_content(filename)
64
64
  else:
65
- raise ValueError(f"Unsupported file type: {filename}")
65
+ raise ValueError(f"Unsupported file type: {filename.suffix}")
66
66
 
67
67
  def clean_title(title: str) -> str:
68
68
  """Clean up section title by removing newlines, periods, and all whitespace, converting to lowercase."""
datamule/portfolio.py CHANGED
@@ -1,82 +1,95 @@
1
1
  from pathlib import Path
2
2
  from tqdm import tqdm
3
- from concurrent.futures import ProcessPoolExecutor
3
+ from concurrent.futures import ThreadPoolExecutor
4
4
  from .submission import Submission
5
5
  from .downloader.premiumdownloader import PremiumDownloader
6
6
  from .downloader.downloader import Downloader
7
7
  from .config import Config
8
+ import os
8
9
 
9
10
  class Portfolio:
10
- def create(cls, path):
11
- # This method handles the process pool lifecycle
12
- with ProcessPoolExecutor() as executor:
13
- portfolio = cls(path, executor)
14
- return portfolio
15
-
16
- def __init__(self, path, executor=None):
11
+ def __init__(self, path):
17
12
  self.path = Path(path)
18
- # check if path exists
13
+ self.submissions = []
14
+ self.MAX_WORKERS = os.cpu_count() - 1
15
+
19
16
  if self.path.exists():
20
- folders = [f for f in self.path.iterdir() if f.is_dir()]
21
- print(f"Loading {len(folders)} submissions")
22
-
23
- if executor is None:
24
- # Fall back to sequential loading if no executor
25
- self.submissions = [Submission(f) for f in tqdm(folders, desc="Loading submissions")]
26
- else:
27
- # Use provided executor for parallel loading
28
- self.submissions = list(tqdm(
29
- executor.map(Submission, folders),
30
- total=len(folders),
31
- desc="Loading submissions"
32
- ))
33
-
34
- else:
35
- pass
17
+ self._load_submissions()
18
+
19
+ def _load_submissions(self):
20
+ folders = [f for f in self.path.iterdir() if f.is_dir()]
21
+ print(f"Loading {len(folders)} submissions")
22
+
23
+ with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor:
24
+ self.submissions = list(tqdm(
25
+ executor.map(Submission, folders),
26
+ total=len(folders),
27
+ desc="Loading submissions"
28
+ ))
29
+
30
+ def process_submissions(self, callback):
31
+ """Process all submissions using a thread pool."""
32
+ with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor:
33
+ results = list(tqdm(
34
+ executor.map(callback, self.submissions),
35
+ total=len(self.submissions),
36
+ desc="Processing submissions"
37
+ ))
38
+ return results
39
+
40
+ def process_documents(self, callback):
41
+ """Process all documents using a thread pool."""
42
+ documents = [doc for sub in self.submissions for doc in sub]
43
+
44
+ with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor:
45
+ results = list(tqdm(
46
+ executor.map(callback, documents),
47
+ total=len(documents),
48
+ desc="Processing documents"
49
+ ))
50
+ return results
36
51
 
37
52
  def download_submissions(self, cik=None, ticker=None, submission_type=None, filing_date=None, provider=None):
38
53
  if provider is None:
39
54
  config = Config()
40
55
  provider = config.get_default_source()
41
56
 
42
- if provider == 'sec':
43
- downloader = Downloader()
44
- elif provider == 'datamule':
45
- downloader = PremiumDownloader()
46
-
47
- downloader.download_submissions(output_dir=self.path, cik=cik, ticker=ticker, submission_type=submission_type, filing_date=filing_date
48
- )
57
+ downloader = PremiumDownloader() if provider == 'datamule' else Downloader()
58
+ downloader.download_submissions(
59
+ output_dir=self.path,
60
+ cik=cik,
61
+ ticker=ticker,
62
+ submission_type=submission_type,
63
+ filing_date=filing_date
64
+ )
49
65
 
50
66
  # Reload submissions after download
51
- self.__init__(self.path)
52
-
67
+ self._load_submissions()
68
+
53
69
  def __iter__(self):
54
70
  return iter(self.submissions)
55
71
 
56
72
  def document_type(self, document_types):
57
- # Convert single document type to list for consistent handling
73
+ """Filter documents by type(s)."""
58
74
  if isinstance(document_types, str):
59
75
  document_types = [document_types]
60
76
 
61
77
  for submission in self.submissions:
62
78
  yield from submission.document_type(document_types)
63
79
 
64
- def contains_string(self, pattern, document_types=None, executor=None):
80
+ def contains_string(self, pattern, document_types=None):
81
+ """Search for pattern in documents, with optional type filter."""
65
82
  def check_document(document):
66
83
  return document if document.contains_string(pattern) else None
67
84
 
68
- documents = list(self.document_type(document_types) if document_types else (
69
- doc for sub in tqdm(self.submissions, desc="Collecting documents") for doc in sub
70
- ))
85
+ # Get documents, filtered by type if specified
86
+ documents = list(self.document_type(document_types)) if document_types else [
87
+ doc for sub in self.submissions for doc in sub
88
+ ]
71
89
 
72
- if executor:
73
- results = list(tqdm(
74
- executor.map(check_document, documents),
75
- total=len(documents),
76
- desc=f"Searching for '{pattern}'"
77
- ))
78
- yield from (doc for doc in results if doc is not None)
79
- else:
80
- for document in tqdm(documents, desc=f"Searching for '{pattern}'"):
81
- if document.contains_string(pattern):
82
- yield document
90
+ with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor:
91
+ results = executor.map(check_document, documents)
92
+
93
+ for doc in tqdm(results, total=len(documents), desc=f"Searching for '{pattern}'"):
94
+ if doc is not None:
95
+ yield doc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: datamule
3
- Version: 0.429
3
+ Version: 0.430
4
4
  Summary: Making it easier to use SEC filings.
5
5
  Home-page: https://github.com/john-friedman/datamule-python
6
6
  Author: John Friedman
@@ -24,8 +24,8 @@ Requires-Dist: pandas; extra == "dataset-builder"
24
24
  Requires-Dist: google-generativeai; extra == "dataset-builder"
25
25
  Requires-Dist: psutil; extra == "dataset-builder"
26
26
  Provides-Extra: all
27
- Requires-Dist: google-generativeai; extra == "all"
27
+ Requires-Dist: openai; extra == "all"
28
28
  Requires-Dist: pandas; extra == "all"
29
+ Requires-Dist: google-generativeai; extra == "all"
29
30
  Requires-Dist: psutil; extra == "all"
30
- Requires-Dist: openai; extra == "all"
31
31
  Requires-Dist: flask; extra == "all"
@@ -1,10 +1,10 @@
1
1
  datamule/__init__.py,sha256=ghCMkcNrtQ2dYz9ulnlZ0JSe-aJF0YSVa3da2g0eIWk,2425
2
2
  datamule/config.py,sha256=lrXwhbWFAF3eTa6B4OQgvexSYvaFa-EkWofpLn6AKZM,911
3
- datamule/document.py,sha256=NoJS6Q9f7Z2bZzKkaoxooYHdDwqs-TCw_BkVH3krtIU,5774
3
+ datamule/document.py,sha256=i8F0uPXKsA_PLbNk84phJ6aqHKDaFA_-5z94qH6R5-0,5859
4
4
  datamule/helper.py,sha256=8HOjB3Y7svw_zjEY-AY5JKOJ-LrBiuQMPyok3MH6CCg,4716
5
5
  datamule/monitor.py,sha256=WVds1HGV_ojYgWmo0b4Dsiv9mzZ85HHnCucH-7XoUw8,9350
6
6
  datamule/packageupdater.py,sha256=X73XlXs9bYSPiwtceaSOEq6wSCqKoG8lyhNyuha6aG0,9801
7
- datamule/portfolio.py,sha256=rXVTtoVoiBgHTuaKUIzul2HY5cZxNeyrTkHwmCnGZMY,3292
7
+ datamule/portfolio.py,sha256=OiFocVGUWKfnxrSORN42GNJhzqG35fybNIaE2S_h49c,3660
8
8
  datamule/submission.py,sha256=cfX7fvHQBObff0N1jzikCGTuAUE1bGIyqenLRxch9eg,2865
9
9
  datamule/data/company_former_names.csv,sha256=zTBWdV12_JE3aROFOMrFNTHLPW_M4TDruxtl15-XfA0,714528
10
10
  datamule/data/company_metadata.csv,sha256=X7uSIwConqC0sz-moIhXIISg6FI7GLGSlvAfDDf8Sd0,3078648
@@ -13,7 +13,7 @@ datamule/data/sec-glossary.csv,sha256=TPjTBVM3kyFd8xHsmihykepvKbuLAAthOfEDjh_H-K
13
13
  datamule/data/xbrl_descriptions.csv,sha256=Hg9BOo9zSjR7Khvx0pikILcbmDK_A404dmQtWuESK4s,2631548
14
14
  datamule/dataset_builder/dataset_builder.py,sha256=h1JDzLcMKxxMcXcD24EyqjUPp78iLWCovfNLQtpwZi4,10005
15
15
  datamule/downloader/downloader.py,sha256=jXt9bTHH0NgV3E54B1xEChNq24FkUvpIX3BvXxVxgVQ,14921
16
- datamule/downloader/premiumdownloader.py,sha256=jLr9jWDUrOQQXagbpbqc9rc2H_od6tDhyKnmNKABids,14537
16
+ datamule/downloader/premiumdownloader.py,sha256=8th3Oas9l0wc1yTC7TvK0oNNYAF_UKpW3m_TtVc9WTo,14583
17
17
  datamule/mulebot/__init__.py,sha256=GM5cTnijSSLO9GXFdsCuz5B1iwGUcxDbpoBQ6zw1Odo,30
18
18
  datamule/mulebot/helper.py,sha256=Hzzr2HReHpFe2GfpVU79EXvQFx3oL9UiwkJp--Sd1N4,1114
19
19
  datamule/mulebot/mulebot.py,sha256=wN0Tv5fvarXgk1LRCcgPhj8Xgd8uYFn-cMucxTxRsEo,5966
@@ -39,7 +39,7 @@ datamule/parser/document_parsing/basic_13g_parser.py,sha256=H9_MuOgkYVTIGwhj9w_W
39
39
  datamule/parser/document_parsing/basic_8k_parser.py,sha256=fzf8q9LOpBMHGWw-sfqUq3pyFZBlw47nLJBQWPhtGGg,2549
40
40
  datamule/parser/document_parsing/form_d_parser.py,sha256=NTAfC8W3i2y7aIofXoLlAbY-4F6QVELYfIPIrVErjVY,2105
41
41
  datamule/parser/document_parsing/generalized_item_parser.py,sha256=M2bmYivSXe0POyBtDlPMykyyCgG8n1egRpJuZtZTR_g,2694
42
- datamule/parser/document_parsing/helper.py,sha256=wEhqx70CQ0SyEfmOfSCcNsf2TNTtomZvTbmoOhwG3kk,2607
42
+ datamule/parser/document_parsing/helper.py,sha256=wk4dotrn8Ejbb4OMc7nCxqxK-x_O9ChjM8_Qoh9nOiQ,2614
43
43
  datamule/parser/document_parsing/information_table_parser_13fhr.py,sha256=vCIEqxOoBf1lnOKTJAifnPEXihtSPZcwRFW8yWhEZLc,1808
44
44
  datamule/parser/document_parsing/insider_trading_parser.py,sha256=IrH5a3qikPjC1GFoYzapfYW_CArbK0FkbJLbWSfsYNQ,7264
45
45
  datamule/parser/document_parsing/mappings.py,sha256=dq6EjaxxDHjH-sg62adRwJOf1v736QiLwXavOHs2vy8,5380
@@ -47,8 +47,8 @@ datamule/parser/document_parsing/n_port_p_parser.py,sha256=T6GliMm-TETPsFM-hDKt1
47
47
  datamule/parser/document_parsing/sec_parser.py,sha256=YewOdOsi0P25teQuxS5DNEND9ZCyxE2ewK1DoP9mPto,2788
48
48
  datamule/parser/document_parsing/sgml_parser.py,sha256=ASpe1SzgPj4qk0VOmmuMiEQeatjcwZzsuO3MvsYCHhc,3410
49
49
  datamule/parser/sgml_parsing/sgml_parser_cy.c,sha256=66QwBAmhxkKdhCgCjOkg29umbIgQoK4T5_mmMy3NkkM,841089
50
- datamule/parser/sgml_parsing/sgml_parser_cy.cp311-win_amd64.pyd,sha256=hxC7wal9q5mM2m0LeL_Zmor4p8OEmvmildMRfNMi9KQ,134144
51
- datamule-0.429.dist-info/METADATA,sha256=6v54qYPbi034JPyV1c2Qg7124Aq1V19Xy8_ris2YLZc,1037
52
- datamule-0.429.dist-info/WHEEL,sha256=nkBcd8Ko0v5sEcSagm2-x_RVrb8gBSkTa8VFFZ0Mr1o,101
53
- datamule-0.429.dist-info/top_level.txt,sha256=iOfgmtSMFVyr7JGl_bYSTDry79JbmsG4p8zKq89ktKk,9
54
- datamule-0.429.dist-info/RECORD,,
50
+ datamule/parser/sgml_parsing/sgml_parser_cy.cp311-win_amd64.pyd,sha256=ba4PiYLkkt5Zu03EW0rMYSRx7O8OIZl0jhJmQgYnabI,134144
51
+ datamule-0.430.dist-info/METADATA,sha256=Je7_NZ1r2SsMkjhxfVoW4geNldopmZixvucYQE6Y6Pw,1037
52
+ datamule-0.430.dist-info/WHEEL,sha256=aYhuBGe9EmKAtiGSW4PepBJu0RwWKJi5OB2SjRJXvrY,101
53
+ datamule-0.430.dist-info/top_level.txt,sha256=iOfgmtSMFVyr7JGl_bYSTDry79JbmsG4p8zKq89ktKk,9
54
+ datamule-0.430.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.7.0)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp311-cp311-win_amd64
5
5