rust-crate-pipeline 1.3.2__py3-none-any.whl → 1.3.3__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.
@@ -1,327 +1,321 @@
1
- # network.py
2
- import os
3
- import sys
4
- import re
5
- import time
6
- import logging
7
- import requests
8
- from bs4 import BeautifulSoup, Tag
9
- from typing import Any, Union
10
- from .config import PipelineConfig
11
-
12
- # Import utilities
13
- # Add the parent directory to the path to import utils
14
- sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
15
-
16
-
17
- class GitHubBatchClient:
18
- def __init__(self, config: PipelineConfig) -> None:
19
- self.config = config
20
- # Simple headers without dependency on HTTPClientUtils
21
- self.headers = {
22
- "Accept": "application/vnd.github.v3+json",
23
- "User-Agent": "SigilDERG-Data-Production/1.3.2",
24
- }
25
- if config.github_token:
26
- self.headers["Authorization"] = f"token {config.github_token}"
27
-
28
- # Simple session without dependency on HTTPClientUtils
29
- self.session = requests.Session()
30
- self.session.headers.update(self.headers)
31
- self.remaining_calls = 5000
32
- self.reset_time = 0
33
-
34
- def check_rate_limit(self) -> None:
35
- """Check and update current rate limit status"""
36
- try:
37
- response = self.session.get(
38
- "https://api.github.com/rate_limit", headers=self.headers
39
- )
40
- if response.ok:
41
- data = response.json()
42
- self.remaining_calls = data["resources"]["core"]["remaining"]
43
- self.reset_time = data["resources"]["core"]["reset"]
44
-
45
- if self.remaining_calls < 100:
46
- reset_in = self.reset_time - time.time()
47
- logging.warning(
48
- f"GitHub API rate limit low: {self.remaining_calls} remaining. "
49
- f"Resets in {reset_in / 60:.1f} minutes"
50
- )
51
- except Exception:
52
- pass
53
-
54
- def get_repo_stats(self, owner: str, repo: str) -> "dict[str, Any]":
55
- """Get repository statistics"""
56
- try:
57
- url = f"https://api.github.com/repos/{owner}/{repo}"
58
- response = self.session.get(url, headers=self.headers)
59
- if response.ok:
60
- return response.json()
61
- else:
62
- logging.warning(
63
- f"Failed to get repo stats for {owner}/{repo}: "
64
- f"{response.status_code}"
65
- )
66
- return {}
67
- except Exception as e:
68
- logging.error(f"Error fetching repo stats: {str(e)}")
69
- return {}
70
-
71
- def batch_get_repo_stats(self, repo_list: "list[str]") -> "dict[str, dict[str, Any]]":
72
- """Get statistics for multiple repositories in a batch"""
73
- self.check_rate_limit()
74
-
75
- results: "dict[str, dict[str, Any]]" = {}
76
- for repo_url in repo_list:
77
- # Extract owner/repo from URL
78
- match = re.search(r"github\.com/([^/]+)/([^/\.]+)", repo_url)
79
- if not match:
80
- continue
81
-
82
- owner, repo = match.groups()
83
- repo = repo.split(".")[0] # Remove .git extension if present
84
-
85
- # Get stats
86
- stats = self.get_repo_stats(owner, repo)
87
- results[repo_url] = stats
88
-
89
- # Be nice to GitHub API
90
- time.sleep(0.1)
91
- return results
92
-
93
-
94
- class CrateAPIClient:
95
- def __init__(self, config: PipelineConfig) -> None:
96
- self.config = config
97
- # Simple session without dependency on HTTPClientUtils
98
- self.session = requests.Session()
99
- self.session.headers.update({"User-Agent": "SigilDERG-Data-Production/1.3.2"})
100
-
101
- def fetch_crate_metadata(self, crate_name: str) -> "dict[str, Any] | None":
102
- """Fetch metadata with retry logic"""
103
- for attempt in range(self.config.max_retries):
104
- try:
105
- return self._fetch_metadata(crate_name)
106
- except Exception as e:
107
- logging.warning(
108
- f"Attempt {
109
- attempt +
110
- 1} failed for {crate_name}: {
111
- str(e)}"
112
- )
113
- wait = 2**attempt
114
- time.sleep(wait)
115
- return None
116
-
117
- def _fetch_metadata(self, crate_name: str) -> "dict[str, Any] | None":
118
- """Enhanced metadata fetching that tries multiple sources"""
119
- # First try crates.io (primary source)
120
- try:
121
- r = self.session.get(f"https://crates.io/api/v1/crates/{crate_name}")
122
- if r.ok:
123
- data = r.json()
124
- crate_data = data["crate"]
125
- latest = crate_data["newest_version"]
126
-
127
- # Get readme
128
- readme_response = self.session.get(
129
- f"https://crates.io/api/v1/crates/{crate_name}/readme"
130
- )
131
- readme = readme_response.text if readme_response.ok else ""
132
-
133
- # Get dependencies
134
- deps_url = (
135
- f"https://crates.io/api/v1/crates/{crate_name}/"
136
- f"{latest}/dependencies"
137
- )
138
- deps_response = self.session.get(deps_url)
139
- deps: list[dict[str, Any]] = (
140
- deps_response.json().get("dependencies", [])
141
- if deps_response.ok
142
- else []
143
- )
144
-
145
- # Get features - using the versions endpoint
146
- features = []
147
- versions_response = self.session.get(
148
- f"https://crates.io/api/v1/crates/{crate_name}/{latest}"
149
- )
150
- if versions_response.ok:
151
- version_data = versions_response.json().get("version", {})
152
- features_dict = version_data.get("features", {})
153
- features = [
154
- {"name": k, "dependencies": v} for k, v in features_dict.items()
155
- ]
156
-
157
- # Repository info and GitHub stars
158
- repo = crate_data.get("repository", "")
159
- gh_stars = 0
160
-
161
- # Check if it's a GitHub repo
162
- if "github.com" in repo and self.config.github_token:
163
- match = re.search(r"github.com/([^/]+)/([^/]+)", repo)
164
- if match:
165
- owner, repo_name = match.groups()
166
- repo_name = repo_name.split(".")[0] # Handle .git extensions
167
- gh_url = f"https://api.github.com/repos/{owner}/{repo_name}"
168
- gh_headers: dict[str, str] = {}
169
- if self.config.github_token:
170
- gh_headers["Authorization"] = (
171
- f"token {self.config.github_token}"
172
- )
173
-
174
- gh = self.session.get(gh_url, headers=gh_headers)
175
- if gh.ok:
176
- gh_data = gh.json()
177
- gh_stars = gh_data.get("stargazers_count", 0)
178
-
179
- # Check if it's hosted on lib.rs
180
- lib_rs_data = {}
181
- if "lib.rs" in repo:
182
- lib_rs_url = f"https://lib.rs/crates/{crate_name}"
183
- lib_rs_response = self.session.get(lib_rs_url)
184
- if lib_rs_response.ok:
185
- soup = BeautifulSoup(lib_rs_response.text, "html.parser")
186
- # Get README from lib.rs if not already available
187
- if not readme:
188
- readme_div = soup.find("div", class_="readme")
189
- if readme_div:
190
- readme = readme_div.get_text(
191
- strip=True
192
- ) # Get lib.rs specific stats
193
- stats_div = soup.find("div", class_="crate-stats")
194
- if isinstance(stats_div, Tag):
195
- downloads_text = stats_div.find(
196
- string=re.compile(r"[\d,]+ downloads")
197
- )
198
- if downloads_text:
199
- lib_rs_data["librs_downloads"] = int(
200
- re.sub(r"[^\d]", "", str(downloads_text))
201
- )
202
-
203
- # Extract code snippets and sections (simplified)
204
- code_snippets: list[str] = (
205
- []
206
- ) # Simplified - would normally extract from readme
207
- readme_sections: dict[str, str] = (
208
- {}
209
- ) # Simplified - would normally parse sections
210
-
211
- result: dict[str, Any] = {
212
- "name": crate_name,
213
- "version": latest,
214
- "description": crate_data.get("description", ""),
215
- "repository": repo,
216
- "keywords": crate_data.get("keywords", []),
217
- "categories": crate_data.get("categories", []),
218
- "readme": readme,
219
- "downloads": crate_data.get("downloads", 0),
220
- "github_stars": gh_stars,
221
- "dependencies": deps,
222
- "code_snippets": code_snippets,
223
- "features": features,
224
- "readme_sections": readme_sections,
225
- **lib_rs_data,
226
- }
227
-
228
- return result
229
-
230
- except Exception as e:
231
- logging.error(
232
- f"Failed fetching metadata for {crate_name}: {
233
- str(e)}"
234
- )
235
- raise
236
-
237
- # If crates.io fails, try lib.rs
238
- try:
239
- r = self.session.get(f"https://lib.rs/crates/{crate_name}")
240
- if r.ok:
241
- soup = BeautifulSoup(r.text, "html.parser")
242
-
243
- # Extract metadata from lib.rs page
244
- h1 = soup.select_one("h1")
245
- name = h1.text.strip() if h1 else crate_name
246
-
247
- # Find description
248
- desc_elem = soup.select_one(".description")
249
- description = desc_elem.text.strip() if desc_elem else ""
250
-
251
- # Find repository link
252
- repo_link: Union[str, None] = None
253
- for a in soup.select("a"):
254
- href = a.get("href")
255
- if href and isinstance(href, str) and "github.com" in href:
256
- repo_link = href
257
- break
258
-
259
- # Find keywords
260
- keywords_elem = soup.select_one(".keywords")
261
- keywords = (
262
- [k.text.strip() for k in keywords_elem.find_all("a")]
263
- if keywords_elem
264
- else []
265
- )
266
-
267
- # Basic metadata from lib.rs
268
- return {
269
- "name": name,
270
- "version": "latest", # lib.rs doesn't easily expose version
271
- "description": description,
272
- "repository": repo_link or "",
273
- "keywords": keywords,
274
- "categories": [],
275
- "readme": "",
276
- "downloads": 0,
277
- "github_stars": 0,
278
- "dependencies": [],
279
- "code_snippets": [],
280
- "features": [],
281
- "readme_sections": {},
282
- "source": "lib.rs",
283
- }
284
- except Exception:
285
- pass
286
-
287
- # Finally, try GitHub search
288
- try:
289
- # This is a simplification - GitHub's search API requires
290
- # authentication
291
- gh_search_headers: dict[str, str] = {}
292
- if self.config.github_token:
293
- gh_search_headers["Authorization"] = f"token {self.config.github_token}"
294
-
295
- search_url = (
296
- f"https://api.github.com/search/repositories?"
297
- f"q={crate_name}+language:rust"
298
- )
299
- r = requests.get(search_url, headers=gh_search_headers)
300
-
301
- if r.ok:
302
- results = r.json().get("items", [])
303
- if results:
304
- repo = results[0] # Take first match
305
-
306
- # Basic metadata from GitHub
307
- return {
308
- "name": crate_name,
309
- "version": "unknown",
310
- "description": repo.get("description", ""),
311
- "repository": repo.get("html_url", ""),
312
- "keywords": [],
313
- "categories": [],
314
- "readme": "",
315
- "downloads": 0,
316
- "github_stars": repo.get("stargazers_count", 0),
317
- "dependencies": [],
318
- "code_snippets": [],
319
- "features": [],
320
- "readme_sections": {},
321
- "source": "github",
322
- }
323
- except Exception:
324
- pass
325
-
326
- # If all sources fail
327
- return None
1
+ # network.py
2
+ import os
3
+ import sys
4
+ import re
5
+ import time
6
+ import logging
7
+ import requests
8
+ from bs4 import BeautifulSoup, Tag
9
+ from typing import Any, Union
10
+ from .config import PipelineConfig
11
+
12
+ # Import utilities
13
+ # Add the parent directory to the path to import utils
14
+ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
15
+
16
+
17
+ class GitHubBatchClient:
18
+ def __init__(self, config: PipelineConfig) -> None:
19
+ self.config = config
20
+ # Simple headers without dependency on HTTPClientUtils
21
+ self.headers = {
22
+ "Accept": "application/vnd.github.v3+json",
23
+ "User-Agent": "SigilDERG-Data-Production/1.3.2",
24
+ }
25
+ if config.github_token:
26
+ self.headers["Authorization"] = f"token {config.github_token}"
27
+
28
+ # Simple session without dependency on HTTPClientUtils
29
+ self.session = requests.Session()
30
+ self.session.headers.update(self.headers)
31
+ self.remaining_calls = 5000
32
+ self.reset_time = 0
33
+
34
+ def check_rate_limit(self) -> None:
35
+ """Check and update current rate limit status"""
36
+ try:
37
+ response = self.session.get(
38
+ "https://api.github.com/rate_limit", headers=self.headers
39
+ )
40
+ if response.ok:
41
+ data = response.json()
42
+ self.remaining_calls = data["resources"]["core"]["remaining"]
43
+ self.reset_time = data["resources"]["core"]["reset"]
44
+
45
+ if self.remaining_calls < 100:
46
+ reset_in = self.reset_time - time.time()
47
+ logging.warning(
48
+ f"GitHub API rate limit low: {self.remaining_calls} remaining. Resets in {reset_in / 60:.1f} minutes"
49
+ )
50
+ except Exception:
51
+ pass
52
+
53
+ def get_repo_stats(self, owner: str, repo: str) -> "dict[str, Any]":
54
+ """Get repository statistics"""
55
+ try:
56
+ url = f"https://api.github.com/repos/{owner}/{repo}"
57
+ response = self.session.get(url, headers=self.headers)
58
+ if response.ok:
59
+ return response.json()
60
+ else:
61
+ logging.warning(
62
+ f"Failed to get repo stats for {owner}/{repo}: {response.status_code}"
63
+ )
64
+ return {}
65
+ except Exception as e:
66
+ logging.error(f"Error fetching repo stats: {str(e)}")
67
+ return {}
68
+
69
+ def batch_get_repo_stats(self, repo_list: "list[str]") -> "dict[str, dict[str, Any]]":
70
+ """Get statistics for multiple repositories in a batch"""
71
+ self.check_rate_limit()
72
+
73
+ results: "dict[str, dict[str, Any]]" = {}
74
+ for repo_url in repo_list:
75
+ # Extract owner/repo from URL
76
+ match = re.search(r"github\.com/([^/]+)/([^/\.]+)", repo_url)
77
+ if not match:
78
+ continue
79
+
80
+ owner, repo = match.groups()
81
+ repo = repo.split(".")[0] # Remove .git extension if present
82
+
83
+ # Get stats
84
+ stats = self.get_repo_stats(owner, repo)
85
+ results[repo_url] = stats
86
+
87
+ # Be nice to GitHub API
88
+ time.sleep(0.1)
89
+ return results
90
+
91
+
92
+ class CrateAPIClient:
93
+ def __init__(self, config: PipelineConfig) -> None:
94
+ self.config = config
95
+ # Simple session without dependency on HTTPClientUtils
96
+ self.session = requests.Session()
97
+ self.session.headers.update({"User-Agent": "SigilDERG-Data-Production/1.3.2"})
98
+
99
+ def fetch_crate_metadata(self, crate_name: str) -> "dict[str, Any] | None":
100
+ """Fetch metadata with retry logic"""
101
+ for attempt in range(self.config.max_retries):
102
+ try:
103
+ return self._fetch_metadata(crate_name)
104
+ except Exception as e:
105
+ logging.warning(
106
+ f"Attempt {attempt + 1} failed for {crate_name}: {str(e)}"
107
+ )
108
+ wait = 2**attempt
109
+ time.sleep(wait)
110
+ return None
111
+
112
+ def _fetch_metadata(self, crate_name: str) -> "dict[str, Any] | None":
113
+ """Enhanced metadata fetching that tries multiple sources"""
114
+ # First try crates.io (primary source)
115
+ try:
116
+ r = self.session.get(f"https://crates.io/api/v1/crates/{crate_name}")
117
+ if r.ok:
118
+ data = r.json()
119
+ crate_data = data["crate"]
120
+ latest = crate_data["newest_version"]
121
+
122
+ # Get readme
123
+ readme_response = self.session.get(
124
+ f"https://crates.io/api/v1/crates/{crate_name}/readme"
125
+ )
126
+ readme = readme_response.text if readme_response.ok else ""
127
+
128
+ # Get dependencies
129
+ deps_url = (
130
+ f"https://crates.io/api/v1/crates/{crate_name}/"
131
+ f"{latest}/dependencies"
132
+ )
133
+ deps_response = self.session.get(deps_url)
134
+ deps: list[dict[str, Any]] = (
135
+ deps_response.json().get("dependencies", [])
136
+ if deps_response.ok
137
+ else []
138
+ )
139
+
140
+ # Get features - using the versions endpoint
141
+ features = []
142
+ versions_response = self.session.get(
143
+ f"https://crates.io/api/v1/crates/{crate_name}/{latest}"
144
+ )
145
+ if versions_response.ok:
146
+ version_data = versions_response.json().get("version", {})
147
+ features_dict = version_data.get("features", {})
148
+ features = [
149
+ {"name": k, "dependencies": v} for k, v in features_dict.items()
150
+ ]
151
+
152
+ # Repository info and GitHub stars
153
+ repo = crate_data.get("repository", "")
154
+ gh_stars = 0
155
+
156
+ # Check if it's a GitHub repo
157
+ if "github.com" in repo and self.config.github_token:
158
+ match = re.search(r"github.com/([^/]+)/([^/]+)", repo)
159
+ if match:
160
+ owner, repo_name = match.groups()
161
+ repo_name = repo_name.split(".")[0] # Handle .git extensions
162
+ gh_url = f"https://api.github.com/repos/{owner}/{repo_name}"
163
+ gh_headers: dict[str, str] = {}
164
+ if self.config.github_token:
165
+ gh_headers["Authorization"] = (
166
+ f"token {self.config.github_token}"
167
+ )
168
+
169
+ gh = self.session.get(gh_url, headers=gh_headers)
170
+ if gh.ok:
171
+ gh_data = gh.json()
172
+ gh_stars = gh_data.get("stargazers_count", 0)
173
+
174
+ # Check if it's hosted on lib.rs
175
+ lib_rs_data = {}
176
+ if "lib.rs" in repo:
177
+ lib_rs_url = f"https://lib.rs/crates/{crate_name}"
178
+ lib_rs_response = self.session.get(lib_rs_url)
179
+ if lib_rs_response.ok:
180
+ soup = BeautifulSoup(lib_rs_response.text, "html.parser")
181
+ # Get README from lib.rs if not already available
182
+ if not readme:
183
+ readme_div = soup.find("div", class_="readme")
184
+ if readme_div:
185
+ readme = readme_div.get_text(
186
+ strip=True
187
+ ) # Get lib.rs specific stats
188
+ stats_div = soup.find("div", class_="crate-stats")
189
+ if isinstance(stats_div, Tag):
190
+ downloads_text = stats_div.find(
191
+ string=re.compile(r"[\d,]+ downloads")
192
+ )
193
+ if downloads_text:
194
+ lib_rs_data["librs_downloads"] = int(
195
+ re.sub(r"[^\d]", "", str(downloads_text))
196
+ )
197
+
198
+ # Extract code snippets and sections (simplified)
199
+ code_snippets: list[str] = (
200
+ []
201
+ ) # Simplified - would normally extract from readme
202
+ readme_sections: dict[str, str] = (
203
+ {}
204
+ ) # Simplified - would normally parse sections
205
+
206
+ result: dict[str, Any] = {
207
+ "name": crate_name,
208
+ "version": latest,
209
+ "description": crate_data.get("description", ""),
210
+ "repository": repo,
211
+ "keywords": crate_data.get("keywords", []),
212
+ "categories": crate_data.get("categories", []),
213
+ "readme": readme,
214
+ "downloads": crate_data.get("downloads", 0),
215
+ "github_stars": gh_stars,
216
+ "dependencies": deps,
217
+ "code_snippets": code_snippets,
218
+ "features": features,
219
+ "readme_sections": readme_sections,
220
+ **lib_rs_data,
221
+ }
222
+
223
+ return result
224
+
225
+ except Exception as e:
226
+ logging.error(
227
+ f"Failed fetching metadata for {crate_name}: {str(e)}"
228
+ )
229
+ raise
230
+
231
+ # If crates.io fails, try lib.rs
232
+ try:
233
+ r = self.session.get(f"https://lib.rs/crates/{crate_name}")
234
+ if r.ok:
235
+ soup = BeautifulSoup(r.text, "html.parser")
236
+
237
+ # Extract metadata from lib.rs page
238
+ h1 = soup.select_one("h1")
239
+ name = h1.text.strip() if h1 else crate_name
240
+
241
+ # Find description
242
+ desc_elem = soup.select_one(".description")
243
+ description = desc_elem.text.strip() if desc_elem else ""
244
+
245
+ # Find repository link
246
+ repo_link: Union[str, None] = None
247
+ for a in soup.select("a"):
248
+ href = a.get("href")
249
+ if href and isinstance(href, str) and "github.com" in href:
250
+ repo_link = href
251
+ break
252
+
253
+ # Find keywords
254
+ keywords_elem = soup.select_one(".keywords")
255
+ keywords = (
256
+ [k.text.strip() for k in keywords_elem.find_all("a")]
257
+ if keywords_elem
258
+ else []
259
+ )
260
+
261
+ # Basic metadata from lib.rs
262
+ return {
263
+ "name": name,
264
+ "version": "latest", # lib.rs doesn't easily expose version
265
+ "description": description,
266
+ "repository": repo_link or "",
267
+ "keywords": keywords,
268
+ "categories": [],
269
+ "readme": "",
270
+ "downloads": 0,
271
+ "github_stars": 0,
272
+ "dependencies": [],
273
+ "code_snippets": [],
274
+ "features": [],
275
+ "readme_sections": {},
276
+ "source": "lib.rs",
277
+ }
278
+ except Exception:
279
+ pass
280
+
281
+ # Finally, try GitHub search
282
+ try:
283
+ # This is a simplification - GitHub's search API requires
284
+ # authentication
285
+ gh_search_headers: dict[str, str] = {}
286
+ if self.config.github_token:
287
+ gh_search_headers["Authorization"] = f"token {self.config.github_token}"
288
+
289
+ search_url = (
290
+ f"https://api.github.com/search/repositories?"
291
+ f"q={crate_name}+language:rust"
292
+ )
293
+ r = requests.get(search_url, headers=gh_search_headers)
294
+
295
+ if r.ok:
296
+ results = r.json().get("items", [])
297
+ if results:
298
+ repo = results[0] # Take first match
299
+
300
+ # Basic metadata from GitHub
301
+ return {
302
+ "name": crate_name,
303
+ "version": "unknown",
304
+ "description": repo.get("description", ""),
305
+ "repository": repo.get("html_url", ""),
306
+ "keywords": [],
307
+ "categories": [],
308
+ "readme": "",
309
+ "downloads": 0,
310
+ "github_stars": repo.get("stargazers_count", 0),
311
+ "dependencies": [],
312
+ "code_snippets": [],
313
+ "features": [],
314
+ "readme_sections": {},
315
+ "source": "github",
316
+ }
317
+ except Exception:
318
+ pass
319
+
320
+ # If all sources fail
321
+ return None
@@ -0,0 +1,334 @@
1
+ # progress_monitor.py
2
+ """
3
+ Real-time progress monitoring for the Rust Crate Pipeline (CLI-only).
4
+
5
+ This module provides:
6
+ - Live progress bars with ETA
7
+ - Real-time statistics and metrics
8
+ - Status printouts
9
+ - Performance monitoring
10
+ - Error tracking and reporting
11
+ - Status JSON file for external tools/scripts
12
+ """
13
+
14
+ import time
15
+ import threading
16
+ import json
17
+ import os
18
+ from datetime import datetime, timedelta
19
+ from typing import Dict, List, Optional, Any, Union
20
+ from dataclasses import dataclass, field
21
+ from collections import deque
22
+ import logging
23
+
24
+ try:
25
+ from tqdm import tqdm
26
+ TQDM_AVAILABLE = True
27
+ except ImportError:
28
+ TQDM_AVAILABLE = False
29
+
30
+ try:
31
+ import psutil
32
+ PSUTIL_AVAILABLE = True
33
+ except ImportError:
34
+ PSUTIL_AVAILABLE = False
35
+
36
+
37
+ @dataclass
38
+ class PipelineMetrics:
39
+ """Real-time pipeline metrics and statistics."""
40
+ total_crates: int = 0
41
+ processed_crates: int = 0
42
+ successful_crates: int = 0
43
+ failed_crates: int = 0
44
+ skipped_crates: int = 0
45
+ current_batch: int = 0
46
+ total_batches: int = 0
47
+ start_time: Optional[datetime] = None
48
+ current_operation: str = "Initializing"
49
+ errors: List[Dict[str, Any]] = field(default_factory=list)
50
+ warnings: List[Dict[str, Any]] = field(default_factory=list)
51
+ performance_stats: Dict[str, Any] = field(default_factory=dict)
52
+
53
+ @property
54
+ def progress_percentage(self) -> float:
55
+ """Calculate progress percentage."""
56
+ if self.total_crates == 0:
57
+ return 0.0
58
+ return (self.processed_crates / self.total_crates) * 100
59
+
60
+ @property
61
+ def success_rate(self) -> float:
62
+ """Calculate success rate percentage."""
63
+ if self.processed_crates == 0:
64
+ return 0.0
65
+ return (self.successful_crates / self.processed_crates) * 100
66
+
67
+ @property
68
+ def elapsed_time(self) -> timedelta:
69
+ """Calculate elapsed time."""
70
+ if not self.start_time:
71
+ return timedelta(0)
72
+ return datetime.now() - self.start_time
73
+
74
+ @property
75
+ def estimated_completion(self) -> Optional[datetime]:
76
+ """Estimate completion time."""
77
+ if self.processed_crates == 0 or not self.start_time:
78
+ return None
79
+
80
+ avg_time_per_crate = self.elapsed_time / self.processed_crates
81
+ remaining_crates = self.total_crates - self.processed_crates
82
+ estimated_remaining = avg_time_per_crate * remaining_crates
83
+
84
+ return datetime.now() + estimated_remaining
85
+
86
+
87
+ class ProgressMonitor:
88
+ """Real-time progress monitoring with live dashboard."""
89
+
90
+ def __init__(self, total_crates: int, output_dir: str = "output"):
91
+ self.metrics = PipelineMetrics(total_crates=total_crates)
92
+ self.output_dir = output_dir
93
+ self.logger = logging.getLogger(__name__)
94
+
95
+ # Performance tracking
96
+ self.crate_times: deque = deque(maxlen=100) # Last 100 crate processing times
97
+ self.batch_times: deque = deque(maxlen=50) # Last 50 batch processing times
98
+
99
+ # Status tracking
100
+ self.current_crate: Optional[str] = None
101
+ self.current_operation: str = "Initializing"
102
+ self.status_file = os.path.join(output_dir, "pipeline_status.json")
103
+
104
+ # Thread safety
105
+ self._lock = threading.Lock()
106
+
107
+ # Initialize
108
+ self.metrics.start_time = datetime.now()
109
+ self._save_status()
110
+
111
+ # Create output directory if it doesn't exist
112
+ os.makedirs(output_dir, exist_ok=True)
113
+
114
+ def start_crate(self, crate_name: str) -> None:
115
+ """Mark the start of processing a crate."""
116
+ with self._lock:
117
+ self.current_crate = crate_name
118
+ self.current_operation = f"Processing {crate_name}"
119
+ self.metrics.current_operation = self.current_operation
120
+ self._save_status()
121
+
122
+ def complete_crate(self, crate_name: str, success: bool = True,
123
+ processing_time: Optional[float] = None) -> None:
124
+ """Mark the completion of processing a crate."""
125
+ with self._lock:
126
+ self.metrics.processed_crates += 1
127
+
128
+ if success:
129
+ self.metrics.successful_crates += 1
130
+ else:
131
+ self.metrics.failed_crates += 1
132
+
133
+ if processing_time:
134
+ self.crate_times.append(processing_time)
135
+
136
+ self.current_crate = None
137
+ self.current_operation = "Waiting for next crate"
138
+ self.metrics.current_operation = self.current_operation
139
+
140
+ # Update performance stats
141
+ self._update_performance_stats()
142
+ self._save_status()
143
+
144
+ def skip_crate(self, crate_name: str, reason: str = "Unknown") -> None:
145
+ """Mark a crate as skipped."""
146
+ with self._lock:
147
+ self.metrics.processed_crates += 1
148
+ self.metrics.skipped_crates += 1
149
+
150
+ self.metrics.warnings.append({
151
+ "crate": crate_name,
152
+ "reason": reason,
153
+ "timestamp": datetime.now().isoformat()
154
+ })
155
+
156
+ self._save_status()
157
+
158
+ def start_batch(self, batch_num: int, batch_size: int) -> None:
159
+ """Mark the start of processing a batch."""
160
+ with self._lock:
161
+ self.metrics.current_batch = batch_num
162
+ self.current_operation = f"Processing batch {batch_num}"
163
+ self.metrics.current_operation = self.current_operation
164
+ self._save_status()
165
+
166
+ def complete_batch(self, batch_num: int, processing_time: Optional[float] = None) -> None:
167
+ """Mark the completion of processing a batch."""
168
+ with self._lock:
169
+ if processing_time:
170
+ self.batch_times.append(processing_time)
171
+
172
+ self.current_operation = "Batch completed, preparing next batch"
173
+ self.metrics.current_operation = self.current_operation
174
+ self._save_status()
175
+
176
+ def add_error(self, crate_name: str, error: str, error_type: str = "Processing") -> None:
177
+ """Add an error to the metrics."""
178
+ with self._lock:
179
+ self.metrics.errors.append({
180
+ "crate": crate_name,
181
+ "error": error,
182
+ "type": error_type,
183
+ "timestamp": datetime.now().isoformat()
184
+ })
185
+ self._save_status()
186
+
187
+ def add_warning(self, crate_name: str, warning: str) -> None:
188
+ """Add a warning to the metrics."""
189
+ with self._lock:
190
+ self.metrics.warnings.append({
191
+ "crate": crate_name,
192
+ "warning": warning,
193
+ "timestamp": datetime.now().isoformat()
194
+ })
195
+ self._save_status()
196
+
197
+ def update_operation(self, operation: str) -> None:
198
+ """Update the current operation description."""
199
+ with self._lock:
200
+ self.current_operation = operation
201
+ self.metrics.current_operation = operation
202
+ self._save_status()
203
+
204
+ def _update_performance_stats(self) -> None:
205
+ """Update performance statistics."""
206
+ if self.crate_times:
207
+ self.metrics.performance_stats.update({
208
+ "avg_crate_time": sum(self.crate_times) / len(self.crate_times),
209
+ "min_crate_time": min(self.crate_times),
210
+ "max_crate_time": max(self.crate_times),
211
+ "crates_per_minute": len(self.crate_times) / (sum(self.crate_times) / 60)
212
+ })
213
+
214
+ if self.batch_times:
215
+ self.metrics.performance_stats.update({
216
+ "avg_batch_time": sum(self.batch_times) / len(self.batch_times),
217
+ "min_batch_time": min(self.batch_times),
218
+ "max_batch_time": max(self.batch_times)
219
+ })
220
+
221
+ # System stats if available
222
+ if PSUTIL_AVAILABLE:
223
+ try:
224
+ cpu_percent = psutil.cpu_percent()
225
+ memory = psutil.virtual_memory()
226
+ disk = psutil.disk_usage(self.output_dir)
227
+
228
+ self.metrics.performance_stats.update({
229
+ "system_cpu_percent": cpu_percent,
230
+ "system_memory_percent": memory.percent,
231
+ "system_disk_percent": disk.percent,
232
+ "system_memory_available": memory.available,
233
+ "system_disk_free": disk.free
234
+ })
235
+ except Exception as e:
236
+ self.logger.warning(f"Failed to get system stats: {e}")
237
+
238
+ def _save_status(self) -> None:
239
+ """Save current status to file."""
240
+ try:
241
+ status_data = {
242
+ "metrics": {
243
+ "total_crates": self.metrics.total_crates,
244
+ "processed_crates": self.metrics.processed_crates,
245
+ "successful_crates": self.metrics.successful_crates,
246
+ "failed_crates": self.metrics.failed_crates,
247
+ "skipped_crates": self.metrics.skipped_crates,
248
+ "progress_percentage": self.metrics.progress_percentage,
249
+ "success_rate": self.metrics.success_rate,
250
+ "current_batch": self.metrics.current_batch,
251
+ "total_batches": self.metrics.total_batches,
252
+ "start_time": self.metrics.start_time.isoformat() if self.metrics.start_time else None,
253
+ "elapsed_time": str(self.metrics.elapsed_time),
254
+ "estimated_completion": self.metrics.estimated_completion.isoformat() if self.metrics.estimated_completion else None,
255
+ "current_operation": self.metrics.current_operation
256
+ },
257
+ "current_crate": self.current_crate,
258
+ "performance_stats": self.metrics.performance_stats,
259
+ "errors": self.metrics.errors[-10:], # Last 10 errors
260
+ "warnings": self.metrics.warnings[-10:], # Last 10 warnings
261
+ "last_updated": datetime.now().isoformat()
262
+ }
263
+
264
+ with open(self.status_file, 'w') as f:
265
+ json.dump(status_data, f, indent=2)
266
+
267
+ except Exception as e:
268
+ self.logger.error(f"Failed to save status: {e}")
269
+
270
+ def get_status_summary(self) -> Dict[str, Any]:
271
+ """Get a summary of current status."""
272
+ with self._lock:
273
+ return {
274
+ "progress": f"{self.metrics.progress_percentage:.1f}%",
275
+ "processed": f"{self.metrics.processed_crates}/{self.metrics.total_crates}",
276
+ "success_rate": f"{self.metrics.success_rate:.1f}%",
277
+ "elapsed_time": str(self.metrics.elapsed_time),
278
+ "estimated_completion": self.metrics.estimated_completion.isoformat() if self.metrics.estimated_completion else None,
279
+ "current_operation": self.current_operation,
280
+ "current_crate": self.current_crate,
281
+ "errors_count": len(self.metrics.errors),
282
+ "warnings_count": len(self.metrics.warnings)
283
+ }
284
+
285
+ def print_status(self) -> None:
286
+ """Print current status to console."""
287
+ summary = self.get_status_summary()
288
+
289
+ print("\n" + "="*80)
290
+ print("🚀 RUST CRATE PIPELINE - REAL-TIME STATUS")
291
+ print("="*80)
292
+ print(f"📊 Progress: {summary['progress']} ({summary['processed']} crates)")
293
+ print(f"✅ Success Rate: {summary['success_rate']}")
294
+ print(f"⏱️ Elapsed Time: {summary['elapsed_time']}")
295
+ if summary['estimated_completion']:
296
+ print(f"🎯 Estimated Completion: {summary['estimated_completion']}")
297
+ print(f"🔄 Current Operation: {summary['current_operation']}")
298
+ if summary['current_crate']:
299
+ print(f"📦 Current Crate: {summary['current_crate']}")
300
+ print(f"❌ Errors: {summary['errors_count']}")
301
+ print(f"⚠️ Warnings: {summary['warnings_count']}")
302
+
303
+ # Performance stats
304
+ if self.metrics.performance_stats:
305
+ stats = self.metrics.performance_stats
306
+ if 'avg_crate_time' in stats:
307
+ print(f"⚡ Avg Crate Time: {stats['avg_crate_time']:.2f}s")
308
+ if 'crates_per_minute' in stats:
309
+ print(f"🚀 Processing Rate: {stats['crates_per_minute']:.1f} crates/min")
310
+ if 'system_cpu_percent' in stats:
311
+ print(f"💻 System CPU: {stats['system_cpu_percent']:.1f}%")
312
+ if 'system_memory_percent' in stats:
313
+ print(f"🧠 System Memory: {stats['system_memory_percent']:.1f}%")
314
+
315
+ print("="*80)
316
+
317
+ def create_progress_bar(self, desc: str = "Processing crates") -> Optional[Any]:
318
+ """Create a progress bar if tqdm is available."""
319
+ if not TQDM_AVAILABLE:
320
+ return None
321
+
322
+ return tqdm(
323
+ total=self.metrics.total_crates,
324
+ desc=desc,
325
+ unit="crate",
326
+ bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}]"
327
+ )
328
+
329
+
330
+ def create_monitor(total_crates: int, output_dir: str = "output") -> ProgressMonitor:
331
+ """Create and configure a CLI-only progress monitor."""
332
+ monitor = ProgressMonitor(total_crates, output_dir)
333
+ print("✅ Real-time CLI progress monitoring enabled")
334
+ return monitor
@@ -1,74 +1,74 @@
1
- from typing import Dict, List, Tuple, Optional, Any
2
- """Version information for rust-crate-pipeline."""
3
-
4
- __version__ = "1.3.2"
5
- __version_info__ = tuple(int(x) for x in __version__.split("-")[0].split("."))
6
-
7
- # Version history
8
- # 1.2.5-dev.20250621 - Dev branch: experimental, not a formal
9
- # release. Originated from v1.2.5.
10
- # 1.2.5 - Last official release.
11
- # 1.5.1 - Configuration Standardization Release: Model Path Consistency
12
- # - Standardized all configuration to use GGUF model paths
13
- # - Updated CLI defaults for --crawl4ai-model to
14
- # ~/models/deepseek/deepseek-coder-6.7b-instruct.Q4_K_M.gguf
15
- # - Enhanced Rule Zero alignment with transparent configuration practices
16
- # - Updated all test files to use consistent GGUF model path references
17
- # - Comprehensive documentation updates for proper model configuration
18
- # - Removed inconsistent Ollama references in favor of llama-cpp-python
19
- # - Ensured CLI help text and JSON examples reflect correct model paths
20
- # 1.5.0 - Major Release: Enhanced Web Scraping with Crawl4AI Integration
21
- # - Integrated Crawl4AI for advanced web scraping capabilities
22
- # - Added JavaScript-rendered content extraction via Playwright
23
- # - Enhanced README parsing with LLM-powered content analysis
24
- # - New CLI options: --enable-crawl4ai, --disable-crawl4ai, --crawl4ai-model
25
- # - Enhanced configuration with local GGUF model paths and crawl4ai_timeout
26
- # - Comprehensive test coverage for all Crawl4AI features
27
- # - Rule Zero compliant with full transparency and audit trails
28
- # 1.4.0 - Major Release: Rule Zero Compliance Audit Complete
29
- # - Completed comprehensive Rule Zero alignment audit
30
- # - Eliminated all code redundancy and dead code
31
- # - Achieved 100% test coverage (22/22 tests passing)
32
- # - Refactored to pure asyncio architecture (thread-free)
33
- # - Suppressed Pydantic deprecation warnings
34
- # - Full production readiness with Docker support
35
- # - Enhanced documentation with PyPI cross-references
36
- # - Certified Rule Zero compliance across all four principles
37
- # 1.3.1 - Bug Fix Release: Crawl4AI Integration Cleanup
38
- # - Fixed CSS selector syntax errors in Crawl4AI integration
39
- # - Cleaned up duplicate and obsolete test files
40
- # - Resolved import conflicts between workspace and integration configs
41
- # - Improved error handling in enhanced scraping module
42
- # - Standardized on direct llama.cpp approach (removed Ollama dependencies)
43
- # - Enhanced Rule Zero compliance with transparent cleanup process
44
- # - Fixed type annotation compatibility issues
45
- # - Fixed Python 3.9 compatibility for type annotations
46
- # - Updated dict[str, Any] to "dict[str, Any]" format
47
- # - Fixed Union type expressions in conditional imports
48
- # - Resolved IDE linter errors in network.py, pipeline.py, and production_config.py
49
- # - Improved code quality and maintainability
50
- # 1.3.0 - Quality & Integration Release: Comprehensive code quality improvements
51
- # - Fixed all critical PEP 8 violations (F821, F811, E114)
52
- # - Enhanced error handling with graceful dependency fallbacks
53
- # - Improved module integration and import path resolution
54
- # - Added comprehensive test validation (21/21 tests passing)
55
- # - Enhanced async support and Unicode handling
56
- # - Production-ready CLI interfaces with robust error handling
57
- # - Full Rule Zero compliance validation
58
- # 1.2.0 - Major release: Production-ready, cleaned codebase
59
- # - Unified documentation into single comprehensive README
60
- # - Removed all non-essential development and test files
61
- # - Optimized for PyPI distribution and Docker deployment
62
- # - Enhanced GitHub token integration and setup
63
- # 1.1.2 - Production release: Cleaned up non-essential files
64
- # - Unified documentation into single README
65
- # - Optimized for PyPI distribution
66
- # 1.1.1 - Bug fix: Added missing python-dateutil dependency
67
- # - Fixed relativedelta import error
68
- # 1.1.0 - Updated author and contact information
69
- # - Enhanced package configuration
70
- # 0.1.0 - Initial release
71
- # - Core pipeline functionality
72
- # - AI-powered metadata enrichment
73
- # - Dependency analysis
74
- # - PyPI package setup
1
+ from typing import Dict, List, Tuple, Optional, Any
2
+ """Version information for rust-crate-pipeline."""
3
+
4
+ __version__ = "1.3.3"
5
+ __version_info__ = tuple(int(x) for x in __version__.split("-")[0].split("."))
6
+
7
+ # Version history
8
+ # 1.2.5-dev.20250621 - Dev branch: experimental, not a formal
9
+ # release. Originated from v1.2.5.
10
+ # 1.2.5 - Last official release.
11
+ # 1.5.1 - Configuration Standardization Release: Model Path Consistency
12
+ # - Standardized all configuration to use GGUF model paths
13
+ # - Updated CLI defaults for --crawl4ai-model to
14
+ # ~/models/deepseek/deepseek-coder-6.7b-instruct.Q4_K_M.gguf
15
+ # - Enhanced Rule Zero alignment with transparent configuration practices
16
+ # - Updated all test files to use consistent GGUF model path references
17
+ # - Comprehensive documentation updates for proper model configuration
18
+ # - Removed inconsistent Ollama references in favor of llama-cpp-python
19
+ # - Ensured CLI help text and JSON examples reflect correct model paths
20
+ # 1.5.0 - Major Release: Enhanced Web Scraping with Crawl4AI Integration
21
+ # - Integrated Crawl4AI for advanced web scraping capabilities
22
+ # - Added JavaScript-rendered content extraction via Playwright
23
+ # - Enhanced README parsing with LLM-powered content analysis
24
+ # - New CLI options: --enable-crawl4ai, --disable-crawl4ai, --crawl4ai-model
25
+ # - Enhanced configuration with local GGUF model paths and crawl4ai_timeout
26
+ # - Comprehensive test coverage for all Crawl4AI features
27
+ # - Rule Zero compliant with full transparency and audit trails
28
+ # 1.4.0 - Major Release: Rule Zero Compliance Audit Complete
29
+ # - Completed comprehensive Rule Zero alignment audit
30
+ # - Eliminated all code redundancy and dead code
31
+ # - Achieved 100% test coverage (22/22 tests passing)
32
+ # - Refactored to pure asyncio architecture (thread-free)
33
+ # - Suppressed Pydantic deprecation warnings
34
+ # - Full production readiness with Docker support
35
+ # - Enhanced documentation with PyPI cross-references
36
+ # - Certified Rule Zero compliance across all four principles
37
+ # 1.3.1 - Bug Fix Release: Crawl4AI Integration Cleanup
38
+ # - Fixed CSS selector syntax errors in Crawl4AI integration
39
+ # - Cleaned up duplicate and obsolete test files
40
+ # - Resolved import conflicts between workspace and integration configs
41
+ # - Improved error handling in enhanced scraping module
42
+ # - Standardized on direct llama.cpp approach (removed Ollama dependencies)
43
+ # - Enhanced Rule Zero compliance with transparent cleanup process
44
+ # - Fixed type annotation compatibility issues
45
+ # - Fixed Python 3.9 compatibility for type annotations
46
+ # - Updated dict[str, Any] to "dict[str, Any]" format
47
+ # - Fixed Union type expressions in conditional imports
48
+ # - Resolved IDE linter errors in network.py, pipeline.py, and production_config.py
49
+ # - Improved code quality and maintainability
50
+ # 1.3.0 - Quality & Integration Release: Comprehensive code quality improvements
51
+ # - Fixed all critical PEP 8 violations (F821, F811, E114)
52
+ # - Enhanced error handling with graceful dependency fallbacks
53
+ # - Improved module integration and import path resolution
54
+ # - Added comprehensive test validation (21/21 tests passing)
55
+ # - Enhanced async support and Unicode handling
56
+ # - Production-ready CLI interfaces with robust error handling
57
+ # - Full Rule Zero compliance validation
58
+ # 1.2.0 - Major release: Production-ready, cleaned codebase
59
+ # - Unified documentation into single comprehensive README
60
+ # - Removed all non-essential development and test files
61
+ # - Optimized for PyPI distribution and Docker deployment
62
+ # - Enhanced GitHub token integration and setup
63
+ # 1.1.2 - Production release: Cleaned up non-essential files
64
+ # - Unified documentation into single README
65
+ # - Optimized for PyPI distribution
66
+ # 1.1.1 - Bug fix: Added missing python-dateutil dependency
67
+ # - Fixed relativedelta import error
68
+ # 1.1.0 - Updated author and contact information
69
+ # - Enhanced package configuration
70
+ # 0.1.0 - Initial release
71
+ # - Core pipeline functionality
72
+ # - AI-powered metadata enrichment
73
+ # - Dependency analysis
74
+ # - PyPI package setup
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rust-crate-pipeline
3
- Version: 1.3.2
3
+ Version: 1.3.3
4
4
  Summary: A comprehensive system for gathering, enriching, and analyzing metadata for Rust crates using AI-powered insights
