pypi-info 0.51__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.
- pypi_info-0.51/MANIFEST.in +8 -0
- pypi_info-0.51/PKG-INFO +61 -0
- pypi_info-0.51/README.md +40 -0
- pypi_info-0.51/pypi_info/__init__.py +1 -0
- pypi_info-0.51/pypi_info/__main__.py +9 -0
- pypi_info-0.51/pypi_info/__version__.py +1 -0
- pypi_info-0.51/pypi_info/pipinfo.py +606 -0
- pypi_info-0.51/pypi_info.egg-info/PKG-INFO +61 -0
- pypi_info-0.51/pypi_info.egg-info/SOURCES.txt +13 -0
- pypi_info-0.51/pypi_info.egg-info/dependency_links.txt +1 -0
- pypi_info-0.51/pypi_info.egg-info/entry_points.txt +4 -0
- pypi_info-0.51/pypi_info.egg-info/requires.txt +2 -0
- pypi_info-0.51/pypi_info.egg-info/top_level.txt +1 -0
- pypi_info-0.51/setup.cfg +4 -0
- pypi_info-0.51/setup.py +63 -0
pypi_info-0.51/PKG-INFO
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pypi_info
|
|
3
|
+
Version: 0.51
|
|
4
|
+
Summary: Search and get description of package on pypi.org
|
|
5
|
+
Home-page: https://github.com/cumulus13/pipinfo
|
|
6
|
+
Author: Hadi Cahyadi
|
|
7
|
+
Author-email: cumulus13@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
17
|
+
Requires-Python: >=3.7
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: rich
|
|
20
|
+
Requires-Dist: rich_argparse
|
|
21
|
+
|
|
22
|
+
# pypi-info
|
|
23
|
+
|
|
24
|
+
🐍 PyPI Package Information Tool - Get detailed info about Python packages
|
|
25
|
+
|
|
26
|
+
## usage
|
|
27
|
+
```bash:
|
|
28
|
+
Usage: pypi-info [-h] [-l] [-d] [-p PATH] [--version-download VERSION_DOWNLOAD] [--author] [--home] [--tags] [--urls] [-v]
|
|
29
|
+
[package]
|
|
30
|
+
|
|
31
|
+
🐍 PyPI Package Information Tool - Get detailed info about Python packages
|
|
32
|
+
|
|
33
|
+
Positional Arguments:
|
|
34
|
+
package 📦 Package name to search for
|
|
35
|
+
|
|
36
|
+
Options:
|
|
37
|
+
-h, --help show this help message and exit
|
|
38
|
+
-l, --last 🔍 Show only the latest version information
|
|
39
|
+
-d, --download 📥 Download the package with progress bar
|
|
40
|
+
-p, --path PATH 📁 Directory path to save downloaded files (default: current directory)
|
|
41
|
+
--version-download VERSION_DOWNLOAD
|
|
42
|
+
🔢 Specific version to download (default: latest)
|
|
43
|
+
--author 👤 Show author information
|
|
44
|
+
--home 🏠 Show home page URL
|
|
45
|
+
--tags 🏷️ Show package classifiers/tags
|
|
46
|
+
--urls 🔗 Show all project URLs
|
|
47
|
+
-v, --version Show version
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## author
|
|
51
|
+
[Hadi Cahyadi](mailto:cumulus13@gmail.com)
|
|
52
|
+
|
|
53
|
+
## requirements
|
|
54
|
+
- rich
|
|
55
|
+
- rich_argparse
|
|
56
|
+
|
|
57
|
+
[](https://www.buymeacoffee.com/cumulus13)
|
|
58
|
+
|
|
59
|
+
[](https://ko-fi.com/cumulus13)
|
|
60
|
+
|
|
61
|
+
[Support me on Patreon](https://www.patreon.com/cumulus13)
|
pypi_info-0.51/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# pypi-info
|
|
2
|
+
|
|
3
|
+
🐍 PyPI Package Information Tool - Get detailed info about Python packages
|
|
4
|
+
|
|
5
|
+
## usage
|
|
6
|
+
```bash:
|
|
7
|
+
Usage: pypi-info [-h] [-l] [-d] [-p PATH] [--version-download VERSION_DOWNLOAD] [--author] [--home] [--tags] [--urls] [-v]
|
|
8
|
+
[package]
|
|
9
|
+
|
|
10
|
+
🐍 PyPI Package Information Tool - Get detailed info about Python packages
|
|
11
|
+
|
|
12
|
+
Positional Arguments:
|
|
13
|
+
package 📦 Package name to search for
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
-h, --help show this help message and exit
|
|
17
|
+
-l, --last 🔍 Show only the latest version information
|
|
18
|
+
-d, --download 📥 Download the package with progress bar
|
|
19
|
+
-p, --path PATH 📁 Directory path to save downloaded files (default: current directory)
|
|
20
|
+
--version-download VERSION_DOWNLOAD
|
|
21
|
+
🔢 Specific version to download (default: latest)
|
|
22
|
+
--author 👤 Show author information
|
|
23
|
+
--home 🏠 Show home page URL
|
|
24
|
+
--tags 🏷️ Show package classifiers/tags
|
|
25
|
+
--urls 🔗 Show all project URLs
|
|
26
|
+
-v, --version Show version
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## author
|
|
30
|
+
[Hadi Cahyadi](mailto:cumulus13@gmail.com)
|
|
31
|
+
|
|
32
|
+
## requirements
|
|
33
|
+
- rich
|
|
34
|
+
- rich_argparse
|
|
35
|
+
|
|
36
|
+
[](https://www.buymeacoffee.com/cumulus13)
|
|
37
|
+
|
|
38
|
+
[](https://ko-fi.com/cumulus13)
|
|
39
|
+
|
|
40
|
+
[Support me on Patreon](https://www.patreon.com/cumulus13)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .pipinfo import *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "0.51"
|
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# author: Hadi Cahyadi (cumulus13@gmail.com)
|
|
3
|
+
# create in 10 minutes
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
PyPI Package Information Tool
|
|
7
|
+
A beautiful command-line tool to fetch and display PyPI package information.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import urllib.request
|
|
15
|
+
# import urllib.parse
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Dict, Any, List, Optional
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
# import tempfile
|
|
20
|
+
# import shutil
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from rich.console import Console
|
|
24
|
+
from rich.table import Table
|
|
25
|
+
from rich.panel import Panel
|
|
26
|
+
from rich.columns import Columns
|
|
27
|
+
from rich.text import Text
|
|
28
|
+
from rich.markdown import Markdown
|
|
29
|
+
from rich.progress import Progress, BarColumn, TextColumn, TimeRemainingColumn
|
|
30
|
+
from rich.tree import Tree
|
|
31
|
+
from rich.align import Align
|
|
32
|
+
from rich_argparse import RichHelpFormatter, _lazy_rich as rr
|
|
33
|
+
except ImportError:
|
|
34
|
+
print("❌ Error: rich and rich-argparse packages are required!")
|
|
35
|
+
print("Install with: pip install rich rich-argparse")
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
|
|
38
|
+
console = Console()
|
|
39
|
+
|
|
40
|
+
class CustomRichHelpFormatter(RichHelpFormatter):
|
|
41
|
+
"""A custom RichHelpFormatter with modified styles."""
|
|
42
|
+
|
|
43
|
+
styles: dict[str, rr.StyleType] = {
|
|
44
|
+
"argparse.args": "bold #FFFF00", # Yellow
|
|
45
|
+
"argparse.groups": "#AA55FF", # Purple
|
|
46
|
+
"argparse.help": "bold #00FFFF", # Cyan
|
|
47
|
+
"argparse.metavar": "bold #FF00FF", # Magenta
|
|
48
|
+
"argparse.syntax": "underline", # Underlined
|
|
49
|
+
"argparse.text": "white", # White
|
|
50
|
+
"argparse.prog": "bold #00AAFF italic", # Blue italic
|
|
51
|
+
"argparse.default": "bold", # Bold
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class PyPIClient:
|
|
55
|
+
"""Client for interacting with PyPI API."""
|
|
56
|
+
|
|
57
|
+
BASE_URL = "https://pypi.org/pypi"
|
|
58
|
+
|
|
59
|
+
def __init__(self):
|
|
60
|
+
self.session_headers = {
|
|
61
|
+
'User-Agent': 'PyPI-Info-Tool/1.0 (https://github.com/user/pypi-info-tool)'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def get_package_info(self, package_name: str) -> Optional[Dict[str, Any]]:
|
|
65
|
+
"""Fetch package information from PyPI API."""
|
|
66
|
+
url = f"{self.BASE_URL}/{package_name}/json"
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
with console.status(f"[bold blue]🔍 Searching for package '{package_name}'...", spinner="dots"):
|
|
70
|
+
req = urllib.request.Request(url, headers=self.session_headers)
|
|
71
|
+
with urllib.request.urlopen(req, timeout=10) as response:
|
|
72
|
+
if response.status == 200:
|
|
73
|
+
return json.loads(response.read().decode('utf-8'))
|
|
74
|
+
else:
|
|
75
|
+
return None
|
|
76
|
+
except urllib.error.HTTPError as e:
|
|
77
|
+
if e.code == 404:
|
|
78
|
+
console.print(f"[red]❌ Package '{package_name}' not found on PyPI[/red]")
|
|
79
|
+
else:
|
|
80
|
+
console.print(f"[red]❌ HTTP Error {e.code}: {e.reason}[/red]")
|
|
81
|
+
return None
|
|
82
|
+
except Exception as e:
|
|
83
|
+
console.print(f"[red]❌ Error fetching package info: {str(e)}[/red]")
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
def download_package(self, package_name: str, version: str = None,
|
|
87
|
+
download_path: str = ".", progress_callback=None) -> bool:
|
|
88
|
+
"""Download package from PyPI."""
|
|
89
|
+
package_info = self.get_package_info(package_name)
|
|
90
|
+
if not package_info:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
# Get the version to download
|
|
94
|
+
if version is None or version == "latest":
|
|
95
|
+
version = package_info['info']['version']
|
|
96
|
+
|
|
97
|
+
# Find the download URL
|
|
98
|
+
releases = package_info.get('releases', {})
|
|
99
|
+
if version not in releases:
|
|
100
|
+
console.print(f"[red]❌ Version {version} not found for {package_name}[/red]")
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
files = releases[version]
|
|
104
|
+
if not files:
|
|
105
|
+
console.print(f"[red]❌ No files available for {package_name} {version}[/red]")
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
# Prefer wheel files, then source distributions
|
|
109
|
+
download_file = None
|
|
110
|
+
for file_info in files:
|
|
111
|
+
if file_info['packagetype'] == 'bdist_wheel':
|
|
112
|
+
download_file = file_info
|
|
113
|
+
break
|
|
114
|
+
|
|
115
|
+
if not download_file:
|
|
116
|
+
for file_info in files:
|
|
117
|
+
if file_info['packagetype'] == 'sdist':
|
|
118
|
+
download_file = file_info
|
|
119
|
+
break
|
|
120
|
+
|
|
121
|
+
if not download_file:
|
|
122
|
+
download_file = files[0] # Fallback to first available
|
|
123
|
+
|
|
124
|
+
# Download the file
|
|
125
|
+
download_url = download_file['url']
|
|
126
|
+
filename = download_file['filename']
|
|
127
|
+
file_size = download_file.get('size', 0)
|
|
128
|
+
|
|
129
|
+
download_path = Path(download_path)
|
|
130
|
+
download_path.mkdir(parents=True, exist_ok=True)
|
|
131
|
+
filepath = download_path / filename
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
with Progress(
|
|
135
|
+
TextColumn("[bold blue]📥 Downloading"),
|
|
136
|
+
TextColumn("[bold yellow]{task.fields[filename]}"),
|
|
137
|
+
BarColumn(bar_width=40),
|
|
138
|
+
TextColumn("[progress.percentage]{task.percentage:>3.1f}%"),
|
|
139
|
+
TextColumn("[blue]({task.completed:,}/{task.total:,} bytes)"),
|
|
140
|
+
TimeRemainingColumn(),
|
|
141
|
+
console=console,
|
|
142
|
+
transient=False
|
|
143
|
+
) as progress:
|
|
144
|
+
|
|
145
|
+
task = progress.add_task(
|
|
146
|
+
"download",
|
|
147
|
+
filename=filename,
|
|
148
|
+
total=file_size if file_size > 0 else None
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
req = urllib.request.Request(download_url, headers=self.session_headers)
|
|
152
|
+
with urllib.request.urlopen(req) as response:
|
|
153
|
+
with open(filepath, 'wb') as f:
|
|
154
|
+
downloaded = 0
|
|
155
|
+
while True:
|
|
156
|
+
chunk = response.read(8192)
|
|
157
|
+
if not chunk:
|
|
158
|
+
break
|
|
159
|
+
f.write(chunk)
|
|
160
|
+
downloaded += len(chunk)
|
|
161
|
+
if file_size > 0:
|
|
162
|
+
progress.update(task, completed=downloaded)
|
|
163
|
+
elif progress_callback:
|
|
164
|
+
progress_callback(downloaded)
|
|
165
|
+
|
|
166
|
+
console.print(f"[green]✅ Downloaded: {filepath}[/green]")
|
|
167
|
+
return True
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
console.print(f"[red]❌ Download failed: {str(e)}[/red]")
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
class PackageInfoDisplay:
|
|
174
|
+
"""Display package information in a beautiful format."""
|
|
175
|
+
|
|
176
|
+
def __init__(self):
|
|
177
|
+
self.console = console
|
|
178
|
+
|
|
179
|
+
def format_size(self, size_bytes: int) -> str:
|
|
180
|
+
"""Format file size in human readable format."""
|
|
181
|
+
if size_bytes == 0:
|
|
182
|
+
return "0 B"
|
|
183
|
+
|
|
184
|
+
size_names = ["B", "KB", "MB", "GB"]
|
|
185
|
+
i = 0
|
|
186
|
+
while size_bytes >= 1024 and i < len(size_names) - 1:
|
|
187
|
+
size_bytes /= 1024.0
|
|
188
|
+
i += 1
|
|
189
|
+
|
|
190
|
+
return f"{size_bytes:.1f} {size_names[i]}"
|
|
191
|
+
|
|
192
|
+
def format_date(self, date_str: str) -> str:
|
|
193
|
+
"""Format ISO date string to readable format."""
|
|
194
|
+
try:
|
|
195
|
+
dt = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
|
|
196
|
+
return dt.strftime("%B %d, %Y")
|
|
197
|
+
except:
|
|
198
|
+
return date_str
|
|
199
|
+
|
|
200
|
+
def create_header_panel(self, info: Dict[str, Any]) -> Panel:
|
|
201
|
+
"""Create the main header panel."""
|
|
202
|
+
name = info.get('name', 'Unknown')
|
|
203
|
+
version = info.get('version', 'Unknown')
|
|
204
|
+
summary = info.get('summary', 'No description available')
|
|
205
|
+
|
|
206
|
+
# Create title with emoji
|
|
207
|
+
title_text = Text()
|
|
208
|
+
title_text.append("📦 ", style="bold blue")
|
|
209
|
+
title_text.append(name, style="bold white")
|
|
210
|
+
title_text.append(f" {version}", style="bold green")
|
|
211
|
+
|
|
212
|
+
# Summary
|
|
213
|
+
summary_text = Text(summary, style="italic cyan")
|
|
214
|
+
|
|
215
|
+
content = Align.center(
|
|
216
|
+
Text.assemble(
|
|
217
|
+
title_text, "\n\n",
|
|
218
|
+
summary_text
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return Panel(
|
|
223
|
+
content,
|
|
224
|
+
title="[bold blue]📋 Package Information[/bold blue]",
|
|
225
|
+
border_style="blue",
|
|
226
|
+
padding=(1, 2)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def create_basic_info_table(self, info: Dict[str, Any]) -> Table:
|
|
230
|
+
"""Create basic information table."""
|
|
231
|
+
table = Table(show_header=False, box=None, padding=(0, 1))
|
|
232
|
+
table.add_column("Property", style="bold yellow", width=20)
|
|
233
|
+
table.add_column("Value", style="white")
|
|
234
|
+
|
|
235
|
+
# Basic info
|
|
236
|
+
basic_fields = [
|
|
237
|
+
("🏷️ Name", info.get('name', 'N/A')),
|
|
238
|
+
("🔢 Version", info.get('version', 'N/A')),
|
|
239
|
+
("👤 Author", info.get('author', 'N/A')),
|
|
240
|
+
("📧 Author Email", info.get('author_email', 'N/A')),
|
|
241
|
+
("🏠 Home Page", info.get('home_page', 'N/A')),
|
|
242
|
+
("🛠️ Maintainer", info.get('maintainer', 'N/A')),
|
|
243
|
+
("📨 Maintainer Email", info.get('maintainer_email', 'N/A')),
|
|
244
|
+
("📄 License", info.get('license', 'N/A')),
|
|
245
|
+
("🐍 Python Requires", info.get('requires_python', 'N/A')),
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
for prop, value in basic_fields:
|
|
249
|
+
if value and value != 'N/A':
|
|
250
|
+
# Truncate long values
|
|
251
|
+
if len(str(value)) > 50:
|
|
252
|
+
value = str(value)[:47] + "..."
|
|
253
|
+
table.add_row(prop, str(value))
|
|
254
|
+
|
|
255
|
+
return table
|
|
256
|
+
|
|
257
|
+
def create_urls_table(self, info: Dict[str, Any]) -> Optional[Table]:
|
|
258
|
+
"""Create project URLs table."""
|
|
259
|
+
project_urls = info.get('project_urls', {})
|
|
260
|
+
if not project_urls:
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
table = Table(title="🔗 Project URLs", show_header=False, box=None, padding=(0, 1))
|
|
264
|
+
table.add_column("Type", style="bold cyan", width=15)
|
|
265
|
+
table.add_column("URL", style="blue underline")
|
|
266
|
+
|
|
267
|
+
for url_type, url in project_urls.items():
|
|
268
|
+
if url:
|
|
269
|
+
# Truncate very long URLs
|
|
270
|
+
display_url = url if len(url) <= 60 else url[:57] + "..."
|
|
271
|
+
table.add_row(f"🌐 {url_type}", display_url)
|
|
272
|
+
|
|
273
|
+
return table
|
|
274
|
+
|
|
275
|
+
def create_classifiers_tree(self, info: Dict[str, Any]) -> Optional[Tree]:
|
|
276
|
+
"""Create classifiers tree."""
|
|
277
|
+
classifiers = info.get('classifiers', [])
|
|
278
|
+
if not classifiers:
|
|
279
|
+
return None
|
|
280
|
+
|
|
281
|
+
tree = Tree("🏷️ [bold yellow]Classifiers")
|
|
282
|
+
|
|
283
|
+
# Group classifiers by category
|
|
284
|
+
categories = {}
|
|
285
|
+
for classifier in classifiers:
|
|
286
|
+
parts = classifier.split(' :: ')
|
|
287
|
+
if len(parts) >= 2:
|
|
288
|
+
category = parts[0]
|
|
289
|
+
subcategory = ' :: '.join(parts[1:])
|
|
290
|
+
if category not in categories:
|
|
291
|
+
categories[category] = []
|
|
292
|
+
categories[category].append(subcategory)
|
|
293
|
+
|
|
294
|
+
for category, items in categories.items():
|
|
295
|
+
category_node = tree.add(f"[bold cyan]{category}")
|
|
296
|
+
for item in items[:5]: # Limit to 5 items per category
|
|
297
|
+
category_node.add(f"[white]{item}")
|
|
298
|
+
if len(items) > 5:
|
|
299
|
+
category_node.add(f"[dim]... and {len(items) - 5} more")
|
|
300
|
+
|
|
301
|
+
return tree
|
|
302
|
+
|
|
303
|
+
def create_releases_table(self, releases: Dict[str, List], latest_version: str) -> Table:
|
|
304
|
+
"""Create releases table showing recent versions."""
|
|
305
|
+
table = Table(title="📦 Recent Releases", box=None)
|
|
306
|
+
table.add_column("Version", style="bold green", width=15)
|
|
307
|
+
table.add_column("Release Date", style="cyan", width=20)
|
|
308
|
+
table.add_column("Files", style="yellow", width=10)
|
|
309
|
+
table.add_column("Size", style="magenta", width=12)
|
|
310
|
+
|
|
311
|
+
# Sort versions by upload time (newest first)
|
|
312
|
+
version_data = []
|
|
313
|
+
for version, files in releases.items():
|
|
314
|
+
if files:
|
|
315
|
+
upload_time = files[0].get('upload_time_iso_8601', '')
|
|
316
|
+
total_size = sum(f.get('size', 0) for f in files)
|
|
317
|
+
version_data.append((version, upload_time, len(files), total_size))
|
|
318
|
+
|
|
319
|
+
# Sort by upload time (newest first) and take top 10
|
|
320
|
+
version_data.sort(key=lambda x: x[1], reverse=True)
|
|
321
|
+
|
|
322
|
+
for i, (version, upload_time, file_count, total_size) in enumerate(version_data[:10]):
|
|
323
|
+
version_display = version
|
|
324
|
+
if version == latest_version:
|
|
325
|
+
version_display = f"{version} [bold red](latest)[/bold red]"
|
|
326
|
+
|
|
327
|
+
date_display = self.format_date(upload_time) if upload_time else "Unknown"
|
|
328
|
+
size_display = self.format_size(total_size) if total_size > 0 else "Unknown"
|
|
329
|
+
|
|
330
|
+
table.add_row(
|
|
331
|
+
version_display,
|
|
332
|
+
date_display,
|
|
333
|
+
str(file_count),
|
|
334
|
+
size_display
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
return table
|
|
338
|
+
|
|
339
|
+
def display_package_info(self, package_data: Dict[str, Any], show_last_only: bool = False):
|
|
340
|
+
"""Display complete package information."""
|
|
341
|
+
info = package_data.get('info', {})
|
|
342
|
+
releases = package_data.get('releases', {})
|
|
343
|
+
|
|
344
|
+
# Header
|
|
345
|
+
self.console.print()
|
|
346
|
+
self.console.print(self.create_header_panel(info))
|
|
347
|
+
self.console.print()
|
|
348
|
+
|
|
349
|
+
if show_last_only:
|
|
350
|
+
# Show only latest version info
|
|
351
|
+
latest_version = info.get('version', 'Unknown')
|
|
352
|
+
latest_files = releases.get(latest_version, [])
|
|
353
|
+
|
|
354
|
+
if latest_files:
|
|
355
|
+
table = Table(title=f"📦 Latest Version ({latest_version})", box=None)
|
|
356
|
+
table.add_column("File", style="bold green")
|
|
357
|
+
table.add_column("Type", style="cyan")
|
|
358
|
+
table.add_column("Size", style="yellow")
|
|
359
|
+
table.add_column("Upload Date", style="magenta")
|
|
360
|
+
|
|
361
|
+
for file_info in latest_files:
|
|
362
|
+
table.add_row(
|
|
363
|
+
file_info.get('filename', 'Unknown'),
|
|
364
|
+
file_info.get('packagetype', 'Unknown'),
|
|
365
|
+
self.format_size(file_info.get('size', 0)),
|
|
366
|
+
self.format_date(file_info.get('upload_time_iso_8601', ''))
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
self.console.print(table)
|
|
370
|
+
else:
|
|
371
|
+
self.console.print("[yellow]⚠️ No files found for latest version[/yellow]")
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
# Create layout columns
|
|
375
|
+
left_column = []
|
|
376
|
+
right_column = []
|
|
377
|
+
|
|
378
|
+
# Basic info (left column)
|
|
379
|
+
basic_table = self.create_basic_info_table(info)
|
|
380
|
+
left_column.append(Panel(basic_table, title="[bold green]ℹ️ Basic Information", border_style="green"))
|
|
381
|
+
|
|
382
|
+
# URLs (right column)
|
|
383
|
+
urls_table = self.create_urls_table(info)
|
|
384
|
+
if urls_table:
|
|
385
|
+
right_column.append(Panel(urls_table, title="[bold blue]🔗 Links", border_style="blue"))
|
|
386
|
+
|
|
387
|
+
# Display two columns
|
|
388
|
+
if left_column and right_column:
|
|
389
|
+
self.console.print(Columns([left_column[0], right_column[0]], equal=True, expand=True))
|
|
390
|
+
self.console.print()
|
|
391
|
+
elif left_column:
|
|
392
|
+
self.console.print(left_column[0])
|
|
393
|
+
self.console.print()
|
|
394
|
+
|
|
395
|
+
# Classifiers tree
|
|
396
|
+
classifiers_tree = self.create_classifiers_tree(info)
|
|
397
|
+
if classifiers_tree:
|
|
398
|
+
self.console.print(Panel(classifiers_tree, title="[bold yellow]🏷️ Categories", border_style="yellow"))
|
|
399
|
+
self.console.print()
|
|
400
|
+
|
|
401
|
+
# Description
|
|
402
|
+
description = info.get('description', '').strip()
|
|
403
|
+
if description and len(description) > 100:
|
|
404
|
+
# Try to render as markdown if it looks like markdown
|
|
405
|
+
if any(marker in description for marker in ['#', '*', '`', '```', '[', '](']):
|
|
406
|
+
try:
|
|
407
|
+
md = Markdown(description[:2000] + ("..." if len(description) > 2000 else ""))
|
|
408
|
+
self.console.print(Panel(md, title="[bold cyan]📖 Description", border_style="cyan"))
|
|
409
|
+
self.console.print()
|
|
410
|
+
except:
|
|
411
|
+
# Fallback to plain text
|
|
412
|
+
desc_text = description[:1000] + ("..." if len(description) > 1000 else "")
|
|
413
|
+
self.console.print(Panel(desc_text, title="[bold cyan]📖 Description", border_style="cyan"))
|
|
414
|
+
self.console.print()
|
|
415
|
+
else:
|
|
416
|
+
desc_text = description[:1000] + ("..." if len(description) > 1000 else "")
|
|
417
|
+
self.console.print(Panel(desc_text, title="[bold cyan]📖 Description", border_style="cyan"))
|
|
418
|
+
self.console.print()
|
|
419
|
+
|
|
420
|
+
# Recent releases
|
|
421
|
+
if releases:
|
|
422
|
+
releases_table = self.create_releases_table(releases, info.get('version', ''))
|
|
423
|
+
self.console.print(releases_table)
|
|
424
|
+
self.console.print()
|
|
425
|
+
|
|
426
|
+
# Statistics
|
|
427
|
+
total_files = sum(len(files) for files in releases.values())
|
|
428
|
+
total_size = sum(sum(f.get('size', 0) for f in files) for files in releases.values())
|
|
429
|
+
|
|
430
|
+
stats_table = Table(show_header=False, box=None, padding=(0, 2))
|
|
431
|
+
stats_table.add_column("Metric", style="bold yellow")
|
|
432
|
+
stats_table.add_column("Value", style="bold white")
|
|
433
|
+
|
|
434
|
+
stats_table.add_row("📊 Total Versions", str(len(releases)))
|
|
435
|
+
stats_table.add_row("📁 Total Files", str(total_files))
|
|
436
|
+
stats_table.add_row("💾 Total Size", self.format_size(total_size))
|
|
437
|
+
|
|
438
|
+
self.console.print(Panel(stats_table, title="[bold magenta]📈 Statistics", border_style="magenta"))
|
|
439
|
+
|
|
440
|
+
def get_version():
|
|
441
|
+
"""
|
|
442
|
+
Get the version of the ddf module.
|
|
443
|
+
Version is taken from the __version__.py file if it exists.
|
|
444
|
+
The content of __version__.py should be:
|
|
445
|
+
version = "0.33"
|
|
446
|
+
"""
|
|
447
|
+
try:
|
|
448
|
+
version_file = Path(__file__).parent / "__version__.py"
|
|
449
|
+
if version_file.is_file():
|
|
450
|
+
with open(version_file, "r") as f:
|
|
451
|
+
for line in f:
|
|
452
|
+
if line.strip().startswith("version"):
|
|
453
|
+
parts = line.split("=")
|
|
454
|
+
if len(parts) == 2:
|
|
455
|
+
return parts[1].strip().strip('"').strip("'")
|
|
456
|
+
except Exception as e:
|
|
457
|
+
if os.getenv('TRACEBACK') and os.getenv('TRACEBACK') in ['1', 'true', 'True']:
|
|
458
|
+
console.print_exception(show_locals=False)
|
|
459
|
+
else:
|
|
460
|
+
console.log(f"[white on red]ERROR:[/] [white on blue]{e}[/]")
|
|
461
|
+
|
|
462
|
+
return "UNKNOWN VERSION"
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def main():
|
|
466
|
+
"""Main function."""
|
|
467
|
+
parser = argparse.ArgumentParser(
|
|
468
|
+
description="🐍 PyPI Package Information Tool - Get detailed info about Python packages",
|
|
469
|
+
formatter_class=CustomRichHelpFormatter,
|
|
470
|
+
prog="pipinfo"
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
parser.add_argument(
|
|
474
|
+
'package',
|
|
475
|
+
nargs='?',
|
|
476
|
+
help='📦 Package name to search for'
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
parser.add_argument(
|
|
480
|
+
'-l', '--last',
|
|
481
|
+
action='store_true',
|
|
482
|
+
help='🔍 Show only the latest version information'
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
parser.add_argument(
|
|
486
|
+
'-d', '--download',
|
|
487
|
+
action='store_true',
|
|
488
|
+
help='📥 Download the package with progress bar'
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
parser.add_argument(
|
|
492
|
+
'-p', '--path',
|
|
493
|
+
default='.',
|
|
494
|
+
help='📁 Directory path to save downloaded files (default: current directory)'
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
parser.add_argument(
|
|
498
|
+
'--version-download',
|
|
499
|
+
help='🔢 Specific version to download (default: latest)'
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
parser.add_argument(
|
|
503
|
+
'--author',
|
|
504
|
+
action='store_true',
|
|
505
|
+
help='👤 Show author information'
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
parser.add_argument(
|
|
509
|
+
'--home',
|
|
510
|
+
action='store_true',
|
|
511
|
+
help='🏠 Show home page URL'
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
parser.add_argument(
|
|
515
|
+
'--tags',
|
|
516
|
+
action='store_true',
|
|
517
|
+
help='🏷️ Show package classifiers/tags'
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
parser.add_argument(
|
|
521
|
+
'--urls',
|
|
522
|
+
action='store_true',
|
|
523
|
+
help='🔗 Show all project URLs'
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
parser.add_argument('-v', '--version', action='version', version=f"[bold #FFFF00]version:[/] [bold #00FFFF]{get_version()}[/]", help="Show version")
|
|
527
|
+
|
|
528
|
+
args = parser.parse_args()
|
|
529
|
+
|
|
530
|
+
# Show help if no package specified
|
|
531
|
+
if not args.package:
|
|
532
|
+
parser.print_help()
|
|
533
|
+
return
|
|
534
|
+
|
|
535
|
+
# Initialize client and display
|
|
536
|
+
client = PyPIClient()
|
|
537
|
+
display = PackageInfoDisplay()
|
|
538
|
+
|
|
539
|
+
# Get package information
|
|
540
|
+
console.print(f"\n[bold blue]🔍 Searching PyPI for '{args.package}'...[/bold blue]")
|
|
541
|
+
package_data = client.get_package_info(args.package)
|
|
542
|
+
|
|
543
|
+
if not package_data:
|
|
544
|
+
console.print(f"[red]❌ Could not find package '{args.package}' on PyPI[/red]")
|
|
545
|
+
return
|
|
546
|
+
|
|
547
|
+
info = package_data.get('info', {})
|
|
548
|
+
|
|
549
|
+
# Handle specific info requests
|
|
550
|
+
if args.author:
|
|
551
|
+
author = info.get('author', 'N/A')
|
|
552
|
+
author_email = info.get('author_email', 'N/A')
|
|
553
|
+
console.print(f"[bold yellow]👤 Author:[/bold yellow] {author}")
|
|
554
|
+
if author_email != 'N/A':
|
|
555
|
+
console.print(f"[bold yellow]📧 Email:[/bold yellow] {author_email}")
|
|
556
|
+
return
|
|
557
|
+
|
|
558
|
+
if args.home:
|
|
559
|
+
home_page = info.get('home_page') or info.get('project_urls', {}).get('Homepage', 'N/A')
|
|
560
|
+
console.print(f"[bold yellow]🏠 Home Page:[/bold yellow] {home_page}")
|
|
561
|
+
return
|
|
562
|
+
|
|
563
|
+
if args.tags:
|
|
564
|
+
classifiers = info.get('classifiers', [])
|
|
565
|
+
if classifiers:
|
|
566
|
+
console.print("[bold yellow]🏷️ Package Tags/Classifiers:[/bold yellow]")
|
|
567
|
+
for classifier in classifiers:
|
|
568
|
+
console.print(f" • {classifier}")
|
|
569
|
+
else:
|
|
570
|
+
console.print("[yellow]No classifiers found[/yellow]")
|
|
571
|
+
return
|
|
572
|
+
|
|
573
|
+
if args.urls:
|
|
574
|
+
project_urls = info.get('project_urls', {})
|
|
575
|
+
if project_urls:
|
|
576
|
+
console.print("[bold yellow]🔗 Project URLs:[/bold yellow]")
|
|
577
|
+
for url_type, url in project_urls.items():
|
|
578
|
+
console.print(f" 🌐 [cyan]{url_type}:[/cyan] {url}")
|
|
579
|
+
else:
|
|
580
|
+
console.print("[yellow]No project URLs found[/yellow]")
|
|
581
|
+
return
|
|
582
|
+
|
|
583
|
+
# Download package if requested
|
|
584
|
+
if args.download:
|
|
585
|
+
version = args.version_download or "latest"
|
|
586
|
+
console.print(f"\n[bold green]📥 Downloading {args.package} (version: {version})...[/bold green]")
|
|
587
|
+
success = client.download_package(args.package, version, args.path)
|
|
588
|
+
if not success:
|
|
589
|
+
return
|
|
590
|
+
console.print()
|
|
591
|
+
|
|
592
|
+
# Display package information
|
|
593
|
+
display.display_package_info(package_data, args.last)
|
|
594
|
+
|
|
595
|
+
# Final message
|
|
596
|
+
console.print(f"[dim]💡 Use --download to download this package, or --help for more options[/dim]")
|
|
597
|
+
|
|
598
|
+
if __name__ == "__main__":
|
|
599
|
+
try:
|
|
600
|
+
main()
|
|
601
|
+
except KeyboardInterrupt:
|
|
602
|
+
console.print("\n[yellow]⚠️ Operation cancelled by user[/yellow]")
|
|
603
|
+
sys.exit(1)
|
|
604
|
+
except Exception as e:
|
|
605
|
+
console.print(f"\n[red]❌ Unexpected error: {str(e)}[/red]")
|
|
606
|
+
sys.exit(1)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pypi_info
|
|
3
|
+
Version: 0.51
|
|
4
|
+
Summary: Search and get description of package on pypi.org
|
|
5
|
+
Home-page: https://github.com/cumulus13/pipinfo
|
|
6
|
+
Author: Hadi Cahyadi
|
|
7
|
+
Author-email: cumulus13@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
17
|
+
Requires-Python: >=3.7
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: rich
|
|
20
|
+
Requires-Dist: rich_argparse
|
|
21
|
+
|
|
22
|
+
# pypi-info
|
|
23
|
+
|
|
24
|
+
🐍 PyPI Package Information Tool - Get detailed info about Python packages
|
|
25
|
+
|
|
26
|
+
## usage
|
|
27
|
+
```bash:
|
|
28
|
+
Usage: pypi-info [-h] [-l] [-d] [-p PATH] [--version-download VERSION_DOWNLOAD] [--author] [--home] [--tags] [--urls] [-v]
|
|
29
|
+
[package]
|
|
30
|
+
|
|
31
|
+
🐍 PyPI Package Information Tool - Get detailed info about Python packages
|
|
32
|
+
|
|
33
|
+
Positional Arguments:
|
|
34
|
+
package 📦 Package name to search for
|
|
35
|
+
|
|
36
|
+
Options:
|
|
37
|
+
-h, --help show this help message and exit
|
|
38
|
+
-l, --last 🔍 Show only the latest version information
|
|
39
|
+
-d, --download 📥 Download the package with progress bar
|
|
40
|
+
-p, --path PATH 📁 Directory path to save downloaded files (default: current directory)
|
|
41
|
+
--version-download VERSION_DOWNLOAD
|
|
42
|
+
🔢 Specific version to download (default: latest)
|
|
43
|
+
--author 👤 Show author information
|
|
44
|
+
--home 🏠 Show home page URL
|
|
45
|
+
--tags 🏷️ Show package classifiers/tags
|
|
46
|
+
--urls 🔗 Show all project URLs
|
|
47
|
+
-v, --version Show version
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## author
|
|
51
|
+
[Hadi Cahyadi](mailto:cumulus13@gmail.com)
|
|
52
|
+
|
|
53
|
+
## requirements
|
|
54
|
+
- rich
|
|
55
|
+
- rich_argparse
|
|
56
|
+
|
|
57
|
+
[](https://www.buymeacoffee.com/cumulus13)
|
|
58
|
+
|
|
59
|
+
[](https://ko-fi.com/cumulus13)
|
|
60
|
+
|
|
61
|
+
[Support me on Patreon](https://www.patreon.com/cumulus13)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
pypi_info/__init__.py
|
|
5
|
+
pypi_info/__main__.py
|
|
6
|
+
pypi_info/__version__.py
|
|
7
|
+
pypi_info/pipinfo.py
|
|
8
|
+
pypi_info.egg-info/PKG-INFO
|
|
9
|
+
pypi_info.egg-info/SOURCES.txt
|
|
10
|
+
pypi_info.egg-info/dependency_links.txt
|
|
11
|
+
pypi_info.egg-info/entry_points.txt
|
|
12
|
+
pypi_info.egg-info/requires.txt
|
|
13
|
+
pypi_info.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pypi_info
|
pypi_info-0.51/setup.cfg
ADDED
pypi_info-0.51/setup.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import shutil
|
|
4
|
+
|
|
5
|
+
NAME = 'pypi_info'
|
|
6
|
+
shutil.copy('__version__.py', str(Path(NAME) / '__version__.py'))
|
|
7
|
+
|
|
8
|
+
def get_version():
|
|
9
|
+
try:
|
|
10
|
+
with open(f"{NAME.replace('-', '_')}/__version__.py", "r") as f:
|
|
11
|
+
for line in f:
|
|
12
|
+
if line.strip().startswith("version"):
|
|
13
|
+
parts = line.split("=")
|
|
14
|
+
if len(parts) == 2:
|
|
15
|
+
return parts[1].strip().strip('"').strip("'")
|
|
16
|
+
except Exception as e:
|
|
17
|
+
print(f"Error getting version: {e}")
|
|
18
|
+
return "0.1"
|
|
19
|
+
|
|
20
|
+
def requirements():
|
|
21
|
+
try:
|
|
22
|
+
with open('requirements.txt', 'r') as f:
|
|
23
|
+
return [line.strip() for line in f if line.strip()]
|
|
24
|
+
except Exception as e:
|
|
25
|
+
print(f"Error reading requirements: {e}")
|
|
26
|
+
return []
|
|
27
|
+
|
|
28
|
+
setup(
|
|
29
|
+
name=NAME,
|
|
30
|
+
version=get_version(),
|
|
31
|
+
# packages=find_packages(exclude=["*.pipinfo1.py"]),
|
|
32
|
+
packages=[NAME.replace("-","_")],
|
|
33
|
+
include_package_data=True,
|
|
34
|
+
# package_data={
|
|
35
|
+
# 'pipinfo': ['batmaker.ini'],
|
|
36
|
+
# },
|
|
37
|
+
install_requires=requirements(),
|
|
38
|
+
entry_points={
|
|
39
|
+
'console_scripts': [
|
|
40
|
+
'pipinfo = pypi_info.pipinfo:main',
|
|
41
|
+
'pypi-info = pypi_info.pipinfo:main',
|
|
42
|
+
'pypi_info = pypi_info.pipinfo:main',
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
author="Hadi Cahyadi",
|
|
46
|
+
author_email="cumulus13@gmail.com",
|
|
47
|
+
description="Search and get description of package on pypi.org",
|
|
48
|
+
long_description=(Path(__file__).parent / "README.md").read_text(encoding="utf-8") if Path("README.md").exists() else "",
|
|
49
|
+
long_description_content_type="text/markdown",
|
|
50
|
+
url="https://github.com/cumulus13/pipinfo",
|
|
51
|
+
classifiers=[
|
|
52
|
+
"Development Status :: 4 - Beta",
|
|
53
|
+
"Environment :: Console",
|
|
54
|
+
"Intended Audience :: Developers",
|
|
55
|
+
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
|
56
|
+
"Programming Language :: Python :: 3",
|
|
57
|
+
"Operating System :: OS Independent",
|
|
58
|
+
"Topic :: Software Development :: Libraries",
|
|
59
|
+
"Topic :: Internet :: WWW/HTTP :: Indexing/Search",
|
|
60
|
+
],
|
|
61
|
+
python_requires='>=3.7',
|
|
62
|
+
license="MIT",
|
|
63
|
+
)
|