Skip to content

Commit

Permalink
feat: Added Chroma client upgrade check
Browse files Browse the repository at this point in the history
- __version__ is now exported at chromadb package level
- New check against pypi for latest chroma version (failures are ignored)

Refs: chroma-core#846
  • Loading branch information
tazarov committed Dec 15, 2023
1 parent c120b5a commit f1f774d
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 0 deletions.
1 change: 1 addition & 0 deletions chromadb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"UpdateCollectionMetadata",
"QueryResult",
"GetResult",
"__version__",
]

logger = logging.getLogger(__name__)
Expand Down
27 changes: 27 additions & 0 deletions chromadb/api/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import json
import logging
from typing import ClassVar, Dict, Optional, Sequence
from urllib import request
from uuid import UUID
import uuid

Expand Down Expand Up @@ -28,6 +31,9 @@
from chromadb.telemetry.product.events import ClientStartEvent
from chromadb.types import Database, Tenant, Where, WhereDocument
import chromadb.utils.embedding_functions as ef
from chromadb.utils.client_utils import compare_versions

logger = logging.getLogger(__name__)


class SharedSystemClient:
Expand Down Expand Up @@ -128,6 +134,7 @@ class Client(SharedSystemClient, ClientAPI):
_server: ServerAPI
# An internal admin client for verifying that databases and tenants exist
_admin_client: AdminAPI
_upgrade_check_url: str = "https://pypi.org/pypi/chromadb/json"

# region Initialization
def __init__(
Expand All @@ -137,6 +144,7 @@ def __init__(
settings: Settings = Settings(),
) -> None:
super().__init__(settings=settings)
self._upgrade_check()
self.tenant = tenant
self.database = database
# Create an admin client for verifying that databases and tenants exist
Expand Down Expand Up @@ -164,6 +172,25 @@ def from_system(

# endregion

def _upgrade_check(self) -> None:
"""Check pypi index for new version if possible."""
try:
data = json.load(
request.urlopen(request.Request(self._upgrade_check_url), timeout=5)
)
from chromadb import __version__ as local_chroma_version

latest_version = data["info"]["version"]
if compare_versions(latest_version, local_chroma_version) > 0:
logger.info(
f"\033[38;5;069m[notice]\033[0m A new release of chromadb is available: "
f"\033[38;5;196m{local_chroma_version}!\033[0m -> "
f"\033[38;5;082m{latest_version}\033[0m\n"
"\033[38;5;069m[notice]\033[0m To upgrade, run `pip install --upgrade chromadb`."
)
except Exception:
pass

# region BaseAPI Methods
# Note - we could do this in less verbose ways, but they break type checking
@override
Expand Down
70 changes: 70 additions & 0 deletions chromadb/test/client/test_client_upgrade.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import json
from unittest.mock import patch

import pytest
from pytest_httpserver import HTTPServer

import chromadb


def test_new_release_available(caplog: pytest.LogCaptureFixture) -> None:
with patch(
"chromadb.api.client.Client._upgrade_check_url",
new="http://localhost:8008/pypi/chromadb/json",
):
with HTTPServer(port=8008) as httpserver:
# Define the response
httpserver.expect_request("/pypi/chromadb/json").respond_with_data(
json.dumps({"info": {"version": "99.99.99"}})
)

# Your code that makes the HTTP call
chromadb.Client()

assert "A new release of chromadb is available" in caplog.text


def test_on_latest_release(caplog: pytest.LogCaptureFixture) -> None:
with HTTPServer(port=8008) as httpserver:
# Define the response
httpserver.expect_request("/pypi/chromadb/json").respond_with_data(
json.dumps({"info": {"version": chromadb.__version__}})
)

# Your code that makes the HTTP call
chromadb.Client()

assert "A new release of chromadb is available" not in caplog.text


def test_local_version_newer_than_latest(caplog: pytest.LogCaptureFixture) -> None:
with patch(
"chromadb.api.client.Client._upgrade_check_url",
new="http://localhost:8008/pypi/chromadb/json",
):
with HTTPServer(port=8008) as httpserver:
# Define the response
httpserver.expect_request("/pypi/chromadb/json").respond_with_data(
json.dumps({"info": {"version": "0.0.1"}})
)

# Your code that makes the HTTP call
chromadb.Client()

assert "A new release of chromadb is available" not in caplog.text


def test_pypi_unavailable(caplog: pytest.LogCaptureFixture) -> None:
with patch(
"chromadb.api.client.Client._upgrade_check_url",
new="http://localhost:8008/pypi/chromadb/json",
):
with HTTPServer(port=8009) as httpserver:
# Define the response
httpserver.expect_request("/pypi/chromadb/json").respond_with_data(
json.dumps({"info": {"version": "99.99.99"}})
)

chromadb.Client()

assert "A new release of chromadb is available" not in caplog.text
19 changes: 19 additions & 0 deletions chromadb/utils/client_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
def compare_versions(version1: str, version2: str) -> int:
"""Compares two versions of the format X.Y.Z and returns 1 if version1 is greater than version2, -1 if version1 is
less than version2, and 0 if version1 is equal to version2.
"""
v1_components = list(map(int, version1.split(".")))
v2_components = list(map(int, version2.split(".")))

for v1, v2 in zip(v1_components, v2_components):
if v1 > v2:
return 1
elif v1 < v2:
return -1

if len(v1_components) > len(v2_components):
return 1
elif len(v1_components) < len(v2_components):
return -1

return 0

0 comments on commit f1f774d

Please sign in to comment.