omdev 0.0.0.dev192__py3-none-any.whl → 0.0.0.dev194__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.
- omdev/pyproject/cli.py +1 -1
- omdev/pyproject/pkg.py +1 -1
- omdev/pyproject/resources/{docker-dev.bash → docker-dev.sh} +4 -4
- omdev/pyproject/resources/{python.bash → python.sh} +4 -3
- omdev/scripts/pyproject.py +262 -254
- {omdev-0.0.0.dev192.dist-info → omdev-0.0.0.dev194.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev192.dist-info → omdev-0.0.0.dev194.dist-info}/RECORD +11 -14
- omdev/toml/__init__.py +0 -1
- omdev/toml/parser.py +0 -826
- omdev/toml/writer.py +0 -114
- {omdev-0.0.0.dev192.dist-info → omdev-0.0.0.dev194.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev192.dist-info → omdev-0.0.0.dev194.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev192.dist-info → omdev-0.0.0.dev194.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev192.dist-info → omdev-0.0.0.dev194.dist-info}/top_level.txt +0 -0
omdev/pyproject/cli.py
CHANGED
@@ -37,12 +37,12 @@ from omlish.argparse.cli import ArgparseCli
|
|
37
37
|
from omlish.argparse.cli import argparse_arg
|
38
38
|
from omlish.argparse.cli import argparse_cmd
|
39
39
|
from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
|
40
|
+
from omlish.formats.toml.parser import toml_loads
|
40
41
|
from omlish.lite.cached import cached_nullary
|
41
42
|
from omlish.lite.check import check
|
42
43
|
from omlish.lite.runtime import check_lite_runtime_version
|
43
44
|
from omlish.logs.standard import configure_standard_logging
|
44
45
|
|
45
|
-
from ..toml.parser import toml_loads
|
46
46
|
from .configs import PyprojectConfig
|
47
47
|
from .configs import PyprojectConfigPreparer
|
48
48
|
from .pkg import BasePyprojectPackageGenerator
|
omdev/pyproject/pkg.py
CHANGED
@@ -35,6 +35,7 @@ import tempfile
|
|
35
35
|
import types
|
36
36
|
import typing as ta
|
37
37
|
|
38
|
+
from omlish.formats.toml.writer import TomlWriter
|
38
39
|
from omlish.lite.cached import cached_nullary
|
39
40
|
from omlish.lite.logs import log
|
40
41
|
from omlish.subprocesses import subprocesses
|
@@ -42,7 +43,6 @@ from omlish.subprocesses import subprocesses
|
|
42
43
|
from ..cexts.magic import CextMagic
|
43
44
|
from ..magic.find import find_magic_files
|
44
45
|
from ..revisions import GitRevisionAdder
|
45
|
-
from ..toml.writer import TomlWriter
|
46
46
|
|
47
47
|
|
48
48
|
#
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/bin/
|
1
|
+
#!/bin/sh
|
2
2
|
set -e
|
3
3
|
|
4
4
|
if [ -t 1 ] ; then
|
@@ -9,7 +9,7 @@ fi
|
|
9
9
|
|
10
10
|
SERVICE_NAME="@PROJECT-dev"
|
11
11
|
if ! [ $# -eq 0 ] ; then
|
12
|
-
if [ "$1"
|
12
|
+
if [ "$1" = "--amd64" ] ; then
|
13
13
|
SERVICE_NAME="$SERVICE_NAME-amd64"
|
14
14
|
shift
|
15
15
|
fi
|
@@ -22,9 +22,9 @@ if [ -z "$CONTAINER_ID" ] ; then
|
|
22
22
|
fi
|
23
23
|
|
24
24
|
if [ -z "$DOCKER_HOST_PLATFORM" ] ; then
|
25
|
-
if [ $(uname)
|
25
|
+
if [ $(uname) = "Linux" ] ; then
|
26
26
|
DOCKER_HOST_PLATFORM=linux
|
27
|
-
elif [ $(uname)
|
27
|
+
elif [ $(uname) = "Darwin" ] ; then
|
28
28
|
DOCKER_HOST_PLATFORM=darwin
|
29
29
|
fi
|
30
30
|
fi
|
@@ -1,15 +1,16 @@
|
|
1
|
-
#!/bin/
|
1
|
+
#!/bin/sh
|
2
2
|
set -e
|
3
3
|
|
4
4
|
if [ -z "${VENV}" ] ; then
|
5
|
-
if [ $(uname)
|
5
|
+
if [ $(uname) = "Linux" ] && (cat /proc/mounts | grep -E '^overlay / .*/(docker|desktop-containerd)/' > /dev/null) ; then
|
6
6
|
VENV=docker
|
7
7
|
else
|
8
8
|
VENV=default
|
9
9
|
fi
|
10
10
|
fi
|
11
11
|
|
12
|
-
|
12
|
+
SCRIPT_DIR=$(dirname "$0")
|
13
|
+
VENV_PYTHON_PATH="$SCRIPT_DIR/.venvs/$VENV/bin/python"
|
13
14
|
if [ -f "$VENV_PYTHON_PATH" ] ; then
|
14
15
|
PYTHON="$VENV_PYTHON_PATH"
|
15
16
|
elif command -v python3 &> /dev/null ; then
|
omdev/scripts/pyproject.py
CHANGED
@@ -88,14 +88,14 @@ VersionCmpLocalType = ta.Union['NegativeInfinityVersionType', _VersionCmpLocalTy
|
|
88
88
|
VersionCmpKey = ta.Tuple[int, ta.Tuple[int, ...], VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpLocalType] # noqa
|
89
89
|
VersionComparisonMethod = ta.Callable[[VersionCmpKey, VersionCmpKey], bool]
|
90
90
|
|
91
|
-
#
|
91
|
+
# ../../omlish/asyncs/asyncio/timeouts.py
|
92
|
+
AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
|
93
|
+
|
94
|
+
# ../../omlish/formats/toml/parser.py
|
92
95
|
TomlParseFloat = ta.Callable[[str], ta.Any]
|
93
96
|
TomlKey = ta.Tuple[str, ...]
|
94
97
|
TomlPos = int # ta.TypeAlias
|
95
98
|
|
96
|
-
# ../../omlish/asyncs/asyncio/timeouts.py
|
97
|
-
AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
|
98
|
-
|
99
99
|
# ../../omlish/lite/cached.py
|
100
100
|
T = ta.TypeVar('T')
|
101
101
|
CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
|
@@ -621,7 +621,258 @@ def canonicalize_version(
|
|
621
621
|
|
622
622
|
|
623
623
|
########################################
|
624
|
-
# ../../
|
624
|
+
# ../../wheelfile.py
|
625
|
+
# https://github.com/pypa/wheel/blob/7bb46d7727e6e89fe56b3c78297b3af2672bbbe2/src/wheel/wheelfile.py
|
626
|
+
# MIT License
|
627
|
+
#
|
628
|
+
# Copyright (c) 2012 Daniel Holth <dholth@fastmail.fm> and contributors
|
629
|
+
#
|
630
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
631
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
632
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
633
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
634
|
+
#
|
635
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
636
|
+
# Software.
|
637
|
+
#
|
638
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
639
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
640
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
641
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
642
|
+
|
643
|
+
|
644
|
+
class WheelError(Exception):
|
645
|
+
pass
|
646
|
+
|
647
|
+
|
648
|
+
# Non-greedy matching of an optional build number may be too clever (more invalid wheel filenames will match). Separate
|
649
|
+
# regex for .dist-info?
|
650
|
+
WHEEL_INFO_RE = re.compile(
|
651
|
+
r'^'
|
652
|
+
r'(?P<namever>(?P<name>[^\s-]+?)-(?P<ver>[^\s-]+?))'
|
653
|
+
r'(-(?P<build>\d[^\s-]*))?-'
|
654
|
+
r'(?P<pyver>[^\s-]+?)-'
|
655
|
+
r'(?P<abi>[^\s-]+?)-'
|
656
|
+
r'(?P<plat>\S+)'
|
657
|
+
r'\.whl$',
|
658
|
+
re.VERBOSE,
|
659
|
+
)
|
660
|
+
|
661
|
+
|
662
|
+
class WheelFile(zipfile.ZipFile):
|
663
|
+
"""
|
664
|
+
A ZipFile derivative class that also reads SHA-256 hashes from .dist-info/RECORD and checks any read files against
|
665
|
+
those.
|
666
|
+
"""
|
667
|
+
|
668
|
+
_default_algorithm = hashlib.sha256
|
669
|
+
|
670
|
+
def __init__(
|
671
|
+
self,
|
672
|
+
file: str,
|
673
|
+
mode: str = 'r', # ta.Literal["r", "w", "x", "a"]
|
674
|
+
compression: int = zipfile.ZIP_DEFLATED,
|
675
|
+
) -> None:
|
676
|
+
basename = os.path.basename(file)
|
677
|
+
self.parsed_filename = WHEEL_INFO_RE.match(basename)
|
678
|
+
if not basename.endswith('.whl') or self.parsed_filename is None:
|
679
|
+
raise WheelError(f'Bad wheel filename {basename!r}')
|
680
|
+
|
681
|
+
super().__init__( # type: ignore
|
682
|
+
file,
|
683
|
+
mode,
|
684
|
+
compression=compression,
|
685
|
+
allowZip64=True,
|
686
|
+
)
|
687
|
+
|
688
|
+
self.dist_info_path = '{}.dist-info'.format(self.parsed_filename.group('namever'))
|
689
|
+
self.record_path = self.dist_info_path + '/RECORD'
|
690
|
+
self._file_hashes: ta.Dict[str, ta.Union[ta.Tuple[None, None], ta.Tuple[int, bytes]]] = {}
|
691
|
+
self._file_sizes: ta.Dict[str, int] = {}
|
692
|
+
|
693
|
+
if mode == 'r':
|
694
|
+
# Ignore RECORD and any embedded wheel signatures
|
695
|
+
self._file_hashes[self.record_path] = None, None
|
696
|
+
self._file_hashes[self.record_path + '.jws'] = None, None
|
697
|
+
self._file_hashes[self.record_path + '.p7s'] = None, None
|
698
|
+
|
699
|
+
# Fill in the expected hashes by reading them from RECORD
|
700
|
+
try:
|
701
|
+
record = self.open(self.record_path)
|
702
|
+
except KeyError:
|
703
|
+
raise WheelError(f'Missing {self.record_path} file') from None
|
704
|
+
|
705
|
+
with record:
|
706
|
+
for line in csv.reader(io.TextIOWrapper(record, newline='', encoding='utf-8')):
|
707
|
+
path, hash_sum, size = line
|
708
|
+
if not hash_sum:
|
709
|
+
continue
|
710
|
+
|
711
|
+
algorithm, hash_sum = hash_sum.split('=')
|
712
|
+
try:
|
713
|
+
hashlib.new(algorithm)
|
714
|
+
except ValueError:
|
715
|
+
raise WheelError(f'Unsupported hash algorithm: {algorithm}') from None
|
716
|
+
|
717
|
+
if algorithm.lower() in {'md5', 'sha1'}:
|
718
|
+
raise WheelError(f'Weak hash algorithm ({algorithm}) is not permitted by PEP 427')
|
719
|
+
|
720
|
+
self._file_hashes[path] = ( # type: ignore
|
721
|
+
algorithm,
|
722
|
+
self._urlsafe_b64decode(hash_sum.encode('ascii')),
|
723
|
+
)
|
724
|
+
|
725
|
+
@staticmethod
|
726
|
+
def _urlsafe_b64encode(data: bytes) -> bytes:
|
727
|
+
"""urlsafe_b64encode without padding"""
|
728
|
+
return base64.urlsafe_b64encode(data).rstrip(b'=')
|
729
|
+
|
730
|
+
@staticmethod
|
731
|
+
def _urlsafe_b64decode(data: bytes) -> bytes:
|
732
|
+
"""urlsafe_b64decode without padding"""
|
733
|
+
pad = b'=' * (4 - (len(data) & 3))
|
734
|
+
return base64.urlsafe_b64decode(data + pad)
|
735
|
+
|
736
|
+
def open( # type: ignore # noqa
|
737
|
+
self,
|
738
|
+
name_or_info: ta.Union[str, zipfile.ZipInfo],
|
739
|
+
mode: str = 'r', # ta.Literal["r", "w"]
|
740
|
+
pwd: ta.Optional[bytes] = None,
|
741
|
+
) -> ta.IO[bytes]:
|
742
|
+
def _update_crc(newdata: bytes) -> None:
|
743
|
+
eof = ef._eof # type: ignore # noqa
|
744
|
+
update_crc_orig(newdata)
|
745
|
+
running_hash.update(newdata)
|
746
|
+
if eof and running_hash.digest() != expected_hash:
|
747
|
+
raise WheelError(f"Hash mismatch for file '{ef_name}'")
|
748
|
+
|
749
|
+
ef_name = name_or_info.filename if isinstance(name_or_info, zipfile.ZipInfo) else name_or_info
|
750
|
+
if (
|
751
|
+
mode == 'r'
|
752
|
+
and not ef_name.endswith('/')
|
753
|
+
and ef_name not in self._file_hashes
|
754
|
+
):
|
755
|
+
raise WheelError(f"No hash found for file '{ef_name}'")
|
756
|
+
|
757
|
+
ef = super().open(name_or_info, mode, pwd) # noqa
|
758
|
+
if mode == 'r' and not ef_name.endswith('/'):
|
759
|
+
algorithm, expected_hash = self._file_hashes[ef_name]
|
760
|
+
if expected_hash is not None:
|
761
|
+
# Monkey patch the _update_crc method to also check for the hash from RECORD
|
762
|
+
running_hash = hashlib.new(algorithm) # type: ignore
|
763
|
+
update_crc_orig, ef._update_crc = ef._update_crc, _update_crc # type: ignore # noqa
|
764
|
+
|
765
|
+
return ef
|
766
|
+
|
767
|
+
def write_files(self, base_dir: str) -> None:
|
768
|
+
deferred: list[tuple[str, str]] = []
|
769
|
+
for root, dirnames, filenames in os.walk(base_dir):
|
770
|
+
# Sort the directory names so that `os.walk` will walk them in a defined order on the next iteration.
|
771
|
+
dirnames.sort()
|
772
|
+
for name in sorted(filenames):
|
773
|
+
path = os.path.normpath(os.path.join(root, name))
|
774
|
+
if os.path.isfile(path):
|
775
|
+
arcname = os.path.relpath(path, base_dir).replace(os.path.sep, '/')
|
776
|
+
if arcname == self.record_path:
|
777
|
+
pass
|
778
|
+
elif root.endswith('.dist-info'):
|
779
|
+
deferred.append((path, arcname))
|
780
|
+
else:
|
781
|
+
self.write(path, arcname)
|
782
|
+
|
783
|
+
deferred.sort()
|
784
|
+
for path, arcname in deferred:
|
785
|
+
self.write(path, arcname)
|
786
|
+
|
787
|
+
def write( # type: ignore # noqa
|
788
|
+
self,
|
789
|
+
filename: str,
|
790
|
+
arcname: ta.Optional[str] = None,
|
791
|
+
compress_type: ta.Optional[int] = None,
|
792
|
+
) -> None:
|
793
|
+
with open(filename, 'rb') as f:
|
794
|
+
st = os.fstat(f.fileno())
|
795
|
+
data = f.read()
|
796
|
+
|
797
|
+
zinfo = zipfile.ZipInfo(
|
798
|
+
arcname or filename,
|
799
|
+
date_time=self._get_zipinfo_datetime(st.st_mtime),
|
800
|
+
)
|
801
|
+
zinfo.external_attr = (stat.S_IMODE(st.st_mode) | stat.S_IFMT(st.st_mode)) << 16
|
802
|
+
zinfo.compress_type = compress_type or self.compression
|
803
|
+
self.writestr(zinfo, data, compress_type)
|
804
|
+
|
805
|
+
_MINIMUM_TIMESTAMP = 315532800 # 1980-01-01 00:00:00 UTC
|
806
|
+
|
807
|
+
@classmethod
|
808
|
+
def _get_zipinfo_datetime(cls, timestamp: ta.Optional[float] = None) -> ta.Any:
|
809
|
+
# Some applications need reproducible .whl files, but they can't do this without forcing the timestamp of the
|
810
|
+
# individual ZipInfo objects. See issue #143.
|
811
|
+
timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', timestamp or time.time()))
|
812
|
+
timestamp = max(timestamp, cls._MINIMUM_TIMESTAMP)
|
813
|
+
return time.gmtime(timestamp)[0:6]
|
814
|
+
|
815
|
+
def writestr( # type: ignore # noqa
|
816
|
+
self,
|
817
|
+
zinfo_or_arcname: ta.Union[str, zipfile.ZipInfo],
|
818
|
+
data: ta.Any, # SizedBuffer | str,
|
819
|
+
compress_type: ta.Optional[int] = None,
|
820
|
+
) -> None:
|
821
|
+
if isinstance(zinfo_or_arcname, str):
|
822
|
+
zinfo_or_arcname = zipfile.ZipInfo(
|
823
|
+
zinfo_or_arcname,
|
824
|
+
date_time=self._get_zipinfo_datetime(),
|
825
|
+
)
|
826
|
+
zinfo_or_arcname.compress_type = self.compression
|
827
|
+
zinfo_or_arcname.external_attr = (0o664 | stat.S_IFREG) << 16
|
828
|
+
|
829
|
+
if isinstance(data, str):
|
830
|
+
data = data.encode('utf-8')
|
831
|
+
|
832
|
+
super().writestr(zinfo_or_arcname, data, compress_type)
|
833
|
+
fname = (
|
834
|
+
zinfo_or_arcname.filename
|
835
|
+
if isinstance(zinfo_or_arcname, zipfile.ZipInfo)
|
836
|
+
else zinfo_or_arcname
|
837
|
+
)
|
838
|
+
if fname != self.record_path:
|
839
|
+
hash_ = self._default_algorithm(data) # type: ignore
|
840
|
+
self._file_hashes[fname] = ( # type: ignore
|
841
|
+
hash_.name,
|
842
|
+
self._urlsafe_b64encode(hash_.digest()).decode('ascii'),
|
843
|
+
)
|
844
|
+
self._file_sizes[fname] = len(data)
|
845
|
+
|
846
|
+
def close(self) -> None:
|
847
|
+
# Write RECORD
|
848
|
+
if self.fp is not None and self.mode == 'w' and self._file_hashes:
|
849
|
+
data = io.StringIO()
|
850
|
+
writer = csv.writer(data, delimiter=',', quotechar='"', lineterminator='\n')
|
851
|
+
writer.writerows((
|
852
|
+
(fname, algorithm + '=' + hash_, self._file_sizes[fname]) # type: ignore
|
853
|
+
for fname, (algorithm, hash_) in self._file_hashes.items()
|
854
|
+
))
|
855
|
+
writer.writerow((format(self.record_path), '', ''))
|
856
|
+
self.writestr(self.record_path, data.getvalue())
|
857
|
+
|
858
|
+
super().close()
|
859
|
+
|
860
|
+
|
861
|
+
########################################
|
862
|
+
# ../../../omlish/asyncs/asyncio/timeouts.py
|
863
|
+
|
864
|
+
|
865
|
+
def asyncio_maybe_timeout(
|
866
|
+
fut: AwaitableT,
|
867
|
+
timeout: ta.Optional[float] = None,
|
868
|
+
) -> AwaitableT:
|
869
|
+
if timeout is not None:
|
870
|
+
fut = asyncio.wait_for(fut, timeout) # type: ignore
|
871
|
+
return fut
|
872
|
+
|
873
|
+
|
874
|
+
########################################
|
875
|
+
# ../../../omlish/formats/toml/parser.py
|
625
876
|
# SPDX-License-Identifier: MIT
|
626
877
|
# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
|
627
878
|
# Licensed to PSF under a Contributor Agreement.
|
@@ -1439,7 +1690,7 @@ def toml_make_safe_parse_float(parse_float: TomlParseFloat) -> TomlParseFloat:
|
|
1439
1690
|
|
1440
1691
|
|
1441
1692
|
########################################
|
1442
|
-
#
|
1693
|
+
# ../../../omlish/formats/toml/writer.py
|
1443
1694
|
|
1444
1695
|
|
1445
1696
|
class TomlWriter:
|
@@ -1552,256 +1803,13 @@ class TomlWriter:
|
|
1552
1803
|
else:
|
1553
1804
|
raise TypeError(obj)
|
1554
1805
|
|
1555
|
-
|
1556
|
-
########################################
|
1557
|
-
# ../../wheelfile.py
|
1558
|
-
# https://github.com/pypa/wheel/blob/7bb46d7727e6e89fe56b3c78297b3af2672bbbe2/src/wheel/wheelfile.py
|
1559
|
-
# MIT License
|
1560
|
-
#
|
1561
|
-
# Copyright (c) 2012 Daniel Holth <dholth@fastmail.fm> and contributors
|
1562
|
-
#
|
1563
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
1564
|
-
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
1565
|
-
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
1566
|
-
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
1567
|
-
#
|
1568
|
-
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
1569
|
-
# Software.
|
1570
|
-
#
|
1571
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
1572
|
-
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
1573
|
-
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
1574
|
-
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1575
|
-
|
1576
|
-
|
1577
|
-
class WheelError(Exception):
|
1578
|
-
pass
|
1579
|
-
|
1580
|
-
|
1581
|
-
# Non-greedy matching of an optional build number may be too clever (more invalid wheel filenames will match). Separate
|
1582
|
-
# regex for .dist-info?
|
1583
|
-
WHEEL_INFO_RE = re.compile(
|
1584
|
-
r'^'
|
1585
|
-
r'(?P<namever>(?P<name>[^\s-]+?)-(?P<ver>[^\s-]+?))'
|
1586
|
-
r'(-(?P<build>\d[^\s-]*))?-'
|
1587
|
-
r'(?P<pyver>[^\s-]+?)-'
|
1588
|
-
r'(?P<abi>[^\s-]+?)-'
|
1589
|
-
r'(?P<plat>\S+)'
|
1590
|
-
r'\.whl$',
|
1591
|
-
re.VERBOSE,
|
1592
|
-
)
|
1593
|
-
|
1594
|
-
|
1595
|
-
class WheelFile(zipfile.ZipFile):
|
1596
|
-
"""
|
1597
|
-
A ZipFile derivative class that also reads SHA-256 hashes from .dist-info/RECORD and checks any read files against
|
1598
|
-
those.
|
1599
|
-
"""
|
1600
|
-
|
1601
|
-
_default_algorithm = hashlib.sha256
|
1602
|
-
|
1603
|
-
def __init__(
|
1604
|
-
self,
|
1605
|
-
file: str,
|
1606
|
-
mode: str = 'r', # ta.Literal["r", "w", "x", "a"]
|
1607
|
-
compression: int = zipfile.ZIP_DEFLATED,
|
1608
|
-
) -> None:
|
1609
|
-
basename = os.path.basename(file)
|
1610
|
-
self.parsed_filename = WHEEL_INFO_RE.match(basename)
|
1611
|
-
if not basename.endswith('.whl') or self.parsed_filename is None:
|
1612
|
-
raise WheelError(f'Bad wheel filename {basename!r}')
|
1613
|
-
|
1614
|
-
super().__init__( # type: ignore
|
1615
|
-
file,
|
1616
|
-
mode,
|
1617
|
-
compression=compression,
|
1618
|
-
allowZip64=True,
|
1619
|
-
)
|
1620
|
-
|
1621
|
-
self.dist_info_path = '{}.dist-info'.format(self.parsed_filename.group('namever'))
|
1622
|
-
self.record_path = self.dist_info_path + '/RECORD'
|
1623
|
-
self._file_hashes: ta.Dict[str, ta.Union[ta.Tuple[None, None], ta.Tuple[int, bytes]]] = {}
|
1624
|
-
self._file_sizes: ta.Dict[str, int] = {}
|
1625
|
-
|
1626
|
-
if mode == 'r':
|
1627
|
-
# Ignore RECORD and any embedded wheel signatures
|
1628
|
-
self._file_hashes[self.record_path] = None, None
|
1629
|
-
self._file_hashes[self.record_path + '.jws'] = None, None
|
1630
|
-
self._file_hashes[self.record_path + '.p7s'] = None, None
|
1631
|
-
|
1632
|
-
# Fill in the expected hashes by reading them from RECORD
|
1633
|
-
try:
|
1634
|
-
record = self.open(self.record_path)
|
1635
|
-
except KeyError:
|
1636
|
-
raise WheelError(f'Missing {self.record_path} file') from None
|
1637
|
-
|
1638
|
-
with record:
|
1639
|
-
for line in csv.reader(io.TextIOWrapper(record, newline='', encoding='utf-8')):
|
1640
|
-
path, hash_sum, size = line
|
1641
|
-
if not hash_sum:
|
1642
|
-
continue
|
1643
|
-
|
1644
|
-
algorithm, hash_sum = hash_sum.split('=')
|
1645
|
-
try:
|
1646
|
-
hashlib.new(algorithm)
|
1647
|
-
except ValueError:
|
1648
|
-
raise WheelError(f'Unsupported hash algorithm: {algorithm}') from None
|
1649
|
-
|
1650
|
-
if algorithm.lower() in {'md5', 'sha1'}:
|
1651
|
-
raise WheelError(f'Weak hash algorithm ({algorithm}) is not permitted by PEP 427')
|
1652
|
-
|
1653
|
-
self._file_hashes[path] = ( # type: ignore
|
1654
|
-
algorithm,
|
1655
|
-
self._urlsafe_b64decode(hash_sum.encode('ascii')),
|
1656
|
-
)
|
1657
|
-
|
1658
|
-
@staticmethod
|
1659
|
-
def _urlsafe_b64encode(data: bytes) -> bytes:
|
1660
|
-
"""urlsafe_b64encode without padding"""
|
1661
|
-
return base64.urlsafe_b64encode(data).rstrip(b'=')
|
1662
|
-
|
1663
|
-
@staticmethod
|
1664
|
-
def _urlsafe_b64decode(data: bytes) -> bytes:
|
1665
|
-
"""urlsafe_b64decode without padding"""
|
1666
|
-
pad = b'=' * (4 - (len(data) & 3))
|
1667
|
-
return base64.urlsafe_b64decode(data + pad)
|
1668
|
-
|
1669
|
-
def open( # type: ignore # noqa
|
1670
|
-
self,
|
1671
|
-
name_or_info: ta.Union[str, zipfile.ZipInfo],
|
1672
|
-
mode: str = 'r', # ta.Literal["r", "w"]
|
1673
|
-
pwd: ta.Optional[bytes] = None,
|
1674
|
-
) -> ta.IO[bytes]:
|
1675
|
-
def _update_crc(newdata: bytes) -> None:
|
1676
|
-
eof = ef._eof # type: ignore # noqa
|
1677
|
-
update_crc_orig(newdata)
|
1678
|
-
running_hash.update(newdata)
|
1679
|
-
if eof and running_hash.digest() != expected_hash:
|
1680
|
-
raise WheelError(f"Hash mismatch for file '{ef_name}'")
|
1681
|
-
|
1682
|
-
ef_name = name_or_info.filename if isinstance(name_or_info, zipfile.ZipInfo) else name_or_info
|
1683
|
-
if (
|
1684
|
-
mode == 'r'
|
1685
|
-
and not ef_name.endswith('/')
|
1686
|
-
and ef_name not in self._file_hashes
|
1687
|
-
):
|
1688
|
-
raise WheelError(f"No hash found for file '{ef_name}'")
|
1689
|
-
|
1690
|
-
ef = super().open(name_or_info, mode, pwd) # noqa
|
1691
|
-
if mode == 'r' and not ef_name.endswith('/'):
|
1692
|
-
algorithm, expected_hash = self._file_hashes[ef_name]
|
1693
|
-
if expected_hash is not None:
|
1694
|
-
# Monkey patch the _update_crc method to also check for the hash from RECORD
|
1695
|
-
running_hash = hashlib.new(algorithm) # type: ignore
|
1696
|
-
update_crc_orig, ef._update_crc = ef._update_crc, _update_crc # type: ignore # noqa
|
1697
|
-
|
1698
|
-
return ef
|
1699
|
-
|
1700
|
-
def write_files(self, base_dir: str) -> None:
|
1701
|
-
deferred: list[tuple[str, str]] = []
|
1702
|
-
for root, dirnames, filenames in os.walk(base_dir):
|
1703
|
-
# Sort the directory names so that `os.walk` will walk them in a defined order on the next iteration.
|
1704
|
-
dirnames.sort()
|
1705
|
-
for name in sorted(filenames):
|
1706
|
-
path = os.path.normpath(os.path.join(root, name))
|
1707
|
-
if os.path.isfile(path):
|
1708
|
-
arcname = os.path.relpath(path, base_dir).replace(os.path.sep, '/')
|
1709
|
-
if arcname == self.record_path:
|
1710
|
-
pass
|
1711
|
-
elif root.endswith('.dist-info'):
|
1712
|
-
deferred.append((path, arcname))
|
1713
|
-
else:
|
1714
|
-
self.write(path, arcname)
|
1715
|
-
|
1716
|
-
deferred.sort()
|
1717
|
-
for path, arcname in deferred:
|
1718
|
-
self.write(path, arcname)
|
1719
|
-
|
1720
|
-
def write( # type: ignore # noqa
|
1721
|
-
self,
|
1722
|
-
filename: str,
|
1723
|
-
arcname: ta.Optional[str] = None,
|
1724
|
-
compress_type: ta.Optional[int] = None,
|
1725
|
-
) -> None:
|
1726
|
-
with open(filename, 'rb') as f:
|
1727
|
-
st = os.fstat(f.fileno())
|
1728
|
-
data = f.read()
|
1729
|
-
|
1730
|
-
zinfo = zipfile.ZipInfo(
|
1731
|
-
arcname or filename,
|
1732
|
-
date_time=self._get_zipinfo_datetime(st.st_mtime),
|
1733
|
-
)
|
1734
|
-
zinfo.external_attr = (stat.S_IMODE(st.st_mode) | stat.S_IFMT(st.st_mode)) << 16
|
1735
|
-
zinfo.compress_type = compress_type or self.compression
|
1736
|
-
self.writestr(zinfo, data, compress_type)
|
1737
|
-
|
1738
|
-
_MINIMUM_TIMESTAMP = 315532800 # 1980-01-01 00:00:00 UTC
|
1806
|
+
#
|
1739
1807
|
|
1740
1808
|
@classmethod
|
1741
|
-
def
|
1742
|
-
|
1743
|
-
|
1744
|
-
|
1745
|
-
timestamp = max(timestamp, cls._MINIMUM_TIMESTAMP)
|
1746
|
-
return time.gmtime(timestamp)[0:6]
|
1747
|
-
|
1748
|
-
def writestr( # type: ignore # noqa
|
1749
|
-
self,
|
1750
|
-
zinfo_or_arcname: ta.Union[str, zipfile.ZipInfo],
|
1751
|
-
data: ta.Any, # SizedBuffer | str,
|
1752
|
-
compress_type: ta.Optional[int] = None,
|
1753
|
-
) -> None:
|
1754
|
-
if isinstance(zinfo_or_arcname, str):
|
1755
|
-
zinfo_or_arcname = zipfile.ZipInfo(
|
1756
|
-
zinfo_or_arcname,
|
1757
|
-
date_time=self._get_zipinfo_datetime(),
|
1758
|
-
)
|
1759
|
-
zinfo_or_arcname.compress_type = self.compression
|
1760
|
-
zinfo_or_arcname.external_attr = (0o664 | stat.S_IFREG) << 16
|
1761
|
-
|
1762
|
-
if isinstance(data, str):
|
1763
|
-
data = data.encode('utf-8')
|
1764
|
-
|
1765
|
-
super().writestr(zinfo_or_arcname, data, compress_type)
|
1766
|
-
fname = (
|
1767
|
-
zinfo_or_arcname.filename
|
1768
|
-
if isinstance(zinfo_or_arcname, zipfile.ZipInfo)
|
1769
|
-
else zinfo_or_arcname
|
1770
|
-
)
|
1771
|
-
if fname != self.record_path:
|
1772
|
-
hash_ = self._default_algorithm(data) # type: ignore
|
1773
|
-
self._file_hashes[fname] = ( # type: ignore
|
1774
|
-
hash_.name,
|
1775
|
-
self._urlsafe_b64encode(hash_.digest()).decode('ascii'),
|
1776
|
-
)
|
1777
|
-
self._file_sizes[fname] = len(data)
|
1778
|
-
|
1779
|
-
def close(self) -> None:
|
1780
|
-
# Write RECORD
|
1781
|
-
if self.fp is not None and self.mode == 'w' and self._file_hashes:
|
1782
|
-
data = io.StringIO()
|
1783
|
-
writer = csv.writer(data, delimiter=',', quotechar='"', lineterminator='\n')
|
1784
|
-
writer.writerows((
|
1785
|
-
(fname, algorithm + '=' + hash_, self._file_sizes[fname]) # type: ignore
|
1786
|
-
for fname, (algorithm, hash_) in self._file_hashes.items()
|
1787
|
-
))
|
1788
|
-
writer.writerow((format(self.record_path), '', ''))
|
1789
|
-
self.writestr(self.record_path, data.getvalue())
|
1790
|
-
|
1791
|
-
super().close()
|
1792
|
-
|
1793
|
-
|
1794
|
-
########################################
|
1795
|
-
# ../../../omlish/asyncs/asyncio/timeouts.py
|
1796
|
-
|
1797
|
-
|
1798
|
-
def asyncio_maybe_timeout(
|
1799
|
-
fut: AwaitableT,
|
1800
|
-
timeout: ta.Optional[float] = None,
|
1801
|
-
) -> AwaitableT:
|
1802
|
-
if timeout is not None:
|
1803
|
-
fut = asyncio.wait_for(fut, timeout) # type: ignore
|
1804
|
-
return fut
|
1809
|
+
def write_str(cls, obj: ta.Any) -> str:
|
1810
|
+
out = io.StringIO()
|
1811
|
+
cls(out).write_value(obj)
|
1812
|
+
return out.getvalue()
|
1805
1813
|
|
1806
1814
|
|
1807
1815
|
########################################
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: omdev
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev194
|
4
4
|
Summary: omdev
|
5
5
|
Author: wrmsr
|
6
6
|
License: BSD-3-Clause
|
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Operating System :: POSIX
|
13
13
|
Requires-Python: >=3.12
|
14
14
|
License-File: LICENSE
|
15
|
-
Requires-Dist: omlish==0.0.0.
|
15
|
+
Requires-Dist: omlish==0.0.0.dev194
|
16
16
|
Provides-Extra: all
|
17
17
|
Requires-Dist: black~=24.10; extra == "all"
|
18
18
|
Requires-Dist: pycparser~=2.22; extra == "all"
|