5
5
  Home-page: https://github.com/Superuser666-Sigil/SigilDERG-Data_Production
6
6
  Author: SuperUser666-Sigil
@@ -8,12 +8,13 @@ rust_crate_pipeline/crate_analysis.py,sha256=GsoXemJ9VFyAbb4Sm5gY5ToTqNtOA4pI38A
8
8
  rust_crate_pipeline/crate_list.txt,sha256=W3NxDtxvihyKp9SN85FYXX6p8Hh49IFih1M4-c-CynM,4334
9
9
  rust_crate_pipeline/github_token_checker.py,sha256=COXXS9uoLV9WYIcT02C-bV5uH3fa9D9HJImc07vMjLs,3766
10
10
  rust_crate_pipeline/main.py,sha256=iGYEAYvXkoFFvaA6DIVGiUL3wLhiCzatB6Fvf-Yrj2A,18858
11
- rust_crate_pipeline/network.py,sha256=SSSolG5QdK4m9E77Ko3m-8DM1xz1Ha9XP9yeLSCfuaU,13308
11
+ rust_crate_pipeline/network.py,sha256=khyjfOplaDvMxLWGB-JbPQnc27ZfozKGYBFw2b3BScM,12834
12
12
  rust_crate_pipeline/pipeline.py,sha256=YN6PEhg0Si_oo6-Wtm_PviytzWzpQupTPC2e4L1F7XE,16349
