sideload 1.2.0__py3-none-any.whl → 1.4.0__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.

Potentially problematic release.


This version of sideload might be problematic. Click here for more details.

sideload/cli.py CHANGED
@@ -33,9 +33,19 @@ console = Console()
33
33
 
34
34
 
35
35
  class SideloadClient:
36
- def __init__(self, jsonbin_token: str, collection_id: str):
36
+ def __init__(
37
+ self,
38
+ jsonbin_token: str,
39
+ collection_id: str,
40
+ verify_ssl: bool = True,
41
+ key_type: str = "master"
42
+ ):
37
43
  self.collection_id = collection_id
38
- self.connector = JSONBinConnector(jsonbin_token)
44
+ self.connector = JSONBinConnector(
45
+ jsonbin_token,
46
+ verify_ssl=verify_ssl,
47
+ key_type=key_type
48
+ )
39
49
  self.manager = SideloadBinManager(self.connector)
40
50
 
41
51
  def __enter__(self):
@@ -380,6 +390,17 @@ Examples:
380
390
  type=Path,
381
391
  help="Working directory for extraction (for debugging, defaults to temp directory)",
382
392
  )
393
+ download_parser.add_argument(
394
+ "--no-verify-ssl",
395
+ action="store_true",
396
+ help="Disable SSL certificate verification for JSONBin API",
397
+ )
398
+ download_parser.add_argument(
399
+ "--jsonbin-key-type",
400
+ choices=["master", "access"],
401
+ default=None,
402
+ help="JSONBin key type to use (default: from JSONBIN_KEY_TYPE env or 'master')",
403
+ )
383
404
 
384
405
  args = parser.parse_args()
385
406
 
@@ -394,6 +415,19 @@ Examples:
394
415
  jsonbin_token = args.token or os.environ.get("JSONBIN_TOKEN")
395
416
  collection_id = args.collection or os.environ.get("SIDELOAD_COLLECTION_ID")
396
417
 
418
+ # Get SSL verification setting (CLI flag overrides env var)
419
+ verify_ssl = not args.no_verify_ssl
420
+ if args.no_verify_ssl:
421
+ # If CLI flag is set, disable SSL verification
422
+ verify_ssl = False
423
+ else:
424
+ # Otherwise check environment variable (default to True)
425
+ env_verify = os.environ.get("JSONBIN_VERIFY_SSL", "true").lower()
426
+ verify_ssl = env_verify in ("true", "1", "yes")
427
+
428
+ # Get key type (CLI arg > env var > default)
429
+ key_type = args.jsonbin_key_type or os.environ.get("JSONBIN_KEY_TYPE", "master").lower()
430
+
397
431
  if not jsonbin_token:
