diff --git a/PKG-INFO b/PKG-INFO index 1942719b08e3ebbf416fe0fa9e031538eebede0e..fdec0535197b444fdd81adc7545efc0e228c7d79 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: swh.model -Version: 2.3.0 +Version: 2.4.0 Summary: Software Heritage data model Home-page: https://forge.softwareheritage.org/diffusion/DMOD/ Author: Software Heritage developers @@ -38,4 +38,5 @@ Classifier: Development Status :: 5 - Production/Stable Requires-Python: >=3.7 Description-Content-Type: text/markdown Provides-Extra: cli +Provides-Extra: testing-minimal Provides-Extra: testing diff --git a/debian/changelog b/debian/changelog index ff4be10a8d6d3de4aa895592821410e80336a730..900d3c2c2083666f07176593c491dbf83d50e86f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,10 @@ -swh-model (2.3.0-1~swh1~bpo10+1) buster-swh; urgency=medium +swh-model (2.4.0-1~swh1) unstable-swh; urgency=medium - * Rebuild for buster-swh + * New upstream release 2.4.0 - (tagged by Antoine Lambert + <antoine.lambert@inria.fr> on 2021-04-13 15:26:51 +0200) + * Upstream changes: - version 2.4.0 - -- Software Heritage autobuilder (on jenkins-debian1) <jenkins@jenkins-debian1.internal.softwareheritage.org> Fri, 19 Mar 2021 16:18:56 +0000 + -- Software Heritage autobuilder (on jenkins-debian1) <jenkins@jenkins-debian1.internal.softwareheritage.org> Tue, 13 Apr 2021 13:31:21 +0000 swh-model (2.3.0-1~swh1) unstable-swh; urgency=medium diff --git a/docs/persistent-identifiers.rst b/docs/persistent-identifiers.rst index bb8078437632f3f0bb9b958aa0dfaf08a49c491c..2f20cf18b51dc5e05ab4f18e9c67d40f38330e18 100644 --- a/docs/persistent-identifiers.rst +++ b/docs/persistent-identifiers.rst @@ -300,7 +300,7 @@ third party. An implementation of SWHID that allows to do so locally is the `swh identify <https://docs.softwareheritage.org/devel/swh-model/cli.html>`_ tool, available from the `swh.model <https://pypi.org/project/swh.model/>`_ Python package under the GPL license. This package can be installed via the ``pip`` -package manager with the one liner ``pip3 install swh.model`` on any machine with +package manager with the one liner ``pip3 install swh.model[cli]`` on any machine with Python (at least version 3.7) and ``pip`` installed (on a Debian or Ubuntu system a simple ``apt install python3 python3-pip`` will suffice, see `the general instructions <https://packaging.python.org/tutorials/installing-packages/>`_ for other platforms). diff --git a/pytest.ini b/pytest.ini index 9fa2d75a39cb3c34a52416c06d11f80d11abb1fd..b15e082c739bb4a8d62e9b2be811a076339b9e0b 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,3 +3,4 @@ addopts = --doctest-modules -p no:pytest_swh_core norecursedirs = docs .* markers = fs: tests that involve filesystem ios + requires_optional_deps: tests in test_cli.py that should not run if optional dependencies are not installed diff --git a/requirements-test.txt b/requirements-test.txt index f906d8ae9a901078112c21d99324e3fca99cdb92..984743d9b5d31b47b5cda7114b22dd0218ee86fb 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,3 @@ Click -dulwich pytest pytz diff --git a/setup.py b/setup.py index 8f9d32fdcec61b2ac22ea9928cfe8ebabca8cd8a..a44dcbf60b851ec2b7936b2c43a341096f718d3d 100755 --- a/setup.py +++ b/setup.py @@ -54,6 +54,7 @@ setup( ), extras_require={ "cli": parse_requirements("cli"), + "testing-minimal": parse_requirements("test"), "testing": parse_requirements("test") + parse_requirements("cli"), }, include_package_data=True, diff --git a/swh.model.egg-info/PKG-INFO b/swh.model.egg-info/PKG-INFO index 1942719b08e3ebbf416fe0fa9e031538eebede0e..fdec0535197b444fdd81adc7545efc0e228c7d79 100644 --- a/swh.model.egg-info/PKG-INFO +++ b/swh.model.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: swh.model -Version: 2.3.0 +Version: 2.4.0 Summary: Software Heritage data model Home-page: https://forge.softwareheritage.org/diffusion/DMOD/ Author: Software Heritage developers @@ -38,4 +38,5 @@ Classifier: Development Status :: 5 - Production/Stable Requires-Python: >=3.7 Description-Content-Type: text/markdown Provides-Extra: cli +Provides-Extra: testing-minimal Provides-Extra: testing diff --git a/swh.model.egg-info/requires.txt b/swh.model.egg-info/requires.txt index 919e9687732fb52e32fa2fd42085d88933a043db..d478e8e380b52d3700261fa3e6a071bc015c4c61 100644 --- a/swh.model.egg-info/requires.txt +++ b/swh.model.egg-info/requires.txt @@ -16,9 +16,13 @@ dulwich [testing] Click -dulwich pytest pytz swh.core>=0.3 Click dulwich + +[testing-minimal] +Click +pytest +pytz diff --git a/swh/model/cli.py b/swh/model/cli.py index 8ac925079d9442cfa0b75afb13eae4bb43b019fe..effdf8a86334046840917a96a3debb954360f580 100644 --- a/swh/model/cli.py +++ b/swh/model/cli.py @@ -9,9 +9,22 @@ from typing import Dict, List, Optional # WARNING: do not import unnecessary things here to keep cli startup time under # control -import click +try: + import click +except ImportError: + print( + "Cannot run swh-identify; the Click package is not installed." + "Please install 'swh.model[cli]' for full functionality.", + file=sys.stderr, + ) + exit(1) + +try: + from swh.core.cli import swh as swh_cli_group +except ImportError: + # stub so that swh-identify can be used when swh-core isn't installed + swh_cli_group = click # type: ignore -from swh.core.cli import swh as swh_cli_group from swh.model.identifiers import CoreSWHID, ObjectType CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) @@ -96,7 +109,13 @@ def swhid_of_origin(url): def swhid_of_git_repo(path) -> CoreSWHID: - import dulwich.repo + try: + import dulwich.repo + except ImportError: + raise click.ClickException( + "Cannot compute snapshot identifier; the Dulwich package is not installed. " + "Please install 'swh.model[cli]' for full functionality.", + ) from swh.model import hashutil from swh.model.identifiers import snapshot_identifier diff --git a/swh/model/identifiers.py b/swh/model/identifiers.py index c9f20dcb7b52b83dbd13f42a633822f87c961222..563da9e6944f40a0db8909d69b62ef30c93f9b40 100644 --- a/swh/model/identifiers.py +++ b/swh/model/identifiers.py @@ -925,7 +925,7 @@ class _BaseSWHID(Generic[_TObjectType]): return cls(**parts) except ValueError as e: raise ValidationError( - "ValueError: %(args)", params={"args": e.args} + "ValueError: %(args)s", params={"args": e.args} ) from None @@ -996,7 +996,7 @@ def _parse_lines_qualifier( return (int(lines), None) except ValueError: raise ValidationError( - "Invalid format for the lines qualifier: %(lines)", params={"lines": lines} + "Invalid format for the lines qualifier: %(lines)s", params={"lines": lines} ) @@ -1152,7 +1152,7 @@ class QualifiedSWHID(_BaseSWHID[ObjectType]): invalid_qualifiers = set(qualifiers) - SWHID_QUALIFIERS if invalid_qualifiers: raise ValidationError( - "Invalid qualifier(s): %(qualifiers)", + "Invalid qualifier(s): %(qualifiers)s", params={"qualifiers": ", ".join(invalid_qualifiers)}, ) try: @@ -1228,13 +1228,13 @@ def _parse_swhid(swhid: str) -> Dict[str, Any]: if qualifiers_raw: for qualifier in qualifiers_raw.split(SWHID_CTXT_SEP): try: - k, v = qualifier.split("=") + k, v = qualifier.split("=", maxsplit=1) + parts["qualifiers"][k] = v except ValueError: raise ValidationError( "Invalid SWHID: invalid qualifier: %(qualifier)s", params={"qualifier": qualifier}, ) - parts["qualifiers"][k] = v parts["scheme_version"] = int(parts["scheme_version"]) parts["object_id"] = hash_to_bytes(parts["object_id"]) diff --git a/swh/model/tests/swh_model_data.py b/swh/model/tests/swh_model_data.py index c4700cba3475382deffb45160cbdf8a88dfb0176..cd0ef4321ce58b1ac5a8b45476bd378ea0386f05 100644 --- a/swh/model/tests/swh_model_data.py +++ b/swh/model/tests/swh_model_data.py @@ -152,6 +152,16 @@ RELEASES = [ message=b"foo", synthetic=False, ), + Release( + id=hash_to_bytes("ee4d20e80af850cc0f417d25dc5073792c5010d2"), + name=b"this-is-a/tag/1.0", + date=None, + author=None, + target_type=ObjectType.DIRECTORY, + target=b"\x05" * 20, + message=b"bar", + synthetic=False, + ), ] ORIGINS = [ diff --git a/swh/model/tests/test_cli.py b/swh/model/tests/test_cli.py index 9a006607f15d96be1917ca8a2cbc12aeb763f5f8..de0de4806c7fa2defb55bcbbc705adcbf610847f 100644 --- a/swh/model/tests/test_cli.py +++ b/swh/model/tests/test_cli.py @@ -4,9 +4,11 @@ # See top-level LICENSE file for more information import os +import sys import tarfile import tempfile import unittest +import unittest.mock from click.testing import CliRunner import pytest @@ -52,6 +54,7 @@ class TestIdentify(DataMixin, unittest.TestCase): result = self.runner.invoke(cli.identify, ["--type", "directory", path]) self.assertSWHID(result, "swh:1:dir:e8b0f1466af8608c8a3fb9879db172b887e80759") + @pytest.mark.requires_optional_deps def test_snapshot_id(self): """identify a snapshot""" tarball = os.path.join( @@ -68,6 +71,18 @@ class TestIdentify(DataMixin, unittest.TestCase): result, "swh:1:snp:abc888898124270905a0ef3c67e872ce08e7e0c1" ) + def test_snapshot_without_dulwich(self): + """checks swh-identify returns a 'nice' message instead of a traceback + when dulwich is not installed""" + with unittest.mock.patch.dict(sys.modules, {"dulwich": None}): + with tempfile.TemporaryDirectory(prefix="swh.model.cli") as d: + result = self.runner.invoke( + cli.identify, ["--type", "snapshot", d], catch_exceptions=False, + ) + + assert result.exit_code == 1 + assert "'swh.model[cli]'" in result.output + def test_origin_id(self): """identify an origin URL""" url = "https://github.com/torvalds/linux" diff --git a/swh/model/tests/test_identifiers.py b/swh/model/tests/test_identifiers.py index 3ba570867e6cd529166547978bd948068c3b4a5e..42934ba8d1dec7a022c460350de0d44cebc02eb8 100644 --- a/swh/model/tests/test_identifiers.py +++ b/swh/model/tests/test_identifiers.py @@ -1241,6 +1241,12 @@ VALID_SWHIDS = [ ), None, # Neither does ExtendedSWHID ), + ( + f"swh:1:cnt:{HASH};origin=https://github.com/python/cpython;lines=1-18/", + None, # likewise + None, + None, # likewise + ), ( f"swh:1:cnt:{HASH};origin=https://github.com/python/cpython;lines=18", None, # likewise @@ -1298,8 +1304,10 @@ def test_parse_unparse_swhids(string, core, qualified, extended): for (cls, parsed_swhid) in zip(classes, [core, qualified, extended]): if parsed_swhid is None: # This class should not accept this SWHID - with pytest.raises(ValidationError): + with pytest.raises(ValidationError) as excinfo: cls.from_string(string) + # Check string serialization for exception + assert str(excinfo.value) is not None else: # This class should assert cls.from_string(string) == parsed_swhid @@ -1508,6 +1516,14 @@ QUALIFIED_SWHIDS = [ origin="https://example.org/foo%3Bbar%25baz", ), ), + ( + f"swh:1:cnt:{HASH};origin=https://example.org?project=test", + QualifiedSWHID( + object_type=ObjectType.CONTENT, + object_id=_x(HASH), + origin="https://example.org?project=test", + ), + ), # visit: ( f"swh:1:cnt:{HASH};visit=swh:1:snp:{HASH}", @@ -1562,6 +1578,12 @@ QUALIFIED_SWHIDS = [ object_type=ObjectType.CONTENT, object_id=_x(HASH), path=b"/foo%bar" ), ), + ( + f"swh:1:cnt:{HASH};path=/foo/bar%3Dbaz", + QualifiedSWHID( + object_type=ObjectType.CONTENT, object_id=_x(HASH), path=b"/foo/bar=baz" + ), + ), # lines ( f"swh:1:cnt:{HASH};lines=1-18", diff --git a/tox.ini b/tox.ini index bffcdfafd6924e3f97839d387b9611ab495ebaff..25baa11db70079f1d41f3123066b763f44ed0024 100644 --- a/tox.ini +++ b/tox.ini @@ -1,16 +1,25 @@ [tox] -envlist=black,flake8,mypy,py3 +envlist=black,flake8,mypy,py3-{minimal,full} [testenv] extras = - testing + full: testing + minimal: testing-minimal deps = pytest-cov commands = pytest --cov={envsitepackagesdir}/swh/model \ --doctest-modules \ - {envsitepackagesdir}/swh/model \ - --cov-branch {posargs} + full: {envsitepackagesdir}/swh/model \ + minimal: {envsitepackagesdir}/swh/model/tests/test_cli.py -m 'not requires_optional_deps' \ + --cov-branch {posargs} + +[testenv:py3] +skip_install = true +deps = tox +commands = + tox -e py3-full -- {posargs} + tox -e py3-minimal -- {posargs} [testenv:black] skip_install = true