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 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"