13
13
  rust_crate_pipeline/production_config.py,sha256=uWylP9AIZZx7-9aT4sFmAKEEW9miJDxaiek8VE6WP-0,2372
14
+ rust_crate_pipeline/progress_monitor.py,sha256=5K9KP-Xggi1JEINfRmq2W-wGUHtNIBTcocpDtB1t8iM,13743
14
15
  rust_crate_pipeline/unified_llm_processor.py,sha256=eo7KotNuqwc7_hgpFm18QLokFoufFslnvi8TnDsSYEg,25064
15
16
  rust_crate_pipeline/unified_pipeline.py,sha256=2yglmXVlQfSkVq0HVTPonDee6VxWaQWZw0X2l4lLBGw,23704
16
- rust_crate_pipeline/version.py,sha256=f9QzOtJjGR2-83eFezB55H6KgfM0Gi5XAPOooI5Y5_M,4489
17
+ rust_crate_pipeline/version.py,sha256=rvOvTXgn4ikcVgnUnkRQ9jMb3vGNh_UC85DLNgzLELw,4415
17
18
  rust_crate_pipeline/core/__init__.py,sha256=Sq4HWdANGqoYln7JdCog7m3BsGeR3tHdseeflvNetoQ,509
18
19
  rust_crate_pipeline/core/canon_registry.py,sha256=36tmt_wU6-kSyZnGfh53N64C7E3G-QR7GFbr9epj4zg,4700
