Add warning for loose version constraints (#5536)

* Add warning for loose version constraints

* Update wording [ci skip]

* Tweak error message

Co-authored-by: Matthew Honnibal <honnibal+gh@gmail.com>
This commit is contained in:
Ines Montani 2020-06-05 03:42:15 -07:00 committed by GitHub
parent 8411d4f4e6
commit d93cbeb14f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 63 additions and 7 deletions

View File

@ -113,6 +113,12 @@ class Warnings(object):
"ignored during training.") "ignored during training.")
# TODO: fix numbering after merging develop into master # TODO: fix numbering after merging develop into master
W094 = ("Model '{model}' ({model_version}) specifies an under-constrained "
"spaCy version requirement: {version}. This can lead to compatibility "
"problems with older versions, or as new spaCy versions are "
"released, because the model may say it's compatible when it's "
'not. Consider changing the "spacy_version" in your meta.json to a '
"version range, with a lower and upper pin. For example: {example}")
W095 = ("Model '{model}' ({model_version}) requires spaCy {version} and is " W095 = ("Model '{model}' ({model_version}) requires spaCy {version} and is "
"incompatible with the current version ({current}). This may lead " "incompatible with the current version ({current}). This may lead "
"to unexpected results or runtime errors. To resolve this, " "to unexpected results or runtime errors. To resolve this, "

View File

@ -109,3 +109,21 @@ def test_ascii_filenames():
) )
def test_is_compatible_version(version, constraint, compatible): def test_is_compatible_version(version, constraint, compatible):
assert util.is_compatible_version(version, constraint) is compatible assert util.is_compatible_version(version, constraint) is compatible
@pytest.mark.parametrize(
"constraint,expected",
[
("3.0.0", False),
("==3.0.0", False),
(">=2.3.0", True),
(">2.0.0", True),
("<=2.0.0", True),
(">2.0.0,<3.0.0", False),
(">=2.0.0,<3.0.0", False),
("!=1.1,>=1.0,~=1.0", True),
("n/a", None),
],
)
def test_is_unconstrained_version(constraint, expected):
assert util.is_unconstrained_version(constraint) is expected

View File

@ -264,6 +264,31 @@ def is_compatible_version(version, constraint, prereleases=True):
return version in spec return version in spec
def is_unconstrained_version(constraint, prereleases=True):
# We have an exact version, this is the ultimate constrained version
if constraint[0].isdigit():
return False
try:
spec = SpecifierSet(constraint)
except InvalidSpecifier:
return None
spec.prereleases = prereleases
specs = [sp for sp in spec]
# We only have one version spec and it defines > or >=
if len(specs) == 1 and specs[0].operator in (">", ">="):
return True
# One specifier is exact version
if any(sp.operator in ("==") for sp in specs):
return False
has_upper = any(sp.operator in ("<", "<=") for sp in specs)
has_lower = any(sp.operator in (">", ">=") for sp in specs)
# We have a version spec that defines an upper and lower bound
if has_upper and has_lower:
return False
# Everything else, like only an upper version, only a lower version etc.
return True
def get_model_version_range(spacy_version): def get_model_version_range(spacy_version):
"""Generate a version range like >=1.2.3,<1.3.0 based on a given spaCy """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 version. Models are always compatible across patch versions but not
@ -334,14 +359,21 @@ def get_model_meta(path):
raise ValueError(Errors.E054.format(setting=setting)) raise ValueError(Errors.E054.format(setting=setting))
if "spacy_version" in meta: if "spacy_version" in meta:
if not is_compatible_version(about.__version__, meta["spacy_version"]): if not is_compatible_version(about.__version__, meta["spacy_version"]):
warnings.warn( warn_msg = Warnings.W095.format(
Warnings.W095.format(
model=f"{meta['lang']}_{meta['name']}", model=f"{meta['lang']}_{meta['name']}",
model_version=meta["version"], model_version=meta["version"],
version=meta["spacy_version"], version=meta["spacy_version"],
current=about.__version__, current=about.__version__,
) )
warnings.warn(warn_msg)
if is_unconstrained_version(meta["spacy_version"]):
warn_msg = Warnings.W094.format(
model=f"{meta['lang']}_{meta['name']}",
model_version=meta["version"],
version=meta["spacy_version"],
example=get_model_version_range(about.__version__),
) )
warnings.warn(warn_msg)
return meta return meta