esuls 0.1.2__tar.gz → 0.1.3__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.
- {esuls-0.1.2/src/esuls.egg-info → esuls-0.1.3}/PKG-INFO +3 -1
- {esuls-0.1.2 → esuls-0.1.3}/pyproject.toml +3 -1
- esuls-0.1.3/src/esuls/download_icon.py +123 -0
- {esuls-0.1.2 → esuls-0.1.3/src/esuls.egg-info}/PKG-INFO +3 -1
- {esuls-0.1.2 → esuls-0.1.3}/src/esuls.egg-info/SOURCES.txt +1 -0
- {esuls-0.1.2 → esuls-0.1.3}/src/esuls.egg-info/requires.txt +2 -0
- {esuls-0.1.2 → esuls-0.1.3}/LICENSE +0 -0
- {esuls-0.1.2 → esuls-0.1.3}/README.md +0 -0
- {esuls-0.1.2 → esuls-0.1.3}/setup.cfg +0 -0
- {esuls-0.1.2 → esuls-0.1.3}/src/esuls/__init__.py +0 -0
- {esuls-0.1.2 → esuls-0.1.3}/src/esuls/db_cli.py +0 -0
- {esuls-0.1.2 → esuls-0.1.3}/src/esuls/request_cli.py +0 -0
- {esuls-0.1.2 → esuls-0.1.3}/src/esuls/utils.py +0 -0
- {esuls-0.1.2 → esuls-0.1.3}/src/esuls.egg-info/dependency_links.txt +0 -0
- {esuls-0.1.2 → esuls-0.1.3}/src/esuls.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: esuls
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Utility library for async database operations, HTTP requests, and parallel execution
|
|
5
5
|
Author-email: IperGiove <ipergiove@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -17,6 +17,8 @@ Requires-Dist: curl-cffi>=0.13.0
|
|
|
17
17
|
Requires-Dist: fake-useragent>=2.2.0
|
|
18
18
|
Requires-Dist: httpx>=0.28.1
|
|
19
19
|
Requires-Dist: loguru>=0.7.3
|
|
20
|
+
Requires-Dist: pillow>=12.0.0
|
|
21
|
+
Requires-Dist: python-magic>=0.4.27
|
|
20
22
|
Dynamic: license-file
|
|
21
23
|
|
|
22
24
|
# esuls
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "esuls"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
8
8
|
description = "Utility library for async database operations, HTTP requests, and parallel execution"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.14"
|
|
@@ -25,6 +25,8 @@ dependencies = [
|
|
|
25
25
|
"fake-useragent>=2.2.0",
|
|
26
26
|
"httpx>=0.28.1",
|
|
27
27
|
"loguru>=0.7.3",
|
|
28
|
+
"pillow>=12.0.0",
|
|
29
|
+
"python-magic>=0.4.27",
|
|
28
30
|
]
|
|
29
31
|
|
|
30
32
|
[project.urls]
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from urllib.parse import urlparse, unquote
|
|
2
|
+
from typing import TypedDict, Optional, Dict, Any
|
|
3
|
+
import asyncio
|
|
4
|
+
import io
|
|
5
|
+
import magic
|
|
6
|
+
from PIL import Image
|
|
7
|
+
from .request_cli import make_request
|
|
8
|
+
|
|
9
|
+
# Type definition
|
|
10
|
+
class IconData(TypedDict):
|
|
11
|
+
data: bytes
|
|
12
|
+
size: int
|
|
13
|
+
mimetype: str
|
|
14
|
+
name: str
|
|
15
|
+
|
|
16
|
+
# MIME types mapping
|
|
17
|
+
MIME_TO_EXT: Dict[str, str] = {
|
|
18
|
+
'image/jpeg': '.jpg',
|
|
19
|
+
'image/png': '.png',
|
|
20
|
+
'image/gif': '.gif',
|
|
21
|
+
'image/webp': '.webp',
|
|
22
|
+
'image/svg+xml': '.svg',
|
|
23
|
+
'image/x-icon': '.ico',
|
|
24
|
+
'image/vnd.microsoft.icon': '.ico',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async def download_icon(url: str, filename: Optional[str] = None) -> Optional[IconData]:
|
|
28
|
+
"""
|
|
29
|
+
Download and validate an image from a URL.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
url: Target image URL
|
|
33
|
+
filename: Optional custom filename
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
IconData object or None if download/validation fails
|
|
37
|
+
"""
|
|
38
|
+
if not url:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
# Process filename
|
|
42
|
+
if not filename:
|
|
43
|
+
filename = _extract_filename(url)
|
|
44
|
+
|
|
45
|
+
if any(term in filename.lower() for term in ['unknown', 'missing']):
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
# Fetch image data
|
|
49
|
+
response = await make_request(url, max_attempt=3, add_user_agent=True)
|
|
50
|
+
if not response:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
file_buffer = response.content
|
|
54
|
+
mime_type = _detect_mime_type(file_buffer)
|
|
55
|
+
|
|
56
|
+
if not mime_type:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
if not verify_image(file_buffer, mime_type):
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
# Generate filename with correct extension
|
|
63
|
+
base_name = filename.rsplit('.', 1)[0]
|
|
64
|
+
extension = MIME_TO_EXT.get(mime_type, '')
|
|
65
|
+
final_filename = f"{base_name}{extension}"
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
"data": file_buffer,
|
|
69
|
+
"size": len(file_buffer),
|
|
70
|
+
"mimetype": mime_type,
|
|
71
|
+
"name": final_filename,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
def _extract_filename(url: str) -> str:
|
|
75
|
+
"""Extract filename from URL path."""
|
|
76
|
+
parsed_url = urlparse(url)
|
|
77
|
+
path_components = parsed_url.path.split('/')
|
|
78
|
+
filename = next((comp for comp in reversed(path_components) if comp))
|
|
79
|
+
return unquote(filename)
|
|
80
|
+
|
|
81
|
+
def _detect_mime_type(data: bytes) -> Optional[str]:
|
|
82
|
+
"""Detect MIME type from file content."""
|
|
83
|
+
mime = magic.Magic(mime=True)
|
|
84
|
+
return mime.from_buffer(data)
|
|
85
|
+
|
|
86
|
+
def verify_image(data: bytes, mime_type: str) -> bool:
|
|
87
|
+
"""Verify image data integrity."""
|
|
88
|
+
if not mime_type.startswith('image/'):
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
# SVG validation (basic XML check)
|
|
93
|
+
if mime_type == 'image/svg+xml':
|
|
94
|
+
return b'<svg' in data and b'</svg>' in data
|
|
95
|
+
|
|
96
|
+
# Standard image validation through PIL
|
|
97
|
+
with Image.open(io.BytesIO(data)) as img:
|
|
98
|
+
img.verify()
|
|
99
|
+
return True
|
|
100
|
+
except Exception:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
url = "https://pbs.twimg.com/profile_images/1899026397915488256/mc-jPC-w.jpg"
|
|
107
|
+
icon_data = asyncio.run(download_icon(url))
|
|
108
|
+
if icon_data:
|
|
109
|
+
print(f"Downloaded: {icon_data['name']}")
|
|
110
|
+
print(f"MIME type: {icon_data['mimetype']}")
|
|
111
|
+
print(f"Size: {icon_data['size']} bytes")
|
|
112
|
+
|
|
113
|
+
# Display image if it's a standard format (not SVG)
|
|
114
|
+
if icon_data['mimetype'] != 'image/svg+xml':
|
|
115
|
+
try:
|
|
116
|
+
img = Image.open(io.BytesIO(icon_data['data']))
|
|
117
|
+
print(f"Image dimensions: {img.size}")
|
|
118
|
+
img.show()
|
|
119
|
+
print("Image displayed!")
|
|
120
|
+
except Exception as e:
|
|
121
|
+
print(f"Could not display image: {e}")
|
|
122
|
+
else:
|
|
123
|
+
print("SVG image downloaded (cannot display with PIL)")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: esuls
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Utility library for async database operations, HTTP requests, and parallel execution
|
|
5
5
|
Author-email: IperGiove <ipergiove@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -17,6 +17,8 @@ Requires-Dist: curl-cffi>=0.13.0
|
|
|
17
17
|
Requires-Dist: fake-useragent>=2.2.0
|
|
18
18
|
Requires-Dist: httpx>=0.28.1
|
|
19
19
|
Requires-Dist: loguru>=0.7.3
|
|
20
|
+
Requires-Dist: pillow>=12.0.0
|
|
21
|
+
Requires-Dist: python-magic>=0.4.27
|
|
20
22
|
Dynamic: license-file
|
|
21
23
|
|
|
22
24
|
# esuls
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|