398
432
  console.print(
399
433
  "❌ JSONBin token required. Set JSONBIN_TOKEN environment variable or use --token",
@@ -418,10 +452,12 @@ Examples:
418
452
  console.print(f" [dim]Output:[/dim] {args.output}")
419
453
  console.print(f" [dim]Work Directory:[/dim] {args.work_dir or 'temp (auto-cleanup)'}")
420
454
  console.print(f" [dim]Collection ID:[/dim] {collection_id}")
455
+ console.print(f" [dim]SSL Verification:[/dim] {verify_ssl}")
456
+ console.print(f" [dim]JSONBin Key Type:[/dim] {key_type}")
421
457
  console.print()
422
458
 
423
459
  try:
424
- with SideloadClient(jsonbin_token, collection_id) as client:
460
+ with SideloadClient(jsonbin_token, collection_id, verify_ssl, key_type) as client:
425
461
  # Create the request
426
462
  console.print(
427
463
  f"🌐 Requesting download for: [bold blue]{args.url}[/bold blue]"
@@ -449,9 +485,14 @@ Examples:
449
485
 
450
486
  console.print(Rule("📦 Downloading Packages"))
451
487
 
452
- with tempfile.TemporaryDirectory() as temp_dir:
453
- temp_path = Path(temp_dir)
454
- wheel_files = client.download_packages(package_names, temp_path, args.debug)
488
+ # Use work_dir for downloads if provided, otherwise use temp directory
489
+ use_work_dir = args.work_dir is not None
490
+ if use_work_dir:
491
+ download_dir = args.work_dir / "wheels"
492
+ download_dir.mkdir(parents=True, exist_ok=True)
493
+ console.print(f"[yellow]📥 Downloading packages to: {download_dir}[/yellow]")
494
+
495
+ wheel_files = client.download_packages(package_names, download_dir, args.debug)
455
496
 
456
497
  if not wheel_files:
457
498
  console.print(
@@ -476,6 +517,35 @@ Examples:
476
517
  console.print(
477
518
  f"📊 File size: [cyan]{output_file.stat().st_size:,} bytes[/cyan]"
478
519
  )
520
+ else:
521
+ # Use temporary directory for downloads
522
+ with tempfile.TemporaryDirectory() as temp_dir:
523
+ temp_path = Path(temp_dir)
524
+ wheel_files = client.download_packages(package_names, temp_path, args.debug)
525
+
526
+ if not wheel_files:
527
+ console.print(
528
+ "❌ No packages were downloaded successfully", style="red"
529
+ )
530
+ return
531
+
532
+ # Extract and reassemble
533
+ console.print(Rule("🔧 Reassembling File"))
534
+ original_filename = data.get("filename", "downloaded_file")
535
+ output_file = args.output / original_filename
536
+
537
+ client.extract_and_reassemble(
538
+ wheel_files, package_names, original_filename, output_file, args.debug, args.work_dir
539
+ )
540
+
541
+ # Success!
542
+ console.print(Rule("✨ Complete"))
543
+ console.print(
544
+ f"🎉 File successfully downloaded to: [bold green]{output_file}[/bold green]"
545
+ )
546
+ console.print(
547
+ f"📊 File size: [cyan]{output_file.stat().st_size:,} bytes[/cyan]"
548
+ )
479
549
 
480
550
  except KeyboardInterrupt:
481
551
  console.print("\n⚠️ Download interrupted by user", style="yellow")
@@ -2,13 +2,26 @@ import os
2
2
  import httpx
3
3
 
4
4
  JSONBIN_TOKEN = os.environ["JSONBIN_TOKEN"]
5
+ JSONBIN_VERIFY_SSL = os.environ.get("JSONBIN_VERIFY_SSL", "true").lower() in ("true", "1", "yes")
6
+ JSONBIN_KEY_TYPE = os.environ.get("JSONBIN_KEY_TYPE", "master").lower() # "master" or "access"
5
7
 
6
8
 
7
9
  class JSONBinConnector:
8
- def __init__(self):
10
+ def __init__(self, verify_ssl: bool = None, key_type: str = None):
11
+ # Use provided values or fall back to environment variables
12
+ verify = JSONBIN_VERIFY_SSL if verify_ssl is None else verify_ssl
13
+ key_type = key_type or JSONBIN_KEY_TYPE
14
+
15
+ # Determine the key header to use
16
+ if key_type == "access":
17
+ key_header = "X-Access-Key"
18
+ else:
19
+ key_header = "X-Master-Key"
20
+
9
21
  self.client = httpx.Client(
10
22
  base_url="https://api.jsonbin.io/v3",
11
- headers={"X-Master-Key": JSONBIN_TOKEN, "Content-Type": "application/json"},
23
+ headers={key_header: JSONBIN_TOKEN, "Content-Type": "application/json"},
24
+ verify=verify,
12
25
  )
13
26
 
14
27
  def get_collections(self) -> list:
sideload/jsonbin_old.py CHANGED
@@ -11,19 +11,35 @@ from typing import Dict, List, Optional, Any
11
11
  class JSONBinConnector:
12
12
  """A connector for JSONBin.io API with httpx"""
13
13
 
14
- def __init__(self, api_token: str, base_url: str = "https://api.jsonbin.io/v3"):
14
+ def __init__(
15
+ self,
16
+ api_token: str,
17
+ base_url: str = "https://api.jsonbin.io/v3",
18
+ verify_ssl: bool = True,
19
+ key_type: str = "master"
20
+ ):
15
21
  """
16
22
  Initialize the JSONBin connector
17
23
 
18
24
  Args:
19
25
  api_token: JSONBin API token
20
26
  base_url: JSONBin API base URL
27
+ verify_ssl: Whether to verify SSL certificates (default: True)
28
+ key_type: Type of key to use - "master" or "access" (default: "master")
21
29
  """
22
30
  self.api_token = api_token
23
31
  self.base_url = base_url
32
+
33
+ # Determine the key header to use
34
+ if key_type == "access":
35
+ key_header = "X-Access-Key"
36
+ else:
37
+ key_header = "X-Master-Key"
38
+
24
39
  self.client = httpx.Client(
25
40
  base_url=base_url,
26
- headers={"X-Master-Key": api_token, "Content-Type": "application/json"},
41
+ headers={key_header: api_token, "Content-Type": "application/json"},
42
+ verify=verify_ssl,
27
43
  )
28
44
 
29
45
  def __enter__(self):
sideload/main.py CHANGED
@@ -11,7 +11,7 @@ from sideload.jsonbin_connector import JSONBinConnector
11
11
 
12
12
  JSONBIN_TOKEN = os.environ["JSONBIN_TOKEN"]
13
13
  PYPI_TOKEN = os.environ["PYPI_TOKEN"]
14
- MAX_PACKAGE_SIZE = 95 * 1024 * 1024 # 95 MB
14
+ MAX_PACKAGE_SIZE = 92 * 1024 * 1024 # 95 MB
15
15
 
16
16
  LAST_BINS: dict[str, str | None] = {}
17
17
 
@@ -6,16 +6,32 @@ Admin script to delete all sideload-* packages from PyPI using browser automatio
6
6
  import os
7
7
  import sys
8
8
  import asyncio
9
+ import random
9
10
  from playwright.async_api import async_playwright
11
+ import pyotp
10
12
 
11
13
  PYPI_USER = os.environ.get("PYPI_USER")
12
14
  PYPI_PASSWORD = os.environ.get("PYPI_PASSWORD")
15
+ PYPI_TOTP = os.environ.get("PYPI_TOTP") # Optional: TOTP secret key
13
16
 
14
17
  if not PYPI_USER or not PYPI_PASSWORD:
15
18
  print("❌ PYPI_USER and PYPI_PASSWORD environment variables must be set")
16
19
  sys.exit(1)
17
20
 
18
21
 
22
+ async def human_like_mouse_movement(page):
23
+ """Simulate human-like mouse movements across the page"""
24
+ viewport_size = page.viewport_size
25
+ width = viewport_size.get('width', 1280) if viewport_size else 1280
26
+ height = viewport_size.get('height', 720) if viewport_size else 720
27
+
28
+ for _ in range(random.randint(3, 6)):
29
+ x = random.randint(50, width - 50)
30
+ y = random.randint(50, height - 50)
31
+ await page.mouse.move(x, y)
32
+ await asyncio.sleep(random.uniform(0.15, 0.4))
33
+
34
+
19
35
  async def delete_project(page, package_name: str) -> bool:
20
36
  """Delete a project from PyPI using browser automation"""
21
37
  try:
@@ -35,8 +51,34 @@ async def delete_project(page, package_name: str) -> bool:
35
51
  await page.evaluate('window.scrollTo(0, document.body.scrollHeight)')
36
52
  await asyncio.sleep(0.5)
37
53
 
54
+ # Extract the exact project name from the page to ensure correct case
55
+ # Look for the project name in the delete section
56
+ exact_project_name = await page.evaluate('''() => {
57
+ // Find the label that mentions the project name
58
+ const labels = Array.from(document.querySelectorAll('label'));
59
+ for (const label of labels) {
60
+ const text = label.textContent;
61
+ if (text && text.includes('confirm by typing the project name')) {
62
+ // Extract the project name from something like "confirm by typing the project name (project-name) below"
63
+ const match = text.match(/\\(([^)]+)\\)/);
64
+ if (match) return match[1];
65
+ }
66
+ }
67
+ // Fallback: get from URL
68
+ const path = window.location.pathname;
69
+ const urlMatch = path.match(/\\/manage\\/project\\/([^\\/]+)/);
70
+ return urlMatch ? decodeURIComponent(urlMatch[1]) : null;
71
+ }''')
72
+
73
+ if not exact_project_name:
74
+ print(f" ⚠️ Could not extract exact project name from page, using: {package_name}")
75
+ exact_project_name = package_name
76
+ else:
77
+ print(f" 📝 Extracted exact project name: {exact_project_name}")
78
+
38
79
  # Find and check all "I understand..." checkboxes
39
80
  checkboxes = await page.query_selector_all('input[type="checkbox"]')
81
+ print(f" ✓ Found {len(checkboxes)} checkboxes")
40
82
  for checkbox in checkboxes:
41
83
  await checkbox.check()
42
84
 
@@ -46,29 +88,55 @@ async def delete_project(page, package_name: str) -> bool:
46
88
  print(f" ⚠️ Could not find confirmation input for {package_name}")
47
89
  return False
48
90
 
49
- await confirm_input.fill(package_name)
91
+ # Clear and type the exact project name with human-like behavior
92
+ await confirm_input.clear()
93
+ await asyncio.sleep(random.uniform(0.2, 0.5))
94
+
95
+ # Move mouse before typing
96
+ await human_like_mouse_movement(page)
97
+
98
+ # Type each character with varying delays
99
+ for char in exact_project_name:
100
+ await confirm_input.type(char, delay=random.randint(80, 200))
101
+ await asyncio.sleep(random.uniform(0.05, 0.15))
102
+
103
+ print(f" ✓ Typed project name: {exact_project_name}")
50
104
 
51
- # Find and click the delete link that opens the modal
52
- # This is the button with href="#project_name-modal"
53
- delete_link = await page.query_selector('a.button--danger[data-delete-confirm-target="button"]')
105
+ # Wait a moment for the button to become enabled (human-like pause)
106
+ await asyncio.sleep(random.uniform(1.0, 2.0))
107
+
108
+ # Move mouse naturally
109
+ await human_like_mouse_movement(page)
110
+
111
+ # Find and click the delete link
112
+ delete_link = await page.query_selector('[data-delete-confirm-target="button"]')
54
113
  if not delete_link:
55
- print(f" ⚠️ Could not find delete link for {package_name}")
114
+ print(f" ⚠️ Could not find delete button")
56
115
  return False
57
116
 
58
- # Wait a moment to ensure button is enabled
59
- await asyncio.sleep(0.5)
117
+ # Check if the button is enabled
118
+ is_disabled = await delete_link.evaluate('(el) => el.classList.contains("button--disabled") || el.hasAttribute("disabled")')
119
+ if is_disabled:
120
+ print(f" ⚠️ Delete button is still disabled")
121
+ # Take a screenshot for debugging
122
+ await page.screenshot(path=f"debug_{package_name}.png")
123
+ print(f" 📸 Screenshot saved as debug_{package_name}.png")
124
+ return False
60
125
 
61
- # Click the link to open the modal
62
- await delete_link.click()
63
- await asyncio.sleep(0.5)
126
+ print(f" 🖱️ Clicking delete button...")
64
127
 
65
- # Now find and click the actual delete button in the modal
66
- modal_delete_button = await page.query_selector('button[type="submit"]')
67
- if not modal_delete_button:
68
- print(f" ⚠️ Could not find modal delete button for {package_name}")
69
- return False
128
+ # Move mouse to the button area naturally
129
+ box = await delete_link.bounding_box()
130
+ if box:
131
+ # Move to a random point within the button
132
+ target_x = box['x'] + random.uniform(10, box['width'] - 10)
133
+ target_y = box['y'] + random.uniform(10, box['height'] - 10)
134
+ await page.mouse.move(target_x, target_y)
135
+ await asyncio.sleep(random.uniform(0.2, 0.5))
70
136
 
71
- await modal_delete_button.click()
137
+ # Click and wait for navigation
138
+ await delete_link.click()
139
+ await asyncio.sleep(random.uniform(0.5, 1.0))
72
140
  await page.wait_for_load_state("networkidle")
73
141
 
74
142
  print(f" ✅ Deleted {package_name}")
@@ -76,6 +144,14 @@ async def delete_project(page, package_name: str) -> bool:
76
144
 
77
145
  except Exception as e:
78
146
  print(f" ❌ Error deleting {package_name}: {e}")
147
+ import traceback
148
+ traceback.print_exc()
149
+ # Take screenshot on error
150
+ try:
151
+ await page.screenshot(path=f"error_{package_name}.png")
152
+ print(f" 📸 Error screenshot saved as error_{package_name}.png")
153
+ except:
154
+ pass
79
155
  return False
80
156
 
81
157
 
@@ -107,6 +183,10 @@ async def get_user_projects(page) -> list[str]:
107
183
  async def main():
108
184
  print("🧹 PyPI Sideload Package Cleanup Tool")
109
185
  print("=" * 50)
186
+ if PYPI_TOTP:
187
+ print("🔐 TOTP auto-generation enabled")
188
+ else:
189
+ print("⚠️ TOTP auto-generation disabled (set PYPI_TOTP to enable)")
110
190
  print()
111
191
 
112
192
  async with async_playwright() as p:
@@ -121,27 +201,128 @@ async def main():
121
201
  print(" Opening login page...")
122
202
  await page.goto('https://pypi.org/account/login/')
123
203
 
124
- # Fill in login credentials
204
+ # Fill in login credentials (fast, no need to be slow here)
125
205
  print(" Filling credentials...")
126
206
  await page.fill('#username', PYPI_USER)
207
+ await asyncio.sleep(random.uniform(0.2, 0.4))
127
208
  await page.fill('#password', PYPI_PASSWORD)
209
+ await asyncio.sleep(random.uniform(0.3, 0.6))
128
210
 
129
211
  # Submit login form
130
212
  await page.click('input[type="submit"]')
131
- await asyncio.sleep(1)
213
+ await asyncio.sleep(2)
132
214
 
133
215
  # Wait for user to complete TOTP if required
134
216
  current_url = page.url
135
217
  if '/account/two-factor/' in current_url:
136
- print(" ⚠️ TOTP required - please enter your 2FA code in the browser...")
137
- while True:
138
- current_url = page.url
139
- # Wait until we're not on TOTP page anymore
140
- if '/account/two-factor/' not in current_url:
141
- break
142
- await asyncio.sleep(1)
143
-
144
- print("✅ Login completed!")
218
+ if PYPI_TOTP:
219
+ print(" 🔐 Generating TOTP code...")
220
+
221
+ # Simulate human-like delay before interacting
222
+ await asyncio.sleep(random.uniform(1.5, 2.5))
223
+
224
+ # Move mouse around naturally across the page
225
+ print(" 🖱️ Moving mouse naturally...")
226
+ await human_like_mouse_movement(page)
227
+
228
+ totp = pyotp.TOTP(PYPI_TOTP)
229
+ code = totp.now()
230
+ print(f" ✓ Generated TOTP code: {code}")
231
+
232
+ # Find TOTP input field and enter the code
233
+ totp_input = await page.query_selector('input[name="totp_value"]')
234
+ if not totp_input:
235
+ totp_input = await page.query_selector('input[type="text"]')
236
+
237
+ if totp_input:
238
+ # Click on the input field naturally
239
+ await totp_input.click()
240
+ await asyncio.sleep(random.uniform(0.4, 0.8))
241
+
242
+ # Type each character slowly with human-like delays
243
+ print(" ⌨️ Typing TOTP code slowly...")
244
+ for i, char in enumerate(code):
245
+ await totp_input.type(char, delay=random.randint(100, 250))
246
+ await asyncio.sleep(random.uniform(0.1, 0.25))
247
+ # Occasionally move mouse during typing
248
+ if i % 2 == 0:
249
+ x = random.randint(200, 600)
250
+ y = random.randint(200, 500)
251
+ await page.mouse.move(x, y)
252
+
253
+ print(" ✓ Entered TOTP code")
254
+
255
+ # Wait a bit before submitting (like a human would)
256
+ await asyncio.sleep(random.uniform(0.8, 1.5))
257
+
258
+ # More mouse movements
259
+ print(" 🖱️ Moving mouse before submit...")
260
+ await human_like_mouse_movement(page)
261
+
262
+ # Submit the form
263
+ submit_button = await page.query_selector('button[type="submit"]')
264
+ if not submit_button:
265
+ submit_button = await page.query_selector('input[type="submit"]')
266
+
267
+ if submit_button:
268
+ await submit_button.click()
269
+ print(" ✓ Submitted TOTP, waiting for response...")
270
+
271
+ # Wait for navigation
272
+ await asyncio.sleep(4)
273
+
274
+ # Check if we've successfully logged in
275
+ current_url = page.url
276
+ if '/account/' in current_url or '/manage/' in current_url:
277
+ print(" ✓ Successfully logged in!")
278
+ elif '/account/two-factor/' in current_url:
279
+ # Still on 2FA page - likely a captcha
280
+ print("\n" + "=" * 60)
281
+ print(" 🤖 CAPTCHA DETECTED!")
282
+ print(" 👤 Please solve the captcha in the browser window")
283
+ print(" ⏳ Waiting for you to complete it...")
284
+ print("=" * 60 + "\n")
285
+
286
+ # Wait for user to solve captcha - keep checking
287
+ # We need to wait until we're actually logged in (on account page or similar)
288
+ while True:
289
+ await asyncio.sleep(2)
290
+ current_url = page.url
291
+ # Check if we've successfully logged in (not just left the 2FA page)
292
+ if ('/account/' in current_url or '/manage/' in current_url) and '/account/two-factor/' not in current_url:
293
+ break
294
+ # Print status every 2 seconds to show we're still waiting
295
+ print(" ⏳ Still waiting for captcha completion...", end='\r')
296
+
297
+ print("\n ✓ Captcha solved and logged in! Continuing...")
298
+ else:
299
+ print(f" ⚠️ Unexpected page: {current_url}")
300
+ print(" ⏳ Waiting for login to complete...")
301
+ # Wait until we're on a known good page
302
+ while True:
303
+ await asyncio.sleep(2)
304
+ current_url = page.url
305
+ if '/account/' in current_url or '/manage/' in current_url:
306
+ break
307
+ print(" ⏳ Still waiting...", end='\r')
308
+ print("\n ✓ Login completed!")
309
+ else:
310
+ print(" ⚠️ Could not find submit button")
311
+ else:
312
+ print(" ⚠️ Could not find TOTP input field")
313
+ else:
314
+ print(" ⚠️ TOTP required - please enter your 2FA code in the browser...")
315
+ print(" 👤 Waiting for you to complete 2FA (and captcha if present)...")
316
+ while True:
317
+ await asyncio.sleep(2)
318
+ current_url = page.url
319
+ # Wait until we're actually logged in
320
+ if '/account/two-factor/' not in current_url and ('/account/' in current_url or '/manage/' in current_url):
321
+ break
322
+ print(" ⏳ Still waiting for 2FA completion...", end='\r')
323
+ print("\n ✓ 2FA completed!")
324
+
325
+ print("\n✅ Login completed!")
145
326
 
146
327
  # Get all sideload projects
147
328
  projects = await get_user_projects(page)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sideload
3
- Version: 1.2.0
3
+ Version: 1.4.0
4
4
  Summary: Download large files via PyPI packages
5
5
  Author: Sygmei
6
6
  Author-email: Sygmei <3835355+Sygmei@users.noreply.github.com>
@@ -11,6 +11,7 @@ Requires-Dist: rich>=13.0.0
11
11
  Requires-Dist: httpx>=0.28.1
12
12
  Requires-Dist: pip>=25.2
13
13
  Requires-Dist: playwright>=1.55.0
14
+ Requires-Dist: pyotp>=2.9.0
14
15
  Requires-Python: >=3.12
15
16
  Description-Content-Type: text/markdown
16
17
 
@@ -0,0 +1,11 @@
1
+ sideload/__init__.py,sha256=Y3rHLtR7n0sjLXrn-BEcrbIHx-9uE1tPZkooavo7xcA,222
2
+ sideload/cli.py,sha256=4nG0QLwcYypN2A7ENffEE4IEh0VNzwrAdL7cX4ZxjPM,21932
3
+ sideload/jsonbin.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ sideload/jsonbin_connector.py,sha256=IS7YdSBZeZMoTwOyA1I0T7aw36-KERYNnD324dRRI5A,2305
5
+ sideload/jsonbin_old.py,sha256=Rs657F3dTtdoxVYolByuyINABCIQx_VLxwxgdne1mws,8865
6
+ sideload/main.py,sha256=_YE0b93jhDFN8PcBfHLKbZp1EPQGfWq3wIYyX7nT7Qo,7600
7
+ sideload/scripts/cleanup_pypi.py,sha256=YPzYVsutFVsRoCi4qG6vZo8x5YiLzpAfJ7xKxZiZWgI,15264
8
+ sideload-1.4.0.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
9
+ sideload-1.4.0.dist-info/entry_points.txt,sha256=7ULrIjaVhrxMhuddTeoPjeIrqmIvVc9cSU3lZU2_YqE,44
10
+ sideload-1.4.0.dist-info/METADATA,sha256=VbhpNuxWsOFNVCvw9gBBUtT-mxqKvM626A5rGvzB4xE,4309
11
+ sideload-1.4.0.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- sideload/__init__.py,sha256=Y3rHLtR7n0sjLXrn-BEcrbIHx-9uE1tPZkooavo7xcA,222
2
- sideload/cli.py,sha256=JCedG4Xqz3AQ-24eerMW8ZdBpTx_sOZI1nfXDzOU9dE,18843
3
- sideload/jsonbin.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- sideload/jsonbin_connector.py,sha256=HtR1Pwnpm5jfYcmnvcug9HaKxFpsyJBXYYlLuzWPiTE,1680
5
- sideload/jsonbin_old.py,sha256=ve21WsV7Ay60moFfrR4lSM_JRZNqo4z5v69cNw0Iqo0,8411
6
- sideload/main.py,sha256=EiZguc4-ug8RM6vXkZuXW_ps8jFWoAfeKBBNy7FX-F0,7600
7
- sideload/scripts/cleanup_pypi.py,sha256=CouuOhnbpSbrFc1KhlOeZAuajVVpGF9d4qwFpIKkEvY,5906
8
- sideload-1.2.0.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
9
- sideload-1.2.0.dist-info/entry_points.txt,sha256=7ULrIjaVhrxMhuddTeoPjeIrqmIvVc9cSU3lZU2_YqE,44
10
- sideload-1.2.0.dist-info/METADATA,sha256=OVl1VRCVXj0m3JeHtdEQ0bjX6LyEVf8R4_tKTATPBIw,4281
11
- sideload-1.2.0.dist-info/RECORD,,