Skip to content
This repository has been archived by the owner on Nov 14, 2023. It is now read-only.

Commit

Permalink
Fix version checks in SDK (cvat-ai#5991)
Browse files Browse the repository at this point in the history
- Added missing compatibility with micro- and smaller releases
  • Loading branch information
zhiltsov-max authored and mikhail-treskin committed Jul 1, 2023
1 parent 0b39906 commit e47700b
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Escaping in the `filter` parameter in generated URLs
(<https://github.com/opencv/cvat/issues/5566>)
- Rotation property lost during saving a mutable attribute (<https://github.com/opencv/cvat/pull/5968>)
<<<<<<< HEAD
- Incorrect calculation of working time in analytics (<https://github.com/opencv/cvat/pull/5973>)
=======
- Server micro version support check in SDK/CLI (<https://github.com/opencv/cvat/pull/5991>)
>>>>>>> 0c853b15b (Fix version checks in SDK (#5991))
### Security
- TDB
Expand Down
21 changes: 13 additions & 8 deletions cvat-sdk/cvat_sdk/core/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from typing import Any, Dict, Iterator, Optional, Sequence, Tuple, TypeVar

import attrs
import packaging.specifiers as specifiers
import packaging.version as pv
import platformdirs
import urllib3
Expand Down Expand Up @@ -253,25 +254,29 @@ def check_server_version(self, fail_if_unsupported: Optional[bool] = None) -> No
raise IncompatibleVersionException(msg)
return

sdk_version = pv.Version(VERSION)

# We only check base version match. Micro releases and fixes do not affect
# API compatibility in general.
if all(
server_version.base_version != sv.base_version for sv in self.SUPPORTED_SERVER_VERSIONS
if not any(
self._is_version_compatible(server_version, supported_version)
for supported_version in self.SUPPORTED_SERVER_VERSIONS
):
msg = (
"Server version '%s' is not compatible with SDK version '%s'. "
"Some SDK functions may not work properly with this server. "
"You can continue using this SDK, or you can "
"try to update with 'pip install cvat-sdk'."
) % (server_version, sdk_version)
) % (server_version, pv.Version(VERSION))
self.logger.warning(msg)
if fail_if_unsupported:
raise IncompatibleVersionException(msg)

def _is_version_compatible(self, current: pv.Version, target: pv.Version) -> bool:
# Check for (major, minor) compatibility.
# Micro releases and fixes do not affect API compatibility in general.
epoch = f"{target.epoch}!" if target.epoch else "" # 1.0 ~= 0!1.0 is false
return current in specifiers.Specifier(
f"~= {epoch}{target.major}.{target.minor}.{target.micro}"
)

def get_server_version(self) -> pv.Version:
# TODO: allow to use this endpoint unauthorized
(about, _) = self.api_client.server_api.retrieve_about()
return pv.Version(about.version)

Expand Down
47 changes: 46 additions & 1 deletion tests/python/sdk/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import io
from contextlib import ExitStack
from logging import Logger
from typing import Tuple
from typing import List, Tuple

import packaging.version as pv
import pytest
Expand Down Expand Up @@ -160,6 +160,51 @@ def mocked_version(_):
assert "Server version '0' is not compatible with SDK version" in logger_stream.getvalue()


@pytest.mark.parametrize(
"server_version, supported_versions, expect_supported",
[
# Currently, it is ~=, as defined in https://peps.python.org/pep-0440/
("3.2", ["2.0"], False),
("2", ["2.1"], False),
("2.1", ["2.1"], True),
("2.1a", ["2.1"], False),
("2.1.post1", ["2.1"], True),
("2.1", ["2.1.pre1"], True),
("2.1.1", ["2.1"], True),
("2.2", ["2.1"], False),
("2.2", ["2.1.0", "2.3"], False),
("2.2", ["2.1", "2.2", "2.3"], True),
("2.2.post1", ["2.1", "2.2", "2.3"], True),
("2.2.pre1", ["2.1", "2.2", "2.3"], False),
("2.2", ["2.3"], False),
("2.1.0.dev123", ["2.1.post2"], False),
("1!1.3", ["2.1"], False),
("1!1.3.1", ["2.1", "1!1.3"], True),
("1!1.1.dev12", ["1!1.1"], False),
],
)
def test_can_check_server_version_compatibility(
fxt_logger: Tuple[Logger, io.StringIO],
monkeypatch: pytest.MonkeyPatch,
server_version: str,
supported_versions: List[str],
expect_supported: bool,
):
logger, _ = fxt_logger

monkeypatch.setattr(Client, "get_server_version", lambda _: pv.Version(server_version))
monkeypatch.setattr(
Client, "SUPPORTED_SERVER_VERSIONS", [pv.Version(v) for v in supported_versions]
)
config = Config(allow_unsupported_server=False)

with ExitStack() as es:
if not expect_supported:
es.enter_context(pytest.raises(IncompatibleVersionException))

Client(url=BASE_URL, logger=logger, config=config, check_server_version=True)


@pytest.mark.parametrize("verify", [True, False])
def test_can_control_ssl_verification_with_config(verify: bool):
config = Config(verify_ssl=verify)
Expand Down

0 comments on commit e47700b

Please sign in to comment.