import os
import re
import subprocess
from dallinger.version import __version__ as dallinger_version
from yaspin import yaspin
# Bump versions by changing these two lines.
dallinger_minimum_version = "10.0.1"
psynet_version = "11.1.0"
dallinger_minimum_version_parts = dallinger_minimum_version.split(".")
dallinger_maximum_major_version = int(dallinger_minimum_version_parts[0]) + 1
dallinger_maximum_version = f"{dallinger_maximum_major_version}.0.0"
dallinger_version_requirement = (
f"dallinger>={dallinger_minimum_version}, <{dallinger_maximum_version}"
)
[docs]
def check_versions():
"Check whether the PsyNet and Dallinger versions installed locally match the ones specified in the requirements.txt file."
if os.environ.get("SKIP_VERSION_CHECK"):
print(
"SKIP_VERSION_CHECK is set so we will skip checking PsyNet versions specified vs. installed."
)
return
with yaspin(
text="Checking whether PsyNet and Dallinger versions specified and installed are the same...",
color="green",
) as spinner:
with open("requirements.txt", "r") as file:
versions = get_all_version_infos(file.read())
for package_name, version_infos in versions.items():
consistent = version_infos["consistent"]
if consistent:
spinner.ok("✔")
else:
spinner.color = "red"
spinner.fail("✗")
assert consistent, (
f"The {package_name} versions installed on your local computer and specified in requirements.txt do not match.\n"
f'\nVersion installed locally: {version_infos["installed"]}'
f'\nVersion specified in requirements.txt: {version_infos["specified"]}'
"\n\nYou can skip this check by writing `export SKIP_VERSION_CHECK=1` (without quotes) in your terminal."
)
def get_all_version_infos(file_content):
versions = {}
for package_name in ["Dallinger", "PsyNet"]:
versions[package_name] = {
"specified": None,
"installed": None,
"consistent": True,
}
specified = None
installed = None
requirements = [
line for line in file_content.splitlines() if package_name.lower() in line
]
if len(requirements) == 0:
continue
requirement = requirements[0]
match = re.search(
f"/{package_name}(?:\\.git)?@([^#]+)(?:#egg={package_name})?",
requirement,
re.IGNORECASE,
)
# We either assume PsyNet is specified in the correct requirement syntax
# or as a standard requirement (e.g. 'psynet==10.0.0')
specified = (
match.group(1) if match is not None else re.split("==", requirement)[-1]
)
# In case just specified as the package name
if specified == package_name.lower():
continue
if specified_using_version(specified):
if specified.startswith("v"):
specified = specified[1:]
# Get installed version via the Dallinger/PsyNet API
installed = installed_version_for(package_name)
else:
# Get installed version from `pip freeze`
installed = commit_hash_or_version_from_pip_freeze(package_name)
consistent = specified is None or specified == installed
versions[package_name] = {
"specified": specified,
"installed": installed,
"consistent": consistent,
}
return versions
def specified_using_version(specified):
return (
specified.startswith("v")
or re.search(r"^\d+\.\d+\.\d+$", specified) is not None
)
def installed_version_for(package_name):
import psynet
if package_name == "Dallinger":
return dallinger_version
if package_name == "PsyNet":
return psynet.__version__
raise ValueError(f"Unsupported package '{package_name}'")
def get_pip_freeze_requirement(name):
pip_freeze_stdout = subprocess.run(
["pip freeze"],
shell=True,
capture_output=True,
).stdout
return [
line.decode()
for line in pip_freeze_stdout.splitlines()
if f"{name}" in line.decode()
][0]
def commit_hash_or_version_from_pip_freeze(package_name):
line = get_pip_freeze_requirement(package_name)
match = re.search(f".*{package_name}(?:\\.git)?@([^#]*)", line, re.IGNORECASE)
if match is not None:
return match.group(1)
return line.split("==")[-1]
def parse_version(x):
parts = x.split(".")
assert len(parts) == 3, f"Invalid version specifier: {x}"
major, minor, patch = parts
# Strip anything that comes after a letter, so that e.g. 9.4.0a1 -> "9.4.0"
patch = re.sub("[a-zA-Z].*", "", patch)
return int(major), int(minor), int(patch)
[docs]
def version_is_greater(x, y, strict: bool = True):
"""
Returns True if version number x is (strictly) greater than version number y.
"""
x_parsed = parse_version(x)
y_parsed = parse_version(y)
for x_i, y_i in zip(x_parsed, y_parsed):
if x_i < y_i:
return False
elif x_i > y_i:
return True
if strict:
return False
else:
return True
def check_dallinger_version():
import dallinger
current_dallinger_version = dallinger.version.__version__
environment_variable = "SKIP_CHECK_DALLINGER_VERSION"
if not os.environ.get(environment_variable, None):
if not (
version_is_greater(
current_dallinger_version, dallinger_minimum_version, strict=False
)
and version_is_greater(
dallinger_maximum_version, current_dallinger_version, strict=True
)
):
raise ValueError(
f"The current installed version of Dallinger ({current_dallinger_version}) "
f"is incompatible with PsyNet's requirements ({dallinger_version_requirement}). "
"Please install an appropriate version of Dallinger, or (only if you know what you're doing!) "
f"disable this check by setting the environment variable {environment_variable} to a non-empty string."
)