kopia lustrzana https://github.com/jupyterhub/repo2docker
Move runtime.txt handling into base class
This will allow it to be used for Python (and other languages) in futurepull/1428/head
rodzic
e795060aec
commit
cab6fce923
|
@ -1,3 +1,4 @@
|
|||
import datetime
|
||||
import hashlib
|
||||
import io
|
||||
import logging
|
||||
|
@ -750,3 +751,41 @@ class BaseImage(BuildPack):
|
|||
# the only path evaluated at container start time rather than build time
|
||||
return os.path.join("${REPO_DIR}", start)
|
||||
return None
|
||||
|
||||
@property
|
||||
def runtime_info(self):
|
||||
"""
|
||||
Return parsed contents of runtime.txt
|
||||
|
||||
Returns (runtime, version, date), tuple components may be None.
|
||||
Returns (None, None, None) if runtime.txt not found.
|
||||
"""
|
||||
if hasattr(self, "_runtime"):
|
||||
return self._runtime
|
||||
|
||||
self._runtime = (None, None, None)
|
||||
|
||||
runtime_path = self.binder_path("runtime.txt")
|
||||
try:
|
||||
with open(runtime_path) as f:
|
||||
runtime_txt = f.read().strip()
|
||||
except FileNotFoundError:
|
||||
return self._runtime
|
||||
|
||||
runtime = None
|
||||
version = None
|
||||
date = None
|
||||
|
||||
parts = runtime_txt.split("-", 2)
|
||||
if len(parts) > 0 and parts[0]:
|
||||
runtime = parts[0]
|
||||
if len(parts) > 1 and parts[1]:
|
||||
version = parts[1]
|
||||
if len(parts) > 2 and parts[2]:
|
||||
date = parts[2]
|
||||
if not re.match(r"\d\d\d\d-\d\d-\d\d", date):
|
||||
raise ValueError(f"Invalid date, expected YYYY-MM-DD: {date}")
|
||||
date = datetime.datetime.fromisoformat(date).date()
|
||||
|
||||
self._runtime = (runtime, version, date)
|
||||
return self._runtime
|
||||
|
|
|
@ -187,12 +187,9 @@ class PipfileBuildPack(CondaBuildPack):
|
|||
def detect(self):
|
||||
"""Check if current repo should be built with the Pipfile buildpack."""
|
||||
# first make sure python is not explicitly unwanted
|
||||
runtime_txt = self.binder_path("runtime.txt")
|
||||
if os.path.exists(runtime_txt):
|
||||
with open(runtime_txt) as f:
|
||||
runtime = f.read().strip()
|
||||
if not runtime.startswith("python-"):
|
||||
return False
|
||||
runtime = self.runtime_info[0]
|
||||
if runtime and runtime != "python":
|
||||
return False
|
||||
|
||||
pipfile = self.binder_path("Pipfile")
|
||||
pipfile_lock = self.binder_path("Pipfile.lock")
|
||||
|
|
|
@ -15,14 +15,10 @@ class PythonBuildPack(CondaBuildPack):
|
|||
if hasattr(self, "_python_version"):
|
||||
return self._python_version
|
||||
|
||||
try:
|
||||
with open(self.binder_path("runtime.txt")) as f:
|
||||
runtime = f.read().strip()
|
||||
except FileNotFoundError:
|
||||
runtime = ""
|
||||
runtime, version, _ = self.runtime_info
|
||||
|
||||
if not runtime.startswith("python-"):
|
||||
# not a Python runtime (e.g. R, which subclasses this)
|
||||
if runtime != "python" or not version:
|
||||
# Either not specified, or not a Python runtime (e.g. R, which subclasses this)
|
||||
# use the default Python
|
||||
self._python_version = self.major_pythons["3"]
|
||||
self.log.warning(
|
||||
|
@ -30,7 +26,7 @@ class PythonBuildPack(CondaBuildPack):
|
|||
)
|
||||
return self._python_version
|
||||
|
||||
py_version_info = runtime.split("-", 1)[1].split(".")
|
||||
py_version_info = version.split(".")
|
||||
py_version = ""
|
||||
if len(py_version_info) == 1:
|
||||
py_version = self.major_pythons[py_version_info[0]]
|
||||
|
@ -138,16 +134,10 @@ class PythonBuildPack(CondaBuildPack):
|
|||
def detect(self):
|
||||
"""Check if current repo should be built with the Python buildpack."""
|
||||
requirements_txt = self.binder_path("requirements.txt")
|
||||
runtime_txt = self.binder_path("runtime.txt")
|
||||
setup_py = "setup.py"
|
||||
|
||||
if os.path.exists(runtime_txt):
|
||||
with open(runtime_txt) as f:
|
||||
runtime = f.read().strip()
|
||||
if runtime.startswith("python-"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
if self.runtime_info[0]:
|
||||
return self.runtime_info[0] == "python"
|
||||
if not self.binder_dir and os.path.exists(setup_py):
|
||||
return True
|
||||
return os.path.exists(requirements_txt)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import datetime
|
||||
import os
|
||||
import re
|
||||
import warnings
|
||||
from functools import lru_cache
|
||||
|
||||
import requests
|
||||
|
@ -49,7 +49,14 @@ class RBuildPack(PythonBuildPack):
|
|||
def runtime(self):
|
||||
"""
|
||||
Return contents of runtime.txt if it exists, '' otherwise
|
||||
|
||||
Deprecated, use `runtime_info` instead.
|
||||
"""
|
||||
warnings.warn(
|
||||
"`{self.__class__.__name__}.runtime` is deprecated. Use `runtime_info` instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
if not hasattr(self, "_runtime"):
|
||||
runtime_path = self.binder_path("runtime.txt")
|
||||
try:
|
||||
|
@ -90,11 +97,11 @@ class RBuildPack(PythonBuildPack):
|
|||
r_version = version_map["4.2"]
|
||||
|
||||
if not hasattr(self, "_r_version"):
|
||||
parts = self.runtime.split("-")
|
||||
runtime, version, date = self.runtime_info
|
||||
# If runtime.txt is not set, or if it isn't of the form r-<version>-<yyyy>-<mm>-<dd>,
|
||||
# we don't use any of it in determining r version and just use the default
|
||||
if len(parts) == 5:
|
||||
r_version = parts[1]
|
||||
if version and date:
|
||||
r_version = version
|
||||
# For versions of form x.y, we want to explicitly provide x.y.z - latest patchlevel
|
||||
# available. Users can however explicitly specify the full version to get something specific
|
||||
if r_version in version_map:
|
||||
|
@ -116,15 +123,11 @@ class RBuildPack(PythonBuildPack):
|
|||
Returns '' if no date is specified
|
||||
"""
|
||||
if not hasattr(self, "_checkpoint_date"):
|
||||
match = re.match(r"r-(\d.\d(.\d)?-)?(\d\d\d\d)-(\d\d)-(\d\d)", self.runtime)
|
||||
if not match:
|
||||
self._checkpoint_date = False
|
||||
runtime, version, date = self.runtime_info
|
||||
if runtime == "r" and date:
|
||||
self._checkpoint_date = date
|
||||
else:
|
||||
# turn the last three groups of the match into a date
|
||||
self._checkpoint_date = datetime.date(
|
||||
*[int(s) for s in match.groups()[-3:]]
|
||||
)
|
||||
|
||||
self._checkpoint_date = False
|
||||
return self._checkpoint_date
|
||||
|
||||
def detect(self):
|
||||
|
@ -142,13 +145,9 @@ class RBuildPack(PythonBuildPack):
|
|||
|
||||
description_R = "DESCRIPTION"
|
||||
if not self.binder_dir and os.path.exists(description_R):
|
||||
if not self.checkpoint_date:
|
||||
# no R snapshot date set through runtime.txt
|
||||
# Set it to two days ago from today
|
||||
self._checkpoint_date = datetime.date.today() - datetime.timedelta(
|
||||
days=2
|
||||
)
|
||||
self._runtime = f"r-{str(self._checkpoint_date)}"
|
||||
# no R snapshot date set through runtime.txt
|
||||
# Set it to two days ago from today
|
||||
self._checkpoint_date = datetime.date.today() - datetime.timedelta(days=2)
|
||||
return True
|
||||
|
||||
@lru_cache
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
from datetime import date
|
||||
from os.path import join as pjoin
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
|
||||
from repo2docker.buildpacks import LegacyBinderDockerBuildPack, PythonBuildPack
|
||||
from repo2docker.buildpacks import (
|
||||
BaseImage,
|
||||
LegacyBinderDockerBuildPack,
|
||||
PythonBuildPack,
|
||||
)
|
||||
from repo2docker.utils import chdir
|
||||
|
||||
|
||||
|
@ -46,3 +51,25 @@ def test_unsupported_python(tmpdir, python_version, base_image):
|
|||
assert bp.python_version == python_version
|
||||
with pytest.raises(ValueError):
|
||||
bp.render()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"runtime_txt, expected",
|
||||
[
|
||||
(None, (None, None, None)),
|
||||
("", (None, None, None)),
|
||||
("abc", ("abc", None, None)),
|
||||
("abc-001", ("abc", "001", None)),
|
||||
("abc-001-2025-06-22", ("abc", "001", date(2025, 6, 22))),
|
||||
("a_b/c-0.0.1-2025-06-22", ("a_b/c", "0.0.1", date(2025, 6, 22))),
|
||||
],
|
||||
)
|
||||
def test_runtime_txt(tmpdir, runtime_txt, expected, base_image):
|
||||
tmpdir.chdir()
|
||||
|
||||
if runtime_txt is not None:
|
||||
with open("runtime.txt", "w") as f:
|
||||
f.write(runtime_txt)
|
||||
|
||||
base = BaseImage(base_image)
|
||||
assert base.runtime_info == expected
|
||||
|
|
Ładowanie…
Reference in New Issue