@@ -748,17 +748,44 @@ def shim(
748
748
:test:
749
749
:filepath: my_shim_setup.py
750
750
751
+ """
752
+ Ensure we can parse YAML manifests. Do this by downloading PyYAML to a
753
+ cache directory and doing a direct import, or downloading to a tempdir
754
+ every execution. If it's already installed, great. This should be a last
755
+ resort.
756
+ """
757
+ import re
758
+ import sys
751
759
import pathlib
752
760
import tempfile
761
+ import platform
753
762
import zipimport
763
+ import contextlib
754
764
import urllib.request
755
765
756
766
import shim
757
767
758
- # For the sake of the example assume you are unable to preinstall
759
- # anything into the environment the shim run in (common reason why we
760
- # use a shim).
761
- PYYAML_URL: str = "https://files.pythonhosted.org/packages/eb/5f/6e6fe6904e1a9c67bc2ca5629a69e7a5a0b17f079da838bab98a1e548b25/PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl"
768
+ # In the event that PyYAML is not installed this installs it locally
769
+ # (relative to this file)
770
+ PYYAML_URL: str = "https://pypi.org/simple/pyyaml/"
771
+ CACHE: pathlib.Path = pathlib.Path(__file__).resolve().parent.joinpath(
772
+ ".cache", "wheels",
773
+ )
774
+
775
+
776
+ @contextlib.contextmanager
777
+ def cache_dir():
778
+ """
779
+ Try to cache locally if possible, create a directory to store wheels in
780
+ relative to this file. If that fails, use a tempdir.
781
+ """
782
+ try:
783
+ CACHE.mkdir(parents=True, exist_ok=True)
784
+ yield CACHE
785
+ except:
786
+ with tempfile.TemporaryDirectory() as tempdir:
787
+ yield tempdir
788
+
762
789
763
790
def setup_shim_func(parsers, next_phase_parsers, **kwargs):
764
791
# Declare another parser
@@ -772,16 +799,36 @@ def setup_shim_func(parsers, next_phase_parsers, **kwargs):
772
799
# Add the parser
773
800
next_phase_parsers[(parser.format, parser.version, parser.name)] = parser
774
801
775
- # Create a temporary directory to hold the pi
776
- with tempfile.TemporaryDirectory() as tempdir:
802
+ # Download PyYAML and load the parser if not preloaded
803
+ if "yaml" not in parsers:
804
+ return
805
+
806
+ # Use ether the cache or a temporary directory to hold the package
807
+ with cache_dir() as package_dir:
777
808
# Path to wheel on disk
778
- wheel_path = pathlib.Path(tempdir, "package.whl")
779
- # Download the wheel
780
- with urllib.request.urlopen(PYYAML_URL) as response:
781
- wheel_path.write_bytes(response.read())
782
- # You'll need to change the wheel for this code to work
783
- if True:
784
- return
809
+ wheel_path = pathlib.Path(package_dir, "package.whl")
810
+ # Download if not cached
811
+ if not wheel_path.exists():
812
+ # Find the correct package
813
+ with urllib.request.urlopen(PYYAML_URL) as response:
814
+ links = re.findall(r"(https://.*.whl)", response.read().decode())
815
+ # Search backwards because last links are the most recent package versions
816
+ end_href = '" '
817
+ links = [
818
+ link[: link.index(end_href)]
819
+ for link in links[::-1]
820
+ if (
821
+ end_href in link
822
+ and f"cp{sys.version_info.major}{sys.version_info.minor}" in link
823
+ and platform.machine() in link
824
+ and {"darwin": "macos"}.get(sys.platform, sys.platform) in link
825
+ )
826
+ ]
827
+ # Grab the most recent applicable wheel link
828
+ wheel_url = links[0]
829
+ # Download the wheel
830
+ with urllib.request.urlopen(wheel_url) as response:
831
+ wheel_path.write_bytes(response.read())
785
832
# Load the module from the downloaded wheel
786
833
yaml = zipimport.zipimporter(str(wheel_path)).load_module("yaml")
787
834
# Setup the parser for use by the shim
@@ -918,14 +965,6 @@ def make_parser():
918
965
description = __doc__ ,
919
966
)
920
967
921
- # TODO Addition of remotely loadable PyPi zip packages? Perhaps it's easier
922
- # if we allow for the importing of a setup file with a setup function in it
923
- # that is called with the shim execution context (the arguments to shim()).
924
- # This is useful because often we find ourselves in a situation where the
925
- # reason we are using the shim is that we have no other dependencies
926
- # installed other than Python itself. Adding the ability to add more parsers
927
- # via the importing of another file which can then import or implement
928
- # parsers would be good.
929
968
parser .add_argument (
930
969
"-l" , "--lockdown" , action = "store_true" , default = False ,
931
970
)
0 commit comments