Use more sophisticated version parsing logic

This commit is contained in:
Ines Montani 2020-05-30 15:01:58 +02:00
parent bed62991ad
commit e47e5a4b10
6 changed files with 41 additions and 43 deletions

View File

@ -5,7 +5,7 @@ import sys
from wasabi import msg
from .. import about
from ..util import is_package
from ..util import is_package, get_base_version
def download(
@ -63,8 +63,7 @@ def get_json(url, desc):
def get_compatibility():
version = about.__version__
version = version.rsplit(".dev", 1)[0]
version = get_base_version(about.__version__)
comp_table = get_json(about.__compatibility__, "compatibility table")
comp = comp_table["spacy"]
if version not in comp:
@ -73,7 +72,7 @@ def get_compatibility():
def get_version(model, comp):
model = model.rsplit(".dev", 1)[0]
model = get_base_version(model)
if model not in comp:
msg.fail(
f"No compatible model found for '{model}' (spaCy v{about.__version__})",

View File

@ -90,7 +90,7 @@ def generate_meta(model_path, existing_meta, msg):
("license", "License", meta.get("license", "MIT")),
]
nlp = util.load_model_from_path(Path(model_path))
meta["spacy_version"] = about.__version__
meta["spacy_version"] = util.get_model_version_range(about.__version__)
meta["pipeline"] = nlp.pipe_names
meta["vectors"] = {
"width": nlp.vocab.vectors_length,
@ -138,7 +138,7 @@ def list_files(data_dir):
def list_requirements(meta):
parent_package = meta.get('parent_package', 'spacy')
requirements = [parent_package + '>=' + meta['spacy_version']]
requirements = [parent_package + meta['spacy_version']]
if 'setup_requires' in meta:
requirements += meta['setup_requires']
if 'requirements' in meta:

View File

@ -4,7 +4,7 @@ import requests
from wasabi import msg
from .. import about
from ..util import get_package_version, get_installed_models, split_version
from ..util import get_package_version, get_installed_models, get_base_version
from ..util import get_package_path, get_model_meta, is_compatible_model
@ -14,7 +14,7 @@ def validate():
with the installed models. Should be run after `pip install -U spacy`.
"""
model_pkgs, compat = get_model_pkgs()
spacy_version = about.__version__.rsplit(".dev", 1)[0]
spacy_version = get_base_version(about.__version__)
current_compat = compat.get(spacy_version, {})
if not current_compat:
msg.warn(f"No compatible models found for v{spacy_version} of spaCy")
@ -78,13 +78,12 @@ def get_model_pkgs():
version = get_package_version(pkg_name)
if package in compat:
is_compat = version in compat[package]
v_maj, v_min = split_version(about.__version__)
spacy_version = f"{v_maj}.{v_min}"
spacy_version = about.__version__
else:
model_path = get_package_path(package)
model_meta = get_model_meta(model_path)
is_compat = is_compatible_model(model_meta)
spacy_version = model_meta.get("spacy_version", "n/a")
is_compat = is_compatible_model(spacy_version)
pkgs[pkg_name] = {
"name": package,
"version": version,

View File

@ -191,13 +191,14 @@ class Language(object):
@property
def meta(self):
spacy_version = util.get_model_version_range(about.__version__)
if self.vocab.lang:
self._meta.setdefault("lang", self.vocab.lang)
else:
self._meta.setdefault("lang", self.lang)
self._meta.setdefault("name", "model")
self._meta.setdefault("version", "0.0.0")
self._meta.setdefault("spacy_version", about.__version__)
self._meta.setdefault("spacy_version", spacy_version)
self._meta.setdefault("description", "")
self._meta.setdefault("author", "")
self._meta.setdefault("email", "")

View File

@ -95,7 +95,15 @@ def test_ascii_filenames():
@pytest.mark.parametrize(
"version,compatible",
[(spacy_version, True), ("2.0.0", False), (">=1.2.3,<4.5.6", False)],
[
(spacy_version, True),
(f">={spacy_version}", True),
("2.0.0", False),
(">=2.0.0", True),
(">=1.0.0,<2.1.1", False),
(">=1.2.3,<4.5.6", True),
("n/a", None),
],
)
def test_is_compatible_model(version, compatible):
assert util.is_compatible_model({"spacy_version": version}) is compatible
assert util.is_compatible_model(version) is compatible

View File

@ -14,6 +14,8 @@ import srsly
import catalogue
import sys
import warnings
from packaging.specifiers import SpecifierSet, InvalidSpecifier
from packaging.version import Version, InvalidVersion
try:
@ -236,42 +238,31 @@ def get_package_version(name):
return None
def split_version(version):
"""RETURNS (tuple): Two integers, the major and minor spaCy version."""
pieces = version.split(".", 3)
return int(pieces[0]), int(pieces[1])
def is_compatible_model(meta):
"""Check if a model is compatible with the current version of spaCy, based
on its meta.json. We compare the version of spaCy the model was created with
with the current version. If the minor version is different, it's considered
incompatible.
meta (dict): The model's meta.
RETURNS (bool / None): Whether the model is compatible with the current
spaCy or None if we don't have enough info.
"""
cur_v = about.__version__
pkg_v = meta.get("spacy_version")
if not pkg_v or not isinstance(pkg_v, str):
def is_compatible_model(constraint):
version = Version(about.__version__)
if constraint[0].isdigit():
# Handle cases where exact version is provided as constraint
constraint = f"=={constraint}"
try:
spec = SpecifierSet(constraint)
except InvalidSpecifier:
return None
# Handle spacy_version values like >=x,<y, just in case
pkg_v = re.sub(r"[^0-9.]", "", pkg_v.split(",")[0])
cur_major, cur_minor = split_version(cur_v)
pkg_major, pkg_minor = split_version(pkg_v)
if cur_major != pkg_major or cur_minor != pkg_minor:
return False
return True
# Allow prereleases and dev versions
spec.prereleases = True
return version in spec
def get_model_version_range(version):
def get_model_version_range(spacy_version):
"""Generate a version range like >=1.2.3,<1.3.0 based on a given spaCy
version. Models are always compatible across patch versions but not
across minor or major versions.
"""
major, minor = split_version(version)
return f">={version},<{major}.{minor + 1}.0"
release = Version(spacy_version).release
return f">={spacy_version},<{release[0]}.{release[1] + 1}.0"
def get_base_version(version):
return Version(version).base_version
def load_config(path, create_objects=False):