19
20
  rust_crate_pipeline/core/irl_engine.py,sha256=QRZUdkN24W9XutLkj8JDplEz6FmnquUrwKsl0s2zRr4,10491
@@ -22,9 +23,9 @@ rust_crate_pipeline/scraping/__init__.py,sha256=ySkTRg7nIxgcbHJQ3L1XzcrOo281NZu0
22
23
  rust_crate_pipeline/scraping/unified_scraper.py,sha256=ZE2gkc0vQ3BOLdSX_IV-kMe8QAm2Av4M7VqpkxEKyT4,9965
23
24
  rust_crate_pipeline/utils/file_utils.py,sha256=tMaCPy7ghs9x4Hxu_sviX8MXU2sBjNvohUrvt4MejoM,2853
24
25
  rust_crate_pipeline/utils/logging_utils.py,sha256=e5jG0Yd6k3exgAdbVca46kWADJ_Qz8UJ3yEJzwTqPyI,2452
25
- rust_crate_pipeline-1.3.2.dist-info/licenses/LICENSE,sha256=tpd4XNpbssrSx9-iErATOLrOh0ivNPfO2I5MAPUpats,1088
26
- rust_crate_pipeline-1.3.2.dist-info/METADATA,sha256=sMYLvaOvjRgZNP1iHuNb0NEWxgqY_uqzdx_PGnWtMT0,11254
27
- rust_crate_pipeline-1.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
- rust_crate_pipeline-1.3.2.dist-info/entry_points.txt,sha256=9Rr_IRuFRIridXxUSdEJbB3ba0NnpEfKmknZXFdYRC0,70
29
- rust_crate_pipeline-1.3.2.dist-info/top_level.txt,sha256=GUdB7RyxHLhijQxui_KTy3B8p_L2APui9C6RYa0FuaE,20
30
- rust_crate_pipeline-1.3.2.dist-info/RECORD,,
26
+ rust_crate_pipeline-1.3.3.dist-info/licenses/LICENSE,sha256=tpd4XNpbssrSx9-iErATOLrOh0ivNPfO2I5MAPUpats,1088
27
+ rust_crate_pipeline-1.3.3.dist-info/METADATA,sha256=71onHncBIIY4LaCuTT6soQPoMdROFUkvFZwHYd4Tbe4,11254
28
+ rust_crate_pipeline-1.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
+ rust_crate_pipeline-1.3.3.dist-info/entry_points.txt,sha256=9Rr_IRuFRIridXxUSdEJbB3ba0NnpEfKmknZXFdYRC0,70
30
+ rust_crate_pipeline-1.3.3.dist-info/top_level.txt,sha256=GUdB7RyxHLhijQxui_KTy3B8p_L2APui9C6RYa0FuaE,20
31
+ rust_crate_pipeline-1.3.3.dist-info/RECORD,,