omdev 0.0.0.dev192__py3-none-any.whl → 0.0.0.dev194__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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/bash
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" == "--amd64" ] ; then
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) == "Linux" ] ; then
25
+ if [ $(uname) = "Linux" ] ; then
26
26
  DOCKER_HOST_PLATFORM=linux
27
- elif [ $(uname) == "Darwin" ] ; then
27
+ elif [ $(uname) = "Darwin" ] ; then
28
28
  DOCKER_HOST_PLATFORM=darwin
29
29
  fi
30
30
  fi
@@ -1,15 +1,16 @@
1
- #!/bin/bash
1
+ #!/bin/sh
2
2
  set -e
3
3
 
4
4
  if [ -z "${VENV}" ] ; then
5
- if [ $(uname) == "Linux" ] && (cat /proc/mounts | egrep '^overlay / .*/(docker|desktop-containerd)/' > /dev/null) ; then
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
- VENV_PYTHON_PATH="${BASH_SOURCE%/*}/.venvs/$VENV/bin/python"
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
@@ -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
- # ../toml/parser.py
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
- # ../../toml/parser.py
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
- # ../../toml/writer.py
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 _get_zipinfo_datetime(cls, timestamp: ta.Optional[float] = None) -> ta.Any:
1742
- # Some applications need reproducible .whl files, but they can't do this without forcing the timestamp of the
1743
- # individual ZipInfo objects. See issue #143.
1744
- timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', timestamp or time.time()))
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.dev192
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.dev192
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"