copyparty 1.17.1__py3-none-any.whl → 1.17.2__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.
copyparty/__main__.py CHANGED
@@ -1020,6 +1020,7 @@ def add_upload(ap):
1020
1020
  ap2.add_argument("--df", metavar="GiB", type=u, default="0", help="ensure \033[33mGiB\033[0m free disk space by rejecting upload requests; assumes gigabytes unless a unit suffix is given: [\033[32m256m\033[0m], [\033[32m4\033[0m], [\033[32m2T\033[0m] (volflag=df)")
1021
1021
  ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="windows-only: minimum size of incoming uploads through up2k before they are made into sparse files")
1022
1022
  ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; [\033[32m-1\033[0m] = forbidden/always-off, [\033[32m0\033[0m] = default-off and warn if enabled, [\033[32m1\033[0m] = default-off, [\033[32m2\033[0m] = on, [\033[32m3\033[0m] = on and disable datecheck")
1023
+ ap2.add_argument("--nosubtle", metavar="N", type=int, default=0, help="when to use a wasm-hasher instead of the browser's builtin; faster on chrome, but buggy in older chrome versions. [\033[32m0\033[0m] = only when necessary (non-https), [\033[32m1\033[0m] = always (all browsers), [\033[32m2\033[0m] = always on chrome/firefox, [\033[32m3\033[0m] = always on chrome, [\033[32mN\033[0m] = chrome-version N and newer (recommendation: 137)")
1023
1024
  ap2.add_argument("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good when latency is low (same-country), 2~4 for android-clients, 2~6 for cross-atlantic. Max is 6 in most browsers. Big values increase network-speed but may reduce HDD-speed")
1024
1025
  ap2.add_argument("--u2sz", metavar="N,N,N", type=u, default="1,64,96", help="web-client: default upload chunksize (MiB); sets \033[33mmin,default,max\033[0m in the settings gui. Each HTTP POST will aim for \033[33mdefault\033[0m, and never exceed \033[33mmax\033[0m. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32m1,1,1\033[0m]")
1025
1026
  ap2.add_argument("--u2ow", metavar="NUM", type=int, default=0, help="web-client: default setting for when to replace/overwrite existing files; [\033[32m0\033[0m]=never, [\033[32m1\033[0m]=if-client-newer, [\033[32m2\033[0m]=always (volflag=u2ow)")
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 17, 1)
3
+ VERSION = (1, 17, 2)
4
4
  CODENAME = "mixtape.m3u"
5
- BUILD_DT = (2025, 5, 18)
5
+ BUILD_DT = (2025, 5, 27)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
copyparty/authsrv.py CHANGED
@@ -350,7 +350,6 @@ class VFS(object):
350
350
  self.flags = flags # config options
351
351
  self.root = self
352
352
  self.dev = 0 # st_dev
353
- self.badcfg1 = False
354
353
  self.nodes = {} # child nodes
355
354
  self.histtab = {} # all realpath->histpath
356
355
  self.dbpaths = {} # all realpath->dbpath
@@ -870,6 +869,7 @@ class AuthSrv(object):
870
869
  self.warn_anonwrite = warn_anonwrite
871
870
  self.line_ctr = 0
872
871
  self.indent = ""
872
+ self.is_lxc = args.c == ["/z/initcfg"]
873
873
 
874
874
  # fwd-decl
875
875
  self.vfs = VFS(log_func, "", "", "", AXS(), {})
@@ -880,6 +880,8 @@ class AuthSrv(object):
880
880
  self.defpw = {}
881
881
  self.grps = {}
882
882
  self.re_pwd = None
883
+ self.cfg_files_loaded = []
884
+ self.badcfg1 = False
883
885
 
884
886
  # all volumes observed since last restart
885
887
  self.idp_vols = {} # vpath->abspath
@@ -1475,8 +1477,10 @@ class AuthSrv(object):
1475
1477
  daxs = {}
1476
1478
  mflags = {} # vpath:flags
1477
1479
  mount = {} # dst:src (vp:(ap,vp0))
1480
+ cfg_files_loaded = []
1478
1481
 
1479
1482
  self.idp_vols = {} # yolo
1483
+ self.badcfg1 = False
1480
1484
 
1481
1485
  if self.args.a:
1482
1486
  # list of username:password
@@ -1537,6 +1541,7 @@ class AuthSrv(object):
1537
1541
  zst = [(max(0, len(x) - 2) * " ") + "└" + x[-1] for x in zstt]
1538
1542
  t = "loaded {} config files:\n{}"
1539
1543
  self.log(t.format(len(zst), "\n".join(zst)))
1544
+ cfg_files_loaded = zst
1540
1545
 
1541
1546
  except:
1542
1547
  lns = lns[: self.line_ctr]
@@ -1561,9 +1566,14 @@ class AuthSrv(object):
1561
1566
  if not mount and not self.args.idp_h_usr:
1562
1567
  # -h says our defaults are CWD at root and read/write for everyone
1563
1568
  axs = AXS(["*"], ["*"], None, None)
1564
- if os.path.exists("/z/initcfg"):
1565
- t = "Read-access has been disabled due to failsafe: Docker detected, but the config does not define any volumes. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to all of /w/ by adding the following arguments to the docker container: -v .::rw"
1566
- self.log(t, 1)
1569
+ if self.is_lxc:
1570
+ t = "Read-access has been disabled due to failsafe: Docker detected, but %s. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to all of /w/ by adding the following arguments to the docker container: -v .::rw"
1571
+ if len(cfg_files_loaded) == 1:
1572
+ self.log(t % ("no config-file was provided",), 1)
1573
+ t = "it is strongly recommended to add a config-file instead, for example based on https://github.com/9001/copyparty/blob/hovudstraum/docs/examples/docker/basic-docker-compose/copyparty.conf"
1574
+ self.log(t, 3)
1575
+ else:
1576
+ self.log(t % ("the config does not define any volumes",), 1)
1567
1577
  axs = AXS()
1568
1578
  elif self.args.c:
1569
1579
  t = "Read-access has been disabled due to failsafe: No volumes were defined by the config-file. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to the working-directory by adding the following arguments: -v .::rw"
@@ -1571,7 +1581,7 @@ class AuthSrv(object):
1571
1581
  axs = AXS()
1572
1582
  vfs = VFS(self.log_func, absreal("."), "", "", axs, {})
1573
1583
  if not axs.uread:
1574
- vfs.badcfg1 = True
1584
+ self.badcfg1 = True
1575
1585
  elif "" not in mount:
1576
1586
  # there's volumes but no root; make root inaccessible
1577
1587
  zsd = {"d2d": True, "tcolor": self.args.tcolor}
@@ -2426,6 +2436,7 @@ class AuthSrv(object):
2426
2436
  self.defpw = defpw
2427
2437
  self.grps = grps
2428
2438
  self.iacct = {v: k for k, v in acct.items()}
2439
+ self.cfg_files_loaded = cfg_files_loaded
2429
2440
 
2430
2441
  self.load_sessions()
2431
2442
 
@@ -2556,6 +2567,7 @@ class AuthSrv(object):
2556
2567
  "idxh": int(self.args.ih),
2557
2568
  "themes": self.args.themes,
2558
2569
  "turbolvl": self.args.turbo,
2570
+ "nosubtle": self.args.nosubtle,
2559
2571
  "u2j": self.args.u2j,
2560
2572
  "u2sz": self.args.u2sz,
2561
2573
  "u2ts": vf["u2ts"],
copyparty/httpcli.py CHANGED
@@ -184,11 +184,11 @@ class HttpCli(object):
184
184
  self.log_src = conn.log_src # mypy404
185
185
  self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey
186
186
  self.tls = hasattr(self.s, "cipher")
187
+ self.is_vproxied = bool(self.args.R)
187
188
 
188
189
  # placeholders; assigned by run()
189
190
  self.keepalive = False
190
191
  self.is_https = False
191
- self.is_vproxied = False
192
192
  self.in_hdr_recv = True
193
193
  self.headers = {}
194
194
  self.mode = " " # http verb
@@ -396,7 +396,6 @@ class HttpCli(object):
396
396
  self.bad_xff = True
397
397
  else:
398
398
  self.ip = cli_ip
399
- self.is_vproxied = bool(self.args.R)
400
399
  self.log_src = self.conn.set_rproxy(self.ip)
401
400
  self.host = self.headers.get("x-forwarded-host") or self.host
402
401
  trusted_xff = True
@@ -529,6 +528,7 @@ class HttpCli(object):
529
528
  else:
530
529
  t = "incorrect --rp-loc or webserver config; expected vpath starting with %r but got %r"
531
530
  self.log(t % (self.args.R, vpath), 1)
531
+ self.is_vproxied = False
532
532
 
533
533
  self.ouparam = uparam.copy()
534
534
 
@@ -1228,10 +1228,19 @@ class HttpCli(object):
1228
1228
  else:
1229
1229
  return self.tx_404(True)
1230
1230
  else:
1231
- vfs = self.asrv.vfs
1232
- if vfs.badcfg1:
1233
- t = "<h2>access denied due to failsafe; check server log</h2>"
1234
- html = self.j2s("splash", this=self, msg=t)
1231
+ if (
1232
+ self.asrv.badcfg1
1233
+ and "h" not in self.ouparam
1234
+ and "hc" not in self.ouparam
1235
+ ):
1236
+ zs1 = "copyparty refused to start due to a failsafe: invalid server config; check server log"
1237
+ zs2 = 'you may <a href="/?h">access the controlpanel</a> but nothing will work until you shutdown the copyparty container and %s config-file (or provide the configuration as command-line arguments)'
1238
+ if self.asrv.is_lxc and len(self.asrv.cfg_files_loaded) == 1:
1239
+ zs2 = zs2 % ("add a",)
1240
+ else:
1241
+ zs2 = zs2 % ("fix the",)
1242
+
1243
+ html = self.j2s("msg", h1=zs1, h2=zs2)
1235
1244
  self.reply(html.encode("utf-8", "replace"), 500)
1236
1245
  return True
1237
1246
 
@@ -2576,10 +2585,6 @@ class HttpCli(object):
2576
2585
  x = self.conn.hsrv.broker.ask("up2k.handle_json", body, self.u2fh.aps)
2577
2586
  ret = x.get()
2578
2587
 
2579
- if self.is_vproxied:
2580
- if "purl" in ret:
2581
- ret["purl"] = self.args.SR + ret["purl"]
2582
-
2583
2588
  if self.args.shr and self.vpath.startswith(self.args.shr1):
2584
2589
  # strip common suffix (uploader's folder structure)
2585
2590
  vp_req, vp_vfs = vroots(self.vpath, vjoin(dbv.vpath, vrem))
@@ -2589,6 +2594,10 @@ class HttpCli(object):
2589
2594
  raise Pebkac(500, t % zt)
2590
2595
  ret["purl"] = vp_req + ret["purl"][len(vp_vfs) :]
2591
2596
 
2597
+ if self.is_vproxied:
2598
+ if "purl" in ret:
2599
+ ret["purl"] = self.args.SR + ret["purl"]
2600
+
2592
2601
  ret = json.dumps(ret)
2593
2602
  self.log(ret)
2594
2603
  self.reply(ret.encode("utf-8"), mime="application/json")
Binary file
Binary file
copyparty/web/up2k.js.gz CHANGED
Binary file
copyparty/web/util.js.gz CHANGED
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copyparty
3
- Version: 1.17.1
3
+ Version: 1.17.2
4
4
  Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
5
5
  Author-email: ed <copyparty@ocv.me>
6
6
  License: MIT
@@ -72,6 +72,8 @@ turn almost any device into a file server with resumable uploads/downloads using
72
72
 
73
73
  🎬 **videos:** [upload](https://a.ocv.me/pub/demo/pics-vids/up2k.webm) // [cli-upload](https://a.ocv.me/pub/demo/pics-vids/u2cli.webm) // [race-the-beam](https://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm)
74
74
 
75
+ made in Norway 🇳🇴
76
+
75
77
 
76
78
  ## readme toc
77
79
 
@@ -2576,6 +2578,11 @@ below are some tweaks roughly ordered by usefulness:
2576
2578
 
2577
2579
  when uploading files,
2578
2580
 
2581
+ * when uploading from very fast storage (NVMe SSD) with chrome/firefox, enable `[wasm]` in the `[⚙️] settings` tab to more effectively use all CPU-cores for hashing
2582
+ * don't do this on Safari (runs faster without)
2583
+ * don't do this on older browsers; likely to provoke browser-bugs (browser eats all RAM and crashes)
2584
+ * can be made default-enabled serverside with `--nosubtle 137` (chrome v137+) or `--nosubtle 2` (chrome+firefox)
2585
+
2579
2586
  * chrome is recommended (unfortunately), at least compared to firefox:
2580
2587
  * up to 90% faster when hashing, especially on SSDs
2581
2588
  * up to 40% faster when uploading over extremely fast internets
@@ -1,7 +1,7 @@
1
1
  copyparty/__init__.py,sha256=TnFSStmHlwlRIClWW8jSHxZpt3dl_kN6_pEnqBqh3mE,2638
2
- copyparty/__main__.py,sha256=FZs3IMHTFyeR_-Ftx-M-61_gOwmQCM3bD5b9jo6nrbs,120810
3
- copyparty/__version__.py,sha256=OCUlop4ig08TTAg6PnNAKigP9eAnhhG-82RUqzvbE-w,253
4
- copyparty/authsrv.py,sha256=Z7VH7nO-Yd2X33d8qdMemb6bsAGZqgtaRPFgSAF7rl0,113463
2
+ copyparty/__main__.py,sha256=WRhqmrVEBU1_ejCNxEsn_5rhfrztjA-V04kZnUL_IIs,121255
3
+ copyparty/__version__.py,sha256=3LDY1k8_pNQJ6jOZqNHZXY9IYsLJzzrl1fDwi-GXsDw,253
4
+ copyparty/authsrv.py,sha256=aDPAs4XwchHA2CBor3K6CakXxljQ6AWOY1wTv5Jt2Iw,114130
5
5
  copyparty/broker_mp.py,sha256=QdOXXvV2Xn6J0CysEqyY3GZbqxQMyWnTpnba-a5lMc0,4987
6
6
  copyparty/broker_mpw.py,sha256=PpSS4SK3pItlpfD8OwVr3QmJEPKlUgaf2nuMOozixgU,3347
7
7
  copyparty/broker_thr.py,sha256=fjoYtpSscUA7-nMl4r1n2R7UK3J9lrvLS3rUZ-iJzKQ,1721
@@ -11,7 +11,7 @@ copyparty/cfg.py,sha256=H2p2bHRWk2e4rOx5NZWUVHUcoETy3dtzDkeC8TdosRY,14522
11
11
  copyparty/dxml.py,sha256=vu5uZQtwvwoqnFHbULs2Zh_y2DETu0T-ENpMZ1i2CV4,2505
12
12
  copyparty/fsutil.py,sha256=NC_CJC4TDag399vVDH9_uQfdfpTMwRFLNxERSWhlVvs,4594
13
13
  copyparty/ftpd.py,sha256=G7PApVIFeSzRo4-D-9uRb8NxYgz6nFwTEbrOk1ErYCU,17969
14
- copyparty/httpcli.py,sha256=9yCKMqUD3J2m1tmvx9VIF_RkGi2LvbBHkUx4xxBzVL4,221960
14
+ copyparty/httpcli.py,sha256=ze5Sl51itREFGiYM3p4at32Zol7RSJDHiDKNJel0MFk,222513
15
15
  copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
16
16
  copyparty/httpsrv.py,sha256=pxH_Eh8ElBLvOEDejgpP9Bvk65HNEou-03aYIcgXhrs,18090
17
17
  copyparty/ico.py,sha256=-7QjF_jIxnPo4Vr0oUPksQ_U_Ef0HRsSPm3s71idOz8,3879
@@ -57,7 +57,7 @@ copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8
57
57
  copyparty/web/baguettebox.js.gz,sha256=r2c_hOZV_RTyl4CqWWX14FDWP8nnDVwGkDl4Sfk0rU4,8239
58
58
  copyparty/web/browser.css.gz,sha256=-w-OUbKy0UdL9vYKAC95ayl4MesDAFjqrq60uAxPj4U,11728
59
59
  copyparty/web/browser.html,sha256=auvhLVE_t0aIN0q-nk0zOWFqITgDhroMAAviBNLoFfc,4788
60
- copyparty/web/browser.js.gz,sha256=wdxJBHlMCo1Oh9v1YZsj6rucul7qhQts1-iVfmFPjr8,94639
60
+ copyparty/web/browser.js.gz,sha256=GEQR3qsVSFWoJun_cj-3ljE5ccL6pApSPwzNj36XToc,94957
61
61
  copyparty/web/browser2.html,sha256=NRUZ08GH-e2YcGXcoz0UjYg6JIVF42u4IMX4HHwWTmg,1587
62
62
  copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
63
63
  copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
@@ -79,13 +79,13 @@ copyparty/web/shares.html,sha256=YctvUrKuBYu42kxVylyW2_DEHm7Ik6uHqzfzVZ4N0ac,254
79
79
  copyparty/web/shares.js.gz,sha256=emeY2-wjkh8x1JgaW6ny5fcC7XpZzZzfE1f-sEyntQ4,940
80
80
  copyparty/web/splash.css.gz,sha256=S8_A7JJl71xACRBYGzafeaD82OacW6Fa7oKPiNyrhAs,1087
81
81
  copyparty/web/splash.html,sha256=ouFB1P9g0K-S3LZQtOLfNz3GLXIRjyETkxo9aJZhiYk,6249
82
- copyparty/web/splash.js.gz,sha256=4VqNznN10-bT33IJm3VWzBEJ1s08XZyxFB1TYPUkuAo,2739
82
+ copyparty/web/splash.js.gz,sha256=Qh0KoPWKoJ77cyzOwnhUaCTI5XUoPVV3YJURKklqpBg,2739
83
83
  copyparty/web/svcs.html,sha256=cxgrhX9wD0Z_kvidry3aS9ubuGXYDj2f4ehq1X8T1EA,14227
84
84
  copyparty/web/svcs.js.gz,sha256=lMXEP9W-VlXyANlva4q0ASSxvvHYlE2CrmxGgZXZop0,713
85
85
  copyparty/web/ui.css.gz,sha256=iDjrmq32aDN6l2S5AjCQdKjD6bxmzP6ji2WjM1FjKiU,2819
86
- copyparty/web/up2k.js.gz,sha256=7AKmoJOtFh9tx3Ha7w2F-z69-XZo_LzyR3ilWnBO_D8,24524
87
- copyparty/web/util.js.gz,sha256=Ha2u-RG4HAYOL_QZnt426man0BdhucVNYOQjG5v69AA,15254
88
- copyparty/web/w.hash.js.gz,sha256=JhJagnqIkcKng_hs6otEgzcuQE7keToG_r5dd2o3EfU,1108
86
+ copyparty/web/up2k.js.gz,sha256=vf9Kth2JQ4F-XmWhjcGXqy6gA4eOkg0-EfVjAGQsW5o,24794
87
+ copyparty/web/util.js.gz,sha256=VYXSNNvydhFx-LdPMNjSovMEi-9euYzH4tntPTH8wcg,15255
88
+ copyparty/web/w.hash.js.gz,sha256=cFH6Xo4YRgH9Wr7RmHMSEfpuTmmIvEmzmSvv4RLmyPU,1193
89
89
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
90
  copyparty/web/a/partyfuse.py,sha256=9p5Hpg_IBiSimv7j9kmPhCGpy-FLXSRUOYnLjJ5JifU,28049
91
91
  copyparty/web/a/u2c.py,sha256=auXzLj04dt_lw4H70PhNUK0GjrQEThrybo2-77SLsUg,53165
@@ -109,9 +109,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
109
109
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
110
110
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
111
111
  copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
112
- copyparty-1.17.1.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
- copyparty-1.17.1.dist-info/METADATA,sha256=ApeqUFduyJZInDL2s46CSphR3JT7XsAmrTvoOlLCa20,163313
114
- copyparty-1.17.1.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
115
- copyparty-1.17.1.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
- copyparty-1.17.1.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
- copyparty-1.17.1.dist-info/RECORD,,
112
+ copyparty-1.17.2.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
113
+ copyparty-1.17.2.dist-info/METADATA,sha256=kCbbfcG7Nl-bV-Opp56naw3fdqw3PYchKl5bXlamDXk,163776
114
+ copyparty-1.17.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
115
+ copyparty-1.17.2.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
116
+ copyparty-1.17.2.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
117
+ copyparty-1.17.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5